aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon
diff options
context:
space:
mode:
authorDonggeun Kim <dg77.kim@samsung.com>2011-06-20 03:48:19 -0400
committerGuenter Roeck <guenter.roeck@ericsson.com>2011-07-28 03:17:33 -0400
commitf22aaaa70d8c24e5dc7d23a219c4beace8354b65 (patch)
tree855158946f04f4d4846b7cab573d55652163ec18 /drivers/hwmon
parentdabaa0d2b4085a2037d80a40b86ba215f00b601e (diff)
hwmon: Driver for NTC Thermistors
Add support for NTC Thermistor series. In this release, the following thermistors are supported: NCP15WB473, NCP18WB473, NCP03WB473, and NCP15WL333. This driver is based on the datasheet of MURATA. The driver in the patch does conversion from the raw ADC value (either voltage or resistence) to temperature. In order to use voltage values as input, the circuit schematics should be provided with the platform data. A compensation table for each type of thermistor is provided for the conversion. Signed-off-by: Donggeun Kim <dg77.kim@samsung.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Signed-off-by: KyungMin Park <kyungmin.park@samsung.com> Reviewed-by: Shubhrajyoti D <shubhrajyoti@ti.com> Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com>
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig14
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/ntc_thermistor.c453
3 files changed, 468 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index dec5eee3542..770fc819562 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,20 @@ config SENSORS_MAX6650
777 This driver can also be built as a module. If so, the module 777 This driver can also be built as a module. If so, the module
778 will be called max6650. 778 will be called max6650.
779 779
780config SENSORS_NTC_THERMISTOR
781 tristate "NTC thermistor support"
782 depends on EXPERIMENTAL
783 help
784 This driver supports NTC thermistors sensor reading and its
785 interpretation. The driver can also monitor the temperature and
786 send notifications about the temperature.
787
788 Currently, this driver supports
789 NCP15WB473, NCP18WB473, NCP21WB473, NCP03WB473, and NCP15WL333.
790
791 This driver can also be built as a module. If so, the module
792 will be called ntc-thermistor.
793
780config SENSORS_PC87360 794config SENSORS_PC87360
781 tristate "National Semiconductor PC87360 family" 795 tristate "National Semiconductor PC87360 family"
782 select HWMON_VID 796 select HWMON_VID
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index a6d519e568c..1b7dc770aa6 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_SENSORS_MAX6639) += max6639.o
92obj-$(CONFIG_SENSORS_MAX6642) += max6642.o 92obj-$(CONFIG_SENSORS_MAX6642) += max6642.o
93obj-$(CONFIG_SENSORS_MAX6650) += max6650.o 93obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
94obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o 94obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
95obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o
95obj-$(CONFIG_SENSORS_PC87360) += pc87360.o 96obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
96obj-$(CONFIG_SENSORS_PC87427) += pc87427.o 97obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
97obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o 98obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c
new file mode 100644
index 00000000000..d7926f4336b
--- /dev/null
+++ b/drivers/hwmon/ntc_thermistor.c
@@ -0,0 +1,453 @@
1/*
2 * ntc_thermistor.c - NTC Thermistors
3 *
4 * Copyright (C) 2010 Samsung Electronics
5 * MyungJoo Ham <myungjoo.ham@samsung.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22
23#include <linux/slab.h>
24#include <linux/module.h>
25#include <linux/pm_runtime.h>
26#include <linux/math64.h>
27#include <linux/platform_device.h>
28#include <linux/err.h>
29
30#include <linux/platform_data/ntc_thermistor.h>
31
32#include <linux/hwmon.h>
33#include <linux/hwmon-sysfs.h>
34
35struct ntc_compensation {
36 int temp_C;
37 unsigned int ohm;
38};
39
40/*
41 * A compensation table should be sorted by the values of .ohm
42 * in descending order.
43 * The following compensation tables are from the specification of Murata NTC
44 * Thermistors Datasheet
45 */
46const struct ntc_compensation ncpXXwb473[] = {
47 { .temp_C = -40, .ohm = 1747920 },
48 { .temp_C = -35, .ohm = 1245428 },
49 { .temp_C = -30, .ohm = 898485 },
50 { .temp_C = -25, .ohm = 655802 },
51 { .temp_C = -20, .ohm = 483954 },
52 { .temp_C = -15, .ohm = 360850 },
53 { .temp_C = -10, .ohm = 271697 },
54 { .temp_C = -5, .ohm = 206463 },
55 { .temp_C = 0, .ohm = 158214 },
56 { .temp_C = 5, .ohm = 122259 },
57 { .temp_C = 10, .ohm = 95227 },
58 { .temp_C = 15, .ohm = 74730 },
59 { .temp_C = 20, .ohm = 59065 },
60 { .temp_C = 25, .ohm = 47000 },
61 { .temp_C = 30, .ohm = 37643 },
62 { .temp_C = 35, .ohm = 30334 },
63 { .temp_C = 40, .ohm = 24591 },
64 { .temp_C = 45, .ohm = 20048 },
65 { .temp_C = 50, .ohm = 16433 },
66 { .temp_C = 55, .ohm = 13539 },
67 { .temp_C = 60, .ohm = 11209 },
68 { .temp_C = 65, .ohm = 9328 },
69 { .temp_C = 70, .ohm = 7798 },
70 { .temp_C = 75, .ohm = 6544 },
71 { .temp_C = 80, .ohm = 5518 },
72 { .temp_C = 85, .ohm = 4674 },
73 { .temp_C = 90, .ohm = 3972 },
74 { .temp_C = 95, .ohm = 3388 },
75 { .temp_C = 100, .ohm = 2902 },
76 { .temp_C = 105, .ohm = 2494 },
77 { .temp_C = 110, .ohm = 2150 },
78 { .temp_C = 115, .ohm = 1860 },
79 { .temp_C = 120, .ohm = 1615 },
80 { .temp_C = 125, .ohm = 1406 },
81};
82const struct ntc_compensation ncpXXwl333[] = {
83 { .temp_C = -40, .ohm = 1610154 },
84 { .temp_C = -35, .ohm = 1130850 },
85 { .temp_C = -30, .ohm = 802609 },
86 { .temp_C = -25, .ohm = 575385 },
87 { .temp_C = -20, .ohm = 416464 },
88 { .temp_C = -15, .ohm = 304219 },
89 { .temp_C = -10, .ohm = 224193 },
90 { .temp_C = -5, .ohm = 166623 },
91 { .temp_C = 0, .ohm = 124850 },
92 { .temp_C = 5, .ohm = 94287 },
93 { .temp_C = 10, .ohm = 71747 },
94 { .temp_C = 15, .ohm = 54996 },
95 { .temp_C = 20, .ohm = 42455 },
96 { .temp_C = 25, .ohm = 33000 },
97 { .temp_C = 30, .ohm = 25822 },
98 { .temp_C = 35, .ohm = 20335 },
99 { .temp_C = 40, .ohm = 16115 },
100 { .temp_C = 45, .ohm = 12849 },
101 { .temp_C = 50, .ohm = 10306 },
102 { .temp_C = 55, .ohm = 8314 },
103 { .temp_C = 60, .ohm = 6746 },
104 { .temp_C = 65, .ohm = 5503 },
105 { .temp_C = 70, .ohm = 4513 },
106 { .temp_C = 75, .ohm = 3721 },
107 { .temp_C = 80, .ohm = 3084 },
108 { .temp_C = 85, .ohm = 2569 },
109 { .temp_C = 90, .ohm = 2151 },
110 { .temp_C = 95, .ohm = 1809 },
111 { .temp_C = 100, .ohm = 1529 },
112 { .temp_C = 105, .ohm = 1299 },
113 { .temp_C = 110, .ohm = 1108 },
114 { .temp_C = 115, .ohm = 949 },
115 { .temp_C = 120, .ohm = 817 },
116 { .temp_C = 125, .ohm = 707 },
117};
118
119struct ntc_data {
120 struct device *hwmon_dev;
121 struct ntc_thermistor_platform_data *pdata;
122 const struct ntc_compensation *comp;
123 struct device *dev;
124 int n_comp;
125 char name[PLATFORM_NAME_SIZE];
126};
127
128static inline u64 div64_u64_safe(u64 dividend, u64 divisor)
129{
130 if (divisor == 0 && dividend == 0)
131 return 0;
132 if (divisor == 0)
133 return UINT_MAX;
134 return div64_u64(dividend, divisor);
135}
136
137static unsigned int get_ohm_of_thermistor(struct ntc_data *data,
138 unsigned int uV)
139{
140 struct ntc_thermistor_platform_data *pdata = data->pdata;
141 u64 mV = uV / 1000;
142 u64 pmV = pdata->pullup_uV / 1000;
143 u64 N, puO, pdO;
144 puO = pdata->pullup_ohm;
145 pdO = pdata->pulldown_ohm;
146
147 if (mV == 0) {
148 if (pdata->connect == NTC_CONNECTED_POSITIVE)
149 return UINT_MAX;
150 return 0;
151 }
152 if (mV >= pmV)
153 return (pdata->connect == NTC_CONNECTED_POSITIVE) ?
154 0 : UINT_MAX;
155
156 if (pdata->connect == NTC_CONNECTED_POSITIVE && puO == 0)
157 N = div64_u64_safe(pdO * (pmV - mV), mV);
158 else if (pdata->connect == NTC_CONNECTED_GROUND && pdO == 0)
159 N = div64_u64_safe(puO * mV, pmV - mV);
160 else if (pdata->connect == NTC_CONNECTED_POSITIVE)
161 N = div64_u64_safe(pdO * puO * (pmV - mV),
162 puO * mV - pdO * (pmV - mV));
163 else
164 N = div64_u64_safe(pdO * puO * mV, pdO * (pmV - mV) - puO * mV);
165
166 return (unsigned int) N;
167}
168
169static int lookup_comp(struct ntc_data *data,
170 unsigned int ohm, int *i_low, int *i_high)
171{
172 int start, end, mid = -1;
173
174 /* Do a binary search on compensation table */
175 start = 0;
176 end = data->n_comp;
177
178 while (end > start) {
179 mid = start + (end - start) / 2;
180 if (data->comp[mid].ohm < ohm)
181 end = mid;
182 else if (data->comp[mid].ohm > ohm)
183 start = mid + 1;
184 else
185 break;
186 }
187
188 if (mid == 0) {
189 if (data->comp[mid].ohm > ohm) {
190 *i_high = mid;
191 *i_low = mid + 1;
192 return 0;
193 } else {
194 *i_low = mid;
195 *i_high = -1;
196 return -EINVAL;
197 }
198 }
199 if (mid == (data->n_comp - 1)) {
200 if (data->comp[mid].ohm <= ohm) {
201 *i_low = mid;
202 *i_high = mid - 1;
203 return 0;
204 } else {
205 *i_low = -1;
206 *i_high = mid;
207 return -EINVAL;
208 }
209 }
210
211 if (data->comp[mid].ohm <= ohm) {
212 *i_low = mid;
213 *i_high = mid - 1;
214 }
215 if (data->comp[mid].ohm > ohm) {
216 *i_low = mid + 1;
217 *i_high = mid;
218 }
219
220 return 0;
221}
222
223static int get_temp_mC(struct ntc_data *data, unsigned int ohm, int *temp)
224{
225 int low, high;
226 int ret;
227
228 ret = lookup_comp(data, ohm, &low, &high);
229 if (ret) {
230 /* Unable to use linear approximation */
231 if (low != -1)
232 *temp = data->comp[low].temp_C * 1000;
233 else if (high != -1)
234 *temp = data->comp[high].temp_C * 1000;
235 else
236 return ret;
237 } else {
238 *temp = data->comp[low].temp_C * 1000 +
239 ((data->comp[high].temp_C - data->comp[low].temp_C) *
240 1000 * ((int)ohm - (int)data->comp[low].ohm)) /
241 ((int)data->comp[high].ohm - (int)data->comp[low].ohm);
242 }
243
244 return 0;
245}
246
247static int ntc_thermistor_read(struct ntc_data *data, int *temp)
248{
249 int ret;
250 int read_ohm, read_uV;
251 unsigned int ohm = 0;
252
253 if (data->pdata->read_ohm) {
254 read_ohm = data->pdata->read_ohm();
255 if (read_ohm < 0)
256 return read_ohm;
257 ohm = (unsigned int)read_ohm;
258 }
259
260 if (data->pdata->read_uV) {
261 read_uV = data->pdata->read_uV();
262 if (read_uV < 0)
263 return read_uV;
264 ohm = get_ohm_of_thermistor(data, (unsigned int)read_uV);
265 }
266
267 ret = get_temp_mC(data, ohm, temp);
268 if (ret) {
269 dev_dbg(data->dev, "Sensor reading function not available.\n");
270 return ret;
271 }
272
273 return 0;
274}
275
276static ssize_t ntc_show_name(struct device *dev,
277 struct device_attribute *attr, char *buf)
278{
279 struct ntc_data *data = dev_get_drvdata(dev);
280
281 return sprintf(buf, "%s\n", data->name);
282}
283
284static ssize_t ntc_show_type(struct device *dev,
285 struct device_attribute *attr, char *buf)
286{
287 return sprintf(buf, "4\n");
288}
289
290static ssize_t ntc_show_temp(struct device *dev,
291 struct device_attribute *attr, char *buf)
292{
293 struct ntc_data *data = dev_get_drvdata(dev);
294 int temp, ret;
295
296 ret = ntc_thermistor_read(data, &temp);
297 if (ret)
298 return ret;
299 return sprintf(buf, "%d\n", temp);
300}
301
302static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, ntc_show_type, NULL, 0);
303static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ntc_show_temp, NULL, 0);
304static DEVICE_ATTR(name, S_IRUGO, ntc_show_name, NULL);
305
306static struct attribute *ntc_attributes[] = {
307 &dev_attr_name.attr,
308 &sensor_dev_attr_temp1_type.dev_attr.attr,
309 &sensor_dev_attr_temp1_input.dev_attr.attr,
310 NULL,
311};
312
313static const struct attribute_group ntc_attr_group = {
314 .attrs = ntc_attributes,
315};
316
317static int __devinit ntc_thermistor_probe(struct platform_device *pdev)
318{
319 struct ntc_data *data;
320 struct ntc_thermistor_platform_data *pdata = pdev->dev.platform_data;
321 int ret = 0;
322
323 if (!pdata) {
324 dev_err(&pdev->dev, "No platform init data supplied.\n");
325 return -ENODEV;
326 }
327
328 /* Either one of the two is required. */
329 if (!pdata->read_uV && !pdata->read_ohm) {
330 dev_err(&pdev->dev, "Both read_uV and read_ohm missing."
331 "Need either one of the two.\n");
332 return -EINVAL;
333 }
334
335 if (pdata->read_uV && pdata->read_ohm) {
336 dev_warn(&pdev->dev, "Only one of read_uV and read_ohm "
337 "is needed; ignoring read_uV.\n");
338 pdata->read_uV = NULL;
339 }
340
341 if (pdata->read_uV && (pdata->pullup_uV == 0 ||
342 (pdata->pullup_ohm == 0 && pdata->connect ==
343 NTC_CONNECTED_GROUND) ||
344 (pdata->pulldown_ohm == 0 && pdata->connect ==
345 NTC_CONNECTED_POSITIVE) ||
346 (pdata->connect != NTC_CONNECTED_POSITIVE &&
347 pdata->connect != NTC_CONNECTED_GROUND))) {
348 dev_err(&pdev->dev, "Required data to use read_uV not "
349 "supplied.\n");
350 return -EINVAL;
351 }
352
353 data = kzalloc(sizeof(struct ntc_data), GFP_KERNEL);
354 if (!data)
355 return -ENOMEM;
356
357 data->dev = &pdev->dev;
358 data->pdata = pdata;
359 strncpy(data->name, pdev->id_entry->name, PLATFORM_NAME_SIZE);
360
361 switch (pdev->id_entry->driver_data) {
362 case TYPE_NCPXXWB473:
363 data->comp = ncpXXwb473;
364 data->n_comp = ARRAY_SIZE(ncpXXwb473);
365 break;
366 case TYPE_NCPXXWL333:
367 data->comp = ncpXXwl333;
368 data->n_comp = ARRAY_SIZE(ncpXXwl333);
369 break;
370 default:
371 dev_err(&pdev->dev, "Unknown device type: %lu(%s)\n",
372 pdev->id_entry->driver_data,
373 pdev->id_entry->name);
374 ret = -EINVAL;
375 goto err;
376 }
377
378 platform_set_drvdata(pdev, data);
379
380 ret = sysfs_create_group(&data->dev->kobj, &ntc_attr_group);
381 if (ret) {
382 dev_err(data->dev, "unable to create sysfs files\n");
383 goto err;
384 }
385
386 data->hwmon_dev = hwmon_device_register(data->dev);
387 if (IS_ERR_OR_NULL(data->hwmon_dev)) {
388 dev_err(data->dev, "unable to register as hwmon device.\n");
389 ret = -EINVAL;
390 goto err_after_sysfs;
391 }
392
393 dev_info(&pdev->dev, "Thermistor %s:%d (type: %s/%lu) successfully probed.\n",
394 pdev->name, pdev->id, pdev->id_entry->name,
395 pdev->id_entry->driver_data);
396 return 0;
397err_after_sysfs:
398 sysfs_remove_group(&data->dev->kobj, &ntc_attr_group);
399err:
400 kfree(data);
401 return ret;
402}
403
404static int __devexit ntc_thermistor_remove(struct platform_device *pdev)
405{
406 struct ntc_data *data = platform_get_drvdata(pdev);
407
408 hwmon_device_unregister(data->hwmon_dev);
409 sysfs_remove_group(&data->dev->kobj, &ntc_attr_group);
410 platform_set_drvdata(pdev, NULL);
411
412 kfree(data);
413
414 return 0;
415}
416
417static const struct platform_device_id ntc_thermistor_id[] = {
418 { "ncp15wb473", TYPE_NCPXXWB473 },
419 { "ncp18wb473", TYPE_NCPXXWB473 },
420 { "ncp21wb473", TYPE_NCPXXWB473 },
421 { "ncp03wb473", TYPE_NCPXXWB473 },
422 { "ncp15wl333", TYPE_NCPXXWL333 },
423 { },
424};
425
426static struct platform_driver ntc_thermistor_driver = {
427 .driver = {
428 .name = "ntc-thermistor",
429 .owner = THIS_MODULE,
430 },
431 .probe = ntc_thermistor_probe,
432 .remove = __devexit_p(ntc_thermistor_remove),
433 .id_table = ntc_thermistor_id,
434};
435
436static int __init ntc_thermistor_init(void)
437{
438 return platform_driver_register(&ntc_thermistor_driver);
439}
440
441module_init(ntc_thermistor_init);
442
443static void __exit ntc_thermistor_cleanup(void)
444{
445 platform_driver_unregister(&ntc_thermistor_driver);
446}
447
448module_exit(ntc_thermistor_cleanup);
449
450MODULE_DESCRIPTION("NTC Thermistor Driver");
451MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
452MODULE_LICENSE("GPL");
453MODULE_ALIAS("platform:ntc-thermistor");