aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon
diff options
context:
space:
mode:
authorWolfram Sang <w.sang@pengutronix.de>2010-03-05 16:17:23 -0500
committerJean Delvare <khali@linux-fr.org>2010-03-05 16:17:23 -0500
commitd84ca5b345c2b77ebf053d534ada6af2332a43b6 (patch)
tree6ed7d8e10828692cf6512255e08aae7036485785 /drivers/hwmon
parent5852f9609d21794c45964129b03365883150a6d0 (diff)
hwmon: Add driver for ADT7411 voltage and temperature sensor
Add basic support for the ADT7411. Reads out all conversion results (via I2C, SPI yet missing) and allows some on-the-fly configuration. Tested with a custom board. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Signed-off-by: Wolfram Sang <w.sang@pengutronix.de> Signed-off-by: Jean Delvare <khali@linux-fr.org>
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig10
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/adt7411.c359
3 files changed, 370 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index cdfb0637f247..77d032fb813d 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -170,6 +170,16 @@ config SENSORS_ADM9240
170 This driver can also be built as a module. If so, the module 170 This driver can also be built as a module. If so, the module
171 will be called adm9240. 171 will be called adm9240.
172 172
173config SENSORS_ADT7411
174 tristate "Analog Devices ADT7411"
175 depends on I2C && EXPERIMENTAL
176 help
177 If you say yes here you get support for the Analog Devices
178 ADT7411 voltage and temperature monitoring chip.
179
180 This driver can also be built as a module. If so, the module
181 will be called adt7411.
182
173config SENSORS_ADT7462 183config SENSORS_ADT7462
174 tristate "Analog Devices ADT7462" 184 tristate "Analog Devices ADT7462"
175 depends on I2C && EXPERIMENTAL 185 depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 4bc215c0953f..5fe67bf961b3 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o
29obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o 29obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
30obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o 30obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
31obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o 31obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o
32obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o
32obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o 33obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
33obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o 34obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
34obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o 35obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o
diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c
new file mode 100644
index 000000000000..9e9246fe2245
--- /dev/null
+++ b/drivers/hwmon/adt7411.c
@@ -0,0 +1,359 @@
1/*
2 * Driver for the ADT7411 (I2C/SPI 8 channel 10 bit ADC & temperature-sensor)
3 *
4 * Copyright (C) 2008, 2010 Pengutronix
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 version 2 as
8 * published by the Free Software Foundation.
9 *
10 * TODO: SPI, support for external temperature sensor
11 * use power-down mode for suspend?, interrupt handling?
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/delay.h>
19#include <linux/mutex.h>
20#include <linux/jiffies.h>
21#include <linux/i2c.h>
22#include <linux/hwmon.h>
23#include <linux/hwmon-sysfs.h>
24
25#define ADT7411_REG_INT_TEMP_VDD_LSB 0x03
26#define ADT7411_REG_EXT_TEMP_AIN14_LSB 0x04
27#define ADT7411_REG_VDD_MSB 0x06
28#define ADT7411_REG_INT_TEMP_MSB 0x07
29#define ADT7411_REG_EXT_TEMP_AIN1_MSB 0x08
30
31#define ADT7411_REG_CFG1 0x18
32#define ADT7411_CFG1_START_MONITOR (1 << 0)
33
34#define ADT7411_REG_CFG2 0x19
35#define ADT7411_CFG2_DISABLE_AVG (1 << 5)
36
37#define ADT7411_REG_CFG3 0x1a
38#define ADT7411_CFG3_ADC_CLK_225 (1 << 0)
39#define ADT7411_CFG3_REF_VDD (1 << 4)
40
41#define ADT7411_REG_DEVICE_ID 0x4d
42#define ADT7411_REG_MANUFACTURER_ID 0x4e
43
44#define ADT7411_DEVICE_ID 0x2
45#define ADT7411_MANUFACTURER_ID 0x41
46
47static const unsigned short normal_i2c[] = { 0x48, 0x4a, 0x4b, I2C_CLIENT_END };
48
49struct adt7411_data {
50 struct mutex device_lock; /* for "atomic" device accesses */
51 unsigned long next_update;
52 int vref_cached;
53 bool ref_is_vdd;
54 struct device *hwmon_dev;
55};
56
57/*
58 * When reading a register containing (up to 4) lsb, all associated
59 * msb-registers get locked by the hardware. After _one_ of those msb is read,
60 * _all_ are unlocked. In order to use this locking correctly, reading lsb/msb
61 * is protected here with a mutex, too.
62 */
63static int adt7411_read_10_bit(struct i2c_client *client, u8 lsb_reg,
64 u8 msb_reg, u8 lsb_shift)
65{
66 struct adt7411_data *data = i2c_get_clientdata(client);
67 int val, tmp;
68
69 mutex_lock(&data->device_lock);
70
71 val = i2c_smbus_read_byte_data(client, lsb_reg);
72 if (val < 0)
73 goto exit_unlock;
74
75 tmp = (val >> lsb_shift) & 3;
76 val = i2c_smbus_read_byte_data(client, msb_reg);
77
78 if (val >= 0)
79 val = (val << 2) | tmp;
80
81 exit_unlock:
82 mutex_unlock(&data->device_lock);
83
84 return val;
85}
86
87static int adt7411_modify_bit(struct i2c_client *client, u8 reg, u8 bit,
88 bool flag)
89{
90 struct adt7411_data *data = i2c_get_clientdata(client);
91 int ret, val;
92
93 mutex_lock(&data->device_lock);
94
95 ret = i2c_smbus_read_byte_data(client, reg);
96 if (ret < 0)
97 goto exit_unlock;
98
99 if (flag)
100 val = ret | bit;
101 else
102 val = ret & ~bit;
103
104 ret = i2c_smbus_write_byte_data(client, reg, val);
105
106 exit_unlock:
107 mutex_unlock(&data->device_lock);
108 return ret;
109}
110
111static ssize_t adt7411_show_vdd(struct device *dev,
112 struct device_attribute *attr, char *buf)
113{
114 struct i2c_client *client = to_i2c_client(dev);
115 int ret = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB,
116 ADT7411_REG_VDD_MSB, 2);
117
118 return ret < 0 ? ret : sprintf(buf, "%u\n", ret * 7000 / 1024);
119}
120
121static ssize_t adt7411_show_temp(struct device *dev,
122 struct device_attribute *attr, char *buf)
123{
124 struct i2c_client *client = to_i2c_client(dev);
125 int val = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB,
126 ADT7411_REG_INT_TEMP_MSB, 0);
127
128 if (val < 0)
129 return val;
130
131 val = val & 0x200 ? val - 0x400 : val; /* 10 bit signed */
132
133 return sprintf(buf, "%d\n", val * 250);
134}
135
136static ssize_t adt7411_show_input(struct device *dev,
137 struct device_attribute *attr, char *buf)
138{
139 int nr = to_sensor_dev_attr(attr)->index;
140 struct i2c_client *client = to_i2c_client(dev);
141 struct adt7411_data *data = i2c_get_clientdata(client);
142 int val;
143 u8 lsb_reg, lsb_shift;
144
145 if (time_after_eq(jiffies, data->next_update)) {
146 val = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3);
147 if (val < 0)
148 return val;
149 data->ref_is_vdd = val & ADT7411_CFG3_REF_VDD;
150
151 if (data->ref_is_vdd) {
152 val = adt7411_read_10_bit(client,
153 ADT7411_REG_INT_TEMP_VDD_LSB,
154 ADT7411_REG_VDD_MSB, 2);
155 if (val < 0)
156 return val;
157
158 data->vref_cached = val * 7000 / 1024;
159 } else {
160 data->vref_cached = 2250;
161 }
162
163 data->next_update = jiffies + HZ;
164 }
165
166 lsb_reg = ADT7411_REG_EXT_TEMP_AIN14_LSB + (nr >> 2);
167 lsb_shift = 2 * (nr & 0x03);
168 val = adt7411_read_10_bit(client, lsb_reg,
169 ADT7411_REG_EXT_TEMP_AIN1_MSB + nr, lsb_shift);
170
171 return val < 0 ? val :
172 sprintf(buf, "%u\n", val * data->vref_cached / 1024);
173}
174
175static ssize_t adt7411_show_bit(struct device *dev,
176 struct device_attribute *attr, char *buf)
177{
178 struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr);
179 struct i2c_client *client = to_i2c_client(dev);
180 int ret = i2c_smbus_read_byte_data(client, attr2->index);
181
182 return ret < 0 ? ret : sprintf(buf, "%u\n", !!(ret & attr2->nr));
183}
184
185static ssize_t adt7411_set_bit(struct device *dev,
186 struct device_attribute *attr, const char *buf,
187 size_t count)
188{
189 struct sensor_device_attribute_2 *s_attr2 = to_sensor_dev_attr_2(attr);
190 struct i2c_client *client = to_i2c_client(dev);
191 struct adt7411_data *data = i2c_get_clientdata(client);
192 int ret;
193 unsigned long flag;
194
195 ret = strict_strtoul(buf, 0, &flag);
196 if (ret || flag > 1)
197 return -EINVAL;
198
199 ret = adt7411_modify_bit(client, s_attr2->index, s_attr2->nr, flag);
200
201 /* force update */
202 data->next_update = jiffies;
203
204 return ret < 0 ? ret : count;
205}
206
207#define ADT7411_BIT_ATTR(__name, __reg, __bit) \
208 SENSOR_DEVICE_ATTR_2(__name, S_IRUGO | S_IWUSR, adt7411_show_bit, \
209 adt7411_set_bit, __bit, __reg)
210
211static DEVICE_ATTR(temp1_input, S_IRUGO, adt7411_show_temp, NULL);
212static DEVICE_ATTR(in0_input, S_IRUGO, adt7411_show_vdd, NULL);
213static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, adt7411_show_input, NULL, 0);
214static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, adt7411_show_input, NULL, 1);
215static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, adt7411_show_input, NULL, 2);
216static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, adt7411_show_input, NULL, 3);
217static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, adt7411_show_input, NULL, 4);
218static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, adt7411_show_input, NULL, 5);
219static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, adt7411_show_input, NULL, 6);
220static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, adt7411_show_input, NULL, 7);
221static ADT7411_BIT_ATTR(no_average, ADT7411_REG_CFG2, ADT7411_CFG2_DISABLE_AVG);
222static ADT7411_BIT_ATTR(fast_sampling, ADT7411_REG_CFG3, ADT7411_CFG3_ADC_CLK_225);
223static ADT7411_BIT_ATTR(adc_ref_vdd, ADT7411_REG_CFG3, ADT7411_CFG3_REF_VDD);
224
225static struct attribute *adt7411_attrs[] = {
226 &dev_attr_temp1_input.attr,
227 &dev_attr_in0_input.attr,
228 &sensor_dev_attr_in1_input.dev_attr.attr,
229 &sensor_dev_attr_in2_input.dev_attr.attr,
230 &sensor_dev_attr_in3_input.dev_attr.attr,
231 &sensor_dev_attr_in4_input.dev_attr.attr,
232 &sensor_dev_attr_in5_input.dev_attr.attr,
233 &sensor_dev_attr_in6_input.dev_attr.attr,
234 &sensor_dev_attr_in7_input.dev_attr.attr,
235 &sensor_dev_attr_in8_input.dev_attr.attr,
236 &sensor_dev_attr_no_average.dev_attr.attr,
237 &sensor_dev_attr_fast_sampling.dev_attr.attr,
238 &sensor_dev_attr_adc_ref_vdd.dev_attr.attr,
239 NULL
240};
241
242static const struct attribute_group adt7411_attr_grp = {
243 .attrs = adt7411_attrs,
244};
245
246static int adt7411_detect(struct i2c_client *client,
247 struct i2c_board_info *info)
248{
249 int val;
250
251 if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
252 return -ENODEV;
253
254 val = i2c_smbus_read_byte_data(client, ADT7411_REG_MANUFACTURER_ID);
255 if (val < 0 || val != ADT7411_MANUFACTURER_ID) {
256 dev_dbg(&client->dev, "Wrong manufacturer ID. Got %d, "
257 "expected %d\n", val, ADT7411_MANUFACTURER_ID);
258 return -ENODEV;
259 }
260
261 val = i2c_smbus_read_byte_data(client, ADT7411_REG_DEVICE_ID);
262 if (val < 0 || val != ADT7411_DEVICE_ID) {
263 dev_dbg(&client->dev, "Wrong device ID. Got %d, "
264 "expected %d\n", val, ADT7411_DEVICE_ID);
265 return -ENODEV;
266 }
267
268 strlcpy(info->type, "adt7411", I2C_NAME_SIZE);
269
270 return 0;
271}
272
273static int __devinit adt7411_probe(struct i2c_client *client,
274 const struct i2c_device_id *id)
275{
276 struct adt7411_data *data;
277 int ret;
278
279 data = kzalloc(sizeof(*data), GFP_KERNEL);
280 if (!data)
281 return -ENOMEM;
282
283 i2c_set_clientdata(client, data);
284 mutex_init(&data->device_lock);
285
286 ret = adt7411_modify_bit(client, ADT7411_REG_CFG1,
287 ADT7411_CFG1_START_MONITOR, 1);
288 if (ret < 0)
289 goto exit_free;
290
291 /* force update on first occasion */
292 data->next_update = jiffies;
293
294 ret = sysfs_create_group(&client->dev.kobj, &adt7411_attr_grp);
295 if (ret)
296 goto exit_free;
297
298 data->hwmon_dev = hwmon_device_register(&client->dev);
299 if (IS_ERR(data->hwmon_dev)) {
300 ret = PTR_ERR(data->hwmon_dev);
301 goto exit_remove;
302 }
303
304 dev_info(&client->dev, "successfully registered\n");
305
306 return 0;
307
308 exit_remove:
309 sysfs_remove_group(&client->dev.kobj, &adt7411_attr_grp);
310 exit_free:
311 i2c_set_clientdata(client, NULL);
312 kfree(data);
313 return ret;
314}
315
316static int __devexit adt7411_remove(struct i2c_client *client)
317{
318 struct adt7411_data *data = i2c_get_clientdata(client);
319
320 hwmon_device_unregister(data->hwmon_dev);
321 sysfs_remove_group(&client->dev.kobj, &adt7411_attr_grp);
322 i2c_set_clientdata(client, NULL);
323 kfree(data);
324 return 0;
325}
326
327static const struct i2c_device_id adt7411_id[] = {
328 { "adt7411", 0 },
329 { }
330};
331
332static struct i2c_driver adt7411_driver = {
333 .driver = {
334 .name = "adt7411",
335 },
336 .probe = adt7411_probe,
337 .remove = __devexit_p(adt7411_remove),
338 .id_table = adt7411_id,
339 .detect = adt7411_detect,
340 .address_list = normal_i2c,
341 .class = I2C_CLASS_HWMON,
342};
343
344static int __init sensors_adt7411_init(void)
345{
346 return i2c_add_driver(&adt7411_driver);
347}
348module_init(sensors_adt7411_init)
349
350static void __exit sensors_adt7411_exit(void)
351{
352 i2c_del_driver(&adt7411_driver);
353}
354module_exit(sensors_adt7411_exit)
355
356MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de> and "
357 "Wolfram Sang <w.sang@pengutronix.de>");
358MODULE_DESCRIPTION("ADT7411 driver");
359MODULE_LICENSE("GPL v2");