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