xref: /openbsd/sys/dev/ofw/ofw_thermal.c (revision d415bd75)
1 /*	$OpenBSD: ofw_thermal.c,v 1.7 2020/12/31 11:11:22 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 <sys/types.h>
19 #include <sys/systm.h>
20 #include <sys/malloc.h>
21 #include <sys/stdint.h>
22 #include <sys/task.h>
23 #include <sys/timeout.h>
24 
25 #include <machine/bus.h>
26 
27 #include <dev/ofw/openfirm.h>
28 #include <dev/ofw/ofw_thermal.h>
29 
30 LIST_HEAD(, thermal_sensor) thermal_sensors =
31         LIST_HEAD_INITIALIZER(thermal_sensors);
32 
33 LIST_HEAD(, cooling_device) cooling_devices =
34         LIST_HEAD_INITIALIZER(cooling_devices);
35 
36 struct taskq *tztq;
37 
38 struct trippoint {
39 	int32_t		tp_temperature;
40 	uint32_t	tp_hysteresis;
41 	int		tp_type;
42 	uint32_t	tp_phandle;
43 };
44 
45 #define THERMAL_NONE		0
46 #define THERMAL_ACTIVE		1
47 #define THERMAL_PASSIVE		2
48 #define THERMAL_HOT		3
49 #define THERMAL_CRITICAL	4
50 
51 struct cmap {
52 	uint32_t	*cm_cdev;
53 	uint32_t	*cm_cdevend;
54 	uint32_t	cm_trip;
55 };
56 
57 struct cdev {
58 	uint32_t	cd_phandle;
59 	int32_t		cd_level;
60 	int		cd_active;
61 	LIST_ENTRY(cdev) cd_list;
62 };
63 
64 struct thermal_zone {
65 	int		tz_node;
66 	char		tz_name[64];
67 	struct task	tz_poll_task;
68 	struct timeout	tz_poll_to;
69 	uint32_t	*tz_sensors;
70 	uint32_t	tz_polling_delay;
71 	uint32_t	tz_polling_delay_passive;
72 	LIST_ENTRY(thermal_zone) tz_list;
73 
74 	struct trippoint *tz_trips;
75 	int		tz_ntrips;
76 	struct trippoint *tz_tp;
77 
78 	struct cmap	*tz_cmaps;
79 	int		tz_ncmaps;
80 	struct cmap	*tz_cm;
81 
82 	LIST_HEAD(, cdev) tz_cdevs;
83 
84 	int32_t		tz_temperature;
85 };
86 
87 LIST_HEAD(, thermal_zone) thermal_zones =
88 	LIST_HEAD_INITIALIZER(thermal_zones);
89 
90 void
91 thermal_sensor_register(struct thermal_sensor *ts)
92 {
93 	ts->ts_cells = OF_getpropint(ts->ts_node, "#thermal-sensor-cells", 0);
94 	ts->ts_phandle = OF_getpropint(ts->ts_node, "phandle", 0);
95 	if (ts->ts_phandle == 0)
96 		return;
97 
98 	LIST_INSERT_HEAD(&thermal_sensors, ts, ts_list);
99 }
100 
101 void
102 thermal_sensor_update(struct thermal_sensor *ts, uint32_t *cells)
103 {
104 	struct thermal_zone *tz;
105 
106 	LIST_FOREACH(tz, &thermal_zones, tz_list) {
107 		if (tz->tz_sensors[0] == ts->ts_phandle &&
108 		    memcmp(&tz->tz_sensors[1], cells,
109 		    ts->ts_cells * sizeof(uint32_t)) == 0)
110 			task_add(tztq, &tz->tz_poll_task);
111 	}
112 }
113 
114 void
115 cooling_device_register(struct cooling_device *cd)
116 {
117 	cd->cd_cells = OF_getpropint(cd->cd_node, "#cooling-cells", 0);
118 	cd->cd_phandle = OF_getpropint(cd->cd_node, "phandle", 0);
119 	if (cd->cd_phandle == 0)
120 		return;
121 
122 	LIST_INSERT_HEAD(&cooling_devices, cd, cd_list);
123 }
124 
125 int32_t
126 thermal_get_temperature_cells(uint32_t *cells)
127 {
128 	struct thermal_sensor *ts;
129 	uint32_t phandle = cells[0];
130 
131 	LIST_FOREACH(ts, &thermal_sensors, ts_list) {
132 		if (ts->ts_phandle == phandle)
133 			break;
134 	}
135 
136 	if (ts && ts->ts_get_temperature)
137 		return ts->ts_get_temperature(ts->ts_cookie, &cells[1]);
138 
139 	return THERMAL_SENSOR_MAX;
140 }
141 
142 void
143 thermal_zone_poll_timeout(void *arg)
144 {
145 	struct thermal_zone *tz = arg;
146 
147 	task_add(tztq, &tz->tz_poll_task);
148 }
149 
150 uint32_t *
151 cdev_next_cdev(uint32_t *cells)
152 {
153 	uint32_t phandle = cells[0];
154 	int node, ncells;
155 
156 	node = OF_getnodebyphandle(phandle);
157 	if (node == 0)
158 		return NULL;
159 
160 	ncells = OF_getpropint(node, "#cooling-cells", 2);
161 	return cells + ncells + 1;
162 }
163 
164 uint32_t
165 cdev_get_level(uint32_t *cells)
166 {
167 	struct cooling_device *cd;
168 	uint32_t phandle = cells[0];
169 
170 	LIST_FOREACH(cd, &cooling_devices, cd_list) {
171 		if (cd->cd_phandle == phandle)
172 			break;
173 	}
174 
175 	if (cd && cd->cd_get_level)
176 		return cd->cd_get_level(cd->cd_cookie, &cells[1]);
177 
178 	return 0;
179 }
180 
181 void
182 cdev_set_level(uint32_t *cells, uint32_t level)
183 {
184 	struct cooling_device *cd;
185 	uint32_t phandle = cells[0];
186 
187 	LIST_FOREACH(cd, &cooling_devices, cd_list) {
188 		if (cd->cd_phandle == phandle)
189 			break;
190 	}
191 
192 	if (cd && cd->cd_set_level)
193 		cd->cd_set_level(cd->cd_cookie, &cells[1], level);
194 }
195 
196 
197 void
198 cmap_deactivate(struct thermal_zone *tz, struct cmap *cm)
199 {
200 	struct cdev *cd;
201 	uint32_t *cdev;
202 
203 	if (cm == NULL)
204 		return;
205 
206 	cdev = cm->cm_cdev;
207 	while (cdev && cdev < cm->cm_cdevend) {
208 		LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
209 			if (cd->cd_phandle == cdev[0])
210 				break;
211 		}
212 		KASSERT(cd != NULL);
213 		cd->cd_active = 0;
214 		cdev = cdev_next_cdev(cdev);
215 	}
216 }
217 
218 void
219 cmap_activate(struct thermal_zone *tz, struct cmap *cm, int32_t delta)
220 {
221 	struct cdev *cd;
222 	uint32_t *cdev;
223 	int32_t min, max;
224 
225 	if (cm == NULL)
226 		return;
227 
228 	cdev = cm->cm_cdev;
229 	while (cdev && cdev < cm->cm_cdevend) {
230 		LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
231 			if (cd->cd_phandle == cdev[0])
232 				break;
233 		}
234 		KASSERT(cd != NULL);
235 
236 		min = (cdev[1] == THERMAL_NO_LIMIT) ? 0 : cdev[1];
237 		max = (cdev[2] == THERMAL_NO_LIMIT) ? INT32_MAX : cdev[2];
238 
239 		cd->cd_active = 1;
240 		cd->cd_level = cdev_get_level(cdev) + delta;
241 		cd->cd_level = MAX(cd->cd_level, min);
242 		cd->cd_level = MIN(cd->cd_level, max);
243 		cdev_set_level(cdev, cd->cd_level);
244 		cdev = cdev_next_cdev(cdev);
245 	}
246 }
247 
248 void
249 cmap_finish(struct thermal_zone *tz)
250 {
251 	struct cdev *cd;
252 
253 	LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
254 		if (cd->cd_active == 0 && cd->cd_level != 0) {
255 			cdev_set_level(&cd->cd_phandle, 0);
256 			cd->cd_level = 0;
257 		}
258 	}
259 }
260 
261 void
262 thermal_zone_poll(void *arg)
263 {
264 	struct thermal_zone *tz = arg;
265 	struct trippoint *tp, *newtp;
266 	struct cmap *cm, *newcm;
267 	uint32_t polling_delay;
268 	int32_t temp, delta;
269 	int i;
270 
271 	temp = thermal_get_temperature_cells(tz->tz_sensors);
272 	if (temp == THERMAL_SENSOR_MAX)
273 		goto out;
274 
275 	newtp = NULL;
276 	tp = tz->tz_trips;
277 	for (i = 0; i < tz->tz_ntrips; i++) {
278 		if (temp < tp->tp_temperature && tp != tz->tz_tp)
279 			break;
280 		if (temp < tp->tp_temperature - tp->tp_hysteresis)
281 			break;
282 		newtp = tp++;
283 	}
284 
285 	/* Short circuit if we didn't hit a trip point. */
286 	if (newtp == NULL && tz->tz_tp == NULL)
287 		goto out;
288 
289 	/*
290 	 * If the current temperature is above the trip temperature:
291 	 *  - increase the cooling level if the temperature is rising
292 	 *  - do nothing if the temperature is falling
293 	 * If the current temperature is below the trip temperature:
294 	 *  - do nothing if the temperature is rising
295 	 *  - decrease the cooling level if the temperature is falling
296 	 */
297 	delta = 0;
298 	if (newtp && tz->tz_temperature != THERMAL_SENSOR_MAX) {
299 		if (temp >= newtp->tp_temperature) {
300 			if (temp > tz->tz_temperature)
301 				delta = 1;
302 		} else {
303 			if (temp < tz->tz_temperature)
304 				delta = -1;
305 		}
306 	}
307 
308 	newcm = NULL;
309 	cm = tz->tz_cmaps;
310 	for (i = 0; i < tz->tz_ncmaps; i++) {
311 		if (newtp && cm->cm_trip == newtp->tp_phandle) {
312 			newcm = cm;
313 			break;
314 		}
315 		cm++;
316 	}
317 
318 	cmap_deactivate(tz, tz->tz_cm);
319 	cmap_activate(tz, newcm, delta);
320 	cmap_finish(tz);
321 
322 	tz->tz_tp = newtp;
323 	tz->tz_cm = newcm;
324 
325 out:
326 	tz->tz_temperature = temp;
327 	if (tz->tz_tp && tz->tz_tp->tp_type == THERMAL_PASSIVE)
328 		polling_delay = tz->tz_polling_delay_passive;
329 	else
330 		polling_delay = tz->tz_polling_delay;
331 	timeout_add_msec(&tz->tz_poll_to, polling_delay);
332 }
333 
334 void
335 thermal_zone_init(int node)
336 {
337 	struct thermal_zone *tz;
338 	struct trippoint *tp;
339 	struct cmap *cm;
340 	struct cdev *cd;
341 	int len, i;
342 
343 	len = OF_getproplen(node, "thermal-sensors");
344 	if (len <= 0)
345 		return;
346 
347 	if (OF_getnodebyname(node, "trips") == 0)
348 		return;
349 	if (OF_getnodebyname(node, "cooling-maps") == 0)
350 		return;
351 
352 	tz = malloc(sizeof(struct thermal_zone), M_DEVBUF, M_ZERO | M_WAITOK);
353 	tz->tz_node = node;
354 
355 	OF_getprop(node, "name", &tz->tz_name, sizeof(tz->tz_name));
356 	tz->tz_name[sizeof(tz->tz_name) - 1] = 0;
357 	tz->tz_sensors = malloc(len, M_DEVBUF, M_WAITOK);
358 	OF_getpropintarray(node, "thermal-sensors", tz->tz_sensors, len);
359 	tz->tz_polling_delay = OF_getpropint(node, "polling-delay", 0);
360 	tz->tz_polling_delay_passive =
361 	    OF_getpropint(node, "polling-delay-passive", tz->tz_polling_delay);
362 
363 	task_set(&tz->tz_poll_task, thermal_zone_poll, tz);
364 	timeout_set(&tz->tz_poll_to, thermal_zone_poll_timeout, tz);
365 
366 	/*
367 	 * Trip points for this thermal zone.
368 	 */
369 	node = OF_getnodebyname(tz->tz_node, "trips");
370 	for (node = OF_child(node); node != 0; node = OF_peer(node))
371 		tz->tz_ntrips++;
372 
373 	tz->tz_trips = mallocarray(tz->tz_ntrips, sizeof(struct trippoint),
374 	    M_DEVBUF, M_ZERO | M_WAITOK);
375 
376 	node = OF_getnodebyname(tz->tz_node, "trips");
377 	for (node = OF_child(node); node != 0; node = OF_peer(node)) {
378 		char type[32] = "none";
379 		int32_t temp;
380 
381 		temp = OF_getpropint(node, "temperature", THERMAL_SENSOR_MAX);
382 
383 		/* Sorted insertion, since tree might not be */
384 		for (i = 0; i < tz->tz_ntrips; i++) {
385 			/* No trip point should be 0 degC, take it */
386 			if (tz->tz_trips[i].tp_temperature == 0)
387 				break;
388 			/* We should be bigger than the one before us */
389 			if (tz->tz_trips[i].tp_temperature < temp)
390 				continue;
391 			/* Free current slot */
392 			memmove(&tz->tz_trips[i + 1], &tz->tz_trips[i],
393 			    (tz->tz_ntrips - (i + 1)) * sizeof(*tp));
394 			break;
395 		}
396 		tp = &tz->tz_trips[i];
397 		tp->tp_temperature = temp;
398 		tp->tp_hysteresis = OF_getpropint(node, "hysteresis", 0);
399 		OF_getprop(node, "type", type, sizeof(type));
400 		if (strcmp(type, "active") == 0)
401 			tp->tp_type = THERMAL_ACTIVE;
402 		else if (strcmp(type, "passive") == 0)
403 			tp->tp_type = THERMAL_PASSIVE;
404 		else if (strcmp(type, "hot") == 0)
405 			tp->tp_type = THERMAL_HOT;
406 		else if (strcmp(type, "critical") == 0)
407 			tp->tp_type = THERMAL_CRITICAL;
408 		tp->tp_phandle = OF_getpropint(node, "phandle", 0);
409 		tp++;
410 	}
411 
412 	/*
413 	 * Cooling maps for this thermal zone.
414 	 */
415 	node = OF_getnodebyname(tz->tz_node, "cooling-maps");
416 	for (node = OF_child(node); node != 0; node = OF_peer(node))
417 		tz->tz_ncmaps++;
418 
419 	tz->tz_cmaps = mallocarray(tz->tz_ncmaps, sizeof(struct cmap),
420 	    M_DEVBUF, M_ZERO | M_WAITOK);
421 	cm = tz->tz_cmaps;
422 
423 	node = OF_getnodebyname(tz->tz_node, "cooling-maps");
424 	for (node = OF_child(node); node != 0; node = OF_peer(node)) {
425 		len = OF_getproplen(node, "cooling-device");
426 		if (len <= 0)
427 			continue;
428 		cm->cm_cdev = malloc(len, M_DEVBUF, M_ZERO | M_WAITOK);
429 		OF_getpropintarray(node, "cooling-device", cm->cm_cdev, len);
430 		cm->cm_cdevend = cm->cm_cdev + len / sizeof(uint32_t);
431 		cm->cm_trip = OF_getpropint(node, "trip", 0);
432 		cm++;
433 	}
434 
435 	/*
436 	 * Create a list of all the possible cooling devices from the
437 	 * cooling maps for this thermal zone, and initialize their
438 	 * state.
439 	 */
440 	LIST_INIT(&tz->tz_cdevs);
441 	cm = tz->tz_cmaps;
442 	for (i = 0; i < tz->tz_ncmaps; i++) {
443 		uint32_t *cdev;
444 
445 		cdev = cm->cm_cdev;
446 		while (cdev && cdev < cm->cm_cdevend) {
447 			LIST_FOREACH(cd, &tz->tz_cdevs, cd_list) {
448 				if (cd->cd_phandle == cdev[0])
449 					break;
450 			}
451 			if (cd == NULL) {
452 				cd = malloc(sizeof(struct cdev), M_DEVBUF,
453 				    M_ZERO | M_WAITOK);
454 				cd->cd_phandle = cdev[0];
455 				cd->cd_level = 0;
456 				cd->cd_active = 0;
457 				LIST_INSERT_HEAD(&tz->tz_cdevs, cd, cd_list);
458 			}
459 			cdev = cdev_next_cdev(cdev);
460 		}
461 		cm++;
462 	}
463 
464 	/* Start polling if we are requested to do so. */
465 	if (tz->tz_polling_delay > 0)
466 		timeout_add_msec(&tz->tz_poll_to, tz->tz_polling_delay);
467 	LIST_INSERT_HEAD(&thermal_zones, tz, tz_list);
468 }
469 
470 void
471 thermal_init(void)
472 {
473 	int node = OF_finddevice("/thermal-zones");
474 
475 	if (node == -1)
476 		return;
477 
478 	tztq = taskq_create("tztq", 1, IPL_SOFTCLOCK, 0);
479 
480 	for (node = OF_child(node); node != 0; node = OF_peer(node))
481 		thermal_zone_init(node);
482 }
483