1 /* $NetBSD: sunxi_thermal.c,v 1.13 2021/01/27 03:10:20 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2016-2017 Jared McNeill <jmcneill@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.13 2021/01/27 03:10:20 thorpej 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 H5_ADC_ACQUIRE_TIME 0x1df 112 #define H5_FILTER 0x6 113 #define H5_INTC 0x3a070 114 #define H5_TEMP_DIV 20 115 #define H5_TEMP_BASE_L 233832448 116 #define H5_TEMP_MUL_L 124885 117 #define H5_TEMP_BASE_H_0 271581184 118 #define H5_TEMP_MUL_H_0 152253 119 #define H5_TEMP_BASE_H_1 289406976 120 #define H5_TEMP_MUL_H_1 166723 121 #define H5_CLK_RATE 24000000 122 #define H5_INIT_ALARM 105 /* degC */ 123 #define H5_INIT_SHUT 120 /* degC */ 124 125 #define TEMP_C_TO_K 273150000 126 #define SENSOR_ENABLE_ALL (SENSOR0_EN|SENSOR1_EN|SENSOR2_EN) 127 #define SHUT_INT_ALL (SHUT_INT0_STS|SHUT_INT1_STS|SHUT_INT2_STS) 128 #define ALARM_INT_ALL (ALARM_INT0_STS) 129 130 #define MAX_SENSORS 3 131 132 #if notyet 133 #define THROTTLE_ENABLE_DEFAULT 1 134 135 /* Enable thermal throttling */ 136 static int sunxi_thermal_throttle_enable = THROTTLE_ENABLE_DEFAULT; 137 #endif 138 139 struct sunxi_thermal_sensor { 140 const char *name; 141 const char *desc; 142 int init_alarm; 143 int init_shut; 144 }; 145 146 struct sunxi_thermal_config { 147 struct sunxi_thermal_sensor sensors[MAX_SENSORS]; 148 int nsensors; 149 uint64_t clk_rate; 150 uint32_t adc_acquire_time; 151 int adc_cali_en; 152 uint32_t filter; 153 uint32_t intc; 154 int (*to_temp)(u_int, uint32_t); 155 uint32_t (*to_reg)(u_int, int); 156 int calib0, calib1; 157 uint32_t calib0_mask, calib1_mask; 158 }; 159 160 static int 161 a83t_to_temp(u_int sensor, uint32_t val) 162 { 163 return ((A83T_TEMP_BASE - (val * A83T_TEMP_MUL)) / A83T_TEMP_DIV); 164 } 165 166 static const struct sunxi_thermal_config a83t_config = { 167 .nsensors = 3, 168 .sensors = { 169 [0] = { 170 .name = "cluster0", 171 .desc = "CPU cluster 0 temperature", 172 }, 173 [1] = { 174 .name = "cluster1", 175 .desc = "CPU cluster 1 temperature", 176 }, 177 [2] = { 178 .name = "gpu", 179 .desc = "GPU temperature", 180 }, 181 }, 182 .clk_rate = A83T_CLK_RATE, 183 .adc_acquire_time = A83T_ADC_ACQUIRE_TIME, 184 .adc_cali_en = 1, 185 .filter = A83T_FILTER, 186 .intc = A83T_INTC, 187 .to_temp = a83t_to_temp, 188 .calib0_mask = 0xffffffff, 189 .calib1_mask = 0xffffffff, 190 }; 191 192 static int 193 a64_to_temp(u_int sensor, uint32_t val) 194 { 195 return ((A64_TEMP_BASE - (val * A64_TEMP_MUL)) / A64_TEMP_DIV); 196 } 197 198 static const struct sunxi_thermal_config a64_config = { 199 .nsensors = 3, 200 .sensors = { 201 [0] = { 202 .name = "cpu", 203 .desc = "CPU temperature", 204 }, 205 [1] = { 206 .name = "gpu1", 207 .desc = "GPU temperature 1", 208 }, 209 [2] = { 210 .name = "gpu2", 211 .desc = "GPU temperature 2", 212 }, 213 }, 214 .clk_rate = A64_CLK_RATE, 215 .adc_acquire_time = A64_ADC_ACQUIRE_TIME, 216 .filter = A64_FILTER, 217 .intc = A64_INTC, 218 .to_temp = a64_to_temp, 219 }; 220 221 static int 222 h3_to_temp(u_int sensor, uint32_t val) 223 { 224 return (H3_TEMP_BASE - ((val * H3_TEMP_MUL) / H3_TEMP_DIV)); 225 } 226 227 static uint32_t 228 h3_to_reg(u_int sensor, int val) 229 { 230 return ((H3_TEMP_MINUS - (val * H3_TEMP_DIV)) / H3_TEMP_MUL); 231 } 232 233 static const struct sunxi_thermal_config h3_config = { 234 .nsensors = 1, 235 .sensors = { 236 [0] = { 237 .name = "cpu", 238 .desc = "CPU temperature", 239 .init_alarm = H3_INIT_ALARM, 240 .init_shut = H3_INIT_SHUT, 241 }, 242 }, 243 .clk_rate = H3_CLK_RATE, 244 .adc_acquire_time = H3_ADC_ACQUIRE_TIME, 245 .filter = H3_FILTER, 246 .intc = H3_INTC, 247 .to_temp = h3_to_temp, 248 .to_reg = h3_to_reg, 249 .calib0_mask = 0xfff, 250 }; 251 252 static int 253 h5_to_temp(u_int sensor, uint32_t val) 254 { 255 int base, mul; 256 257 if (val >= 0x500) { 258 base = H5_TEMP_BASE_L; 259 mul = H5_TEMP_MUL_L; 260 } else { 261 base = sensor == 0 ? H5_TEMP_BASE_H_0 : H5_TEMP_BASE_H_1; 262 mul = sensor == 0 ? H5_TEMP_MUL_H_0 : H5_TEMP_MUL_H_1; 263 } 264 265 return (base - val * mul) >> H5_TEMP_DIV; 266 } 267 268 static uint32_t 269 h5_to_reg(u_int sensor, int val) 270 { 271 int base, mul; 272 273 if (val <= 70) { 274 base = H5_TEMP_BASE_L; 275 mul = H5_TEMP_MUL_L; 276 } else { 277 base = sensor == 0 ? H5_TEMP_BASE_H_0 : H5_TEMP_BASE_H_1; 278 mul = sensor == 0 ? H5_TEMP_MUL_H_0 : H5_TEMP_MUL_H_1; 279 } 280 281 return (base - (val << H5_TEMP_DIV)) / mul; 282 } 283 284 static const struct sunxi_thermal_config h5_config = { 285 .nsensors = 2, 286 .sensors = { 287 [0] = { 288 .name = "cpu", 289 .desc = "CPU temperature", 290 .init_alarm = H5_INIT_ALARM, 291 .init_shut = H5_INIT_SHUT, 292 }, 293 [1] = { 294 .name = "gpu", 295 .desc = "GPU temperature", 296 .init_alarm = H5_INIT_ALARM, 297 .init_shut = H5_INIT_SHUT, 298 }, 299 }, 300 .clk_rate = H5_CLK_RATE, 301 .adc_acquire_time = H5_ADC_ACQUIRE_TIME, 302 .filter = H5_FILTER, 303 .intc = H5_INTC, 304 .to_temp = h5_to_temp, 305 .to_reg = h5_to_reg, 306 }; 307 308 static struct device_compatible_entry compat_data[] = { 309 { .compat = "allwinner,sun8i-a83t-ts", .data = &a83t_config }, 310 { .compat = "allwinner,sun8i-h3-ts", .data = &h3_config }, 311 { .compat = "allwinner,sun50i-a64-ts", .data = &a64_config }, 312 { .compat = "allwinner,sun50i-h5-ts", .data = &h5_config }, 313 DEVICE_COMPAT_EOL 314 }; 315 316 struct sunxi_thermal_softc { 317 device_t dev; 318 int phandle; 319 bus_space_tag_t bst; 320 bus_space_handle_t bsh; 321 const struct sunxi_thermal_config *conf; 322 323 kmutex_t lock; 324 callout_t tick; 325 326 struct sysmon_envsys *sme; 327 envsys_data_t data[MAX_SENSORS]; 328 }; 329 330 #define RD4(sc, reg) \ 331 bus_space_read_4((sc)->bst, (sc)->bsh, (reg)) 332 #define WR4(sc, reg, val) \ 333 bus_space_write_4((sc)->bst, (sc)->bsh, (reg), (val)) 334 335 static int 336 sunxi_thermal_init(struct sunxi_thermal_softc *sc) 337 { 338 uint32_t calib[2]; 339 int error; 340 341 if (sc->conf->calib0_mask != 0 || sc->conf->calib1_mask != 0) { 342 /* Read calibration settings from SRAM */ 343 error = sunxi_sid_read_tscalib(calib); 344 if (error != 0) 345 return error; 346 347 calib[0] &= sc->conf->calib0_mask; 348 calib[1] &= sc->conf->calib1_mask; 349 350 /* Write calibration settings to thermal controller */ 351 if (calib[0] != 0) 352 WR4(sc, THS_CALIB0, calib[0]); 353 if (calib[1] != 0) 354 WR4(sc, THS_CALIB1, calib[1]); 355 } 356 357 /* Configure ADC acquire time (CLK_IN/(N+1)) and enable sensors */ 358 WR4(sc, THS_CTRL1, ADC_CALI_EN); 359 WR4(sc, THS_CTRL0, sc->conf->adc_acquire_time); 360 WR4(sc, THS_CTRL2, sc->conf->adc_acquire_time << SENSOR_ACQ1_SHIFT); 361 362 /* Enable average filter */ 363 WR4(sc, THS_FILTER, sc->conf->filter); 364 365 /* Enable interrupts */ 366 WR4(sc, THS_INTS, RD4(sc, THS_INTS)); 367 WR4(sc, THS_INTC, sc->conf->intc | SHUT_INT_ALL | ALARM_INT_ALL); 368 369 /* Enable sensors */ 370 WR4(sc, THS_CTRL2, RD4(sc, THS_CTRL2) | SENSOR_ENABLE_ALL); 371 372 return 0; 373 } 374 375 static int 376 sunxi_thermal_gettemp(struct sunxi_thermal_softc *sc, int sensor) 377 { 378 uint32_t val; 379 380 val = RD4(sc, THS_DATA0 + (sensor * 4)); 381 382 return sc->conf->to_temp(sensor, val); 383 } 384 385 static int 386 sunxi_thermal_getshut(struct sunxi_thermal_softc *sc, int sensor) 387 { 388 uint32_t val; 389 390 val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4)); 391 val = (val >> SHUT_T_HOT_SHIFT) & SHUT_T_HOT_MASK; 392 393 return sc->conf->to_temp(sensor, val); 394 } 395 396 static void 397 sunxi_thermal_setshut(struct sunxi_thermal_softc *sc, int sensor, int temp) 398 { 399 uint32_t val; 400 401 val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4)); 402 val &= ~(SHUT_T_HOT_MASK << SHUT_T_HOT_SHIFT); 403 val |= (sc->conf->to_reg(sensor, temp) << SHUT_T_HOT_SHIFT); 404 WR4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4), val); 405 } 406 407 static int 408 sunxi_thermal_gethyst(struct sunxi_thermal_softc *sc, int sensor) 409 { 410 uint32_t val; 411 412 val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4)); 413 val = (val >> ALARM_T_HYST_SHIFT) & ALARM_T_HYST_MASK; 414 415 return sc->conf->to_temp(sensor, val); 416 } 417 418 static int 419 sunxi_thermal_getalarm(struct sunxi_thermal_softc *sc, int sensor) 420 { 421 uint32_t val; 422 423 val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4)); 424 val = (val >> ALARM_T_HOT_SHIFT) & ALARM_T_HOT_MASK; 425 426 return sc->conf->to_temp(sensor, val); 427 } 428 429 static void 430 sunxi_thermal_setalarm(struct sunxi_thermal_softc *sc, int sensor, int temp) 431 { 432 uint32_t val; 433 434 val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4)); 435 val &= ~(ALARM_T_HOT_MASK << ALARM_T_HOT_SHIFT); 436 val |= (sc->conf->to_reg(sensor, temp) << ALARM_T_HOT_SHIFT); 437 WR4(sc, THS_ALARM0_CTRL + (sensor * 4), val); 438 } 439 440 static void 441 sunxi_thermal_task_shut(void *arg) 442 { 443 struct sunxi_thermal_softc * const sc = arg; 444 445 device_printf(sc->dev, 446 "WARNING - current temperature exceeds safe limits\n"); 447 448 kern_reboot(RB_POWERDOWN, NULL); 449 } 450 451 static void 452 sunxi_thermal_task_alarm(void *arg) 453 { 454 struct sunxi_thermal_softc * const sc = arg; 455 456 const int alarm_val = sunxi_thermal_getalarm(sc, 0); 457 const int temp_val = sunxi_thermal_gettemp(sc, 0); 458 459 if (temp_val < alarm_val) 460 pmf_event_inject(NULL, PMFE_THROTTLE_DISABLE); 461 else 462 callout_schedule(&sc->tick, hz); 463 } 464 465 static void 466 sunxi_thermal_tick(void *arg) 467 { 468 struct sunxi_thermal_softc * const sc = arg; 469 470 sysmon_task_queue_sched(0, sunxi_thermal_task_alarm, sc); 471 } 472 473 static int 474 sunxi_thermal_intr(void *arg) 475 { 476 struct sunxi_thermal_softc * const sc = arg; 477 uint32_t ints; 478 479 mutex_enter(&sc->lock); 480 481 ints = RD4(sc, THS_INTS); 482 WR4(sc, THS_INTS, ints); 483 484 if ((ints & SHUT_INT_ALL) != 0) 485 sysmon_task_queue_sched(0, sunxi_thermal_task_shut, sc); 486 487 if ((ints & ALARM_INT_ALL) != 0) { 488 pmf_event_inject(NULL, PMFE_THROTTLE_ENABLE); 489 sysmon_task_queue_sched(0, sunxi_thermal_task_alarm, sc); 490 } 491 492 mutex_exit(&sc->lock); 493 494 return 1; 495 } 496 497 static void 498 sunxi_thermal_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 499 { 500 struct sunxi_thermal_softc * const sc = sme->sme_cookie; 501 502 const int64_t temp = sunxi_thermal_gettemp(sc, edata->private); 503 504 edata->value_cur = temp * 1000000 + TEMP_C_TO_K; 505 edata->state = ENVSYS_SVALID; 506 } 507 508 static int 509 sunxi_thermal_init_clocks(struct sunxi_thermal_softc *sc) 510 { 511 struct fdtbus_reset *rst; 512 struct clk *clk; 513 int error; 514 515 clk = fdtbus_clock_get(sc->phandle, "ahb"); 516 if (clk) { 517 error = clk_enable(clk); 518 if (error != 0) 519 return error; 520 } 521 522 clk = fdtbus_clock_get(sc->phandle, "ths"); 523 if (clk) { 524 error = clk_set_rate(clk, sc->conf->clk_rate); 525 if (error != 0) 526 return error; 527 error = clk_enable(clk); 528 if (error != 0) 529 return error; 530 } 531 532 rst = fdtbus_reset_get_index(sc->phandle, 0); 533 if (rst) { 534 error = fdtbus_reset_deassert(rst); 535 if (error != 0) 536 return error; 537 } 538 539 return 0; 540 } 541 542 static int 543 sunxi_thermal_match(device_t parent, cfdata_t cf, void *aux) 544 { 545 struct fdt_attach_args * const faa = aux; 546 547 return of_compatible_match(faa->faa_phandle, compat_data); 548 } 549 550 static void 551 sunxi_thermal_attach(device_t parent, device_t self, void *aux) 552 { 553 struct sunxi_thermal_softc * const sc = device_private(self); 554 struct fdt_attach_args * const faa = aux; 555 const int phandle = faa->faa_phandle; 556 char intrstr[128]; 557 bus_addr_t addr; 558 bus_size_t size; 559 void *ih; 560 int i; 561 562 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 563 aprint_error(": couldn't get registers\n"); 564 return; 565 } 566 567 sc->dev = self; 568 sc->phandle = phandle; 569 sc->bst = faa->faa_bst; 570 sc->conf = of_compatible_lookup(phandle, compat_data)->data; 571 if (bus_space_map(sc->bst, addr, size, 0, &sc->bsh) != 0) { 572 aprint_error(": couldn't map registers\n"); 573 return; 574 } 575 mutex_init(&sc->lock, MUTEX_DEFAULT, IPL_VM); 576 callout_init(&sc->tick, CALLOUT_MPSAFE); 577 callout_setfunc(&sc->tick, sunxi_thermal_tick, sc); 578 579 if (sunxi_thermal_init_clocks(sc) != 0) { 580 aprint_error(": couldn't enable clocks\n"); 581 return; 582 } 583 584 if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { 585 aprint_error(": couldn't decode interrupt\n"); 586 return; 587 } 588 589 aprint_naive("\n"); 590 aprint_normal(": Thermal sensor controller\n"); 591 592 ih = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, FDT_INTR_MPSAFE, 593 sunxi_thermal_intr, sc, device_xname(self)); 594 if (ih == NULL) { 595 aprint_error_dev(self, "couldn't establish interrupt on %s\n", 596 intrstr); 597 return; 598 } 599 aprint_normal_dev(self, "interrupting on %s\n", intrstr); 600 601 for (i = 0; i < sc->conf->nsensors; i++) { 602 if (sc->conf->sensors[i].init_alarm > 0) 603 sunxi_thermal_setalarm(sc, i, 604 sc->conf->sensors[i].init_alarm); 605 if (sc->conf->sensors[i].init_shut > 0) 606 sunxi_thermal_setshut(sc, i, 607 sc->conf->sensors[i].init_shut); 608 } 609 610 if (sunxi_thermal_init(sc) != 0) { 611 aprint_error_dev(self, "failed to initialize sensors\n"); 612 return; 613 } 614 615 sc->sme = sysmon_envsys_create(); 616 sc->sme->sme_name = device_xname(self); 617 sc->sme->sme_cookie = sc; 618 sc->sme->sme_refresh = sunxi_thermal_refresh; 619 for (i = 0; i < sc->conf->nsensors; i++) { 620 sc->data[i].private = i; 621 sc->data[i].units = ENVSYS_STEMP; 622 sc->data[i].state = ENVSYS_SINVALID; 623 strlcpy(sc->data[i].desc, sc->conf->sensors[i].desc, 624 sizeof(sc->data[i].desc)); 625 sysmon_envsys_sensor_attach(sc->sme, &sc->data[i]); 626 } 627 sysmon_envsys_register(sc->sme); 628 629 for (i = 0; i < sc->conf->nsensors; i++) { 630 device_printf(self, 631 "%s: alarm %dC hyst %dC shut %dC\n", 632 sc->conf->sensors[i].name, 633 sunxi_thermal_getalarm(sc, i), 634 sunxi_thermal_gethyst(sc, i), 635 sunxi_thermal_getshut(sc, i)); 636 } 637 } 638 639 CFATTACH_DECL_NEW(sunxi_thermal, sizeof(struct sunxi_thermal_softc), 640 sunxi_thermal_match, sunxi_thermal_attach, NULL, NULL); 641