xref: /freebsd/sys/arm/allwinner/aw_thermal.c (revision 97eb836f)
1343044c4SJared McNeill /*-
2343044c4SJared McNeill  * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
3343044c4SJared McNeill  * All rights reserved.
4343044c4SJared McNeill  *
5343044c4SJared McNeill  * Redistribution and use in source and binary forms, with or without
6343044c4SJared McNeill  * modification, are permitted provided that the following conditions
7343044c4SJared McNeill  * are met:
8343044c4SJared McNeill  * 1. Redistributions of source code must retain the above copyright
9343044c4SJared McNeill  *    notice, this list of conditions and the following disclaimer.
10343044c4SJared McNeill  * 2. Redistributions in binary form must reproduce the above copyright
11343044c4SJared McNeill  *    notice, this list of conditions and the following disclaimer in the
12343044c4SJared McNeill  *    documentation and/or other materials provided with the distribution.
13343044c4SJared McNeill  *
14343044c4SJared McNeill  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15343044c4SJared McNeill  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16343044c4SJared McNeill  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17343044c4SJared McNeill  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18343044c4SJared McNeill  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19343044c4SJared McNeill  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20343044c4SJared McNeill  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21343044c4SJared McNeill  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22343044c4SJared McNeill  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23343044c4SJared McNeill  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24343044c4SJared McNeill  * SUCH DAMAGE.
25343044c4SJared McNeill  *
26343044c4SJared McNeill  * $FreeBSD$
27343044c4SJared McNeill  */
28343044c4SJared McNeill 
29343044c4SJared McNeill /*
30343044c4SJared McNeill  * Allwinner thermal sensor controller
31343044c4SJared McNeill  */
32343044c4SJared McNeill 
33343044c4SJared McNeill #include <sys/cdefs.h>
34343044c4SJared McNeill __FBSDID("$FreeBSD$");
35343044c4SJared McNeill 
36343044c4SJared McNeill #include <sys/param.h>
37343044c4SJared McNeill #include <sys/systm.h>
38343044c4SJared McNeill #include <sys/bus.h>
39343044c4SJared McNeill #include <sys/rman.h>
40343044c4SJared McNeill #include <sys/kernel.h>
41343044c4SJared McNeill #include <sys/sysctl.h>
42d69d5ab0SJared McNeill #include <sys/reboot.h>
43343044c4SJared McNeill #include <sys/module.h>
443c2b90f1SJared McNeill #include <sys/cpu.h>
452e4f9347SJared McNeill #include <sys/taskqueue.h>
46343044c4SJared McNeill #include <machine/bus.h>
47343044c4SJared McNeill 
48343044c4SJared McNeill #include <dev/ofw/ofw_bus.h>
49343044c4SJared McNeill #include <dev/ofw/ofw_bus_subr.h>
50343044c4SJared McNeill 
51d69d5ab0SJared McNeill #include <dev/extres/clk/clk.h>
52d69d5ab0SJared McNeill #include <dev/extres/hwreset/hwreset.h>
5397eb836fSEmmanuel Vadot #include <dev/extres/nvmem/nvmem.h>
54d69d5ab0SJared McNeill 
55343044c4SJared McNeill #include <arm/allwinner/aw_sid.h>
56343044c4SJared McNeill 
573c2b90f1SJared McNeill #include "cpufreq_if.h"
5897eb836fSEmmanuel Vadot #include "nvmem_if.h"
593c2b90f1SJared McNeill 
60343044c4SJared McNeill #define	THS_CTRL0		0x00
61d69d5ab0SJared McNeill #define	THS_CTRL1		0x04
62d69d5ab0SJared McNeill #define	 ADC_CALI_EN		(1 << 17)
63343044c4SJared McNeill #define	THS_CTRL2		0x40
64343044c4SJared McNeill #define	 SENSOR_ACQ1_SHIFT	16
65343044c4SJared McNeill #define	 SENSOR2_EN		(1 << 2)
66343044c4SJared McNeill #define	 SENSOR1_EN		(1 << 1)
67343044c4SJared McNeill #define	 SENSOR0_EN		(1 << 0)
68343044c4SJared McNeill #define	THS_INTC		0x44
69343044c4SJared McNeill #define	THS_INTS		0x48
70d69d5ab0SJared McNeill #define	 THS2_DATA_IRQ_STS	(1 << 10)
71d69d5ab0SJared McNeill #define	 THS1_DATA_IRQ_STS	(1 << 9)
72d69d5ab0SJared McNeill #define	 THS0_DATA_IRQ_STS	(1 << 8)
73d69d5ab0SJared McNeill #define	 SHUT_INT2_STS		(1 << 6)
74d69d5ab0SJared McNeill #define	 SHUT_INT1_STS		(1 << 5)
75d69d5ab0SJared McNeill #define	 SHUT_INT0_STS		(1 << 4)
76d69d5ab0SJared McNeill #define	 ALARM_INT2_STS		(1 << 2)
77d69d5ab0SJared McNeill #define	 ALARM_INT1_STS		(1 << 1)
78d69d5ab0SJared McNeill #define	 ALARM_INT0_STS		(1 << 0)
793c2b90f1SJared McNeill #define	THS_ALARM0_CTRL		0x50
803c2b90f1SJared McNeill #define	 ALARM_T_HOT_MASK	0xfff
813c2b90f1SJared McNeill #define	 ALARM_T_HOT_SHIFT	16
823c2b90f1SJared McNeill #define	 ALARM_T_HYST_MASK	0xfff
833c2b90f1SJared McNeill #define	 ALARM_T_HYST_SHIFT	0
843c2b90f1SJared McNeill #define	THS_SHUTDOWN0_CTRL	0x60
853c2b90f1SJared McNeill #define	 SHUT_T_HOT_MASK	0xfff
863c2b90f1SJared McNeill #define	 SHUT_T_HOT_SHIFT	16
87343044c4SJared McNeill #define	THS_FILTER		0x70
88343044c4SJared McNeill #define	THS_CALIB0		0x74
89343044c4SJared McNeill #define	THS_CALIB1		0x78
90343044c4SJared McNeill #define	THS_DATA0		0x80
91343044c4SJared McNeill #define	THS_DATA1		0x84
92343044c4SJared McNeill #define	THS_DATA2		0x88
93343044c4SJared McNeill #define	 DATA_MASK		0xfff
94343044c4SJared McNeill 
95d69d5ab0SJared McNeill #define	A83T_ADC_ACQUIRE_TIME	0x17
96d69d5ab0SJared McNeill #define	A83T_FILTER		0x4
97d69d5ab0SJared McNeill #define	A83T_INTC		0x1000
98d69d5ab0SJared McNeill #define	A83T_TEMP_BASE		2719000
994e7f43baSJared McNeill #define	A83T_TEMP_MUL		1000
100d69d5ab0SJared McNeill #define	A83T_TEMP_DIV		14186
101d69d5ab0SJared McNeill #define	A83T_CLK_RATE		24000000
102d69d5ab0SJared McNeill 
103d69d5ab0SJared McNeill #define	A64_ADC_ACQUIRE_TIME	0x190
104d69d5ab0SJared McNeill #define	A64_FILTER		0x6
105d69d5ab0SJared McNeill #define	A64_INTC		0x18000
106d69d5ab0SJared McNeill #define	A64_TEMP_BASE		2170000
1074e7f43baSJared McNeill #define	A64_TEMP_MUL		1000
108d69d5ab0SJared McNeill #define	A64_TEMP_DIV		8560
109d69d5ab0SJared McNeill #define	A64_CLK_RATE		4000000
110d69d5ab0SJared McNeill 
1114e7f43baSJared McNeill #define	H3_ADC_ACQUIRE_TIME	0x3f
1124e7f43baSJared McNeill #define	H3_FILTER		0x6
1134e7f43baSJared McNeill #define	H3_INTC			0x191000
1140a30b4b2SJared McNeill #define	H3_TEMP_BASE		217
1153c2b90f1SJared McNeill #define	H3_TEMP_MUL		1000
1160a30b4b2SJared McNeill #define	H3_TEMP_DIV		8253
1170a30b4b2SJared McNeill #define	H3_TEMP_MINUS		1794000
1184e7f43baSJared McNeill #define	H3_CLK_RATE		4000000
1190a30b4b2SJared McNeill #define	H3_INIT_ALARM		90	/* degC */
1200a30b4b2SJared McNeill #define	H3_INIT_SHUT		105	/* degC */
1214e7f43baSJared McNeill 
122d69d5ab0SJared McNeill #define	TEMP_C_TO_K		273
123343044c4SJared McNeill #define	SENSOR_ENABLE_ALL	(SENSOR0_EN|SENSOR1_EN|SENSOR2_EN)
124d69d5ab0SJared McNeill #define	SHUT_INT_ALL		(SHUT_INT0_STS|SHUT_INT1_STS|SHUT_INT2_STS)
1253c2b90f1SJared McNeill #define	ALARM_INT_ALL		(ALARM_INT0_STS)
126343044c4SJared McNeill 
127d69d5ab0SJared McNeill #define	MAX_SENSORS	3
1283c2b90f1SJared McNeill #define	MAX_CF_LEVELS	64
1293c2b90f1SJared McNeill 
1303c2b90f1SJared McNeill #define	THROTTLE_ENABLE_DEFAULT	1
1313c2b90f1SJared McNeill 
1323c2b90f1SJared McNeill /* Enable thermal throttling */
1333c2b90f1SJared McNeill static int aw_thermal_throttle_enable = THROTTLE_ENABLE_DEFAULT;
1343c2b90f1SJared McNeill TUNABLE_INT("hw.aw_thermal.throttle_enable", &aw_thermal_throttle_enable);
135343044c4SJared McNeill 
136d69d5ab0SJared McNeill struct aw_thermal_sensor {
137343044c4SJared McNeill 	const char		*name;
138343044c4SJared McNeill 	const char		*desc;
1390a30b4b2SJared McNeill 	int			init_alarm;
1400a30b4b2SJared McNeill 	int			init_shut;
141343044c4SJared McNeill };
142343044c4SJared McNeill 
143d69d5ab0SJared McNeill struct aw_thermal_config {
144d69d5ab0SJared McNeill 	struct aw_thermal_sensor	sensors[MAX_SENSORS];
145d69d5ab0SJared McNeill 	int				nsensors;
146d69d5ab0SJared McNeill 	uint64_t			clk_rate;
147d69d5ab0SJared McNeill 	uint32_t			adc_acquire_time;
1483c2b90f1SJared McNeill 	int				adc_cali_en;
149d69d5ab0SJared McNeill 	uint32_t			filter;
150d69d5ab0SJared McNeill 	uint32_t			intc;
1513c2b90f1SJared McNeill 	int				(*to_temp)(uint32_t);
1520a30b4b2SJared McNeill 	uint32_t			(*to_reg)(int);
1534e7f43baSJared McNeill 	int				temp_base;
1544e7f43baSJared McNeill 	int				temp_mul;
1554e7f43baSJared McNeill 	int				temp_div;
1563c2b90f1SJared McNeill 	int				calib0, calib1;
1573c2b90f1SJared McNeill 	uint32_t			calib0_mask, calib1_mask;
158d69d5ab0SJared McNeill };
159d69d5ab0SJared McNeill 
1603c2b90f1SJared McNeill static int
1613c2b90f1SJared McNeill a83t_to_temp(uint32_t val)
1623c2b90f1SJared McNeill {
1633c2b90f1SJared McNeill 	return ((A83T_TEMP_BASE - (val * A83T_TEMP_MUL)) / A83T_TEMP_DIV);
1643c2b90f1SJared McNeill }
1653c2b90f1SJared McNeill 
166d69d5ab0SJared McNeill static const struct aw_thermal_config a83t_config = {
167d69d5ab0SJared McNeill 	.nsensors = 3,
168d69d5ab0SJared McNeill 	.sensors = {
169d69d5ab0SJared McNeill 		[0] = {
170d69d5ab0SJared McNeill 			.name = "cluster0",
171d69d5ab0SJared McNeill 			.desc = "CPU cluster 0 temperature",
172d69d5ab0SJared McNeill 		},
173d69d5ab0SJared McNeill 		[1] = {
174d69d5ab0SJared McNeill 			.name = "cluster1",
175d69d5ab0SJared McNeill 			.desc = "CPU cluster 1 temperature",
176d69d5ab0SJared McNeill 		},
177d69d5ab0SJared McNeill 		[2] = {
178d69d5ab0SJared McNeill 			.name = "gpu",
179d69d5ab0SJared McNeill 			.desc = "GPU temperature",
180d69d5ab0SJared McNeill 		},
181d69d5ab0SJared McNeill 	},
182d69d5ab0SJared McNeill 	.clk_rate = A83T_CLK_RATE,
183d69d5ab0SJared McNeill 	.adc_acquire_time = A83T_ADC_ACQUIRE_TIME,
1843c2b90f1SJared McNeill 	.adc_cali_en = 1,
185d69d5ab0SJared McNeill 	.filter = A83T_FILTER,
186d69d5ab0SJared McNeill 	.intc = A83T_INTC,
1873c2b90f1SJared McNeill 	.to_temp = a83t_to_temp,
1883c2b90f1SJared McNeill 	.calib0_mask = 0xffffffff,
1893c2b90f1SJared McNeill 	.calib1_mask = 0xffffffff,
190d69d5ab0SJared McNeill };
191d69d5ab0SJared McNeill 
1923c2b90f1SJared McNeill static int
1933c2b90f1SJared McNeill a64_to_temp(uint32_t val)
1943c2b90f1SJared McNeill {
1953c2b90f1SJared McNeill 	return ((A64_TEMP_BASE - (val * A64_TEMP_MUL)) / A64_TEMP_DIV);
1963c2b90f1SJared McNeill }
1973c2b90f1SJared McNeill 
198d69d5ab0SJared McNeill static const struct aw_thermal_config a64_config = {
199d69d5ab0SJared McNeill 	.nsensors = 3,
200d69d5ab0SJared McNeill 	.sensors = {
201d69d5ab0SJared McNeill 		[0] = {
202d69d5ab0SJared McNeill 			.name = "cpu",
203d69d5ab0SJared McNeill 			.desc = "CPU temperature",
204d69d5ab0SJared McNeill 		},
205d69d5ab0SJared McNeill 		[1] = {
206d69d5ab0SJared McNeill 			.name = "gpu1",
207d69d5ab0SJared McNeill 			.desc = "GPU temperature 1",
208d69d5ab0SJared McNeill 		},
209d69d5ab0SJared McNeill 		[2] = {
210d69d5ab0SJared McNeill 			.name = "gpu2",
211d69d5ab0SJared McNeill 			.desc = "GPU temperature 2",
212d69d5ab0SJared McNeill 		},
213d69d5ab0SJared McNeill 	},
214d69d5ab0SJared McNeill 	.clk_rate = A64_CLK_RATE,
215d69d5ab0SJared McNeill 	.adc_acquire_time = A64_ADC_ACQUIRE_TIME,
216d69d5ab0SJared McNeill 	.filter = A64_FILTER,
217d69d5ab0SJared McNeill 	.intc = A64_INTC,
2183c2b90f1SJared McNeill 	.to_temp = a64_to_temp,
219343044c4SJared McNeill };
220343044c4SJared McNeill 
2213c2b90f1SJared McNeill static int
2223c2b90f1SJared McNeill h3_to_temp(uint32_t val)
2233c2b90f1SJared McNeill {
2240a30b4b2SJared McNeill 	return (H3_TEMP_BASE - ((val * H3_TEMP_MUL) / H3_TEMP_DIV));
2250a30b4b2SJared McNeill }
2260a30b4b2SJared McNeill 
2270a30b4b2SJared McNeill static uint32_t
2280a30b4b2SJared McNeill h3_to_reg(int val)
2290a30b4b2SJared McNeill {
2300a30b4b2SJared McNeill 	return ((H3_TEMP_MINUS - (val * H3_TEMP_DIV)) / H3_TEMP_MUL);
2313c2b90f1SJared McNeill }
2323c2b90f1SJared McNeill 
2334e7f43baSJared McNeill static const struct aw_thermal_config h3_config = {
2344e7f43baSJared McNeill 	.nsensors = 1,
2354e7f43baSJared McNeill 	.sensors = {
2364e7f43baSJared McNeill 		[0] = {
2374e7f43baSJared McNeill 			.name = "cpu",
2384e7f43baSJared McNeill 			.desc = "CPU temperature",
2390a30b4b2SJared McNeill 			.init_alarm = H3_INIT_ALARM,
2400a30b4b2SJared McNeill 			.init_shut = H3_INIT_SHUT,
2414e7f43baSJared McNeill 		},
2424e7f43baSJared McNeill 	},
2434e7f43baSJared McNeill 	.clk_rate = H3_CLK_RATE,
2444e7f43baSJared McNeill 	.adc_acquire_time = H3_ADC_ACQUIRE_TIME,
2454e7f43baSJared McNeill 	.filter = H3_FILTER,
2464e7f43baSJared McNeill 	.intc = H3_INTC,
2473c2b90f1SJared McNeill 	.to_temp = h3_to_temp,
2480a30b4b2SJared McNeill 	.to_reg = h3_to_reg,
2493c2b90f1SJared McNeill 	.calib0_mask = 0xfff,
2504e7f43baSJared McNeill };
2514e7f43baSJared McNeill 
252343044c4SJared McNeill static struct ofw_compat_data compat_data[] = {
253d69d5ab0SJared McNeill 	{ "allwinner,sun8i-a83t-ts",	(uintptr_t)&a83t_config },
2544e7f43baSJared McNeill 	{ "allwinner,sun8i-h3-ts",	(uintptr_t)&h3_config },
255d69d5ab0SJared McNeill 	{ "allwinner,sun50i-a64-ts",	(uintptr_t)&a64_config },
256343044c4SJared McNeill 	{ NULL,				(uintptr_t)NULL }
257343044c4SJared McNeill };
258343044c4SJared McNeill 
259343044c4SJared McNeill #define	THS_CONF(d)		\
260343044c4SJared McNeill 	(void *)ofw_bus_search_compatible((d), compat_data)->ocd_data
261343044c4SJared McNeill 
262343044c4SJared McNeill struct aw_thermal_softc {
2633c2b90f1SJared McNeill 	device_t			dev;
264d69d5ab0SJared McNeill 	struct resource			*res[2];
265d69d5ab0SJared McNeill 	struct aw_thermal_config	*conf;
2663c2b90f1SJared McNeill 
2672e4f9347SJared McNeill 	struct task			cf_task;
2683c2b90f1SJared McNeill 	int				throttle;
2693c2b90f1SJared McNeill 	int				min_freq;
2703c2b90f1SJared McNeill 	struct cf_level			levels[MAX_CF_LEVELS];
2713c2b90f1SJared McNeill 	eventhandler_tag		cf_pre_tag;
272343044c4SJared McNeill };
273343044c4SJared McNeill 
274343044c4SJared McNeill static struct resource_spec aw_thermal_spec[] = {
275343044c4SJared McNeill 	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
276d69d5ab0SJared McNeill 	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
277343044c4SJared McNeill 	{ -1, 0 }
278343044c4SJared McNeill };
279343044c4SJared McNeill 
280d69d5ab0SJared McNeill #define	RD4(sc, reg)		bus_read_4((sc)->res[0], (reg))
281d69d5ab0SJared McNeill #define	WR4(sc, reg, val)	bus_write_4((sc)->res[0], (reg), (val))
282343044c4SJared McNeill 
283343044c4SJared McNeill static int
284343044c4SJared McNeill aw_thermal_init(struct aw_thermal_softc *sc)
285343044c4SJared McNeill {
28697eb836fSEmmanuel Vadot 	phandle_t node;
28797eb836fSEmmanuel Vadot 	uint32_t calib[2];
288343044c4SJared McNeill 	int error;
289343044c4SJared McNeill 
29097eb836fSEmmanuel Vadot 	node = ofw_bus_get_node(sc->dev);
2910a30b4b2SJared McNeill 	if (sc->conf->calib0_mask != 0 || sc->conf->calib1_mask != 0) {
29297eb836fSEmmanuel Vadot 		error = nvmem_read_cell_by_name(node, "ths_calibration",
29397eb836fSEmmanuel Vadot 		    (void *)calib, sizeof(calib));
29497eb836fSEmmanuel Vadot 		/* Read calibration settings from EFUSE */
29597eb836fSEmmanuel Vadot 		if (error != 0) {
29697eb836fSEmmanuel Vadot 			device_printf(sc->dev, "Cannot read THS efuse\n");
297343044c4SJared McNeill 			return (error);
29897eb836fSEmmanuel Vadot 		}
299343044c4SJared McNeill 
30097eb836fSEmmanuel Vadot 		device_printf(sc->dev, "calib0: %x\n", calib[0]);
30197eb836fSEmmanuel Vadot 		device_printf(sc->dev, "calib1: %x\n", calib[1]);
30297eb836fSEmmanuel Vadot 
30397eb836fSEmmanuel Vadot 		calib[0] &= sc->conf->calib0_mask;
30497eb836fSEmmanuel Vadot 		calib[1] &= sc->conf->calib1_mask;
3053c2b90f1SJared McNeill 
306343044c4SJared McNeill 		/* Write calibration settings to thermal controller */
30797eb836fSEmmanuel Vadot 		if (calib[0] != 0)
30897eb836fSEmmanuel Vadot 			WR4(sc, THS_CALIB0, calib[0]);
30997eb836fSEmmanuel Vadot 		if (calib[1] != 0)
31097eb836fSEmmanuel Vadot 			WR4(sc, THS_CALIB1, calib[1]);
3114e7f43baSJared McNeill 	}
312343044c4SJared McNeill 
313343044c4SJared McNeill 	/* Configure ADC acquire time (CLK_IN/(N+1)) and enable sensors */
314d69d5ab0SJared McNeill 	WR4(sc, THS_CTRL1, ADC_CALI_EN);
315d69d5ab0SJared McNeill 	WR4(sc, THS_CTRL0, sc->conf->adc_acquire_time);
316d69d5ab0SJared McNeill 	WR4(sc, THS_CTRL2, sc->conf->adc_acquire_time << SENSOR_ACQ1_SHIFT);
317343044c4SJared McNeill 
318343044c4SJared McNeill 	/* Enable average filter */
319d69d5ab0SJared McNeill 	WR4(sc, THS_FILTER, sc->conf->filter);
320d69d5ab0SJared McNeill 
321d69d5ab0SJared McNeill 	/* Enable interrupts */
322d69d5ab0SJared McNeill 	WR4(sc, THS_INTS, RD4(sc, THS_INTS));
3233c2b90f1SJared McNeill 	WR4(sc, THS_INTC, sc->conf->intc | SHUT_INT_ALL | ALARM_INT_ALL);
324d69d5ab0SJared McNeill 
325d69d5ab0SJared McNeill 	/* Enable sensors */
326d69d5ab0SJared McNeill 	WR4(sc, THS_CTRL2, RD4(sc, THS_CTRL2) | SENSOR_ENABLE_ALL);
327343044c4SJared McNeill 
328343044c4SJared McNeill 	return (0);
329343044c4SJared McNeill }
330343044c4SJared McNeill 
331343044c4SJared McNeill static int
332d69d5ab0SJared McNeill aw_thermal_gettemp(struct aw_thermal_softc *sc, int sensor)
333d69d5ab0SJared McNeill {
334d69d5ab0SJared McNeill 	uint32_t val;
335d69d5ab0SJared McNeill 
336d69d5ab0SJared McNeill 	val = RD4(sc, THS_DATA0 + (sensor * 4));
337d69d5ab0SJared McNeill 
3380a30b4b2SJared McNeill 	return (sc->conf->to_temp(val));
3393c2b90f1SJared McNeill }
3403c2b90f1SJared McNeill 
3413c2b90f1SJared McNeill static int
3423c2b90f1SJared McNeill aw_thermal_getshut(struct aw_thermal_softc *sc, int sensor)
3433c2b90f1SJared McNeill {
3443c2b90f1SJared McNeill 	uint32_t val;
3453c2b90f1SJared McNeill 
3463c2b90f1SJared McNeill 	val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4));
3473c2b90f1SJared McNeill 	val = (val >> SHUT_T_HOT_SHIFT) & SHUT_T_HOT_MASK;
3483c2b90f1SJared McNeill 
3490a30b4b2SJared McNeill 	return (sc->conf->to_temp(val));
3500a30b4b2SJared McNeill }
3510a30b4b2SJared McNeill 
3520a30b4b2SJared McNeill static void
3530a30b4b2SJared McNeill aw_thermal_setshut(struct aw_thermal_softc *sc, int sensor, int temp)
3540a30b4b2SJared McNeill {
3550a30b4b2SJared McNeill 	uint32_t val;
3560a30b4b2SJared McNeill 
3570a30b4b2SJared McNeill 	val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4));
3580a30b4b2SJared McNeill 	val &= ~(SHUT_T_HOT_MASK << SHUT_T_HOT_SHIFT);
3590a30b4b2SJared McNeill 	val |= (sc->conf->to_reg(temp) << SHUT_T_HOT_SHIFT);
3600a30b4b2SJared McNeill 	WR4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4), val);
3613c2b90f1SJared McNeill }
3623c2b90f1SJared McNeill 
3633c2b90f1SJared McNeill static int
3643c2b90f1SJared McNeill aw_thermal_gethyst(struct aw_thermal_softc *sc, int sensor)
3653c2b90f1SJared McNeill {
3663c2b90f1SJared McNeill 	uint32_t val;
3673c2b90f1SJared McNeill 
3683c2b90f1SJared McNeill 	val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
3693c2b90f1SJared McNeill 	val = (val >> ALARM_T_HYST_SHIFT) & ALARM_T_HYST_MASK;
3703c2b90f1SJared McNeill 
3710a30b4b2SJared McNeill 	return (sc->conf->to_temp(val));
3723c2b90f1SJared McNeill }
3733c2b90f1SJared McNeill 
3743c2b90f1SJared McNeill static int
3753c2b90f1SJared McNeill aw_thermal_getalarm(struct aw_thermal_softc *sc, int sensor)
3763c2b90f1SJared McNeill {
3773c2b90f1SJared McNeill 	uint32_t val;
3783c2b90f1SJared McNeill 
3793c2b90f1SJared McNeill 	val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
3803c2b90f1SJared McNeill 	val = (val >> ALARM_T_HOT_SHIFT) & ALARM_T_HOT_MASK;
3813c2b90f1SJared McNeill 
3820a30b4b2SJared McNeill 	return (sc->conf->to_temp(val));
3830a30b4b2SJared McNeill }
3840a30b4b2SJared McNeill 
3850a30b4b2SJared McNeill static void
3860a30b4b2SJared McNeill aw_thermal_setalarm(struct aw_thermal_softc *sc, int sensor, int temp)
3870a30b4b2SJared McNeill {
3880a30b4b2SJared McNeill 	uint32_t val;
3890a30b4b2SJared McNeill 
3900a30b4b2SJared McNeill 	val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
3910a30b4b2SJared McNeill 	val &= ~(ALARM_T_HOT_MASK << ALARM_T_HOT_SHIFT);
3920a30b4b2SJared McNeill 	val |= (sc->conf->to_reg(temp) << ALARM_T_HOT_SHIFT);
3930a30b4b2SJared McNeill 	WR4(sc, THS_ALARM0_CTRL + (sensor * 4), val);
394343044c4SJared McNeill }
395343044c4SJared McNeill 
396343044c4SJared McNeill static int
397343044c4SJared McNeill aw_thermal_sysctl(SYSCTL_HANDLER_ARGS)
398343044c4SJared McNeill {
399343044c4SJared McNeill 	struct aw_thermal_softc *sc;
400d69d5ab0SJared McNeill 	int sensor, val;
401343044c4SJared McNeill 
402343044c4SJared McNeill 	sc = arg1;
403343044c4SJared McNeill 	sensor = arg2;
404343044c4SJared McNeill 
4050a30b4b2SJared McNeill 	val = aw_thermal_gettemp(sc, sensor) + TEMP_C_TO_K;
406343044c4SJared McNeill 
407343044c4SJared McNeill 	return sysctl_handle_opaque(oidp, &val, sizeof(val), req);
408343044c4SJared McNeill }
409343044c4SJared McNeill 
410d69d5ab0SJared McNeill static void
4113c2b90f1SJared McNeill aw_thermal_throttle(struct aw_thermal_softc *sc, int enable)
4123c2b90f1SJared McNeill {
4133c2b90f1SJared McNeill 	device_t cf_dev;
4143c2b90f1SJared McNeill 	int count, error;
4153c2b90f1SJared McNeill 
4163c2b90f1SJared McNeill 	if (enable == sc->throttle)
4173c2b90f1SJared McNeill 		return;
4183c2b90f1SJared McNeill 
4193c2b90f1SJared McNeill 	if (enable != 0) {
4203c2b90f1SJared McNeill 		/* Set the lowest available frequency */
4213c2b90f1SJared McNeill 		cf_dev = devclass_get_device(devclass_find("cpufreq"), 0);
4223c2b90f1SJared McNeill 		if (cf_dev == NULL)
4233c2b90f1SJared McNeill 			return;
4243c2b90f1SJared McNeill 		count = MAX_CF_LEVELS;
4253c2b90f1SJared McNeill 		error = CPUFREQ_LEVELS(cf_dev, sc->levels, &count);
4263c2b90f1SJared McNeill 		if (error != 0 || count == 0)
4273c2b90f1SJared McNeill 			return;
4283c2b90f1SJared McNeill 		sc->min_freq = sc->levels[count - 1].total_set.freq;
4293c2b90f1SJared McNeill 		error = CPUFREQ_SET(cf_dev, &sc->levels[count - 1],
4303c2b90f1SJared McNeill 		    CPUFREQ_PRIO_USER);
4313c2b90f1SJared McNeill 		if (error != 0)
4323c2b90f1SJared McNeill 			return;
4333c2b90f1SJared McNeill 	}
4343c2b90f1SJared McNeill 
4353c2b90f1SJared McNeill 	sc->throttle = enable;
4363c2b90f1SJared McNeill }
4373c2b90f1SJared McNeill 
4383c2b90f1SJared McNeill static void
4392e4f9347SJared McNeill aw_thermal_cf_task(void *arg, int pending)
4402e4f9347SJared McNeill {
4412e4f9347SJared McNeill 	struct aw_thermal_softc *sc;
4422e4f9347SJared McNeill 
4432e4f9347SJared McNeill 	sc = arg;
4442e4f9347SJared McNeill 
4452e4f9347SJared McNeill 	aw_thermal_throttle(sc, 1);
4462e4f9347SJared McNeill }
4472e4f9347SJared McNeill 
4482e4f9347SJared McNeill static void
4493c2b90f1SJared McNeill aw_thermal_cf_pre_change(void *arg, const struct cf_level *level, int *status)
4503c2b90f1SJared McNeill {
4513c2b90f1SJared McNeill 	struct aw_thermal_softc *sc;
4523c2b90f1SJared McNeill 	int temp_cur, temp_alarm;
4533c2b90f1SJared McNeill 
4543c2b90f1SJared McNeill 	sc = arg;
4553c2b90f1SJared McNeill 
4563c2b90f1SJared McNeill 	if (aw_thermal_throttle_enable == 0 || sc->throttle == 0 ||
4573c2b90f1SJared McNeill 	    level->total_set.freq == sc->min_freq)
4583c2b90f1SJared McNeill 		return;
4593c2b90f1SJared McNeill 
4603c2b90f1SJared McNeill 	temp_cur = aw_thermal_gettemp(sc, 0);
4613c2b90f1SJared McNeill 	temp_alarm = aw_thermal_getalarm(sc, 0);
4623c2b90f1SJared McNeill 
4633c2b90f1SJared McNeill 	if (temp_cur < temp_alarm)
4643c2b90f1SJared McNeill 		aw_thermal_throttle(sc, 0);
4653c2b90f1SJared McNeill 	else
4663c2b90f1SJared McNeill 		*status = ENXIO;
4673c2b90f1SJared McNeill }
4683c2b90f1SJared McNeill 
4693c2b90f1SJared McNeill static void
470d69d5ab0SJared McNeill aw_thermal_intr(void *arg)
471d69d5ab0SJared McNeill {
472d69d5ab0SJared McNeill 	struct aw_thermal_softc *sc;
473d69d5ab0SJared McNeill 	device_t dev;
474d69d5ab0SJared McNeill 	uint32_t ints;
475d69d5ab0SJared McNeill 
476d69d5ab0SJared McNeill 	dev = arg;
477d69d5ab0SJared McNeill 	sc = device_get_softc(dev);
478d69d5ab0SJared McNeill 
479d69d5ab0SJared McNeill 	ints = RD4(sc, THS_INTS);
480d69d5ab0SJared McNeill 	WR4(sc, THS_INTS, ints);
481d69d5ab0SJared McNeill 
482d69d5ab0SJared McNeill 	if ((ints & SHUT_INT_ALL) != 0) {
483d69d5ab0SJared McNeill 		device_printf(dev,
484d69d5ab0SJared McNeill 		    "WARNING - current temperature exceeds safe limits\n");
485d69d5ab0SJared McNeill 		shutdown_nice(RB_POWEROFF);
486d69d5ab0SJared McNeill 	}
4873c2b90f1SJared McNeill 
4883c2b90f1SJared McNeill 	if ((ints & ALARM_INT_ALL) != 0)
4892e4f9347SJared McNeill 		taskqueue_enqueue(taskqueue_thread, &sc->cf_task);
490d69d5ab0SJared McNeill }
491d69d5ab0SJared McNeill 
492343044c4SJared McNeill static int
493343044c4SJared McNeill aw_thermal_probe(device_t dev)
494343044c4SJared McNeill {
495343044c4SJared McNeill 	if (!ofw_bus_status_okay(dev))
496343044c4SJared McNeill 		return (ENXIO);
497343044c4SJared McNeill 
498343044c4SJared McNeill 	if (THS_CONF(dev) == NULL)
499343044c4SJared McNeill 		return (ENXIO);
500343044c4SJared McNeill 
501343044c4SJared McNeill 	device_set_desc(dev, "Allwinner Thermal Sensor Controller");
502343044c4SJared McNeill 	return (BUS_PROBE_DEFAULT);
503343044c4SJared McNeill }
504343044c4SJared McNeill 
505343044c4SJared McNeill static int
506343044c4SJared McNeill aw_thermal_attach(device_t dev)
507343044c4SJared McNeill {
508343044c4SJared McNeill 	struct aw_thermal_softc *sc;
509d69d5ab0SJared McNeill 	clk_t clk_ahb, clk_ths;
510d69d5ab0SJared McNeill 	hwreset_t rst;
511d69d5ab0SJared McNeill 	int i, error;
512d69d5ab0SJared McNeill 	void *ih;
513343044c4SJared McNeill 
514343044c4SJared McNeill 	sc = device_get_softc(dev);
515d69d5ab0SJared McNeill 	clk_ahb = clk_ths = NULL;
516d69d5ab0SJared McNeill 	rst = NULL;
517d69d5ab0SJared McNeill 	ih = NULL;
518343044c4SJared McNeill 
519343044c4SJared McNeill 	sc->conf = THS_CONF(dev);
5202e4f9347SJared McNeill 	TASK_INIT(&sc->cf_task, 0, aw_thermal_cf_task, sc);
521343044c4SJared McNeill 
522d69d5ab0SJared McNeill 	if (bus_alloc_resources(dev, aw_thermal_spec, sc->res) != 0) {
523343044c4SJared McNeill 		device_printf(dev, "cannot allocate resources for device\n");
524343044c4SJared McNeill 		return (ENXIO);
525343044c4SJared McNeill 	}
526343044c4SJared McNeill 
527d69d5ab0SJared McNeill 	if (clk_get_by_ofw_name(dev, 0, "ahb", &clk_ahb) == 0) {
528d69d5ab0SJared McNeill 		error = clk_enable(clk_ahb);
529d69d5ab0SJared McNeill 		if (error != 0) {
530d69d5ab0SJared McNeill 			device_printf(dev, "cannot enable ahb clock\n");
531d69d5ab0SJared McNeill 			goto fail;
532d69d5ab0SJared McNeill 		}
533d69d5ab0SJared McNeill 	}
534d69d5ab0SJared McNeill 	if (clk_get_by_ofw_name(dev, 0, "ths", &clk_ths) == 0) {
535d69d5ab0SJared McNeill 		error = clk_set_freq(clk_ths, sc->conf->clk_rate, 0);
536d69d5ab0SJared McNeill 		if (error != 0) {
537d69d5ab0SJared McNeill 			device_printf(dev, "cannot set ths clock rate\n");
538d69d5ab0SJared McNeill 			goto fail;
539d69d5ab0SJared McNeill 		}
540d69d5ab0SJared McNeill 		error = clk_enable(clk_ths);
541d69d5ab0SJared McNeill 		if (error != 0) {
542d69d5ab0SJared McNeill 			device_printf(dev, "cannot enable ths clock\n");
543d69d5ab0SJared McNeill 			goto fail;
544d69d5ab0SJared McNeill 		}
545d69d5ab0SJared McNeill 	}
546d69d5ab0SJared McNeill 	if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) {
547d69d5ab0SJared McNeill 		error = hwreset_deassert(rst);
548d69d5ab0SJared McNeill 		if (error != 0) {
549d69d5ab0SJared McNeill 			device_printf(dev, "cannot de-assert reset\n");
550d69d5ab0SJared McNeill 			goto fail;
551d69d5ab0SJared McNeill 		}
552d69d5ab0SJared McNeill 	}
553343044c4SJared McNeill 
554d69d5ab0SJared McNeill 	error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE,
555d69d5ab0SJared McNeill 	    NULL, aw_thermal_intr, dev, &ih);
556d69d5ab0SJared McNeill 	if (error != 0) {
557d69d5ab0SJared McNeill 		device_printf(dev, "cannot setup interrupt handler\n");
558d69d5ab0SJared McNeill 		goto fail;
559d69d5ab0SJared McNeill 	}
560d69d5ab0SJared McNeill 
5610a30b4b2SJared McNeill 	for (i = 0; i < sc->conf->nsensors; i++) {
5620a30b4b2SJared McNeill 		if (sc->conf->sensors[i].init_alarm > 0)
5630a30b4b2SJared McNeill 			aw_thermal_setalarm(sc, i,
5640a30b4b2SJared McNeill 			    sc->conf->sensors[i].init_alarm);
5650a30b4b2SJared McNeill 		if (sc->conf->sensors[i].init_shut > 0)
5660a30b4b2SJared McNeill 			aw_thermal_setshut(sc, i,
5670a30b4b2SJared McNeill 			    sc->conf->sensors[i].init_shut);
5680a30b4b2SJared McNeill 	}
5690a30b4b2SJared McNeill 
570d69d5ab0SJared McNeill 	if (aw_thermal_init(sc) != 0)
571d69d5ab0SJared McNeill 		goto fail;
572d69d5ab0SJared McNeill 
573d69d5ab0SJared McNeill 	for (i = 0; i < sc->conf->nsensors; i++)
574343044c4SJared McNeill 		SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
575343044c4SJared McNeill 		    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
576d69d5ab0SJared McNeill 		    OID_AUTO, sc->conf->sensors[i].name,
577343044c4SJared McNeill 		    CTLTYPE_INT | CTLFLAG_RD,
578d69d5ab0SJared McNeill 		    sc, i, aw_thermal_sysctl, "IK0",
579d69d5ab0SJared McNeill 		    sc->conf->sensors[i].desc);
580343044c4SJared McNeill 
5813c2b90f1SJared McNeill 	if (bootverbose)
5823c2b90f1SJared McNeill 		for (i = 0; i < sc->conf->nsensors; i++) {
5833c2b90f1SJared McNeill 			device_printf(dev,
5843c2b90f1SJared McNeill 			    "#%d: alarm %dC hyst %dC shut %dC\n", i,
5850a30b4b2SJared McNeill 			    aw_thermal_getalarm(sc, i),
5860a30b4b2SJared McNeill 			    aw_thermal_gethyst(sc, i),
5870a30b4b2SJared McNeill 			    aw_thermal_getshut(sc, i));
5883c2b90f1SJared McNeill 		}
5893c2b90f1SJared McNeill 
5903c2b90f1SJared McNeill 	sc->cf_pre_tag = EVENTHANDLER_REGISTER(cpufreq_pre_change,
5913c2b90f1SJared McNeill 	    aw_thermal_cf_pre_change, sc, EVENTHANDLER_PRI_FIRST);
5923c2b90f1SJared McNeill 
593343044c4SJared McNeill 	return (0);
594d69d5ab0SJared McNeill 
595d69d5ab0SJared McNeill fail:
596d69d5ab0SJared McNeill 	if (ih != NULL)
597d69d5ab0SJared McNeill 		bus_teardown_intr(dev, sc->res[1], ih);
598d69d5ab0SJared McNeill 	if (rst != NULL)
599d69d5ab0SJared McNeill 		hwreset_release(rst);
600d69d5ab0SJared McNeill 	if (clk_ahb != NULL)
601d69d5ab0SJared McNeill 		clk_release(clk_ahb);
602d69d5ab0SJared McNeill 	if (clk_ths != NULL)
603d69d5ab0SJared McNeill 		clk_release(clk_ths);
604d69d5ab0SJared McNeill 	bus_release_resources(dev, aw_thermal_spec, sc->res);
605d69d5ab0SJared McNeill 
606d69d5ab0SJared McNeill 	return (ENXIO);
607343044c4SJared McNeill }
608343044c4SJared McNeill 
609343044c4SJared McNeill static device_method_t aw_thermal_methods[] = {
610343044c4SJared McNeill 	/* Device interface */
611343044c4SJared McNeill 	DEVMETHOD(device_probe,		aw_thermal_probe),
612343044c4SJared McNeill 	DEVMETHOD(device_attach,	aw_thermal_attach),
613343044c4SJared McNeill 
614343044c4SJared McNeill 	DEVMETHOD_END
615343044c4SJared McNeill };
616343044c4SJared McNeill 
617343044c4SJared McNeill static driver_t aw_thermal_driver = {
618343044c4SJared McNeill 	"aw_thermal",
619343044c4SJared McNeill 	aw_thermal_methods,
620343044c4SJared McNeill 	sizeof(struct aw_thermal_softc),
621343044c4SJared McNeill };
622343044c4SJared McNeill 
623343044c4SJared McNeill static devclass_t aw_thermal_devclass;
624343044c4SJared McNeill 
625343044c4SJared McNeill DRIVER_MODULE(aw_thermal, simplebus, aw_thermal_driver, aw_thermal_devclass,
626343044c4SJared McNeill     0, 0);
627343044c4SJared McNeill MODULE_VERSION(aw_thermal, 1);
628