Commit | Line | Data |
---|---|---|
51c2a487 LPC |
1 | /* |
2 | * adt7x10.c - Part of lm_sensors, Linux kernel modules for hardware | |
3 | * monitoring | |
4 | * This driver handles the ADT7410 and compatible digital temperature sensors. | |
5 | * Hartmut Knaack <knaack.h@gmx.de> 2012-07-22 | |
6 | * based on lm75.c by Frodo Looijaard <frodol@dds.nl> | |
7 | * and adt7410.c from iio-staging by Sonic Zhang <sonic.zhang@analog.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program; if not, write to the Free Software | |
21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
22 | */ | |
23 | ||
24 | #include <linux/module.h> | |
25 | #include <linux/init.h> | |
26 | #include <linux/slab.h> | |
27 | #include <linux/jiffies.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/delay.h> | |
4b5e536b | 33 | #include <linux/interrupt.h> |
51c2a487 LPC |
34 | |
35 | #include "adt7x10.h" | |
36 | ||
37 | /* | |
38 | * ADT7X10 status | |
39 | */ | |
40 | #define ADT7X10_STAT_T_LOW (1 << 4) | |
41 | #define ADT7X10_STAT_T_HIGH (1 << 5) | |
42 | #define ADT7X10_STAT_T_CRIT (1 << 6) | |
43 | #define ADT7X10_STAT_NOT_RDY (1 << 7) | |
44 | ||
45 | /* | |
46 | * ADT7X10 config | |
47 | */ | |
48 | #define ADT7X10_FAULT_QUEUE_MASK (1 << 0 | 1 << 1) | |
49 | #define ADT7X10_CT_POLARITY (1 << 2) | |
50 | #define ADT7X10_INT_POLARITY (1 << 3) | |
51 | #define ADT7X10_EVENT_MODE (1 << 4) | |
52 | #define ADT7X10_MODE_MASK (1 << 5 | 1 << 6) | |
53 | #define ADT7X10_FULL (0 << 5 | 0 << 6) | |
54 | #define ADT7X10_PD (1 << 5 | 1 << 6) | |
55 | #define ADT7X10_RESOLUTION (1 << 7) | |
56 | ||
57 | /* | |
58 | * ADT7X10 masks | |
59 | */ | |
60 | #define ADT7X10_T13_VALUE_MASK 0xFFF8 | |
61 | #define ADT7X10_T_HYST_MASK 0xF | |
62 | ||
63 | /* straight from the datasheet */ | |
64 | #define ADT7X10_TEMP_MIN (-55000) | |
65 | #define ADT7X10_TEMP_MAX 150000 | |
66 | ||
67 | /* Each client has this additional data */ | |
68 | struct adt7x10_data { | |
69 | const struct adt7x10_ops *ops; | |
70 | const char *name; | |
71 | struct device *hwmon_dev; | |
72 | struct mutex update_lock; | |
73 | u8 config; | |
74 | u8 oldconfig; | |
75 | bool valid; /* true if registers valid */ | |
76 | unsigned long last_updated; /* In jiffies */ | |
77 | s16 temp[4]; /* Register values, | |
78 | 0 = input | |
79 | 1 = high | |
80 | 2 = low | |
81 | 3 = critical */ | |
82 | u8 hyst; /* hysteresis offset */ | |
83 | }; | |
84 | ||
85 | static int adt7x10_read_byte(struct device *dev, u8 reg) | |
86 | { | |
87 | struct adt7x10_data *d = dev_get_drvdata(dev); | |
88 | return d->ops->read_byte(dev, reg); | |
89 | } | |
90 | ||
91 | static int adt7x10_write_byte(struct device *dev, u8 reg, u8 data) | |
92 | { | |
93 | struct adt7x10_data *d = dev_get_drvdata(dev); | |
94 | return d->ops->write_byte(dev, reg, data); | |
95 | } | |
96 | ||
97 | static int adt7x10_read_word(struct device *dev, u8 reg) | |
98 | { | |
99 | struct adt7x10_data *d = dev_get_drvdata(dev); | |
100 | return d->ops->read_word(dev, reg); | |
101 | } | |
102 | ||
103 | static int adt7x10_write_word(struct device *dev, u8 reg, u16 data) | |
104 | { | |
105 | struct adt7x10_data *d = dev_get_drvdata(dev); | |
106 | return d->ops->write_word(dev, reg, data); | |
107 | } | |
108 | ||
109 | static const u8 ADT7X10_REG_TEMP[4] = { | |
110 | ADT7X10_TEMPERATURE, /* input */ | |
111 | ADT7X10_T_ALARM_HIGH, /* high */ | |
112 | ADT7X10_T_ALARM_LOW, /* low */ | |
113 | ADT7X10_T_CRIT, /* critical */ | |
114 | }; | |
115 | ||
4b5e536b LPC |
116 | static irqreturn_t adt7x10_irq_handler(int irq, void *private) |
117 | { | |
118 | struct device *dev = private; | |
119 | int status; | |
120 | ||
121 | status = adt7x10_read_byte(dev, ADT7X10_STATUS); | |
122 | if (status < 0) | |
123 | return IRQ_HANDLED; | |
124 | ||
125 | if (status & ADT7X10_STAT_T_HIGH) | |
126 | sysfs_notify(&dev->kobj, NULL, "temp1_max_alarm"); | |
127 | if (status & ADT7X10_STAT_T_LOW) | |
128 | sysfs_notify(&dev->kobj, NULL, "temp1_min_alarm"); | |
129 | if (status & ADT7X10_STAT_T_CRIT) | |
130 | sysfs_notify(&dev->kobj, NULL, "temp1_crit_alarm"); | |
131 | ||
132 | return IRQ_HANDLED; | |
133 | } | |
134 | ||
51c2a487 LPC |
135 | static int adt7x10_temp_ready(struct device *dev) |
136 | { | |
137 | int i, status; | |
138 | ||
139 | for (i = 0; i < 6; i++) { | |
140 | status = adt7x10_read_byte(dev, ADT7X10_STATUS); | |
141 | if (status < 0) | |
142 | return status; | |
143 | if (!(status & ADT7X10_STAT_NOT_RDY)) | |
144 | return 0; | |
145 | msleep(60); | |
146 | } | |
147 | return -ETIMEDOUT; | |
148 | } | |
149 | ||
150 | static int adt7x10_update_temp(struct device *dev) | |
151 | { | |
152 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
153 | int ret = 0; | |
154 | ||
155 | mutex_lock(&data->update_lock); | |
156 | ||
157 | if (time_after(jiffies, data->last_updated + HZ + HZ / 2) | |
158 | || !data->valid) { | |
159 | int temp; | |
160 | ||
161 | dev_dbg(dev, "Starting update\n"); | |
162 | ||
163 | ret = adt7x10_temp_ready(dev); /* check for new value */ | |
164 | if (ret) | |
165 | goto abort; | |
166 | ||
167 | temp = adt7x10_read_word(dev, ADT7X10_REG_TEMP[0]); | |
168 | if (temp < 0) { | |
169 | ret = temp; | |
170 | dev_dbg(dev, "Failed to read value: reg %d, error %d\n", | |
171 | ADT7X10_REG_TEMP[0], ret); | |
172 | goto abort; | |
173 | } | |
174 | data->temp[0] = temp; | |
175 | data->last_updated = jiffies; | |
176 | data->valid = true; | |
177 | } | |
178 | ||
179 | abort: | |
180 | mutex_unlock(&data->update_lock); | |
181 | return ret; | |
182 | } | |
183 | ||
184 | static int adt7x10_fill_cache(struct device *dev) | |
185 | { | |
186 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
187 | int ret; | |
188 | int i; | |
189 | ||
190 | for (i = 1; i < ARRAY_SIZE(data->temp); i++) { | |
191 | ret = adt7x10_read_word(dev, ADT7X10_REG_TEMP[i]); | |
192 | if (ret < 0) { | |
193 | dev_dbg(dev, "Failed to read value: reg %d, error %d\n", | |
194 | ADT7X10_REG_TEMP[i], ret); | |
195 | return ret; | |
196 | } | |
197 | data->temp[i] = ret; | |
198 | } | |
199 | ||
200 | ret = adt7x10_read_byte(dev, ADT7X10_T_HYST); | |
201 | if (ret < 0) { | |
202 | dev_dbg(dev, "Failed to read value: reg %d, error %d\n", | |
203 | ADT7X10_T_HYST, ret); | |
204 | return ret; | |
205 | } | |
206 | data->hyst = ret; | |
207 | ||
208 | return 0; | |
209 | } | |
210 | ||
211 | static s16 ADT7X10_TEMP_TO_REG(long temp) | |
212 | { | |
213 | return DIV_ROUND_CLOSEST(clamp_val(temp, ADT7X10_TEMP_MIN, | |
214 | ADT7X10_TEMP_MAX) * 128, 1000); | |
215 | } | |
216 | ||
217 | static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg) | |
218 | { | |
219 | /* in 13 bit mode, bits 0-2 are status flags - mask them out */ | |
220 | if (!(data->config & ADT7X10_RESOLUTION)) | |
221 | reg &= ADT7X10_T13_VALUE_MASK; | |
222 | /* | |
223 | * temperature is stored in twos complement format, in steps of | |
224 | * 1/128°C | |
225 | */ | |
226 | return DIV_ROUND_CLOSEST(reg * 1000, 128); | |
227 | } | |
228 | ||
229 | /*-----------------------------------------------------------------------*/ | |
230 | ||
231 | /* sysfs attributes for hwmon */ | |
232 | ||
233 | static ssize_t adt7x10_show_temp(struct device *dev, | |
234 | struct device_attribute *da, | |
235 | char *buf) | |
236 | { | |
237 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | |
238 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
239 | ||
240 | ||
241 | if (attr->index == 0) { | |
242 | int ret; | |
243 | ||
244 | ret = adt7x10_update_temp(dev); | |
245 | if (ret) | |
246 | return ret; | |
247 | } | |
248 | ||
249 | return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data, | |
250 | data->temp[attr->index])); | |
251 | } | |
252 | ||
253 | static ssize_t adt7x10_set_temp(struct device *dev, | |
254 | struct device_attribute *da, | |
255 | const char *buf, size_t count) | |
256 | { | |
257 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | |
258 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
259 | int nr = attr->index; | |
260 | long temp; | |
261 | int ret; | |
262 | ||
263 | ret = kstrtol(buf, 10, &temp); | |
264 | if (ret) | |
265 | return ret; | |
266 | ||
267 | mutex_lock(&data->update_lock); | |
268 | data->temp[nr] = ADT7X10_TEMP_TO_REG(temp); | |
269 | ret = adt7x10_write_word(dev, ADT7X10_REG_TEMP[nr], data->temp[nr]); | |
270 | if (ret) | |
271 | count = ret; | |
272 | mutex_unlock(&data->update_lock); | |
273 | return count; | |
274 | } | |
275 | ||
276 | static ssize_t adt7x10_show_t_hyst(struct device *dev, | |
277 | struct device_attribute *da, | |
278 | char *buf) | |
279 | { | |
280 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | |
281 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
282 | int nr = attr->index; | |
283 | int hyst; | |
284 | ||
285 | hyst = (data->hyst & ADT7X10_T_HYST_MASK) * 1000; | |
286 | ||
287 | /* | |
288 | * hysteresis is stored as a 4 bit offset in the device, convert it | |
289 | * to an absolute value | |
290 | */ | |
291 | if (nr == 2) /* min has positive offset, others have negative */ | |
292 | hyst = -hyst; | |
293 | return sprintf(buf, "%d\n", | |
294 | ADT7X10_REG_TO_TEMP(data, data->temp[nr]) - hyst); | |
295 | } | |
296 | ||
297 | static ssize_t adt7x10_set_t_hyst(struct device *dev, | |
298 | struct device_attribute *da, | |
299 | const char *buf, size_t count) | |
300 | { | |
301 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
302 | int limit, ret; | |
303 | long hyst; | |
304 | ||
305 | ret = kstrtol(buf, 10, &hyst); | |
306 | if (ret) | |
307 | return ret; | |
308 | /* convert absolute hysteresis value to a 4 bit delta value */ | |
309 | limit = ADT7X10_REG_TO_TEMP(data, data->temp[1]); | |
310 | hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX); | |
311 | data->hyst = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), | |
312 | 0, ADT7X10_T_HYST_MASK); | |
313 | ret = adt7x10_write_byte(dev, ADT7X10_T_HYST, data->hyst); | |
314 | if (ret) | |
315 | return ret; | |
316 | ||
317 | return count; | |
318 | } | |
319 | ||
320 | static ssize_t adt7x10_show_alarm(struct device *dev, | |
321 | struct device_attribute *da, | |
322 | char *buf) | |
323 | { | |
324 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | |
325 | int ret; | |
326 | ||
327 | ret = adt7x10_read_byte(dev, ADT7X10_STATUS); | |
328 | if (ret < 0) | |
329 | return ret; | |
330 | ||
331 | return sprintf(buf, "%d\n", !!(ret & attr->index)); | |
332 | } | |
333 | ||
334 | static ssize_t adt7x10_show_name(struct device *dev, | |
335 | struct device_attribute *da, | |
336 | char *buf) | |
337 | { | |
338 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
339 | ||
340 | return sprintf(buf, "%s\n", data->name); | |
341 | } | |
342 | ||
343 | static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, adt7x10_show_temp, NULL, 0); | |
344 | static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, | |
345 | adt7x10_show_temp, adt7x10_set_temp, 1); | |
346 | static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, | |
347 | adt7x10_show_temp, adt7x10_set_temp, 2); | |
348 | static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, | |
349 | adt7x10_show_temp, adt7x10_set_temp, 3); | |
350 | static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, | |
351 | adt7x10_show_t_hyst, adt7x10_set_t_hyst, 1); | |
352 | static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO, | |
353 | adt7x10_show_t_hyst, NULL, 2); | |
354 | static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, | |
355 | adt7x10_show_t_hyst, NULL, 3); | |
356 | static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, adt7x10_show_alarm, | |
357 | NULL, ADT7X10_STAT_T_LOW); | |
358 | static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, adt7x10_show_alarm, | |
359 | NULL, ADT7X10_STAT_T_HIGH); | |
360 | static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, adt7x10_show_alarm, | |
361 | NULL, ADT7X10_STAT_T_CRIT); | |
362 | static DEVICE_ATTR(name, S_IRUGO, adt7x10_show_name, NULL); | |
363 | ||
364 | static struct attribute *adt7x10_attributes[] = { | |
365 | &sensor_dev_attr_temp1_input.dev_attr.attr, | |
366 | &sensor_dev_attr_temp1_max.dev_attr.attr, | |
367 | &sensor_dev_attr_temp1_min.dev_attr.attr, | |
368 | &sensor_dev_attr_temp1_crit.dev_attr.attr, | |
369 | &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, | |
370 | &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, | |
371 | &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, | |
372 | &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, | |
373 | &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, | |
374 | &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, | |
375 | NULL | |
376 | }; | |
377 | ||
378 | static const struct attribute_group adt7x10_group = { | |
379 | .attrs = adt7x10_attributes, | |
380 | }; | |
381 | ||
4b5e536b | 382 | int adt7x10_probe(struct device *dev, const char *name, int irq, |
51c2a487 LPC |
383 | const struct adt7x10_ops *ops) |
384 | { | |
385 | struct adt7x10_data *data; | |
386 | int ret; | |
387 | ||
388 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | |
389 | if (!data) | |
390 | return -ENOMEM; | |
391 | ||
392 | data->ops = ops; | |
393 | data->name = name; | |
394 | ||
395 | dev_set_drvdata(dev, data); | |
396 | mutex_init(&data->update_lock); | |
397 | ||
398 | /* configure as specified */ | |
399 | ret = adt7x10_read_byte(dev, ADT7X10_CONFIG); | |
400 | if (ret < 0) { | |
401 | dev_dbg(dev, "Can't read config? %d\n", ret); | |
402 | return ret; | |
403 | } | |
404 | data->oldconfig = ret; | |
405 | ||
406 | /* | |
407 | * Set to 16 bit resolution, continous conversion and comparator mode. | |
408 | */ | |
409 | data->config = data->oldconfig; | |
4b5e536b LPC |
410 | data->config &= ~(ADT7X10_MODE_MASK | ADT7X10_CT_POLARITY | |
411 | ADT7X10_INT_POLARITY); | |
51c2a487 | 412 | data->config |= ADT7X10_FULL | ADT7X10_RESOLUTION | ADT7X10_EVENT_MODE; |
4b5e536b | 413 | |
51c2a487 LPC |
414 | if (data->config != data->oldconfig) { |
415 | ret = adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config); | |
416 | if (ret) | |
417 | return ret; | |
418 | } | |
419 | dev_dbg(dev, "Config %02x\n", data->config); | |
420 | ||
421 | ret = adt7x10_fill_cache(dev); | |
422 | if (ret) | |
423 | goto exit_restore; | |
424 | ||
425 | /* Register sysfs hooks */ | |
426 | ret = sysfs_create_group(&dev->kobj, &adt7x10_group); | |
427 | if (ret) | |
428 | goto exit_restore; | |
429 | ||
430 | /* | |
431 | * The I2C device will already have it's own 'name' attribute, but for | |
432 | * the SPI device we need to register it. name will only be non NULL if | |
433 | * the device doesn't register the 'name' attribute on its own. | |
434 | */ | |
435 | if (name) { | |
436 | ret = device_create_file(dev, &dev_attr_name); | |
437 | if (ret) | |
438 | goto exit_remove; | |
439 | } | |
440 | ||
441 | data->hwmon_dev = hwmon_device_register(dev); | |
442 | if (IS_ERR(data->hwmon_dev)) { | |
443 | ret = PTR_ERR(data->hwmon_dev); | |
444 | goto exit_remove_name; | |
445 | } | |
446 | ||
4b5e536b LPC |
447 | if (irq > 0) { |
448 | ret = request_threaded_irq(irq, NULL, adt7x10_irq_handler, | |
449 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | |
450 | dev_name(dev), dev); | |
451 | if (ret) | |
452 | goto exit_hwmon_device_unregister; | |
453 | } | |
454 | ||
51c2a487 LPC |
455 | return 0; |
456 | ||
4b5e536b LPC |
457 | exit_hwmon_device_unregister: |
458 | hwmon_device_unregister(data->hwmon_dev); | |
51c2a487 LPC |
459 | exit_remove_name: |
460 | if (name) | |
461 | device_remove_file(dev, &dev_attr_name); | |
462 | exit_remove: | |
463 | sysfs_remove_group(&dev->kobj, &adt7x10_group); | |
464 | exit_restore: | |
465 | adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig); | |
466 | return ret; | |
467 | } | |
468 | EXPORT_SYMBOL_GPL(adt7x10_probe); | |
469 | ||
4b5e536b | 470 | int adt7x10_remove(struct device *dev, int irq) |
51c2a487 LPC |
471 | { |
472 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
473 | ||
4b5e536b LPC |
474 | if (irq > 0) |
475 | free_irq(irq, dev); | |
476 | ||
51c2a487 LPC |
477 | hwmon_device_unregister(data->hwmon_dev); |
478 | if (data->name) | |
479 | device_remove_file(dev, &dev_attr_name); | |
480 | sysfs_remove_group(&dev->kobj, &adt7x10_group); | |
481 | if (data->oldconfig != data->config) | |
482 | adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig); | |
483 | return 0; | |
484 | } | |
485 | EXPORT_SYMBOL_GPL(adt7x10_remove); | |
486 | ||
487 | #ifdef CONFIG_PM_SLEEP | |
488 | ||
489 | static int adt7x10_suspend(struct device *dev) | |
490 | { | |
491 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
492 | ||
493 | return adt7x10_write_byte(dev, ADT7X10_CONFIG, | |
494 | data->config | ADT7X10_PD); | |
495 | } | |
496 | ||
497 | static int adt7x10_resume(struct device *dev) | |
498 | { | |
499 | struct adt7x10_data *data = dev_get_drvdata(dev); | |
500 | ||
501 | return adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config); | |
502 | } | |
503 | ||
504 | SIMPLE_DEV_PM_OPS(adt7x10_dev_pm_ops, adt7x10_suspend, adt7x10_resume); | |
505 | EXPORT_SYMBOL_GPL(adt7x10_dev_pm_ops); | |
506 | ||
507 | #endif /* CONFIG_PM_SLEEP */ | |
508 | ||
509 | MODULE_AUTHOR("Hartmut Knaack"); | |
510 | MODULE_DESCRIPTION("ADT7410/ADT7420, ADT7310/ADT7320 common code"); | |
511 | MODULE_LICENSE("GPL"); |