2 * db8500_thermal.c - DB8500 Thermal Management Implementation
4 * Copyright (C) 2012 ST-Ericsson
5 * Copyright (C) 2012 Linaro Ltd.
7 * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
20 #include <linux/cpu_cooling.h>
21 #include <linux/interrupt.h>
22 #include <linux/mfd/dbx500-prcmu.h>
23 #include <linux/module.h>
25 #include <linux/platform_data/db8500_thermal.h>
26 #include <linux/platform_device.h>
27 #include <linux/slab.h>
28 #include <linux/thermal.h>
30 #define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
31 #define PRCMU_DEFAULT_LOW_TEMP 0
33 struct db8500_thermal_zone
{
34 struct thermal_zone_device
*therm_dev
;
36 struct work_struct therm_work
;
37 struct db8500_thsens_platform_data
*trip_tab
;
38 enum thermal_device_mode mode
;
39 enum thermal_trend trend
;
40 unsigned long cur_temp_pseudo
;
41 unsigned int cur_index
;
44 /* Local function to check if thermal zone matches cooling devices */
45 static int db8500_thermal_match_cdev(struct thermal_cooling_device
*cdev
,
46 struct db8500_trip_point
*trip_point
)
50 if (!strlen(cdev
->type
))
53 for (i
= 0; i
< COOLING_DEV_MAX
; i
++) {
54 if (!strcmp(trip_point
->cdev_name
[i
], cdev
->type
))
61 /* Callback to bind cooling device to thermal zone */
62 static int db8500_cdev_bind(struct thermal_zone_device
*thermal
,
63 struct thermal_cooling_device
*cdev
)
65 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
66 struct db8500_thsens_platform_data
*ptrips
= pzone
->trip_tab
;
67 unsigned long max_state
, upper
, lower
;
70 cdev
->ops
->get_max_state(cdev
, &max_state
);
72 for (i
= 0; i
< ptrips
->num_trips
; i
++) {
73 if (db8500_thermal_match_cdev(cdev
, &ptrips
->trip_points
[i
]))
76 upper
= lower
= i
> max_state
? max_state
: i
;
78 ret
= thermal_zone_bind_cooling_device(thermal
, i
, cdev
,
79 upper
, lower
, THERMAL_WEIGHT_DEFAULT
);
81 dev_info(&cdev
->device
, "%s bind to %d: %d-%s\n", cdev
->type
,
82 i
, ret
, ret
? "fail" : "succeed");
88 /* Callback to unbind cooling device from thermal zone */
89 static int db8500_cdev_unbind(struct thermal_zone_device
*thermal
,
90 struct thermal_cooling_device
*cdev
)
92 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
93 struct db8500_thsens_platform_data
*ptrips
= pzone
->trip_tab
;
96 for (i
= 0; i
< ptrips
->num_trips
; i
++) {
97 if (db8500_thermal_match_cdev(cdev
, &ptrips
->trip_points
[i
]))
100 ret
= thermal_zone_unbind_cooling_device(thermal
, i
, cdev
);
102 dev_info(&cdev
->device
, "%s unbind from %d: %s\n", cdev
->type
,
103 i
, ret
? "fail" : "succeed");
109 /* Callback to get current temperature */
110 static int db8500_sys_get_temp(struct thermal_zone_device
*thermal
, int *temp
)
112 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
115 * TODO: There is no PRCMU interface to get temperature data currently,
116 * so a pseudo temperature is returned , it works for thermal framework
117 * and this will be fixed when the PRCMU interface is available.
119 *temp
= pzone
->cur_temp_pseudo
;
124 /* Callback to get temperature changing trend */
125 static int db8500_sys_get_trend(struct thermal_zone_device
*thermal
,
126 int trip
, enum thermal_trend
*trend
)
128 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
130 *trend
= pzone
->trend
;
135 /* Callback to get thermal zone mode */
136 static int db8500_sys_get_mode(struct thermal_zone_device
*thermal
,
137 enum thermal_device_mode
*mode
)
139 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
141 mutex_lock(&pzone
->th_lock
);
143 mutex_unlock(&pzone
->th_lock
);
148 /* Callback to set thermal zone mode */
149 static int db8500_sys_set_mode(struct thermal_zone_device
*thermal
,
150 enum thermal_device_mode mode
)
152 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
154 mutex_lock(&pzone
->th_lock
);
157 if (mode
== THERMAL_DEVICE_ENABLED
)
158 schedule_work(&pzone
->therm_work
);
160 mutex_unlock(&pzone
->th_lock
);
165 /* Callback to get trip point type */
166 static int db8500_sys_get_trip_type(struct thermal_zone_device
*thermal
,
167 int trip
, enum thermal_trip_type
*type
)
169 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
170 struct db8500_thsens_platform_data
*ptrips
= pzone
->trip_tab
;
172 if (trip
>= ptrips
->num_trips
)
175 *type
= ptrips
->trip_points
[trip
].type
;
180 /* Callback to get trip point temperature */
181 static int db8500_sys_get_trip_temp(struct thermal_zone_device
*thermal
,
184 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
185 struct db8500_thsens_platform_data
*ptrips
= pzone
->trip_tab
;
187 if (trip
>= ptrips
->num_trips
)
190 *temp
= ptrips
->trip_points
[trip
].temp
;
195 /* Callback to get critical trip point temperature */
196 static int db8500_sys_get_crit_temp(struct thermal_zone_device
*thermal
,
199 struct db8500_thermal_zone
*pzone
= thermal
->devdata
;
200 struct db8500_thsens_platform_data
*ptrips
= pzone
->trip_tab
;
203 for (i
= ptrips
->num_trips
- 1; i
> 0; i
--) {
204 if (ptrips
->trip_points
[i
].type
== THERMAL_TRIP_CRITICAL
) {
205 *temp
= ptrips
->trip_points
[i
].temp
;
213 static struct thermal_zone_device_ops thdev_ops
= {
214 .bind
= db8500_cdev_bind
,
215 .unbind
= db8500_cdev_unbind
,
216 .get_temp
= db8500_sys_get_temp
,
217 .get_trend
= db8500_sys_get_trend
,
218 .get_mode
= db8500_sys_get_mode
,
219 .set_mode
= db8500_sys_set_mode
,
220 .get_trip_type
= db8500_sys_get_trip_type
,
221 .get_trip_temp
= db8500_sys_get_trip_temp
,
222 .get_crit_temp
= db8500_sys_get_crit_temp
,
225 static void db8500_thermal_update_config(struct db8500_thermal_zone
*pzone
,
226 unsigned int idx
, enum thermal_trend trend
,
227 unsigned long next_low
, unsigned long next_high
)
229 prcmu_stop_temp_sense();
231 pzone
->cur_index
= idx
;
232 pzone
->cur_temp_pseudo
= (next_low
+ next_high
)/2;
233 pzone
->trend
= trend
;
235 prcmu_config_hotmon((u8
)(next_low
/1000), (u8
)(next_high
/1000));
236 prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME
);
239 static irqreturn_t
prcmu_low_irq_handler(int irq
, void *irq_data
)
241 struct db8500_thermal_zone
*pzone
= irq_data
;
242 struct db8500_thsens_platform_data
*ptrips
= pzone
->trip_tab
;
243 unsigned int idx
= pzone
->cur_index
;
244 unsigned long next_low
, next_high
;
246 if (unlikely(idx
== 0))
247 /* Meaningless for thermal management, ignoring it */
251 next_high
= ptrips
->trip_points
[0].temp
;
252 next_low
= PRCMU_DEFAULT_LOW_TEMP
;
254 next_high
= ptrips
->trip_points
[idx
-1].temp
;
255 next_low
= ptrips
->trip_points
[idx
-2].temp
;
259 db8500_thermal_update_config(pzone
, idx
, THERMAL_TREND_DROPPING
,
260 next_low
, next_high
);
262 dev_dbg(&pzone
->therm_dev
->device
,
263 "PRCMU set max %ld, min %ld\n", next_high
, next_low
);
265 schedule_work(&pzone
->therm_work
);
270 static irqreturn_t
prcmu_high_irq_handler(int irq
, void *irq_data
)
272 struct db8500_thermal_zone
*pzone
= irq_data
;
273 struct db8500_thsens_platform_data
*ptrips
= pzone
->trip_tab
;
274 unsigned int idx
= pzone
->cur_index
;
275 unsigned long next_low
, next_high
;
277 if (idx
< ptrips
->num_trips
- 1) {
278 next_high
= ptrips
->trip_points
[idx
+1].temp
;
279 next_low
= ptrips
->trip_points
[idx
].temp
;
282 db8500_thermal_update_config(pzone
, idx
, THERMAL_TREND_RAISING
,
283 next_low
, next_high
);
285 dev_dbg(&pzone
->therm_dev
->device
,
286 "PRCMU set max %ld, min %ld\n", next_high
, next_low
);
287 } else if (idx
== ptrips
->num_trips
- 1)
288 pzone
->cur_temp_pseudo
= ptrips
->trip_points
[idx
].temp
+ 1;
290 schedule_work(&pzone
->therm_work
);
295 static void db8500_thermal_work(struct work_struct
*work
)
297 enum thermal_device_mode cur_mode
;
298 struct db8500_thermal_zone
*pzone
;
300 pzone
= container_of(work
, struct db8500_thermal_zone
, therm_work
);
302 mutex_lock(&pzone
->th_lock
);
303 cur_mode
= pzone
->mode
;
304 mutex_unlock(&pzone
->th_lock
);
306 if (cur_mode
== THERMAL_DEVICE_DISABLED
)
309 thermal_zone_device_update(pzone
->therm_dev
, THERMAL_EVENT_UNSPECIFIED
);
310 dev_dbg(&pzone
->therm_dev
->device
, "thermal work finished.\n");
314 static struct db8500_thsens_platform_data
*
315 db8500_thermal_parse_dt(struct platform_device
*pdev
)
317 struct db8500_thsens_platform_data
*ptrips
;
318 struct device_node
*np
= pdev
->dev
.of_node
;
324 ptrips
= devm_kzalloc(&pdev
->dev
, sizeof(*ptrips
), GFP_KERNEL
);
328 if (of_property_read_u32(np
, "num-trips", &tmp_data
))
331 if (tmp_data
> THERMAL_MAX_TRIPS
)
334 ptrips
->num_trips
= tmp_data
;
336 for (i
= 0; i
< ptrips
->num_trips
; i
++) {
337 sprintf(prop_name
, "trip%d-temp", i
);
338 if (of_property_read_u32(np
, prop_name
, &tmp_data
))
341 ptrips
->trip_points
[i
].temp
= tmp_data
;
343 sprintf(prop_name
, "trip%d-type", i
);
344 if (of_property_read_string(np
, prop_name
, &tmp_str
))
347 if (!strcmp(tmp_str
, "active"))
348 ptrips
->trip_points
[i
].type
= THERMAL_TRIP_ACTIVE
;
349 else if (!strcmp(tmp_str
, "passive"))
350 ptrips
->trip_points
[i
].type
= THERMAL_TRIP_PASSIVE
;
351 else if (!strcmp(tmp_str
, "hot"))
352 ptrips
->trip_points
[i
].type
= THERMAL_TRIP_HOT
;
353 else if (!strcmp(tmp_str
, "critical"))
354 ptrips
->trip_points
[i
].type
= THERMAL_TRIP_CRITICAL
;
358 sprintf(prop_name
, "trip%d-cdev-num", i
);
359 if (of_property_read_u32(np
, prop_name
, &tmp_data
))
362 if (tmp_data
> COOLING_DEV_MAX
)
365 for (j
= 0; j
< tmp_data
; j
++) {
366 sprintf(prop_name
, "trip%d-cdev-name%d", i
, j
);
367 if (of_property_read_string(np
, prop_name
, &tmp_str
))
370 if (strlen(tmp_str
) >= THERMAL_NAME_LENGTH
)
373 strcpy(ptrips
->trip_points
[i
].cdev_name
[j
], tmp_str
);
379 dev_err(&pdev
->dev
, "Parsing device tree data error.\n");
383 static inline struct db8500_thsens_platform_data
*
384 db8500_thermal_parse_dt(struct platform_device
*pdev
)
390 static int db8500_thermal_probe(struct platform_device
*pdev
)
392 struct db8500_thermal_zone
*pzone
= NULL
;
393 struct db8500_thsens_platform_data
*ptrips
= NULL
;
394 struct device_node
*np
= pdev
->dev
.of_node
;
395 int low_irq
, high_irq
, ret
= 0;
396 unsigned long dft_low
, dft_high
;
399 ptrips
= db8500_thermal_parse_dt(pdev
);
401 ptrips
= dev_get_platdata(&pdev
->dev
);
406 pzone
= devm_kzalloc(&pdev
->dev
, sizeof(*pzone
), GFP_KERNEL
);
410 mutex_init(&pzone
->th_lock
);
411 mutex_lock(&pzone
->th_lock
);
413 pzone
->mode
= THERMAL_DEVICE_DISABLED
;
414 pzone
->trip_tab
= ptrips
;
416 INIT_WORK(&pzone
->therm_work
, db8500_thermal_work
);
418 low_irq
= platform_get_irq_byname(pdev
, "IRQ_HOTMON_LOW");
420 dev_err(&pdev
->dev
, "Get IRQ_HOTMON_LOW failed.\n");
425 ret
= devm_request_threaded_irq(&pdev
->dev
, low_irq
, NULL
,
426 prcmu_low_irq_handler
, IRQF_NO_SUSPEND
| IRQF_ONESHOT
,
427 "dbx500_temp_low", pzone
);
429 dev_err(&pdev
->dev
, "Failed to allocate temp low irq.\n");
433 high_irq
= platform_get_irq_byname(pdev
, "IRQ_HOTMON_HIGH");
435 dev_err(&pdev
->dev
, "Get IRQ_HOTMON_HIGH failed.\n");
440 ret
= devm_request_threaded_irq(&pdev
->dev
, high_irq
, NULL
,
441 prcmu_high_irq_handler
, IRQF_NO_SUSPEND
| IRQF_ONESHOT
,
442 "dbx500_temp_high", pzone
);
444 dev_err(&pdev
->dev
, "Failed to allocate temp high irq.\n");
448 pzone
->therm_dev
= thermal_zone_device_register("db8500_thermal_zone",
449 ptrips
->num_trips
, 0, pzone
, &thdev_ops
, NULL
, 0, 0);
451 if (IS_ERR(pzone
->therm_dev
)) {
452 dev_err(&pdev
->dev
, "Register thermal zone device failed.\n");
453 ret
= PTR_ERR(pzone
->therm_dev
);
456 dev_info(&pdev
->dev
, "Thermal zone device registered.\n");
458 dft_low
= PRCMU_DEFAULT_LOW_TEMP
;
459 dft_high
= ptrips
->trip_points
[0].temp
;
461 db8500_thermal_update_config(pzone
, 0, THERMAL_TREND_STABLE
,
464 platform_set_drvdata(pdev
, pzone
);
465 pzone
->mode
= THERMAL_DEVICE_ENABLED
;
468 mutex_unlock(&pzone
->th_lock
);
473 static int db8500_thermal_remove(struct platform_device
*pdev
)
475 struct db8500_thermal_zone
*pzone
= platform_get_drvdata(pdev
);
477 thermal_zone_device_unregister(pzone
->therm_dev
);
478 cancel_work_sync(&pzone
->therm_work
);
479 mutex_destroy(&pzone
->th_lock
);
484 static int db8500_thermal_suspend(struct platform_device
*pdev
,
487 struct db8500_thermal_zone
*pzone
= platform_get_drvdata(pdev
);
489 flush_work(&pzone
->therm_work
);
490 prcmu_stop_temp_sense();
495 static int db8500_thermal_resume(struct platform_device
*pdev
)
497 struct db8500_thermal_zone
*pzone
= platform_get_drvdata(pdev
);
498 struct db8500_thsens_platform_data
*ptrips
= pzone
->trip_tab
;
499 unsigned long dft_low
, dft_high
;
501 dft_low
= PRCMU_DEFAULT_LOW_TEMP
;
502 dft_high
= ptrips
->trip_points
[0].temp
;
504 db8500_thermal_update_config(pzone
, 0, THERMAL_TREND_STABLE
,
511 static const struct of_device_id db8500_thermal_match
[] = {
512 { .compatible
= "stericsson,db8500-thermal" },
517 static struct platform_driver db8500_thermal_driver
= {
519 .name
= "db8500-thermal",
520 .of_match_table
= of_match_ptr(db8500_thermal_match
),
522 .probe
= db8500_thermal_probe
,
523 .suspend
= db8500_thermal_suspend
,
524 .resume
= db8500_thermal_resume
,
525 .remove
= db8500_thermal_remove
,
528 module_platform_driver(db8500_thermal_driver
);
530 MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
531 MODULE_DESCRIPTION("DB8500 thermal driver");
532 MODULE_LICENSE("GPL");