fcu.c revision 217286
1/*- 2 * Copyright (c) 2010 Andreas Tobler 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: head/sys/powerpc/powermac/fcu.c 217286 2011-01-11 21:18:29Z andreast $"); 29 30#include <sys/param.h> 31#include <sys/bus.h> 32#include <sys/systm.h> 33#include <sys/module.h> 34#include <sys/callout.h> 35#include <sys/conf.h> 36#include <sys/cpu.h> 37#include <sys/ctype.h> 38#include <sys/kernel.h> 39#include <sys/reboot.h> 40#include <sys/rman.h> 41#include <sys/sysctl.h> 42#include <sys/limits.h> 43 44#include <machine/bus.h> 45#include <machine/md_var.h> 46 47#include <dev/iicbus/iicbus.h> 48#include <dev/iicbus/iiconf.h> 49 50#include <dev/ofw/openfirm.h> 51#include <dev/ofw/ofw_bus.h> 52 53/* FCU registers 54 * /u3@0,f8000000/i2c@f8001000/fan@15e 55 */ 56#define FCU_RPM_FAIL 0x0b /* fans states in bits 0<1-6>7 */ 57#define FCU_RPM_AVAILABLE 0x0c 58#define FCU_RPM_ACTIVE 0x0d 59#define FCU_RPM_READ(x) 0x11 + (x) * 2 60#define FCU_RPM_SET(x) 0x10 + (x) * 2 61 62#define FCU_PWM_FAIL 0x2b 63#define FCU_PWM_AVAILABLE 0x2c 64#define FCU_PWM_ACTIVE 0x2d 65#define FCU_PWM_RPM(x) 0x31 + (x) * 2 /* Get RPM. */ 66#define FCU_PWM_SGET(x) 0x30 + (x) * 2 /* Set or get PWM. */ 67 68struct fcu_fan { 69 int id; 70 cell_t min; 71 cell_t max; 72 char location[32]; 73 enum { 74 FCU_FAN_RPM, 75 FCU_FAN_PWM 76 } type; 77 int setpoint; 78 int rpm; 79}; 80 81struct fcu_softc { 82 device_t sc_dev; 83 struct intr_config_hook enum_hook; 84 uint32_t sc_addr; 85 struct fcu_fan *sc_fans; 86 int sc_nfans; 87}; 88 89/* We can read the PWM and the RPM from a PWM controlled fan. 90 * Offer both values via sysctl. 91 */ 92enum { 93 FCU_PWM_SYSCTL_PWM = 1 << 8, 94 FCU_PWM_SYSCTL_RPM = 2 << 8 95}; 96 97static int fcu_rpm_shift; 98 99/* Regular bus attachment functions */ 100static int fcu_probe(device_t); 101static int fcu_attach(device_t); 102 103/* Utility functions */ 104static void fcu_attach_fans(device_t dev); 105static int fcu_fill_fan_prop(device_t dev); 106static int fcu_fan_set_rpm(device_t dev, struct fcu_fan *fan, int rpm); 107static int fcu_fan_get_rpm(device_t dev, struct fcu_fan *fan, int *rpm); 108static int fcu_fan_set_pwm(device_t dev, struct fcu_fan *fan, int pwm); 109static int fcu_fan_get_pwm(device_t dev, struct fcu_fan *fan, int *pwm, 110 int *rpm); 111static int fcu_fanrpm_sysctl(SYSCTL_HANDLER_ARGS); 112static void fcu_start(void *xdev); 113static int fcu_write(device_t dev, uint32_t addr, uint8_t reg, uint8_t *buf, 114 int len); 115static int fcu_read_1(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data); 116 117static device_method_t fcu_methods[] = { 118 /* Device interface */ 119 DEVMETHOD(device_probe, fcu_probe), 120 DEVMETHOD(device_attach, fcu_attach), 121 { 0, 0 }, 122}; 123 124static driver_t fcu_driver = { 125 "fcu", 126 fcu_methods, 127 sizeof(struct fcu_softc) 128}; 129 130static devclass_t fcu_devclass; 131 132DRIVER_MODULE(fcu, iicbus, fcu_driver, fcu_devclass, 0, 0); 133MALLOC_DEFINE(M_FCU, "fcu", "FCU Sensor Information"); 134 135static int 136fcu_write(device_t dev, uint32_t addr, uint8_t reg, uint8_t *buff, 137 int len) 138{ 139 unsigned char buf[4]; 140 struct iic_msg msg[] = { 141 { addr, IIC_M_WR, 0, buf } 142 }; 143 144 msg[0].len = len + 1; 145 buf[0] = reg; 146 memcpy(buf + 1, buff, len); 147 if (iicbus_transfer(dev, msg, 1) != 0) { 148 device_printf(dev, "iicbus write failed\n"); 149 return (EIO); 150 } 151 152 return (0); 153 154} 155 156static int 157fcu_read_1(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data) 158{ 159 uint8_t buf[4]; 160 161 struct iic_msg msg[2] = { 162 { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, 163 { addr, IIC_M_RD, 1, buf }, 164 }; 165 166 if (iicbus_transfer(dev, msg, 2) != 0) { 167 device_printf(dev, "iicbus read failed\n"); 168 return (EIO); 169 } 170 171 *data = *((uint8_t*)buf); 172 173 return (0); 174} 175 176static int 177fcu_probe(device_t dev) 178{ 179 const char *name, *compatible; 180 struct fcu_softc *sc; 181 182 name = ofw_bus_get_name(dev); 183 compatible = ofw_bus_get_compat(dev); 184 185 if (!name) 186 return (ENXIO); 187 188 if (strcmp(name, "fan") != 0 || strcmp(compatible, "fcu") != 0) 189 return (ENXIO); 190 191 sc = device_get_softc(dev); 192 sc->sc_dev = dev; 193 sc->sc_addr = iicbus_get_addr(dev); 194 195 device_set_desc(dev, "Apple Fan Control Unit"); 196 197 return (0); 198} 199 200static int 201fcu_attach(device_t dev) 202{ 203 struct fcu_softc *sc; 204 205 sc = device_get_softc(dev); 206 207 sc->enum_hook.ich_func = fcu_start; 208 sc->enum_hook.ich_arg = dev; 209 210 /* We have to wait until interrupts are enabled. I2C read and write 211 * only works if the interrupts are available. 212 * The unin/i2c is controlled by the htpic on unin. But this is not 213 * the master. The openpic on mac-io is controlling the htpic. 214 * This one gets attached after the mac-io probing and then the 215 * interrupts will be available. 216 */ 217 218 if (config_intrhook_establish(&sc->enum_hook) != 0) 219 return (ENOMEM); 220 221 return (0); 222} 223 224static void 225fcu_start(void *xdev) 226{ 227 unsigned char buf[1] = { 0xff }; 228 struct fcu_softc *sc; 229 230 device_t dev = (device_t)xdev; 231 232 sc = device_get_softc(dev); 233 234 /* Start the fcu device. */ 235 fcu_write(sc->sc_dev, sc->sc_addr, 0xe, buf, 1); 236 fcu_write(sc->sc_dev, sc->sc_addr, 0x2e, buf, 1); 237 fcu_read_1(sc->sc_dev, sc->sc_addr, 0, buf); 238 fcu_rpm_shift = (buf[0] == 1) ? 2 : 3; 239 240 device_printf(dev, "FCU initialized, RPM shift: %d\n", 241 fcu_rpm_shift); 242 243 /* Detect and attach child devices. */ 244 245 fcu_attach_fans(dev); 246 247 config_intrhook_disestablish(&sc->enum_hook); 248 249} 250 251static int 252fcu_fan_set_rpm(device_t dev, struct fcu_fan *fan, int rpm) 253{ 254 uint8_t reg; 255 struct fcu_softc *sc; 256 unsigned char buf[2]; 257 258 sc = device_get_softc(dev); 259 260 /* Clamp to allowed range */ 261 rpm = max(fan->min, rpm); 262 rpm = min(fan->max, rpm); 263 264 if (fan->type == FCU_FAN_RPM) { 265 reg = FCU_RPM_SET(fan->id); 266 fan->setpoint = rpm; 267 } else { 268 device_printf(dev, "Unknown fan type: %d\n", fan->type); 269 return (EIO); 270 } 271 272 buf[0] = rpm >> (8 - fcu_rpm_shift); 273 buf[1] = rpm << fcu_rpm_shift; 274 275 fcu_write(sc->sc_dev, sc->sc_addr, reg, buf, 2); 276 277 return (0); 278} 279 280static int 281fcu_fan_get_rpm(device_t dev, struct fcu_fan *fan, int *rpm) 282{ 283 uint8_t reg; 284 struct fcu_softc *sc; 285 uint8_t buff[2] = { 0, 0 }; 286 uint8_t active = 0, avail = 0, fail = 0; 287 288 sc = device_get_softc(dev); 289 290 if (fan->type == FCU_FAN_RPM) { 291 /* Check if the fan is available. */ 292 reg = FCU_RPM_AVAILABLE; 293 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &avail); 294 if ((avail & (1 << fan->id)) == 0) { 295 device_printf(dev, "RPM Fan not available ID: %d\n", 296 fan->id); 297 return (EIO); 298 } 299 /* Check if we have a failed fan. */ 300 reg = FCU_RPM_FAIL; 301 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &fail); 302 if ((fail & (1 << fan->id)) != 0) { 303 device_printf(dev, "RPM Fan failed ID: %d\n", fan->id); 304 return (EIO); 305 } 306 /* Check if fan is active. */ 307 reg = FCU_RPM_ACTIVE; 308 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &active); 309 if ((active & (1 << fan->id)) == 0) { 310 device_printf(dev, "RPM Fan not active ID: %d\n", 311 fan->id); 312 return (ENXIO); 313 } 314 reg = FCU_RPM_READ(fan->id); 315 316 } else { 317 device_printf(dev, "Unknown fan type: %d\n", fan->type); 318 return (EIO); 319 } 320 321 /* It seems that we can read the fans rpm. */ 322 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, buff); 323 324 *rpm = (buff[0] << (8 - fcu_rpm_shift)) | buff[1] >> fcu_rpm_shift; 325 326 return (0); 327} 328 329static int 330fcu_fan_set_pwm(device_t dev, struct fcu_fan *fan, int pwm) 331{ 332 uint8_t reg; 333 struct fcu_softc *sc; 334 uint8_t buf[2]; 335 336 sc = device_get_softc(dev); 337 338 /* Clamp to allowed range */ 339 pwm = max(fan->min, pwm); 340 pwm = min(fan->max, pwm); 341 342 if (fan->type == FCU_FAN_PWM) { 343 reg = FCU_PWM_SGET(fan->id); 344 if (pwm > 100) 345 pwm = 100; 346 if (pwm < 30) 347 pwm = 30; 348 fan->setpoint = pwm; 349 } else { 350 device_printf(dev, "Unknown fan type: %d\n", fan->type); 351 return (EIO); 352 } 353 354 buf[0] = (pwm * 2550) / 1000; 355 356 fcu_write(sc->sc_dev, sc->sc_addr, reg, buf, 1); 357 358 return (0); 359} 360 361static int 362fcu_fan_get_pwm(device_t dev, struct fcu_fan *fan, int *pwm, int *rpm) 363{ 364 uint8_t reg; 365 struct fcu_softc *sc; 366 uint8_t buf[2]; 367 uint8_t active = 0, avail = 0, fail = 0; 368 369 sc = device_get_softc(dev); 370 371 if (fan->type == FCU_FAN_PWM) { 372 /* Check if the fan is available. */ 373 reg = FCU_PWM_AVAILABLE; 374 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &avail); 375 if ((avail & (1 << fan->id)) == 0) { 376 device_printf(dev, "PWM Fan not available ID: %d\n", 377 fan->id); 378 return (EIO); 379 } 380 /* Check if we have a failed fan. */ 381 reg = FCU_PWM_FAIL; 382 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &fail); 383 if ((fail & (1 << fan->id)) != 0) { 384 device_printf(dev, "PWM Fan failed ID: %d\n", fan->id); 385 return (EIO); 386 } 387 /* Check if fan is active. */ 388 reg = FCU_PWM_ACTIVE; 389 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &active); 390 if ((active & (1 << fan->id)) == 0) { 391 device_printf(dev, "PWM Fan not active ID: %d\n", 392 fan->id); 393 return (ENXIO); 394 } 395 reg = FCU_PWM_SGET(fan->id); 396 } else { 397 device_printf(dev, "Unknown fan type: %d\n", fan->type); 398 return (EIO); 399 } 400 401 /* It seems that we can read the fans pwm. */ 402 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, buf); 403 404 *pwm = (buf[0] * 1000) / 2550; 405 406 /* Now read the rpm. */ 407 reg = FCU_PWM_RPM(fan->id); 408 fcu_read_1(sc->sc_dev, sc->sc_addr, reg, buf); 409 *rpm = (buf[0] << (8 - fcu_rpm_shift)) | buf[1] >> fcu_rpm_shift; 410 411 return (0); 412} 413 414/* 415 * This function returns the number of fans. If we call it the second time 416 * and we have allocated memory for sc->sc_fans, we fill in the properties. 417 */ 418static int 419fcu_fill_fan_prop(device_t dev) 420{ 421 phandle_t child; 422 struct fcu_softc *sc; 423 u_int id[8]; 424 char location[96]; 425 char type[64]; 426 int i = 0, j, len = 0, prop_len, prev_len = 0; 427 428 sc = device_get_softc(dev); 429 430 child = ofw_bus_get_node(dev); 431 432 /* Fill the fan location property. */ 433 prop_len = OF_getprop(child, "hwctrl-location", location, 434 sizeof(location)); 435 while (len < prop_len) { 436 if (sc->sc_fans != NULL) { 437 strcpy(sc->sc_fans[i].location, location + len); 438 } 439 prev_len = strlen(location + len) + 1; 440 len += prev_len; 441 i++; 442 } 443 if (sc->sc_fans == NULL) 444 return (i); 445 446 /* Fill the fan type property. */ 447 len = 0; 448 i = 0; 449 prev_len = 0; 450 prop_len = OF_getprop(child, "hwctrl-type", type, sizeof(type)); 451 while (len < prop_len) { 452 if (strcmp(type + len, "fan-rpm") == 0) 453 sc->sc_fans[i].type = FCU_FAN_RPM; 454 else 455 sc->sc_fans[i].type = FCU_FAN_PWM; 456 prev_len = strlen(type + len) + 1; 457 len += prev_len; 458 i++; 459 } 460 461 /* Fill the fan ID property. */ 462 prop_len = OF_getprop(child, "hwctrl-id", id, sizeof(id)); 463 for (j = 0; j < i; j++) 464 sc->sc_fans[j].id = ((id[j] >> 8) & 0x0f) % 8; 465 466 return (i); 467} 468 469static int 470fcu_fanrpm_sysctl(SYSCTL_HANDLER_ARGS) 471{ 472 device_t fcu; 473 struct fcu_softc *sc; 474 struct fcu_fan *fan; 475 int rpm = 0, pwm = 0, error; 476 477 fcu = arg1; 478 sc = device_get_softc(fcu); 479 fan = &sc->sc_fans[arg2 & 0x00ff]; 480 if (fan->type == FCU_FAN_RPM) { 481 fcu_fan_get_rpm(fcu, fan, &rpm); 482 error = sysctl_handle_int(oidp, &rpm, 0, req); 483 } else { 484 fcu_fan_get_pwm(fcu, fan, &pwm, &rpm); 485 486 switch (arg2 & 0xff00) { 487 case FCU_PWM_SYSCTL_PWM: 488 error = sysctl_handle_int(oidp, &pwm, 0, req); 489 break; 490 case FCU_PWM_SYSCTL_RPM: 491 error = sysctl_handle_int(oidp, &rpm, 0, req); 492 break; 493 default: 494 /* This should never happen */ 495 error = -1; 496 }; 497 } 498 499 /* We can only read the RPM from a PWM controlled fan, so return. */ 500 if ((arg2 & 0xff00) == FCU_PWM_SYSCTL_RPM) 501 return (0); 502 503 if (error || !req->newptr) 504 return (error); 505 506 if (fan->type == FCU_FAN_RPM) 507 return (fcu_fan_set_rpm(fcu, fan, rpm)); 508 else 509 return (fcu_fan_set_pwm(fcu, fan, pwm)); 510} 511 512static void 513fcu_attach_fans(device_t dev) 514{ 515 struct fcu_softc *sc; 516 struct sysctl_oid *oid, *fanroot_oid; 517 struct sysctl_ctx_list *ctx; 518 char sysctl_name[32]; 519 int i, j; 520 521 sc = device_get_softc(dev); 522 523 sc->sc_nfans = 0; 524 525 /* Count the actual number of fans. */ 526 sc->sc_nfans = fcu_fill_fan_prop(dev); 527 528 device_printf(dev, "%d fans detected!\n", sc->sc_nfans); 529 530 if (sc->sc_nfans == 0) { 531 device_printf(dev, "WARNING: No fans detected!\n"); 532 return; 533 } 534 535 sc->sc_fans = malloc(sc->sc_nfans * sizeof(struct fcu_fan), M_FCU, 536 M_WAITOK | M_ZERO); 537 538 ctx = device_get_sysctl_ctx(dev); 539 fanroot_oid = SYSCTL_ADD_NODE(ctx, 540 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fans", 541 CTLFLAG_RD, 0, "FCU Fan Information"); 542 543 /* Now we can fill the properties into the allocated struct. */ 544 sc->sc_nfans = fcu_fill_fan_prop(dev); 545 546 /* Add sysctls for the fans. */ 547 for (i = 0; i < sc->sc_nfans; i++) { 548 for (j = 0; j < strlen(sc->sc_fans[i].location); j++) { 549 sysctl_name[j] = tolower(sc->sc_fans[i].location[j]); 550 if (isspace(sysctl_name[j])) 551 sysctl_name[j] = '_'; 552 } 553 sysctl_name[j] = 0; 554 555 if (sc->sc_fans[i].type == FCU_FAN_RPM) { 556 sc->sc_fans[i].min = 2400 >> fcu_rpm_shift; 557 sc->sc_fans[i].max = 56000 >> fcu_rpm_shift; 558 fcu_fan_get_rpm(dev, &sc->sc_fans[i], 559 &sc->sc_fans[i].setpoint); 560 561 oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid), 562 OID_AUTO, sysctl_name, 563 CTLFLAG_RD, 0, "Fan Information"); 564 SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, 565 "minrpm", CTLTYPE_INT | CTLFLAG_RD, 566 &(sc->sc_fans[i].min), sizeof(cell_t), 567 "Minimum allowed RPM"); 568 SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, 569 "maxrpm", CTLTYPE_INT | CTLFLAG_RD, 570 &(sc->sc_fans[i].max), sizeof(cell_t), 571 "Maximum allowed RPM"); 572 /* I use i to pass the fan id. */ 573 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, 574 "rpm", CTLTYPE_INT | CTLFLAG_RW, dev, i, 575 fcu_fanrpm_sysctl, "I", "Fan RPM"); 576 } else { 577 sc->sc_fans[i].min = 30; 578 sc->sc_fans[i].max = 100; 579 fcu_fan_get_pwm(dev, &sc->sc_fans[i], 580 &sc->sc_fans[i].setpoint, 581 &sc->sc_fans[i].rpm); 582 583 oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid), 584 OID_AUTO, sysctl_name, 585 CTLFLAG_RD, 0, "Fan Information"); 586 SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, 587 "minpwm", CTLTYPE_INT | CTLFLAG_RD, 588 &(sc->sc_fans[i].min), sizeof(cell_t), 589 "Minimum allowed PWM in %"); 590 SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, 591 "maxpwm", CTLTYPE_INT | CTLFLAG_RD, 592 &(sc->sc_fans[i].max), sizeof(cell_t), 593 "Maximum allowed PWM in %"); 594 /* I use i to pass the fan id or'ed with the type 595 * of info I want to display/modify. 596 */ 597 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, 598 "pwm", CTLTYPE_INT | CTLFLAG_RW, dev, 599 FCU_PWM_SYSCTL_PWM | i, 600 fcu_fanrpm_sysctl, "I", "Fan PWM in %"); 601 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, 602 "rpm", CTLTYPE_INT | CTLFLAG_RD, dev, 603 FCU_PWM_SYSCTL_RPM | i, 604 fcu_fanrpm_sysctl, "I", "Fan RPM"); 605 } 606 } 607 608 /* Dump fan location, type & RPM. */ 609 if (bootverbose) { 610 device_printf(dev, "Fans\n"); 611 for (i = 0; i < sc->sc_nfans; i++) { 612 device_printf(dev, "Location: %s type: %d ID: %d " 613 "RPM: %d\n", sc->sc_fans[i].location, 614 sc->sc_fans[i].type, sc->sc_fans[i].id, 615 (sc->sc_fans[i].type == FCU_FAN_RPM) ? 616 sc->sc_fans[i].setpoint : 617 sc->sc_fans[i].rpm ); 618 } 619 } 620} 621