Home | History | Annotate | Line # | Download | only in sunxi
sunxi_thermal.c revision 1.3.2.2
      1 /* $NetBSD: sunxi_thermal.c,v 1.3.2.2 2017/12/03 11:35:56 jdolecek Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2016-2017 Jared McNeill <jmcneill (at) invisible.ca>
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  */
     28 
     29 /*
     30  * Allwinner thermal sensor controller
     31  */
     32 
     33 #include <sys/cdefs.h>
     34 __KERNEL_RCSID(0, "$NetBSD: sunxi_thermal.c,v 1.3.2.2 2017/12/03 11:35:56 jdolecek Exp $");
     35 
     36 #include <sys/param.h>
     37 #include <sys/systm.h>
     38 #include <sys/device.h>
     39 #include <sys/kernel.h>
     40 #include <sys/reboot.h>
     41 
     42 #include <dev/sysmon/sysmonvar.h>
     43 #include <dev/sysmon/sysmon_taskq.h>
     44 
     45 #include <dev/fdt/fdtvar.h>
     46 
     47 #include <arm/sunxi/sunxi_sid.h>
     48 
     49 #define	THS_CTRL0		0x00
     50 #define	THS_CTRL1		0x04
     51 #define	 ADC_CALI_EN		(1 << 17)
     52 #define	THS_CTRL2		0x40
     53 #define	 SENSOR_ACQ1_SHIFT	16
     54 #define	 SENSOR2_EN		(1 << 2)
     55 #define	 SENSOR1_EN		(1 << 1)
     56 #define	 SENSOR0_EN		(1 << 0)
     57 #define	THS_INTC		0x44
     58 #define	THS_INTS		0x48
     59 #define	 THS2_DATA_IRQ_STS	(1 << 10)
     60 #define	 THS1_DATA_IRQ_STS	(1 << 9)
     61 #define	 THS0_DATA_IRQ_STS	(1 << 8)
     62 #define	 SHUT_INT2_STS		(1 << 6)
     63 #define	 SHUT_INT1_STS		(1 << 5)
     64 #define	 SHUT_INT0_STS		(1 << 4)
     65 #define	 ALARM_INT2_STS		(1 << 2)
     66 #define	 ALARM_INT1_STS		(1 << 1)
     67 #define	 ALARM_INT0_STS		(1 << 0)
     68 #define	THS_ALARM0_CTRL		0x50
     69 #define	 ALARM_T_HOT_MASK	0xfff
     70 #define	 ALARM_T_HOT_SHIFT	16
     71 #define	 ALARM_T_HYST_MASK	0xfff
     72 #define	 ALARM_T_HYST_SHIFT	0
     73 #define	THS_SHUTDOWN0_CTRL	0x60
     74 #define	 SHUT_T_HOT_MASK	0xfff
     75 #define	 SHUT_T_HOT_SHIFT	16
     76 #define	THS_FILTER		0x70
     77 #define	THS_CALIB0		0x74
     78 #define	THS_CALIB1		0x78
     79 #define	THS_DATA0		0x80
     80 #define	THS_DATA1		0x84
     81 #define	THS_DATA2		0x88
     82 #define	 DATA_MASK		0xfff
     83 
     84 #define	A83T_ADC_ACQUIRE_TIME	0x17
     85 #define	A83T_FILTER		0x4
     86 #define	A83T_INTC		0x1000
     87 #define	A83T_TEMP_BASE		2719000
     88 #define	A83T_TEMP_MUL		1000
     89 #define	A83T_TEMP_DIV		14186
     90 #define	A83T_CLK_RATE		24000000
     91 
     92 #define	A64_ADC_ACQUIRE_TIME	0x190
     93 #define	A64_FILTER		0x6
     94 #define	A64_INTC		0x18000
     95 #define	A64_TEMP_BASE		2170000
     96 #define	A64_TEMP_MUL		1000
     97 #define	A64_TEMP_DIV		8560
     98 #define	A64_CLK_RATE		4000000
     99 
    100 #define	H3_ADC_ACQUIRE_TIME	0x3f
    101 #define	H3_FILTER		0x6
    102 #define	H3_INTC			0x191000
    103 #define	H3_TEMP_BASE		217
    104 #define	H3_TEMP_MUL		1000
    105 #define	H3_TEMP_DIV		8253
    106 #define	H3_TEMP_MINUS		1794000
    107 #define	H3_CLK_RATE		4000000
    108 #define	H3_INIT_ALARM		90	/* degC */
    109 #define	H3_INIT_SHUT		105	/* degC */
    110 
    111 #define	TEMP_C_TO_K		273150000
    112 #define	SENSOR_ENABLE_ALL	(SENSOR0_EN|SENSOR1_EN|SENSOR2_EN)
    113 #define	SHUT_INT_ALL		(SHUT_INT0_STS|SHUT_INT1_STS|SHUT_INT2_STS)
    114 #define	ALARM_INT_ALL		(ALARM_INT0_STS)
    115 
    116 #define	MAX_SENSORS	3
    117 
    118 #if notyet
    119 #define	THROTTLE_ENABLE_DEFAULT	1
    120 
    121 /* Enable thermal throttling */
    122 static int sunxi_thermal_throttle_enable = THROTTLE_ENABLE_DEFAULT;
    123 #endif
    124 
    125 struct sunxi_thermal_sensor {
    126 	const char		*name;
    127 	const char		*desc;
    128 	int			init_alarm;
    129 	int			init_shut;
    130 };
    131 
    132 struct sunxi_thermal_config {
    133 	struct sunxi_thermal_sensor	sensors[MAX_SENSORS];
    134 	int				nsensors;
    135 	uint64_t			clk_rate;
    136 	uint32_t			adc_acquire_time;
    137 	int				adc_cali_en;
    138 	uint32_t			filter;
    139 	uint32_t			intc;
    140 	int				(*to_temp)(uint32_t);
    141 	uint32_t			(*to_reg)(int);
    142 	int				temp_base;
    143 	int				temp_mul;
    144 	int				temp_div;
    145 	int				calib0, calib1;
    146 	uint32_t			calib0_mask, calib1_mask;
    147 };
    148 
    149 static int
    150 a83t_to_temp(uint32_t val)
    151 {
    152 	return ((A83T_TEMP_BASE - (val * A83T_TEMP_MUL)) / A83T_TEMP_DIV);
    153 }
    154 
    155 static const struct sunxi_thermal_config a83t_config = {
    156 	.nsensors = 3,
    157 	.sensors = {
    158 		[0] = {
    159 			.name = "cluster0",
    160 			.desc = "CPU cluster 0 temperature",
    161 		},
    162 		[1] = {
    163 			.name = "cluster1",
    164 			.desc = "CPU cluster 1 temperature",
    165 		},
    166 		[2] = {
    167 			.name = "gpu",
    168 			.desc = "GPU temperature",
    169 		},
    170 	},
    171 	.clk_rate = A83T_CLK_RATE,
    172 	.adc_acquire_time = A83T_ADC_ACQUIRE_TIME,
    173 	.adc_cali_en = 1,
    174 	.filter = A83T_FILTER,
    175 	.intc = A83T_INTC,
    176 	.to_temp = a83t_to_temp,
    177 	.calib0_mask = 0xffffffff,
    178 	.calib1_mask = 0xffffffff,
    179 };
    180 
    181 static int
    182 a64_to_temp(uint32_t val)
    183 {
    184 	return ((A64_TEMP_BASE - (val * A64_TEMP_MUL)) / A64_TEMP_DIV);
    185 }
    186 
    187 static const struct sunxi_thermal_config a64_config = {
    188 	.nsensors = 3,
    189 	.sensors = {
    190 		[0] = {
    191 			.name = "cpu",
    192 			.desc = "CPU temperature",
    193 		},
    194 		[1] = {
    195 			.name = "gpu1",
    196 			.desc = "GPU temperature 1",
    197 		},
    198 		[2] = {
    199 			.name = "gpu2",
    200 			.desc = "GPU temperature 2",
    201 		},
    202 	},
    203 	.clk_rate = A64_CLK_RATE,
    204 	.adc_acquire_time = A64_ADC_ACQUIRE_TIME,
    205 	.filter = A64_FILTER,
    206 	.intc = A64_INTC,
    207 	.to_temp = a64_to_temp,
    208 };
    209 
    210 static int
    211 h3_to_temp(uint32_t val)
    212 {
    213 	return (H3_TEMP_BASE - ((val * H3_TEMP_MUL) / H3_TEMP_DIV));
    214 }
    215 
    216 static uint32_t
    217 h3_to_reg(int val)
    218 {
    219 	return ((H3_TEMP_MINUS - (val * H3_TEMP_DIV)) / H3_TEMP_MUL);
    220 }
    221 
    222 static const struct sunxi_thermal_config h3_config = {
    223 	.nsensors = 1,
    224 	.sensors = {
    225 		[0] = {
    226 			.name = "cpu",
    227 			.desc = "CPU temperature",
    228 			.init_alarm = H3_INIT_ALARM,
    229 			.init_shut = H3_INIT_SHUT,
    230 		},
    231 	},
    232 	.clk_rate = H3_CLK_RATE,
    233 	.adc_acquire_time = H3_ADC_ACQUIRE_TIME,
    234 	.filter = H3_FILTER,
    235 	.intc = H3_INTC,
    236 	.to_temp = h3_to_temp,
    237 	.to_reg = h3_to_reg,
    238 	.calib0_mask = 0xfff,
    239 };
    240 
    241 static struct of_compat_data compat_data[] = {
    242 	{ "allwinner,sun8i-a83t-ts",	(uintptr_t)&a83t_config },
    243 	{ "allwinner,sun8i-h3-ts",	(uintptr_t)&h3_config },
    244 	{ "allwinner,sun50i-a64-ts",	(uintptr_t)&a64_config },
    245 	{ NULL,				(uintptr_t)NULL }
    246 };
    247 
    248 struct sunxi_thermal_softc {
    249 	device_t			dev;
    250 	int				phandle;
    251 	bus_space_tag_t			bst;
    252 	bus_space_handle_t		bsh;
    253 	struct sunxi_thermal_config	*conf;
    254 
    255 	kmutex_t			lock;
    256 	callout_t			tick;
    257 
    258 	struct sysmon_envsys		*sme;
    259 	envsys_data_t			data[MAX_SENSORS];
    260 };
    261 
    262 #define	RD4(sc, reg)		\
    263 	bus_space_read_4((sc)->bst, (sc)->bsh, (reg))
    264 #define	WR4(sc, reg, val)	\
    265 	bus_space_write_4((sc)->bst, (sc)->bsh, (reg), (val))
    266 
    267 static int
    268 sunxi_thermal_init(struct sunxi_thermal_softc *sc)
    269 {
    270 	uint32_t calib[2];
    271 	int error;
    272 
    273 	if (sc->conf->calib0_mask != 0 || sc->conf->calib1_mask != 0) {
    274 		/* Read calibration settings from SRAM */
    275 		error = sunxi_sid_read_tscalib(calib);
    276 		if (error != 0)
    277 			return error;
    278 
    279 		calib[0] &= sc->conf->calib0_mask;
    280 		calib[1] &= sc->conf->calib1_mask;
    281 
    282 		/* Write calibration settings to thermal controller */
    283 		if (calib[0] != 0)
    284 			WR4(sc, THS_CALIB0, calib[0]);
    285 		if (calib[1] != 0)
    286 			WR4(sc, THS_CALIB1, calib[1]);
    287 	}
    288 
    289 	/* Configure ADC acquire time (CLK_IN/(N+1)) and enable sensors */
    290 	WR4(sc, THS_CTRL1, ADC_CALI_EN);
    291 	WR4(sc, THS_CTRL0, sc->conf->adc_acquire_time);
    292 	WR4(sc, THS_CTRL2, sc->conf->adc_acquire_time << SENSOR_ACQ1_SHIFT);
    293 
    294 	/* Enable average filter */
    295 	WR4(sc, THS_FILTER, sc->conf->filter);
    296 
    297 	/* Enable interrupts */
    298 	WR4(sc, THS_INTS, RD4(sc, THS_INTS));
    299 	WR4(sc, THS_INTC, sc->conf->intc | SHUT_INT_ALL | ALARM_INT_ALL);
    300 
    301 	/* Enable sensors */
    302 	WR4(sc, THS_CTRL2, RD4(sc, THS_CTRL2) | SENSOR_ENABLE_ALL);
    303 
    304 	return 0;
    305 }
    306 
    307 static int
    308 sunxi_thermal_gettemp(struct sunxi_thermal_softc *sc, int sensor)
    309 {
    310 	uint32_t val;
    311 
    312 	val = RD4(sc, THS_DATA0 + (sensor * 4));
    313 
    314 	return sc->conf->to_temp(val);
    315 }
    316 
    317 static int
    318 sunxi_thermal_getshut(struct sunxi_thermal_softc *sc, int sensor)
    319 {
    320 	uint32_t val;
    321 
    322 	val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4));
    323 	val = (val >> SHUT_T_HOT_SHIFT) & SHUT_T_HOT_MASK;
    324 
    325 	return sc->conf->to_temp(val);
    326 }
    327 
    328 static void
    329 sunxi_thermal_setshut(struct sunxi_thermal_softc *sc, int sensor, int temp)
    330 {
    331 	uint32_t val;
    332 
    333 	val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4));
    334 	val &= ~(SHUT_T_HOT_MASK << SHUT_T_HOT_SHIFT);
    335 	val |= (sc->conf->to_reg(temp) << SHUT_T_HOT_SHIFT);
    336 	WR4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4), val);
    337 }
    338 
    339 static int
    340 sunxi_thermal_gethyst(struct sunxi_thermal_softc *sc, int sensor)
    341 {
    342 	uint32_t val;
    343 
    344 	val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
    345 	val = (val >> ALARM_T_HYST_SHIFT) & ALARM_T_HYST_MASK;
    346 
    347 	return sc->conf->to_temp(val);
    348 }
    349 
    350 static int
    351 sunxi_thermal_getalarm(struct sunxi_thermal_softc *sc, int sensor)
    352 {
    353 	uint32_t val;
    354 
    355 	val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
    356 	val = (val >> ALARM_T_HOT_SHIFT) & ALARM_T_HOT_MASK;
    357 
    358 	return sc->conf->to_temp(val);
    359 }
    360 
    361 static void
    362 sunxi_thermal_setalarm(struct sunxi_thermal_softc *sc, int sensor, int temp)
    363 {
    364 	uint32_t val;
    365 
    366 	val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
    367 	val &= ~(ALARM_T_HOT_MASK << ALARM_T_HOT_SHIFT);
    368 	val |= (sc->conf->to_reg(temp) << ALARM_T_HOT_SHIFT);
    369 	WR4(sc, THS_ALARM0_CTRL + (sensor * 4), val);
    370 }
    371 
    372 static void
    373 sunxi_thermal_task_shut(void *arg)
    374 {
    375 	struct sunxi_thermal_softc * const sc = arg;
    376 
    377 	device_printf(sc->dev,
    378 	    "WARNING - current temperature exceeds safe limits\n");
    379 
    380 	cpu_reboot(RB_POWERDOWN, NULL);
    381 }
    382 
    383 static void
    384 sunxi_thermal_task_alarm(void *arg)
    385 {
    386 	struct sunxi_thermal_softc * const sc = arg;
    387 
    388 	const int alarm_val = sunxi_thermal_getalarm(sc, 0);
    389 	const int temp_val = sunxi_thermal_gettemp(sc, 0);
    390 
    391 	if (temp_val < alarm_val)
    392 		pmf_event_inject(NULL, PMFE_THROTTLE_DISABLE);
    393 	else
    394 		callout_schedule(&sc->tick, hz);
    395 }
    396 
    397 static void
    398 sunxi_thermal_tick(void *arg)
    399 {
    400 	struct sunxi_thermal_softc * const sc = arg;
    401 
    402 	sysmon_task_queue_sched(0, sunxi_thermal_task_alarm, sc);
    403 }
    404 
    405 static int
    406 sunxi_thermal_intr(void *arg)
    407 {
    408 	struct sunxi_thermal_softc * const sc = arg;
    409 	uint32_t ints;
    410 
    411 	mutex_enter(&sc->lock);
    412 
    413 	ints = RD4(sc, THS_INTS);
    414 	WR4(sc, THS_INTS, ints);
    415 
    416 	if ((ints & SHUT_INT_ALL) != 0)
    417 		sysmon_task_queue_sched(0, sunxi_thermal_task_shut, sc);
    418 
    419 	if ((ints & ALARM_INT_ALL) != 0) {
    420 		pmf_event_inject(NULL, PMFE_THROTTLE_ENABLE);
    421 		sysmon_task_queue_sched(0, sunxi_thermal_task_alarm, sc);
    422 	}
    423 
    424 	mutex_exit(&sc->lock);
    425 
    426 	return 1;
    427 }
    428 
    429 static void
    430 sunxi_thermal_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
    431 {
    432 	struct sunxi_thermal_softc * const sc = sme->sme_cookie;
    433 
    434 	const int64_t temp = sunxi_thermal_gettemp(sc, edata->private);
    435 
    436 	edata->value_cur = temp * 1000000 + TEMP_C_TO_K;
    437 	edata->state = ENVSYS_SVALID;
    438 }
    439 
    440 static int
    441 sunxi_thermal_init_clocks(struct sunxi_thermal_softc *sc)
    442 {
    443 	struct fdtbus_reset *rst;
    444 	struct clk *clk;
    445 	int error;
    446 
    447 	clk = fdtbus_clock_get(sc->phandle, "ahb");
    448 	if (clk == NULL)
    449 		return ENXIO;
    450 	error = clk_enable(clk);
    451 	if (error != 0)
    452 		return error;
    453 
    454 	clk = fdtbus_clock_get(sc->phandle, "ths");
    455 	if (clk == NULL)
    456 		return ENXIO;
    457 	error = clk_set_rate(clk, sc->conf->clk_rate);
    458 	if (error != 0)
    459 		return error;
    460 	error = clk_enable(clk);
    461 	if (error != 0)
    462 		return error;
    463 
    464 	rst = fdtbus_reset_get_index(sc->phandle, 0);
    465 	if (rst == NULL)
    466 		return ENXIO;
    467 	error = fdtbus_reset_deassert(rst);
    468 	if (error != 0)
    469 		return error;
    470 
    471 	return 0;
    472 }
    473 
    474 static int
    475 sunxi_thermal_match(device_t parent, cfdata_t cf, void *aux)
    476 {
    477 	struct fdt_attach_args * const faa = aux;
    478 
    479 	return of_match_compat_data(faa->faa_phandle, compat_data);
    480 }
    481 
    482 static void
    483 sunxi_thermal_attach(device_t parent, device_t self, void *aux)
    484 {
    485 	struct sunxi_thermal_softc * const sc = device_private(self);
    486 	struct fdt_attach_args * const faa = aux;
    487 	const int phandle = faa->faa_phandle;
    488 	char intrstr[128];
    489 	bus_addr_t addr;
    490 	bus_size_t size;
    491 	void *ih;
    492 	int i;
    493 
    494 	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
    495 		aprint_error(": couldn't get registers\n");
    496 		return;
    497 	}
    498 
    499 	sc->dev = self;
    500 	sc->phandle = phandle;
    501 	sc->bst = faa->faa_bst;
    502 	sc->conf = (void *)of_search_compatible(phandle, compat_data)->data;
    503 	if (bus_space_map(sc->bst, addr, size, 0, &sc->bsh) != 0) {
    504 		aprint_error(": couldn't map registers\n");
    505 		return;
    506 	}
    507 	mutex_init(&sc->lock, MUTEX_DEFAULT, IPL_VM);
    508 	callout_init(&sc->tick, CALLOUT_MPSAFE);
    509 	callout_setfunc(&sc->tick, sunxi_thermal_tick, sc);
    510 
    511 	if (sunxi_thermal_init_clocks(sc) != 0) {
    512 		aprint_error(": couldn't enable clocks\n");
    513 		return;
    514 	}
    515 
    516 	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
    517 		aprint_error(": couldn't decode interrupt\n");
    518 		return;
    519 	}
    520 
    521 	aprint_naive("\n");
    522 	aprint_normal(": Thermal sensor controller\n");
    523 
    524 	ih = fdtbus_intr_establish(phandle, 0, IPL_VM, FDT_INTR_MPSAFE,
    525 	    sunxi_thermal_intr, sc);
    526 	if (ih == NULL) {
    527 		aprint_error_dev(self, "couldn't establish interrupt on %s\n",
    528 		    intrstr);
    529 		return;
    530 	}
    531 	aprint_normal_dev(self, "interrupting on %s\n", intrstr);
    532 
    533 	for (i = 0; i < sc->conf->nsensors; i++) {
    534 		if (sc->conf->sensors[i].init_alarm > 0)
    535 			sunxi_thermal_setalarm(sc, i,
    536 			    sc->conf->sensors[i].init_alarm);
    537 		if (sc->conf->sensors[i].init_shut > 0)
    538 			sunxi_thermal_setshut(sc, i,
    539 			    sc->conf->sensors[i].init_shut);
    540 	}
    541 
    542 	if (sunxi_thermal_init(sc) != 0) {
    543 		aprint_error_dev(self, "failed to initialize sensors\n");
    544 		return;
    545 	}
    546 
    547 	sc->sme = sysmon_envsys_create();
    548 	sc->sme->sme_name = device_xname(self);
    549 	sc->sme->sme_cookie = sc;
    550 	sc->sme->sme_refresh = sunxi_thermal_refresh;
    551 	for (i = 0; i < sc->conf->nsensors; i++) {
    552 		sc->data[i].private = i;
    553 		sc->data[i].units = ENVSYS_STEMP;
    554 		sc->data[i].state = ENVSYS_SINVALID;
    555 		strlcpy(sc->data[1].desc, sc->conf->sensors[i].desc,
    556 		    sizeof(sc->data[1].desc));
    557 		sysmon_envsys_sensor_attach(sc->sme, &sc->data[i]);
    558 	}
    559 	sysmon_envsys_register(sc->sme);
    560 
    561 	for (i = 0; i < sc->conf->nsensors; i++) {
    562 		device_printf(self,
    563 		    "%s: alarm %dC hyst %dC shut %dC\n",
    564 		    sc->conf->sensors[i].name,
    565 		    sunxi_thermal_getalarm(sc, i),
    566 		    sunxi_thermal_gethyst(sc, i),
    567 		    sunxi_thermal_getshut(sc, i));
    568 	}
    569 }
    570 
    571 CFATTACH_DECL_NEW(sunxi_thermal, sizeof(struct sunxi_thermal_softc),
    572     sunxi_thermal_match, sunxi_thermal_attach, NULL, NULL);
    573