1 /* $NetBSD: fcu.c,v 1.7 2025/09/17 14:15:59 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2018 Michael Lorenz 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __KERNEL_RCSID(0, "$NetBSD: fcu.c,v 1.7 2025/09/17 14:15:59 thorpej Exp $"); 31 32 #include <sys/param.h> 33 #include <sys/systm.h> 34 #include <sys/device.h> 35 #include <sys/conf.h> 36 #include <sys/bus.h> 37 #include <sys/kthread.h> 38 #include <sys/sysctl.h> 39 40 #include <dev/i2c/i2cvar.h> 41 42 #include <dev/sysmon/sysmonvar.h> 43 44 #include <dev/ofw/openfirm.h> 45 46 #include <macppc/dev/fancontrolvar.h> 47 48 #include "opt_fcu.h" 49 50 #ifdef FCU_DEBUG 51 #define DPRINTF printf 52 #else 53 #define DPRINTF if (0) printf 54 #endif 55 56 /* FCU registers, from OpenBSD's fcu.c */ 57 #define FCU_FAN_FAIL 0x0b /* fans states in bits 0<1-6>7 */ 58 #define FCU_FAN_ACTIVE 0x0d 59 #define FCU_FANREAD(x) 0x11 + (x)*2 60 #define FCU_FANSET(x) 0x10 + (x)*2 61 #define FCU_PWM_FAIL 0x2b 62 #define FCU_PWM_ACTIVE 0x2d 63 #define FCU_PWMREAD(x) 0x30 + (x)*2 64 65 66 typedef struct _fcu_fan { 67 int target; 68 int reg; 69 int base_rpm, max_rpm; 70 int step; 71 int duty; /* for pwm fans */ 72 } fcu_fan_t; 73 74 #define FCU_ZONE_CPU 0 75 #define FCU_ZONE_CASE 1 76 #define FCU_ZONE_DRIVEBAY 2 77 #define FCU_ZONE_COUNT 3 78 79 struct fcu_softc { 80 device_t sc_dev; 81 i2c_tag_t sc_i2c; 82 i2c_addr_t sc_addr; 83 struct sysctlnode *sc_sysctl_me; 84 struct sysmon_envsys *sc_sme; 85 envsys_data_t sc_sensors[32]; 86 int sc_nsensors; 87 fancontrol_zone_t sc_zones[FCU_ZONE_COUNT]; 88 fcu_fan_t sc_fans[FANCONTROL_MAX_FANS]; 89 int sc_nfans; 90 lwp_t *sc_thread; 91 bool sc_dying, sc_pwm; 92 uint8_t sc_eeprom0[160]; 93 uint8_t sc_eeprom1[160]; 94 }; 95 96 static int fcu_match(device_t, cfdata_t, void *); 97 static void fcu_attach(device_t, device_t, void *); 98 99 static void fcu_sensors_refresh(struct sysmon_envsys *, envsys_data_t *); 100 static void fcu_configure_sensor(struct fcu_softc *, envsys_data_t *); 101 102 static bool is_cpu(const envsys_data_t *); 103 static bool is_case(const envsys_data_t *); 104 static bool is_drive(const envsys_data_t *); 105 106 static int fcu_set_rpm(void *, int, int); 107 static int fcu_get_rpm(void *, int); 108 static void fcu_adjust(void *); 109 110 CFATTACH_DECL_NEW(fcu, sizeof(struct fcu_softc), 111 fcu_match, fcu_attach, NULL, NULL); 112 113 static const struct device_compatible_entry compat_data[] = { 114 { .compat = "fcu" }, 115 DEVICE_COMPAT_EOL 116 }; 117 118 static int 119 fcu_match(device_t parent, cfdata_t match, void *aux) 120 { 121 struct i2c_attach_args *ia = aux; 122 int match_result; 123 124 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 125 return match_result; 126 127 if (ia->ia_addr == 0x2f) 128 return I2C_MATCH_ADDRESS_ONLY; 129 130 return 0; 131 } 132 133 static void 134 fcu_attach(device_t parent, device_t self, void *aux) 135 { 136 struct fcu_softc *sc = device_private(self); 137 struct i2c_attach_args *ia = aux; 138 int phandle = devhandle_to_of(device_handle(self)); 139 int i; 140 141 sc->sc_dev = self; 142 sc->sc_i2c = ia->ia_tag; 143 sc->sc_addr = ia->ia_addr; 144 145 aprint_naive("\n"); 146 aprint_normal(": Fan Control Unit\n"); 147 148 sysctl_createv(NULL, 0, NULL, (void *) &sc->sc_sysctl_me, 149 CTLFLAG_READWRITE, 150 CTLTYPE_NODE, device_xname(sc->sc_dev), NULL, 151 NULL, 0, NULL, 0, 152 CTL_MACHDEP, CTL_CREATE, CTL_EOL); 153 154 if (get_cpuid(0, sc->sc_eeprom0) < 160) { 155 /* 156 * XXX this should never happen, we depend on the EEPROM for 157 * calibration data to make sense of temperature and voltage 158 * sensors elsewhere, and fan parameters here. 159 */ 160 aprint_error_dev(self, "no EEPROM data for CPU 0\n"); 161 return; 162 } 163 164 /* init zones */ 165 sc->sc_zones[FCU_ZONE_CPU].name = "CPUs"; 166 sc->sc_zones[FCU_ZONE_CPU].filter = is_cpu; 167 sc->sc_zones[FCU_ZONE_CPU].cookie = sc; 168 sc->sc_zones[FCU_ZONE_CPU].get_rpm = fcu_get_rpm; 169 sc->sc_zones[FCU_ZONE_CPU].set_rpm = fcu_set_rpm; 170 sc->sc_zones[FCU_ZONE_CPU].Tmin = 50; 171 sc->sc_zones[FCU_ZONE_CPU].Tmax = 85; 172 sc->sc_zones[FCU_ZONE_CPU].nfans = 0; 173 sc->sc_zones[FCU_ZONE_CASE].name = "Slots"; 174 sc->sc_zones[FCU_ZONE_CASE].filter = is_case; 175 sc->sc_zones[FCU_ZONE_CASE].cookie = sc; 176 sc->sc_zones[FCU_ZONE_CASE].Tmin = 50; 177 sc->sc_zones[FCU_ZONE_CASE].Tmax = 75; 178 sc->sc_zones[FCU_ZONE_CASE].nfans = 0; 179 sc->sc_zones[FCU_ZONE_CASE].get_rpm = fcu_get_rpm; 180 sc->sc_zones[FCU_ZONE_CASE].set_rpm = fcu_set_rpm; 181 sc->sc_zones[FCU_ZONE_DRIVEBAY].name = "Drivebays"; 182 sc->sc_zones[FCU_ZONE_DRIVEBAY].filter = is_drive; 183 sc->sc_zones[FCU_ZONE_DRIVEBAY].cookie = sc; 184 sc->sc_zones[FCU_ZONE_DRIVEBAY].get_rpm = fcu_get_rpm; 185 sc->sc_zones[FCU_ZONE_DRIVEBAY].set_rpm = fcu_set_rpm; 186 sc->sc_zones[FCU_ZONE_DRIVEBAY].Tmin = 30; 187 sc->sc_zones[FCU_ZONE_DRIVEBAY].Tmax = 50; 188 sc->sc_zones[FCU_ZONE_DRIVEBAY].nfans = 0; 189 190 sc->sc_sme = sysmon_envsys_create(); 191 sc->sc_sme->sme_name = device_xname(self); 192 sc->sc_sme->sme_cookie = sc; 193 sc->sc_sme->sme_refresh = fcu_sensors_refresh; 194 195 sc->sc_sensors[0].units = ENVSYS_SFANRPM; 196 sc->sc_sensors[1].state = ENVSYS_SINVALID; 197 sc->sc_nfans = 0; 198 199 /* round up sensors */ 200 int ch; 201 202 sc->sc_nsensors = 0; 203 ch = OF_child(phandle); 204 if (ch == 0) { 205 /* old style data, no individual nodes for fans, annoying */ 206 char loc[256], tp[256], descr[32], type[32]; 207 uint32_t reg_rpm = 0x10, reg_pwm = 0x32, reg; 208 uint32_t id[16]; 209 int num, lidx = 0, tidx = 0; 210 211 num = OF_getprop(phandle, "hwctrl-id", id, 64); 212 OF_getprop(phandle, "hwctrl-location", loc, 1024); 213 OF_getprop(phandle, "hwctrl-type", tp, 1024); 214 while (num > 0) { 215 envsys_data_t *s = &sc->sc_sensors[sc->sc_nsensors]; 216 217 s->state = ENVSYS_SINVALID; 218 strcpy(descr, &loc[lidx]); 219 strcpy(type, &tp[tidx]); 220 if (strstr(type, "rpm") != NULL) { 221 s->units = ENVSYS_SFANRPM; 222 reg = reg_rpm; 223 reg_rpm += 2; 224 } else if (strstr(type, "pwm") != NULL) { 225 s->units = ENVSYS_SFANRPM; 226 reg = reg_pwm; 227 reg_pwm += 2; 228 } else goto skip; 229 230 s->private = reg; 231 strcpy(s->desc, descr); 232 233 fcu_configure_sensor(sc, s); 234 235 sysmon_envsys_sensor_attach(sc->sc_sme, s); 236 sc->sc_nsensors++; 237 skip: 238 lidx += strlen(descr) + 1; 239 tidx += strlen(type) + 1; 240 num -= 4; 241 } 242 } else { 243 /* new style, with individual nodes */ 244 while (ch != 0) { 245 char type[32], descr[32]; 246 uint32_t reg; 247 248 envsys_data_t *s = &sc->sc_sensors[sc->sc_nsensors]; 249 250 s->state = ENVSYS_SINVALID; 251 252 if (OF_getprop(ch, "device_type", type, 32) <= 0) 253 goto next; 254 255 if (strcmp(type, "fan-rpm-control") == 0) { 256 s->units = ENVSYS_SFANRPM; 257 } else if (strcmp(type, "fan-pwm-control") == 0) { 258 /* XXX we get the type from the register number */ 259 s->units = ENVSYS_SFANRPM; 260 /* skip those for now since we don't really know how to interpret them */ 261 #if 0 262 } else if (strcmp(type, "power-sensor") == 0) { 263 s->units = ENVSYS_SVOLTS_DC; 264 #endif 265 } else if (strcmp(type, "gpi-sensor") == 0) { 266 s->units = ENVSYS_INDICATOR; 267 } else { 268 /* ignore other types for now */ 269 goto next; 270 } 271 272 if (OF_getprop(ch, "reg", ®, sizeof(reg)) <= 0) 273 goto next; 274 s->private = reg; 275 276 if (OF_getprop(ch, "location", descr, 32) <= 0) 277 goto next; 278 strcpy(s->desc, descr); 279 280 fcu_configure_sensor(sc, s); 281 282 sysmon_envsys_sensor_attach(sc->sc_sme, s); 283 sc->sc_nsensors++; 284 next: 285 ch = OF_peer(ch); 286 } 287 } 288 sysmon_envsys_register(sc->sc_sme); 289 290 /* setup sysctls for our zones etc. */ 291 for (i = 0; i < FCU_ZONE_COUNT; i++) { 292 fancontrol_init_zone(&sc->sc_zones[i], sc->sc_sysctl_me); 293 } 294 295 sc->sc_dying = FALSE; 296 kthread_create(PRI_NONE, 0, curcpu(), fcu_adjust, sc, &sc->sc_thread, 297 "fan control"); 298 } 299 300 static void 301 fcu_configure_sensor(struct fcu_softc *sc, envsys_data_t *s) 302 { 303 int have_eeprom1 = 1; 304 305 if (get_cpuid(1, sc->sc_eeprom1) < 160) 306 have_eeprom1 = 0; 307 308 if (s->units == ENVSYS_SFANRPM) { 309 fcu_fan_t *fan = &sc->sc_fans[sc->sc_nfans]; 310 uint8_t *eeprom = NULL; 311 uint16_t rmin, rmax; 312 313 if (strstr(s->desc, "CPU A") != NULL) 314 eeprom = sc->sc_eeprom0; 315 if (strstr(s->desc, "CPU B") != NULL) { 316 /* 317 * XXX 318 * this should never happen 319 */ 320 if (have_eeprom1 == 0) { 321 eeprom = sc->sc_eeprom0; 322 } else 323 eeprom = sc->sc_eeprom1; 324 } 325 326 fan->reg = s->private; 327 fan->target = 0; 328 fan->duty = 0x80; 329 330 /* speed settings from EEPROM */ 331 if (strstr(s->desc, "PUMP") != NULL) { 332 KASSERT(eeprom != NULL); 333 memcpy(&rmin, &eeprom[0x54], 2); 334 memcpy(&rmax, &eeprom[0x56], 2); 335 fan->base_rpm = rmin; 336 fan->max_rpm = rmax; 337 fan->step = (rmax - rmin) / 30; 338 } else if (strstr(s->desc, "INTAKE") != NULL) { 339 KASSERT(eeprom != NULL); 340 memcpy(&rmin, &eeprom[0x4c], 2); 341 memcpy(&rmax, &eeprom[0x4e], 2); 342 fan->base_rpm = rmin; 343 fan->max_rpm = rmax; 344 fan->step = (rmax - rmin) / 30; 345 } else if (strstr(s->desc, "EXHAUST") != NULL) { 346 KASSERT(eeprom != NULL); 347 memcpy(&rmin, &eeprom[0x50], 2); 348 memcpy(&rmax, &eeprom[0x52], 2); 349 fan->base_rpm = rmin; 350 fan->max_rpm = rmax; 351 fan->step = (rmax - rmin) / 30; 352 } else if (strstr(s->desc, "DRIVE") != NULL ) { 353 fan->base_rpm = 1000; 354 fan->max_rpm = 3000; 355 fan->step = 100; 356 } else { 357 fan->base_rpm = 1000; 358 fan->max_rpm = 3000; 359 fan->step = 100; 360 } 361 DPRINTF("fan %s: %d - %d rpm, step %d\n", 362 s->desc, fan->base_rpm, fan->max_rpm, fan->step); 363 364 /* now stuff them into zones */ 365 if (strstr(s->desc, "CPU") != NULL) { 366 fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_CPU]; 367 z->fans[z->nfans].num = sc->sc_nfans; 368 z->fans[z->nfans].min_rpm = fan->base_rpm; 369 z->fans[z->nfans].max_rpm = fan->max_rpm; 370 z->fans[z->nfans].name = s->desc; 371 z->nfans++; 372 } else if ((strstr(s->desc, "BACKSIDE") != NULL) || 373 (strstr(s->desc, "SLOT") != NULL)) { 374 fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_CASE]; 375 z->fans[z->nfans].num = sc->sc_nfans; 376 z->fans[z->nfans].min_rpm = fan->base_rpm; 377 z->fans[z->nfans].max_rpm = fan->max_rpm; 378 z->fans[z->nfans].name = s->desc; 379 z->nfans++; 380 } else if (strstr(s->desc, "DRIVE") != NULL) { 381 fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_DRIVEBAY]; 382 z->fans[z->nfans].num = sc->sc_nfans; 383 z->fans[z->nfans].min_rpm = fan->base_rpm; 384 z->fans[z->nfans].max_rpm = fan->max_rpm; 385 z->fans[z->nfans].name = s->desc; 386 z->nfans++; 387 } 388 sc->sc_nfans++; 389 } 390 } 391 static void 392 fcu_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 393 { 394 struct fcu_softc *sc = sme->sme_cookie; 395 uint8_t cmd; 396 uint16_t data = 0; 397 int error; 398 399 if (edata->units == ENVSYS_SFANRPM) { 400 cmd = edata->private + 1; 401 } else 402 cmd = edata->private; 403 404 /* fcu is a macppc only thing so we can safely assume big endian */ 405 iic_acquire_bus(sc->sc_i2c, 0); 406 error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 407 sc->sc_addr, &cmd, 1, &data, 2, 0); 408 iic_release_bus(sc->sc_i2c, 0); 409 410 if (error) { 411 edata->state = ENVSYS_SINVALID; 412 return; 413 } 414 415 edata->state = ENVSYS_SVALID; 416 417 switch (edata->units) { 418 case ENVSYS_SFANRPM: 419 edata->value_cur = data >> 3; 420 break; 421 case ENVSYS_SVOLTS_DC: 422 /* XXX this reads bogus */ 423 edata->value_cur = data * 1000; 424 break; 425 case ENVSYS_INDICATOR: 426 /* guesswork for now */ 427 edata->value_cur = data >> 8; 428 break; 429 default: 430 edata->state = ENVSYS_SINVALID; 431 } 432 } 433 434 static bool 435 is_cpu(const envsys_data_t *edata) 436 { 437 if (edata->units != ENVSYS_STEMP) 438 return false; 439 if (strstr(edata->desc, "CPU") != NULL) 440 return TRUE; 441 return false; 442 } 443 444 static bool 445 is_case(const envsys_data_t *edata) 446 { 447 if (edata->units != ENVSYS_STEMP) 448 return false; 449 if ((strstr(edata->desc, "MLB") != NULL) || 450 (strstr(edata->desc, "BACKSIDE") != NULL) || 451 (strstr(edata->desc, "U3") != NULL)) 452 return TRUE; 453 return false; 454 } 455 456 static bool 457 is_drive(const envsys_data_t *edata) 458 { 459 if (edata->units != ENVSYS_STEMP) 460 return false; 461 if (strstr(edata->desc, "DRIVE") != NULL) 462 return TRUE; 463 return false; 464 } 465 466 static int 467 fcu_get_rpm(void *cookie, int which) 468 { 469 struct fcu_softc *sc = cookie; 470 fcu_fan_t *f = &sc->sc_fans[which]; 471 int error; 472 uint16_t data = 0; 473 uint8_t cmd; 474 475 iic_acquire_bus(sc->sc_i2c, 0); 476 cmd = f->reg + 1; 477 error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 478 sc->sc_addr, &cmd, 1, &data, 2, 0); 479 iic_release_bus(sc->sc_i2c, 0); 480 if (error != 0) return 0; 481 data = data >> 3; 482 return data; 483 } 484 485 static int 486 fcu_set_rpm(void *cookie, int which, int speed) 487 { 488 struct fcu_softc *sc = cookie; 489 fcu_fan_t *f = &sc->sc_fans[which]; 490 int error = 0; 491 uint8_t cmd; 492 493 if (speed > f->max_rpm) speed = f->max_rpm; 494 if (speed < f->base_rpm) speed = f->base_rpm; 495 496 if (f->reg < 0x30) { 497 uint16_t data; 498 /* simple rpm fan, just poke the register */ 499 500 if (f->target == speed) return 0; 501 iic_acquire_bus(sc->sc_i2c, 0); 502 cmd = f->reg; 503 data = (speed << 3); 504 error = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 505 sc->sc_addr, &cmd, 1, &data, 2, 0); 506 iic_release_bus(sc->sc_i2c, 0); 507 } else { 508 int diff; 509 int nduty = f->duty; 510 int current_speed; 511 /* pwm fan, measure speed, then adjust duty cycle */ 512 DPRINTF("pwm fan "); 513 current_speed = fcu_get_rpm(sc, which); 514 diff = current_speed - speed; 515 DPRINTF("d %d s %d t %d diff %d ", f->duty, current_speed, speed, diff); 516 if (diff > 100) { 517 nduty = uimax(20, nduty - 1); 518 } 519 if (diff < -100) { 520 nduty = uimin(0xd0, nduty + 1); 521 } 522 cmd = f->reg; 523 DPRINTF("%s nduty %d", __func__, nduty); 524 if (nduty != f->duty) { 525 uint8_t arg = nduty; 526 iic_acquire_bus(sc->sc_i2c, 0); 527 error = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 528 sc->sc_addr, &cmd, 1, &arg, 1, 0); 529 iic_release_bus(sc->sc_i2c, 0); 530 f->duty = nduty; 531 sc->sc_pwm = TRUE; 532 533 } 534 DPRINTF("ok\n"); 535 } 536 if (error) printf("boo\n"); 537 f->target = speed; 538 return 0; 539 } 540 541 static void 542 fcu_adjust(void *cookie) 543 { 544 struct fcu_softc *sc = cookie; 545 int i; 546 uint8_t cmd, data; 547 548 while (!sc->sc_dying) { 549 /* poke the FCU so we don't go 747 */ 550 iic_acquire_bus(sc->sc_i2c, 0); 551 cmd = FCU_FAN_ACTIVE; 552 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 553 sc->sc_addr, &cmd, 1, &data, 1, 0); 554 iic_release_bus(sc->sc_i2c, 0); 555 sc->sc_pwm = FALSE; 556 for (i = 0; i < FCU_ZONE_COUNT; i++) 557 fancontrol_adjust_zone(&sc->sc_zones[i]); 558 /* 559 * take a shorter nap if we're in the process of adjusting a 560 * PWM fan, which relies on measuring speed and then changing 561 * its duty cycle until we're reasonable close to the target 562 * speed 563 */ 564 kpause("fanctrl", true, mstohz(sc->sc_pwm ? 1000 : 2000), NULL); 565 } 566 kthread_exit(0); 567 } 568