diff options
author | Felten, Lothar <l-felten@ti.com> | 2012-05-12 04:36:38 -0400 |
---|---|---|
committer | Guenter Roeck <guenter.roeck@ericsson.com> | 2012-05-22 09:48:02 -0400 |
commit | f7c2fe386ae92b471a0edd4fa4bed7033224b9bf (patch) | |
tree | 8cb48fef02388eb5e64314aeb10143e5c20833cc /drivers/hwmon/ina2xx.c | |
parent | 9172b5d124c2f54374d8cc5ed6098ecd8fb988cd (diff) |
hwmon: INA219 and INA226 support
Add support for the Texas Instruments INA219 and INA226 power monitors.
Signed-off-by: Lothar Felten <l-felten@ti.com>
[guenter.roeck@ericsson.com: formatting cleanup; check for smbus word data;
select PGA=8 for INA219]
Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com>
Diffstat (limited to 'drivers/hwmon/ina2xx.c')
-rw-r--r-- | drivers/hwmon/ina2xx.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c new file mode 100644 index 000000000000..7f3f4a385729 --- /dev/null +++ b/drivers/hwmon/ina2xx.c | |||
@@ -0,0 +1,368 @@ | |||
1 | /* | ||
2 | * Driver for Texas Instruments INA219, INA226 power monitor chips | ||
3 | * | ||
4 | * INA219: | ||
5 | * Zero Drift Bi-Directional Current/Power Monitor with I2C Interface | ||
6 | * Datasheet: http://www.ti.com/product/ina219 | ||
7 | * | ||
8 | * INA226: | ||
9 | * Bi-Directional Current/Power Monitor with I2C Interface | ||
10 | * Datasheet: http://www.ti.com/product/ina226 | ||
11 | * | ||
12 | * Copyright (C) 2012 Lothar Felten <l-felten@ti.com> | ||
13 | * Thanks to Jan Volkering | ||
14 | * | ||
15 | * This program is free software; you can redistribute it and/or modify | ||
16 | * it under the terms of the GNU General Public License as published by | ||
17 | * the Free Software Foundation; version 2 of the License. | ||
18 | */ | ||
19 | |||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/err.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/i2c.h> | ||
26 | #include <linux/hwmon.h> | ||
27 | #include <linux/hwmon-sysfs.h> | ||
28 | |||
29 | #include <linux/platform_data/ina2xx.h> | ||
30 | |||
31 | /* common register definitions */ | ||
32 | #define INA2XX_CONFIG 0x00 | ||
33 | #define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ | ||
34 | #define INA2XX_BUS_VOLTAGE 0x02 /* readonly */ | ||
35 | #define INA2XX_POWER 0x03 /* readonly */ | ||
36 | #define INA2XX_CURRENT 0x04 /* readonly */ | ||
37 | #define INA2XX_CALIBRATION 0x05 | ||
38 | |||
39 | /* INA226 register definitions */ | ||
40 | #define INA226_MASK_ENABLE 0x06 | ||
41 | #define INA226_ALERT_LIMIT 0x07 | ||
42 | #define INA226_DIE_ID 0xFF | ||
43 | |||
44 | |||
45 | /* register count */ | ||
46 | #define INA219_REGISTERS 6 | ||
47 | #define INA226_REGISTERS 8 | ||
48 | |||
49 | #define INA2XX_MAX_REGISTERS 8 | ||
50 | |||
51 | /* settings - depend on use case */ | ||
52 | #define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */ | ||
53 | #define INA226_CONFIG_DEFAULT 0x4527 /* averages=16 */ | ||
54 | |||
55 | /* worst case is 68.10 ms (~14.6Hz, ina219) */ | ||
56 | #define INA2XX_CONVERSION_RATE 15 | ||
57 | |||
58 | enum ina2xx_ids { ina219, ina226 }; | ||
59 | |||
60 | struct ina2xx_data { | ||
61 | struct device *hwmon_dev; | ||
62 | |||
63 | struct mutex update_lock; | ||
64 | bool valid; | ||
65 | unsigned long last_updated; | ||
66 | |||
67 | int kind; | ||
68 | int registers; | ||
69 | u16 regs[INA2XX_MAX_REGISTERS]; | ||
70 | }; | ||
71 | |||
72 | int ina2xx_read_word(struct i2c_client *client, int reg) | ||
73 | { | ||
74 | int val = i2c_smbus_read_word_data(client, reg); | ||
75 | if (unlikely(val < 0)) { | ||
76 | dev_dbg(&client->dev, | ||
77 | "Failed to read register: %d\n", reg); | ||
78 | return val; | ||
79 | } | ||
80 | return be16_to_cpu(val); | ||
81 | } | ||
82 | |||
83 | void ina2xx_write_word(struct i2c_client *client, int reg, int data) | ||
84 | { | ||
85 | i2c_smbus_write_word_data(client, reg, cpu_to_be16(data)); | ||
86 | } | ||
87 | |||
88 | static struct ina2xx_data *ina2xx_update_device(struct device *dev) | ||
89 | { | ||
90 | struct i2c_client *client = to_i2c_client(dev); | ||
91 | struct ina2xx_data *data = i2c_get_clientdata(client); | ||
92 | struct ina2xx_data *ret = data; | ||
93 | |||
94 | mutex_lock(&data->update_lock); | ||
95 | |||
96 | if (time_after(jiffies, data->last_updated + | ||
97 | HZ / INA2XX_CONVERSION_RATE) || !data->valid) { | ||
98 | |||
99 | int i; | ||
100 | |||
101 | dev_dbg(&client->dev, "Starting ina2xx update\n"); | ||
102 | |||
103 | /* Read all registers */ | ||
104 | for (i = 0; i < data->registers; i++) { | ||
105 | int rv = ina2xx_read_word(client, i); | ||
106 | if (rv < 0) { | ||
107 | ret = ERR_PTR(rv); | ||
108 | goto abort; | ||
109 | } | ||
110 | data->regs[i] = rv; | ||
111 | } | ||
112 | data->last_updated = jiffies; | ||
113 | data->valid = 1; | ||
114 | } | ||
115 | abort: | ||
116 | mutex_unlock(&data->update_lock); | ||
117 | return ret; | ||
118 | } | ||
119 | |||
120 | static int ina219_get_value(struct ina2xx_data *data, u8 reg) | ||
121 | { | ||
122 | /* | ||
123 | * calculate exact value for the given register | ||
124 | * we assume default power-on reset settings: | ||
125 | * bus voltage range 32V | ||
126 | * gain = /8 | ||
127 | * adc 1 & 2 -> conversion time 532uS | ||
128 | * mode is continuous shunt and bus | ||
129 | * calibration value is INA219_CALIBRATION_VALUE | ||
130 | */ | ||
131 | int val = data->regs[reg]; | ||
132 | |||
133 | switch (reg) { | ||
134 | case INA2XX_SHUNT_VOLTAGE: | ||
135 | /* LSB=10uV. Convert to mV. */ | ||
136 | val = DIV_ROUND_CLOSEST(val, 100); | ||
137 | break; | ||
138 | case INA2XX_BUS_VOLTAGE: | ||
139 | /* LSB=4mV. Register is not right aligned, convert to mV. */ | ||
140 | val = (val >> 3) * 4; | ||
141 | break; | ||
142 | case INA2XX_POWER: | ||
143 | /* LSB=20mW. Convert to uW */ | ||
144 | val = val * 20 * 1000; | ||
145 | break; | ||
146 | case INA2XX_CURRENT: | ||
147 | /* LSB=1mA (selected). Is in mA */ | ||
148 | break; | ||
149 | default: | ||
150 | /* programmer goofed */ | ||
151 | WARN_ON_ONCE(1); | ||
152 | val = 0; | ||
153 | break; | ||
154 | } | ||
155 | |||
156 | return val; | ||
157 | } | ||
158 | |||
159 | static int ina226_get_value(struct ina2xx_data *data, u8 reg) | ||
160 | { | ||
161 | /* | ||
162 | * calculate exact value for the given register | ||
163 | * we assume default power-on reset settings: | ||
164 | * bus voltage range 32V | ||
165 | * gain = /8 | ||
166 | * adc 1 & 2 -> conversion time 532uS | ||
167 | * mode is continuous shunt and bus | ||
168 | * calibration value is INA226_CALIBRATION_VALUE | ||
169 | */ | ||
170 | int val = data->regs[reg]; | ||
171 | |||
172 | switch (reg) { | ||
173 | case INA2XX_SHUNT_VOLTAGE: | ||
174 | /* LSB=2.5uV. Convert to mV. */ | ||
175 | val = DIV_ROUND_CLOSEST(val, 400); | ||
176 | break; | ||
177 | case INA2XX_BUS_VOLTAGE: | ||
178 | /* LSB=1.25mV. Convert to mV. */ | ||
179 | val = val + DIV_ROUND_CLOSEST(val, 4); | ||
180 | break; | ||
181 | case INA2XX_POWER: | ||
182 | /* LSB=25mW. Convert to uW */ | ||
183 | val = val * 25 * 1000; | ||
184 | break; | ||
185 | case INA2XX_CURRENT: | ||
186 | /* LSB=1mA (selected). Is in mA */ | ||
187 | break; | ||
188 | default: | ||
189 | /* programmer goofed */ | ||
190 | WARN_ON_ONCE(1); | ||
191 | val = 0; | ||
192 | break; | ||
193 | } | ||
194 | |||
195 | return val; | ||
196 | } | ||
197 | |||
198 | static ssize_t ina2xx_show_value(struct device *dev, | ||
199 | struct device_attribute *da, char *buf) | ||
200 | { | ||
201 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
202 | struct ina2xx_data *data = ina2xx_update_device(dev); | ||
203 | int value = 0; | ||
204 | |||
205 | if (IS_ERR(data)) | ||
206 | return PTR_ERR(data); | ||
207 | |||
208 | switch (data->kind) { | ||
209 | case ina219: | ||
210 | value = ina219_get_value(data, attr->index); | ||
211 | break; | ||
212 | case ina226: | ||
213 | value = ina226_get_value(data, attr->index); | ||
214 | break; | ||
215 | default: | ||
216 | WARN_ON_ONCE(1); | ||
217 | break; | ||
218 | } | ||
219 | return snprintf(buf, PAGE_SIZE, "%d\n", value); | ||
220 | } | ||
221 | |||
222 | /* shunt voltage */ | ||
223 | static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, \ | ||
224 | ina2xx_show_value, NULL, INA2XX_SHUNT_VOLTAGE); | ||
225 | |||
226 | /* bus voltage */ | ||
227 | static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, \ | ||
228 | ina2xx_show_value, NULL, INA2XX_BUS_VOLTAGE); | ||
229 | |||
230 | /* calculated current */ | ||
231 | static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, \ | ||
232 | ina2xx_show_value, NULL, INA2XX_CURRENT); | ||
233 | |||
234 | /* calculated power */ | ||
235 | static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, \ | ||
236 | ina2xx_show_value, NULL, INA2XX_POWER); | ||
237 | |||
238 | /* pointers to created device attributes */ | ||
239 | static struct attribute *ina2xx_attributes[] = { | ||
240 | &sensor_dev_attr_in0_input.dev_attr.attr, | ||
241 | &sensor_dev_attr_in1_input.dev_attr.attr, | ||
242 | &sensor_dev_attr_curr1_input.dev_attr.attr, | ||
243 | &sensor_dev_attr_power1_input.dev_attr.attr, | ||
244 | NULL, | ||
245 | }; | ||
246 | |||
247 | static const struct attribute_group ina2xx_group = { | ||
248 | .attrs = ina2xx_attributes, | ||
249 | }; | ||
250 | |||
251 | static int ina2xx_probe(struct i2c_client *client, | ||
252 | const struct i2c_device_id *id) | ||
253 | { | ||
254 | struct i2c_adapter *adapter = client->adapter; | ||
255 | struct ina2xx_data *data; | ||
256 | struct ina2xx_platform_data *pdata; | ||
257 | int ret = 0; | ||
258 | long shunt = 10000; /* default shunt value 10mOhms */ | ||
259 | |||
260 | if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) | ||
261 | return -ENODEV; | ||
262 | |||
263 | data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); | ||
264 | if (!data) | ||
265 | return -ENOMEM; | ||
266 | |||
267 | if (client->dev.platform_data) { | ||
268 | pdata = | ||
269 | (struct ina2xx_platform_data *)client->dev.platform_data; | ||
270 | shunt = pdata->shunt_uohms; | ||
271 | } | ||
272 | |||
273 | if (shunt <= 0) | ||
274 | return -ENODEV; | ||
275 | |||
276 | /* set the device type */ | ||
277 | data->kind = id->driver_data; | ||
278 | |||
279 | switch (data->kind) { | ||
280 | case ina219: | ||
281 | /* device configuration */ | ||
282 | ina2xx_write_word(client, INA2XX_CONFIG, INA219_CONFIG_DEFAULT); | ||
283 | |||
284 | /* set current LSB to 1mA, shunt is in uOhms */ | ||
285 | /* (equation 13 in datasheet) */ | ||
286 | ina2xx_write_word(client, INA2XX_CALIBRATION, 40960000 / shunt); | ||
287 | dev_info(&client->dev, | ||
288 | "power monitor INA219 (Rshunt = %li uOhm)\n", shunt); | ||
289 | data->registers = INA219_REGISTERS; | ||
290 | break; | ||
291 | case ina226: | ||
292 | /* device configuration */ | ||
293 | ina2xx_write_word(client, INA2XX_CONFIG, INA226_CONFIG_DEFAULT); | ||
294 | |||
295 | /* set current LSB to 1mA, shunt is in uOhms */ | ||
296 | /* (equation 1 in datasheet)*/ | ||
297 | ina2xx_write_word(client, INA2XX_CALIBRATION, 5120000 / shunt); | ||
298 | dev_info(&client->dev, | ||
299 | "power monitor INA226 (Rshunt = %li uOhm)\n", shunt); | ||
300 | data->registers = INA226_REGISTERS; | ||
301 | break; | ||
302 | default: | ||
303 | /* unknown device id */ | ||
304 | return -ENODEV; | ||
305 | } | ||
306 | |||
307 | i2c_set_clientdata(client, data); | ||
308 | mutex_init(&data->update_lock); | ||
309 | |||
310 | ret = sysfs_create_group(&client->dev.kobj, &ina2xx_group); | ||
311 | if (ret) | ||
312 | return ret; | ||
313 | |||
314 | data->hwmon_dev = hwmon_device_register(&client->dev); | ||
315 | if (IS_ERR(data->hwmon_dev)) { | ||
316 | ret = PTR_ERR(data->hwmon_dev); | ||
317 | goto out_err_hwmon; | ||
318 | } | ||
319 | |||
320 | return 0; | ||
321 | |||
322 | out_err_hwmon: | ||
323 | sysfs_remove_group(&client->dev.kobj, &ina2xx_group); | ||
324 | return ret; | ||
325 | } | ||
326 | |||
327 | static int ina2xx_remove(struct i2c_client *client) | ||
328 | { | ||
329 | struct ina2xx_data *data = i2c_get_clientdata(client); | ||
330 | |||
331 | hwmon_device_unregister(data->hwmon_dev); | ||
332 | sysfs_remove_group(&client->dev.kobj, &ina2xx_group); | ||
333 | |||
334 | return 0; | ||
335 | } | ||
336 | |||
337 | static const struct i2c_device_id ina2xx_id[] = { | ||
338 | { "ina219", ina219 }, | ||
339 | { "ina226", ina226 }, | ||
340 | { } | ||
341 | }; | ||
342 | MODULE_DEVICE_TABLE(i2c, ina2xx_id); | ||
343 | |||
344 | static struct i2c_driver ina2xx_driver = { | ||
345 | .driver = { | ||
346 | .name = "ina2xx", | ||
347 | }, | ||
348 | .probe = ina2xx_probe, | ||
349 | .remove = ina2xx_remove, | ||
350 | .id_table = ina2xx_id, | ||
351 | }; | ||
352 | |||
353 | static int __init ina2xx_init(void) | ||
354 | { | ||
355 | return i2c_add_driver(&ina2xx_driver); | ||
356 | } | ||
357 | |||
358 | static void __exit ina2xx_exit(void) | ||
359 | { | ||
360 | i2c_del_driver(&ina2xx_driver); | ||
361 | } | ||
362 | |||
363 | MODULE_AUTHOR("Lothar Felten <l-felten@ti.com>"); | ||
364 | MODULE_DESCRIPTION("ina2xx driver"); | ||
365 | MODULE_LICENSE("GPL"); | ||
366 | |||
367 | module_init(ina2xx_init); | ||
368 | module_exit(ina2xx_exit); | ||