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