/*- * Copyright (c) 2010 Andreas Tobler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* FCU registers * /u3@0,f8000000/i2c@f8001000/fan@15e */ #define FCU_RPM_FAIL 0x0b /* fans states in bits 0<1-6>7 */ #define FCU_RPM_AVAILABLE 0x0c #define FCU_RPM_ACTIVE 0x0d #define FCU_RPM_READ(x) 0x11 + (x) * 2 #define FCU_RPM_SET(x) 0x10 + (x) * 2 #define FCU_PWM_FAIL 0x2b #define FCU_PWM_AVAILABLE 0x2c #define FCU_PWM_ACTIVE 0x2d #define FCU_PWM_RPM(x) 0x31 + (x) * 2 /* Get RPM. */ #define FCU_PWM_SGET(x) 0x30 + (x) * 2 /* Set or get PWM. */ struct fcu_fan { int id; cell_t min; cell_t max; char location[32]; enum { FCU_FAN_RPM, FCU_FAN_PWM } type; int setpoint; int rpm; }; struct fcu_softc { device_t sc_dev; struct intr_config_hook enum_hook; uint32_t sc_addr; struct fcu_fan *sc_fans; int sc_nfans; }; /* We can read the PWM and the RPM from a PWM controlled fan. * Offer both values via sysctl. */ enum { FCU_PWM_SYSCTL_PWM = 1 << 8, FCU_PWM_SYSCTL_RPM = 2 << 8 }; static int fcu_rpm_shift; /* Regular bus attachment functions */ static int fcu_probe(device_t); static int fcu_attach(device_t); /* Utility functions */ static void fcu_attach_fans(device_t dev); static int fcu_fill_fan_prop(device_t dev); static int fcu_fan_set_rpm(device_t dev, struct fcu_fan *fan, int rpm); static int fcu_fan_get_rpm(device_t dev, struct fcu_fan *fan, int *rpm); static int fcu_fan_set_pwm(device_t dev, struct fcu_fan *fan, int pwm); static int fcu_fan_get_pwm(device_t dev, struct fcu_fan *fan, int *pwm, int *rpm); static int fcu_fanrpm_sysctl(SYSCTL_HANDLER_ARGS); static void fcu_start(void *xdev); static int fcu_write(device_t dev, uint32_t addr, uint8_t reg, uint8_t *buf, int len); static int fcu_read_1(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data); static device_method_t fcu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fcu_probe), DEVMETHOD(device_attach, fcu_attach), { 0, 0 }, }; static driver_t fcu_driver = { "fcu", fcu_methods, sizeof(struct fcu_softc) }; static devclass_t fcu_devclass; DRIVER_MODULE(fcu, iicbus, fcu_driver, fcu_devclass, 0, 0); MALLOC_DEFINE(M_FCU, "fcu", "FCU Sensor Information"); static int fcu_write(device_t dev, uint32_t addr, uint8_t reg, uint8_t *buff, int len) { unsigned char buf[4]; struct iic_msg msg[] = { { addr, IIC_M_WR, 0, buf } }; msg[0].len = len + 1; buf[0] = reg; memcpy(buf + 1, buff, len); if (iicbus_transfer(dev, msg, 1) != 0) { device_printf(dev, "iicbus write failed\n"); return (EIO); } return (0); } static int fcu_read_1(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data) { uint8_t buf[4]; struct iic_msg msg[2] = { { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, { addr, IIC_M_RD, 1, buf }, }; if (iicbus_transfer(dev, msg, 2) != 0) { device_printf(dev, "iicbus read failed\n"); return (EIO); } *data = *((uint8_t*)buf); return (0); } static int fcu_probe(device_t dev) { const char *name, *compatible; struct fcu_softc *sc; name = ofw_bus_get_name(dev); compatible = ofw_bus_get_compat(dev); if (!name) return (ENXIO); if (strcmp(name, "fan") != 0 || strcmp(compatible, "fcu") != 0) return (ENXIO); sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_addr = iicbus_get_addr(dev); device_set_desc(dev, "Apple Fan Control Unit"); return (0); } static int fcu_attach(device_t dev) { struct fcu_softc *sc; sc = device_get_softc(dev); sc->enum_hook.ich_func = fcu_start; sc->enum_hook.ich_arg = dev; /* We have to wait until interrupts are enabled. I2C read and write * only works if the interrupts are available. * The unin/i2c is controlled by the htpic on unin. But this is not * the master. The openpic on mac-io is controlling the htpic. * This one gets attached after the mac-io probing and then the * interrupts will be available. */ if (config_intrhook_establish(&sc->enum_hook) != 0) return (ENOMEM); return (0); } static void fcu_start(void *xdev) { unsigned char buf[1] = { 0xff }; struct fcu_softc *sc; device_t dev = (device_t)xdev; sc = device_get_softc(dev); /* Start the fcu device. */ fcu_write(sc->sc_dev, sc->sc_addr, 0xe, buf, 1); fcu_write(sc->sc_dev, sc->sc_addr, 0x2e, buf, 1); fcu_read_1(sc->sc_dev, sc->sc_addr, 0, buf); fcu_rpm_shift = (buf[0] == 1) ? 2 : 3; device_printf(dev, "FCU initialized, RPM shift: %d\n", fcu_rpm_shift); /* Detect and attach child devices. */ fcu_attach_fans(dev); config_intrhook_disestablish(&sc->enum_hook); } static int fcu_fan_set_rpm(device_t dev, struct fcu_fan *fan, int rpm) { uint8_t reg; struct fcu_softc *sc; unsigned char buf[2]; sc = device_get_softc(dev); /* Clamp to allowed range */ rpm = max(fan->min, rpm); rpm = min(fan->max, rpm); if (fan->type == FCU_FAN_RPM) { reg = FCU_RPM_SET(fan->id); fan->setpoint = rpm; } else { device_printf(dev, "Unknown fan type: %d\n", fan->type); return (EIO); } buf[0] = rpm >> (8 - fcu_rpm_shift); buf[1] = rpm << fcu_rpm_shift; fcu_write(sc->sc_dev, sc->sc_addr, reg, buf, 2); return (0); } static int fcu_fan_get_rpm(device_t dev, struct fcu_fan *fan, int *rpm) { uint8_t reg; struct fcu_softc *sc; uint8_t buff[2] = { 0, 0 }; uint8_t active = 0, avail = 0, fail = 0; sc = device_get_softc(dev); if (fan->type == FCU_FAN_RPM) { /* Check if the fan is available. */ reg = FCU_RPM_AVAILABLE; fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &avail); if ((avail & (1 << fan->id)) == 0) { device_printf(dev, "RPM Fan not available ID: %d\n", fan->id); return (EIO); } /* Check if we have a failed fan. */ reg = FCU_RPM_FAIL; fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &fail); if ((fail & (1 << fan->id)) != 0) { device_printf(dev, "RPM Fan failed ID: %d\n", fan->id); return (EIO); } /* Check if fan is active. */ reg = FCU_RPM_ACTIVE; fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &active); if ((active & (1 << fan->id)) == 0) { device_printf(dev, "RPM Fan not active ID: %d\n", fan->id); return (ENXIO); } reg = FCU_RPM_READ(fan->id); } else { device_printf(dev, "Unknown fan type: %d\n", fan->type); return (EIO); } /* It seems that we can read the fans rpm. */ fcu_read_1(sc->sc_dev, sc->sc_addr, reg, buff); *rpm = (buff[0] << (8 - fcu_rpm_shift)) | buff[1] >> fcu_rpm_shift; return (0); } static int fcu_fan_set_pwm(device_t dev, struct fcu_fan *fan, int pwm) { uint8_t reg; struct fcu_softc *sc; uint8_t buf[2]; sc = device_get_softc(dev); /* Clamp to allowed range */ pwm = max(fan->min, pwm); pwm = min(fan->max, pwm); if (fan->type == FCU_FAN_PWM) { reg = FCU_PWM_SGET(fan->id); if (pwm > 100) pwm = 100; if (pwm < 30) pwm = 30; fan->setpoint = pwm; } else { device_printf(dev, "Unknown fan type: %d\n", fan->type); return (EIO); } buf[0] = (pwm * 2550) / 1000; fcu_write(sc->sc_dev, sc->sc_addr, reg, buf, 1); return (0); } static int fcu_fan_get_pwm(device_t dev, struct fcu_fan *fan, int *pwm, int *rpm) { uint8_t reg; struct fcu_softc *sc; uint8_t buf[2]; uint8_t active = 0, avail = 0, fail = 0; sc = device_get_softc(dev); if (fan->type == FCU_FAN_PWM) { /* Check if the fan is available. */ reg = FCU_PWM_AVAILABLE; fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &avail); if ((avail & (1 << fan->id)) == 0) { device_printf(dev, "PWM Fan not available ID: %d\n", fan->id); return (EIO); } /* Check if we have a failed fan. */ reg = FCU_PWM_FAIL; fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &fail); if ((fail & (1 << fan->id)) != 0) { device_printf(dev, "PWM Fan failed ID: %d\n", fan->id); return (EIO); } /* Check if fan is active. */ reg = FCU_PWM_ACTIVE; fcu_read_1(sc->sc_dev, sc->sc_addr, reg, &active); if ((active & (1 << fan->id)) == 0) { device_printf(dev, "PWM Fan not active ID: %d\n", fan->id); return (ENXIO); } reg = FCU_PWM_SGET(fan->id); } else { device_printf(dev, "Unknown fan type: %d\n", fan->type); return (EIO); } /* It seems that we can read the fans pwm. */ fcu_read_1(sc->sc_dev, sc->sc_addr, reg, buf); *pwm = (buf[0] * 1000) / 2550; /* Now read the rpm. */ reg = FCU_PWM_RPM(fan->id); fcu_read_1(sc->sc_dev, sc->sc_addr, reg, buf); *rpm = (buf[0] << (8 - fcu_rpm_shift)) | buf[1] >> fcu_rpm_shift; return (0); } /* * This function returns the number of fans. If we call it the second time * and we have allocated memory for sc->sc_fans, we fill in the properties. */ static int fcu_fill_fan_prop(device_t dev) { phandle_t child; struct fcu_softc *sc; u_int id[8]; char location[96]; char type[64]; int i = 0, j, len = 0, prop_len, prev_len = 0; sc = device_get_softc(dev); child = ofw_bus_get_node(dev); /* Fill the fan location property. */ prop_len = OF_getprop(child, "hwctrl-location", location, sizeof(location)); while (len < prop_len) { if (sc->sc_fans != NULL) { strcpy(sc->sc_fans[i].location, location + len); } prev_len = strlen(location + len) + 1; len += prev_len; i++; } if (sc->sc_fans == NULL) return (i); /* Fill the fan type property. */ len = 0; i = 0; prev_len = 0; prop_len = OF_getprop(child, "hwctrl-type", type, sizeof(type)); while (len < prop_len) { if (strcmp(type + len, "fan-rpm") == 0) sc->sc_fans[i].type = FCU_FAN_RPM; else sc->sc_fans[i].type = FCU_FAN_PWM; prev_len = strlen(type + len) + 1; len += prev_len; i++; } /* Fill the fan ID property. */ prop_len = OF_getprop(child, "hwctrl-id", id, sizeof(id)); for (j = 0; j < i; j++) sc->sc_fans[j].id = ((id[j] >> 8) & 0x0f) % 8; return (i); } static int fcu_fanrpm_sysctl(SYSCTL_HANDLER_ARGS) { device_t fcu; struct fcu_softc *sc; struct fcu_fan *fan; int rpm = 0, pwm = 0, error; fcu = arg1; sc = device_get_softc(fcu); fan = &sc->sc_fans[arg2 & 0x00ff]; if (fan->type == FCU_FAN_RPM) { fcu_fan_get_rpm(fcu, fan, &rpm); error = sysctl_handle_int(oidp, &rpm, 0, req); } else { fcu_fan_get_pwm(fcu, fan, &pwm, &rpm); switch (arg2 & 0xff00) { case FCU_PWM_SYSCTL_PWM: error = sysctl_handle_int(oidp, &pwm, 0, req); break; case FCU_PWM_SYSCTL_RPM: error = sysctl_handle_int(oidp, &rpm, 0, req); break; default: /* This should never happen */ error = -1; }; } /* We can only read the RPM from a PWM controlled fan, so return. */ if ((arg2 & 0xff00) == FCU_PWM_SYSCTL_RPM) return (0); if (error || !req->newptr) return (error); if (fan->type == FCU_FAN_RPM) return (fcu_fan_set_rpm(fcu, fan, rpm)); else return (fcu_fan_set_pwm(fcu, fan, pwm)); } static void fcu_attach_fans(device_t dev) { struct fcu_softc *sc; struct sysctl_oid *oid, *fanroot_oid; struct sysctl_ctx_list *ctx; char sysctl_name[32]; int i, j; sc = device_get_softc(dev); sc->sc_nfans = 0; /* Count the actual number of fans. */ sc->sc_nfans = fcu_fill_fan_prop(dev); device_printf(dev, "%d fans detected!\n", sc->sc_nfans); if (sc->sc_nfans == 0) { device_printf(dev, "WARNING: No fans detected!\n"); return; } sc->sc_fans = malloc(sc->sc_nfans * sizeof(struct fcu_fan), M_FCU, M_WAITOK | M_ZERO); ctx = device_get_sysctl_ctx(dev); fanroot_oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fans", CTLFLAG_RD, 0, "FCU Fan Information"); /* Now we can fill the properties into the allocated struct. */ sc->sc_nfans = fcu_fill_fan_prop(dev); /* Add sysctls for the fans. */ for (i = 0; i < sc->sc_nfans; i++) { for (j = 0; j < strlen(sc->sc_fans[i].location); j++) { sysctl_name[j] = tolower(sc->sc_fans[i].location[j]); if (isspace(sysctl_name[j])) sysctl_name[j] = '_'; } sysctl_name[j] = 0; if (sc->sc_fans[i].type == FCU_FAN_RPM) { sc->sc_fans[i].min = 2400 >> fcu_rpm_shift; sc->sc_fans[i].max = 56000 >> fcu_rpm_shift; fcu_fan_get_rpm(dev, &sc->sc_fans[i], &sc->sc_fans[i].setpoint); oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid), OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Fan Information"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "minrpm", CTLTYPE_INT | CTLFLAG_RD, &(sc->sc_fans[i].min), sizeof(cell_t), "Minimum allowed RPM"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "maxrpm", CTLTYPE_INT | CTLFLAG_RD, &(sc->sc_fans[i].max), sizeof(cell_t), "Maximum allowed RPM"); /* I use i to pass the fan id. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "rpm", CTLTYPE_INT | CTLFLAG_RW, dev, i, fcu_fanrpm_sysctl, "I", "Fan RPM"); } else { sc->sc_fans[i].min = 30; sc->sc_fans[i].max = 100; fcu_fan_get_pwm(dev, &sc->sc_fans[i], &sc->sc_fans[i].setpoint, &sc->sc_fans[i].rpm); oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid), OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Fan Information"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "minpwm", CTLTYPE_INT | CTLFLAG_RD, &(sc->sc_fans[i].min), sizeof(cell_t), "Minimum allowed PWM in %"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "maxpwm", CTLTYPE_INT | CTLFLAG_RD, &(sc->sc_fans[i].max), sizeof(cell_t), "Maximum allowed PWM in %"); /* I use i to pass the fan id or'ed with the type * of info I want to display/modify. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "pwm", CTLTYPE_INT | CTLFLAG_RW, dev, FCU_PWM_SYSCTL_PWM | i, fcu_fanrpm_sysctl, "I", "Fan PWM in %"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "rpm", CTLTYPE_INT | CTLFLAG_RD, dev, FCU_PWM_SYSCTL_RPM | i, fcu_fanrpm_sysctl, "I", "Fan RPM"); } } /* Dump fan location, type & RPM. */ if (bootverbose) { device_printf(dev, "Fans\n"); for (i = 0; i < sc->sc_nfans; i++) { device_printf(dev, "Location: %s type: %d ID: %d " "RPM: %d\n", sc->sc_fans[i].location, sc->sc_fans[i].type, sc->sc_fans[i].id, (sc->sc_fans[i].type == FCU_FAN_RPM) ? sc->sc_fans[i].setpoint : sc->sc_fans[i].rpm ); } } }