aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/thermal/imx_thermal.c
diff options
context:
space:
mode:
authorShawn Guo <shawn.guo@linaro.org>2013-06-24 02:30:44 -0400
committerEduardo Valentin <eduardo.valentin@ti.com>2013-08-13 09:45:34 -0400
commitca3de46b50809000b5ba708634e26ad979a4a63a (patch)
treed13421a447efb3007a8a220f3e5ec6affe21345a /drivers/thermal/imx_thermal.c
parentace120dcf23b3bbba00d797a898481997381052f (diff)
thermal: add imx thermal driver support
This is based on the initial imx thermal work done by Rob Lee <rob.lee@linaro.org> (Not sure if the email address is still valid). Since he is no longer interested in the work and I have rewritten a significant amount of the code, I just took the authorship over from him. It adds the imx thermal support using Temperature Monitor (TEMPMON) block found on some Freescale i.MX SoCs. The driver uses syscon regmap interface to access TEMPMON control registers and calibration data, and supports cpufreq as the cooling device. Signed-off-by: Shawn Guo <shawn.guo@linaro.org> Signed-off-by: Eduardo Valentin <eduardo.valentin@ti.com>
Diffstat (limited to 'drivers/thermal/imx_thermal.c')
-rw-r--r--drivers/thermal/imx_thermal.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
new file mode 100644
index 000000000000..d16c33c7f3f0
--- /dev/null
+++ b/drivers/thermal/imx_thermal.c
@@ -0,0 +1,397 @@
1/*
2 * Copyright 2013 Freescale Semiconductor, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 */
9
10#include <linux/cpu_cooling.h>
11#include <linux/cpufreq.h>
12#include <linux/delay.h>
13#include <linux/device.h>
14#include <linux/init.h>
15#include <linux/io.h>
16#include <linux/kernel.h>
17#include <linux/mfd/syscon.h>
18#include <linux/module.h>
19#include <linux/of.h>
20#include <linux/platform_device.h>
21#include <linux/regmap.h>
22#include <linux/slab.h>
23#include <linux/thermal.h>
24#include <linux/types.h>
25
26#define REG_SET 0x4
27#define REG_CLR 0x8
28#define REG_TOG 0xc
29
30#define MISC0 0x0150
31#define MISC0_REFTOP_SELBIASOFF (1 << 3)
32
33#define TEMPSENSE0 0x0180
34#define TEMPSENSE0_TEMP_CNT_SHIFT 8
35#define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT)
36#define TEMPSENSE0_FINISHED (1 << 2)
37#define TEMPSENSE0_MEASURE_TEMP (1 << 1)
38#define TEMPSENSE0_POWER_DOWN (1 << 0)
39
40#define TEMPSENSE1 0x0190
41#define TEMPSENSE1_MEASURE_FREQ 0xffff
42
43#define OCOTP_ANA1 0x04e0
44
45/* The driver supports 1 passive trip point and 1 critical trip point */
46enum imx_thermal_trip {
47 IMX_TRIP_PASSIVE,
48 IMX_TRIP_CRITICAL,
49 IMX_TRIP_NUM,
50};
51
52/*
53 * It defines the temperature in millicelsius for passive trip point
54 * that will trigger cooling action when crossed.
55 */
56#define IMX_TEMP_PASSIVE 85000
57
58/*
59 * The maximum die temperature on imx parts is 105C, let's give some cushion
60 * for noise and possible temperature rise between measurements.
61 */
62#define IMX_TEMP_CRITICAL 100000
63
64#define IMX_POLLING_DELAY 2000 /* millisecond */
65#define IMX_PASSIVE_DELAY 1000
66
67struct imx_thermal_data {
68 struct thermal_zone_device *tz;
69 struct thermal_cooling_device *cdev;
70 enum thermal_device_mode mode;
71 struct regmap *tempmon;
72 int c1, c2; /* See formula in imx_get_sensor_data() */
73};
74
75static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
76{
77 struct imx_thermal_data *data = tz->devdata;
78 struct regmap *map = data->tempmon;
79 static unsigned long last_temp;
80 unsigned int n_meas;
81 u32 val;
82
83 /*
84 * Every time we measure the temperature, we will power on the
85 * temperature sensor, enable measurements, take a reading,
86 * disable measurements, power off the temperature sensor.
87 */
88 regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
89 regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
90
91 /*
92 * According to the temp sensor designers, it may require up to ~17us
93 * to complete a measurement.
94 */
95 usleep_range(20, 50);
96
97 regmap_read(map, TEMPSENSE0, &val);
98 regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
99 regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
100
101 if ((val & TEMPSENSE0_FINISHED) == 0) {
102 dev_dbg(&tz->device, "temp measurement never finished\n");
103 return -EAGAIN;
104 }
105
106 n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT;
107
108 /* See imx_get_sensor_data() for formula derivation */
109 *temp = data->c2 + data->c1 * n_meas;
110
111 if (*temp != last_temp) {
112 dev_dbg(&tz->device, "millicelsius: %ld\n", *temp);
113 last_temp = *temp;
114 }
115
116 return 0;
117}
118
119static int imx_get_mode(struct thermal_zone_device *tz,
120 enum thermal_device_mode *mode)
121{
122 struct imx_thermal_data *data = tz->devdata;
123
124 *mode = data->mode;
125
126 return 0;
127}
128
129static int imx_set_mode(struct thermal_zone_device *tz,
130 enum thermal_device_mode mode)
131{
132 struct imx_thermal_data *data = tz->devdata;
133
134 if (mode == THERMAL_DEVICE_ENABLED) {
135 tz->polling_delay = IMX_POLLING_DELAY;
136 tz->passive_delay = IMX_PASSIVE_DELAY;
137 } else {
138 tz->polling_delay = 0;
139 tz->passive_delay = 0;
140 }
141
142 data->mode = mode;
143 thermal_zone_device_update(tz);
144
145 return 0;
146}
147
148static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
149 enum thermal_trip_type *type)
150{
151 *type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE :
152 THERMAL_TRIP_CRITICAL;
153 return 0;
154}
155
156static int imx_get_crit_temp(struct thermal_zone_device *tz,
157 unsigned long *temp)
158{
159 *temp = IMX_TEMP_CRITICAL;
160 return 0;
161}
162
163static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
164 unsigned long *temp)
165{
166 *temp = (trip == IMX_TRIP_PASSIVE) ? IMX_TEMP_PASSIVE :
167 IMX_TEMP_CRITICAL;
168 return 0;
169}
170
171static int imx_bind(struct thermal_zone_device *tz,
172 struct thermal_cooling_device *cdev)
173{
174 int ret;
175
176 ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
177 THERMAL_NO_LIMIT,
178 THERMAL_NO_LIMIT);
179 if (ret) {
180 dev_err(&tz->device,
181 "binding zone %s with cdev %s failed:%d\n",
182 tz->type, cdev->type, ret);
183 return ret;
184 }
185
186 return 0;
187}
188
189static int imx_unbind(struct thermal_zone_device *tz,
190 struct thermal_cooling_device *cdev)
191{
192 int ret;
193
194 ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
195 if (ret) {
196 dev_err(&tz->device,
197 "unbinding zone %s with cdev %s failed:%d\n",
198 tz->type, cdev->type, ret);
199 return ret;
200 }
201
202 return 0;
203}
204
205static const struct thermal_zone_device_ops imx_tz_ops = {
206 .bind = imx_bind,
207 .unbind = imx_unbind,
208 .get_temp = imx_get_temp,
209 .get_mode = imx_get_mode,
210 .set_mode = imx_set_mode,
211 .get_trip_type = imx_get_trip_type,
212 .get_trip_temp = imx_get_trip_temp,
213 .get_crit_temp = imx_get_crit_temp,
214};
215
216static int imx_get_sensor_data(struct platform_device *pdev)
217{
218 struct imx_thermal_data *data = platform_get_drvdata(pdev);
219 struct regmap *map;
220 int t1, t2, n1, n2;
221 int ret;
222 u32 val;
223
224 map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
225 "fsl,tempmon-data");
226 if (IS_ERR(map)) {
227 ret = PTR_ERR(map);
228 dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
229 return ret;
230 }
231
232 ret = regmap_read(map, OCOTP_ANA1, &val);
233 if (ret) {
234 dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
235 return ret;
236 }
237
238 if (val == 0 || val == ~0) {
239 dev_err(&pdev->dev, "invalid sensor calibration data\n");
240 return -EINVAL;
241 }
242
243 /*
244 * Sensor data layout:
245 * [31:20] - sensor value @ 25C
246 * [19:8] - sensor value of hot
247 * [7:0] - hot temperature value
248 */
249 n1 = val >> 20;
250 n2 = (val & 0xfff00) >> 8;
251 t2 = val & 0xff;
252 t1 = 25; /* t1 always 25C */
253
254 /*
255 * Derived from linear interpolation,
256 * Tmeas = T2 + (Nmeas - N2) * (T1 - T2) / (N1 - N2)
257 * We want to reduce this down to the minimum computation necessary
258 * for each temperature read. Also, we want Tmeas in millicelsius
259 * and we don't want to lose precision from integer division. So...
260 * milli_Tmeas = 1000 * T2 + 1000 * (Nmeas - N2) * (T1 - T2) / (N1 - N2)
261 * Let constant c1 = 1000 * (T1 - T2) / (N1 - N2)
262 * milli_Tmeas = (1000 * T2) + c1 * (Nmeas - N2)
263 * milli_Tmeas = (1000 * T2) + (c1 * Nmeas) - (c1 * N2)
264 * Let constant c2 = (1000 * T2) - (c1 * N2)
265 * milli_Tmeas = c2 + (c1 * Nmeas)
266 */
267 data->c1 = 1000 * (t1 - t2) / (n1 - n2);
268 data->c2 = 1000 * t2 - data->c1 * n2;
269
270 return 0;
271}
272
273static int imx_thermal_probe(struct platform_device *pdev)
274{
275 struct imx_thermal_data *data;
276 struct cpumask clip_cpus;
277 struct regmap *map;
278 int ret;
279
280 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
281 if (!data)
282 return -ENOMEM;
283
284 map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
285 if (IS_ERR(map)) {
286 ret = PTR_ERR(map);
287 dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
288 return ret;
289 }
290 data->tempmon = map;
291
292 platform_set_drvdata(pdev, data);
293
294 ret = imx_get_sensor_data(pdev);
295 if (ret) {
296 dev_err(&pdev->dev, "failed to get sensor data\n");
297 return ret;
298 }
299
300 /* Make sure sensor is in known good state for measurements */
301 regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
302 regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
303 regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ);
304 regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF);
305 regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
306
307 cpumask_set_cpu(0, &clip_cpus);
308 data->cdev = cpufreq_cooling_register(&clip_cpus);
309 if (IS_ERR(data->cdev)) {
310 ret = PTR_ERR(data->cdev);
311 dev_err(&pdev->dev,
312 "failed to register cpufreq cooling device: %d\n", ret);
313 return ret;
314 }
315
316 data->tz = thermal_zone_device_register("imx_thermal_zone",
317 IMX_TRIP_NUM, 0, data,
318 &imx_tz_ops, NULL,
319 IMX_PASSIVE_DELAY,
320 IMX_POLLING_DELAY);
321 if (IS_ERR(data->tz)) {
322 ret = PTR_ERR(data->tz);
323 dev_err(&pdev->dev,
324 "failed to register thermal zone device %d\n", ret);
325 cpufreq_cooling_unregister(data->cdev);
326 return ret;
327 }
328
329 data->mode = THERMAL_DEVICE_ENABLED;
330
331 return 0;
332}
333
334static int imx_thermal_remove(struct platform_device *pdev)
335{
336 struct imx_thermal_data *data = platform_get_drvdata(pdev);
337
338 thermal_zone_device_unregister(data->tz);
339 cpufreq_cooling_unregister(data->cdev);
340
341 return 0;
342}
343
344#ifdef CONFIG_PM_SLEEP
345static int imx_thermal_suspend(struct device *dev)
346{
347 struct imx_thermal_data *data = dev_get_drvdata(dev);
348 struct regmap *map = data->tempmon;
349 u32 val;
350
351 regmap_read(map, TEMPSENSE0, &val);
352 if ((val & TEMPSENSE0_POWER_DOWN) == 0) {
353 /*
354 * If a measurement is taking place, wait for a long enough
355 * time for it to finish, and then check again. If it still
356 * does not finish, something must go wrong.
357 */
358 udelay(50);
359 regmap_read(map, TEMPSENSE0, &val);
360 if ((val & TEMPSENSE0_POWER_DOWN) == 0)
361 return -ETIMEDOUT;
362 }
363
364 return 0;
365}
366
367static int imx_thermal_resume(struct device *dev)
368{
369 /* Nothing to do for now */
370 return 0;
371}
372#endif
373
374static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
375 imx_thermal_suspend, imx_thermal_resume);
376
377static const struct of_device_id of_imx_thermal_match[] = {
378 { .compatible = "fsl,imx6q-tempmon", },
379 { /* end */ }
380};
381
382static struct platform_driver imx_thermal = {
383 .driver = {
384 .name = "imx_thermal",
385 .owner = THIS_MODULE,
386 .pm = &imx_thermal_pm_ops,
387 .of_match_table = of_imx_thermal_match,
388 },
389 .probe = imx_thermal_probe,
390 .remove = imx_thermal_remove,
391};
392module_platform_driver(imx_thermal);
393
394MODULE_AUTHOR("Freescale Semiconductor, Inc.");
395MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
396MODULE_LICENSE("GPL v2");
397MODULE_ALIAS("platform:imx-thermal");