diff options
author | Roland Stigge <stigge@antcom.de> | 2011-01-09 09:31:39 -0500 |
---|---|---|
committer | Guenter Roeck <guenter.roeck@ericsson.com> | 2011-01-09 12:10:10 -0500 |
commit | 6099469805c24af14250e182bb9ca082b8a6b716 (patch) | |
tree | 9b08dd1a10ff89f2557575f5bf95b07babf92beb /drivers/hwmon | |
parent | 430400b86304ea729ba10f2966a8af67da60a37f (diff) |
hwmon: Support for Dallas Semiconductor DS620
Driver for Dallas Semiconductor DS620 temperature sensor and thermostat
Signed-off-by: Roland Stigge <stigge@antcom.de>
Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com>
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/ds620.c | 337 |
3 files changed, 348 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0f09567257fd..bdc13d28b1ea 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -274,6 +274,16 @@ config SENSORS_ATXP1 | |||
274 | This driver can also be built as a module. If so, the module | 274 | This driver can also be built as a module. If so, the module |
275 | will be called atxp1. | 275 | will be called atxp1. |
276 | 276 | ||
277 | config SENSORS_DS620 | ||
278 | tristate "Dallas Semiconductor DS620" | ||
279 | depends on I2C | ||
280 | help | ||
281 | If you say yes here you get support for Dallas Semiconductor | ||
282 | DS620 sensor chip. | ||
283 | |||
284 | This driver can also be built as a module. If so, the module | ||
285 | will be called ds620. | ||
286 | |||
277 | config SENSORS_DS1621 | 287 | config SENSORS_DS1621 |
278 | tristate "Dallas Semiconductor DS1621 and DS1625" | 288 | tristate "Dallas Semiconductor DS1621 and DS1625" |
279 | depends on I2C | 289 | depends on I2C |
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 20e74086e681..dde02d99c238 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile | |||
@@ -41,6 +41,7 @@ obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o | |||
41 | obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o | 41 | obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o |
42 | obj-$(CONFIG_SENSORS_PKGTEMP) += pkgtemp.o | 42 | obj-$(CONFIG_SENSORS_PKGTEMP) += pkgtemp.o |
43 | obj-$(CONFIG_SENSORS_DME1737) += dme1737.o | 43 | obj-$(CONFIG_SENSORS_DME1737) += dme1737.o |
44 | obj-$(CONFIG_SENSORS_DS620) += ds620.o | ||
44 | obj-$(CONFIG_SENSORS_DS1621) += ds1621.o | 45 | obj-$(CONFIG_SENSORS_DS1621) += ds1621.o |
45 | obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o | 46 | obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o |
46 | obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o | 47 | obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o |
diff --git a/drivers/hwmon/ds620.c b/drivers/hwmon/ds620.c new file mode 100644 index 000000000000..257957c69d92 --- /dev/null +++ b/drivers/hwmon/ds620.c | |||
@@ -0,0 +1,337 @@ | |||
1 | /* | ||
2 | * ds620.c - Support for temperature sensor and thermostat DS620 | ||
3 | * | ||
4 | * Copyright (C) 2010, 2011 Roland Stigge <stigge@antcom.de> | ||
5 | * | ||
6 | * based on ds1621.c by Christian W. Zuckschwerdt <zany@triq.net> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/slab.h> | ||
26 | #include <linux/jiffies.h> | ||
27 | #include <linux/i2c.h> | ||
28 | #include <linux/hwmon.h> | ||
29 | #include <linux/hwmon-sysfs.h> | ||
30 | #include <linux/err.h> | ||
31 | #include <linux/mutex.h> | ||
32 | #include <linux/sysfs.h> | ||
33 | #include <linux/i2c/ds620.h> | ||
34 | |||
35 | /* | ||
36 | * Many DS620 constants specified below | ||
37 | * 15 14 13 12 11 10 09 08 | ||
38 | * |Done|NVB |THF |TLF |R1 |R0 |AUTOC|1SHOT| | ||
39 | * | ||
40 | * 07 06 05 04 03 02 01 00 | ||
41 | * |PO2 |PO1 |A2 |A1 |A0 | | | | | ||
42 | */ | ||
43 | #define DS620_REG_CONFIG_DONE 0x8000 | ||
44 | #define DS620_REG_CONFIG_NVB 0x4000 | ||
45 | #define DS620_REG_CONFIG_THF 0x2000 | ||
46 | #define DS620_REG_CONFIG_TLF 0x1000 | ||
47 | #define DS620_REG_CONFIG_R1 0x0800 | ||
48 | #define DS620_REG_CONFIG_R0 0x0400 | ||
49 | #define DS620_REG_CONFIG_AUTOC 0x0200 | ||
50 | #define DS620_REG_CONFIG_1SHOT 0x0100 | ||
51 | #define DS620_REG_CONFIG_PO2 0x0080 | ||
52 | #define DS620_REG_CONFIG_PO1 0x0040 | ||
53 | #define DS620_REG_CONFIG_A2 0x0020 | ||
54 | #define DS620_REG_CONFIG_A1 0x0010 | ||
55 | #define DS620_REG_CONFIG_A0 0x0008 | ||
56 | |||
57 | /* The DS620 registers */ | ||
58 | static const u8 DS620_REG_TEMP[3] = { | ||
59 | 0xAA, /* input, word, RO */ | ||
60 | 0xA2, /* min, word, RW */ | ||
61 | 0xA0, /* max, word, RW */ | ||
62 | }; | ||
63 | |||
64 | #define DS620_REG_CONF 0xAC /* word, RW */ | ||
65 | #define DS620_COM_START 0x51 /* no data */ | ||
66 | #define DS620_COM_STOP 0x22 /* no data */ | ||
67 | |||
68 | /* Each client has this additional data */ | ||
69 | struct ds620_data { | ||
70 | struct device *hwmon_dev; | ||
71 | struct mutex update_lock; | ||
72 | char valid; /* !=0 if following fields are valid */ | ||
73 | unsigned long last_updated; /* In jiffies */ | ||
74 | |||
75 | u16 temp[3]; /* Register values, word */ | ||
76 | }; | ||
77 | |||
78 | /* | ||
79 | * Temperature registers are word-sized. | ||
80 | * DS620 uses a high-byte first convention, which is exactly opposite to | ||
81 | * the SMBus standard. | ||
82 | */ | ||
83 | static int ds620_read_temp(struct i2c_client *client, u8 reg) | ||
84 | { | ||
85 | int ret; | ||
86 | |||
87 | ret = i2c_smbus_read_word_data(client, reg); | ||
88 | if (ret < 0) | ||
89 | return ret; | ||
90 | return swab16(ret); | ||
91 | } | ||
92 | |||
93 | static int ds620_write_temp(struct i2c_client *client, u8 reg, u16 value) | ||
94 | { | ||
95 | return i2c_smbus_write_word_data(client, reg, swab16(value)); | ||
96 | } | ||
97 | |||
98 | static void ds620_init_client(struct i2c_client *client) | ||
99 | { | ||
100 | struct ds620_platform_data *ds620_info = client->dev.platform_data; | ||
101 | u16 conf, new_conf; | ||
102 | |||
103 | new_conf = conf = | ||
104 | swab16(i2c_smbus_read_word_data(client, DS620_REG_CONF)); | ||
105 | |||
106 | /* switch to continuous conversion mode */ | ||
107 | new_conf &= ~DS620_REG_CONFIG_1SHOT; | ||
108 | /* already high at power-on, but don't trust the BIOS! */ | ||
109 | new_conf |= DS620_REG_CONFIG_PO2; | ||
110 | /* thermostat mode according to platform data */ | ||
111 | if (ds620_info && ds620_info->pomode == 1) | ||
112 | new_conf &= ~DS620_REG_CONFIG_PO1; /* PO_LOW */ | ||
113 | else if (ds620_info && ds620_info->pomode == 2) | ||
114 | new_conf |= DS620_REG_CONFIG_PO1; /* PO_HIGH */ | ||
115 | else | ||
116 | new_conf &= ~DS620_REG_CONFIG_PO2; /* always low */ | ||
117 | /* with highest precision */ | ||
118 | new_conf |= DS620_REG_CONFIG_R1 | DS620_REG_CONFIG_R0; | ||
119 | |||
120 | if (conf != new_conf) | ||
121 | i2c_smbus_write_word_data(client, DS620_REG_CONF, | ||
122 | swab16(new_conf)); | ||
123 | |||
124 | /* start conversion */ | ||
125 | i2c_smbus_write_byte(client, DS620_COM_START); | ||
126 | } | ||
127 | |||
128 | static struct ds620_data *ds620_update_client(struct device *dev) | ||
129 | { | ||
130 | struct i2c_client *client = to_i2c_client(dev); | ||
131 | struct ds620_data *data = i2c_get_clientdata(client); | ||
132 | struct ds620_data *ret = data; | ||
133 | |||
134 | mutex_lock(&data->update_lock); | ||
135 | |||
136 | if (time_after(jiffies, data->last_updated + HZ + HZ / 2) | ||
137 | || !data->valid) { | ||
138 | int i; | ||
139 | int res; | ||
140 | |||
141 | dev_dbg(&client->dev, "Starting ds620 update\n"); | ||
142 | |||
143 | for (i = 0; i < ARRAY_SIZE(data->temp); i++) { | ||
144 | res = ds620_read_temp(client, | ||
145 | DS620_REG_TEMP[i]); | ||
146 | if (res < 0) { | ||
147 | ret = ERR_PTR(res); | ||
148 | goto abort; | ||
149 | } | ||
150 | |||
151 | data->temp[i] = res; | ||
152 | } | ||
153 | |||
154 | data->last_updated = jiffies; | ||
155 | data->valid = 1; | ||
156 | } | ||
157 | abort: | ||
158 | mutex_unlock(&data->update_lock); | ||
159 | |||
160 | return ret; | ||
161 | } | ||
162 | |||
163 | static ssize_t show_temp(struct device *dev, struct device_attribute *da, | ||
164 | char *buf) | ||
165 | { | ||
166 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
167 | struct ds620_data *data = ds620_update_client(dev); | ||
168 | |||
169 | if (IS_ERR(data)) | ||
170 | return PTR_ERR(data); | ||
171 | |||
172 | return sprintf(buf, "%d\n", ((data->temp[attr->index] / 8) * 625) / 10); | ||
173 | } | ||
174 | |||
175 | static ssize_t set_temp(struct device *dev, struct device_attribute *da, | ||
176 | const char *buf, size_t count) | ||
177 | { | ||
178 | int res; | ||
179 | long val; | ||
180 | |||
181 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
182 | struct i2c_client *client = to_i2c_client(dev); | ||
183 | struct ds620_data *data = i2c_get_clientdata(client); | ||
184 | |||
185 | res = strict_strtol(buf, 10, &val); | ||
186 | |||
187 | if (res) | ||
188 | return res; | ||
189 | |||
190 | val = (val * 10 / 625) * 8; | ||
191 | |||
192 | mutex_lock(&data->update_lock); | ||
193 | data->temp[attr->index] = val; | ||
194 | ds620_write_temp(client, DS620_REG_TEMP[attr->index], | ||
195 | data->temp[attr->index]); | ||
196 | mutex_unlock(&data->update_lock); | ||
197 | return count; | ||
198 | } | ||
199 | |||
200 | static ssize_t show_alarm(struct device *dev, struct device_attribute *da, | ||
201 | char *buf) | ||
202 | { | ||
203 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | ||
204 | struct ds620_data *data = ds620_update_client(dev); | ||
205 | struct i2c_client *client = to_i2c_client(dev); | ||
206 | u16 conf, new_conf; | ||
207 | int res; | ||
208 | |||
209 | if (IS_ERR(data)) | ||
210 | return PTR_ERR(data); | ||
211 | |||
212 | /* reset alarms if necessary */ | ||
213 | res = i2c_smbus_read_word_data(client, DS620_REG_CONF); | ||
214 | if (res < 0) | ||
215 | return res; | ||
216 | |||
217 | conf = swab16(res); | ||
218 | new_conf = conf; | ||
219 | new_conf &= ~attr->index; | ||
220 | if (conf != new_conf) { | ||
221 | res = i2c_smbus_write_word_data(client, DS620_REG_CONF, | ||
222 | swab16(new_conf)); | ||
223 | if (res < 0) | ||
224 | return res; | ||
225 | } | ||
226 | |||
227 | return sprintf(buf, "%d\n", !!(conf & attr->index)); | ||
228 | } | ||
229 | |||
230 | static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); | ||
231 | static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, 1); | ||
232 | static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, 2); | ||
233 | static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, | ||
234 | DS620_REG_CONFIG_TLF); | ||
235 | static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, | ||
236 | DS620_REG_CONFIG_THF); | ||
237 | |||
238 | static struct attribute *ds620_attributes[] = { | ||
239 | &sensor_dev_attr_temp1_input.dev_attr.attr, | ||
240 | &sensor_dev_attr_temp1_min.dev_attr.attr, | ||
241 | &sensor_dev_attr_temp1_max.dev_attr.attr, | ||
242 | &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, | ||
243 | &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, | ||
244 | NULL | ||
245 | }; | ||
246 | |||
247 | static const struct attribute_group ds620_group = { | ||
248 | .attrs = ds620_attributes, | ||
249 | }; | ||
250 | |||
251 | static int ds620_probe(struct i2c_client *client, | ||
252 | const struct i2c_device_id *id) | ||
253 | { | ||
254 | struct ds620_data *data; | ||
255 | int err; | ||
256 | |||
257 | data = kzalloc(sizeof(struct ds620_data), GFP_KERNEL); | ||
258 | if (!data) { | ||
259 | err = -ENOMEM; | ||
260 | goto exit; | ||
261 | } | ||
262 | |||
263 | i2c_set_clientdata(client, data); | ||
264 | mutex_init(&data->update_lock); | ||
265 | |||
266 | /* Initialize the DS620 chip */ | ||
267 | ds620_init_client(client); | ||
268 | |||
269 | /* Register sysfs hooks */ | ||
270 | err = sysfs_create_group(&client->dev.kobj, &ds620_group); | ||
271 | if (err) | ||
272 | goto exit_free; | ||
273 | |||
274 | data->hwmon_dev = hwmon_device_register(&client->dev); | ||
275 | if (IS_ERR(data->hwmon_dev)) { | ||
276 | err = PTR_ERR(data->hwmon_dev); | ||
277 | goto exit_remove_files; | ||
278 | } | ||
279 | |||
280 | dev_info(&client->dev, "temperature sensor found\n"); | ||
281 | |||
282 | return 0; | ||
283 | |||
284 | exit_remove_files: | ||
285 | sysfs_remove_group(&client->dev.kobj, &ds620_group); | ||
286 | exit_free: | ||
287 | kfree(data); | ||
288 | exit: | ||
289 | return err; | ||
290 | } | ||
291 | |||
292 | static int ds620_remove(struct i2c_client *client) | ||
293 | { | ||
294 | struct ds620_data *data = i2c_get_clientdata(client); | ||
295 | |||
296 | hwmon_device_unregister(data->hwmon_dev); | ||
297 | sysfs_remove_group(&client->dev.kobj, &ds620_group); | ||
298 | |||
299 | kfree(data); | ||
300 | |||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | static const struct i2c_device_id ds620_id[] = { | ||
305 | {"ds620", 0}, | ||
306 | {} | ||
307 | }; | ||
308 | |||
309 | MODULE_DEVICE_TABLE(i2c, ds620_id); | ||
310 | |||
311 | /* This is the driver that will be inserted */ | ||
312 | static struct i2c_driver ds620_driver = { | ||
313 | .class = I2C_CLASS_HWMON, | ||
314 | .driver = { | ||
315 | .name = "ds620", | ||
316 | }, | ||
317 | .probe = ds620_probe, | ||
318 | .remove = ds620_remove, | ||
319 | .id_table = ds620_id, | ||
320 | }; | ||
321 | |||
322 | static int __init ds620_init(void) | ||
323 | { | ||
324 | return i2c_add_driver(&ds620_driver); | ||
325 | } | ||
326 | |||
327 | static void __exit ds620_exit(void) | ||
328 | { | ||
329 | i2c_del_driver(&ds620_driver); | ||
330 | } | ||
331 | |||
332 | MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>"); | ||
333 | MODULE_DESCRIPTION("DS620 driver"); | ||
334 | MODULE_LICENSE("GPL"); | ||
335 | |||
336 | module_init(ds620_init); | ||
337 | module_exit(ds620_exit); | ||