]>
Commit | Line | Data |
---|---|---|
d24c586a SS |
1 | From 744af645bfdb0bfe73fa28df06da48783f85e6a9 Mon Sep 17 00:00:00 2001 |
2 | From: Shawn Guo <shawn.guo@linaro.org> | |
3 | Date: Mon, 24 Jun 2013 14:30:44 +0800 | |
4 | Subject: [PATCH 5/5] thermal: add imx thermal driver support | |
5 | ||
6 | This is based on the initial imx thermal work done by | |
7 | Rob Lee <rob.lee@linaro.org> (Not sure if the email address is still | |
8 | valid). Since he is no longer interested in the work and I have | |
9 | rewritten a significant amount of the code, I just took the authorship | |
10 | over from him. | |
11 | ||
12 | It adds the imx thermal support using Temperature Monitor (TEMPMON) | |
13 | block found on some Freescale i.MX SoCs. The driver uses syscon regmap | |
14 | interface to access TEMPMON control registers and calibration data, and | |
15 | supports cpufreq as the cooling device. | |
16 | ||
17 | Signed-off-by: Shawn Guo <shawn.guo@linaro.org> | |
18 | --- | |
19 | .../devicetree/bindings/thermal/imx-thermal.txt | 17 + | |
20 | drivers/thermal/Kconfig | 11 + | |
21 | drivers/thermal/Makefile | 1 + | |
22 | drivers/thermal/imx_thermal.c | 397 ++++++++++++++++++++ | |
23 | 4 files changed, 426 insertions(+) | |
24 | create mode 100644 Documentation/devicetree/bindings/thermal/imx-thermal.txt | |
25 | create mode 100644 drivers/thermal/imx_thermal.c | |
26 | ||
27 | diff --git a/Documentation/devicetree/bindings/thermal/imx-thermal.txt b/Documentation/devicetree/bindings/thermal/imx-thermal.txt | |
28 | new file mode 100644 | |
29 | index 0000000..541c25e | |
30 | --- /dev/null | |
31 | +++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt | |
32 | @@ -0,0 +1,17 @@ | |
33 | +* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs | |
34 | + | |
35 | +Required properties: | |
36 | +- compatible : "fsl,imx6q-thermal" | |
37 | +- fsl,tempmon : phandle pointer to system controller that contains TEMPMON | |
38 | + control registers, e.g. ANATOP on imx6q. | |
39 | +- fsl,tempmon-data : phandle pointer to fuse controller that contains TEMPMON | |
40 | + calibration data, e.g. OCOTP on imx6q. The details about calibration data | |
41 | + can be found in SoC Reference Manual. | |
42 | + | |
43 | +Example: | |
44 | + | |
45 | +tempmon { | |
46 | + compatible = "fsl,imx6q-tempmon"; | |
47 | + fsl,tempmon = <&anatop>; | |
48 | + fsl,tempmon-data = <&ocotp>; | |
49 | +}; | |
50 | diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig | |
51 | index 5e3c025..e91d78f 100644 | |
52 | --- a/drivers/thermal/Kconfig | |
53 | +++ b/drivers/thermal/Kconfig | |
54 | @@ -91,6 +91,17 @@ config THERMAL_EMULATION | |
55 | because userland can easily disable the thermal policy by simply | |
56 | flooding this sysfs node with low temperature values. | |
57 | ||
58 | +config IMX_THERMAL | |
59 | + tristate "Temperature sensor driver for Freescale i.MX SoCs" | |
60 | + depends on CPU_THERMAL | |
61 | + depends on MFD_SYSCON | |
62 | + depends on OF | |
63 | + help | |
64 | + Support for Temperature Monitor (TEMPMON) found on Freescale i.MX SoCs. | |
65 | + It supports one critical trip point and one passive trip point. The | |
66 | + cpufreq is used as the cooling device to throttle CPUs when the | |
67 | + passive trip is crossed. | |
68 | + | |
69 | config SPEAR_THERMAL | |
70 | bool "SPEAr thermal sensor driver" | |
71 | depends on PLAT_SPEAR | |
72 | diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile | |
73 | index c054d41..6910b2d 100644 | |
74 | --- a/drivers/thermal/Makefile | |
75 | +++ b/drivers/thermal/Makefile | |
76 | @@ -21,6 +21,7 @@ obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o | |
77 | obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o | |
78 | obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o | |
79 | obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o | |
80 | +obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o | |
81 | obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o | |
82 | obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o | |
83 | ||
84 | diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c | |
85 | new file mode 100644 | |
86 | index 0000000..d16c33c | |
87 | --- /dev/null | |
88 | +++ b/drivers/thermal/imx_thermal.c | |
89 | @@ -0,0 +1,397 @@ | |
90 | +/* | |
91 | + * Copyright 2013 Freescale Semiconductor, Inc. | |
92 | + * | |
93 | + * This program is free software; you can redistribute it and/or modify | |
94 | + * it under the terms of the GNU General Public License version 2 as | |
95 | + * published by the Free Software Foundation. | |
96 | + * | |
97 | + */ | |
98 | + | |
99 | +#include <linux/cpu_cooling.h> | |
100 | +#include <linux/cpufreq.h> | |
101 | +#include <linux/delay.h> | |
102 | +#include <linux/device.h> | |
103 | +#include <linux/init.h> | |
104 | +#include <linux/io.h> | |
105 | +#include <linux/kernel.h> | |
106 | +#include <linux/mfd/syscon.h> | |
107 | +#include <linux/module.h> | |
108 | +#include <linux/of.h> | |
109 | +#include <linux/platform_device.h> | |
110 | +#include <linux/regmap.h> | |
111 | +#include <linux/slab.h> | |
112 | +#include <linux/thermal.h> | |
113 | +#include <linux/types.h> | |
114 | + | |
115 | +#define REG_SET 0x4 | |
116 | +#define REG_CLR 0x8 | |
117 | +#define REG_TOG 0xc | |
118 | + | |
119 | +#define MISC0 0x0150 | |
120 | +#define MISC0_REFTOP_SELBIASOFF (1 << 3) | |
121 | + | |
122 | +#define TEMPSENSE0 0x0180 | |
123 | +#define TEMPSENSE0_TEMP_CNT_SHIFT 8 | |
124 | +#define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT) | |
125 | +#define TEMPSENSE0_FINISHED (1 << 2) | |
126 | +#define TEMPSENSE0_MEASURE_TEMP (1 << 1) | |
127 | +#define TEMPSENSE0_POWER_DOWN (1 << 0) | |
128 | + | |
129 | +#define TEMPSENSE1 0x0190 | |
130 | +#define TEMPSENSE1_MEASURE_FREQ 0xffff | |
131 | + | |
132 | +#define OCOTP_ANA1 0x04e0 | |
133 | + | |
134 | +/* The driver supports 1 passive trip point and 1 critical trip point */ | |
135 | +enum imx_thermal_trip { | |
136 | + IMX_TRIP_PASSIVE, | |
137 | + IMX_TRIP_CRITICAL, | |
138 | + IMX_TRIP_NUM, | |
139 | +}; | |
140 | + | |
141 | +/* | |
142 | + * It defines the temperature in millicelsius for passive trip point | |
143 | + * that will trigger cooling action when crossed. | |
144 | + */ | |
145 | +#define IMX_TEMP_PASSIVE 85000 | |
146 | + | |
147 | +/* | |
148 | + * The maximum die temperature on imx parts is 105C, let's give some cushion | |
149 | + * for noise and possible temperature rise between measurements. | |
150 | + */ | |
151 | +#define IMX_TEMP_CRITICAL 100000 | |
152 | + | |
153 | +#define IMX_POLLING_DELAY 2000 /* millisecond */ | |
154 | +#define IMX_PASSIVE_DELAY 1000 | |
155 | + | |
156 | +struct imx_thermal_data { | |
157 | + struct thermal_zone_device *tz; | |
158 | + struct thermal_cooling_device *cdev; | |
159 | + enum thermal_device_mode mode; | |
160 | + struct regmap *tempmon; | |
161 | + int c1, c2; /* See formula in imx_get_sensor_data() */ | |
162 | +}; | |
163 | + | |
164 | +static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) | |
165 | +{ | |
166 | + struct imx_thermal_data *data = tz->devdata; | |
167 | + struct regmap *map = data->tempmon; | |
168 | + static unsigned long last_temp; | |
169 | + unsigned int n_meas; | |
170 | + u32 val; | |
171 | + | |
172 | + /* | |
173 | + * Every time we measure the temperature, we will power on the | |
174 | + * temperature sensor, enable measurements, take a reading, | |
175 | + * disable measurements, power off the temperature sensor. | |
176 | + */ | |
177 | + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); | |
178 | + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); | |
179 | + | |
180 | + /* | |
181 | + * According to the temp sensor designers, it may require up to ~17us | |
182 | + * to complete a measurement. | |
183 | + */ | |
184 | + usleep_range(20, 50); | |
185 | + | |
186 | + regmap_read(map, TEMPSENSE0, &val); | |
187 | + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); | |
188 | + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); | |
189 | + | |
190 | + if ((val & TEMPSENSE0_FINISHED) == 0) { | |
191 | + dev_dbg(&tz->device, "temp measurement never finished\n"); | |
192 | + return -EAGAIN; | |
193 | + } | |
194 | + | |
195 | + n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT; | |
196 | + | |
197 | + /* See imx_get_sensor_data() for formula derivation */ | |
198 | + *temp = data->c2 + data->c1 * n_meas; | |
199 | + | |
200 | + if (*temp != last_temp) { | |
201 | + dev_dbg(&tz->device, "millicelsius: %ld\n", *temp); | |
202 | + last_temp = *temp; | |
203 | + } | |
204 | + | |
205 | + return 0; | |
206 | +} | |
207 | + | |
208 | +static int imx_get_mode(struct thermal_zone_device *tz, | |
209 | + enum thermal_device_mode *mode) | |
210 | +{ | |
211 | + struct imx_thermal_data *data = tz->devdata; | |
212 | + | |
213 | + *mode = data->mode; | |
214 | + | |
215 | + return 0; | |
216 | +} | |
217 | + | |
218 | +static int imx_set_mode(struct thermal_zone_device *tz, | |
219 | + enum thermal_device_mode mode) | |
220 | +{ | |
221 | + struct imx_thermal_data *data = tz->devdata; | |
222 | + | |
223 | + if (mode == THERMAL_DEVICE_ENABLED) { | |
224 | + tz->polling_delay = IMX_POLLING_DELAY; | |
225 | + tz->passive_delay = IMX_PASSIVE_DELAY; | |
226 | + } else { | |
227 | + tz->polling_delay = 0; | |
228 | + tz->passive_delay = 0; | |
229 | + } | |
230 | + | |
231 | + data->mode = mode; | |
232 | + thermal_zone_device_update(tz); | |
233 | + | |
234 | + return 0; | |
235 | +} | |
236 | + | |
237 | +static int imx_get_trip_type(struct thermal_zone_device *tz, int trip, | |
238 | + enum thermal_trip_type *type) | |
239 | +{ | |
240 | + *type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE : | |
241 | + THERMAL_TRIP_CRITICAL; | |
242 | + return 0; | |
243 | +} | |
244 | + | |
245 | +static int imx_get_crit_temp(struct thermal_zone_device *tz, | |
246 | + unsigned long *temp) | |
247 | +{ | |
248 | + *temp = IMX_TEMP_CRITICAL; | |
249 | + return 0; | |
250 | +} | |
251 | + | |
252 | +static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip, | |
253 | + unsigned long *temp) | |
254 | +{ | |
255 | + *temp = (trip == IMX_TRIP_PASSIVE) ? IMX_TEMP_PASSIVE : | |
256 | + IMX_TEMP_CRITICAL; | |
257 | + return 0; | |
258 | +} | |
259 | + | |
260 | +static int imx_bind(struct thermal_zone_device *tz, | |
261 | + struct thermal_cooling_device *cdev) | |
262 | +{ | |
263 | + int ret; | |
264 | + | |
265 | + ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev, | |
266 | + THERMAL_NO_LIMIT, | |
267 | + THERMAL_NO_LIMIT); | |
268 | + if (ret) { | |
269 | + dev_err(&tz->device, | |
270 | + "binding zone %s with cdev %s failed:%d\n", | |
271 | + tz->type, cdev->type, ret); | |
272 | + return ret; | |
273 | + } | |
274 | + | |
275 | + return 0; | |
276 | +} | |
277 | + | |
278 | +static int imx_unbind(struct thermal_zone_device *tz, | |
279 | + struct thermal_cooling_device *cdev) | |
280 | +{ | |
281 | + int ret; | |
282 | + | |
283 | + ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev); | |
284 | + if (ret) { | |
285 | + dev_err(&tz->device, | |
286 | + "unbinding zone %s with cdev %s failed:%d\n", | |
287 | + tz->type, cdev->type, ret); | |
288 | + return ret; | |
289 | + } | |
290 | + | |
291 | + return 0; | |
292 | +} | |
293 | + | |
294 | +static const struct thermal_zone_device_ops imx_tz_ops = { | |
295 | + .bind = imx_bind, | |
296 | + .unbind = imx_unbind, | |
297 | + .get_temp = imx_get_temp, | |
298 | + .get_mode = imx_get_mode, | |
299 | + .set_mode = imx_set_mode, | |
300 | + .get_trip_type = imx_get_trip_type, | |
301 | + .get_trip_temp = imx_get_trip_temp, | |
302 | + .get_crit_temp = imx_get_crit_temp, | |
303 | +}; | |
304 | + | |
305 | +static int imx_get_sensor_data(struct platform_device *pdev) | |
306 | +{ | |
307 | + struct imx_thermal_data *data = platform_get_drvdata(pdev); | |
308 | + struct regmap *map; | |
309 | + int t1, t2, n1, n2; | |
310 | + int ret; | |
311 | + u32 val; | |
312 | + | |
313 | + map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | |
314 | + "fsl,tempmon-data"); | |
315 | + if (IS_ERR(map)) { | |
316 | + ret = PTR_ERR(map); | |
317 | + dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); | |
318 | + return ret; | |
319 | + } | |
320 | + | |
321 | + ret = regmap_read(map, OCOTP_ANA1, &val); | |
322 | + if (ret) { | |
323 | + dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); | |
324 | + return ret; | |
325 | + } | |
326 | + | |
327 | + if (val == 0 || val == ~0) { | |
328 | + dev_err(&pdev->dev, "invalid sensor calibration data\n"); | |
329 | + return -EINVAL; | |
330 | + } | |
331 | + | |
332 | + /* | |
333 | + * Sensor data layout: | |
334 | + * [31:20] - sensor value @ 25C | |
335 | + * [19:8] - sensor value of hot | |
336 | + * [7:0] - hot temperature value | |
337 | + */ | |
338 | + n1 = val >> 20; | |
339 | + n2 = (val & 0xfff00) >> 8; | |
340 | + t2 = val & 0xff; | |
341 | + t1 = 25; /* t1 always 25C */ | |
342 | + | |
343 | + /* | |
344 | + * Derived from linear interpolation, | |
345 | + * Tmeas = T2 + (Nmeas - N2) * (T1 - T2) / (N1 - N2) | |
346 | + * We want to reduce this down to the minimum computation necessary | |
347 | + * for each temperature read. Also, we want Tmeas in millicelsius | |
348 | + * and we don't want to lose precision from integer division. So... | |
349 | + * milli_Tmeas = 1000 * T2 + 1000 * (Nmeas - N2) * (T1 - T2) / (N1 - N2) | |
350 | + * Let constant c1 = 1000 * (T1 - T2) / (N1 - N2) | |
351 | + * milli_Tmeas = (1000 * T2) + c1 * (Nmeas - N2) | |
352 | + * milli_Tmeas = (1000 * T2) + (c1 * Nmeas) - (c1 * N2) | |
353 | + * Let constant c2 = (1000 * T2) - (c1 * N2) | |
354 | + * milli_Tmeas = c2 + (c1 * Nmeas) | |
355 | + */ | |
356 | + data->c1 = 1000 * (t1 - t2) / (n1 - n2); | |
357 | + data->c2 = 1000 * t2 - data->c1 * n2; | |
358 | + | |
359 | + return 0; | |
360 | +} | |
361 | + | |
362 | +static int imx_thermal_probe(struct platform_device *pdev) | |
363 | +{ | |
364 | + struct imx_thermal_data *data; | |
365 | + struct cpumask clip_cpus; | |
366 | + struct regmap *map; | |
367 | + int ret; | |
368 | + | |
369 | + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | |
370 | + if (!data) | |
371 | + return -ENOMEM; | |
372 | + | |
373 | + map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); | |
374 | + if (IS_ERR(map)) { | |
375 | + ret = PTR_ERR(map); | |
376 | + dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret); | |
377 | + return ret; | |
378 | + } | |
379 | + data->tempmon = map; | |
380 | + | |
381 | + platform_set_drvdata(pdev, data); | |
382 | + | |
383 | + ret = imx_get_sensor_data(pdev); | |
384 | + if (ret) { | |
385 | + dev_err(&pdev->dev, "failed to get sensor data\n"); | |
386 | + return ret; | |
387 | + } | |
388 | + | |
389 | + /* Make sure sensor is in known good state for measurements */ | |
390 | + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); | |
391 | + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); | |
392 | + regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); | |
393 | + regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF); | |
394 | + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); | |
395 | + | |
396 | + cpumask_set_cpu(0, &clip_cpus); | |
397 | + data->cdev = cpufreq_cooling_register(&clip_cpus); | |
398 | + if (IS_ERR(data->cdev)) { | |
399 | + ret = PTR_ERR(data->cdev); | |
400 | + dev_err(&pdev->dev, | |
401 | + "failed to register cpufreq cooling device: %d\n", ret); | |
402 | + return ret; | |
403 | + } | |
404 | + | |
405 | + data->tz = thermal_zone_device_register("imx_thermal_zone", | |
406 | + IMX_TRIP_NUM, 0, data, | |
407 | + &imx_tz_ops, NULL, | |
408 | + IMX_PASSIVE_DELAY, | |
409 | + IMX_POLLING_DELAY); | |
410 | + if (IS_ERR(data->tz)) { | |
411 | + ret = PTR_ERR(data->tz); | |
412 | + dev_err(&pdev->dev, | |
413 | + "failed to register thermal zone device %d\n", ret); | |
414 | + cpufreq_cooling_unregister(data->cdev); | |
415 | + return ret; | |
416 | + } | |
417 | + | |
418 | + data->mode = THERMAL_DEVICE_ENABLED; | |
419 | + | |
420 | + return 0; | |
421 | +} | |
422 | + | |
423 | +static int imx_thermal_remove(struct platform_device *pdev) | |
424 | +{ | |
425 | + struct imx_thermal_data *data = platform_get_drvdata(pdev); | |
426 | + | |
427 | + thermal_zone_device_unregister(data->tz); | |
428 | + cpufreq_cooling_unregister(data->cdev); | |
429 | + | |
430 | + return 0; | |
431 | +} | |
432 | + | |
433 | +#ifdef CONFIG_PM_SLEEP | |
434 | +static int imx_thermal_suspend(struct device *dev) | |
435 | +{ | |
436 | + struct imx_thermal_data *data = dev_get_drvdata(dev); | |
437 | + struct regmap *map = data->tempmon; | |
438 | + u32 val; | |
439 | + | |
440 | + regmap_read(map, TEMPSENSE0, &val); | |
441 | + if ((val & TEMPSENSE0_POWER_DOWN) == 0) { | |
442 | + /* | |
443 | + * If a measurement is taking place, wait for a long enough | |
444 | + * time for it to finish, and then check again. If it still | |
445 | + * does not finish, something must go wrong. | |
446 | + */ | |
447 | + udelay(50); | |
448 | + regmap_read(map, TEMPSENSE0, &val); | |
449 | + if ((val & TEMPSENSE0_POWER_DOWN) == 0) | |
450 | + return -ETIMEDOUT; | |
451 | + } | |
452 | + | |
453 | + return 0; | |
454 | +} | |
455 | + | |
456 | +static int imx_thermal_resume(struct device *dev) | |
457 | +{ | |
458 | + /* Nothing to do for now */ | |
459 | + return 0; | |
460 | +} | |
461 | +#endif | |
462 | + | |
463 | +static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops, | |
464 | + imx_thermal_suspend, imx_thermal_resume); | |
465 | + | |
466 | +static const struct of_device_id of_imx_thermal_match[] = { | |
467 | + { .compatible = "fsl,imx6q-tempmon", }, | |
468 | + { /* end */ } | |
469 | +}; | |
470 | + | |
471 | +static struct platform_driver imx_thermal = { | |
472 | + .driver = { | |
473 | + .name = "imx_thermal", | |
474 | + .owner = THIS_MODULE, | |
475 | + .pm = &imx_thermal_pm_ops, | |
476 | + .of_match_table = of_imx_thermal_match, | |
477 | + }, | |
478 | + .probe = imx_thermal_probe, | |
479 | + .remove = imx_thermal_remove, | |
480 | +}; | |
481 | +module_platform_driver(imx_thermal); | |
482 | + | |
483 | +MODULE_AUTHOR("Freescale Semiconductor, Inc."); | |
484 | +MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs"); | |
485 | +MODULE_LICENSE("GPL v2"); | |
486 | +MODULE_ALIAS("platform:imx-thermal"); | |
487 | -- | |
488 | 1.7.10.4 | |
489 |