linux/drivers/thermal/db8500_thermal.c
<<
>>
Prefs
   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-2019 Linaro Ltd.
   7 *
   8 * Authors: Hongbo Zhang, Linus Walleij
   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
  23/**
  24 * db8500_thermal_points - the interpolation points that trigger
  25 * interrupts
  26 */
  27static const unsigned long db8500_thermal_points[] = {
  28        15000,
  29        20000,
  30        25000,
  31        30000,
  32        35000,
  33        40000,
  34        45000,
  35        50000,
  36        55000,
  37        60000,
  38        65000,
  39        70000,
  40        75000,
  41        80000,
  42        /*
  43         * This is where things start to get really bad for the
  44         * SoC and the thermal zones should be set up to trigger
  45         * critical temperature at 85000 mC so we don't get above
  46         * this point.
  47         */
  48        85000,
  49        90000,
  50        95000,
  51        100000,
  52};
  53
  54struct db8500_thermal_zone {
  55        struct thermal_zone_device *tz;
  56        enum thermal_trend trend;
  57        unsigned long interpolated_temp;
  58        unsigned int cur_index;
  59};
  60
  61/* Callback to get current temperature */
  62static int db8500_thermal_get_temp(void *data, int *temp)
  63{
  64        struct db8500_thermal_zone *th = data;
  65
  66        /*
  67         * TODO: There is no PRCMU interface to get temperature data currently,
  68         * so a pseudo temperature is returned , it works for thermal framework
  69         * and this will be fixed when the PRCMU interface is available.
  70         */
  71        *temp = th->interpolated_temp;
  72
  73        return 0;
  74}
  75
  76/* Callback to get temperature changing trend */
  77static int db8500_thermal_get_trend(void *data, int trip, enum thermal_trend *trend)
  78{
  79        struct db8500_thermal_zone *th = data;
  80
  81        *trend = th->trend;
  82
  83        return 0;
  84}
  85
  86static struct thermal_zone_of_device_ops thdev_ops = {
  87        .get_temp = db8500_thermal_get_temp,
  88        .get_trend = db8500_thermal_get_trend,
  89};
  90
  91static void db8500_thermal_update_config(struct db8500_thermal_zone *th,
  92                                         unsigned int idx,
  93                                         enum thermal_trend trend,
  94                                         unsigned long next_low,
  95                                         unsigned long next_high)
  96{
  97        prcmu_stop_temp_sense();
  98
  99        th->cur_index = idx;
 100        th->interpolated_temp = (next_low + next_high)/2;
 101        th->trend = trend;
 102
 103        /*
 104         * The PRCMU accept absolute temperatures in celsius so divide
 105         * down the millicelsius with 1000
 106         */
 107        prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
 108        prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
 109}
 110
 111static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
 112{
 113        struct db8500_thermal_zone *th = irq_data;
 114        unsigned int idx = th->cur_index;
 115        unsigned long next_low, next_high;
 116
 117        if (idx == 0)
 118                /* Meaningless for thermal management, ignoring it */
 119                return IRQ_HANDLED;
 120
 121        if (idx == 1) {
 122                next_high = db8500_thermal_points[0];
 123                next_low = PRCMU_DEFAULT_LOW_TEMP;
 124        } else {
 125                next_high = db8500_thermal_points[idx - 1];
 126                next_low = db8500_thermal_points[idx - 2];
 127        }
 128        idx -= 1;
 129
 130        db8500_thermal_update_config(th, idx, THERMAL_TREND_DROPPING,
 131                                     next_low, next_high);
 132        dev_dbg(&th->tz->device,
 133                "PRCMU set max %ld, min %ld\n", next_high, next_low);
 134
 135        thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED);
 136
 137        return IRQ_HANDLED;
 138}
 139
 140static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
 141{
 142        struct db8500_thermal_zone *th = irq_data;
 143        unsigned int idx = th->cur_index;
 144        unsigned long next_low, next_high;
 145        int num_points = ARRAY_SIZE(db8500_thermal_points);
 146
 147        if (idx < num_points - 1) {
 148                next_high = db8500_thermal_points[idx+1];
 149                next_low = db8500_thermal_points[idx];
 150                idx += 1;
 151
 152                db8500_thermal_update_config(th, idx, THERMAL_TREND_RAISING,
 153                                             next_low, next_high);
 154
 155                dev_dbg(&th->tz->device,
 156                        "PRCMU set max %ld, min %ld\n", next_high, next_low);
 157        } else if (idx == num_points - 1)
 158                /* So we roof out 1 degree over the max point */
 159                th->interpolated_temp = db8500_thermal_points[idx] + 1;
 160
 161        thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED);
 162
 163        return IRQ_HANDLED;
 164}
 165
 166static int db8500_thermal_probe(struct platform_device *pdev)
 167{
 168        struct db8500_thermal_zone *th = NULL;
 169        struct device *dev = &pdev->dev;
 170        int low_irq, high_irq, ret = 0;
 171
 172        th = devm_kzalloc(dev, sizeof(*th), GFP_KERNEL);
 173        if (!th)
 174                return -ENOMEM;
 175
 176        low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
 177        if (low_irq < 0) {
 178                dev_err(dev, "Get IRQ_HOTMON_LOW failed\n");
 179                return low_irq;
 180        }
 181
 182        ret = devm_request_threaded_irq(dev, low_irq, NULL,
 183                prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
 184                "dbx500_temp_low", th);
 185        if (ret < 0) {
 186                dev_err(dev, "failed to allocate temp low irq\n");
 187                return ret;
 188        }
 189
 190        high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
 191        if (high_irq < 0) {
 192                dev_err(dev, "Get IRQ_HOTMON_HIGH failed\n");
 193                return high_irq;
 194        }
 195
 196        ret = devm_request_threaded_irq(dev, high_irq, NULL,
 197                prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
 198                "dbx500_temp_high", th);
 199        if (ret < 0) {
 200                dev_err(dev, "failed to allocate temp high irq\n");
 201                return ret;
 202        }
 203
 204        /* register of thermal sensor and get info from DT */
 205        th->tz = devm_thermal_zone_of_sensor_register(dev, 0, th, &thdev_ops);
 206        if (IS_ERR(th->tz)) {
 207                dev_err(dev, "register thermal zone sensor failed\n");
 208                return PTR_ERR(th->tz);
 209        }
 210        dev_info(dev, "thermal zone sensor registered\n");
 211
 212        /* Start measuring at the lowest point */
 213        db8500_thermal_update_config(th, 0, THERMAL_TREND_STABLE,
 214                                     PRCMU_DEFAULT_LOW_TEMP,
 215                                     db8500_thermal_points[0]);
 216
 217        platform_set_drvdata(pdev, th);
 218
 219        return 0;
 220}
 221
 222static int db8500_thermal_suspend(struct platform_device *pdev,
 223                pm_message_t state)
 224{
 225        prcmu_stop_temp_sense();
 226
 227        return 0;
 228}
 229
 230static int db8500_thermal_resume(struct platform_device *pdev)
 231{
 232        struct db8500_thermal_zone *th = platform_get_drvdata(pdev);
 233
 234        /* Resume and start measuring at the lowest point */
 235        db8500_thermal_update_config(th, 0, THERMAL_TREND_STABLE,
 236                                     PRCMU_DEFAULT_LOW_TEMP,
 237                                     db8500_thermal_points[0]);
 238
 239        return 0;
 240}
 241
 242static const struct of_device_id db8500_thermal_match[] = {
 243        { .compatible = "stericsson,db8500-thermal" },
 244        {},
 245};
 246MODULE_DEVICE_TABLE(of, db8500_thermal_match);
 247
 248static struct platform_driver db8500_thermal_driver = {
 249        .driver = {
 250                .name = "db8500-thermal",
 251                .of_match_table = of_match_ptr(db8500_thermal_match),
 252        },
 253        .probe = db8500_thermal_probe,
 254        .suspend = db8500_thermal_suspend,
 255        .resume = db8500_thermal_resume,
 256};
 257
 258module_platform_driver(db8500_thermal_driver);
 259
 260MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
 261MODULE_DESCRIPTION("DB8500 thermal driver");
 262MODULE_LICENSE("GPL");
 263