1/* $NetBSD: fcu.c,v 1.5 2022/04/08 10:17:53 andvar 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.5 2022/04/08 10:17:53 andvar 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//#define FCU_DEBUG 49#ifdef FCU_DEBUG 50#define DPRINTF printf 51#else 52#define DPRINTF if (0) printf 53#endif 54 55/* FCU registers, from OpenBSD's fcu.c */ 56#define FCU_FAN_FAIL 0x0b /* fans states in bits 0<1-6>7 */ 57#define FCU_FAN_ACTIVE 0x0d 58#define FCU_FANREAD(x) 0x11 + (x)*2 59#define FCU_FANSET(x) 0x10 + (x)*2 60#define FCU_PWM_FAIL 0x2b 61#define FCU_PWM_ACTIVE 0x2d 62#define FCU_PWMREAD(x) 0x30 + (x)*2 63 64 65typedef struct _fcu_fan { 66 int target; 67 int reg; 68 int base_rpm, max_rpm; 69 int step; 70 int duty; /* for pwm fans */ 71} fcu_fan_t; 72 73#define FCU_ZONE_CPU 0 74#define FCU_ZONE_CASE 1 75#define FCU_ZONE_DRIVEBAY 2 76#define FCU_ZONE_COUNT 3 77 78struct fcu_softc { 79 device_t sc_dev; 80 i2c_tag_t sc_i2c; 81 i2c_addr_t sc_addr; 82 struct sysctlnode *sc_sysctl_me; 83 struct sysmon_envsys *sc_sme; 84 envsys_data_t sc_sensors[32]; 85 int sc_nsensors; 86 fancontrol_zone_t sc_zones[FCU_ZONE_COUNT]; 87 fcu_fan_t sc_fans[FANCONTROL_MAX_FANS]; 88 int sc_nfans; 89 lwp_t *sc_thread; 90 bool sc_dying, sc_pwm; 91 uint8_t sc_eeprom0[160]; 92 uint8_t sc_eeprom1[160]; 93}; 94 95static int fcu_match(device_t, cfdata_t, void *); 96static void fcu_attach(device_t, device_t, void *); 97 98static void fcu_sensors_refresh(struct sysmon_envsys *, envsys_data_t *); 99 100static bool is_cpu(const envsys_data_t *); 101static bool is_case(const envsys_data_t *); 102static bool is_drive(const envsys_data_t *); 103 104static int fcu_set_rpm(void *, int, int); 105static int fcu_get_rpm(void *, int); 106static void fcu_adjust(void *); 107 108CFATTACH_DECL_NEW(fcu, sizeof(struct fcu_softc), 109 fcu_match, fcu_attach, NULL, NULL); 110 111static const struct device_compatible_entry compat_data[] = { 112 { .compat = "fcu" }, 113 DEVICE_COMPAT_EOL 114}; 115 116static int 117fcu_match(device_t parent, cfdata_t match, void *aux) 118{ 119 struct i2c_attach_args *ia = aux; 120 int match_result; 121 122 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 123 return match_result; 124 125 if (ia->ia_addr == 0x2f) 126 return I2C_MATCH_ADDRESS_ONLY; 127 128 return 0; 129} 130 131static void 132fcu_attach(device_t parent, device_t self, void *aux) 133{ 134 struct fcu_softc *sc = device_private(self); 135 struct i2c_attach_args *ia = aux; 136 int have_eeprom1 = 1, i; 137 138 sc->sc_dev = self; 139 sc->sc_i2c = ia->ia_tag; 140 sc->sc_addr = ia->ia_addr; 141 142 aprint_naive("\n"); 143 aprint_normal(": Fan Control Unit\n"); 144 145 sysctl_createv(NULL, 0, NULL, (void *) &sc->sc_sysctl_me, 146 CTLFLAG_READWRITE, 147 CTLTYPE_NODE, device_xname(sc->sc_dev), NULL, 148 NULL, 0, NULL, 0, 149 CTL_MACHDEP, CTL_CREATE, CTL_EOL); 150 151 if (get_cpuid(0, sc->sc_eeprom0) < 160) { 152 /* 153 * XXX this should never happen, we depend on the EEPROM for 154 * calibration data to make sense of temperature and voltage 155 * sensors elsewhere, and fan parameters here. 156 */ 157 aprint_error_dev(self, "no EEPROM data for CPU 0\n"); 158 return; 159 } 160 if (get_cpuid(1, sc->sc_eeprom1) < 160) 161 have_eeprom1 = 0; 162 163 /* init zones */ 164 sc->sc_zones[FCU_ZONE_CPU].name = "CPUs"; 165 sc->sc_zones[FCU_ZONE_CPU].filter = is_cpu; 166 sc->sc_zones[FCU_ZONE_CPU].cookie = sc; 167 sc->sc_zones[FCU_ZONE_CPU].get_rpm = fcu_get_rpm; 168 sc->sc_zones[FCU_ZONE_CPU].set_rpm = fcu_set_rpm; 169 sc->sc_zones[FCU_ZONE_CPU].Tmin = 50; 170 sc->sc_zones[FCU_ZONE_CPU].Tmax = 85; 171 sc->sc_zones[FCU_ZONE_CPU].nfans = 0; 172 sc->sc_zones[FCU_ZONE_CASE].name = "Slots"; 173 sc->sc_zones[FCU_ZONE_CASE].filter = is_case; 174 sc->sc_zones[FCU_ZONE_CASE].cookie = sc; 175 sc->sc_zones[FCU_ZONE_CASE].Tmin = 50; 176 sc->sc_zones[FCU_ZONE_CASE].Tmax = 75; 177 sc->sc_zones[FCU_ZONE_CASE].nfans = 0; 178 sc->sc_zones[FCU_ZONE_CASE].get_rpm = fcu_get_rpm; 179 sc->sc_zones[FCU_ZONE_CASE].set_rpm = fcu_set_rpm; 180 sc->sc_zones[FCU_ZONE_DRIVEBAY].name = "Drivebays"; 181 sc->sc_zones[FCU_ZONE_DRIVEBAY].filter = is_drive; 182 sc->sc_zones[FCU_ZONE_DRIVEBAY].cookie = sc; 183 sc->sc_zones[FCU_ZONE_DRIVEBAY].get_rpm = fcu_get_rpm; 184 sc->sc_zones[FCU_ZONE_DRIVEBAY].set_rpm = fcu_set_rpm; 185 sc->sc_zones[FCU_ZONE_DRIVEBAY].Tmin = 30; 186 sc->sc_zones[FCU_ZONE_DRIVEBAY].Tmax = 50; 187 sc->sc_zones[FCU_ZONE_DRIVEBAY].nfans = 0; 188 189 sc->sc_sme = sysmon_envsys_create(); 190 sc->sc_sme->sme_name = device_xname(self); 191 sc->sc_sme->sme_cookie = sc; 192 sc->sc_sme->sme_refresh = fcu_sensors_refresh; 193 194 sc->sc_sensors[0].units = ENVSYS_SFANRPM; 195 sc->sc_sensors[1].state = ENVSYS_SINVALID; 196 sc->sc_nfans = 0; 197 198 /* round up sensors */ 199 int ch; 200 201 sc->sc_nsensors = 0; 202 ch = OF_child(ia->ia_cookie); 203 while (ch != 0) { 204 char type[32], descr[32]; 205 uint32_t reg; 206 207 envsys_data_t *s = &sc->sc_sensors[sc->sc_nsensors]; 208 209 s->state = ENVSYS_SINVALID; 210 211 if (OF_getprop(ch, "device_type", type, 32) <= 0) 212 goto next; 213 214 if (strcmp(type, "fan-rpm-control") == 0) { 215 s->units = ENVSYS_SFANRPM; 216 } else if (strcmp(type, "fan-pwm-control") == 0) { 217 /* XXX we get the type from the register number */ 218 s->units = ENVSYS_SFANRPM; 219/* skip those for now since we don't really know how to interpret them */ 220#if 0 221 } else if (strcmp(type, "power-sensor") == 0) { 222 s->units = ENVSYS_SVOLTS_DC; 223#endif 224 } else if (strcmp(type, "gpi-sensor") == 0) { 225 s->units = ENVSYS_INDICATOR; 226 } else { 227 /* ignore other types for now */ 228 goto next; 229 } 230 231 if (OF_getprop(ch, "reg", ®, sizeof(reg)) <= 0) 232 goto next; 233 s->private = reg; 234 235 if (OF_getprop(ch, "location", descr, 32) <= 0) 236 goto next; 237 strcpy(s->desc, descr); 238 239 if (s->units == ENVSYS_SFANRPM) { 240 fcu_fan_t *fan = &sc->sc_fans[sc->sc_nfans]; 241 uint8_t *eeprom = NULL; 242 uint16_t rmin, rmax; 243 244 if (strstr(descr, "CPU A") != NULL) 245 eeprom = sc->sc_eeprom0; 246 if (strstr(descr, "CPU B") != NULL) { 247 /* 248 * XXX 249 * this should never happen 250 */ 251 if (have_eeprom1 == 0) { 252 eeprom = sc->sc_eeprom0; 253 } else 254 eeprom = sc->sc_eeprom1; 255 } 256 257 fan->reg = reg; 258 fan->target = 0; 259 fan->duty = 0x80; 260 261 /* speed settings from EEPROM */ 262 if (strstr(descr, "PUMP") != NULL) { 263 KASSERT(eeprom != NULL); 264 memcpy(&rmin, &eeprom[0x54], 2); 265 memcpy(&rmax, &eeprom[0x56], 2); 266 fan->base_rpm = rmin; 267 fan->max_rpm = rmax; 268 fan->step = (rmax - rmin) / 30; 269 } else if (strstr(descr, "INTAKE") != NULL) { 270 KASSERT(eeprom != NULL); 271 memcpy(&rmin, &eeprom[0x4c], 2); 272 memcpy(&rmax, &eeprom[0x4e], 2); 273 fan->base_rpm = rmin; 274 fan->max_rpm = rmax; 275 fan->step = (rmax - rmin) / 30; 276 } else if (strstr(descr, "EXHAUST") != NULL) { 277 KASSERT(eeprom != NULL); 278 memcpy(&rmin, &eeprom[0x50], 2); 279 memcpy(&rmax, &eeprom[0x52], 2); 280 fan->base_rpm = rmin; 281 fan->max_rpm = rmax; 282 fan->step = (rmax - rmin) / 30; 283 } else if (strstr(descr, "DRIVE") != NULL ) { 284 fan->base_rpm = 1000; 285 fan->max_rpm = 3000; 286 fan->step = 100; 287 } else { 288 fan->base_rpm = 1000; 289 fan->max_rpm = 3000; 290 fan->step = 100; 291 } 292 DPRINTF("fan %s: %d - %d rpm, step %d\n", 293 descr, fan->base_rpm, fan->max_rpm, fan->step); 294 295 /* now stuff them into zones */ 296 if (strstr(descr, "CPU") != NULL) { 297 fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_CPU]; 298 z->fans[z->nfans].num = sc->sc_nfans; 299 z->fans[z->nfans].min_rpm = fan->base_rpm; 300 z->fans[z->nfans].max_rpm = fan->max_rpm; 301 z->fans[z->nfans].name = s->desc; 302 z->nfans++; 303 } else if ((strstr(descr, "BACKSIDE") != NULL) || 304 (strstr(descr, "SLOT") != NULL)) { 305 fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_CASE]; 306 z->fans[z->nfans].num = sc->sc_nfans; 307 z->fans[z->nfans].min_rpm = fan->base_rpm; 308 z->fans[z->nfans].max_rpm = fan->max_rpm; 309 z->fans[z->nfans].name = s->desc; 310 z->nfans++; 311 } else if (strstr(descr, "DRIVE") != NULL) { 312 fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_DRIVEBAY]; 313 z->fans[z->nfans].num = sc->sc_nfans; 314 z->fans[z->nfans].min_rpm = fan->base_rpm; 315 z->fans[z->nfans].max_rpm = fan->max_rpm; 316 z->fans[z->nfans].name = s->desc; 317 z->nfans++; 318 } 319 sc->sc_nfans++; 320 } 321 sysmon_envsys_sensor_attach(sc->sc_sme, s); 322 sc->sc_nsensors++; 323next: 324 ch = OF_peer(ch); 325 } 326 sysmon_envsys_register(sc->sc_sme); 327 328 /* setup sysctls for our zones etc. */ 329 for (i = 0; i < FCU_ZONE_COUNT; i++) { 330 fancontrol_init_zone(&sc->sc_zones[i], sc->sc_sysctl_me); 331 } 332 333 sc->sc_dying = FALSE; 334 kthread_create(PRI_NONE, 0, curcpu(), fcu_adjust, sc, &sc->sc_thread, 335 "fan control"); 336} 337 338static void 339fcu_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 340{ 341 struct fcu_softc *sc = sme->sme_cookie; 342 uint8_t cmd; 343 uint16_t data = -1; 344 int error; 345 346 if (edata->units == ENVSYS_SFANRPM) { 347 cmd = edata->private + 1; 348 } else 349 cmd = edata->private; 350 351 /* fcu is a macppc only thing so we can safely assume big endian */ 352 iic_acquire_bus(sc->sc_i2c, 0); 353 error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 354 sc->sc_addr, &cmd, 1, &data, 2, 0); 355 iic_release_bus(sc->sc_i2c, 0); 356 357 if (error) { 358 edata->state = ENVSYS_SINVALID; 359 return; 360 } 361 362 edata->state = ENVSYS_SVALID; 363 364 switch (edata->units) { 365 case ENVSYS_SFANRPM: 366 edata->value_cur = data >> 3; 367 break; 368 case ENVSYS_SVOLTS_DC: 369 /* XXX this reads bogus */ 370 edata->value_cur = data * 1000; 371 break; 372 case ENVSYS_INDICATOR: 373 /* guesswork for now */ 374 edata->value_cur = data >> 8; 375 break; 376 default: 377 edata->state = ENVSYS_SINVALID; 378 } 379} 380 381static bool 382is_cpu(const envsys_data_t *edata) 383{ 384 if (edata->units != ENVSYS_STEMP) 385 return false; 386 if (strstr(edata->desc, "CPU") != NULL) 387 return TRUE; 388 return false; 389} 390 391static bool 392is_case(const envsys_data_t *edata) 393{ 394 if (edata->units != ENVSYS_STEMP) 395 return false; 396 if ((strstr(edata->desc, "MLB") != NULL) || 397 (strstr(edata->desc, "BACKSIDE") != NULL) || 398 (strstr(edata->desc, "U3") != NULL)) 399 return TRUE; 400 return false; 401} 402 403static bool 404is_drive(const envsys_data_t *edata) 405{ 406 if (edata->units != ENVSYS_STEMP) 407 return false; 408 if (strstr(edata->desc, "DRIVE") != NULL) 409 return TRUE; 410 return false; 411} 412 413static int 414fcu_get_rpm(void *cookie, int which) 415{ 416 struct fcu_softc *sc = cookie; 417 fcu_fan_t *f = &sc->sc_fans[which]; 418 int error; 419 uint16_t data; 420 uint8_t cmd; 421 422 iic_acquire_bus(sc->sc_i2c, 0); 423 cmd = f->reg + 1; 424 error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 425 sc->sc_addr, &cmd, 1, &data, 2, 0); 426 iic_release_bus(sc->sc_i2c, 0); 427 if (error != 0) return -1; 428 data = data >> 3; 429 return data; 430} 431 432static int 433fcu_set_rpm(void *cookie, int which, int speed) 434{ 435 struct fcu_softc *sc = cookie; 436 fcu_fan_t *f = &sc->sc_fans[which]; 437 int error = 0; 438 uint8_t cmd; 439 440 if (speed > f->max_rpm) speed = f->max_rpm; 441 if (speed < f->base_rpm) speed = f->base_rpm; 442 443 if (f->reg < 0x30) { 444 uint16_t data; 445 /* simple rpm fan, just poke the register */ 446 447 if (f->target == speed) return 0; 448 iic_acquire_bus(sc->sc_i2c, 0); 449 cmd = f->reg; 450 data = (speed << 3); 451 error = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 452 sc->sc_addr, &cmd, 1, &data, 2, 0); 453 iic_release_bus(sc->sc_i2c, 0); 454 } else { 455 int diff; 456 int nduty = f->duty; 457 int current_speed; 458 /* pwm fan, measure speed, then adjust duty cycle */ 459 DPRINTF("pwm fan "); 460 current_speed = fcu_get_rpm(sc, which); 461 diff = current_speed - speed; 462 DPRINTF("d %d s %d t %d diff %d ", f->duty, current_speed, speed, diff); 463 if (diff > 100) { 464 nduty = uimax(20, nduty - 1); 465 } 466 if (diff < -100) { 467 nduty = uimin(0xd0, nduty + 1); 468 } 469 cmd = f->reg; 470 DPRINTF("%s nduty %d", __func__, nduty); 471 if (nduty != f->duty) { 472 uint8_t arg = nduty; 473 iic_acquire_bus(sc->sc_i2c, 0); 474 error = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 475 sc->sc_addr, &cmd, 1, &arg, 1, 0); 476 iic_release_bus(sc->sc_i2c, 0); 477 f->duty = nduty; 478 sc->sc_pwm = TRUE; 479 480 } 481 DPRINTF("ok\n"); 482 } 483 if (error) printf("boo\n"); 484 f->target = speed; 485 return 0; 486} 487 488static void 489fcu_adjust(void *cookie) 490{ 491 struct fcu_softc *sc = cookie; 492 int i; 493 uint8_t cmd, data; 494 495 while (!sc->sc_dying) { 496 /* poke the FCU so we don't go 747 */ 497 iic_acquire_bus(sc->sc_i2c, 0); 498 cmd = FCU_FAN_ACTIVE; 499 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 500 sc->sc_addr, &cmd, 1, &data, 1, 0); 501 iic_release_bus(sc->sc_i2c, 0); 502 sc->sc_pwm = FALSE; 503 for (i = 0; i < FCU_ZONE_COUNT; i++) 504 fancontrol_adjust_zone(&sc->sc_zones[i]); 505 /* 506 * take a shorter nap if we're in the process of adjusting a 507 * PWM fan, which relies on measuring speed and then changing 508 * its duty cycle until we're reasonable close to the target 509 * speed 510 */ 511 kpause("fanctrl", true, mstohz(sc->sc_pwm ? 1000 : 2000), NULL); 512 } 513 kthread_exit(0); 514} 515