xref: /openbmc/linux/drivers/thermal/db8500_thermal.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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