244 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * db8500_thermal.c - DB8500 Thermal Management Implementation4 *5 * Copyright (C) 2012 ST-Ericsson6 * Copyright (C) 2012-2019 Linaro Ltd.7 *8 * Authors: Hongbo Zhang, Linus Walleij9 */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 0xFFF21#define PRCMU_DEFAULT_LOW_TEMP 022 23/**24 * db8500_thermal_points - the interpolation points that trigger25 * interrupts26 */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 the44 * SoC and the thermal zones should be set up to trigger45 * critical temperature at 85000 mC so we don't get above46 * this point.47 */48 85000,49 90000,50 95000,51 100000,52};53 54struct db8500_thermal_zone {55 struct thermal_zone_device *tz;56 struct device *dev;57 unsigned long interpolated_temp;58 unsigned int cur_index;59};60 61/* Callback to get current temperature */62static int db8500_thermal_get_temp(struct thermal_zone_device *tz, int *temp)63{64 struct db8500_thermal_zone *th = thermal_zone_device_priv(tz);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 framework69 * and this will be fixed when the PRCMU interface is available.70 */71 *temp = th->interpolated_temp;72 73 return 0;74}75 76static const struct thermal_zone_device_ops thdev_ops = {77 .get_temp = db8500_thermal_get_temp,78};79 80static void db8500_thermal_update_config(struct db8500_thermal_zone *th,81 unsigned int idx,82 unsigned long next_low,83 unsigned long next_high)84{85 prcmu_stop_temp_sense();86 87 th->cur_index = idx;88 th->interpolated_temp = (next_low + next_high)/2;89 90 /*91 * The PRCMU accept absolute temperatures in celsius so divide92 * down the millicelsius with 100093 */94 prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));95 prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);96}97 98static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)99{100 struct db8500_thermal_zone *th = irq_data;101 unsigned int idx = th->cur_index;102 unsigned long next_low, next_high;103 104 if (idx == 0)105 /* Meaningless for thermal management, ignoring it */106 return IRQ_HANDLED;107 108 if (idx == 1) {109 next_high = db8500_thermal_points[0];110 next_low = PRCMU_DEFAULT_LOW_TEMP;111 } else {112 next_high = db8500_thermal_points[idx - 1];113 next_low = db8500_thermal_points[idx - 2];114 }115 idx -= 1;116 117 db8500_thermal_update_config(th, idx, next_low, next_high);118 dev_dbg(th->dev,119 "PRCMU set max %ld, min %ld\n", next_high, next_low);120 121 thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED);122 123 return IRQ_HANDLED;124}125 126static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)127{128 struct db8500_thermal_zone *th = irq_data;129 unsigned int idx = th->cur_index;130 unsigned long next_low, next_high;131 int num_points = ARRAY_SIZE(db8500_thermal_points);132 133 if (idx < num_points - 1) {134 next_high = db8500_thermal_points[idx+1];135 next_low = db8500_thermal_points[idx];136 idx += 1;137 138 db8500_thermal_update_config(th, idx, next_low, next_high);139 140 dev_dbg(th->dev,141 "PRCMU set max %ld, min %ld\n", next_high, next_low);142 } else if (idx == num_points - 1)143 /* So we roof out 1 degree over the max point */144 th->interpolated_temp = db8500_thermal_points[idx] + 1;145 146 thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED);147 148 return IRQ_HANDLED;149}150 151static int db8500_thermal_probe(struct platform_device *pdev)152{153 struct db8500_thermal_zone *th = NULL;154 struct device *dev = &pdev->dev;155 int low_irq, high_irq, ret = 0;156 157 th = devm_kzalloc(dev, sizeof(*th), GFP_KERNEL);158 if (!th)159 return -ENOMEM;160 161 th->dev = dev;162 163 low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");164 if (low_irq < 0)165 return low_irq;166 167 ret = devm_request_threaded_irq(dev, low_irq, NULL,168 prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,169 "dbx500_temp_low", th);170 if (ret < 0) {171 dev_err(dev, "failed to allocate temp low irq\n");172 return ret;173 }174 175 high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");176 if (high_irq < 0)177 return high_irq;178 179 ret = devm_request_threaded_irq(dev, high_irq, NULL,180 prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,181 "dbx500_temp_high", th);182 if (ret < 0) {183 dev_err(dev, "failed to allocate temp high irq\n");184 return ret;185 }186 187 /* register of thermal sensor and get info from DT */188 th->tz = devm_thermal_of_zone_register(dev, 0, th, &thdev_ops);189 if (IS_ERR(th->tz)) {190 dev_err(dev, "register thermal zone sensor failed\n");191 return PTR_ERR(th->tz);192 }193 dev_info(dev, "thermal zone sensor registered\n");194 195 /* Start measuring at the lowest point */196 db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP,197 db8500_thermal_points[0]);198 199 platform_set_drvdata(pdev, th);200 201 return 0;202}203 204static int db8500_thermal_suspend(struct platform_device *pdev,205 pm_message_t state)206{207 prcmu_stop_temp_sense();208 209 return 0;210}211 212static int db8500_thermal_resume(struct platform_device *pdev)213{214 struct db8500_thermal_zone *th = platform_get_drvdata(pdev);215 216 /* Resume and start measuring at the lowest point */217 db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP,218 db8500_thermal_points[0]);219 220 return 0;221}222 223static const struct of_device_id db8500_thermal_match[] = {224 { .compatible = "stericsson,db8500-thermal" },225 {},226};227MODULE_DEVICE_TABLE(of, db8500_thermal_match);228 229static struct platform_driver db8500_thermal_driver = {230 .driver = {231 .name = "db8500-thermal",232 .of_match_table = db8500_thermal_match,233 },234 .probe = db8500_thermal_probe,235 .suspend = db8500_thermal_suspend,236 .resume = db8500_thermal_resume,237};238 239module_platform_driver(db8500_thermal_driver);240 241MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");242MODULE_DESCRIPTION("DB8500 thermal driver");243MODULE_LICENSE("GPL");244