]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/thermal/exynos_thermal.c
556d15b4b029f8cc269040682b6b4dd50c48ac1a
[karo-tx-linux.git] / drivers / thermal / exynos_thermal.c
1 /*
2  * exynos_thermal.c - Samsung EXYNOS TMU (Thermal Management Unit)
3  *
4  *  Copyright (C) 2011 Samsung Electronics
5  *  Donggeun Kim <dg77.kim@samsung.com>
6  *  Amit Daniel Kachhap <amit.kachhap@linaro.org>
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include <linux/module.h>
25 #include <linux/err.h>
26 #include <linux/kernel.h>
27 #include <linux/slab.h>
28 #include <linux/platform_device.h>
29 #include <linux/interrupt.h>
30 #include <linux/clk.h>
31 #include <linux/workqueue.h>
32 #include <linux/sysfs.h>
33 #include <linux/kobject.h>
34 #include <linux/io.h>
35 #include <linux/mutex.h>
36
37 #include <linux/platform_data/exynos_thermal.h>
38
39 #define EXYNOS4_TMU_REG_TRIMINFO        0x0
40 #define EXYNOS4_TMU_REG_CONTROL         0x20
41 #define EXYNOS4_TMU_REG_STATUS          0x28
42 #define EXYNOS4_TMU_REG_CURRENT_TEMP    0x40
43 #define EXYNOS4_TMU_REG_THRESHOLD_TEMP  0x44
44 #define EXYNOS4_TMU_REG_TRIG_LEVEL0     0x50
45 #define EXYNOS4_TMU_REG_TRIG_LEVEL1     0x54
46 #define EXYNOS4_TMU_REG_TRIG_LEVEL2     0x58
47 #define EXYNOS4_TMU_REG_TRIG_LEVEL3     0x5C
48 #define EXYNOS4_TMU_REG_PAST_TEMP0      0x60
49 #define EXYNOS4_TMU_REG_PAST_TEMP1      0x64
50 #define EXYNOS4_TMU_REG_PAST_TEMP2      0x68
51 #define EXYNOS4_TMU_REG_PAST_TEMP3      0x6C
52 #define EXYNOS4_TMU_REG_INTEN           0x70
53 #define EXYNOS4_TMU_REG_INTSTAT         0x74
54 #define EXYNOS4_TMU_REG_INTCLEAR        0x78
55
56 #define EXYNOS4_TMU_GAIN_SHIFT          8
57 #define EXYNOS4_TMU_REF_VOLTAGE_SHIFT   24
58
59 #define EXYNOS4_TMU_TRIM_TEMP_MASK      0xff
60 #define EXYNOS4_TMU_CORE_ON     3
61 #define EXYNOS4_TMU_CORE_OFF    2
62 #define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET     50
63 #define EXYNOS4_TMU_TRIG_LEVEL0_MASK    0x1
64 #define EXYNOS4_TMU_TRIG_LEVEL1_MASK    0x10
65 #define EXYNOS4_TMU_TRIG_LEVEL2_MASK    0x100
66 #define EXYNOS4_TMU_TRIG_LEVEL3_MASK    0x1000
67 #define EXYNOS4_TMU_INTCLEAR_VAL        0x1111
68
69 struct exynos4_tmu_data {
70         struct exynos4_tmu_platform_data *pdata;
71         struct resource *mem;
72         void __iomem *base;
73         int irq;
74         struct work_struct irq_work;
75         struct mutex lock;
76         struct clk *clk;
77         u8 temp_error1, temp_error2;
78 };
79
80 /*
81  * TMU treats temperature as a mapped temperature code.
82  * The temperature is converted differently depending on the calibration type.
83  */
84 static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
85 {
86         struct exynos4_tmu_platform_data *pdata = data->pdata;
87         int temp_code;
88
89         /* temp should range between 25 and 125 */
90         if (temp < 25 || temp > 125) {
91                 temp_code = -EINVAL;
92                 goto out;
93         }
94
95         switch (pdata->cal_type) {
96         case TYPE_TWO_POINT_TRIMMING:
97                 temp_code = (temp - 25) *
98                     (data->temp_error2 - data->temp_error1) /
99                     (85 - 25) + data->temp_error1;
100                 break;
101         case TYPE_ONE_POINT_TRIMMING:
102                 temp_code = temp + data->temp_error1 - 25;
103                 break;
104         default:
105                 temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
106                 break;
107         }
108 out:
109         return temp_code;
110 }
111
112 /*
113  * Calculate a temperature value from a temperature code.
114  * The unit of the temperature is degree Celsius.
115  */
116 static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
117 {
118         struct exynos4_tmu_platform_data *pdata = data->pdata;
119         int temp;
120
121         /* temp_code should range between 75 and 175 */
122         if (temp_code < 75 || temp_code > 175) {
123                 temp = -ENODATA;
124                 goto out;
125         }
126
127         switch (pdata->cal_type) {
128         case TYPE_TWO_POINT_TRIMMING:
129                 temp = (temp_code - data->temp_error1) * (85 - 25) /
130                     (data->temp_error2 - data->temp_error1) + 25;
131                 break;
132         case TYPE_ONE_POINT_TRIMMING:
133                 temp = temp_code - data->temp_error1 + 25;
134                 break;
135         default:
136                 temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
137                 break;
138         }
139 out:
140         return temp;
141 }
142
143 static int exynos4_tmu_initialize(struct platform_device *pdev)
144 {
145         struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
146         struct exynos4_tmu_platform_data *pdata = data->pdata;
147         unsigned int status, trim_info;
148         int ret = 0, threshold_code;
149
150         mutex_lock(&data->lock);
151         clk_enable(data->clk);
152
153         status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
154         if (!status) {
155                 ret = -EBUSY;
156                 goto out;
157         }
158
159         /* Save trimming info in order to perform calibration */
160         trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
161         data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
162         data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
163
164         /* Write temperature code for threshold */
165         threshold_code = temp_to_code(data, pdata->threshold);
166         if (threshold_code < 0) {
167                 ret = threshold_code;
168                 goto out;
169         }
170         writeb(threshold_code,
171                 data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
172
173         writeb(pdata->trigger_levels[0],
174                 data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
175         writeb(pdata->trigger_levels[1],
176                 data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
177         writeb(pdata->trigger_levels[2],
178                 data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
179         writeb(pdata->trigger_levels[3],
180                 data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
181
182         writel(EXYNOS4_TMU_INTCLEAR_VAL,
183                 data->base + EXYNOS4_TMU_REG_INTCLEAR);
184 out:
185         clk_disable(data->clk);
186         mutex_unlock(&data->lock);
187
188         return ret;
189 }
190
191 static void exynos4_tmu_control(struct platform_device *pdev, bool on)
192 {
193         struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
194         struct exynos4_tmu_platform_data *pdata = data->pdata;
195         unsigned int con, interrupt_en;
196
197         mutex_lock(&data->lock);
198         clk_enable(data->clk);
199
200         con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
201                 pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
202         if (on) {
203                 con |= EXYNOS4_TMU_CORE_ON;
204                 interrupt_en = pdata->trigger_level3_en << 12 |
205                         pdata->trigger_level2_en << 8 |
206                         pdata->trigger_level1_en << 4 |
207                         pdata->trigger_level0_en;
208         } else {
209                 con |= EXYNOS4_TMU_CORE_OFF;
210                 interrupt_en = 0; /* Disable all interrupts */
211         }
212         writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
213         writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
214
215         clk_disable(data->clk);
216         mutex_unlock(&data->lock);
217 }
218
219 static int exynos4_tmu_read(struct exynos4_tmu_data *data)
220 {
221         u8 temp_code;
222         int temp;
223
224         mutex_lock(&data->lock);
225         clk_enable(data->clk);
226
227         temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
228         temp = code_to_temp(data, temp_code);
229
230         clk_disable(data->clk);
231         mutex_unlock(&data->lock);
232
233         return temp;
234 }
235
236 static void exynos4_tmu_work(struct work_struct *work)
237 {
238         struct exynos4_tmu_data *data = container_of(work,
239                         struct exynos4_tmu_data, irq_work);
240
241         mutex_lock(&data->lock);
242         clk_enable(data->clk);
243
244         writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
245
246         enable_irq(data->irq);
247
248         clk_disable(data->clk);
249         mutex_unlock(&data->lock);
250 }
251
252 static irqreturn_t exynos4_tmu_irq(int irq, void *id)
253 {
254         struct exynos4_tmu_data *data = id;
255
256         disable_irq_nosync(irq);
257         schedule_work(&data->irq_work);
258
259         return IRQ_HANDLED;
260 }
261
262 static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
263 {
264         struct exynos4_tmu_data *data;
265         struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
266         int ret;
267
268         if (!pdata) {
269                 dev_err(&pdev->dev, "No platform init data supplied.\n");
270                 return -ENODEV;
271         }
272
273         data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
274         if (!data) {
275                 dev_err(&pdev->dev, "Failed to allocate driver structure\n");
276                 return -ENOMEM;
277         }
278
279         data->irq = platform_get_irq(pdev, 0);
280         if (data->irq < 0) {
281                 ret = data->irq;
282                 dev_err(&pdev->dev, "Failed to get platform irq\n");
283                 goto err_free;
284         }
285
286         INIT_WORK(&data->irq_work, exynos4_tmu_work);
287
288         data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
289         if (!data->mem) {
290                 ret = -ENOENT;
291                 dev_err(&pdev->dev, "Failed to get platform resource\n");
292                 goto err_free;
293         }
294
295         data->mem = request_mem_region(data->mem->start,
296                         resource_size(data->mem), pdev->name);
297         if (!data->mem) {
298                 ret = -ENODEV;
299                 dev_err(&pdev->dev, "Failed to request memory region\n");
300                 goto err_free;
301         }
302
303         data->base = ioremap(data->mem->start, resource_size(data->mem));
304         if (!data->base) {
305                 ret = -ENODEV;
306                 dev_err(&pdev->dev, "Failed to ioremap memory\n");
307                 goto err_mem_region;
308         }
309
310         ret = request_irq(data->irq, exynos4_tmu_irq,
311                 IRQF_TRIGGER_RISING,
312                 "exynos4-tmu", data);
313         if (ret) {
314                 dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
315                 goto err_io_remap;
316         }
317
318         data->clk = clk_get(NULL, "tmu_apbif");
319         if (IS_ERR(data->clk)) {
320                 ret = PTR_ERR(data->clk);
321                 dev_err(&pdev->dev, "Failed to get clock\n");
322                 goto err_irq;
323         }
324
325         data->pdata = pdata;
326         platform_set_drvdata(pdev, data);
327         mutex_init(&data->lock);
328
329         ret = exynos4_tmu_initialize(pdev);
330         if (ret) {
331                 dev_err(&pdev->dev, "Failed to initialize TMU\n");
332                 goto err_clk;
333         }
334
335         exynos4_tmu_control(pdev, true);
336
337         return 0;
338 err_clk:
339         platform_set_drvdata(pdev, NULL);
340         clk_put(data->clk);
341 err_irq:
342         free_irq(data->irq, data);
343 err_io_remap:
344         iounmap(data->base);
345 err_mem_region:
346         release_mem_region(data->mem->start, resource_size(data->mem));
347 err_free:
348         kfree(data);
349
350         return ret;
351 }
352
353 static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
354 {
355         struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
356
357         exynos4_tmu_control(pdev, false);
358
359         clk_put(data->clk);
360
361         free_irq(data->irq, data);
362
363         iounmap(data->base);
364         release_mem_region(data->mem->start, resource_size(data->mem));
365
366         platform_set_drvdata(pdev, NULL);
367
368         kfree(data);
369
370         return 0;
371 }
372
373 #ifdef CONFIG_PM_SLEEP
374 static int exynos4_tmu_suspend(struct device *dev)
375 {
376         exynos4_tmu_control(to_platform_device(dev), false);
377
378         return 0;
379 }
380
381 static int exynos4_tmu_resume(struct device *dev)
382 {
383         struct platform_device *pdev = to_platform_device(dev);
384
385         exynos4_tmu_initialize(pdev);
386         exynos4_tmu_control(pdev, true);
387
388         return 0;
389 }
390
391 static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
392                          exynos4_tmu_suspend, exynos4_tmu_resume);
393 #define EXYNOS4_TMU_PM  (&exynos4_tmu_pm)
394 #else
395 #define EXYNOS4_TMU_PM  NULL
396 #endif
397
398 static struct platform_driver exynos4_tmu_driver = {
399         .driver = {
400                 .name   = "exynos4-tmu",
401                 .owner  = THIS_MODULE,
402                 .pm     = EXYNOS4_TMU_PM,
403         },
404         .probe = exynos4_tmu_probe,
405         .remove = __devexit_p(exynos4_tmu_remove),
406 };
407
408 module_platform_driver(exynos4_tmu_driver);
409
410 MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
411 MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
412 MODULE_LICENSE("GPL");
413 MODULE_ALIAS("platform:exynos4-tmu");