]>
Commit | Line | Data |
---|---|---|
c3ff9a67 | 1 | /* |
ddfb41ca | 2 | * Hardware monitoring driver for LTC2978 and LTC3880 |
c3ff9a67 GR |
3 | * |
4 | * Copyright (c) 2011 Ericsson AB. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
19 | */ | |
20 | ||
21 | #include <linux/kernel.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/init.h> | |
24 | #include <linux/err.h> | |
25 | #include <linux/slab.h> | |
26 | #include <linux/i2c.h> | |
27 | #include "pmbus.h" | |
28 | ||
ddfb41ca | 29 | enum chips { ltc2978, ltc3880 }; |
c3ff9a67 | 30 | |
ddfb41ca | 31 | /* LTC2978 and LTC3880 */ |
c3ff9a67 GR |
32 | #define LTC2978_MFR_VOUT_PEAK 0xdd |
33 | #define LTC2978_MFR_VIN_PEAK 0xde | |
34 | #define LTC2978_MFR_TEMPERATURE_PEAK 0xdf | |
35 | #define LTC2978_MFR_SPECIAL_ID 0xe7 | |
36 | ||
ddfb41ca | 37 | /* LTC2978 only */ |
c3ff9a67 GR |
38 | #define LTC2978_MFR_VOUT_MIN 0xfb |
39 | #define LTC2978_MFR_VIN_MIN 0xfc | |
40 | #define LTC2978_MFR_TEMPERATURE_MIN 0xfd | |
41 | ||
ddfb41ca GR |
42 | /* LTC3880 only */ |
43 | #define LTC3880_MFR_IOUT_PEAK 0xd7 | |
44 | #define LTC3880_MFR_CLEAR_PEAKS 0xe3 | |
45 | #define LTC3880_MFR_TEMPERATURE2_PEAK 0xf4 | |
46 | ||
c3ff9a67 GR |
47 | #define LTC2978_ID_REV1 0x0121 |
48 | #define LTC2978_ID_REV2 0x0122 | |
ddfb41ca GR |
49 | #define LTC3880_ID 0x4000 |
50 | #define LTC3880_ID_MASK 0xff00 | |
c3ff9a67 GR |
51 | |
52 | /* | |
53 | * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which | |
54 | * happens pretty much each time chip data is updated. Raw peak data therefore | |
55 | * does not provide much value. To be able to provide useful peak data, keep an | |
56 | * internal cache of measured peak data, which is only cleared if an explicit | |
57 | * "clear peak" command is executed for the sensor in question. | |
58 | */ | |
59 | struct ltc2978_data { | |
60 | enum chips id; | |
61 | int vin_min, vin_max; | |
62 | int temp_min, temp_max; | |
63 | int vout_min[8], vout_max[8]; | |
ddfb41ca GR |
64 | int iout_max[2]; |
65 | int temp2_max[2]; | |
c3ff9a67 GR |
66 | struct pmbus_driver_info info; |
67 | }; | |
68 | ||
69 | #define to_ltc2978_data(x) container_of(x, struct ltc2978_data, info) | |
70 | ||
71 | static inline int lin11_to_val(int data) | |
72 | { | |
73 | s16 e = ((s16)data) >> 11; | |
74 | s32 m = (((s16)(data << 5)) >> 5); | |
75 | ||
76 | /* | |
77 | * mantissa is 10 bit + sign, exponent adds up to 15 bit. | |
78 | * Add 6 bit to exponent for maximum accuracy (10 + 15 + 6 = 31). | |
79 | */ | |
80 | e += 6; | |
81 | return (e < 0 ? m >> -e : m << e); | |
82 | } | |
83 | ||
ddfb41ca GR |
84 | static int ltc2978_read_word_data_common(struct i2c_client *client, int page, |
85 | int reg) | |
c3ff9a67 GR |
86 | { |
87 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
88 | struct ltc2978_data *data = to_ltc2978_data(info); | |
89 | int ret; | |
90 | ||
91 | switch (reg) { | |
92 | case PMBUS_VIRT_READ_VIN_MAX: | |
93 | ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_PEAK); | |
94 | if (ret >= 0) { | |
95 | if (lin11_to_val(ret) > lin11_to_val(data->vin_max)) | |
96 | data->vin_max = ret; | |
97 | ret = data->vin_max; | |
98 | } | |
99 | break; | |
100 | case PMBUS_VIRT_READ_VOUT_MAX: | |
101 | ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_PEAK); | |
102 | if (ret >= 0) { | |
103 | /* | |
104 | * VOUT is 16 bit unsigned with fixed exponent, | |
105 | * so we can compare it directly | |
106 | */ | |
107 | if (ret > data->vout_max[page]) | |
108 | data->vout_max[page] = ret; | |
109 | ret = data->vout_max[page]; | |
110 | } | |
111 | break; | |
112 | case PMBUS_VIRT_READ_TEMP_MAX: | |
113 | ret = pmbus_read_word_data(client, page, | |
114 | LTC2978_MFR_TEMPERATURE_PEAK); | |
115 | if (ret >= 0) { | |
116 | if (lin11_to_val(ret) > lin11_to_val(data->temp_max)) | |
117 | data->temp_max = ret; | |
118 | ret = data->temp_max; | |
119 | } | |
120 | break; | |
ddfb41ca GR |
121 | case PMBUS_VIRT_RESET_VOUT_HISTORY: |
122 | case PMBUS_VIRT_RESET_VIN_HISTORY: | |
123 | case PMBUS_VIRT_RESET_TEMP_HISTORY: | |
124 | ret = 0; | |
125 | break; | |
126 | default: | |
127 | ret = -ENODATA; | |
128 | break; | |
129 | } | |
130 | return ret; | |
131 | } | |
132 | ||
133 | static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg) | |
134 | { | |
135 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
136 | struct ltc2978_data *data = to_ltc2978_data(info); | |
137 | int ret; | |
138 | ||
139 | switch (reg) { | |
c3ff9a67 GR |
140 | case PMBUS_VIRT_READ_VIN_MIN: |
141 | ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_MIN); | |
142 | if (ret >= 0) { | |
143 | if (lin11_to_val(ret) < lin11_to_val(data->vin_min)) | |
144 | data->vin_min = ret; | |
145 | ret = data->vin_min; | |
146 | } | |
147 | break; | |
148 | case PMBUS_VIRT_READ_VOUT_MIN: | |
149 | ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_MIN); | |
150 | if (ret >= 0) { | |
151 | /* | |
152 | * VOUT_MIN is known to not be supported on some lots | |
153 | * of LTC2978 revision 1, and will return the maximum | |
154 | * possible voltage if read. If VOUT_MAX is valid and | |
155 | * lower than the reading of VOUT_MIN, use it instead. | |
156 | */ | |
157 | if (data->vout_max[page] && ret > data->vout_max[page]) | |
158 | ret = data->vout_max[page]; | |
159 | if (ret < data->vout_min[page]) | |
160 | data->vout_min[page] = ret; | |
161 | ret = data->vout_min[page]; | |
162 | } | |
163 | break; | |
164 | case PMBUS_VIRT_READ_TEMP_MIN: | |
165 | ret = pmbus_read_word_data(client, page, | |
166 | LTC2978_MFR_TEMPERATURE_MIN); | |
167 | if (ret >= 0) { | |
168 | if (lin11_to_val(ret) | |
169 | < lin11_to_val(data->temp_min)) | |
170 | data->temp_min = ret; | |
171 | ret = data->temp_min; | |
172 | } | |
173 | break; | |
ddfb41ca GR |
174 | case PMBUS_VIRT_READ_IOUT_MAX: |
175 | case PMBUS_VIRT_RESET_IOUT_HISTORY: | |
176 | case PMBUS_VIRT_READ_TEMP2_MAX: | |
177 | case PMBUS_VIRT_RESET_TEMP2_HISTORY: | |
178 | ret = -ENXIO; | |
179 | break; | |
180 | default: | |
181 | ret = ltc2978_read_word_data_common(client, page, reg); | |
182 | break; | |
183 | } | |
184 | return ret; | |
185 | } | |
186 | ||
187 | static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg) | |
188 | { | |
189 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
190 | struct ltc2978_data *data = to_ltc2978_data(info); | |
191 | int ret; | |
192 | ||
193 | switch (reg) { | |
194 | case PMBUS_VIRT_READ_IOUT_MAX: | |
195 | ret = pmbus_read_word_data(client, page, LTC3880_MFR_IOUT_PEAK); | |
196 | if (ret >= 0) { | |
197 | if (lin11_to_val(ret) | |
198 | > lin11_to_val(data->iout_max[page])) | |
199 | data->iout_max[page] = ret; | |
200 | ret = data->iout_max[page]; | |
201 | } | |
202 | break; | |
203 | case PMBUS_VIRT_READ_TEMP2_MAX: | |
204 | ret = pmbus_read_word_data(client, page, | |
205 | LTC3880_MFR_TEMPERATURE2_PEAK); | |
206 | if (ret >= 0) { | |
207 | if (lin11_to_val(ret) | |
208 | > lin11_to_val(data->temp2_max[page])) | |
209 | data->temp2_max[page] = ret; | |
210 | ret = data->temp2_max[page]; | |
211 | } | |
212 | break; | |
213 | case PMBUS_VIRT_READ_VIN_MIN: | |
214 | case PMBUS_VIRT_READ_VOUT_MIN: | |
215 | case PMBUS_VIRT_READ_TEMP_MIN: | |
216 | ret = -ENXIO; | |
217 | break; | |
218 | case PMBUS_VIRT_RESET_IOUT_HISTORY: | |
219 | case PMBUS_VIRT_RESET_TEMP2_HISTORY: | |
c3ff9a67 GR |
220 | ret = 0; |
221 | break; | |
222 | default: | |
ddfb41ca | 223 | ret = ltc2978_read_word_data_common(client, page, reg); |
c3ff9a67 GR |
224 | break; |
225 | } | |
226 | return ret; | |
227 | } | |
228 | ||
ddfb41ca GR |
229 | static int ltc2978_clear_peaks(struct i2c_client *client, int page, |
230 | enum chips id) | |
231 | { | |
232 | int ret; | |
233 | ||
234 | if (id == ltc2978) | |
235 | ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); | |
236 | else | |
237 | ret = pmbus_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS); | |
238 | ||
239 | return ret; | |
240 | } | |
241 | ||
c3ff9a67 GR |
242 | static int ltc2978_write_word_data(struct i2c_client *client, int page, |
243 | int reg, u16 word) | |
244 | { | |
245 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
246 | struct ltc2978_data *data = to_ltc2978_data(info); | |
247 | int ret; | |
248 | ||
249 | switch (reg) { | |
ddfb41ca GR |
250 | case PMBUS_VIRT_RESET_IOUT_HISTORY: |
251 | data->iout_max[page] = 0x7fff; | |
252 | ret = ltc2978_clear_peaks(client, page, data->id); | |
253 | break; | |
254 | case PMBUS_VIRT_RESET_TEMP2_HISTORY: | |
255 | data->temp2_max[page] = 0x7fff; | |
256 | ret = ltc2978_clear_peaks(client, page, data->id); | |
257 | break; | |
c3ff9a67 GR |
258 | case PMBUS_VIRT_RESET_VOUT_HISTORY: |
259 | data->vout_min[page] = 0xffff; | |
260 | data->vout_max[page] = 0; | |
ddfb41ca | 261 | ret = ltc2978_clear_peaks(client, page, data->id); |
c3ff9a67 GR |
262 | break; |
263 | case PMBUS_VIRT_RESET_VIN_HISTORY: | |
264 | data->vin_min = 0x7bff; | |
265 | data->vin_max = 0; | |
ddfb41ca | 266 | ret = ltc2978_clear_peaks(client, page, data->id); |
c3ff9a67 GR |
267 | break; |
268 | case PMBUS_VIRT_RESET_TEMP_HISTORY: | |
269 | data->temp_min = 0x7bff; | |
270 | data->temp_max = 0x7fff; | |
ddfb41ca | 271 | ret = ltc2978_clear_peaks(client, page, data->id); |
c3ff9a67 GR |
272 | break; |
273 | default: | |
274 | ret = -ENODATA; | |
275 | break; | |
276 | } | |
277 | return ret; | |
278 | } | |
279 | ||
280 | static const struct i2c_device_id ltc2978_id[] = { | |
281 | {"ltc2978", ltc2978}, | |
ddfb41ca | 282 | {"ltc3880", ltc3880}, |
c3ff9a67 GR |
283 | {} |
284 | }; | |
285 | MODULE_DEVICE_TABLE(i2c, ltc2978_id); | |
286 | ||
287 | static int ltc2978_probe(struct i2c_client *client, | |
288 | const struct i2c_device_id *id) | |
289 | { | |
290 | int chip_id, ret, i; | |
291 | struct ltc2978_data *data; | |
292 | struct pmbus_driver_info *info; | |
293 | ||
294 | if (!i2c_check_functionality(client->adapter, | |
295 | I2C_FUNC_SMBUS_READ_WORD_DATA)) | |
296 | return -ENODEV; | |
297 | ||
298 | data = kzalloc(sizeof(struct ltc2978_data), GFP_KERNEL); | |
299 | if (!data) | |
300 | return -ENOMEM; | |
301 | ||
302 | chip_id = i2c_smbus_read_word_data(client, LTC2978_MFR_SPECIAL_ID); | |
303 | if (chip_id < 0) { | |
304 | ret = chip_id; | |
305 | goto err_mem; | |
306 | } | |
307 | ||
308 | if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) { | |
309 | data->id = ltc2978; | |
ddfb41ca GR |
310 | } else if ((chip_id & LTC3880_ID_MASK) == LTC3880_ID) { |
311 | data->id = ltc3880; | |
c3ff9a67 GR |
312 | } else { |
313 | dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id); | |
314 | ret = -ENODEV; | |
315 | goto err_mem; | |
316 | } | |
317 | if (data->id != id->driver_data) | |
318 | dev_warn(&client->dev, | |
319 | "Device mismatch: Configured %s, detected %s\n", | |
320 | id->name, | |
321 | ltc2978_id[data->id].name); | |
322 | ||
323 | info = &data->info; | |
c3ff9a67 GR |
324 | info->write_word_data = ltc2978_write_word_data; |
325 | ||
326 | data->vout_min[0] = 0xffff; | |
327 | data->vin_min = 0x7bff; | |
328 | data->temp_min = 0x7bff; | |
329 | data->temp_max = 0x7fff; | |
330 | ||
331 | switch (id->driver_data) { | |
332 | case ltc2978: | |
ddfb41ca | 333 | info->read_word_data = ltc2978_read_word_data; |
c3ff9a67 GR |
334 | info->pages = 8; |
335 | info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | |
336 | | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | |
337 | | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; | |
338 | for (i = 1; i < 8; i++) { | |
339 | info->func[i] = PMBUS_HAVE_VOUT | |
340 | | PMBUS_HAVE_STATUS_VOUT; | |
341 | data->vout_min[i] = 0xffff; | |
342 | } | |
343 | break; | |
ddfb41ca GR |
344 | case ltc3880: |
345 | info->read_word_data = ltc3880_read_word_data; | |
346 | info->pages = 2; | |
347 | info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | |
348 | | PMBUS_HAVE_STATUS_INPUT | |
349 | | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | |
350 | | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | |
351 | | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | |
352 | | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; | |
353 | info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | |
354 | | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | |
355 | | PMBUS_HAVE_POUT | |
356 | | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; | |
357 | data->vout_min[1] = 0xffff; | |
358 | break; | |
c3ff9a67 GR |
359 | default: |
360 | ret = -ENODEV; | |
361 | goto err_mem; | |
362 | } | |
363 | ||
364 | ret = pmbus_do_probe(client, id, info); | |
365 | if (ret) | |
366 | goto err_mem; | |
367 | return 0; | |
368 | ||
369 | err_mem: | |
370 | kfree(data); | |
371 | return ret; | |
372 | } | |
373 | ||
374 | static int ltc2978_remove(struct i2c_client *client) | |
375 | { | |
376 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
377 | const struct ltc2978_data *data = to_ltc2978_data(info); | |
378 | ||
379 | pmbus_do_remove(client); | |
380 | kfree(data); | |
381 | return 0; | |
382 | } | |
383 | ||
384 | /* This is the driver that will be inserted */ | |
385 | static struct i2c_driver ltc2978_driver = { | |
386 | .driver = { | |
387 | .name = "ltc2978", | |
388 | }, | |
389 | .probe = ltc2978_probe, | |
390 | .remove = ltc2978_remove, | |
391 | .id_table = ltc2978_id, | |
392 | }; | |
393 | ||
394 | static int __init ltc2978_init(void) | |
395 | { | |
396 | return i2c_add_driver(<c2978_driver); | |
397 | } | |
398 | ||
399 | static void __exit ltc2978_exit(void) | |
400 | { | |
401 | i2c_del_driver(<c2978_driver); | |
402 | } | |
403 | ||
404 | MODULE_AUTHOR("Guenter Roeck"); | |
ddfb41ca | 405 | MODULE_DESCRIPTION("PMBus driver for LTC2978 and LTC3880"); |
c3ff9a67 GR |
406 | MODULE_LICENSE("GPL"); |
407 | module_init(ltc2978_init); | |
408 | module_exit(ltc2978_exit); |