Many of the cheap arm64 (and armv7) boards will overheat if you run the CPU cores at full throttle for a while. Adding a heatsink may help a little bit, but not enough. Some boards have a microcontroller that monitors the temperature and throttles the CPUs if necessary. Other boards don't and will eventually hit a critical temperature where it will either do an emergency powerdown or will start to become unreliable.
In order to prevent this, the OS is supposed to monitor the temperature and cool the device (either actively or passively) when the temperature gets too high. There are device tree bindings for so-called thermal zones that link together temperature sensors and cooling devices and define trip points that define the temperatures at which we have to start cooling. Most boards use passive cooling through reducing the CPU clock speed and voltage. The diff below implements support for these thermal zones. Most of the code is implemented in the generic FDT support code in dev/ofw. Sensors and cooling devices make themselves known to this layer by registering themselves just like we do for clocks and regulators. This means the code is available on armv7 and octeon as well. On arm64, the CPUs are registered as cooling devices and implement passive cooling by simply clipping the available DVFS states instead of modifying perflevel. I also added registration code to rktemp(4). With these changes my RockPro64 can do a make build without reaching the critical temperature while building clang. The CPU temperature now hovers around 70 degC, which is the temperature associated with the lowest trip point that throttles only the "big" cores. ok? Index: arch/arm64/arm64/cpu.c =================================================================== RCS file: /cvs/src/sys/arch/arm64/arm64/cpu.c,v retrieving revision 1.32 diff -u -p -r1.32 cpu.c --- arch/arm64/arm64/cpu.c 23 Jun 2019 17:14:49 -0000 1.32 +++ arch/arm64/arm64/cpu.c 29 Jun 2019 09:38:31 -0000 @@ -32,6 +32,7 @@ #include <dev/ofw/openfirm.h> #include <dev/ofw/ofw_clock.h> #include <dev/ofw/ofw_regulator.h> +#include <dev/ofw/ofw_thermal.h> #include <dev/ofw/fdt.h> #include <machine/cpufunc.h> @@ -600,10 +601,14 @@ void cpu_opp_mountroot(struct device *); void cpu_opp_dotask(void *); void cpu_opp_setperf(int); +uint32_t cpu_opp_get_cooling_level(void *, uint32_t *); +void cpu_opp_set_cooling_level(void *, uint32_t *, uint32_t); + void cpu_opp_init(struct cpu_info *ci, uint32_t phandle) { struct opp_table *ot; + struct cooling_device *cd; int count, node, child; uint32_t opp_hz, opp_microvolt; uint32_t values[3]; @@ -670,8 +675,16 @@ cpu_opp_init(struct cpu_info *ci, uint32 LIST_INSERT_HEAD(&opp_tables, ot, ot_list); ci->ci_opp_table = ot; + ci->ci_opp_max = ot->ot_nopp - 1; ci->ci_cpu_supply = OF_getpropint(ci->ci_node, "cpu-supply", 0); + cd = malloc(sizeof(struct cooling_device), M_DEVBUF, M_ZERO | M_WAITOK); + cd->cd_node = ci->ci_node; + cd->cd_cookie = ci; + cd->cd_get_level = cpu_opp_get_cooling_level; + cd->cd_set_level = cpu_opp_set_cooling_level; + cooling_device_register(cd); + /* * Do addional checks at mountroot when all the clocks and * regulators are available. @@ -775,7 +788,7 @@ cpu_opp_dotask(void *arg) if (ot->ot_master && ot->ot_master != ci) continue; - opp_idx = ci->ci_opp_idx; + opp_idx = MIN(ci->ci_opp_idx, ci->ci_opp_max); opp_hz = ot->ot_opp[opp_idx].opp_hz; opp_microvolt = ot->ot_opp[opp_idx].opp_microvolt; @@ -844,4 +857,30 @@ cpu_opp_setperf(int level) * regulators might need process context. */ task_add(systq, &cpu_opp_task); +} + +uint32_t +cpu_opp_get_cooling_level(void *cookie, uint32_t *cells) +{ + struct cpu_info *ci = cookie; + struct opp_table *ot = ci->ci_opp_table; + + return ot->ot_nopp - ci->ci_opp_max - 1; +} + +void +cpu_opp_set_cooling_level(void *cookie, uint32_t *cells, uint32_t level) +{ + struct cpu_info *ci = cookie; + struct opp_table *ot = ci->ci_opp_table; + int opp_max; + + if (level > (ot->ot_nopp - 1)) + level = ot->ot_nopp - 1; + + opp_max = (ot->ot_nopp - level - 1); + if (ci->ci_opp_max != opp_max) { + ci->ci_opp_max = opp_max; + task_add(systq, &cpu_opp_task); + } } Index: arch/arm64/dev/mainbus.c =================================================================== RCS file: /cvs/src/sys/arch/arm64/dev/mainbus.c,v retrieving revision 1.13 diff -u -p -r1.13 mainbus.c --- arch/arm64/dev/mainbus.c 23 May 2019 13:41:53 -0000 1.13 +++ arch/arm64/dev/mainbus.c 29 Jun 2019 09:38:32 -0000 @@ -25,6 +25,7 @@ #include <machine/fdt.h> #include <dev/ofw/openfirm.h> #include <dev/ofw/fdt.h> +#include <dev/ofw/ofw_thermal.h> #include <arm64/arm64/arm64var.h> #include <arm64/dev/mainbus.h> @@ -147,6 +148,8 @@ mainbus_attach(struct device *parent, st /* Attach secondary CPUs. */ mainbus_attach_cpus(self, mainbus_match_secondary); + + thermal_init(); } int Index: arch/arm64/include/cpu.h =================================================================== RCS file: /cvs/src/sys/arch/arm64/include/cpu.h,v retrieving revision 1.13 diff -u -p -r1.13 cpu.h --- arch/arm64/include/cpu.h 4 Jun 2019 14:03:21 -0000 1.13 +++ arch/arm64/include/cpu.h 29 Jun 2019 09:38:32 -0000 @@ -110,6 +110,7 @@ struct cpu_info { struct opp_table *ci_opp_table; volatile int ci_opp_idx; + volatile int ci_opp_max; uint32_t ci_cpu_supply; #ifdef MULTIPROCESSOR Index: dev/fdt/rktemp.c =================================================================== RCS file: /cvs/src/sys/dev/fdt/rktemp.c,v retrieving revision 1.4 diff -u -p -r1.4 rktemp.c --- dev/fdt/rktemp.c 1 Jan 2019 15:56:19 -0000 1.4 +++ dev/fdt/rktemp.c 29 Jun 2019 09:38:37 -0000 @@ -28,6 +28,7 @@ #include <dev/ofw/ofw_clock.h> #include <dev/ofw/ofw_misc.h> #include <dev/ofw/ofw_pinctrl.h> +#include <dev/ofw/ofw_thermal.h> #include <dev/ofw/fdt.h> /* Registers */ @@ -205,6 +206,8 @@ struct rktemp_softc { struct ksensor sc_sensors[3]; int sc_nsensors; struct ksensordev sc_sensordev; + + struct thermal_sensor sc_ts; }; int rktemp_match(struct device *, void *, void *); @@ -222,6 +225,7 @@ int32_t rktemp_calc_code(struct rktemp_s int32_t rktemp_calc_temp(struct rktemp_softc *, int32_t); int rktemp_valid(struct rktemp_softc *, int32_t); void rktemp_refresh_sensors(void *); +int32_t rktemp_get_temperature(void *, uint32_t *); int rktemp_match(struct device *parent, void *match, void *aux) @@ -332,6 +336,11 @@ rktemp_attach(struct device *parent, str } sensordev_install(&sc->sc_sensordev); sensor_task_register(sc, rktemp_refresh_sensors, 5); + + sc->sc_ts.ts_node = node; + sc->sc_ts.ts_cookie = sc; + sc->sc_ts.ts_get_temperature = rktemp_get_temperature; + thermal_sensor_register(&sc->sc_ts); } int32_t @@ -434,4 +443,21 @@ rktemp_refresh_sensors(void *arg) else sc->sc_sensors[i].flags |= SENSOR_FINVALID; } +} + +int32_t +rktemp_get_temperature(void *cookie, uint32_t *cells) +{ + struct rktemp_softc *sc = cookie; + uint32_t idx = cells[0]; + int32_t code; + + if (idx >= sc->sc_nsensors) + return THERMAL_SENSOR_MAX; + + code = HREAD4(sc, TSADC_DATA0 + idx * 4); + if (rktemp_valid(sc, code)) + return rktemp_calc_temp(sc, code); + else + return THERMAL_SENSOR_MAX; } Index: dev/ofw/files.ofw =================================================================== RCS file: /cvs/src/sys/dev/ofw/files.ofw,v retrieving revision 1.6 diff -u -p -r1.6 files.ofw --- dev/ofw/files.ofw 4 May 2018 16:12:12 -0000 1.6 +++ dev/ofw/files.ofw 29 Jun 2019 09:38:38 -0000 @@ -7,3 +7,4 @@ file dev/ofw/ofw_misc.c fdt file dev/ofw/ofw_pinctrl.c fdt file dev/ofw/ofw_power.c fdt file dev/ofw/ofw_regulator.c fdt +file dev/ofw/ofw_thermal.c fdt Index: dev/ofw/ofw_thermal.c =================================================================== RCS file: dev/ofw/ofw_thermal.c diff -N dev/ofw/ofw_thermal.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ dev/ofw/ofw_thermal.c 29 Jun 2019 09:38:38 -0000 @@ -0,0 +1,444 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2019 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/stdint.h> +#include <sys/task.h> +#include <sys/timeout.h> + +#include <machine/bus.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_thermal.h> + +LIST_HEAD(, thermal_sensor) thermal_sensors = + LIST_HEAD_INITIALIZER(thermal_sensors); + +LIST_HEAD(, cooling_device) cooling_devices = + LIST_HEAD_INITIALIZER(cooling_devices); + +struct taskq *tztq; + +struct trippoint { + int32_t tp_temperature; + uint32_t tp_hysteresis; + int tp_type; + uint32_t tp_phandle; +}; + +#define THERMAL_NONE 0 +#define THERMAL_ACTIVE 1 +#define THERMAL_PASSIVE 2 +#define THERMAL_HOT 3 +#define THERMAL_CRITICAL 4 + +struct cmap { + uint32_t *cm_cdev; + uint32_t *cm_cdevend; + uint32_t cm_trip; +}; + +struct cdev { + uint32_t cd_phandle; + int32_t cd_level; + int cd_active; + LIST_ENTRY(cdev) cd_list; +}; + +struct thermal_zone { + int tz_node; + char tz_name[64]; + struct task tz_poll_task; + struct timeout tz_poll_to; + uint32_t *tz_sensors; + uint32_t tz_polling_delay; + uint32_t tz_polling_delay_passive; + + struct trippoint *tz_trips; + int tz_ntrips; + struct trippoint *tz_tp; + + struct cmap *tz_cmaps; + int tz_ncmaps; + struct cmap *tz_cm; + + LIST_HEAD(, cdev) tz_cdevs; + + int32_t tz_temperature; +}; + +void +thermal_sensor_register(struct thermal_sensor *ts) +{ + ts->ts_cells = OF_getpropint(ts->ts_node, "#thermal-sensor-cells", 0); + ts->ts_phandle = OF_getpropint(ts->ts_node, "phandle", 0); + if (ts->ts_phandle == 0) + return; + + LIST_INSERT_HEAD(&thermal_sensors, ts, ts_list); +} + +void +cooling_device_register(struct cooling_device *cd) +{ + cd->cd_cells = OF_getpropint(cd->cd_node, "#cooling-cells", 0); + cd->cd_phandle = OF_getpropint(cd->cd_node, "phandle", 0); + if (cd->cd_phandle == 0) + return; + + LIST_INSERT_HEAD(&cooling_devices, cd, cd_list); +} + +int32_t +thermal_get_temperature_cells(uint32_t *cells) +{ + struct thermal_sensor *ts; + uint32_t phandle = cells[0]; + + LIST_FOREACH(ts, &thermal_sensors, ts_list) { + if (ts->ts_phandle == phandle) + break; + } + + if (ts && ts->ts_get_temperature) + return ts->ts_get_temperature(ts->ts_cookie, &cells[1]); + + return THERMAL_SENSOR_MAX; +} + +void +thermal_zone_poll_timeout(void *arg) +{ + struct thermal_zone *tz = arg; + + task_add(tztq, &tz->tz_poll_task); +} + +uint32_t * +cdev_next_cdev(uint32_t *cells) +{ + uint32_t phandle = cells[0]; + int node, ncells; + + node = OF_getnodebyphandle(phandle); + if (node == 0) + return NULL; + + ncells = OF_getpropint(node, "#cooling-cells", 2); + return cells + ncells + 1; +} + +uint32_t +cdev_get_level(uint32_t *cells) +{ + struct cooling_device *cd; + uint32_t phandle = cells[0]; + + LIST_FOREACH(cd, &cooling_devices, cd_list) { + if (cd->cd_phandle == phandle) + break; + } + + if (cd && cd->cd_get_level) + return cd->cd_get_level(cd->cd_cookie, &cells[1]); + + return 0; +} + +void +cdev_set_level(uint32_t *cells, uint32_t level) +{ + struct cooling_device *cd; + uint32_t phandle = cells[0]; + + LIST_FOREACH(cd, &cooling_devices, cd_list) { + if (cd->cd_phandle == phandle) + break; + } + + if (cd && cd->cd_set_level) + cd->cd_set_level(cd->cd_cookie, &cells[1], level); +} + + +void +cmap_deactivate(struct thermal_zone *tz, struct cmap *cm) +{ + struct cdev *cd; + uint32_t *cdev; + + if (cm == NULL) + return; + + cdev = cm->cm_cdev; + while (cdev && cdev < cm->cm_cdevend) { + LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) { + if (cd->cd_phandle == cdev[0]) + break; + } + KASSERT(cd != NULL); + cd->cd_active = 0; + cdev = cdev_next_cdev(cdev); + } +} + +void +cmap_activate(struct thermal_zone *tz, struct cmap *cm, int32_t delta) +{ + struct cdev *cd; + uint32_t *cdev; + int32_t min, max; + + if (cm == NULL) + return; + + cdev = cm->cm_cdev; + while (cdev && cdev < cm->cm_cdevend) { + LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) { + if (cd->cd_phandle == cdev[0]) + break; + } + KASSERT(cd != NULL); + + min = (cdev[1] == THERMAL_NO_LIMIT) ? 0 : cdev[1]; + max = (cdev[2] == THERMAL_NO_LIMIT) ? INT32_MAX : cdev[2]; + + cd->cd_active = 1; + cd->cd_level = cdev_get_level(cdev) + delta; + cd->cd_level = MAX(cd->cd_level, min); + cd->cd_level = MIN(cd->cd_level, max); + cdev_set_level(cdev, cd->cd_level); + cdev = cdev_next_cdev(cdev); + } +} + +void +cmap_finish(struct thermal_zone *tz) +{ + struct cdev *cd; + + LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) { + if (cd->cd_active == 0 && cd->cd_level != 0) { + cdev_set_level(&cd->cd_phandle, 0); + cd->cd_level = 0; + } + } +} + +void +thermal_zone_poll(void *arg) +{ + struct thermal_zone *tz = arg; + struct trippoint *tp, *newtp; + struct cmap *cm, *newcm; + uint32_t polling_delay; + int32_t temp, delta; + int i; + + temp = thermal_get_temperature_cells(tz->tz_sensors); + if (temp == THERMAL_SENSOR_MAX) + return; + + newtp = NULL; + tp = tz->tz_trips; + for (i = 0; i < tz->tz_ntrips; i++) { + if (temp < tp->tp_temperature && tp != tz->tz_tp) + break; + if (temp < tp->tp_temperature - tp->tp_hysteresis) + break; + newtp = tp++; + } + + /* Short circuit if we didn't hit a trip point. */ + if (newtp == NULL && tz->tz_tp == NULL) + goto out; + + /* + * If the current tenperature is above the trip temperature: + * - increase the cooling level if the temperature is rising + * - do nothing if the temperature is falling + * If the current temperature is below the trip tenmperature: + * - do nothing if the temperature is rising + * - decreate the cooling level if the temperature is falling + */ + delta = 0; + if (newtp) { + if (temp >= newtp->tp_temperature) { + if (temp > tz->tz_temperature) + delta = 1; + } else { + if (temp < tz->tz_temperature) + delta = -1; + } + } + + newcm = NULL; + cm = tz->tz_cmaps; + for (i = 0; i < tz->tz_ncmaps; i++) { + if (newtp && cm->cm_trip == newtp->tp_phandle) { + newcm = cm; + break; + } + cm++; + } + + cmap_deactivate(tz, tz->tz_cm); + cmap_activate(tz, newcm, delta); + cmap_finish(tz); + + tz->tz_tp = newtp; + tz->tz_cm = newcm; + +out: + tz->tz_temperature = temp; + if (tz->tz_tp && tz->tz_tp->tp_type == THERMAL_PASSIVE) + polling_delay = tz->tz_polling_delay_passive; + else + polling_delay = tz->tz_polling_delay; + timeout_add_msec(&tz->tz_poll_to, polling_delay); +} + +void +thermal_zone_init(int node) +{ + struct thermal_zone *tz; + struct trippoint *tp; + struct cmap *cm; + struct cdev *cd; + int len, i; + + len = OF_getproplen(node, "thermal-sensors"); + if (len <= 0) + return; + + tz = malloc(sizeof(struct thermal_zone), M_DEVBUF, M_ZERO | M_WAITOK); + tz->tz_node = node; + + OF_getprop(node, "name", &tz->tz_name, sizeof(tz->tz_name)); + tz->tz_name[sizeof(tz->tz_name) - 1] = 0; + tz->tz_sensors = malloc(len, M_DEVBUF, M_WAITOK); + OF_getpropintarray(node, "thermal-sensors", tz->tz_sensors, len); + tz->tz_polling_delay = OF_getpropint(node, "polling-delay", 0); + tz->tz_polling_delay_passive = + OF_getpropint(node, "polling-delay-passive", tz->tz_polling_delay); + + task_set(&tz->tz_poll_task, thermal_zone_poll, tz); + timeout_set(&tz->tz_poll_to, thermal_zone_poll_timeout, tz); + + /* + * Trip points for this thermal zone. + */ + node = OF_getnodebyname(tz->tz_node, "trips"); + for (node = OF_child(node); node != 0; node = OF_peer(node)) + tz->tz_ntrips++; + + tz->tz_trips = mallocarray(tz->tz_ntrips, sizeof(struct trippoint), + M_DEVBUF, M_ZERO | M_WAITOK); + tp = tz->tz_trips; + + node = OF_getnodebyname(tz->tz_node, "trips"); + for (node = OF_child(node); node != 0; node = OF_peer(node)) { + char type[32] = "none"; + + tp->tp_temperature = + OF_getpropint(node, "temperature", THERMAL_SENSOR_MAX); + tp->tp_hysteresis = OF_getpropint(node, "hysteresis", 0); + OF_getprop(node, "type", type, sizeof(type)); + if (strcmp(type, "active") == 0) + tp->tp_type = THERMAL_ACTIVE; + else if (strcmp(type, "passive") == 0) + tp->tp_type = THERMAL_PASSIVE; + else if (strcmp(type, "hot") == 0) + tp->tp_type = THERMAL_HOT; + else if (strcmp(type, "critical") == 0) + tp->tp_type = THERMAL_CRITICAL; + tp->tp_phandle = OF_getpropint(node, "phandle", 0); + tp++; + } + + /* + * Cooling maps for this thermal zone. + */ + node = OF_getnodebyname(tz->tz_node, "cooling-maps"); + for (node = OF_child(node); node != 0; node = OF_peer(node)) + tz->tz_ncmaps++; + + tz->tz_cmaps = mallocarray(tz->tz_ncmaps, sizeof(struct cmap), + M_DEVBUF, M_ZERO | M_WAITOK); + cm = tz->tz_cmaps; + + node = OF_getnodebyname(tz->tz_node, "cooling-maps"); + for (node = OF_child(node); node != 0; node = OF_peer(node)) { + len = OF_getproplen(node, "cooling-device"); + if (len <= 0) + continue; + cm->cm_cdev = malloc(len, M_DEVBUF, M_ZERO | M_WAITOK); + OF_getpropintarray(node, "cooling-device", cm->cm_cdev, len); + cm->cm_cdevend = cm->cm_cdev + len / sizeof(uint32_t); + cm->cm_trip = OF_getpropint(node, "trip", 0); + cm++; + } + + /* + * Create a list of all the possible cooling devices from the + * cooling maps for this thermal zone, and initialize their + * state. + */ + LIST_INIT(&tz->tz_cdevs); + cm = tz->tz_cmaps; + for (i = 0; i < tz->tz_ncmaps; i++) { + uint32_t *cdev; + + cdev = cm->cm_cdev; + while (cdev && cdev < cm->cm_cdevend) { + LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) { + if (cd->cd_phandle == cdev[0]) + break; + } + if (cd == NULL) { + cd = malloc(sizeof(struct cdev), M_DEVBUF, + M_ZERO | M_WAITOK); + cd->cd_phandle = cdev[0]; + cd->cd_level = 0; + cd->cd_active = 0; + LIST_INSERT_HEAD(&tz->tz_cdevs, cd, cd_list); + } + cdev = cdev_next_cdev(cdev); + } + cm++; + } + + /* Start polling if we are requested to do so. */ + if (tz->tz_polling_delay > 0) + timeout_add_msec(&tz->tz_poll_to, tz->tz_polling_delay); +} + +void +thermal_init(void) +{ + int node = OF_finddevice("/thermal-zones"); + + if (node == 0) + return; + + tztq = taskq_create("tztq", 1, IPL_NONE, 0); + + for (node = OF_child(node); node != 0; node = OF_peer(node)) + thermal_zone_init(node); +} Index: dev/ofw/ofw_thermal.h =================================================================== RCS file: dev/ofw/ofw_thermal.h diff -N dev/ofw/ofw_thermal.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ dev/ofw/ofw_thermal.h 29 Jun 2019 09:38:38 -0000 @@ -0,0 +1,53 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2019 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _DEV_OFW_THERMAL_H_ +#define _DEV_OFW_THERMAL_H_ + +struct thermal_sensor { + int ts_node; + void *ts_cookie; + + int32_t (*ts_get_temperature)(void *, uint32_t *); + + LIST_ENTRY(thermal_sensor) ts_list; + uint32_t ts_phandle; + uint32_t ts_cells; +}; + +#define THERMAL_SENSOR_MAX 0xffffffffU + +struct cooling_device { + int cd_node; + void *cd_cookie; + + uint32_t (*cd_get_level)(void *, uint32_t *); + void (*cd_set_level)(void *, uint32_t *, uint32_t); + + LIST_ENTRY(cooling_device) cd_list; + uint32_t cd_phandle; + uint32_t cd_cells; +}; + +#define THERMAL_NO_LIMIT 0xffffffffU + +void thermal_sensor_register(struct thermal_sensor *); +void cooling_device_register(struct cooling_device *); + +void thermal_init(void); + +#endif /* _DEV_OFW_THERMAL_H_ */