aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/pmbus/ltc3815.c
blob: b83a18a583643ac2a3b3f45cb868aa41605e03f8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Hardware monitoring driver for LTC3815
 *
 * Copyright (c) 2015 Linear Technology
 * Copyright (c) 2015 Guenter Roeck
 */

#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "pmbus.h"

#define LTC3815_MFR_IOUT_PEAK	0xd7
#define LTC3815_MFR_VOUT_PEAK	0xdd
#define LTC3815_MFR_VIN_PEAK	0xde
#define LTC3815_MFR_TEMP_PEAK	0xdf
#define LTC3815_MFR_IIN_PEAK	0xe1
#define LTC3815_MFR_SPECIAL_ID	0xe7

#define LTC3815_ID		0x8000
#define LTC3815_ID_MASK		0xff00

static int ltc3815_read_byte_data(struct i2c_client *client, int page, int reg)
{
	int ret;

	switch (reg) {
	case PMBUS_VOUT_MODE:
		/*
		 * The chip returns 0x3e, suggesting VID mode with manufacturer
		 * specific VID codes. Since the output voltage is reported
		 * with a LSB of 0.5mV, override and report direct mode with
		 * appropriate coefficients.
		 */
		ret = 0x40;
		break;
	default:
		ret = -ENODATA;
		break;
	}
	return ret;
}

static int ltc3815_write_byte(struct i2c_client *client, int page, u8 reg)
{
	int ret;

	switch (reg) {
	case PMBUS_CLEAR_FAULTS:
		/*
		 * LTC3815 does not support the CLEAR_FAULTS command.
		 * Emulate it by clearing the status register.
		 */
		ret = pmbus_read_word_data(client, 0, PMBUS_STATUS_WORD);
		if (ret > 0) {
			pmbus_write_word_data(client, 0, PMBUS_STATUS_WORD,
					      ret);
			ret = 0;
		}
		break;
	default:
		ret = -ENODATA;
		break;
	}
	return ret;
}

static int ltc3815_read_word_data(struct i2c_client *client, int page, int reg)
{
	int ret;

	switch (reg) {
	case PMBUS_VIRT_READ_VIN_MAX:
		ret = pmbus_read_word_data(client, page, LTC3815_MFR_VIN_PEAK);
		break;
	case PMBUS_VIRT_READ_VOUT_MAX:
		ret = pmbus_read_word_data(client, page, LTC3815_MFR_VOUT_PEAK);
		break;
	case PMBUS_VIRT_READ_TEMP_MAX:
		ret = pmbus_read_word_data(client, page, LTC3815_MFR_TEMP_PEAK);
		break;
	case PMBUS_VIRT_READ_IOUT_MAX:
		ret = pmbus_read_word_data(client, page, LTC3815_MFR_IOUT_PEAK);
		break;
	case PMBUS_VIRT_READ_IIN_MAX:
		ret = pmbus_read_word_data(client, page, LTC3815_MFR_IIN_PEAK);
		break;
	case PMBUS_VIRT_RESET_VOUT_HISTORY:
	case PMBUS_VIRT_RESET_VIN_HISTORY:
	case PMBUS_VIRT_RESET_TEMP_HISTORY:
	case PMBUS_VIRT_RESET_IOUT_HISTORY:
	case PMBUS_VIRT_RESET_IIN_HISTORY:
		ret = 0;
		break;
	default:
		ret = -ENODATA;
		break;
	}
	return ret;
}

static int ltc3815_write_word_data(struct i2c_client *client, int page,
				   int reg, u16 word)
{
	int ret;

	switch (reg) {
	case PMBUS_VIRT_RESET_IIN_HISTORY:
		ret = pmbus_write_word_data(client, page,
					    LTC3815_MFR_IIN_PEAK, 0);
		break;
	case PMBUS_VIRT_RESET_IOUT_HISTORY:
		ret = pmbus_write_word_data(client, page,
					    LTC3815_MFR_IOUT_PEAK, 0);
		break;
	case PMBUS_VIRT_RESET_VOUT_HISTORY:
		ret = pmbus_write_word_data(client, page,
					    LTC3815_MFR_VOUT_PEAK, 0);
		break;
	case PMBUS_VIRT_RESET_VIN_HISTORY:
		ret = pmbus_write_word_data(client, page,
					    LTC3815_MFR_VIN_PEAK, 0);
		break;
	case PMBUS_VIRT_RESET_TEMP_HISTORY:
		ret = pmbus_write_word_data(client, page,
					    LTC3815_MFR_TEMP_PEAK, 0);
		break;
	default:
		ret = -ENODATA;
		break;
	}
	return ret;
}

static const struct i2c_device_id ltc3815_id[] = {
	{"ltc3815", 0},
	{ }
};
MODULE_DEVICE_TABLE(i2c, ltc3815_id);

static struct pmbus_driver_info ltc3815_info = {
	.pages = 1,
	.format[PSC_VOLTAGE_IN] = direct,
	.format[PSC_VOLTAGE_OUT] = direct,
	.format[PSC_CURRENT_IN] = direct,
	.format[PSC_CURRENT_OUT] = direct,
	.format[PSC_TEMPERATURE] = direct,
	.m[PSC_VOLTAGE_IN] = 250,
	.b[PSC_VOLTAGE_IN] = 0,
	.R[PSC_VOLTAGE_IN] = 0,
	.m[PSC_VOLTAGE_OUT] = 2,
	.b[PSC_VOLTAGE_OUT] = 0,
	.R[PSC_VOLTAGE_OUT] = 3,
	.m[PSC_CURRENT_IN] = 1,
	.b[PSC_CURRENT_IN] = 0,
	.R[PSC_CURRENT_IN] = 2,
	.m[PSC_CURRENT_OUT] = 1,
	.b[PSC_CURRENT_OUT] = 0,
	.R[PSC_CURRENT_OUT] = 2,
	.m[PSC_TEMPERATURE] = 1,
	.b[PSC_TEMPERATURE] = 0,
	.R[PSC_TEMPERATURE] = 0,
	.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_VOUT |
		PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
	.read_byte_data = ltc3815_read_byte_data,
	.read_word_data = ltc3815_read_word_data,
	.write_byte = ltc3815_write_byte,
	.write_word_data = ltc3815_write_word_data,
};

static int ltc3815_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	int chip_id;

	if (!i2c_check_functionality(client->adapter,
				     I2C_FUNC_SMBUS_READ_WORD_DATA))
		return -ENODEV;

	chip_id = i2c_smbus_read_word_data(client, LTC3815_MFR_SPECIAL_ID);
	if (chip_id < 0)
		return chip_id;
	if ((chip_id & LTC3815_ID_MASK) != LTC3815_ID)
		return -ENODEV;

	return pmbus_do_probe(client, id, &ltc3815_info);
}

static struct i2c_driver ltc3815_driver = {
	.driver = {
		   .name = "ltc3815",
		   },
	.probe = ltc3815_probe,
	.remove = pmbus_do_remove,
	.id_table = ltc3815_id,
};

module_i2c_driver(ltc3815_driver);

MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for LTC3815");
MODULE_LICENSE("GPL");