thermal: db8500: Finalize device tree conversion
[linux/fpc-iii.git] / drivers / thermal / db8500_thermal.c
blobd650ae5fdf2ab749efc81acf5c5459b8fb64f089
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * db8500_thermal.c - DB8500 Thermal Management Implementation
5 * Copyright (C) 2012 ST-Ericsson
6 * Copyright (C) 2012 Linaro Ltd.
8 * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
9 */
11 #include <linux/cpu_cooling.h>
12 #include <linux/interrupt.h>
13 #include <linux/mfd/dbx500-prcmu.h>
14 #include <linux/module.h>
15 #include <linux/of.h>
16 #include <linux/platform_device.h>
17 #include <linux/slab.h>
18 #include <linux/thermal.h>
20 #define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
21 #define PRCMU_DEFAULT_LOW_TEMP 0
22 #define COOLING_DEV_MAX 8
24 struct db8500_trip_point {
25 unsigned long temp;
26 enum thermal_trip_type type;
27 char cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH];
30 struct db8500_thsens_platform_data {
31 struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS];
32 int num_trips;
35 struct db8500_thermal_zone {
36 struct thermal_zone_device *therm_dev;
37 struct mutex th_lock;
38 struct work_struct therm_work;
39 struct db8500_thsens_platform_data *trip_tab;
40 enum thermal_device_mode mode;
41 enum thermal_trend trend;
42 unsigned long cur_temp_pseudo;
43 unsigned int cur_index;
46 /* Local function to check if thermal zone matches cooling devices */
47 static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
48 struct db8500_trip_point *trip_point)
50 int i;
52 if (!strlen(cdev->type))
53 return -EINVAL;
55 for (i = 0; i < COOLING_DEV_MAX; i++) {
56 if (!strcmp(trip_point->cdev_name[i], cdev->type))
57 return 0;
60 return -ENODEV;
63 /* Callback to bind cooling device to thermal zone */
64 static int db8500_cdev_bind(struct thermal_zone_device *thermal,
65 struct thermal_cooling_device *cdev)
67 struct db8500_thermal_zone *pzone = thermal->devdata;
68 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
69 unsigned long max_state, upper, lower;
70 int i, ret = -EINVAL;
72 cdev->ops->get_max_state(cdev, &max_state);
74 for (i = 0; i < ptrips->num_trips; i++) {
75 if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
76 continue;
78 upper = lower = i > max_state ? max_state : i;
80 ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
81 upper, lower, THERMAL_WEIGHT_DEFAULT);
83 dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
84 i, ret, ret ? "fail" : "succeed");
87 return ret;
90 /* Callback to unbind cooling device from thermal zone */
91 static int db8500_cdev_unbind(struct thermal_zone_device *thermal,
92 struct thermal_cooling_device *cdev)
94 struct db8500_thermal_zone *pzone = thermal->devdata;
95 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
96 int i, ret = -EINVAL;
98 for (i = 0; i < ptrips->num_trips; i++) {
99 if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
100 continue;
102 ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
104 dev_info(&cdev->device, "%s unbind from %d: %s\n", cdev->type,
105 i, ret ? "fail" : "succeed");
108 return ret;
111 /* Callback to get current temperature */
112 static int db8500_sys_get_temp(struct thermal_zone_device *thermal, int *temp)
114 struct db8500_thermal_zone *pzone = thermal->devdata;
117 * TODO: There is no PRCMU interface to get temperature data currently,
118 * so a pseudo temperature is returned , it works for thermal framework
119 * and this will be fixed when the PRCMU interface is available.
121 *temp = pzone->cur_temp_pseudo;
123 return 0;
126 /* Callback to get temperature changing trend */
127 static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
128 int trip, enum thermal_trend *trend)
130 struct db8500_thermal_zone *pzone = thermal->devdata;
132 *trend = pzone->trend;
134 return 0;
137 /* Callback to get thermal zone mode */
138 static int db8500_sys_get_mode(struct thermal_zone_device *thermal,
139 enum thermal_device_mode *mode)
141 struct db8500_thermal_zone *pzone = thermal->devdata;
143 mutex_lock(&pzone->th_lock);
144 *mode = pzone->mode;
145 mutex_unlock(&pzone->th_lock);
147 return 0;
150 /* Callback to set thermal zone mode */
151 static int db8500_sys_set_mode(struct thermal_zone_device *thermal,
152 enum thermal_device_mode mode)
154 struct db8500_thermal_zone *pzone = thermal->devdata;
156 mutex_lock(&pzone->th_lock);
158 pzone->mode = mode;
159 if (mode == THERMAL_DEVICE_ENABLED)
160 schedule_work(&pzone->therm_work);
162 mutex_unlock(&pzone->th_lock);
164 return 0;
167 /* Callback to get trip point type */
168 static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
169 int trip, enum thermal_trip_type *type)
171 struct db8500_thermal_zone *pzone = thermal->devdata;
172 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
174 if (trip >= ptrips->num_trips)
175 return -EINVAL;
177 *type = ptrips->trip_points[trip].type;
179 return 0;
182 /* Callback to get trip point temperature */
183 static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
184 int trip, int *temp)
186 struct db8500_thermal_zone *pzone = thermal->devdata;
187 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
189 if (trip >= ptrips->num_trips)
190 return -EINVAL;
192 *temp = ptrips->trip_points[trip].temp;
194 return 0;
197 /* Callback to get critical trip point temperature */
198 static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
199 int *temp)
201 struct db8500_thermal_zone *pzone = thermal->devdata;
202 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
203 int i;
205 for (i = ptrips->num_trips - 1; i > 0; i--) {
206 if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) {
207 *temp = ptrips->trip_points[i].temp;
208 return 0;
212 return -EINVAL;
215 static struct thermal_zone_device_ops thdev_ops = {
216 .bind = db8500_cdev_bind,
217 .unbind = db8500_cdev_unbind,
218 .get_temp = db8500_sys_get_temp,
219 .get_trend = db8500_sys_get_trend,
220 .get_mode = db8500_sys_get_mode,
221 .set_mode = db8500_sys_set_mode,
222 .get_trip_type = db8500_sys_get_trip_type,
223 .get_trip_temp = db8500_sys_get_trip_temp,
224 .get_crit_temp = db8500_sys_get_crit_temp,
227 static void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
228 unsigned int idx, enum thermal_trend trend,
229 unsigned long next_low, unsigned long next_high)
231 prcmu_stop_temp_sense();
233 pzone->cur_index = idx;
234 pzone->cur_temp_pseudo = (next_low + next_high)/2;
235 pzone->trend = trend;
237 prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
238 prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
241 static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
243 struct db8500_thermal_zone *pzone = irq_data;
244 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
245 unsigned int idx = pzone->cur_index;
246 unsigned long next_low, next_high;
248 if (unlikely(idx == 0))
249 /* Meaningless for thermal management, ignoring it */
250 return IRQ_HANDLED;
252 if (idx == 1) {
253 next_high = ptrips->trip_points[0].temp;
254 next_low = PRCMU_DEFAULT_LOW_TEMP;
255 } else {
256 next_high = ptrips->trip_points[idx-1].temp;
257 next_low = ptrips->trip_points[idx-2].temp;
259 idx -= 1;
261 db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
262 next_low, next_high);
264 dev_dbg(&pzone->therm_dev->device,
265 "PRCMU set max %ld, min %ld\n", next_high, next_low);
267 schedule_work(&pzone->therm_work);
269 return IRQ_HANDLED;
272 static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
274 struct db8500_thermal_zone *pzone = irq_data;
275 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
276 unsigned int idx = pzone->cur_index;
277 unsigned long next_low, next_high;
279 if (idx < ptrips->num_trips - 1) {
280 next_high = ptrips->trip_points[idx+1].temp;
281 next_low = ptrips->trip_points[idx].temp;
282 idx += 1;
284 db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING,
285 next_low, next_high);
287 dev_dbg(&pzone->therm_dev->device,
288 "PRCMU set max %ld, min %ld\n", next_high, next_low);
289 } else if (idx == ptrips->num_trips - 1)
290 pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
292 schedule_work(&pzone->therm_work);
294 return IRQ_HANDLED;
297 static void db8500_thermal_work(struct work_struct *work)
299 enum thermal_device_mode cur_mode;
300 struct db8500_thermal_zone *pzone;
302 pzone = container_of(work, struct db8500_thermal_zone, therm_work);
304 mutex_lock(&pzone->th_lock);
305 cur_mode = pzone->mode;
306 mutex_unlock(&pzone->th_lock);
308 if (cur_mode == THERMAL_DEVICE_DISABLED)
309 return;
311 thermal_zone_device_update(pzone->therm_dev, THERMAL_EVENT_UNSPECIFIED);
312 dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n");
315 static struct db8500_thsens_platform_data*
316 db8500_thermal_parse_dt(struct platform_device *pdev)
318 struct db8500_thsens_platform_data *ptrips;
319 struct device_node *np = pdev->dev.of_node;
320 char prop_name[32];
321 const char *tmp_str;
322 u32 tmp_data;
323 int i, j;
325 ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
326 if (!ptrips)
327 return NULL;
329 if (of_property_read_u32(np, "num-trips", &tmp_data))
330 goto err_parse_dt;
332 if (tmp_data > THERMAL_MAX_TRIPS)
333 goto err_parse_dt;
335 ptrips->num_trips = tmp_data;
337 for (i = 0; i < ptrips->num_trips; i++) {
338 sprintf(prop_name, "trip%d-temp", i);
339 if (of_property_read_u32(np, prop_name, &tmp_data))
340 goto err_parse_dt;
342 ptrips->trip_points[i].temp = tmp_data;
344 sprintf(prop_name, "trip%d-type", i);
345 if (of_property_read_string(np, prop_name, &tmp_str))
346 goto err_parse_dt;
348 if (!strcmp(tmp_str, "active"))
349 ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
350 else if (!strcmp(tmp_str, "passive"))
351 ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
352 else if (!strcmp(tmp_str, "hot"))
353 ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
354 else if (!strcmp(tmp_str, "critical"))
355 ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
356 else
357 goto err_parse_dt;
359 sprintf(prop_name, "trip%d-cdev-num", i);
360 if (of_property_read_u32(np, prop_name, &tmp_data))
361 goto err_parse_dt;
363 if (tmp_data > COOLING_DEV_MAX)
364 goto err_parse_dt;
366 for (j = 0; j < tmp_data; j++) {
367 sprintf(prop_name, "trip%d-cdev-name%d", i, j);
368 if (of_property_read_string(np, prop_name, &tmp_str))
369 goto err_parse_dt;
371 if (strlen(tmp_str) >= THERMAL_NAME_LENGTH)
372 goto err_parse_dt;
374 strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
377 return ptrips;
379 err_parse_dt:
380 dev_err(&pdev->dev, "Parsing device tree data error.\n");
381 return NULL;
384 static int db8500_thermal_probe(struct platform_device *pdev)
386 struct db8500_thermal_zone *pzone = NULL;
387 struct db8500_thsens_platform_data *ptrips = NULL;
388 struct device_node *np = pdev->dev.of_node;
389 int low_irq, high_irq, ret = 0;
390 unsigned long dft_low, dft_high;
392 if (!np)
393 return -EINVAL;
395 ptrips = db8500_thermal_parse_dt(pdev);
396 if (!ptrips)
397 return -EINVAL;
399 pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
400 if (!pzone)
401 return -ENOMEM;
403 mutex_init(&pzone->th_lock);
404 mutex_lock(&pzone->th_lock);
406 pzone->mode = THERMAL_DEVICE_DISABLED;
407 pzone->trip_tab = ptrips;
409 INIT_WORK(&pzone->therm_work, db8500_thermal_work);
411 low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
412 if (low_irq < 0) {
413 dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
414 ret = low_irq;
415 goto out_unlock;
418 ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
419 prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
420 "dbx500_temp_low", pzone);
421 if (ret < 0) {
422 dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
423 goto out_unlock;
426 high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
427 if (high_irq < 0) {
428 dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
429 ret = high_irq;
430 goto out_unlock;
433 ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
434 prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
435 "dbx500_temp_high", pzone);
436 if (ret < 0) {
437 dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
438 goto out_unlock;
441 pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
442 ptrips->num_trips, 0, pzone, &thdev_ops, NULL, 0, 0);
444 if (IS_ERR(pzone->therm_dev)) {
445 dev_err(&pdev->dev, "Register thermal zone device failed.\n");
446 ret = PTR_ERR(pzone->therm_dev);
447 goto out_unlock;
449 dev_info(&pdev->dev, "Thermal zone device registered.\n");
451 dft_low = PRCMU_DEFAULT_LOW_TEMP;
452 dft_high = ptrips->trip_points[0].temp;
454 db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
455 dft_low, dft_high);
457 platform_set_drvdata(pdev, pzone);
458 pzone->mode = THERMAL_DEVICE_ENABLED;
460 out_unlock:
461 mutex_unlock(&pzone->th_lock);
463 return ret;
466 static int db8500_thermal_remove(struct platform_device *pdev)
468 struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
470 thermal_zone_device_unregister(pzone->therm_dev);
471 cancel_work_sync(&pzone->therm_work);
472 mutex_destroy(&pzone->th_lock);
474 return 0;
477 static int db8500_thermal_suspend(struct platform_device *pdev,
478 pm_message_t state)
480 struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
482 flush_work(&pzone->therm_work);
483 prcmu_stop_temp_sense();
485 return 0;
488 static int db8500_thermal_resume(struct platform_device *pdev)
490 struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
491 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
492 unsigned long dft_low, dft_high;
494 dft_low = PRCMU_DEFAULT_LOW_TEMP;
495 dft_high = ptrips->trip_points[0].temp;
497 db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
498 dft_low, dft_high);
500 return 0;
503 static const struct of_device_id db8500_thermal_match[] = {
504 { .compatible = "stericsson,db8500-thermal" },
507 MODULE_DEVICE_TABLE(of, db8500_thermal_match);
509 static struct platform_driver db8500_thermal_driver = {
510 .driver = {
511 .name = "db8500-thermal",
512 .of_match_table = of_match_ptr(db8500_thermal_match),
514 .probe = db8500_thermal_probe,
515 .suspend = db8500_thermal_suspend,
516 .resume = db8500_thermal_resume,
517 .remove = db8500_thermal_remove,
520 module_platform_driver(db8500_thermal_driver);
522 MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
523 MODULE_DESCRIPTION("DB8500 thermal driver");
524 MODULE_LICENSE("GPL");