Root/drivers/thermal/db8500_thermal.c

1/*
2 * db8500_thermal.c - DB8500 Thermal Management Implementation
3 *
4 * Copyright (C) 2012 ST-Ericsson
5 * Copyright (C) 2012 Linaro Ltd.
6 *
7 * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
8 *
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.
13 *
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.
18 */
19
20#include <linux/cpu_cooling.h>
21#include <linux/interrupt.h>
22#include <linux/mfd/dbx500-prcmu.h>
23#include <linux/module.h>
24#include <linux/of.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>
29
30#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
31#define PRCMU_DEFAULT_LOW_TEMP 0
32
33struct db8500_thermal_zone {
34    struct thermal_zone_device *therm_dev;
35    struct mutex th_lock;
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;
42};
43
44/* Local function to check if thermal zone matches cooling devices */
45static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
46        struct db8500_trip_point *trip_point)
47{
48    int i;
49
50    if (!strlen(cdev->type))
51        return -EINVAL;
52
53    for (i = 0; i < COOLING_DEV_MAX; i++) {
54        if (!strcmp(trip_point->cdev_name[i], cdev->type))
55            return 0;
56    }
57
58    return -ENODEV;
59}
60
61/* Callback to bind cooling device to thermal zone */
62static int db8500_cdev_bind(struct thermal_zone_device *thermal,
63        struct thermal_cooling_device *cdev)
64{
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;
68    int i, ret = -EINVAL;
69
70    cdev->ops->get_max_state(cdev, &max_state);
71
72    for (i = 0; i < ptrips->num_trips; i++) {
73        if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
74            continue;
75
76        upper = lower = i > max_state ? max_state : i;
77
78        ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
79            upper, lower);
80
81        dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
82            i, ret, ret ? "fail" : "succeed");
83    }
84
85    return ret;
86}
87
88/* Callback to unbind cooling device from thermal zone */
89static int db8500_cdev_unbind(struct thermal_zone_device *thermal,
90        struct thermal_cooling_device *cdev)
91{
92    struct db8500_thermal_zone *pzone = thermal->devdata;
93    struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
94    int i, ret = -EINVAL;
95
96    for (i = 0; i < ptrips->num_trips; i++) {
97        if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
98            continue;
99
100        ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
101
102        dev_info(&cdev->device, "%s unbind from %d: %s\n", cdev->type,
103            i, ret ? "fail" : "succeed");
104    }
105
106    return ret;
107}
108
109/* Callback to get current temperature */
110static int db8500_sys_get_temp(struct thermal_zone_device *thermal,
111        unsigned long *temp)
112{
113    struct db8500_thermal_zone *pzone = thermal->devdata;
114
115    /*
116     * TODO: There is no PRCMU interface to get temperature data currently,
117     * so a pseudo temperature is returned , it works for thermal framework
118     * and this will be fixed when the PRCMU interface is available.
119     */
120    *temp = pzone->cur_temp_pseudo;
121
122    return 0;
123}
124
125/* Callback to get temperature changing trend */
126static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
127        int trip, enum thermal_trend *trend)
128{
129    struct db8500_thermal_zone *pzone = thermal->devdata;
130
131    *trend = pzone->trend;
132
133    return 0;
134}
135
136/* Callback to get thermal zone mode */
137static int db8500_sys_get_mode(struct thermal_zone_device *thermal,
138        enum thermal_device_mode *mode)
139{
140    struct db8500_thermal_zone *pzone = thermal->devdata;
141
142    mutex_lock(&pzone->th_lock);
143    *mode = pzone->mode;
144    mutex_unlock(&pzone->th_lock);
145
146    return 0;
147}
148
149/* Callback to set thermal zone mode */
150static int db8500_sys_set_mode(struct thermal_zone_device *thermal,
151        enum thermal_device_mode mode)
152{
153    struct db8500_thermal_zone *pzone = thermal->devdata;
154
155    mutex_lock(&pzone->th_lock);
156
157    pzone->mode = mode;
158    if (mode == THERMAL_DEVICE_ENABLED)
159        schedule_work(&pzone->therm_work);
160
161    mutex_unlock(&pzone->th_lock);
162
163    return 0;
164}
165
166/* Callback to get trip point type */
167static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
168        int trip, enum thermal_trip_type *type)
169{
170    struct db8500_thermal_zone *pzone = thermal->devdata;
171    struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
172
173    if (trip >= ptrips->num_trips)
174        return -EINVAL;
175
176    *type = ptrips->trip_points[trip].type;
177
178    return 0;
179}
180
181/* Callback to get trip point temperature */
182static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
183        int trip, unsigned long *temp)
184{
185    struct db8500_thermal_zone *pzone = thermal->devdata;
186    struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
187
188    if (trip >= ptrips->num_trips)
189        return -EINVAL;
190
191    *temp = ptrips->trip_points[trip].temp;
192
193    return 0;
194}
195
196/* Callback to get critical trip point temperature */
197static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
198        unsigned long *temp)
199{
200    struct db8500_thermal_zone *pzone = thermal->devdata;
201    struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
202    int i;
203
204    for (i = ptrips->num_trips - 1; i > 0; i--) {
205        if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) {
206            *temp = ptrips->trip_points[i].temp;
207            return 0;
208        }
209    }
210
211    return -EINVAL;
212}
213
214static struct thermal_zone_device_ops thdev_ops = {
215    .bind = db8500_cdev_bind,
216    .unbind = db8500_cdev_unbind,
217    .get_temp = db8500_sys_get_temp,
218    .get_trend = db8500_sys_get_trend,
219    .get_mode = db8500_sys_get_mode,
220    .set_mode = db8500_sys_set_mode,
221    .get_trip_type = db8500_sys_get_trip_type,
222    .get_trip_temp = db8500_sys_get_trip_temp,
223    .get_crit_temp = db8500_sys_get_crit_temp,
224};
225
226static void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
227        unsigned int idx, enum thermal_trend trend,
228        unsigned long next_low, unsigned long next_high)
229{
230    prcmu_stop_temp_sense();
231
232    pzone->cur_index = idx;
233    pzone->cur_temp_pseudo = (next_low + next_high)/2;
234    pzone->trend = trend;
235
236    prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
237    prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
238}
239
240static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
241{
242    struct db8500_thermal_zone *pzone = irq_data;
243    struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
244    unsigned int idx = pzone->cur_index;
245    unsigned long next_low, next_high;
246
247    if (unlikely(idx == 0))
248        /* Meaningless for thermal management, ignoring it */
249        return IRQ_HANDLED;
250
251    if (idx == 1) {
252        next_high = ptrips->trip_points[0].temp;
253        next_low = PRCMU_DEFAULT_LOW_TEMP;
254    } else {
255        next_high = ptrips->trip_points[idx-1].temp;
256        next_low = ptrips->trip_points[idx-2].temp;
257    }
258    idx -= 1;
259
260    db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
261        next_low, next_high);
262
263    dev_dbg(&pzone->therm_dev->device,
264        "PRCMU set max %ld, min %ld\n", next_high, next_low);
265
266    schedule_work(&pzone->therm_work);
267
268    return IRQ_HANDLED;
269}
270
271static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
272{
273    struct db8500_thermal_zone *pzone = irq_data;
274    struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
275    unsigned int idx = pzone->cur_index;
276    unsigned long next_low, next_high;
277
278    if (idx < ptrips->num_trips - 1) {
279        next_high = ptrips->trip_points[idx+1].temp;
280        next_low = ptrips->trip_points[idx].temp;
281        idx += 1;
282
283        db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING,
284            next_low, next_high);
285
286        dev_dbg(&pzone->therm_dev->device,
287        "PRCMU set max %ld, min %ld\n", next_high, next_low);
288    } else if (idx == ptrips->num_trips - 1)
289        pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
290
291    schedule_work(&pzone->therm_work);
292
293    return IRQ_HANDLED;
294}
295
296static void db8500_thermal_work(struct work_struct *work)
297{
298    enum thermal_device_mode cur_mode;
299    struct db8500_thermal_zone *pzone;
300
301    pzone = container_of(work, struct db8500_thermal_zone, therm_work);
302
303    mutex_lock(&pzone->th_lock);
304    cur_mode = pzone->mode;
305    mutex_unlock(&pzone->th_lock);
306
307    if (cur_mode == THERMAL_DEVICE_DISABLED)
308        return;
309
310    thermal_zone_device_update(pzone->therm_dev);
311    dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n");
312}
313
314#ifdef CONFIG_OF
315static 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
379err_parse_dt:
380    dev_err(&pdev->dev, "Parsing device tree data error.\n");
381    return NULL;
382}
383#else
384static inline struct db8500_thsens_platform_data*
385        db8500_thermal_parse_dt(struct platform_device *pdev)
386{
387    return NULL;
388}
389#endif
390
391static int db8500_thermal_probe(struct platform_device *pdev)
392{
393    struct db8500_thermal_zone *pzone = NULL;
394    struct db8500_thsens_platform_data *ptrips = NULL;
395    struct device_node *np = pdev->dev.of_node;
396    int low_irq, high_irq, ret = 0;
397    unsigned long dft_low, dft_high;
398
399    if (np)
400        ptrips = db8500_thermal_parse_dt(pdev);
401    else
402        ptrips = dev_get_platdata(&pdev->dev);
403
404    if (!ptrips)
405        return -EINVAL;
406
407    pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
408    if (!pzone)
409        return -ENOMEM;
410
411    mutex_init(&pzone->th_lock);
412    mutex_lock(&pzone->th_lock);
413
414    pzone->mode = THERMAL_DEVICE_DISABLED;
415    pzone->trip_tab = ptrips;
416
417    INIT_WORK(&pzone->therm_work, db8500_thermal_work);
418
419    low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
420    if (low_irq < 0) {
421        dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
422        return low_irq;
423    }
424
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);
428    if (ret < 0) {
429        dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
430        return ret;
431    }
432
433    high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
434    if (high_irq < 0) {
435        dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
436        return high_irq;
437    }
438
439    ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
440        prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
441        "dbx500_temp_high", pzone);
442    if (ret < 0) {
443        dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
444        return ret;
445    }
446
447    pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
448        ptrips->num_trips, 0, pzone, &thdev_ops, NULL, 0, 0);
449
450    if (IS_ERR_OR_NULL(pzone->therm_dev)) {
451        dev_err(&pdev->dev, "Register thermal zone device failed.\n");
452        return PTR_ERR(pzone->therm_dev);
453    }
454    dev_info(&pdev->dev, "Thermal zone device registered.\n");
455
456    dft_low = PRCMU_DEFAULT_LOW_TEMP;
457    dft_high = ptrips->trip_points[0].temp;
458
459    db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
460        dft_low, dft_high);
461
462    platform_set_drvdata(pdev, pzone);
463    pzone->mode = THERMAL_DEVICE_ENABLED;
464    mutex_unlock(&pzone->th_lock);
465
466    return 0;
467}
468
469static int db8500_thermal_remove(struct platform_device *pdev)
470{
471    struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
472
473    thermal_zone_device_unregister(pzone->therm_dev);
474    cancel_work_sync(&pzone->therm_work);
475    mutex_destroy(&pzone->th_lock);
476
477    return 0;
478}
479
480static int db8500_thermal_suspend(struct platform_device *pdev,
481        pm_message_t state)
482{
483    struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
484
485    flush_work(&pzone->therm_work);
486    prcmu_stop_temp_sense();
487
488    return 0;
489}
490
491static int db8500_thermal_resume(struct platform_device *pdev)
492{
493    struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
494    struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
495    unsigned long dft_low, dft_high;
496
497    dft_low = PRCMU_DEFAULT_LOW_TEMP;
498    dft_high = ptrips->trip_points[0].temp;
499
500    db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
501        dft_low, dft_high);
502
503    return 0;
504}
505
506#ifdef CONFIG_OF
507static const struct of_device_id db8500_thermal_match[] = {
508    { .compatible = "stericsson,db8500-thermal" },
509    {},
510};
511#endif
512
513static struct platform_driver db8500_thermal_driver = {
514    .driver = {
515        .owner = THIS_MODULE,
516        .name = "db8500-thermal",
517        .of_match_table = of_match_ptr(db8500_thermal_match),
518    },
519    .probe = db8500_thermal_probe,
520    .suspend = db8500_thermal_suspend,
521    .resume = db8500_thermal_resume,
522    .remove = db8500_thermal_remove,
523};
524
525module_platform_driver(db8500_thermal_driver);
526
527MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
528MODULE_DESCRIPTION("DB8500 thermal driver");
529MODULE_LICENSE("GPL");
530

Archive Download this file



interactive