aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon
diff options
context:
space:
mode:
authorDonggeun Kim <dg77.kim@samsung.com>2011-09-07 05:49:08 -0400
committerGuenter Roeck <guenter.roeck@ericsson.com>2011-10-24 14:09:35 -0400
commit9d97e5c81e15afaef65d00f077f863c94f750839 (patch)
tree01df55fd37f2432ec03c29bf55cc97dfdb6f1b60 /drivers/hwmon
parent5cf231a346fb80d7945aa46e90186a068a3a001b (diff)
hwmon: Add driver for EXYNOS4 TMU
This patch allows to read temperature from TMU(Thermal Management Unit) of SAMSUNG EXYNOS4 series of SoC. 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> Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com>
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig10
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/exynos4_tmu.c524
3 files changed, 535 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 0b62c3c6b7ce..c6fb7611dd10 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -303,6 +303,16 @@ config SENSORS_DS1621
303 This driver can also be built as a module. If so, the module 303 This driver can also be built as a module. If so, the module
304 will be called ds1621. 304 will be called ds1621.
305 305
306config SENSORS_EXYNOS4_TMU
307 tristate "Temperature sensor on Samsung EXYNOS4"
308 depends on EXYNOS4_DEV_TMU
309 help
310 If you say yes here you get support for TMU (Thermal Managment
311 Unit) on SAMSUNG EXYNOS4 series of SoC.
312
313 This driver can also be built as a module. If so, the module
314 will be called exynos4-tmu.
315
306config SENSORS_I5K_AMB 316config SENSORS_I5K_AMB
307 tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets" 317 tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
308 depends on PCI && EXPERIMENTAL 318 depends on PCI && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 3c9ccefea791..dbd8963ec7fa 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
47obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o 47obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
48obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o 48obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
49obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o 49obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o
50obj-$(CONFIG_SENSORS_EXYNOS4_TMU) += exynos4_tmu.o
50obj-$(CONFIG_SENSORS_F71805F) += f71805f.o 51obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
51obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o 52obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
52obj-$(CONFIG_SENSORS_F75375S) += f75375s.o 53obj-$(CONFIG_SENSORS_F75375S) += f75375s.o
diff --git a/drivers/hwmon/exynos4_tmu.c b/drivers/hwmon/exynos4_tmu.c
new file mode 100644
index 000000000000..0170c90c635e
--- /dev/null
+++ b/drivers/hwmon/exynos4_tmu.c
@@ -0,0 +1,524 @@
1/*
2 * exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit)
3 *
4 * Copyright (C) 2011 Samsung Electronics
5 * Donggeun Kim <dg77.kim@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/module.h>
24#include <linux/err.h>
25#include <linux/kernel.h>
26#include <linux/slab.h>
27#include <linux/platform_device.h>
28#include <linux/interrupt.h>
29#include <linux/clk.h>
30#include <linux/workqueue.h>
31#include <linux/sysfs.h>
32#include <linux/kobject.h>
33#include <linux/io.h>
34#include <linux/mutex.h>
35
36#include <linux/hwmon.h>
37#include <linux/hwmon-sysfs.h>
38
39#include <linux/platform_data/exynos4_tmu.h>
40
41#define EXYNOS4_TMU_REG_TRIMINFO 0x0
42#define EXYNOS4_TMU_REG_CONTROL 0x20
43#define EXYNOS4_TMU_REG_STATUS 0x28
44#define EXYNOS4_TMU_REG_CURRENT_TEMP 0x40
45#define EXYNOS4_TMU_REG_THRESHOLD_TEMP 0x44
46#define EXYNOS4_TMU_REG_TRIG_LEVEL0 0x50
47#define EXYNOS4_TMU_REG_TRIG_LEVEL1 0x54
48#define EXYNOS4_TMU_REG_TRIG_LEVEL2 0x58
49#define EXYNOS4_TMU_REG_TRIG_LEVEL3 0x5C
50#define EXYNOS4_TMU_REG_PAST_TEMP0 0x60
51#define EXYNOS4_TMU_REG_PAST_TEMP1 0x64
52#define EXYNOS4_TMU_REG_PAST_TEMP2 0x68
53#define EXYNOS4_TMU_REG_PAST_TEMP3 0x6C
54#define EXYNOS4_TMU_REG_INTEN 0x70
55#define EXYNOS4_TMU_REG_INTSTAT 0x74
56#define EXYNOS4_TMU_REG_INTCLEAR 0x78
57
58#define EXYNOS4_TMU_GAIN_SHIFT 8
59#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT 24
60
61#define EXYNOS4_TMU_TRIM_TEMP_MASK 0xff
62#define EXYNOS4_TMU_CORE_ON 3
63#define EXYNOS4_TMU_CORE_OFF 2
64#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET 50
65#define EXYNOS4_TMU_TRIG_LEVEL0_MASK 0x1
66#define EXYNOS4_TMU_TRIG_LEVEL1_MASK 0x10
67#define EXYNOS4_TMU_TRIG_LEVEL2_MASK 0x100
68#define EXYNOS4_TMU_TRIG_LEVEL3_MASK 0x1000
69#define EXYNOS4_TMU_INTCLEAR_VAL 0x1111
70
71struct exynos4_tmu_data {
72 struct exynos4_tmu_platform_data *pdata;
73 struct device *hwmon_dev;
74 struct resource *mem;
75 void __iomem *base;
76 int irq;
77 struct work_struct irq_work;
78 struct mutex lock;
79 struct clk *clk;
80 u8 temp_error1, temp_error2;
81};
82
83/*
84 * TMU treats temperature as a mapped temperature code.
85 * The temperature is converted differently depending on the calibration type.
86 */
87static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
88{
89 struct exynos4_tmu_platform_data *pdata = data->pdata;
90 int temp_code;
91
92 /* temp should range between 25 and 125 */
93 if (temp < 25 || temp > 125) {
94 temp_code = -EINVAL;
95 goto out;
96 }
97
98 switch (pdata->cal_type) {
99 case TYPE_TWO_POINT_TRIMMING:
100 temp_code = (temp - 25) *
101 (data->temp_error2 - data->temp_error1) /
102 (85 - 25) + data->temp_error1;
103 break;
104 case TYPE_ONE_POINT_TRIMMING:
105 temp_code = temp + data->temp_error1 - 25;
106 break;
107 default:
108 temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
109 break;
110 }
111out:
112 return temp_code;
113}
114
115/*
116 * Calculate a temperature value from a temperature code.
117 * The unit of the temperature is degree Celsius.
118 */
119static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
120{
121 struct exynos4_tmu_platform_data *pdata = data->pdata;
122 int temp;
123
124 /* temp_code should range between 75 and 175 */
125 if (temp_code < 75 || temp_code > 175) {
126 temp = -ENODATA;
127 goto out;
128 }
129
130 switch (pdata->cal_type) {
131 case TYPE_TWO_POINT_TRIMMING:
132 temp = (temp_code - data->temp_error1) * (85 - 25) /
133 (data->temp_error2 - data->temp_error1) + 25;
134 break;
135 case TYPE_ONE_POINT_TRIMMING:
136 temp = temp_code - data->temp_error1 + 25;
137 break;
138 default:
139 temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
140 break;
141 }
142out:
143 return temp;
144}
145
146static int exynos4_tmu_initialize(struct platform_device *pdev)
147{
148 struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
149 struct exynos4_tmu_platform_data *pdata = data->pdata;
150 unsigned int status, trim_info;
151 int ret = 0, threshold_code;
152
153 mutex_lock(&data->lock);
154 clk_enable(data->clk);
155
156 status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
157 if (!status) {
158 ret = -EBUSY;
159 goto out;
160 }
161
162 /* Save trimming info in order to perform calibration */
163 trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
164 data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
165 data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
166
167 /* Write temperature code for threshold */
168 threshold_code = temp_to_code(data, pdata->threshold);
169 if (threshold_code < 0) {
170 ret = threshold_code;
171 goto out;
172 }
173 writeb(threshold_code,
174 data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
175
176 writeb(pdata->trigger_levels[0],
177 data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
178 writeb(pdata->trigger_levels[1],
179 data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
180 writeb(pdata->trigger_levels[2],
181 data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
182 writeb(pdata->trigger_levels[3],
183 data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
184
185 writel(EXYNOS4_TMU_INTCLEAR_VAL,
186 data->base + EXYNOS4_TMU_REG_INTCLEAR);
187out:
188 clk_disable(data->clk);
189 mutex_unlock(&data->lock);
190
191 return ret;
192}
193
194static void exynos4_tmu_control(struct platform_device *pdev, bool on)
195{
196 struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
197 struct exynos4_tmu_platform_data *pdata = data->pdata;
198 unsigned int con, interrupt_en;
199
200 mutex_lock(&data->lock);
201 clk_enable(data->clk);
202
203 con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
204 pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
205 if (on) {
206 con |= EXYNOS4_TMU_CORE_ON;
207 interrupt_en = pdata->trigger_level3_en << 12 |
208 pdata->trigger_level2_en << 8 |
209 pdata->trigger_level1_en << 4 |
210 pdata->trigger_level0_en;
211 } else {
212 con |= EXYNOS4_TMU_CORE_OFF;
213 interrupt_en = 0; /* Disable all interrupts */
214 }
215 writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
216 writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
217
218 clk_disable(data->clk);
219 mutex_unlock(&data->lock);
220}
221
222static int exynos4_tmu_read(struct exynos4_tmu_data *data)
223{
224 u8 temp_code;
225 int temp;
226
227 mutex_lock(&data->lock);
228 clk_enable(data->clk);
229
230 temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
231 temp = code_to_temp(data, temp_code);
232
233 clk_disable(data->clk);
234 mutex_unlock(&data->lock);
235
236 return temp;
237}
238
239static void exynos4_tmu_work(struct work_struct *work)
240{
241 struct exynos4_tmu_data *data = container_of(work,
242 struct exynos4_tmu_data, irq_work);
243
244 mutex_lock(&data->lock);
245 clk_enable(data->clk);
246
247 writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
248
249 kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE);
250
251 enable_irq(data->irq);
252
253 clk_disable(data->clk);
254 mutex_unlock(&data->lock);
255}
256
257static irqreturn_t exynos4_tmu_irq(int irq, void *id)
258{
259 struct exynos4_tmu_data *data = id;
260
261 disable_irq_nosync(irq);
262 schedule_work(&data->irq_work);
263
264 return IRQ_HANDLED;
265}
266
267static ssize_t exynos4_tmu_show_name(struct device *dev,
268 struct device_attribute *attr, char *buf)
269{
270 return sprintf(buf, "exynos4-tmu\n");
271}
272
273static ssize_t exynos4_tmu_show_temp(struct device *dev,
274 struct device_attribute *attr, char *buf)
275{
276 struct exynos4_tmu_data *data = dev_get_drvdata(dev);
277 int ret;
278
279 ret = exynos4_tmu_read(data);
280 if (ret < 0)
281 return ret;
282
283 /* convert from degree Celsius to millidegree Celsius */
284 return sprintf(buf, "%d\n", ret * 1000);
285}
286
287static ssize_t exynos4_tmu_show_alarm(struct device *dev,
288 struct device_attribute *devattr, char *buf)
289{
290 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
291 struct exynos4_tmu_data *data = dev_get_drvdata(dev);
292 struct exynos4_tmu_platform_data *pdata = data->pdata;
293 int temp;
294 unsigned int trigger_level;
295
296 temp = exynos4_tmu_read(data);
297 if (temp < 0)
298 return temp;
299
300 trigger_level = pdata->threshold + pdata->trigger_levels[attr->index];
301
302 return sprintf(buf, "%d\n", !!(temp > trigger_level));
303}
304
305static ssize_t exynos4_tmu_show_level(struct device *dev,
306 struct device_attribute *devattr, char *buf)
307{
308 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
309 struct exynos4_tmu_data *data = dev_get_drvdata(dev);
310 struct exynos4_tmu_platform_data *pdata = data->pdata;
311 unsigned int temp = pdata->threshold +
312 pdata->trigger_levels[attr->index];
313
314 return sprintf(buf, "%u\n", temp * 1000);
315}
316
317static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL);
318static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0);
319
320static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
321 exynos4_tmu_show_alarm, NULL, 1);
322static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
323 exynos4_tmu_show_alarm, NULL, 2);
324static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO,
325 exynos4_tmu_show_alarm, NULL, 3);
326
327static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1);
328static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2);
329static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO,
330 exynos4_tmu_show_level, NULL, 3);
331
332static struct attribute *exynos4_tmu_attributes[] = {
333 &dev_attr_name.attr,
334 &sensor_dev_attr_temp1_input.dev_attr.attr,
335 &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
336 &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
337 &sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr,
338 &sensor_dev_attr_temp1_max.dev_attr.attr,
339 &sensor_dev_attr_temp1_crit.dev_attr.attr,
340 &sensor_dev_attr_temp1_emergency.dev_attr.attr,
341 NULL,
342};
343
344static const struct attribute_group exynos4_tmu_attr_group = {
345 .attrs = exynos4_tmu_attributes,
346};
347
348static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
349{
350 struct exynos4_tmu_data *data;
351 struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
352 int ret;
353
354 if (!pdata) {
355 dev_err(&pdev->dev, "No platform init data supplied.\n");
356 return -ENODEV;
357 }
358
359 data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
360 if (!data) {
361 dev_err(&pdev->dev, "Failed to allocate driver structure\n");
362 return -ENOMEM;
363 }
364
365 data->irq = platform_get_irq(pdev, 0);
366 if (data->irq < 0) {
367 ret = data->irq;
368 dev_err(&pdev->dev, "Failed to get platform irq\n");
369 goto err_free;
370 }
371
372 INIT_WORK(&data->irq_work, exynos4_tmu_work);
373
374 data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
375 if (!data->mem) {
376 ret = -ENOENT;
377 dev_err(&pdev->dev, "Failed to get platform resource\n");
378 goto err_free;
379 }
380
381 data->mem = request_mem_region(data->mem->start,
382 resource_size(data->mem), pdev->name);
383 if (!data->mem) {
384 ret = -ENODEV;
385 dev_err(&pdev->dev, "Failed to request memory region\n");
386 goto err_free;
387 }
388
389 data->base = ioremap(data->mem->start, resource_size(data->mem));
390 if (!data->base) {
391 ret = -ENODEV;
392 dev_err(&pdev->dev, "Failed to ioremap memory\n");
393 goto err_mem_region;
394 }
395
396 ret = request_irq(data->irq, exynos4_tmu_irq,
397 IRQF_DISABLED | IRQF_TRIGGER_RISING,
398 "exynos4-tmu", data);
399 if (ret) {
400 dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
401 goto err_io_remap;
402 }
403
404 data->clk = clk_get(NULL, "tmu_apbif");
405 if (IS_ERR(data->clk)) {
406 ret = PTR_ERR(data->clk);
407 dev_err(&pdev->dev, "Failed to get clock\n");
408 goto err_irq;
409 }
410
411 data->pdata = pdata;
412 platform_set_drvdata(pdev, data);
413 mutex_init(&data->lock);
414
415 ret = exynos4_tmu_initialize(pdev);
416 if (ret) {
417 dev_err(&pdev->dev, "Failed to initialize TMU\n");
418 goto err_clk;
419 }
420
421 ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
422 if (ret) {
423 dev_err(&pdev->dev, "Failed to create sysfs group\n");
424 goto err_clk;
425 }
426
427 data->hwmon_dev = hwmon_device_register(&pdev->dev);
428 if (IS_ERR(data->hwmon_dev)) {
429 ret = PTR_ERR(data->hwmon_dev);
430 dev_err(&pdev->dev, "Failed to register hwmon device\n");
431 goto err_create_group;
432 }
433
434 exynos4_tmu_control(pdev, true);
435
436 return 0;
437
438err_create_group:
439 sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
440err_clk:
441 platform_set_drvdata(pdev, NULL);
442 clk_put(data->clk);
443err_irq:
444 free_irq(data->irq, data);
445err_io_remap:
446 iounmap(data->base);
447err_mem_region:
448 release_mem_region(data->mem->start, resource_size(data->mem));
449err_free:
450 kfree(data);
451
452 return ret;
453}
454
455static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
456{
457 struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
458
459 exynos4_tmu_control(pdev, false);
460
461 hwmon_device_unregister(data->hwmon_dev);
462 sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
463
464 clk_put(data->clk);
465
466 free_irq(data->irq, data);
467
468 iounmap(data->base);
469 release_mem_region(data->mem->start, resource_size(data->mem));
470
471 platform_set_drvdata(pdev, NULL);
472
473 kfree(data);
474
475 return 0;
476}
477
478#ifdef CONFIG_PM
479static int exynos4_tmu_suspend(struct platform_device *pdev, pm_message_t state)
480{
481 exynos4_tmu_control(pdev, false);
482
483 return 0;
484}
485
486static int exynos4_tmu_resume(struct platform_device *pdev)
487{
488 exynos4_tmu_initialize(pdev);
489 exynos4_tmu_control(pdev, true);
490
491 return 0;
492}
493#else
494#define exynos4_tmu_suspend NULL
495#define exynos4_tmu_resume NULL
496#endif
497
498static struct platform_driver exynos4_tmu_driver = {
499 .driver = {
500 .name = "exynos4-tmu",
501 .owner = THIS_MODULE,
502 },
503 .probe = exynos4_tmu_probe,
504 .remove = __devexit_p(exynos4_tmu_remove),
505 .suspend = exynos4_tmu_suspend,
506 .resume = exynos4_tmu_resume,
507};
508
509static int __init exynos4_tmu_driver_init(void)
510{
511 return platform_driver_register(&exynos4_tmu_driver);
512}
513module_init(exynos4_tmu_driver_init);
514
515static void __exit exynos4_tmu_driver_exit(void)
516{
517 platform_driver_unregister(&exynos4_tmu_driver);
518}
519module_exit(exynos4_tmu_driver_exit);
520
521MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
522MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
523MODULE_LICENSE("GPL");
524MODULE_ALIAS("platform:exynos4-tmu");