aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/ltc4215.c
diff options
context:
space:
mode:
authorIra Snyder <iws@ovro.caltech.edu>2009-03-31 18:24:29 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-04-01 11:59:21 -0400
commit72f5de92e199f96cfcea125aefc76c138d8c553c (patch)
treeef24990f791801283f520b5f96a4ea1e592bf37b /drivers/hwmon/ltc4215.c
parent061603275814544842e7df77d1157eff18565997 (diff)
hwmon: Add LTC4215 driver
Add Linux support for the Linear Technology LTC4215 Hot Swap controller I2C monitoring interface. I have tested the driver with my board, and it appears to work fine. With the power supplies disabled, it reads 11.93V input, 1.93V output, no current and no power. With the supplies enabled, it reads 11.93V input, 11.98V output, no current, no power. I'm not drawing any current at the moment, so this is reasonable. The value in the sense register never reads anything except 0, so I expect to get zero from the current and power calculations. I didn't attempt to support changing any of the chip's settings or enabling the FET. I'm not sure even how to do that and still fit within the hwmon framework. :) Signed-off-by: Ira W. Snyder <iws@ovro.caltech.edu> Cc: Jean Delvare <khali@linux-fr.org> Cc: "Mark M. Hoffman" <mhoffman@lightlink.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/hwmon/ltc4215.c')
-rw-r--r--drivers/hwmon/ltc4215.c364
1 files changed, 364 insertions, 0 deletions
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
23static const unsigned short normal_i2c[] = { I2C_CLIENT_END };
24
25/* Insmod parameters */
26I2C_CLIENT_INSMOD_1(ltc4215);
27
28/* Here are names of the chip's registers (a.k.a. commands) */
29enum 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
39struct 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
50static 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 */
83static 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 */
113static 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
140static 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
150static 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
159static 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
172static 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 */
208LTC4215_CURRENT(curr1_input);
209LTC4215_ALARM(curr1_max_alarm, (1 << 2), LTC4215_STATUS);
210
211/* Power (virtual) */
212LTC4215_POWER(power1_input);
213LTC4215_ALARM(power1_alarm, (1 << 3), LTC4215_STATUS);
214
215/* Input Voltage */
216LTC4215_VOLTAGE(in1_input, LTC4215_ADIN);
217LTC4215_ALARM(in1_max_alarm, (1 << 0), LTC4215_STATUS);
218LTC4215_ALARM(in1_min_alarm, (1 << 1), LTC4215_STATUS);
219
220/* Output Voltage */
221LTC4215_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 */
226static 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
242static const struct attribute_group ltc4215_group = {
243 .attrs = ltc4215_attributes,
244};
245
246static 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, &ltc4215_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
277out_hwmon_device_register:
278 sysfs_remove_group(&client->dev.kobj, &ltc4215_group);
279out_sysfs_create_group:
280 kfree(data);
281out_kzalloc:
282 return ret;
283}
284
285static 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, &ltc4215_group);
291
292 kfree(data);
293
294 return 0;
295}
296
297static 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
330static const struct i2c_device_id ltc4215_id[] = {
331 { "ltc4215", ltc4215 },
332 { }
333};
334MODULE_DEVICE_TABLE(i2c, ltc4215_id);
335
336/* This is the driver that will be inserted */
337static 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
349static int __init ltc4215_init(void)
350{
351 return i2c_add_driver(&ltc4215_driver);
352}
353
354static void __exit ltc4215_exit(void)
355{
356 i2c_del_driver(&ltc4215_driver);
357}
358
359MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>");
360MODULE_DESCRIPTION("LTC4215 driver");
361MODULE_LICENSE("GPL");
362
363module_init(ltc4215_init);
364module_exit(ltc4215_exit);