diff options
author | Guenter Roeck <linux@roeck-us.net> | 2009-08-20 17:49:25 -0400 |
---|---|---|
committer | Guenter Roeck <linux@roeck-us.net> | 2013-02-06 12:57:59 -0500 |
commit | ceeadc5c5187b78ffbea737c7a82ecc1e31e80df (patch) | |
tree | 6f506721e61dbc2fd8ed76fb5a69443949869395 /drivers/hwmon | |
parent | 2bf9233a108cea32edbb5a65afa8e609fa146577 (diff) |
hwmon: Driver for Texas Instruments INA209
Add support for the TI / Burr-Brown INA209 voltage / current / power
monitor.
Cc: Paul Hays <haysp@magma.net>
Cc: Ira W. Snyder <iws@ovro.caltech.edu>
Tested-by: Ira W. Snyder <iws@ovro.caltech.edu>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/ina209.c | 636 |
3 files changed, 647 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3588b2a0915c..5e77cef09ca6 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -1157,6 +1157,16 @@ config SENSORS_AMC6821 | |||
1157 | This driver can also be build as a module. If so, the module | 1157 | This driver can also be build as a module. If so, the module |
1158 | will be called amc6821. | 1158 | will be called amc6821. |
1159 | 1159 | ||
1160 | config SENSORS_INA209 | ||
1161 | tristate "TI / Burr Brown INA209" | ||
1162 | depends on I2C | ||
1163 | help | ||
1164 | If you say yes here you get support for the TI / Burr Brown INA209 | ||
1165 | voltage / current / power monitor I2C interface. | ||
1166 | |||
1167 | This driver can also be built as a module. If so, the module will | ||
1168 | be called ina209. | ||
1169 | |||
1160 | config SENSORS_INA2XX | 1170 | config SENSORS_INA2XX |
1161 | tristate "Texas Instruments INA219 and compatibles" | 1171 | tristate "Texas Instruments INA219 and compatibles" |
1162 | depends on I2C | 1172 | depends on I2C |
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index a37a82c64da2..8d6d97ea7c1e 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile | |||
@@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o | |||
65 | obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o | 65 | obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o |
66 | obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o | 66 | obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o |
67 | obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o | 67 | obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o |
68 | obj-$(CONFIG_SENSORS_INA209) += ina209.o | ||
68 | obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o | 69 | obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o |
69 | obj-$(CONFIG_SENSORS_IT87) += it87.o | 70 | obj-$(CONFIG_SENSORS_IT87) += it87.o |
70 | obj-$(CONFIG_SENSORS_JC42) += jc42.o | 71 | obj-$(CONFIG_SENSORS_JC42) += jc42.o |
diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c new file mode 100644 index 000000000000..c6fdd5bd395e --- /dev/null +++ b/drivers/hwmon/ina209.c | |||
@@ -0,0 +1,636 @@ | |||
1 | /* | ||
2 | * Driver for the Texas Instruments / Burr Brown INA209 | ||
3 | * Bidirectional Current/Power Monitor | ||
4 | * | ||
5 | * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net> | ||
6 | * | ||
7 | * Derived from Ira W. Snyder's original driver submission | ||
8 | * Copyright (C) 2008 Paul Hays <Paul.Hays@cattail.ca> | ||
9 | * Copyright (C) 2008-2009 Ira W. Snyder <iws@ovro.caltech.edu> | ||
10 | * | ||
11 | * Aligned with ina2xx driver | ||
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 | * Datasheet: | ||
20 | * http://www.ti.com/lit/gpn/ina209 | ||
21 | */ | ||
22 | |||
23 | #include <linux/kernel.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/err.h> | ||
27 | #include <linux/slab.h> | ||
28 | #include <linux/bug.h> | ||
29 | #include <linux/i2c.h> | ||
30 | #include <linux/hwmon.h> | ||
31 | #include <linux/hwmon-sysfs.h> | ||
32 | |||
33 | #include <linux/platform_data/ina2xx.h> | ||
34 | |||
35 | /* register definitions */ | ||
36 | #define INA209_CONFIGURATION 0x00 | ||
37 | #define INA209_STATUS 0x01 | ||
38 | #define INA209_STATUS_MASK 0x02 | ||
39 | #define INA209_SHUNT_VOLTAGE 0x03 | ||
40 | #define INA209_BUS_VOLTAGE 0x04 | ||
41 | #define INA209_POWER 0x05 | ||
42 | #define INA209_CURRENT 0x06 | ||
43 | #define INA209_SHUNT_VOLTAGE_POS_PEAK 0x07 | ||
44 | #define INA209_SHUNT_VOLTAGE_NEG_PEAK 0x08 | ||
45 | #define INA209_BUS_VOLTAGE_MAX_PEAK 0x09 | ||
46 | #define INA209_BUS_VOLTAGE_MIN_PEAK 0x0a | ||
47 | #define INA209_POWER_PEAK 0x0b | ||
48 | #define INA209_SHUNT_VOLTAGE_POS_WARN 0x0c | ||
49 | #define INA209_SHUNT_VOLTAGE_NEG_WARN 0x0d | ||
50 | #define INA209_POWER_WARN 0x0e | ||
51 | #define INA209_BUS_VOLTAGE_OVER_WARN 0x0f | ||
52 | #define INA209_BUS_VOLTAGE_UNDER_WARN 0x10 | ||
53 | #define INA209_POWER_OVER_LIMIT 0x11 | ||
54 | #define INA209_BUS_VOLTAGE_OVER_LIMIT 0x12 | ||
55 | #define INA209_BUS_VOLTAGE_UNDER_LIMIT 0x13 | ||
56 | #define INA209_CRITICAL_DAC_POS 0x14 | ||
57 | #define INA209_CRITICAL_DAC_NEG 0x15 | ||
58 | #define INA209_CALIBRATION 0x16 | ||
59 | |||
60 | #define INA209_REGISTERS 0x17 | ||
61 | |||
62 | #define INA209_CONFIG_DEFAULT 0x3c47 /* PGA=8, full range */ | ||
63 | #define INA209_SHUNT_DEFAULT 10000 /* uOhm */ | ||
64 | |||
65 | struct ina209_data { | ||
66 | struct device *hwmon_dev; | ||
67 | |||
68 | struct mutex update_lock; | ||
69 | bool valid; | ||
70 | unsigned long last_updated; /* in jiffies */ | ||
71 | |||
72 | u16 regs[INA209_REGISTERS]; /* All chip registers */ | ||
73 | |||
74 | u16 config_orig; /* Original configuration */ | ||
75 | u16 calibration_orig; /* Original calibration */ | ||
76 | u16 update_interval; | ||
77 | }; | ||
78 | |||
79 | static struct ina209_data *ina209_update_device(struct device *dev) | ||
80 | { | ||
81 | struct i2c_client *client = to_i2c_client(dev); | ||
82 | struct ina209_data *data = i2c_get_clientdata(client); | ||
83 | struct ina209_data *ret = data; | ||
84 | s32 val; | ||
85 | int i; | ||
86 | |||
87 | mutex_lock(&data->update_lock); | ||
88 | |||
89 | if (!data->valid || | ||
90 | time_after(jiffies, data->last_updated + data->update_interval)) { | ||
91 | for (i = 0; i < ARRAY_SIZE(data->regs); i++) { | ||
92 | val = i2c_smbus_read_word_swapped(client, i); | ||
93 | if (val < 0) { | ||
94 | ret = ERR_PTR(val); | ||
95 | goto abort; | ||
96 | } | ||
97 | data->regs[i] = val; | ||
98 | } | ||
99 | data->last_updated = jiffies; | ||
100 | data->valid = true; | ||
101 | } | ||
102 | abort: | ||
103 | mutex_unlock(&data->update_lock); | ||
104 | return ret; | ||
105 | } | ||
106 | |||
107 | /* | ||
108 | * Read a value from a device register and convert it to the | ||
109 | * appropriate sysfs units | ||
110 | */ | ||
111 | static long ina209_from_reg(const u8 reg, const u16 val) | ||
112 | { | ||
113 | switch (reg) { | ||
114 | case INA209_SHUNT_VOLTAGE: | ||
115 | case INA209_SHUNT_VOLTAGE_POS_PEAK: | ||
116 | case INA209_SHUNT_VOLTAGE_NEG_PEAK: | ||
117 | case INA209_SHUNT_VOLTAGE_POS_WARN: | ||
118 | case INA209_SHUNT_VOLTAGE_NEG_WARN: | ||
119 | /* LSB=10 uV. Convert to mV. */ | ||
120 | return DIV_ROUND_CLOSEST(val, 100); | ||
121 | |||
122 | case INA209_BUS_VOLTAGE: | ||
123 | case INA209_BUS_VOLTAGE_MAX_PEAK: | ||
124 | case INA209_BUS_VOLTAGE_MIN_PEAK: | ||
125 | case INA209_BUS_VOLTAGE_OVER_WARN: | ||
126 | case INA209_BUS_VOLTAGE_UNDER_WARN: | ||
127 | case INA209_BUS_VOLTAGE_OVER_LIMIT: | ||
128 | case INA209_BUS_VOLTAGE_UNDER_LIMIT: | ||
129 | /* LSB=4 mV, last 3 bits unused */ | ||
130 | return (val >> 3) * 4; | ||
131 | |||
132 | case INA209_CRITICAL_DAC_POS: | ||
133 | /* LSB=1 mV, in the upper 8 bits */ | ||
134 | return val >> 8; | ||
135 | |||
136 | case INA209_CRITICAL_DAC_NEG: | ||
137 | /* LSB=1 mV, in the upper 8 bits */ | ||
138 | return -1 * (val >> 8); | ||
139 | |||
140 | case INA209_POWER: | ||
141 | case INA209_POWER_PEAK: | ||
142 | case INA209_POWER_WARN: | ||
143 | case INA209_POWER_OVER_LIMIT: | ||
144 | /* LSB=20 mW. Convert to uW */ | ||
145 | return val * 20 * 1000L; | ||
146 | |||
147 | case INA209_CURRENT: | ||
148 | /* LSB=1 mA (selected). Is in mA */ | ||
149 | return val; | ||
150 | } | ||
151 | |||
152 | /* programmer goofed */ | ||
153 | WARN_ON_ONCE(1); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | /* | ||
158 | * Take a value and convert it to register format, clamping the value | ||
159 | * to the appropriate range. | ||
160 | */ | ||
161 | static int ina209_to_reg(u8 reg, u16 old, long val) | ||
162 | { | ||
163 | switch (reg) { | ||
164 | case INA209_SHUNT_VOLTAGE_POS_WARN: | ||
165 | case INA209_SHUNT_VOLTAGE_NEG_WARN: | ||
166 | /* Limit to +- 320 mV, 10 uV LSB */ | ||
167 | return clamp_val(val, -320, 320) * 100; | ||
168 | |||
169 | case INA209_BUS_VOLTAGE_OVER_WARN: | ||
170 | case INA209_BUS_VOLTAGE_UNDER_WARN: | ||
171 | case INA209_BUS_VOLTAGE_OVER_LIMIT: | ||
172 | case INA209_BUS_VOLTAGE_UNDER_LIMIT: | ||
173 | /* | ||
174 | * Limit to 0-32000 mV, 4 mV LSB | ||
175 | * | ||
176 | * The last three bits aren't part of the value, but we'll | ||
177 | * preserve them in their original state. | ||
178 | */ | ||
179 | return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3) | ||
180 | | (old & 0x7); | ||
181 | |||
182 | case INA209_CRITICAL_DAC_NEG: | ||
183 | /* | ||
184 | * Limit to -255-0 mV, 1 mV LSB | ||
185 | * Convert the value to a positive value for the register | ||
186 | * | ||
187 | * The value lives in the top 8 bits only, be careful | ||
188 | * and keep original value of other bits. | ||
189 | */ | ||
190 | return (clamp_val(-val, 0, 255) << 8) | (old & 0xff); | ||
191 | |||
192 | case INA209_CRITICAL_DAC_POS: | ||
193 | /* | ||
194 | * Limit to 0-255 mV, 1 mV LSB | ||
195 | * | ||
196 | * The value lives in the top 8 bits only, be careful | ||
197 | * and keep original value of other bits. | ||
198 | */ | ||
199 | return (clamp_val(val, 0, 255) << 8) | (old & 0xff); | ||
200 | |||
201 | case INA209_POWER_WARN: | ||
202 | case INA209_POWER_OVER_LIMIT: | ||
203 | /* 20 mW LSB */ | ||
204 | return DIV_ROUND_CLOSEST(val, 20 * 1000); | ||
205 | } | ||
206 | |||
207 | /* Other registers are read-only, return access error */ | ||
208 | return -EACCES; | ||
209 | } | ||
210 | |||
211 | static int ina209_interval_from_reg(u16 reg) | ||
212 | { | ||
213 | return 68 >> (15 - ((reg >> 3) & 0x0f)); | ||
214 | } | ||
215 | |||
216 | static u16 ina209_reg_from_interval(u16 config, long interval) | ||
217 | { | ||
218 | int i, adc; | ||
219 | |||
220 | if (interval <= 0) { | ||
221 | adc = 8; | ||
222 | } else { | ||
223 | adc = 15; | ||
224 | for (i = 34 + 34 / 2; i; i >>= 1) { | ||
225 | if (i < interval) | ||
226 | break; | ||
227 | adc--; | ||
228 | } | ||
229 | } | ||
230 | return (config & 0xf807) | (adc << 3) | (adc << 7); | ||
231 | } | ||
232 | |||
233 | static ssize_t ina209_set_interval(struct device *dev, | ||
234 | struct device_attribute *da, | ||
235 | const char *buf, size_t count) | ||
236 | { | ||
237 | struct i2c_client *client = to_i2c_client(dev); | ||
238 | struct ina209_data *data = ina209_update_device(dev); | ||
239 | long val; | ||
240 | u16 regval; | ||
241 | int ret; | ||
242 | |||
243 | if (IS_ERR(data)) | ||
244 | return PTR_ERR(data); | ||
245 | |||
246 | ret = kstrtol(buf, 10, &val); | ||
247 | if (ret < 0) | ||
248 | return ret; | ||
249 | |||
250 | mutex_lock(&data->update_lock); | ||
251 | regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION], | ||
252 | val); | ||
253 | i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval); | ||
254 | data->regs[INA209_CONFIGURATION] = regval; | ||
255 | data->update_interval = ina209_interval_from_reg(regval); | ||
256 | mutex_unlock(&data->update_lock); | ||
257 | return count; | ||
258 | } | ||
259 | |||
260 | static ssize_t ina209_show_interval(struct device *dev, | ||
261 | struct device_attribute *da, char *buf) | ||
262 | { | ||
263 | struct i2c_client *client = to_i2c_client(dev); | ||
264 | struct ina209_data *data = i2c_get_clientdata(client); | ||
265 | |||
266 | return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval); | ||
267 | } | ||
268 | |||
269 | /* | ||
270 | * History is reset by writing 1 into bit 0 of the respective peak register. | ||
271 | * Since more than one peak register may be affected by the scope of a | ||
272 | * reset_history attribute write, use a bit mask in attr->index to identify | ||
273 | * which registers are affected. | ||
274 | */ | ||
275 | static u16 ina209_reset_history_regs[] = { | ||
276 | INA209_SHUNT_VOLTAGE_POS_PEAK, | ||
277 | INA209_SHUNT_VOLTAGE_NEG_PEAK, | ||
278 | INA209_BUS_VOLTAGE_MAX_PEAK, | ||
279 | INA209_BUS_VOLTAGE_MIN_PEAK, | ||
280 | INA209_POWER_PEAK | ||
281 | }; | ||
282 | |||
283 | static ssize_t ina209_reset_history(struct device *dev, | ||
284 | struct device_attribute *da, | ||
285 | const char *buf, | ||
286 | size_t count) | ||
287 | { | ||
288 | struct i2c_client *client = to_i2c_client(dev); | ||
289 | struct ina209_data *data = i2c_get_clientdata(client); | ||
290 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
291 | u32 mask = attr->index; | ||
292 | long val; | ||
293 | int i, ret; | ||
294 | |||
295 | ret = kstrtol(buf, 10, &val); | ||
296 | if (ret < 0) | ||
297 | return ret; | ||
298 | |||
299 | mutex_lock(&data->update_lock); | ||
300 | for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) { | ||
301 | if (mask & (1 << i)) | ||
302 | i2c_smbus_write_word_swapped(client, | ||
303 | ina209_reset_history_regs[i], 1); | ||
304 | } | ||
305 | data->valid = false; | ||
306 | mutex_unlock(&data->update_lock); | ||
307 | return count; | ||
308 | } | ||
309 | |||
310 | static ssize_t ina209_set_value(struct device *dev, | ||
311 | struct device_attribute *da, | ||
312 | const char *buf, | ||
313 | size_t count) | ||
314 | { | ||
315 | struct i2c_client *client = to_i2c_client(dev); | ||
316 | struct ina209_data *data = ina209_update_device(dev); | ||
317 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
318 | int reg = attr->index; | ||
319 | long val; | ||
320 | int ret; | ||
321 | |||
322 | if (IS_ERR(data)) | ||
323 | return PTR_ERR(data); | ||
324 | |||
325 | ret = kstrtol(buf, 10, &val); | ||
326 | if (ret < 0) | ||
327 | return ret; | ||
328 | |||
329 | mutex_lock(&data->update_lock); | ||
330 | ret = ina209_to_reg(reg, data->regs[reg], val); | ||
331 | if (ret < 0) { | ||
332 | count = ret; | ||
333 | goto abort; | ||
334 | } | ||
335 | i2c_smbus_write_word_swapped(client, reg, ret); | ||
336 | data->regs[reg] = ret; | ||
337 | abort: | ||
338 | mutex_unlock(&data->update_lock); | ||
339 | return count; | ||
340 | } | ||
341 | |||
342 | static ssize_t ina209_show_value(struct device *dev, | ||
343 | struct device_attribute *da, | ||
344 | char *buf) | ||
345 | { | ||
346 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
347 | struct ina209_data *data = ina209_update_device(dev); | ||
348 | long val; | ||
349 | |||
350 | if (IS_ERR(data)) | ||
351 | return PTR_ERR(data); | ||
352 | |||
353 | val = ina209_from_reg(attr->index, data->regs[attr->index]); | ||
354 | return snprintf(buf, PAGE_SIZE, "%ld\n", val); | ||
355 | } | ||
356 | |||
357 | static ssize_t ina209_show_alarm(struct device *dev, | ||
358 | struct device_attribute *da, | ||
359 | char *buf) | ||
360 | { | ||
361 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
362 | struct ina209_data *data = ina209_update_device(dev); | ||
363 | const unsigned int mask = attr->index; | ||
364 | u16 status; | ||
365 | |||
366 | if (IS_ERR(data)) | ||
367 | return PTR_ERR(data); | ||
368 | |||
369 | status = data->regs[INA209_STATUS]; | ||
370 | |||
371 | /* | ||
372 | * All alarms are in the INA209_STATUS register. To avoid a long | ||
373 | * switch statement, the mask is passed in attr->index | ||
374 | */ | ||
375 | return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask)); | ||
376 | } | ||
377 | |||
378 | /* Shunt voltage, history, limits, alarms */ | ||
379 | static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL, | ||
380 | INA209_SHUNT_VOLTAGE); | ||
381 | static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL, | ||
382 | INA209_SHUNT_VOLTAGE_POS_PEAK); | ||
383 | static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL, | ||
384 | INA209_SHUNT_VOLTAGE_NEG_PEAK); | ||
385 | static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL, | ||
386 | ina209_reset_history, (1 << 0) | (1 << 1)); | ||
387 | static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value, | ||
388 | ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN); | ||
389 | static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value, | ||
390 | ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN); | ||
391 | static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value, | ||
392 | ina209_set_value, INA209_CRITICAL_DAC_POS); | ||
393 | static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value, | ||
394 | ina209_set_value, INA209_CRITICAL_DAC_NEG); | ||
395 | |||
396 | static SENSOR_DEVICE_ATTR(in0_min_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
397 | 1 << 11); | ||
398 | static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
399 | 1 << 12); | ||
400 | static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
401 | 1 << 6); | ||
402 | static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
403 | 1 << 7); | ||
404 | |||
405 | /* Bus voltage, history, limits, alarms */ | ||
406 | static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL, | ||
407 | INA209_BUS_VOLTAGE); | ||
408 | static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL, | ||
409 | INA209_BUS_VOLTAGE_MAX_PEAK); | ||
410 | static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL, | ||
411 | INA209_BUS_VOLTAGE_MIN_PEAK); | ||
412 | static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL, | ||
413 | ina209_reset_history, (1 << 2) | (1 << 3)); | ||
414 | static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value, | ||
415 | ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN); | ||
416 | static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value, | ||
417 | ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN); | ||
418 | static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value, | ||
419 | ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT); | ||
420 | static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value, | ||
421 | ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT); | ||
422 | |||
423 | static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
424 | 1 << 14); | ||
425 | static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
426 | 1 << 15); | ||
427 | static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
428 | 1 << 9); | ||
429 | static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
430 | 1 << 10); | ||
431 | |||
432 | /* Power */ | ||
433 | static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL, | ||
434 | INA209_POWER); | ||
435 | static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value, | ||
436 | NULL, INA209_POWER_PEAK); | ||
437 | static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL, | ||
438 | ina209_reset_history, 1 << 4); | ||
439 | static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value, | ||
440 | ina209_set_value, INA209_POWER_WARN); | ||
441 | static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value, | ||
442 | ina209_set_value, INA209_POWER_OVER_LIMIT); | ||
443 | |||
444 | static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
445 | 1 << 13); | ||
446 | static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL, | ||
447 | 1 << 8); | ||
448 | |||
449 | /* Current */ | ||
450 | static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL, | ||
451 | INA209_CURRENT); | ||
452 | |||
453 | static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, | ||
454 | ina209_show_interval, ina209_set_interval, 0); | ||
455 | |||
456 | /* | ||
457 | * Finally, construct an array of pointers to members of the above objects, | ||
458 | * as required for sysfs_create_group() | ||
459 | */ | ||
460 | static struct attribute *ina209_attributes[] = { | ||
461 | &sensor_dev_attr_in0_input.dev_attr.attr, | ||
462 | &sensor_dev_attr_in0_input_highest.dev_attr.attr, | ||
463 | &sensor_dev_attr_in0_input_lowest.dev_attr.attr, | ||
464 | &sensor_dev_attr_in0_reset_history.dev_attr.attr, | ||
465 | &sensor_dev_attr_in0_max.dev_attr.attr, | ||
466 | &sensor_dev_attr_in0_min.dev_attr.attr, | ||
467 | &sensor_dev_attr_in0_crit_max.dev_attr.attr, | ||
468 | &sensor_dev_attr_in0_crit_min.dev_attr.attr, | ||
469 | &sensor_dev_attr_in0_max_alarm.dev_attr.attr, | ||
470 | &sensor_dev_attr_in0_min_alarm.dev_attr.attr, | ||
471 | &sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr, | ||
472 | &sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr, | ||
473 | |||
474 | &sensor_dev_attr_in1_input.dev_attr.attr, | ||
475 | &sensor_dev_attr_in1_input_highest.dev_attr.attr, | ||
476 | &sensor_dev_attr_in1_input_lowest.dev_attr.attr, | ||
477 | &sensor_dev_attr_in1_reset_history.dev_attr.attr, | ||
478 | &sensor_dev_attr_in1_max.dev_attr.attr, | ||
479 | &sensor_dev_attr_in1_min.dev_attr.attr, | ||
480 | &sensor_dev_attr_in1_crit_max.dev_attr.attr, | ||
481 | &sensor_dev_attr_in1_crit_min.dev_attr.attr, | ||
482 | &sensor_dev_attr_in1_max_alarm.dev_attr.attr, | ||
483 | &sensor_dev_attr_in1_min_alarm.dev_attr.attr, | ||
484 | &sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr, | ||
485 | &sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr, | ||
486 | |||
487 | &sensor_dev_attr_power1_input.dev_attr.attr, | ||
488 | &sensor_dev_attr_power1_input_highest.dev_attr.attr, | ||
489 | &sensor_dev_attr_power1_reset_history.dev_attr.attr, | ||
490 | &sensor_dev_attr_power1_max.dev_attr.attr, | ||
491 | &sensor_dev_attr_power1_crit.dev_attr.attr, | ||
492 | &sensor_dev_attr_power1_max_alarm.dev_attr.attr, | ||
493 | &sensor_dev_attr_power1_crit_alarm.dev_attr.attr, | ||
494 | |||
495 | &sensor_dev_attr_curr1_input.dev_attr.attr, | ||
496 | |||
497 | &sensor_dev_attr_update_interval.dev_attr.attr, | ||
498 | |||
499 | NULL, | ||
500 | }; | ||
501 | |||
502 | static const struct attribute_group ina209_group = { | ||
503 | .attrs = ina209_attributes, | ||
504 | }; | ||
505 | |||
506 | static void ina209_restore_conf(struct i2c_client *client, | ||
507 | struct ina209_data *data) | ||
508 | { | ||
509 | /* Restore initial configuration */ | ||
510 | i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, | ||
511 | data->config_orig); | ||
512 | i2c_smbus_write_word_swapped(client, INA209_CALIBRATION, | ||
513 | data->calibration_orig); | ||
514 | } | ||
515 | |||
516 | static int ina209_init_client(struct i2c_client *client, | ||
517 | struct ina209_data *data) | ||
518 | { | ||
519 | struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); | ||
520 | u32 shunt; | ||
521 | int reg; | ||
522 | |||
523 | reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION); | ||
524 | if (reg < 0) | ||
525 | return reg; | ||
526 | data->calibration_orig = reg; | ||
527 | |||
528 | reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION); | ||
529 | if (reg < 0) | ||
530 | return reg; | ||
531 | data->config_orig = reg; | ||
532 | |||
533 | if (pdata) { | ||
534 | if (pdata->shunt_uohms <= 0) | ||
535 | return -EINVAL; | ||
536 | shunt = pdata->shunt_uohms; | ||
537 | } else if (!of_property_read_u32(client->dev.of_node, "shunt-resistor", | ||
538 | &shunt)) { | ||
539 | if (shunt == 0) | ||
540 | return -EINVAL; | ||
541 | } else { | ||
542 | shunt = data->calibration_orig ? | ||
543 | 40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT; | ||
544 | } | ||
545 | |||
546 | i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, | ||
547 | INA209_CONFIG_DEFAULT); | ||
548 | data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT); | ||
549 | |||
550 | /* | ||
551 | * Calibrate current LSB to 1mA. Shunt is in uOhms. | ||
552 | * See equation 13 in datasheet. | ||
553 | */ | ||
554 | i2c_smbus_write_word_swapped(client, INA209_CALIBRATION, | ||
555 | clamp_val(40960000 / shunt, 1, 65535)); | ||
556 | |||
557 | /* Clear status register */ | ||
558 | i2c_smbus_read_word_swapped(client, INA209_STATUS); | ||
559 | |||
560 | return 0; | ||
561 | } | ||
562 | |||
563 | static int ina209_probe(struct i2c_client *client, | ||
564 | const struct i2c_device_id *id) | ||
565 | { | ||
566 | struct i2c_adapter *adapter = client->adapter; | ||
567 | struct ina209_data *data; | ||
568 | int ret; | ||
569 | |||
570 | if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) | ||
571 | return -ENODEV; | ||
572 | |||
573 | data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); | ||
574 | if (!data) | ||
575 | return -ENOMEM; | ||
576 | |||
577 | i2c_set_clientdata(client, data); | ||
578 | mutex_init(&data->update_lock); | ||
579 | |||
580 | ret = ina209_init_client(client, data); | ||
581 | if (ret) | ||
582 | return ret; | ||
583 | |||
584 | /* Register sysfs hooks */ | ||
585 | ret = sysfs_create_group(&client->dev.kobj, &ina209_group); | ||
586 | if (ret) | ||
587 | goto out_restore_conf; | ||
588 | |||
589 | data->hwmon_dev = hwmon_device_register(&client->dev); | ||
590 | if (IS_ERR(data->hwmon_dev)) { | ||
591 | ret = PTR_ERR(data->hwmon_dev); | ||
592 | goto out_hwmon_device_register; | ||
593 | } | ||
594 | |||
595 | return 0; | ||
596 | |||
597 | out_hwmon_device_register: | ||
598 | sysfs_remove_group(&client->dev.kobj, &ina209_group); | ||
599 | out_restore_conf: | ||
600 | ina209_restore_conf(client, data); | ||
601 | return ret; | ||
602 | } | ||
603 | |||
604 | static int ina209_remove(struct i2c_client *client) | ||
605 | { | ||
606 | struct ina209_data *data = i2c_get_clientdata(client); | ||
607 | |||
608 | hwmon_device_unregister(data->hwmon_dev); | ||
609 | sysfs_remove_group(&client->dev.kobj, &ina209_group); | ||
610 | ina209_restore_conf(client, data); | ||
611 | |||
612 | return 0; | ||
613 | } | ||
614 | |||
615 | static const struct i2c_device_id ina209_id[] = { | ||
616 | { "ina209", 0 }, | ||
617 | { } | ||
618 | }; | ||
619 | MODULE_DEVICE_TABLE(i2c, ina209_id); | ||
620 | |||
621 | /* This is the driver that will be inserted */ | ||
622 | static struct i2c_driver ina209_driver = { | ||
623 | .class = I2C_CLASS_HWMON, | ||
624 | .driver = { | ||
625 | .name = "ina209", | ||
626 | }, | ||
627 | .probe = ina209_probe, | ||
628 | .remove = ina209_remove, | ||
629 | .id_table = ina209_id, | ||
630 | }; | ||
631 | |||
632 | module_i2c_driver(ina209_driver); | ||
633 | |||
634 | MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>, Paul Hays <Paul.Hays@cattail.ca>, Guenter Roeck <linux@roeck-us.net>"); | ||
635 | MODULE_DESCRIPTION("INA209 driver"); | ||
636 | MODULE_LICENSE("GPL"); | ||