diff options
author | Zhang Rui <rui.zhang@intel.com> | 2013-08-15 01:57:14 -0400 |
---|---|---|
committer | Zhang Rui <rui.zhang@intel.com> | 2013-08-15 01:57:14 -0400 |
commit | cba77f5312caa17c3d107593846cbeb35d9273dc (patch) | |
tree | 7256c0f8a5c2304b805e6830c059ed9e5297cbf4 | |
parent | 584d88b2cd3b60507e708d2452651e4d3caa1b81 (diff) | |
parent | ca3de46b50809000b5ba708634e26ad979a4a63a (diff) |
Merge branch 'for_3.12/imx' of git://git.kernel.org/pub/scm/linux/kernel/git/evalenti/linux-soc-thermal into imx
-rw-r--r-- | Documentation/devicetree/bindings/thermal/imx-thermal.txt | 17 | ||||
-rw-r--r-- | drivers/thermal/Kconfig | 11 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/imx_thermal.c | 397 |
4 files changed, 426 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/thermal/imx-thermal.txt b/Documentation/devicetree/bindings/thermal/imx-thermal.txt new file mode 100644 index 000000000000..541c25e49abf --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt | |||
@@ -0,0 +1,17 @@ | |||
1 | * Temperature Monitor (TEMPMON) on Freescale i.MX SoCs | ||
2 | |||
3 | Required properties: | ||
4 | - compatible : "fsl,imx6q-thermal" | ||
5 | - fsl,tempmon : phandle pointer to system controller that contains TEMPMON | ||
6 | control registers, e.g. ANATOP on imx6q. | ||
7 | - fsl,tempmon-data : phandle pointer to fuse controller that contains TEMPMON | ||
8 | calibration data, e.g. OCOTP on imx6q. The details about calibration data | ||
9 | can be found in SoC Reference Manual. | ||
10 | |||
11 | Example: | ||
12 | |||
13 | tempmon { | ||
14 | compatible = "fsl,imx6q-tempmon"; | ||
15 | fsl,tempmon = <&anatop>; | ||
16 | fsl,tempmon-data = <&ocotp>; | ||
17 | }; | ||
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e988c81d763c..69eed55b88b0 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig | |||
@@ -91,6 +91,17 @@ config THERMAL_EMULATION | |||
91 | because userland can easily disable the thermal policy by simply | 91 | because userland can easily disable the thermal policy by simply |
92 | flooding this sysfs node with low temperature values. | 92 | flooding this sysfs node with low temperature values. |
93 | 93 | ||
94 | config IMX_THERMAL | ||
95 | tristate "Temperature sensor driver for Freescale i.MX SoCs" | ||
96 | depends on CPU_THERMAL | ||
97 | depends on MFD_SYSCON | ||
98 | depends on OF | ||
99 | help | ||
100 | Support for Temperature Monitor (TEMPMON) found on Freescale i.MX SoCs. | ||
101 | It supports one critical trip point and one passive trip point. The | ||
102 | cpufreq is used as the cooling device to throttle CPUs when the | ||
103 | passive trip is crossed. | ||
104 | |||
94 | config SPEAR_THERMAL | 105 | config SPEAR_THERMAL |
95 | bool "SPEAr thermal sensor driver" | 106 | bool "SPEAr thermal sensor driver" |
96 | depends on PLAT_SPEAR | 107 | depends on PLAT_SPEAR |
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 67184a293e3f..dff19c610bb0 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile | |||
@@ -21,6 +21,7 @@ obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o | |||
21 | obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o | 21 | obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o |
22 | obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o | 22 | obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o |
23 | obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o | 23 | obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o |
24 | obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o | ||
24 | obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o | 25 | obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o |
25 | obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o | 26 | obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o |
26 | obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o | 27 | obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o |
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 */ | ||
46 | enum 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 | |||
67 | struct 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 | |||
75 | static 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 | |||
119 | static 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 | |||
129 | static 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 | |||
148 | static 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 | |||
156 | static 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 | |||
163 | static 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 | |||
171 | static 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 | |||
189 | static 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 | |||
205 | static 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 | |||
216 | static 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 | |||
273 | static 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 | |||
334 | static 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 | ||
345 | static 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 | |||
367 | static int imx_thermal_resume(struct device *dev) | ||
368 | { | ||
369 | /* Nothing to do for now */ | ||
370 | return 0; | ||
371 | } | ||
372 | #endif | ||
373 | |||
374 | static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops, | ||
375 | imx_thermal_suspend, imx_thermal_resume); | ||
376 | |||
377 | static const struct of_device_id of_imx_thermal_match[] = { | ||
378 | { .compatible = "fsl,imx6q-tempmon", }, | ||
379 | { /* end */ } | ||
380 | }; | ||
381 | |||
382 | static 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 | }; | ||
392 | module_platform_driver(imx_thermal); | ||
393 | |||
394 | MODULE_AUTHOR("Freescale Semiconductor, Inc."); | ||
395 | MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs"); | ||
396 | MODULE_LICENSE("GPL v2"); | ||
397 | MODULE_ALIAS("platform:imx-thermal"); | ||