]> asedeno.scripts.mit.edu Git - linux.git/blob - drivers/thermal/db8500_thermal.c
thermal: db8500: Finalize device tree conversion
[linux.git] / drivers / thermal / db8500_thermal.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * db8500_thermal.c - DB8500 Thermal Management Implementation
4  *
5  * Copyright (C) 2012 ST-Ericsson
6  * Copyright (C) 2012 Linaro Ltd.
7  *
8  * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
9  */
10
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>
19
20 #define PRCMU_DEFAULT_MEASURE_TIME      0xFFF
21 #define PRCMU_DEFAULT_LOW_TEMP          0
22 #define COOLING_DEV_MAX 8
23
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];
28 };
29
30 struct db8500_thsens_platform_data {
31         struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS];
32         int num_trips;
33 };
34
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;
44 };
45
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)
49 {
50         int i;
51
52         if (!strlen(cdev->type))
53                 return -EINVAL;
54
55         for (i = 0; i < COOLING_DEV_MAX; i++) {
56                 if (!strcmp(trip_point->cdev_name[i], cdev->type))
57                         return 0;
58         }
59
60         return -ENODEV;
61 }
62
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)
66 {
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;
71
72         cdev->ops->get_max_state(cdev, &max_state);
73
74         for (i = 0; i < ptrips->num_trips; i++) {
75                 if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
76                         continue;
77
78                 upper = lower = i > max_state ? max_state : i;
79
80                 ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
81                         upper, lower, THERMAL_WEIGHT_DEFAULT);
82
83                 dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
84                         i, ret, ret ? "fail" : "succeed");
85         }
86
87         return ret;
88 }
89
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)
93 {
94         struct db8500_thermal_zone *pzone = thermal->devdata;
95         struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
96         int i, ret = -EINVAL;
97
98         for (i = 0; i < ptrips->num_trips; i++) {
99                 if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
100                         continue;
101
102                 ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
103
104                 dev_info(&cdev->device, "%s unbind from %d: %s\n", cdev->type,
105                         i, ret ? "fail" : "succeed");
106         }
107
108         return ret;
109 }
110
111 /* Callback to get current temperature */
112 static int db8500_sys_get_temp(struct thermal_zone_device *thermal, int *temp)
113 {
114         struct db8500_thermal_zone *pzone = thermal->devdata;
115
116         /*
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.
120          */
121         *temp = pzone->cur_temp_pseudo;
122
123         return 0;
124 }
125
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)
129 {
130         struct db8500_thermal_zone *pzone = thermal->devdata;
131
132         *trend = pzone->trend;
133
134         return 0;
135 }
136
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)
140 {
141         struct db8500_thermal_zone *pzone = thermal->devdata;
142
143         mutex_lock(&pzone->th_lock);
144         *mode = pzone->mode;
145         mutex_unlock(&pzone->th_lock);
146
147         return 0;
148 }
149
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)
153 {
154         struct db8500_thermal_zone *pzone = thermal->devdata;
155
156         mutex_lock(&pzone->th_lock);
157
158         pzone->mode = mode;
159         if (mode == THERMAL_DEVICE_ENABLED)
160                 schedule_work(&pzone->therm_work);
161
162         mutex_unlock(&pzone->th_lock);
163
164         return 0;
165 }
166
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)
170 {
171         struct db8500_thermal_zone *pzone = thermal->devdata;
172         struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
173
174         if (trip >= ptrips->num_trips)
175                 return -EINVAL;
176
177         *type = ptrips->trip_points[trip].type;
178
179         return 0;
180 }
181
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)
185 {
186         struct db8500_thermal_zone *pzone = thermal->devdata;
187         struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
188
189         if (trip >= ptrips->num_trips)
190                 return -EINVAL;
191
192         *temp = ptrips->trip_points[trip].temp;
193
194         return 0;
195 }
196
197 /* Callback to get critical trip point temperature */
198 static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
199                 int *temp)
200 {
201         struct db8500_thermal_zone *pzone = thermal->devdata;
202         struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
203         int i;
204
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;
209                 }
210         }
211
212         return -EINVAL;
213 }
214
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,
225 };
226
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)
230 {
231         prcmu_stop_temp_sense();
232
233         pzone->cur_index = idx;
234         pzone->cur_temp_pseudo = (next_low + next_high)/2;
235         pzone->trend = trend;
236
237         prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
238         prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
239 }
240
241 static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
242 {
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;
247
248         if (unlikely(idx == 0))
249                 /* Meaningless for thermal management, ignoring it */
250                 return IRQ_HANDLED;
251
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;
258         }
259         idx -= 1;
260
261         db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
262                 next_low, next_high);
263
264         dev_dbg(&pzone->therm_dev->device,
265                 "PRCMU set max %ld, min %ld\n", next_high, next_low);
266
267         schedule_work(&pzone->therm_work);
268
269         return IRQ_HANDLED;
270 }
271
272 static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
273 {
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;
278
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;
283
284                 db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING,
285                         next_low, next_high);
286
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;
291
292         schedule_work(&pzone->therm_work);
293
294         return IRQ_HANDLED;
295 }
296
297 static void db8500_thermal_work(struct work_struct *work)
298 {
299         enum thermal_device_mode cur_mode;
300         struct db8500_thermal_zone *pzone;
301
302         pzone = container_of(work, struct db8500_thermal_zone, therm_work);
303
304         mutex_lock(&pzone->th_lock);
305         cur_mode = pzone->mode;
306         mutex_unlock(&pzone->th_lock);
307
308         if (cur_mode == THERMAL_DEVICE_DISABLED)
309                 return;
310
311         thermal_zone_device_update(pzone->therm_dev, THERMAL_EVENT_UNSPECIFIED);
312         dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n");
313 }
314
315 static struct db8500_thsens_platform_data*
316                 db8500_thermal_parse_dt(struct platform_device *pdev)
317 {
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;
324
325         ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
326         if (!ptrips)
327                 return NULL;
328
329         if (of_property_read_u32(np, "num-trips", &tmp_data))
330                 goto err_parse_dt;
331
332         if (tmp_data > THERMAL_MAX_TRIPS)
333                 goto err_parse_dt;
334
335         ptrips->num_trips = tmp_data;
336
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;
341
342                 ptrips->trip_points[i].temp = tmp_data;
343
344                 sprintf(prop_name, "trip%d-type", i);
345                 if (of_property_read_string(np, prop_name, &tmp_str))
346                         goto err_parse_dt;
347
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;
358
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;
362
363                 if (tmp_data > COOLING_DEV_MAX)
364                         goto err_parse_dt;
365
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;
370
371                         if (strlen(tmp_str) >= THERMAL_NAME_LENGTH)
372                                 goto err_parse_dt;
373
374                         strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
375                 }
376         }
377         return ptrips;
378
379 err_parse_dt:
380         dev_err(&pdev->dev, "Parsing device tree data error.\n");
381         return NULL;
382 }
383
384 static int db8500_thermal_probe(struct platform_device *pdev)
385 {
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;
391
392         if (!np)
393                 return -EINVAL;
394
395         ptrips = db8500_thermal_parse_dt(pdev);
396         if (!ptrips)
397                 return -EINVAL;
398
399         pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
400         if (!pzone)
401                 return -ENOMEM;
402
403         mutex_init(&pzone->th_lock);
404         mutex_lock(&pzone->th_lock);
405
406         pzone->mode = THERMAL_DEVICE_DISABLED;
407         pzone->trip_tab = ptrips;
408
409         INIT_WORK(&pzone->therm_work, db8500_thermal_work);
410
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;
416         }
417
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;
424         }
425
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;
431         }
432
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;
439         }
440
441         pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
442                 ptrips->num_trips, 0, pzone, &thdev_ops, NULL, 0, 0);
443
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;
448         }
449         dev_info(&pdev->dev, "Thermal zone device registered.\n");
450
451         dft_low = PRCMU_DEFAULT_LOW_TEMP;
452         dft_high = ptrips->trip_points[0].temp;
453
454         db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
455                 dft_low, dft_high);
456
457         platform_set_drvdata(pdev, pzone);
458         pzone->mode = THERMAL_DEVICE_ENABLED;
459
460 out_unlock:
461         mutex_unlock(&pzone->th_lock);
462
463         return ret;
464 }
465
466 static int db8500_thermal_remove(struct platform_device *pdev)
467 {
468         struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
469
470         thermal_zone_device_unregister(pzone->therm_dev);
471         cancel_work_sync(&pzone->therm_work);
472         mutex_destroy(&pzone->th_lock);
473
474         return 0;
475 }
476
477 static int db8500_thermal_suspend(struct platform_device *pdev,
478                 pm_message_t state)
479 {
480         struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
481
482         flush_work(&pzone->therm_work);
483         prcmu_stop_temp_sense();
484
485         return 0;
486 }
487
488 static int db8500_thermal_resume(struct platform_device *pdev)
489 {
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;
493
494         dft_low = PRCMU_DEFAULT_LOW_TEMP;
495         dft_high = ptrips->trip_points[0].temp;
496
497         db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
498                 dft_low, dft_high);
499
500         return 0;
501 }
502
503 static const struct of_device_id db8500_thermal_match[] = {
504         { .compatible = "stericsson,db8500-thermal" },
505         {},
506 };
507 MODULE_DEVICE_TABLE(of, db8500_thermal_match);
508
509 static struct platform_driver db8500_thermal_driver = {
510         .driver = {
511                 .name = "db8500-thermal",
512                 .of_match_table = of_match_ptr(db8500_thermal_match),
513         },
514         .probe = db8500_thermal_probe,
515         .suspend = db8500_thermal_suspend,
516         .resume = db8500_thermal_resume,
517         .remove = db8500_thermal_remove,
518 };
519
520 module_platform_driver(db8500_thermal_driver);
521
522 MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
523 MODULE_DESCRIPTION("DB8500 thermal driver");
524 MODULE_LICENSE("GPL");