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