1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2aa1acb04Shongbo.zhang /*
3aa1acb04Shongbo.zhang * db8500_thermal.c - DB8500 Thermal Management Implementation
4aa1acb04Shongbo.zhang *
5aa1acb04Shongbo.zhang * Copyright (C) 2012 ST-Ericsson
66c375eccSLinus Walleij * Copyright (C) 2012-2019 Linaro Ltd.
7aa1acb04Shongbo.zhang *
86c375eccSLinus Walleij * Authors: Hongbo Zhang, Linus Walleij
9aa1acb04Shongbo.zhang */
10aa1acb04Shongbo.zhang
11aa1acb04Shongbo.zhang #include <linux/cpu_cooling.h>
12aa1acb04Shongbo.zhang #include <linux/interrupt.h>
13aa1acb04Shongbo.zhang #include <linux/mfd/dbx500-prcmu.h>
14aa1acb04Shongbo.zhang #include <linux/module.h>
15aa1acb04Shongbo.zhang #include <linux/of.h>
16aa1acb04Shongbo.zhang #include <linux/platform_device.h>
17aa1acb04Shongbo.zhang #include <linux/slab.h>
18aa1acb04Shongbo.zhang #include <linux/thermal.h>
19aa1acb04Shongbo.zhang
20aa1acb04Shongbo.zhang #define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
21aa1acb04Shongbo.zhang #define PRCMU_DEFAULT_LOW_TEMP 0
22cb063a83SLinus Walleij
236c375eccSLinus Walleij /**
246c375eccSLinus Walleij * db8500_thermal_points - the interpolation points that trigger
256c375eccSLinus Walleij * interrupts
266c375eccSLinus Walleij */
276c375eccSLinus Walleij static const unsigned long db8500_thermal_points[] = {
286c375eccSLinus Walleij 15000,
296c375eccSLinus Walleij 20000,
306c375eccSLinus Walleij 25000,
316c375eccSLinus Walleij 30000,
326c375eccSLinus Walleij 35000,
336c375eccSLinus Walleij 40000,
346c375eccSLinus Walleij 45000,
356c375eccSLinus Walleij 50000,
366c375eccSLinus Walleij 55000,
376c375eccSLinus Walleij 60000,
386c375eccSLinus Walleij 65000,
396c375eccSLinus Walleij 70000,
406c375eccSLinus Walleij 75000,
416c375eccSLinus Walleij 80000,
426c375eccSLinus Walleij /*
436c375eccSLinus Walleij * This is where things start to get really bad for the
446c375eccSLinus Walleij * SoC and the thermal zones should be set up to trigger
456c375eccSLinus Walleij * critical temperature at 85000 mC so we don't get above
466c375eccSLinus Walleij * this point.
476c375eccSLinus Walleij */
486c375eccSLinus Walleij 85000,
496c375eccSLinus Walleij 90000,
506c375eccSLinus Walleij 95000,
516c375eccSLinus Walleij 100000,
52cb063a83SLinus Walleij };
53aa1acb04Shongbo.zhang
54aa1acb04Shongbo.zhang struct db8500_thermal_zone {
556c375eccSLinus Walleij struct thermal_zone_device *tz;
56311526b7SDaniel Lezcano struct device *dev;
576c375eccSLinus Walleij unsigned long interpolated_temp;
58aa1acb04Shongbo.zhang unsigned int cur_index;
59aa1acb04Shongbo.zhang };
60aa1acb04Shongbo.zhang
61aa1acb04Shongbo.zhang /* Callback to get current temperature */
db8500_thermal_get_temp(struct thermal_zone_device * tz,int * temp)622320be60SDaniel Lezcano static int db8500_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
63aa1acb04Shongbo.zhang {
645f68d078SDaniel Lezcano struct db8500_thermal_zone *th = thermal_zone_device_priv(tz);
65aa1acb04Shongbo.zhang
66aa1acb04Shongbo.zhang /*
67aa1acb04Shongbo.zhang * TODO: There is no PRCMU interface to get temperature data currently,
68aa1acb04Shongbo.zhang * so a pseudo temperature is returned , it works for thermal framework
69aa1acb04Shongbo.zhang * and this will be fixed when the PRCMU interface is available.
70aa1acb04Shongbo.zhang */
716c375eccSLinus Walleij *temp = th->interpolated_temp;
72aa1acb04Shongbo.zhang
73aa1acb04Shongbo.zhang return 0;
74aa1acb04Shongbo.zhang }
75aa1acb04Shongbo.zhang
762320be60SDaniel Lezcano static const struct thermal_zone_device_ops thdev_ops = {
776c375eccSLinus Walleij .get_temp = db8500_thermal_get_temp,
78aa1acb04Shongbo.zhang };
79aa1acb04Shongbo.zhang
db8500_thermal_update_config(struct db8500_thermal_zone * th,unsigned int idx,unsigned long next_low,unsigned long next_high)806c375eccSLinus Walleij static void db8500_thermal_update_config(struct db8500_thermal_zone *th,
816c375eccSLinus Walleij unsigned int idx,
826c375eccSLinus Walleij unsigned long next_low,
836c375eccSLinus Walleij unsigned long next_high)
84aa1acb04Shongbo.zhang {
85aa1acb04Shongbo.zhang prcmu_stop_temp_sense();
86aa1acb04Shongbo.zhang
876c375eccSLinus Walleij th->cur_index = idx;
886c375eccSLinus Walleij th->interpolated_temp = (next_low + next_high)/2;
89aa1acb04Shongbo.zhang
906c375eccSLinus Walleij /*
916c375eccSLinus Walleij * The PRCMU accept absolute temperatures in celsius so divide
926c375eccSLinus Walleij * down the millicelsius with 1000
936c375eccSLinus Walleij */
94aa1acb04Shongbo.zhang prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
95aa1acb04Shongbo.zhang prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
96aa1acb04Shongbo.zhang }
97aa1acb04Shongbo.zhang
prcmu_low_irq_handler(int irq,void * irq_data)98aa1acb04Shongbo.zhang static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
99aa1acb04Shongbo.zhang {
1006c375eccSLinus Walleij struct db8500_thermal_zone *th = irq_data;
1016c375eccSLinus Walleij unsigned int idx = th->cur_index;
102aa1acb04Shongbo.zhang unsigned long next_low, next_high;
103aa1acb04Shongbo.zhang
1046c375eccSLinus Walleij if (idx == 0)
105aa1acb04Shongbo.zhang /* Meaningless for thermal management, ignoring it */
106aa1acb04Shongbo.zhang return IRQ_HANDLED;
107aa1acb04Shongbo.zhang
108aa1acb04Shongbo.zhang if (idx == 1) {
1096c375eccSLinus Walleij next_high = db8500_thermal_points[0];
110aa1acb04Shongbo.zhang next_low = PRCMU_DEFAULT_LOW_TEMP;
111aa1acb04Shongbo.zhang } else {
1126c375eccSLinus Walleij next_high = db8500_thermal_points[idx - 1];
1136c375eccSLinus Walleij next_low = db8500_thermal_points[idx - 2];
114aa1acb04Shongbo.zhang }
115aa1acb04Shongbo.zhang idx -= 1;
116aa1acb04Shongbo.zhang
11766a0b101SDaniel Lezcano db8500_thermal_update_config(th, idx, next_low, next_high);
118311526b7SDaniel Lezcano dev_dbg(th->dev,
119aa1acb04Shongbo.zhang "PRCMU set max %ld, min %ld\n", next_high, next_low);
120aa1acb04Shongbo.zhang
1216c375eccSLinus Walleij thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED);
122aa1acb04Shongbo.zhang
123aa1acb04Shongbo.zhang return IRQ_HANDLED;
124aa1acb04Shongbo.zhang }
125aa1acb04Shongbo.zhang
prcmu_high_irq_handler(int irq,void * irq_data)126aa1acb04Shongbo.zhang static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
127aa1acb04Shongbo.zhang {
1286c375eccSLinus Walleij struct db8500_thermal_zone *th = irq_data;
1296c375eccSLinus Walleij unsigned int idx = th->cur_index;
130aa1acb04Shongbo.zhang unsigned long next_low, next_high;
1316c375eccSLinus Walleij int num_points = ARRAY_SIZE(db8500_thermal_points);
132aa1acb04Shongbo.zhang
1336c375eccSLinus Walleij if (idx < num_points - 1) {
1346c375eccSLinus Walleij next_high = db8500_thermal_points[idx+1];
1356c375eccSLinus Walleij next_low = db8500_thermal_points[idx];
136aa1acb04Shongbo.zhang idx += 1;
137aa1acb04Shongbo.zhang
13866a0b101SDaniel Lezcano db8500_thermal_update_config(th, idx, next_low, next_high);
139aa1acb04Shongbo.zhang
140311526b7SDaniel Lezcano dev_dbg(th->dev,
141aa1acb04Shongbo.zhang "PRCMU set max %ld, min %ld\n", next_high, next_low);
1426c375eccSLinus Walleij } else if (idx == num_points - 1)
1436c375eccSLinus Walleij /* So we roof out 1 degree over the max point */
1446c375eccSLinus Walleij th->interpolated_temp = db8500_thermal_points[idx] + 1;
145aa1acb04Shongbo.zhang
1466c375eccSLinus Walleij thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED);
147aa1acb04Shongbo.zhang
148aa1acb04Shongbo.zhang return IRQ_HANDLED;
149aa1acb04Shongbo.zhang }
150aa1acb04Shongbo.zhang
db8500_thermal_probe(struct platform_device * pdev)151aa1acb04Shongbo.zhang static int db8500_thermal_probe(struct platform_device *pdev)
152aa1acb04Shongbo.zhang {
1536c375eccSLinus Walleij struct db8500_thermal_zone *th = NULL;
1543de9e4dfSLinus Walleij struct device *dev = &pdev->dev;
155aa1acb04Shongbo.zhang int low_irq, high_irq, ret = 0;
156aa1acb04Shongbo.zhang
1576c375eccSLinus Walleij th = devm_kzalloc(dev, sizeof(*th), GFP_KERNEL);
1586c375eccSLinus Walleij if (!th)
159aa1acb04Shongbo.zhang return -ENOMEM;
160aa1acb04Shongbo.zhang
161311526b7SDaniel Lezcano th->dev = dev;
162311526b7SDaniel Lezcano
163aa1acb04Shongbo.zhang low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
1648cf18eeaSYang Li if (low_irq < 0)
1656c375eccSLinus Walleij return low_irq;
166aa1acb04Shongbo.zhang
1673de9e4dfSLinus Walleij ret = devm_request_threaded_irq(dev, low_irq, NULL,
168aa1acb04Shongbo.zhang prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
1696c375eccSLinus Walleij "dbx500_temp_low", th);
170aa1acb04Shongbo.zhang if (ret < 0) {
1716c375eccSLinus Walleij dev_err(dev, "failed to allocate temp low irq\n");
1726c375eccSLinus Walleij return ret;
173aa1acb04Shongbo.zhang }
174aa1acb04Shongbo.zhang
175aa1acb04Shongbo.zhang high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
1768cf18eeaSYang Li if (high_irq < 0)
1776c375eccSLinus Walleij return high_irq;
178aa1acb04Shongbo.zhang
1793de9e4dfSLinus Walleij ret = devm_request_threaded_irq(dev, high_irq, NULL,
180aa1acb04Shongbo.zhang prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
1816c375eccSLinus Walleij "dbx500_temp_high", th);
182aa1acb04Shongbo.zhang if (ret < 0) {
1836c375eccSLinus Walleij dev_err(dev, "failed to allocate temp high irq\n");
1844c7fa83aSAxel Lin return ret;
185aa1acb04Shongbo.zhang }
186aa1acb04Shongbo.zhang
1876c375eccSLinus Walleij /* register of thermal sensor and get info from DT */
1882320be60SDaniel Lezcano th->tz = devm_thermal_of_zone_register(dev, 0, th, &thdev_ops);
1896c375eccSLinus Walleij if (IS_ERR(th->tz)) {
1906c375eccSLinus Walleij dev_err(dev, "register thermal zone sensor failed\n");
1916c375eccSLinus Walleij return PTR_ERR(th->tz);
1926c375eccSLinus Walleij }
1936c375eccSLinus Walleij dev_info(dev, "thermal zone sensor registered\n");
194aa1acb04Shongbo.zhang
1956c375eccSLinus Walleij /* Start measuring at the lowest point */
19666a0b101SDaniel Lezcano db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP,
1976c375eccSLinus Walleij db8500_thermal_points[0]);
1986c375eccSLinus Walleij
1996c375eccSLinus Walleij platform_set_drvdata(pdev, th);
200aa1acb04Shongbo.zhang
201aa1acb04Shongbo.zhang return 0;
202aa1acb04Shongbo.zhang }
203aa1acb04Shongbo.zhang
db8500_thermal_suspend(struct platform_device * pdev,pm_message_t state)204aa1acb04Shongbo.zhang static int db8500_thermal_suspend(struct platform_device *pdev,
205aa1acb04Shongbo.zhang pm_message_t state)
206aa1acb04Shongbo.zhang {
207aa1acb04Shongbo.zhang prcmu_stop_temp_sense();
208aa1acb04Shongbo.zhang
209aa1acb04Shongbo.zhang return 0;
210aa1acb04Shongbo.zhang }
211aa1acb04Shongbo.zhang
db8500_thermal_resume(struct platform_device * pdev)212aa1acb04Shongbo.zhang static int db8500_thermal_resume(struct platform_device *pdev)
213aa1acb04Shongbo.zhang {
2146c375eccSLinus Walleij struct db8500_thermal_zone *th = platform_get_drvdata(pdev);
215aa1acb04Shongbo.zhang
2166c375eccSLinus Walleij /* Resume and start measuring at the lowest point */
21766a0b101SDaniel Lezcano db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP,
2186c375eccSLinus Walleij db8500_thermal_points[0]);
219aa1acb04Shongbo.zhang
220aa1acb04Shongbo.zhang return 0;
221aa1acb04Shongbo.zhang }
222aa1acb04Shongbo.zhang
223aa1acb04Shongbo.zhang static const struct of_device_id db8500_thermal_match[] = {
224aa1acb04Shongbo.zhang { .compatible = "stericsson,db8500-thermal" },
225aa1acb04Shongbo.zhang {},
226aa1acb04Shongbo.zhang };
2278093a116SJavier Martinez Canillas MODULE_DEVICE_TABLE(of, db8500_thermal_match);
228aa1acb04Shongbo.zhang
229aa1acb04Shongbo.zhang static struct platform_driver db8500_thermal_driver = {
230aa1acb04Shongbo.zhang .driver = {
231aa1acb04Shongbo.zhang .name = "db8500-thermal",
232*c39300c4SRuan Jinjie .of_match_table = db8500_thermal_match,
233aa1acb04Shongbo.zhang },
234aa1acb04Shongbo.zhang .probe = db8500_thermal_probe,
235aa1acb04Shongbo.zhang .suspend = db8500_thermal_suspend,
236aa1acb04Shongbo.zhang .resume = db8500_thermal_resume,
237aa1acb04Shongbo.zhang };
238aa1acb04Shongbo.zhang
239aa1acb04Shongbo.zhang module_platform_driver(db8500_thermal_driver);
240aa1acb04Shongbo.zhang
241aa1acb04Shongbo.zhang MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
242aa1acb04Shongbo.zhang MODULE_DESCRIPTION("DB8500 thermal driver");
243aa1acb04Shongbo.zhang MODULE_LICENSE("GPL");
244