diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/hwmon/ina230.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'drivers/hwmon/ina230.c')
-rw-r--r-- | drivers/hwmon/ina230.c | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/drivers/hwmon/ina230.c b/drivers/hwmon/ina230.c new file mode 100644 index 00000000000..3591a946310 --- /dev/null +++ b/drivers/hwmon/ina230.c | |||
@@ -0,0 +1,561 @@ | |||
1 | /* | ||
2 | * ina230.c - driver for TI INA230 current / power monitor sensor | ||
3 | * (also compatible with TI INA226) | ||
4 | * | ||
5 | * | ||
6 | * Copyright (c) 2011, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; version 2 of the License. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
15 | * more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License along | ||
18 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
20 | * | ||
21 | * | ||
22 | * The INA230(/INA226) is a sensor chip made by Texas Instruments. It measures | ||
23 | * power, voltage and current on a power rail and provides an alert on | ||
24 | * over voltage/power | ||
25 | * Complete datasheet can be obtained from ti.com | ||
26 | * | ||
27 | */ | ||
28 | |||
29 | #include <linux/module.h> | ||
30 | #include <linux/kernel.h> | ||
31 | #include <linux/spinlock.h> | ||
32 | #include <linux/sysfs.h> | ||
33 | #include <linux/kobject.h> | ||
34 | #include <linux/hrtimer.h> | ||
35 | #include <linux/slab.h> | ||
36 | #include <linux/interrupt.h> | ||
37 | #include <linux/mutex.h> | ||
38 | #include <linux/i2c.h> | ||
39 | #include <linux/slab.h> | ||
40 | #include <linux/err.h> | ||
41 | #include <linux/gpio.h> | ||
42 | #include <linux/device.h> | ||
43 | #include <linux/sysdev.h> | ||
44 | #include <linux/platform_data/ina230.h> | ||
45 | #include <linux/init.h> | ||
46 | #include <linux/hwmon-sysfs.h> | ||
47 | #include <linux/hwmon.h> | ||
48 | #include <linux/cpu.h> | ||
49 | |||
50 | |||
51 | #define DRIVER_NAME "ina230" | ||
52 | #define MEASURE_BUS_VOLT 0 | ||
53 | |||
54 | /* ina230 (/ ina226)register offsets */ | ||
55 | #define INA230_CONFIG 0 | ||
56 | #define INA230_SHUNT 1 | ||
57 | #define INA230_VOLTAGE 2 | ||
58 | #define INA230_POWER 3 | ||
59 | #define INA230_CURRENT 4 | ||
60 | #define INA230_CAL 5 | ||
61 | #define INA230_MASK 6 | ||
62 | #define INA230_ALERT 7 | ||
63 | |||
64 | /* | ||
65 | Config register for ina230 (/ ina226): | ||
66 | D15|D14 D13 D12|D11 D10 D09|D08 D07 D06|D05 D04 D03|D02 D01 D00 | ||
67 | rst|- - - |AVG |Vbus_CT |Vsh_CT |MODE | ||
68 | */ | ||
69 | #define INA230_RESET (1 << 15) | ||
70 | #define INA230_AVG (0 << 9) /* 0 Averages */ | ||
71 | #define INA230_VBUS_CT (0 << 6) /* Vbus 140us conversion time */ | ||
72 | #define INA230_VSH_CT (0 << 3) /* Vshunt 140us conversion time */ | ||
73 | |||
74 | #if MEASURE_BUS_VOLT | ||
75 | #define INA230_CONT_MODE 5 /* Continuous Shunt measurement */ | ||
76 | #define INA230_TRIG_MODE 1 /* Triggered Shunt measurement */ | ||
77 | #else | ||
78 | #define INA230_CONT_MODE 7 /* Continuous Bus and shunt measure */ | ||
79 | #define INA230_TRIG_MODE 3 /* Triggered Bus and shunt measure */ | ||
80 | #endif | ||
81 | |||
82 | #define INA230_POWER_DOWN 0 | ||
83 | #define INA230_CONT_CONFIG (INA230_AVG | INA230_VBUS_CT | \ | ||
84 | INA230_VSH_CT | INA230_CONT_MODE) | ||
85 | #define INA230_TRIG_CONFIG (INA230_AVG | INA230_VBUS_CT | \ | ||
86 | INA230_VSH_CT | INA230_TRIG_MODE) | ||
87 | |||
88 | /* | ||
89 | Mask register for ina230 (/ina 226): | ||
90 | D15|D14|D13|D12|D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 | ||
91 | SOL|SUL|BOL|BUL|POL|CVR|- - - - - |AFF|CVF|OVF|APO|LEN | ||
92 | */ | ||
93 | #define INA230_MASK_SOL (1 << 15) | ||
94 | #define INA230_MASK_SUL (1 << 14) | ||
95 | |||
96 | |||
97 | struct ina230_data { | ||
98 | struct device *hwmon_dev; | ||
99 | struct i2c_client *client; | ||
100 | struct ina230_platform_data *pdata; | ||
101 | struct mutex mutex; | ||
102 | bool running; | ||
103 | struct notifier_block nb; | ||
104 | }; | ||
105 | |||
106 | |||
107 | /* bus voltage resolution: 1.25mv */ | ||
108 | #define busv_register_to_mv(x) (((x) * 5) >> 2) | ||
109 | |||
110 | /* shunt voltage resolution: 2.5uv */ | ||
111 | #define shuntv_register_to_uv(x) (((x) * 5) >> 1) | ||
112 | #define uv_to_alert_register(x) (((x) << 1) / 5) | ||
113 | |||
114 | |||
115 | |||
116 | static s32 ensure_enabled_start(struct i2c_client *client) | ||
117 | { | ||
118 | struct ina230_data *data = i2c_get_clientdata(client); | ||
119 | int retval; | ||
120 | |||
121 | if (data->running) | ||
122 | return 0; | ||
123 | |||
124 | retval = i2c_smbus_write_word_data(client, INA230_CONFIG, | ||
125 | __constant_cpu_to_be16(INA230_TRIG_CONFIG)); | ||
126 | if (retval < 0) | ||
127 | dev_err(&client->dev, "config data write failed sts: 0x%x\n", | ||
128 | retval); | ||
129 | |||
130 | return retval; | ||
131 | } | ||
132 | |||
133 | |||
134 | static void ensure_enabled_end(struct i2c_client *client) | ||
135 | { | ||
136 | struct ina230_data *data = i2c_get_clientdata(client); | ||
137 | int retval; | ||
138 | |||
139 | if (data->running) | ||
140 | return; | ||
141 | |||
142 | retval = i2c_smbus_write_word_data(client, INA230_CONFIG, | ||
143 | __constant_cpu_to_be16(INA230_POWER_DOWN)); | ||
144 | if (retval < 0) | ||
145 | dev_err(&client->dev, "power down failure sts: 0x%x\n", | ||
146 | retval); | ||
147 | } | ||
148 | |||
149 | |||
150 | static s32 __locked_power_down_ina230(struct i2c_client *client) | ||
151 | { | ||
152 | s32 retval; | ||
153 | struct ina230_data *data = i2c_get_clientdata(client); | ||
154 | |||
155 | if (!data->running) | ||
156 | return 0; | ||
157 | |||
158 | retval = i2c_smbus_write_word_data(client, INA230_MASK, 0); | ||
159 | if (retval < 0) | ||
160 | dev_err(&client->dev, "mask write failure sts: 0x%x\n", | ||
161 | retval); | ||
162 | |||
163 | retval = i2c_smbus_write_word_data(client, INA230_CONFIG, | ||
164 | __constant_cpu_to_be16(INA230_POWER_DOWN)); | ||
165 | if (retval < 0) | ||
166 | dev_err(&client->dev, "power down failure sts: 0x%x\n", | ||
167 | retval); | ||
168 | |||
169 | data->running = false; | ||
170 | |||
171 | return retval; | ||
172 | } | ||
173 | |||
174 | |||
175 | static s32 power_down_ina230(struct i2c_client *client) | ||
176 | { | ||
177 | s32 retval; | ||
178 | struct ina230_data *data = i2c_get_clientdata(client); | ||
179 | |||
180 | mutex_lock(&data->mutex); | ||
181 | retval = __locked_power_down_ina230(client); | ||
182 | mutex_unlock(&data->mutex); | ||
183 | |||
184 | return retval; | ||
185 | } | ||
186 | |||
187 | |||
188 | static s32 __locked_start_current_mon(struct i2c_client *client) | ||
189 | { | ||
190 | s32 retval; | ||
191 | s16 shunt_limit; | ||
192 | s16 alert_mask; | ||
193 | struct ina230_data *data = i2c_get_clientdata(client); | ||
194 | |||
195 | if (!data->pdata->current_threshold) { | ||
196 | dev_err(&client->dev, "no current threshold specified\n"); | ||
197 | return -EINVAL; | ||
198 | } | ||
199 | |||
200 | retval = i2c_smbus_write_word_data(client, INA230_CONFIG, | ||
201 | __constant_cpu_to_be16(INA230_CONT_CONFIG)); | ||
202 | if (retval < 0) { | ||
203 | dev_err(&client->dev, "config data write failed sts: 0x%x\n", | ||
204 | retval); | ||
205 | return retval; | ||
206 | } | ||
207 | |||
208 | shunt_limit = uv_to_alert_register(data->pdata->resistor * | ||
209 | data->pdata->current_threshold); | ||
210 | |||
211 | retval = i2c_smbus_write_word_data(client, INA230_ALERT, | ||
212 | cpu_to_be16(shunt_limit)); | ||
213 | if (retval < 0) { | ||
214 | dev_err(&client->dev, "alert data write failed sts: 0x%x\n", | ||
215 | retval); | ||
216 | return retval; | ||
217 | } | ||
218 | |||
219 | alert_mask = shunt_limit >= 0 ? INA230_MASK_SOL : INA230_MASK_SUL; | ||
220 | retval = i2c_smbus_write_word_data(client, INA230_MASK, | ||
221 | cpu_to_be16(alert_mask)); | ||
222 | if (retval < 0) { | ||
223 | dev_err(&client->dev, "mask data write failed sts: 0x%x\n", | ||
224 | retval); | ||
225 | return retval; | ||
226 | } | ||
227 | |||
228 | data->running = true; | ||
229 | |||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | |||
234 | static void __locked_evaluate_state(struct i2c_client *client) | ||
235 | { | ||
236 | struct ina230_data *data = i2c_get_clientdata(client); | ||
237 | int cpus = num_online_cpus(); | ||
238 | |||
239 | if (data->running) { | ||
240 | if (cpus < data->pdata->min_cores_online || | ||
241 | !data->pdata->current_threshold) | ||
242 | __locked_power_down_ina230(client); | ||
243 | } else { | ||
244 | if (cpus >= data->pdata->min_cores_online && | ||
245 | data->pdata->current_threshold) | ||
246 | __locked_start_current_mon(client); | ||
247 | } | ||
248 | } | ||
249 | |||
250 | |||
251 | static void evaluate_state(struct i2c_client *client) | ||
252 | { | ||
253 | struct ina230_data *data = i2c_get_clientdata(client); | ||
254 | |||
255 | mutex_lock(&data->mutex); | ||
256 | __locked_evaluate_state(client); | ||
257 | mutex_unlock(&data->mutex); | ||
258 | } | ||
259 | |||
260 | |||
261 | static s32 show_rail_name(struct device *dev, | ||
262 | struct device_attribute *attr, | ||
263 | char *buf) | ||
264 | { | ||
265 | struct i2c_client *client = to_i2c_client(dev); | ||
266 | struct ina230_data *data = i2c_get_clientdata(client); | ||
267 | return sprintf(buf, "%s\n", data->pdata->rail_name); | ||
268 | } | ||
269 | |||
270 | |||
271 | static s32 show_current_threshold(struct device *dev, | ||
272 | struct device_attribute *attr, | ||
273 | char *buf) | ||
274 | { | ||
275 | struct i2c_client *client = to_i2c_client(dev); | ||
276 | struct ina230_data *data = i2c_get_clientdata(client); | ||
277 | return sprintf(buf, "%d mA\n", data->pdata->current_threshold); | ||
278 | } | ||
279 | |||
280 | |||
281 | static s32 set_current_threshold(struct device *dev, | ||
282 | struct device_attribute *attr, | ||
283 | const char *buf, size_t count) | ||
284 | { | ||
285 | struct i2c_client *client = to_i2c_client(dev); | ||
286 | struct ina230_data *data = i2c_get_clientdata(client); | ||
287 | s32 retval; | ||
288 | |||
289 | mutex_lock(&data->mutex); | ||
290 | |||
291 | if (strict_strtol(buf, 10, (long *)&(data->pdata->current_threshold))) { | ||
292 | retval = -EINVAL; | ||
293 | goto out; | ||
294 | } | ||
295 | |||
296 | if (data->pdata->current_threshold) { | ||
297 | if (data->running) { | ||
298 | /* force restart */ | ||
299 | retval = __locked_start_current_mon(client); | ||
300 | } else { | ||
301 | __locked_evaluate_state(client); | ||
302 | retval = 0; | ||
303 | } | ||
304 | } else { | ||
305 | retval = __locked_power_down_ina230(client); | ||
306 | } | ||
307 | |||
308 | out: | ||
309 | mutex_unlock(&data->mutex); | ||
310 | if (retval >= 0) | ||
311 | return count; | ||
312 | return retval; | ||
313 | } | ||
314 | |||
315 | |||
316 | |||
317 | |||
318 | #if MEASURE_BUS_VOLT | ||
319 | static s32 show_bus_voltage(struct device *dev, | ||
320 | struct device_attribute *attr, | ||
321 | char *buf) | ||
322 | { | ||
323 | struct i2c_client *client = to_i2c_client(dev); | ||
324 | struct ina230_data *data = i2c_get_clientdata(client); | ||
325 | s32 voltage_mV; | ||
326 | int retval; | ||
327 | |||
328 | mutex_lock(&data->mutex); | ||
329 | retval = ensure_enabled_start(client); | ||
330 | if (retval < 0) { | ||
331 | mutex_unlock(&data->mutex); | ||
332 | return retval; | ||
333 | } | ||
334 | |||
335 | /* getting voltage readings in milli volts*/ | ||
336 | voltage_mV = | ||
337 | (s16)be16_to_cpu(i2c_smbus_read_word_data(client, | ||
338 | INA230_VOLTAGE)); | ||
339 | |||
340 | ensure_enabled_end(client); | ||
341 | mutex_unlock(&data->mutex); | ||
342 | |||
343 | if (voltage_mV < 0) { | ||
344 | dev_err(dev, "%s: failed\n", __func__); | ||
345 | return -1; | ||
346 | } | ||
347 | |||
348 | voltage_mV = busv_register_to_mv(voltage_mV); | ||
349 | |||
350 | return sprintf(buf, "%d mV\n", voltage_mV); | ||
351 | } | ||
352 | #endif | ||
353 | |||
354 | |||
355 | |||
356 | |||
357 | static s32 show_shunt_voltage(struct device *dev, | ||
358 | struct device_attribute *attr, | ||
359 | char *buf) | ||
360 | { | ||
361 | struct i2c_client *client = to_i2c_client(dev); | ||
362 | struct ina230_data *data = i2c_get_clientdata(client); | ||
363 | s32 voltage_uV; | ||
364 | int retval; | ||
365 | |||
366 | mutex_lock(&data->mutex); | ||
367 | retval = ensure_enabled_start(client); | ||
368 | if (retval < 0) { | ||
369 | mutex_unlock(&data->mutex); | ||
370 | return retval; | ||
371 | } | ||
372 | |||
373 | voltage_uV = | ||
374 | (s16)be16_to_cpu(i2c_smbus_read_word_data(client, | ||
375 | INA230_SHUNT)); | ||
376 | |||
377 | ensure_enabled_end(client); | ||
378 | mutex_unlock(&data->mutex); | ||
379 | |||
380 | voltage_uV = shuntv_register_to_uv(voltage_uV); | ||
381 | |||
382 | return sprintf(buf, "%d uV\n", voltage_uV); | ||
383 | } | ||
384 | |||
385 | |||
386 | static s32 show_current(struct device *dev, | ||
387 | struct device_attribute *attr, | ||
388 | char *buf) | ||
389 | { | ||
390 | struct i2c_client *client = to_i2c_client(dev); | ||
391 | struct ina230_data *data = i2c_get_clientdata(client); | ||
392 | s32 voltage_uV; | ||
393 | s32 current_mA; | ||
394 | int retval; | ||
395 | |||
396 | mutex_lock(&data->mutex); | ||
397 | retval = ensure_enabled_start(client); | ||
398 | if (retval < 0) { | ||
399 | mutex_unlock(&data->mutex); | ||
400 | return retval; | ||
401 | } | ||
402 | |||
403 | voltage_uV = | ||
404 | (s16)be16_to_cpu(i2c_smbus_read_word_data(client, | ||
405 | INA230_SHUNT)); | ||
406 | |||
407 | ensure_enabled_end(client); | ||
408 | mutex_unlock(&data->mutex); | ||
409 | |||
410 | voltage_uV = shuntv_register_to_uv(voltage_uV); | ||
411 | current_mA = voltage_uV / data->pdata->resistor; | ||
412 | |||
413 | return sprintf(buf, "%d mA\n", current_mA); | ||
414 | } | ||
415 | |||
416 | |||
417 | static int ina230_hotplug_notify(struct notifier_block *nb, unsigned long event, | ||
418 | void *hcpu) | ||
419 | { | ||
420 | struct ina230_data *data = container_of(nb, struct ina230_data, | ||
421 | nb); | ||
422 | struct i2c_client *client = data->client; | ||
423 | |||
424 | if (event == CPU_ONLINE || event == CPU_DEAD) | ||
425 | evaluate_state(client); | ||
426 | |||
427 | return 0; | ||
428 | } | ||
429 | |||
430 | |||
431 | |||
432 | static struct sensor_device_attribute ina230[] = { | ||
433 | SENSOR_ATTR(rail_name, S_IRUGO, show_rail_name, NULL, 0), | ||
434 | SENSOR_ATTR(current_threshold, S_IWUSR | S_IRUGO, | ||
435 | show_current_threshold, set_current_threshold, 0), | ||
436 | SENSOR_ATTR(shuntvolt1_input, S_IRUGO, show_shunt_voltage, NULL, 0), | ||
437 | SENSOR_ATTR(current1_input, S_IRUGO, show_current, NULL, 0), | ||
438 | #if MEASURE_BUS_VOLT | ||
439 | SENSOR_ATTR(busvolt1_input, S_IRUGO, show_bus_voltage, NULL, 0), | ||
440 | #endif | ||
441 | }; | ||
442 | |||
443 | |||
444 | static int __devinit ina230_probe(struct i2c_client *client, | ||
445 | const struct i2c_device_id *id) | ||
446 | { | ||
447 | struct ina230_data *data; | ||
448 | int err; | ||
449 | u8 i; | ||
450 | |||
451 | data = devm_kzalloc(&client->dev, sizeof(struct ina230_data), | ||
452 | GFP_KERNEL); | ||
453 | if (!data) { | ||
454 | err = -ENOMEM; | ||
455 | goto exit; | ||
456 | } | ||
457 | |||
458 | i2c_set_clientdata(client, data); | ||
459 | data->pdata = client->dev.platform_data; | ||
460 | data->running = false; | ||
461 | data->nb.notifier_call = ina230_hotplug_notify; | ||
462 | data->client = client; | ||
463 | mutex_init(&data->mutex); | ||
464 | |||
465 | err = i2c_smbus_write_word_data(client, INA230_CONFIG, | ||
466 | __constant_cpu_to_be16(INA230_RESET)); | ||
467 | if (err < 0) { | ||
468 | dev_err(&client->dev, "ina230 reset failure status: 0x%x\n", | ||
469 | err); | ||
470 | goto exit; | ||
471 | } | ||
472 | |||
473 | for (i = 0; i < ARRAY_SIZE(ina230); i++) { | ||
474 | err = device_create_file(&client->dev, &ina230[i].dev_attr); | ||
475 | if (err) { | ||
476 | dev_err(&client->dev, "device_create_file failed.\n"); | ||
477 | goto exit_remove; | ||
478 | } | ||
479 | } | ||
480 | |||
481 | data->hwmon_dev = hwmon_device_register(&client->dev); | ||
482 | if (IS_ERR(data->hwmon_dev)) { | ||
483 | err = PTR_ERR(data->hwmon_dev); | ||
484 | goto exit_remove; | ||
485 | } | ||
486 | |||
487 | register_hotcpu_notifier(&(data->nb)); | ||
488 | |||
489 | evaluate_state(client); | ||
490 | |||
491 | return 0; | ||
492 | |||
493 | exit_remove: | ||
494 | for (i = 0; i < ARRAY_SIZE(ina230); i++) | ||
495 | device_remove_file(&client->dev, &ina230[i].dev_attr); | ||
496 | exit: | ||
497 | return err; | ||
498 | } | ||
499 | |||
500 | |||
501 | static int __devexit ina230_remove(struct i2c_client *client) | ||
502 | { | ||
503 | u8 i; | ||
504 | struct ina230_data *data = i2c_get_clientdata(client); | ||
505 | unregister_hotcpu_notifier(&(data->nb)); | ||
506 | power_down_ina230(client); | ||
507 | hwmon_device_unregister(data->hwmon_dev); | ||
508 | for (i = 0; i < ARRAY_SIZE(ina230); i++) | ||
509 | device_remove_file(&client->dev, &ina230[i].dev_attr); | ||
510 | return 0; | ||
511 | } | ||
512 | |||
513 | |||
514 | static int ina230_suspend(struct i2c_client *client) | ||
515 | { | ||
516 | return power_down_ina230(client); | ||
517 | } | ||
518 | |||
519 | |||
520 | static int ina230_resume(struct i2c_client *client) | ||
521 | { | ||
522 | evaluate_state(client); | ||
523 | return 0; | ||
524 | } | ||
525 | |||
526 | |||
527 | static const struct i2c_device_id ina230_id[] = { | ||
528 | {DRIVER_NAME, 0 }, | ||
529 | {} | ||
530 | }; | ||
531 | MODULE_DEVICE_TABLE(i2c, ina230_id); | ||
532 | |||
533 | |||
534 | static struct i2c_driver ina230_driver = { | ||
535 | .class = I2C_CLASS_HWMON, | ||
536 | .driver = { | ||
537 | .name = DRIVER_NAME, | ||
538 | }, | ||
539 | .probe = ina230_probe, | ||
540 | .remove = __devexit_p(ina230_remove), | ||
541 | .suspend = ina230_suspend, | ||
542 | .resume = ina230_resume, | ||
543 | .id_table = ina230_id, | ||
544 | }; | ||
545 | |||
546 | |||
547 | static int __init ina230_init(void) | ||
548 | { | ||
549 | return i2c_add_driver(&ina230_driver); | ||
550 | } | ||
551 | |||
552 | |||
553 | static void __exit ina230_exit(void) | ||
554 | { | ||
555 | i2c_del_driver(&ina230_driver); | ||
556 | } | ||
557 | |||
558 | |||
559 | module_init(ina230_init); | ||
560 | module_exit(ina230_exit); | ||
561 | MODULE_LICENSE("GPL"); | ||