xref: /openbsd-src/sys/dev/ofw/ofw_thermal.c (revision 0d80ee7110d08a1ab6df3e89cadcfc52380f4c85)
1*0d80ee71Skettenis /*	$OpenBSD: ofw_thermal.c,v 1.10 2024/07/01 14:13:43 kettenis Exp $	*/
2b4264e16Skettenis /*
3b4264e16Skettenis  * Copyright (c) 2019 Mark Kettenis
4b4264e16Skettenis  *
5b4264e16Skettenis  * Permission to use, copy, modify, and distribute this software for any
6b4264e16Skettenis  * purpose with or without fee is hereby granted, provided that the above
7b4264e16Skettenis  * copyright notice and this permission notice appear in all copies.
8b4264e16Skettenis  *
9b4264e16Skettenis  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10b4264e16Skettenis  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11b4264e16Skettenis  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12b4264e16Skettenis  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13b4264e16Skettenis  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14b4264e16Skettenis  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15b4264e16Skettenis  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16b4264e16Skettenis  */
17b4264e16Skettenis 
18dfd7428cSdlg #include "kstat.h"
19dfd7428cSdlg 
20b4264e16Skettenis #include <sys/types.h>
21b4264e16Skettenis #include <sys/systm.h>
22b4264e16Skettenis #include <sys/malloc.h>
23b4264e16Skettenis #include <sys/stdint.h>
24b4264e16Skettenis #include <sys/task.h>
25b4264e16Skettenis #include <sys/timeout.h>
26dfd7428cSdlg #include <sys/sched.h>
27dfd7428cSdlg #include <sys/kstat.h>
28b4264e16Skettenis 
29b4264e16Skettenis #include <machine/bus.h>
30b4264e16Skettenis 
31b4264e16Skettenis #include <dev/ofw/openfirm.h>
32b4264e16Skettenis #include <dev/ofw/ofw_thermal.h>
33b4264e16Skettenis 
34b4264e16Skettenis LIST_HEAD(, thermal_sensor) thermal_sensors =
35b4264e16Skettenis         LIST_HEAD_INITIALIZER(thermal_sensors);
36b4264e16Skettenis 
37b4264e16Skettenis LIST_HEAD(, cooling_device) cooling_devices =
38b4264e16Skettenis         LIST_HEAD_INITIALIZER(cooling_devices);
39b4264e16Skettenis 
40b4264e16Skettenis struct taskq *tztq;
41b4264e16Skettenis 
42b4264e16Skettenis struct trippoint {
43dfd7428cSdlg 	int		tp_node;
44b4264e16Skettenis 	int32_t		tp_temperature;
45b4264e16Skettenis 	uint32_t	tp_hysteresis;
46b4264e16Skettenis 	int		tp_type;
47b4264e16Skettenis 	uint32_t	tp_phandle;
48b4264e16Skettenis };
49b4264e16Skettenis 
50b4264e16Skettenis #define THERMAL_NONE		0
51b4264e16Skettenis #define THERMAL_ACTIVE		1
52b4264e16Skettenis #define THERMAL_PASSIVE		2
53b4264e16Skettenis #define THERMAL_HOT		3
54b4264e16Skettenis #define THERMAL_CRITICAL	4
55b4264e16Skettenis 
56dfd7428cSdlg static const char *trip_types[] = {
57dfd7428cSdlg 	[THERMAL_NONE]		= "none",
58dfd7428cSdlg 	[THERMAL_ACTIVE]	= "active",
59dfd7428cSdlg 	[THERMAL_PASSIVE]	= "passive",
60dfd7428cSdlg 	[THERMAL_HOT]		= "hot",
61dfd7428cSdlg 	[THERMAL_CRITICAL]	= "critical",
62dfd7428cSdlg };
63dfd7428cSdlg 
64b4264e16Skettenis struct cmap {
65b4264e16Skettenis 	uint32_t	*cm_cdev;
66b4264e16Skettenis 	uint32_t	*cm_cdevend;
67b4264e16Skettenis 	uint32_t	cm_trip;
68b4264e16Skettenis };
69b4264e16Skettenis 
70b4264e16Skettenis struct cdev {
71b4264e16Skettenis 	uint32_t	cd_phandle;
72b4264e16Skettenis 	int32_t		cd_level;
73b4264e16Skettenis 	int		cd_active;
74b4264e16Skettenis 	LIST_ENTRY(cdev) cd_list;
75b4264e16Skettenis };
76b4264e16Skettenis 
77b4264e16Skettenis struct thermal_zone {
78b4264e16Skettenis 	int		tz_node;
79b4264e16Skettenis 	char		tz_name[64];
80b4264e16Skettenis 	struct task	tz_poll_task;
81b4264e16Skettenis 	struct timeout	tz_poll_to;
82b4264e16Skettenis 	uint32_t	*tz_sensors;
83b4264e16Skettenis 	uint32_t	tz_polling_delay;
84b4264e16Skettenis 	uint32_t	tz_polling_delay_passive;
85d89d806fSkettenis 	LIST_ENTRY(thermal_zone) tz_list;
86b4264e16Skettenis 
87b4264e16Skettenis 	struct trippoint *tz_trips;
88b4264e16Skettenis 	int		tz_ntrips;
89b4264e16Skettenis 	struct trippoint *tz_tp;
90b4264e16Skettenis 
91b4264e16Skettenis 	struct cmap	*tz_cmaps;
92b4264e16Skettenis 	int		tz_ncmaps;
93b4264e16Skettenis 	struct cmap	*tz_cm;
94b4264e16Skettenis 
95b4264e16Skettenis 	LIST_HEAD(, cdev) tz_cdevs;
96b4264e16Skettenis 
97b4264e16Skettenis 	int32_t		tz_temperature;
98dfd7428cSdlg 
99dfd7428cSdlg 	struct rwlock	tz_lock;
100dfd7428cSdlg 	struct kstat	*tz_kstat;
101b4264e16Skettenis };
102b4264e16Skettenis 
103dfd7428cSdlg #if NKSTAT > 0
104dfd7428cSdlg static void	thermal_zone_kstat_attach(struct thermal_zone *);
105dfd7428cSdlg static void	thermal_zone_kstat_update(struct thermal_zone *);
106dfd7428cSdlg #endif /* NKSTAT > 0 */
107dfd7428cSdlg 
108d89d806fSkettenis LIST_HEAD(, thermal_zone) thermal_zones =
109d89d806fSkettenis 	LIST_HEAD_INITIALIZER(thermal_zones);
110d89d806fSkettenis 
111b4264e16Skettenis void
thermal_sensor_register(struct thermal_sensor * ts)112b4264e16Skettenis thermal_sensor_register(struct thermal_sensor *ts)
113b4264e16Skettenis {
114b4264e16Skettenis 	ts->ts_cells = OF_getpropint(ts->ts_node, "#thermal-sensor-cells", 0);
115b4264e16Skettenis 	ts->ts_phandle = OF_getpropint(ts->ts_node, "phandle", 0);
116b4264e16Skettenis 	if (ts->ts_phandle == 0)
117b4264e16Skettenis 		return;
118b4264e16Skettenis 
119b4264e16Skettenis 	LIST_INSERT_HEAD(&thermal_sensors, ts, ts_list);
120b4264e16Skettenis }
121b4264e16Skettenis 
122b4264e16Skettenis void
thermal_sensor_update(struct thermal_sensor * ts,uint32_t * cells)123d89d806fSkettenis thermal_sensor_update(struct thermal_sensor *ts, uint32_t *cells)
124d89d806fSkettenis {
125d89d806fSkettenis 	struct thermal_zone *tz;
126d89d806fSkettenis 
127d89d806fSkettenis 	LIST_FOREACH(tz, &thermal_zones, tz_list) {
128d89d806fSkettenis 		if (tz->tz_sensors[0] == ts->ts_phandle &&
129d89d806fSkettenis 		    memcmp(&tz->tz_sensors[1], cells,
130d89d806fSkettenis 		    ts->ts_cells * sizeof(uint32_t)) == 0)
131d89d806fSkettenis 			task_add(tztq, &tz->tz_poll_task);
132d89d806fSkettenis 	}
133d89d806fSkettenis }
134d89d806fSkettenis 
135d89d806fSkettenis void
cooling_device_register(struct cooling_device * cd)136b4264e16Skettenis cooling_device_register(struct cooling_device *cd)
137b4264e16Skettenis {
138b4264e16Skettenis 	cd->cd_cells = OF_getpropint(cd->cd_node, "#cooling-cells", 0);
139b4264e16Skettenis 	cd->cd_phandle = OF_getpropint(cd->cd_node, "phandle", 0);
140b4264e16Skettenis 	if (cd->cd_phandle == 0)
141b4264e16Skettenis 		return;
142b4264e16Skettenis 
143b4264e16Skettenis 	LIST_INSERT_HEAD(&cooling_devices, cd, cd_list);
144b4264e16Skettenis }
145b4264e16Skettenis 
146b4264e16Skettenis int32_t
thermal_get_temperature_cells(uint32_t * cells)147b4264e16Skettenis thermal_get_temperature_cells(uint32_t *cells)
148b4264e16Skettenis {
149b4264e16Skettenis 	struct thermal_sensor *ts;
150b4264e16Skettenis 	uint32_t phandle = cells[0];
151b4264e16Skettenis 
152b4264e16Skettenis 	LIST_FOREACH(ts, &thermal_sensors, ts_list) {
153b4264e16Skettenis 		if (ts->ts_phandle == phandle)
154b4264e16Skettenis 			break;
155b4264e16Skettenis 	}
156b4264e16Skettenis 
157b4264e16Skettenis 	if (ts && ts->ts_get_temperature)
158b4264e16Skettenis 		return ts->ts_get_temperature(ts->ts_cookie, &cells[1]);
159b4264e16Skettenis 
160b4264e16Skettenis 	return THERMAL_SENSOR_MAX;
161b4264e16Skettenis }
162b4264e16Skettenis 
16368910761Skettenis int
thermal_set_limit_cells(uint32_t * cells,uint32_t temp)16468910761Skettenis thermal_set_limit_cells(uint32_t *cells, uint32_t temp)
16568910761Skettenis {
16668910761Skettenis 	struct thermal_sensor *ts;
16768910761Skettenis 	uint32_t phandle = cells[0];
16868910761Skettenis 
16968910761Skettenis 	LIST_FOREACH(ts, &thermal_sensors, ts_list) {
17068910761Skettenis 		if (ts->ts_phandle == phandle)
17168910761Skettenis 			break;
17268910761Skettenis 	}
17368910761Skettenis 
17468910761Skettenis 	if (ts && ts->ts_set_limit)
17568910761Skettenis 		return ts->ts_set_limit(ts->ts_cookie, &cells[1], temp);
17668910761Skettenis 
17768910761Skettenis 	return ENXIO;
17868910761Skettenis }
17968910761Skettenis 
180b4264e16Skettenis void
thermal_zone_poll_timeout(void * arg)181b4264e16Skettenis thermal_zone_poll_timeout(void *arg)
182b4264e16Skettenis {
183b4264e16Skettenis 	struct thermal_zone *tz = arg;
184b4264e16Skettenis 
185b4264e16Skettenis 	task_add(tztq, &tz->tz_poll_task);
186b4264e16Skettenis }
187b4264e16Skettenis 
188b4264e16Skettenis uint32_t *
cdev_next_cdev(uint32_t * cells)189b4264e16Skettenis cdev_next_cdev(uint32_t *cells)
190b4264e16Skettenis {
191b4264e16Skettenis 	uint32_t phandle = cells[0];
192b4264e16Skettenis 	int node, ncells;
193b4264e16Skettenis 
194b4264e16Skettenis 	node = OF_getnodebyphandle(phandle);
195b4264e16Skettenis 	if (node == 0)
196b4264e16Skettenis 		return NULL;
197b4264e16Skettenis 
198b4264e16Skettenis 	ncells = OF_getpropint(node, "#cooling-cells", 2);
199b4264e16Skettenis 	return cells + ncells + 1;
200b4264e16Skettenis }
201b4264e16Skettenis 
202b4264e16Skettenis uint32_t
cdev_get_level(uint32_t * cells)203b4264e16Skettenis cdev_get_level(uint32_t *cells)
204b4264e16Skettenis {
205b4264e16Skettenis 	struct cooling_device *cd;
206b4264e16Skettenis 	uint32_t phandle = cells[0];
207b4264e16Skettenis 
208b4264e16Skettenis 	LIST_FOREACH(cd, &cooling_devices, cd_list) {
209b4264e16Skettenis 		if (cd->cd_phandle == phandle)
210b4264e16Skettenis 			break;
211b4264e16Skettenis 	}
212b4264e16Skettenis 
213b4264e16Skettenis 	if (cd && cd->cd_get_level)
214b4264e16Skettenis 		return cd->cd_get_level(cd->cd_cookie, &cells[1]);
215b4264e16Skettenis 
216b4264e16Skettenis 	return 0;
217b4264e16Skettenis }
218b4264e16Skettenis 
219b4264e16Skettenis void
cdev_set_level(uint32_t * cells,uint32_t level)220b4264e16Skettenis cdev_set_level(uint32_t *cells, uint32_t level)
221b4264e16Skettenis {
222b4264e16Skettenis 	struct cooling_device *cd;
223b4264e16Skettenis 	uint32_t phandle = cells[0];
224b4264e16Skettenis 
225b4264e16Skettenis 	LIST_FOREACH(cd, &cooling_devices, cd_list) {
226b4264e16Skettenis 		if (cd->cd_phandle == phandle)
227b4264e16Skettenis 			break;
228b4264e16Skettenis 	}
229b4264e16Skettenis 
230b4264e16Skettenis 	if (cd && cd->cd_set_level)
231b4264e16Skettenis 		cd->cd_set_level(cd->cd_cookie, &cells[1], level);
232b4264e16Skettenis }
233b4264e16Skettenis 
234b4264e16Skettenis 
235b4264e16Skettenis void
cmap_deactivate(struct thermal_zone * tz,struct cmap * cm)236b4264e16Skettenis cmap_deactivate(struct thermal_zone *tz, struct cmap *cm)
237b4264e16Skettenis {
238b4264e16Skettenis 	struct cdev *cd;
239b4264e16Skettenis 	uint32_t *cdev;
240b4264e16Skettenis 
241b4264e16Skettenis 	if (cm == NULL)
242b4264e16Skettenis 		return;
243b4264e16Skettenis 
244b4264e16Skettenis 	cdev = cm->cm_cdev;
245b4264e16Skettenis 	while (cdev && cdev < cm->cm_cdevend) {
246b4264e16Skettenis 		LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
247b4264e16Skettenis 			if (cd->cd_phandle == cdev[0])
248b4264e16Skettenis 				break;
249b4264e16Skettenis 		}
250b4264e16Skettenis 		KASSERT(cd != NULL);
251b4264e16Skettenis 		cd->cd_active = 0;
252b4264e16Skettenis 		cdev = cdev_next_cdev(cdev);
253b4264e16Skettenis 	}
254b4264e16Skettenis }
255b4264e16Skettenis 
256b4264e16Skettenis void
cmap_activate(struct thermal_zone * tz,struct cmap * cm,int32_t delta)257b4264e16Skettenis cmap_activate(struct thermal_zone *tz, struct cmap *cm, int32_t delta)
258b4264e16Skettenis {
259b4264e16Skettenis 	struct cdev *cd;
260b4264e16Skettenis 	uint32_t *cdev;
261b4264e16Skettenis 	int32_t min, max;
262b4264e16Skettenis 
263b4264e16Skettenis 	if (cm == NULL)
264b4264e16Skettenis 		return;
265b4264e16Skettenis 
266b4264e16Skettenis 	cdev = cm->cm_cdev;
267b4264e16Skettenis 	while (cdev && cdev < cm->cm_cdevend) {
268b4264e16Skettenis 		LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
269b4264e16Skettenis 			if (cd->cd_phandle == cdev[0])
270b4264e16Skettenis 				break;
271b4264e16Skettenis 		}
272b4264e16Skettenis 		KASSERT(cd != NULL);
273b4264e16Skettenis 
274b4264e16Skettenis 		min = (cdev[1] == THERMAL_NO_LIMIT) ? 0 : cdev[1];
275b4264e16Skettenis 		max = (cdev[2] == THERMAL_NO_LIMIT) ? INT32_MAX : cdev[2];
276b4264e16Skettenis 
277b4264e16Skettenis 		cd->cd_active = 1;
278b4264e16Skettenis 		cd->cd_level = cdev_get_level(cdev) + delta;
279b4264e16Skettenis 		cd->cd_level = MAX(cd->cd_level, min);
280b4264e16Skettenis 		cd->cd_level = MIN(cd->cd_level, max);
281b4264e16Skettenis 		cdev_set_level(cdev, cd->cd_level);
282b4264e16Skettenis 		cdev = cdev_next_cdev(cdev);
283b4264e16Skettenis 	}
284b4264e16Skettenis }
285b4264e16Skettenis 
286b4264e16Skettenis void
cmap_finish(struct thermal_zone * tz)287b4264e16Skettenis cmap_finish(struct thermal_zone *tz)
288b4264e16Skettenis {
289b4264e16Skettenis 	struct cdev *cd;
290b4264e16Skettenis 
291b4264e16Skettenis 	LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
292b4264e16Skettenis 		if (cd->cd_active == 0 && cd->cd_level != 0) {
293b4264e16Skettenis 			cdev_set_level(&cd->cd_phandle, 0);
294b4264e16Skettenis 			cd->cd_level = 0;
295b4264e16Skettenis 		}
296b4264e16Skettenis 	}
297b4264e16Skettenis }
298b4264e16Skettenis 
299b4264e16Skettenis void
thermal_zone_poll(void * arg)300b4264e16Skettenis thermal_zone_poll(void *arg)
301b4264e16Skettenis {
302b4264e16Skettenis 	struct thermal_zone *tz = arg;
303b4264e16Skettenis 	struct trippoint *tp, *newtp;
304b4264e16Skettenis 	struct cmap *cm, *newcm;
305b4264e16Skettenis 	uint32_t polling_delay;
306b4264e16Skettenis 	int32_t temp, delta;
307b4264e16Skettenis 	int i;
308b4264e16Skettenis 
309*0d80ee71Skettenis 	tp = tz->tz_trips;
310b4264e16Skettenis 	temp = thermal_get_temperature_cells(tz->tz_sensors);
311b4264e16Skettenis 	if (temp == THERMAL_SENSOR_MAX)
31216138881Skettenis 		goto out;
313b4264e16Skettenis 
314b4264e16Skettenis 	newtp = NULL;
315b4264e16Skettenis 	for (i = 0; i < tz->tz_ntrips; i++) {
316b4264e16Skettenis 		if (temp < tp->tp_temperature && tp != tz->tz_tp)
317b4264e16Skettenis 			break;
318b4264e16Skettenis 		if (temp < tp->tp_temperature - tp->tp_hysteresis)
319b4264e16Skettenis 			break;
320b4264e16Skettenis 		newtp = tp++;
321b4264e16Skettenis 	}
322b4264e16Skettenis 
323b4264e16Skettenis 	/* Short circuit if we didn't hit a trip point. */
324b4264e16Skettenis 	if (newtp == NULL && tz->tz_tp == NULL)
325b4264e16Skettenis 		goto out;
326b4264e16Skettenis 
327b4264e16Skettenis 	/*
328b4264e16Skettenis 	 * If the current temperature is above the trip temperature:
329b4264e16Skettenis 	 *  - increase the cooling level if the temperature is rising
330b4264e16Skettenis 	 *  - do nothing if the temperature is falling
331b4264e16Skettenis 	 * If the current temperature is below the trip temperature:
332b4264e16Skettenis 	 *  - do nothing if the temperature is rising
333b4264e16Skettenis 	 *  - decrease the cooling level if the temperature is falling
334b4264e16Skettenis 	 */
335b4264e16Skettenis 	delta = 0;
33616138881Skettenis 	if (newtp && tz->tz_temperature != THERMAL_SENSOR_MAX) {
337b4264e16Skettenis 		if (temp >= newtp->tp_temperature) {
338b4264e16Skettenis 			if (temp > tz->tz_temperature)
339b4264e16Skettenis 				delta = 1;
340b4264e16Skettenis 		} else {
341b4264e16Skettenis 			if (temp < tz->tz_temperature)
342b4264e16Skettenis 				delta = -1;
343b4264e16Skettenis 		}
344b4264e16Skettenis 	}
345b4264e16Skettenis 
346b4264e16Skettenis 	newcm = NULL;
347b4264e16Skettenis 	cm = tz->tz_cmaps;
348b4264e16Skettenis 	for (i = 0; i < tz->tz_ncmaps; i++) {
349b4264e16Skettenis 		if (newtp && cm->cm_trip == newtp->tp_phandle) {
350b4264e16Skettenis 			newcm = cm;
351b4264e16Skettenis 			break;
352b4264e16Skettenis 		}
353b4264e16Skettenis 		cm++;
354b4264e16Skettenis 	}
355b4264e16Skettenis 
356b4264e16Skettenis 	cmap_deactivate(tz, tz->tz_cm);
357b4264e16Skettenis 	cmap_activate(tz, newcm, delta);
358b4264e16Skettenis 	cmap_finish(tz);
359b4264e16Skettenis 
360b4264e16Skettenis 	tz->tz_tp = newtp;
361b4264e16Skettenis 	tz->tz_cm = newcm;
362b4264e16Skettenis 
363b4264e16Skettenis out:
364b4264e16Skettenis 	tz->tz_temperature = temp;
365dfd7428cSdlg #if NKSTAT > 0
366dfd7428cSdlg 	thermal_zone_kstat_update(tz);
367dfd7428cSdlg #endif
368b4264e16Skettenis 	if (tz->tz_tp && tz->tz_tp->tp_type == THERMAL_PASSIVE)
369b4264e16Skettenis 		polling_delay = tz->tz_polling_delay_passive;
370b4264e16Skettenis 	else
371b4264e16Skettenis 		polling_delay = tz->tz_polling_delay;
37268910761Skettenis 
37368910761Skettenis 	if (polling_delay > 0)
374b4264e16Skettenis 		timeout_add_msec(&tz->tz_poll_to, polling_delay);
375*0d80ee71Skettenis 	else if (tp)
37668910761Skettenis 		thermal_set_limit_cells(tz->tz_sensors, tp->tp_temperature);
377b4264e16Skettenis }
378b4264e16Skettenis 
379dfd7428cSdlg static int
thermal_zone_triptype(const char * prop)380dfd7428cSdlg thermal_zone_triptype(const char *prop)
381dfd7428cSdlg {
382dfd7428cSdlg 	size_t i;
383dfd7428cSdlg 
384dfd7428cSdlg 	for (i = 0; i < nitems(trip_types); i++) {
385dfd7428cSdlg 		const char *name = trip_types[i];
386dfd7428cSdlg 		if (name == NULL)
387dfd7428cSdlg 			continue;
388dfd7428cSdlg 
389dfd7428cSdlg 		if (strcmp(name, prop) == 0)
390dfd7428cSdlg 			return (i);
391dfd7428cSdlg 	}
392dfd7428cSdlg 
393dfd7428cSdlg 	return (THERMAL_NONE);
394dfd7428cSdlg }
395dfd7428cSdlg 
396b4264e16Skettenis void
thermal_zone_init(int node)397b4264e16Skettenis thermal_zone_init(int node)
398b4264e16Skettenis {
399b4264e16Skettenis 	struct thermal_zone *tz;
400b4264e16Skettenis 	struct trippoint *tp;
401b4264e16Skettenis 	struct cmap *cm;
402b4264e16Skettenis 	struct cdev *cd;
403b4264e16Skettenis 	int len, i;
404b4264e16Skettenis 
405b4264e16Skettenis 	len = OF_getproplen(node, "thermal-sensors");
406b4264e16Skettenis 	if (len <= 0)
407b4264e16Skettenis 		return;
408b4264e16Skettenis 
409fc1dafb7Skettenis 	if (OF_getnodebyname(node, "trips") == 0)
410fc1dafb7Skettenis 		return;
411fc1dafb7Skettenis 	if (OF_getnodebyname(node, "cooling-maps") == 0)
412fc1dafb7Skettenis 		return;
413fc1dafb7Skettenis 
414b4264e16Skettenis 	tz = malloc(sizeof(struct thermal_zone), M_DEVBUF, M_ZERO | M_WAITOK);
415b4264e16Skettenis 	tz->tz_node = node;
416dfd7428cSdlg 	rw_init(&tz->tz_lock, "tzlk");
417b4264e16Skettenis 
418b4264e16Skettenis 	OF_getprop(node, "name", &tz->tz_name, sizeof(tz->tz_name));
419b4264e16Skettenis 	tz->tz_name[sizeof(tz->tz_name) - 1] = 0;
420b4264e16Skettenis 	tz->tz_sensors = malloc(len, M_DEVBUF, M_WAITOK);
421b4264e16Skettenis 	OF_getpropintarray(node, "thermal-sensors", tz->tz_sensors, len);
422b4264e16Skettenis 	tz->tz_polling_delay = OF_getpropint(node, "polling-delay", 0);
423b4264e16Skettenis 	tz->tz_polling_delay_passive =
424b4264e16Skettenis 	    OF_getpropint(node, "polling-delay-passive", tz->tz_polling_delay);
425b4264e16Skettenis 
426b4264e16Skettenis 	task_set(&tz->tz_poll_task, thermal_zone_poll, tz);
427b4264e16Skettenis 	timeout_set(&tz->tz_poll_to, thermal_zone_poll_timeout, tz);
428b4264e16Skettenis 
429b4264e16Skettenis 	/*
430b4264e16Skettenis 	 * Trip points for this thermal zone.
431b4264e16Skettenis 	 */
432b4264e16Skettenis 	node = OF_getnodebyname(tz->tz_node, "trips");
433b4264e16Skettenis 	for (node = OF_child(node); node != 0; node = OF_peer(node))
434b4264e16Skettenis 		tz->tz_ntrips++;
435b4264e16Skettenis 
436b4264e16Skettenis 	tz->tz_trips = mallocarray(tz->tz_ntrips, sizeof(struct trippoint),
437b4264e16Skettenis 	    M_DEVBUF, M_ZERO | M_WAITOK);
438b4264e16Skettenis 
439b4264e16Skettenis 	node = OF_getnodebyname(tz->tz_node, "trips");
440b4264e16Skettenis 	for (node = OF_child(node); node != 0; node = OF_peer(node)) {
441b4264e16Skettenis 		char type[32] = "none";
442fe6c337eSpatrick 		int32_t temp;
443b4264e16Skettenis 
444fe6c337eSpatrick 		temp = OF_getpropint(node, "temperature", THERMAL_SENSOR_MAX);
445fe6c337eSpatrick 
446fe6c337eSpatrick 		/* Sorted insertion, since tree might not be */
447fe6c337eSpatrick 		for (i = 0; i < tz->tz_ntrips; i++) {
448fe6c337eSpatrick 			/* No trip point should be 0 degC, take it */
449fe6c337eSpatrick 			if (tz->tz_trips[i].tp_temperature == 0)
450fe6c337eSpatrick 				break;
451fe6c337eSpatrick 			/* We should be bigger than the one before us */
452fe6c337eSpatrick 			if (tz->tz_trips[i].tp_temperature < temp)
453fe6c337eSpatrick 				continue;
454fe6c337eSpatrick 			/* Free current slot */
455fe6c337eSpatrick 			memmove(&tz->tz_trips[i + 1], &tz->tz_trips[i],
456fe6c337eSpatrick 			    (tz->tz_ntrips - (i + 1)) * sizeof(*tp));
457fe6c337eSpatrick 			break;
458fe6c337eSpatrick 		}
459fe6c337eSpatrick 		tp = &tz->tz_trips[i];
460dfd7428cSdlg 		tp->tp_node = node;
461fe6c337eSpatrick 		tp->tp_temperature = temp;
462b4264e16Skettenis 		tp->tp_hysteresis = OF_getpropint(node, "hysteresis", 0);
463b4264e16Skettenis 		OF_getprop(node, "type", type, sizeof(type));
464dfd7428cSdlg 		tp->tp_type = thermal_zone_triptype(type);
465b4264e16Skettenis 		tp->tp_phandle = OF_getpropint(node, "phandle", 0);
466b4264e16Skettenis 		tp++;
467b4264e16Skettenis 	}
468b4264e16Skettenis 
469b4264e16Skettenis 	/*
470b4264e16Skettenis 	 * Cooling maps for this thermal zone.
471b4264e16Skettenis 	 */
472b4264e16Skettenis 	node = OF_getnodebyname(tz->tz_node, "cooling-maps");
473b4264e16Skettenis 	for (node = OF_child(node); node != 0; node = OF_peer(node))
474b4264e16Skettenis 		tz->tz_ncmaps++;
475b4264e16Skettenis 
476b4264e16Skettenis 	tz->tz_cmaps = mallocarray(tz->tz_ncmaps, sizeof(struct cmap),
477b4264e16Skettenis 	    M_DEVBUF, M_ZERO | M_WAITOK);
478b4264e16Skettenis 	cm = tz->tz_cmaps;
479b4264e16Skettenis 
480b4264e16Skettenis 	node = OF_getnodebyname(tz->tz_node, "cooling-maps");
481b4264e16Skettenis 	for (node = OF_child(node); node != 0; node = OF_peer(node)) {
482b4264e16Skettenis 		len = OF_getproplen(node, "cooling-device");
483b4264e16Skettenis 		if (len <= 0)
484b4264e16Skettenis 			continue;
485b4264e16Skettenis 		cm->cm_cdev = malloc(len, M_DEVBUF, M_ZERO | M_WAITOK);
486b4264e16Skettenis 		OF_getpropintarray(node, "cooling-device", cm->cm_cdev, len);
487b4264e16Skettenis 		cm->cm_cdevend = cm->cm_cdev + len / sizeof(uint32_t);
488b4264e16Skettenis 		cm->cm_trip = OF_getpropint(node, "trip", 0);
489b4264e16Skettenis 		cm++;
490b4264e16Skettenis 	}
491b4264e16Skettenis 
492b4264e16Skettenis 	/*
493b4264e16Skettenis 	 * Create a list of all the possible cooling devices from the
494b4264e16Skettenis 	 * cooling maps for this thermal zone, and initialize their
495b4264e16Skettenis 	 * state.
496b4264e16Skettenis 	 */
497b4264e16Skettenis 	LIST_INIT(&tz->tz_cdevs);
498b4264e16Skettenis 	cm = tz->tz_cmaps;
499b4264e16Skettenis 	for (i = 0; i < tz->tz_ncmaps; i++) {
500b4264e16Skettenis 		uint32_t *cdev;
501b4264e16Skettenis 
502b4264e16Skettenis 		cdev = cm->cm_cdev;
503b4264e16Skettenis 		while (cdev && cdev < cm->cm_cdevend) {
504b4264e16Skettenis 			LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
505b4264e16Skettenis 				if (cd->cd_phandle == cdev[0])
506b4264e16Skettenis 					break;
507b4264e16Skettenis 			}
508b4264e16Skettenis 			if (cd == NULL) {
509b4264e16Skettenis 				cd = malloc(sizeof(struct cdev), M_DEVBUF,
510b4264e16Skettenis 				    M_ZERO | M_WAITOK);
511b4264e16Skettenis 				cd->cd_phandle = cdev[0];
512b4264e16Skettenis 				cd->cd_level = 0;
513b4264e16Skettenis 				cd->cd_active = 0;
514b4264e16Skettenis 				LIST_INSERT_HEAD(&tz->tz_cdevs, cd, cd_list);
515b4264e16Skettenis 			}
516b4264e16Skettenis 			cdev = cdev_next_cdev(cdev);
517b4264e16Skettenis 		}
518b4264e16Skettenis 		cm++;
519b4264e16Skettenis 	}
520b4264e16Skettenis 
521d89d806fSkettenis 	LIST_INSERT_HEAD(&thermal_zones, tz, tz_list);
522dfd7428cSdlg 
523dfd7428cSdlg #if NKSTAT > 0
524dfd7428cSdlg 	thermal_zone_kstat_attach(tz);
525dfd7428cSdlg #endif
52668910761Skettenis 
52768910761Skettenis 	/* Poll once to get things going. */
52868910761Skettenis 	thermal_zone_poll(tz);
529b4264e16Skettenis }
530b4264e16Skettenis 
531b4264e16Skettenis void
thermal_init(void)532b4264e16Skettenis thermal_init(void)
533b4264e16Skettenis {
534b4264e16Skettenis 	int node = OF_finddevice("/thermal-zones");
535b4264e16Skettenis 
53693d59f3fSkettenis 	if (node == -1)
537b4264e16Skettenis 		return;
538b4264e16Skettenis 
539951085b7Skettenis 	tztq = taskq_create("tztq", 1, IPL_SOFTCLOCK, 0);
540b4264e16Skettenis 
541b4264e16Skettenis 	for (node = OF_child(node); node != 0; node = OF_peer(node))
542b4264e16Skettenis 		thermal_zone_init(node);
543b4264e16Skettenis }
544dfd7428cSdlg 
545dfd7428cSdlg #if NKSTAT > 0
546dfd7428cSdlg 
547dfd7428cSdlg static const char *
thermal_zone_tripname(int type)548dfd7428cSdlg thermal_zone_tripname(int type)
549dfd7428cSdlg {
550dfd7428cSdlg 	if (type >= nitems(trip_types))
551dfd7428cSdlg 		return (NULL);
552dfd7428cSdlg 
553dfd7428cSdlg 	return (trip_types[type]);
554dfd7428cSdlg }
555dfd7428cSdlg 
556dfd7428cSdlg struct thermal_zone_kstats {
557dfd7428cSdlg 	struct kstat_kv		tzk_name; /* istr could be short */
558dfd7428cSdlg 	struct kstat_kv		tzk_temp;
559dfd7428cSdlg 	struct kstat_kv		tzk_tp;
560dfd7428cSdlg 	struct kstat_kv		tzk_tp_type;
561dfd7428cSdlg 	struct kstat_kv		tzk_cooling;
562dfd7428cSdlg };
563dfd7428cSdlg 
564dfd7428cSdlg static void
thermal_zone_kstat_update(struct thermal_zone * tz)565dfd7428cSdlg thermal_zone_kstat_update(struct thermal_zone *tz)
566dfd7428cSdlg {
567dfd7428cSdlg 	struct kstat *ks = tz->tz_kstat;
568dfd7428cSdlg 	struct thermal_zone_kstats *tzk;
569dfd7428cSdlg 
570dfd7428cSdlg 	if (ks == NULL)
571dfd7428cSdlg 		return;
572dfd7428cSdlg 
573dfd7428cSdlg 	tzk = ks->ks_data;
574dfd7428cSdlg 
575dfd7428cSdlg 	rw_enter_write(&tz->tz_lock);
576dfd7428cSdlg 	if (tz->tz_temperature == THERMAL_SENSOR_MAX)
577dfd7428cSdlg 		tzk->tzk_temp.kv_type = KSTAT_KV_T_NULL;
578dfd7428cSdlg 	else {
579dfd7428cSdlg 		tzk->tzk_temp.kv_type = KSTAT_KV_T_TEMP;
580dfd7428cSdlg 		kstat_kv_temp(&tzk->tzk_temp) = 273150000 +
581dfd7428cSdlg 		    1000 * tz->tz_temperature;
582dfd7428cSdlg 	}
583dfd7428cSdlg 
584dfd7428cSdlg 	if (tz->tz_tp == NULL) {
585dfd7428cSdlg 		kstat_kv_u32(&tzk->tzk_tp) = 0;
586dfd7428cSdlg 		strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), "none",
587dfd7428cSdlg 		    sizeof(kstat_kv_istr(&tzk->tzk_tp_type)));
588dfd7428cSdlg 	} else {
589dfd7428cSdlg 		int triptype = tz->tz_tp->tp_type;
590dfd7428cSdlg 		const char *tripname = thermal_zone_tripname(triptype);
591dfd7428cSdlg 
592dfd7428cSdlg 		kstat_kv_u32(&tzk->tzk_tp) = tz->tz_tp->tp_node;
593dfd7428cSdlg 
594dfd7428cSdlg 		if (tripname == NULL) {
595dfd7428cSdlg 			snprintf(kstat_kv_istr(&tzk->tzk_tp_type),
596dfd7428cSdlg 			    sizeof(kstat_kv_istr(&tzk->tzk_tp_type)),
597dfd7428cSdlg 			    "%u", triptype);
598dfd7428cSdlg 		} else {
599dfd7428cSdlg 			strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), tripname,
600dfd7428cSdlg 			    sizeof(kstat_kv_istr(&tzk->tzk_tp_type)));
601dfd7428cSdlg 		}
602dfd7428cSdlg 	}
603dfd7428cSdlg 
604dfd7428cSdlg 	kstat_kv_bool(&tzk->tzk_cooling) = (tz->tz_cm != NULL);
605dfd7428cSdlg 
606dfd7428cSdlg 	getnanouptime(&ks->ks_updated);
607dfd7428cSdlg 	rw_exit_write(&tz->tz_lock);
608dfd7428cSdlg }
609dfd7428cSdlg 
610dfd7428cSdlg static void
thermal_zone_kstat_attach(struct thermal_zone * tz)611dfd7428cSdlg thermal_zone_kstat_attach(struct thermal_zone *tz)
612dfd7428cSdlg {
613dfd7428cSdlg 	struct kstat *ks;
614dfd7428cSdlg 	struct thermal_zone_kstats *tzk;
615dfd7428cSdlg 	static unsigned int unit = 0;
616dfd7428cSdlg 
617dfd7428cSdlg 	ks = kstat_create("dt", 0, "thermal-zone", unit++, KSTAT_T_KV, 0);
618dfd7428cSdlg 	if (ks == NULL) {
619dfd7428cSdlg 		printf("unable to create thermal-zone kstats for %s",
620dfd7428cSdlg 		    tz->tz_name);
621dfd7428cSdlg 		return;
622dfd7428cSdlg 	}
623dfd7428cSdlg 
624dfd7428cSdlg 	tzk = malloc(sizeof(*tzk), M_DEVBUF, M_WAITOK|M_ZERO);
625dfd7428cSdlg 
626dfd7428cSdlg 	kstat_kv_init(&tzk->tzk_name, "name", KSTAT_KV_T_ISTR);
627dfd7428cSdlg 	strlcpy(kstat_kv_istr(&tzk->tzk_name), tz->tz_name,
628dfd7428cSdlg 	    sizeof(kstat_kv_istr(&tzk->tzk_name)));
629dfd7428cSdlg 	kstat_kv_init(&tzk->tzk_temp, "temperature", KSTAT_KV_T_NULL);
630dfd7428cSdlg 
631dfd7428cSdlg 	/* XXX dt node is not be the most useful info here. */
632dfd7428cSdlg 	kstat_kv_init(&tzk->tzk_tp, "trip-point-node", KSTAT_KV_T_UINT32);
633dfd7428cSdlg 	kstat_kv_init(&tzk->tzk_tp_type, "trip-type", KSTAT_KV_T_ISTR);
634dfd7428cSdlg 	strlcpy(kstat_kv_istr(&tzk->tzk_tp_type), "unknown",
635dfd7428cSdlg 	    sizeof(kstat_kv_istr(&tzk->tzk_tp_type)));
636dfd7428cSdlg 
637dfd7428cSdlg 	kstat_kv_init(&tzk->tzk_cooling, "active-cooling", KSTAT_KV_T_BOOL);
638dfd7428cSdlg 	kstat_kv_bool(&tzk->tzk_cooling) = 0;
639dfd7428cSdlg 
640dfd7428cSdlg 	ks->ks_softc = tz;
641dfd7428cSdlg 	ks->ks_data = tzk;
642dfd7428cSdlg 	ks->ks_datalen = sizeof(*tzk);
643dfd7428cSdlg 	ks->ks_read = kstat_read_nop;
644dfd7428cSdlg 	kstat_set_rlock(ks, &tz->tz_lock);
645dfd7428cSdlg 
646dfd7428cSdlg 	tz->tz_kstat = ks;
647dfd7428cSdlg 	kstat_install(ks);
648dfd7428cSdlg }
649dfd7428cSdlg #endif /* NKSTAT > 0 */
650