diff options
-rw-r--r-- | Documentation/hwmon/ltc4215 | 50 | ||||
-rw-r--r-- | drivers/hwmon/Kconfig | 11 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/ltc4215.c | 364 |
4 files changed, 426 insertions, 0 deletions
diff --git a/Documentation/hwmon/ltc4215 b/Documentation/hwmon/ltc4215 new file mode 100644 index 000000000000..2e6a21eb656c --- /dev/null +++ b/Documentation/hwmon/ltc4215 | |||
@@ -0,0 +1,50 @@ | |||
1 | Kernel driver ltc4215 | ||
2 | ===================== | ||
3 | |||
4 | Supported chips: | ||
5 | * Linear Technology LTC4215 | ||
6 | Prefix: 'ltc4215' | ||
7 | Addresses scanned: 0x44 | ||
8 | Datasheet: | ||
9 | http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1163,P17572,D12697 | ||
10 | |||
11 | Author: Ira W. Snyder <iws@ovro.caltech.edu> | ||
12 | |||
13 | |||
14 | Description | ||
15 | ----------- | ||
16 | |||
17 | The LTC4215 controller allows a board to be safely inserted and removed | ||
18 | from a live backplane. | ||
19 | |||
20 | |||
21 | Usage Notes | ||
22 | ----------- | ||
23 | |||
24 | This driver does not probe for LTC4215 devices, due to the fact that some | ||
25 | of the possible addresses are unfriendly to probing. You will need to use | ||
26 | the "force" parameter to tell the driver where to find the device. | ||
27 | |||
28 | Example: the following will load the driver for an LTC4215 at address 0x44 | ||
29 | on I2C bus #0: | ||
30 | $ modprobe ltc4215 force=0,0x44 | ||
31 | |||
32 | |||
33 | Sysfs entries | ||
34 | ------------- | ||
35 | |||
36 | The LTC4215 has built-in limits for overvoltage, undervoltage, and | ||
37 | undercurrent warnings. This makes it very likely that the reference | ||
38 | circuit will be used. | ||
39 | |||
40 | in1_input input voltage | ||
41 | in2_input output voltage | ||
42 | |||
43 | in1_min_alarm input undervoltage alarm | ||
44 | in1_max_alarm input overvoltage alarm | ||
45 | |||
46 | curr1_input current | ||
47 | curr1_max_alarm overcurrent alarm | ||
48 | |||
49 | power1_input power usage | ||
50 | power1_alarm power bad alarm | ||
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8a91c9971558..bc6810c22dd7 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -571,6 +571,17 @@ config SENSORS_LM93 | |||
571 | This driver can also be built as a module. If so, the module | 571 | This driver can also be built as a module. If so, the module |
572 | will be called lm93. | 572 | will be called lm93. |
573 | 573 | ||
574 | config SENSORS_LTC4215 | ||
575 | tristate "Linear Technology LTC4215" | ||
576 | depends on I2C && EXPERIMENTAL | ||
577 | default n | ||
578 | help | ||
579 | If you say yes here you get support for Linear Technology LTC4215 | ||
580 | Hot Swap Controller I2C interface. | ||
581 | |||
582 | This driver can also be built as a module. If so, the module will | ||
583 | be called ltc4215. | ||
584 | |||
574 | config SENSORS_LTC4245 | 585 | config SENSORS_LTC4245 |
575 | tristate "Linear Technology LTC4245" | 586 | tristate "Linear Technology LTC4245" |
576 | depends on I2C && EXPERIMENTAL | 587 | depends on I2C && EXPERIMENTAL |
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 81c88822a3eb..4da261d16abf 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile | |||
@@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o | |||
65 | obj-$(CONFIG_SENSORS_LM92) += lm92.o | 65 | obj-$(CONFIG_SENSORS_LM92) += lm92.o |
66 | obj-$(CONFIG_SENSORS_LM93) += lm93.o | 66 | obj-$(CONFIG_SENSORS_LM93) += lm93.o |
67 | obj-$(CONFIG_SENSORS_LM95241) += lm95241.o | 67 | obj-$(CONFIG_SENSORS_LM95241) += lm95241.o |
68 | obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o | ||
68 | obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o | 69 | obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o |
69 | obj-$(CONFIG_SENSORS_MAX1111) += max1111.o | 70 | obj-$(CONFIG_SENSORS_MAX1111) += max1111.o |
70 | obj-$(CONFIG_SENSORS_MAX1619) += max1619.o | 71 | obj-$(CONFIG_SENSORS_MAX1619) += max1619.o |
diff --git a/drivers/hwmon/ltc4215.c b/drivers/hwmon/ltc4215.c new file mode 100644 index 000000000000..9386e2a39211 --- /dev/null +++ b/drivers/hwmon/ltc4215.c | |||
@@ -0,0 +1,364 @@ | |||
1 | /* | ||
2 | * Driver for Linear Technology LTC4215 I2C Hot Swap Controller | ||
3 | * | ||
4 | * Copyright (C) 2009 Ira W. Snyder <iws@ovro.caltech.edu> | ||
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; version 2 of the License. | ||
9 | * | ||
10 | * Datasheet: | ||
11 | * http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1163,P17572,D12697 | ||
12 | */ | ||
13 | |||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/init.h> | ||
17 | #include <linux/err.h> | ||
18 | #include <linux/slab.h> | ||
19 | #include <linux/i2c.h> | ||
20 | #include <linux/hwmon.h> | ||
21 | #include <linux/hwmon-sysfs.h> | ||
22 | |||
23 | static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; | ||
24 | |||
25 | /* Insmod parameters */ | ||
26 | I2C_CLIENT_INSMOD_1(ltc4215); | ||
27 | |||
28 | /* Here are names of the chip's registers (a.k.a. commands) */ | ||
29 | enum ltc4215_cmd { | ||
30 | LTC4215_CONTROL = 0x00, /* rw */ | ||
31 | LTC4215_ALERT = 0x01, /* rw */ | ||
32 | LTC4215_STATUS = 0x02, /* ro */ | ||
33 | LTC4215_FAULT = 0x03, /* rw */ | ||
34 | LTC4215_SENSE = 0x04, /* rw */ | ||
35 | LTC4215_SOURCE = 0x05, /* rw */ | ||
36 | LTC4215_ADIN = 0x06, /* rw */ | ||
37 | }; | ||
38 | |||
39 | struct ltc4215_data { | ||
40 | struct device *hwmon_dev; | ||
41 | |||
42 | struct mutex update_lock; | ||
43 | bool valid; | ||
44 | unsigned long last_updated; /* in jiffies */ | ||
45 | |||
46 | /* Registers */ | ||
47 | u8 regs[7]; | ||
48 | }; | ||
49 | |||
50 | static struct ltc4215_data *ltc4215_update_device(struct device *dev) | ||
51 | { | ||
52 | struct i2c_client *client = to_i2c_client(dev); | ||
53 | struct ltc4215_data *data = i2c_get_clientdata(client); | ||
54 | s32 val; | ||
55 | int i; | ||
56 | |||
57 | mutex_lock(&data->update_lock); | ||
58 | |||
59 | /* The chip's A/D updates 10 times per second */ | ||
60 | if (time_after(jiffies, data->last_updated + HZ / 10) || !data->valid) { | ||
61 | |||
62 | dev_dbg(&client->dev, "Starting ltc4215 update\n"); | ||
63 | |||
64 | /* Read all registers */ | ||
65 | for (i = 0; i < ARRAY_SIZE(data->regs); i++) { | ||
66 | val = i2c_smbus_read_byte_data(client, i); | ||
67 | if (unlikely(val < 0)) | ||
68 | data->regs[i] = 0; | ||
69 | else | ||
70 | data->regs[i] = val; | ||
71 | } | ||
72 | |||
73 | data->last_updated = jiffies; | ||
74 | data->valid = 1; | ||
75 | } | ||
76 | |||
77 | mutex_unlock(&data->update_lock); | ||
78 | |||
79 | return data; | ||
80 | } | ||
81 | |||
82 | /* Return the voltage from the given register in millivolts */ | ||
83 | static int ltc4215_get_voltage(struct device *dev, u8 reg) | ||
84 | { | ||
85 | struct ltc4215_data *data = ltc4215_update_device(dev); | ||
86 | const u8 regval = data->regs[reg]; | ||
87 | u32 voltage = 0; | ||
88 | |||
89 | switch (reg) { | ||
90 | case LTC4215_SENSE: | ||
91 | /* 151 uV per increment */ | ||
92 | voltage = regval * 151 / 1000; | ||
93 | break; | ||
94 | case LTC4215_SOURCE: | ||
95 | /* 60.5 mV per increment */ | ||
96 | voltage = regval * 605 / 10; | ||
97 | break; | ||
98 | case LTC4215_ADIN: | ||
99 | /* The ADIN input is divided by 12.5, and has 4.82 mV | ||
100 | * per increment, so we have the additional multiply */ | ||
101 | voltage = regval * 482 * 125 / 1000; | ||
102 | break; | ||
103 | default: | ||
104 | /* If we get here, the developer messed up */ | ||
105 | WARN_ON_ONCE(1); | ||
106 | break; | ||
107 | } | ||
108 | |||
109 | return voltage; | ||
110 | } | ||
111 | |||
112 | /* Return the current from the sense resistor in mA */ | ||
113 | static unsigned int ltc4215_get_current(struct device *dev) | ||
114 | { | ||
115 | struct ltc4215_data *data = ltc4215_update_device(dev); | ||
116 | |||
117 | /* The strange looking conversions that follow are fixed-point | ||
118 | * math, since we cannot do floating point in the kernel. | ||
119 | * | ||
120 | * Step 1: convert sense register to microVolts | ||
121 | * Step 2: convert voltage to milliAmperes | ||
122 | * | ||
123 | * If you play around with the V=IR equation, you come up with | ||
124 | * the following: X uV / Y mOhm == Z mA | ||
125 | * | ||
126 | * With the resistors that are fractions of a milliOhm, we multiply | ||
127 | * the voltage and resistance by 10, to shift the decimal point. | ||
128 | * Now we can use the normal division operator again. | ||
129 | */ | ||
130 | |||
131 | /* Calculate voltage in microVolts (151 uV per increment) */ | ||
132 | const unsigned int voltage = data->regs[LTC4215_SENSE] * 151; | ||
133 | |||
134 | /* Calculate current in milliAmperes (4 milliOhm sense resistor) */ | ||
135 | const unsigned int curr = voltage / 4; | ||
136 | |||
137 | return curr; | ||
138 | } | ||
139 | |||
140 | static ssize_t ltc4215_show_voltage(struct device *dev, | ||
141 | struct device_attribute *da, | ||
142 | char *buf) | ||
143 | { | ||
144 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
145 | const int voltage = ltc4215_get_voltage(dev, attr->index); | ||
146 | |||
147 | return snprintf(buf, PAGE_SIZE, "%d\n", voltage); | ||
148 | } | ||
149 | |||
150 | static ssize_t ltc4215_show_current(struct device *dev, | ||
151 | struct device_attribute *da, | ||
152 | char *buf) | ||
153 | { | ||
154 | const unsigned int curr = ltc4215_get_current(dev); | ||
155 | |||
156 | return snprintf(buf, PAGE_SIZE, "%u\n", curr); | ||
157 | } | ||
158 | |||
159 | static ssize_t ltc4215_show_power(struct device *dev, | ||
160 | struct device_attribute *da, | ||
161 | char *buf) | ||
162 | { | ||
163 | const unsigned int curr = ltc4215_get_current(dev); | ||
164 | const int output_voltage = ltc4215_get_voltage(dev, LTC4215_ADIN); | ||
165 | |||
166 | /* current in mA * voltage in mV == power in uW */ | ||
167 | const unsigned int power = abs(output_voltage * curr); | ||
168 | |||
169 | return snprintf(buf, PAGE_SIZE, "%u\n", power); | ||
170 | } | ||
171 | |||
172 | static ssize_t ltc4215_show_alarm(struct device *dev, | ||
173 | struct device_attribute *da, | ||
174 | char *buf) | ||
175 | { | ||
176 | struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); | ||
177 | struct ltc4215_data *data = ltc4215_update_device(dev); | ||
178 | const u8 reg = data->regs[attr->index]; | ||
179 | const u32 mask = attr->nr; | ||
180 | |||
181 | return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0); | ||
182 | } | ||
183 | |||
184 | /* These macros are used below in constructing device attribute objects | ||
185 | * for use with sysfs_create_group() to make a sysfs device file | ||
186 | * for each register. | ||
187 | */ | ||
188 | |||
189 | #define LTC4215_VOLTAGE(name, ltc4215_cmd_idx) \ | ||
190 | static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ | ||
191 | ltc4215_show_voltage, NULL, ltc4215_cmd_idx) | ||
192 | |||
193 | #define LTC4215_CURRENT(name) \ | ||
194 | static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ | ||
195 | ltc4215_show_current, NULL, 0); | ||
196 | |||
197 | #define LTC4215_POWER(name) \ | ||
198 | static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ | ||
199 | ltc4215_show_power, NULL, 0); | ||
200 | |||
201 | #define LTC4215_ALARM(name, mask, reg) \ | ||
202 | static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \ | ||
203 | ltc4215_show_alarm, NULL, (mask), reg) | ||
204 | |||
205 | /* Construct a sensor_device_attribute structure for each register */ | ||
206 | |||
207 | /* Current */ | ||
208 | LTC4215_CURRENT(curr1_input); | ||
209 | LTC4215_ALARM(curr1_max_alarm, (1 << 2), LTC4215_STATUS); | ||
210 | |||
211 | /* Power (virtual) */ | ||
212 | LTC4215_POWER(power1_input); | ||
213 | LTC4215_ALARM(power1_alarm, (1 << 3), LTC4215_STATUS); | ||
214 | |||
215 | /* Input Voltage */ | ||
216 | LTC4215_VOLTAGE(in1_input, LTC4215_ADIN); | ||
217 | LTC4215_ALARM(in1_max_alarm, (1 << 0), LTC4215_STATUS); | ||
218 | LTC4215_ALARM(in1_min_alarm, (1 << 1), LTC4215_STATUS); | ||
219 | |||
220 | /* Output Voltage */ | ||
221 | LTC4215_VOLTAGE(in2_input, LTC4215_SOURCE); | ||
222 | |||
223 | /* Finally, construct an array of pointers to members of the above objects, | ||
224 | * as required for sysfs_create_group() | ||
225 | */ | ||
226 | static struct attribute *ltc4215_attributes[] = { | ||
227 | &sensor_dev_attr_curr1_input.dev_attr.attr, | ||
228 | &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, | ||
229 | |||
230 | &sensor_dev_attr_power1_input.dev_attr.attr, | ||
231 | &sensor_dev_attr_power1_alarm.dev_attr.attr, | ||
232 | |||
233 | &sensor_dev_attr_in1_input.dev_attr.attr, | ||
234 | &sensor_dev_attr_in1_max_alarm.dev_attr.attr, | ||
235 | &sensor_dev_attr_in1_min_alarm.dev_attr.attr, | ||
236 | |||
237 | &sensor_dev_attr_in2_input.dev_attr.attr, | ||
238 | |||
239 | NULL, | ||
240 | }; | ||
241 | |||
242 | static const struct attribute_group ltc4215_group = { | ||
243 | .attrs = ltc4215_attributes, | ||
244 | }; | ||
245 | |||
246 | static int ltc4215_probe(struct i2c_client *client, | ||
247 | const struct i2c_device_id *id) | ||
248 | { | ||
249 | struct ltc4215_data *data; | ||
250 | int ret; | ||
251 | |||
252 | data = kzalloc(sizeof(*data), GFP_KERNEL); | ||
253 | if (!data) { | ||
254 | ret = -ENOMEM; | ||
255 | goto out_kzalloc; | ||
256 | } | ||
257 | |||
258 | i2c_set_clientdata(client, data); | ||
259 | mutex_init(&data->update_lock); | ||
260 | |||
261 | /* Initialize the LTC4215 chip */ | ||
262 | /* TODO */ | ||
263 | |||
264 | /* Register sysfs hooks */ | ||
265 | ret = sysfs_create_group(&client->dev.kobj, <c4215_group); | ||
266 | if (ret) | ||
267 | goto out_sysfs_create_group; | ||
268 | |||
269 | data->hwmon_dev = hwmon_device_register(&client->dev); | ||
270 | if (IS_ERR(data->hwmon_dev)) { | ||
271 | ret = PTR_ERR(data->hwmon_dev); | ||
272 | goto out_hwmon_device_register; | ||
273 | } | ||
274 | |||
275 | return 0; | ||
276 | |||
277 | out_hwmon_device_register: | ||
278 | sysfs_remove_group(&client->dev.kobj, <c4215_group); | ||
279 | out_sysfs_create_group: | ||
280 | kfree(data); | ||
281 | out_kzalloc: | ||
282 | return ret; | ||
283 | } | ||
284 | |||
285 | static int ltc4215_remove(struct i2c_client *client) | ||
286 | { | ||
287 | struct ltc4215_data *data = i2c_get_clientdata(client); | ||
288 | |||
289 | hwmon_device_unregister(data->hwmon_dev); | ||
290 | sysfs_remove_group(&client->dev.kobj, <c4215_group); | ||
291 | |||
292 | kfree(data); | ||
293 | |||
294 | return 0; | ||
295 | } | ||
296 | |||
297 | static int ltc4215_detect(struct i2c_client *client, | ||
298 | int kind, | ||
299 | struct i2c_board_info *info) | ||
300 | { | ||
301 | struct i2c_adapter *adapter = client->adapter; | ||
302 | |||
303 | if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | ||
304 | return -ENODEV; | ||
305 | |||
306 | if (kind < 0) { /* probed detection - check the chip type */ | ||
307 | s32 v; /* 8 bits from the chip, or -ERRNO */ | ||
308 | |||
309 | /* | ||
310 | * Register 0x01 bit b7 is reserved, expect 0 | ||
311 | * Register 0x03 bit b6 and b7 are reserved, expect 0 | ||
312 | */ | ||
313 | v = i2c_smbus_read_byte_data(client, LTC4215_ALERT); | ||
314 | if (v < 0 || (v & (1 << 7)) != 0) | ||
315 | return -ENODEV; | ||
316 | |||
317 | v = i2c_smbus_read_byte_data(client, LTC4215_FAULT); | ||
318 | if (v < 0 || (v & ((1 << 6) | (1 << 7))) != 0) | ||
319 | return -ENODEV; | ||
320 | } | ||
321 | |||
322 | strlcpy(info->type, "ltc4215", I2C_NAME_SIZE); | ||
323 | dev_info(&adapter->dev, "ltc4215 %s at address 0x%02x\n", | ||
324 | kind < 0 ? "probed" : "forced", | ||
325 | client->addr); | ||
326 | |||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | static const struct i2c_device_id ltc4215_id[] = { | ||
331 | { "ltc4215", ltc4215 }, | ||
332 | { } | ||
333 | }; | ||
334 | MODULE_DEVICE_TABLE(i2c, ltc4215_id); | ||
335 | |||
336 | /* This is the driver that will be inserted */ | ||
337 | static struct i2c_driver ltc4215_driver = { | ||
338 | .class = I2C_CLASS_HWMON, | ||
339 | .driver = { | ||
340 | .name = "ltc4215", | ||
341 | }, | ||
342 | .probe = ltc4215_probe, | ||
343 | .remove = ltc4215_remove, | ||
344 | .id_table = ltc4215_id, | ||
345 | .detect = ltc4215_detect, | ||
346 | .address_data = &addr_data, | ||
347 | }; | ||
348 | |||
349 | static int __init ltc4215_init(void) | ||
350 | { | ||
351 | return i2c_add_driver(<c4215_driver); | ||
352 | } | ||
353 | |||
354 | static void __exit ltc4215_exit(void) | ||
355 | { | ||
356 | i2c_del_driver(<c4215_driver); | ||
357 | } | ||
358 | |||
359 | MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>"); | ||
360 | MODULE_DESCRIPTION("LTC4215 driver"); | ||
361 | MODULE_LICENSE("GPL"); | ||
362 | |||
363 | module_init(ltc4215_init); | ||
364 | module_exit(ltc4215_exit); | ||