diff options
-rw-r--r-- | sys/conf/files.powerpc | 3 | ||||
-rw-r--r-- | sys/dev/iicbus/ds1775.c | 255 | ||||
-rw-r--r-- | sys/dev/iicbus/max6690.c | 335 | ||||
-rw-r--r-- | sys/powerpc/conf/GENERIC | 3 | ||||
-rw-r--r-- | sys/powerpc/conf/GENERIC64 | 3 | ||||
-rw-r--r-- | sys/powerpc/conf/NOTES | 3 | ||||
-rw-r--r-- | sys/powerpc/powermac/fcu.c | 506 |
7 files changed, 1108 insertions, 0 deletions
diff --git a/sys/conf/files.powerpc b/sys/conf/files.powerpc index e7d1d4d..0dfe031 100644 --- a/sys/conf/files.powerpc +++ b/sys/conf/files.powerpc @@ -26,6 +26,8 @@ dev/cfi/cfi_bus_fdt.c optional cfi fdt dev/fb/fb.c optional sc dev/fdt/fdt_powerpc.c optional fdt dev/hwpmc/hwpmc_powerpc.c optional hwpmc +dev/iicbus/ds1775.c optional ds1775 powermac +dev/iicbus/max6690.c optional max6690 powermac dev/kbd/kbd.c optional sc dev/mem/memutil.c optional mem dev/ofw/openfirm.c optional aim | fdt @@ -138,6 +140,7 @@ powerpc/powermac/ata_dbdma.c optional powermac ata | powermac atamacio powerpc/powermac/cuda.c optional powermac cuda powerpc/powermac/cpcht.c optional powermac pci powerpc/powermac/dbdma.c optional powermac pci +powerpc/powermac/fcu.c optional powermac fcu powerpc/powermac/grackle.c optional powermac pci powerpc/powermac/hrowpic.c optional powermac pci powerpc/powermac/kiic.c optional powermac kiic diff --git a/sys/dev/iicbus/ds1775.c b/sys/dev/iicbus/ds1775.c new file mode 100644 index 0000000..f066f2a --- /dev/null +++ b/sys/dev/iicbus/ds1775.c @@ -0,0 +1,255 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/callout.h> +#include <sys/conf.h> +#include <sys/cpu.h> +#include <sys/ctype.h> +#include <sys/kernel.h> +#include <sys/reboot.h> +#include <sys/rman.h> +#include <sys/sysctl.h> +#include <sys/limits.h> + +#include <machine/bus.h> +#include <machine/md_var.h> + +#include <dev/iicbus/iicbus.h> +#include <dev/iicbus/iiconf.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> + +#define FCU_ZERO_C_TO_K 2732 + +/* Drivebay sensor: LM75/DS1775. */ +#define DS1775_TEMP 0x0 + +struct ds1775_sensor { + char location[32]; +}; + +/* Regular bus attachment functions */ +static int ds1775_probe(device_t); +static int ds1775_attach(device_t); + +/* Utility functions */ +static int ds1775_sensor_sysctl(SYSCTL_HANDLER_ARGS); +static void ds1775_start(void *xdev); +static int ds1775_read_2(device_t dev, uint32_t addr, uint8_t reg, + uint16_t *data); + +struct ds1775_softc { + device_t sc_dev; + struct intr_config_hook enum_hook; + uint32_t sc_addr; + struct ds1775_sensor *sc_sensors; + +}; +static device_method_t ds1775_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ds1775_probe), + DEVMETHOD(device_attach, ds1775_attach), + { 0, 0 }, +}; + +static driver_t ds1775_driver = { + "ds1775", + ds1775_methods, + sizeof(struct ds1775_softc) +}; + +static devclass_t ds1775_devclass; + +DRIVER_MODULE(ds1755, iicbus, ds1775_driver, ds1775_devclass, 0, 0); +MALLOC_DEFINE(M_DS1775, "ds1775", "Temp-Monitor DS1775"); + +static int +ds1775_read_2(device_t dev, uint32_t addr, uint8_t reg, uint16_t *data) +{ + uint8_t buf[4]; + + struct iic_msg msg[2] = { + { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, + { addr, IIC_M_RD, 2, buf }, + }; + + if (iicbus_transfer(dev, msg, 2) != 0) { + device_printf(dev, "iicbus read failed\n"); + return (EIO); + } + + *data = *((uint16_t*)buf); + + return (0); +} + +static int +ds1775_probe(device_t dev) +{ + const char *name, *compatible; + struct ds1775_softc *sc; + + name = ofw_bus_get_name(dev); + compatible = ofw_bus_get_compat(dev); + + if (!name) + return (ENXIO); + + if (strcmp(name, "temp-monitor") != 0 || + strcmp(compatible, "ds1775") != 0) + return (ENXIO); + + sc = device_get_softc(dev); + sc->sc_dev = dev; + sc->sc_addr = iicbus_get_addr(dev); + + device_set_desc(dev, "Temp-Monitor DS1755"); + + return (0); +} + +static int +ds1775_attach(device_t dev) +{ + struct ds1775_softc *sc; + + sc = device_get_softc(dev); + + sc->enum_hook.ich_func = ds1775_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 +ds1775_start(void *xdev) +{ + phandle_t child; + struct ds1775_softc *sc; + struct ds1775_sensor *sens; + struct sysctl_oid *sensroot_oid; + struct sysctl_ctx_list *ctx; + int i; + char sysctl_name[40], sysctl_desc[40]; + const char *units; + + device_t dev = (device_t)xdev; + + sc = device_get_softc(dev); + + child = ofw_bus_get_node(dev); + + sc->sc_sensors = malloc (sizeof(struct ds1775_sensor), + M_DS1775, M_WAITOK | M_ZERO); + + sens = sc->sc_sensors; + + ctx = device_get_sysctl_ctx(dev); + sensroot_oid = device_get_sysctl_tree(dev); + + OF_getprop(child, "hwsensor-location", sens->location, + sizeof(sens->location)); + units = "C"; + + for (i = 0; i < strlen(sens->location); i++) { + sysctl_name[i] = tolower(sens->location[i]); + if (isspace(sysctl_name[i])) + sysctl_name[i] = '_'; + } + sysctl_name[i] = 0; + + sprintf(sysctl_desc,"%s (%s)", sens->location, units); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensroot_oid), OID_AUTO, + sysctl_name, + CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, + 0, ds1775_sensor_sysctl, "IK", sysctl_desc); + + config_intrhook_disestablish(&sc->enum_hook); +} + +static int +ds1775_sensor_read(device_t dev, struct ds1775_sensor *sens, int *temp) +{ + struct ds1775_softc *sc; + uint16_t buf[2]; + uint16_t read; + + sc = device_get_softc(dev); + + ds1775_read_2(sc->sc_dev, sc->sc_addr, DS1775_TEMP, buf); + + read = *((int16_t *)buf); + + /* The default mode of the ADC is 9 bit, the resolution is 0.5 C per + bit. The temperature is in tenth kelvin. + */ + *temp = ((int16_t)(read) >> 7) * 5; + + return (0); +} +static int +ds1775_sensor_sysctl(SYSCTL_HANDLER_ARGS) +{ + device_t dev; + struct ds1775_softc *sc; + struct ds1775_sensor *sens; + int value; + int error; + unsigned int temp; + + dev = arg1; + sc = device_get_softc(dev); + sens = &sc->sc_sensors[arg2]; + + error = ds1775_sensor_read(dev, sens, &value); + if (error != 0) + return (error); + + temp = value + FCU_ZERO_C_TO_K; + + error = sysctl_handle_int(oidp, &temp, 0, req); + + return (error); +} diff --git a/sys/dev/iicbus/max6690.c b/sys/dev/iicbus/max6690.c new file mode 100644 index 0000000..527c643 --- /dev/null +++ b/sys/dev/iicbus/max6690.c @@ -0,0 +1,335 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/callout.h> +#include <sys/conf.h> +#include <sys/cpu.h> +#include <sys/ctype.h> +#include <sys/kernel.h> +#include <sys/reboot.h> +#include <sys/rman.h> +#include <sys/sysctl.h> +#include <sys/limits.h> + +#include <machine/bus.h> +#include <machine/md_var.h> + +#include <dev/iicbus/iicbus.h> +#include <dev/iicbus/iiconf.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> + +#define FCU_ZERO_C_TO_K 2732 + +/* Inlet, Backside, U3 Heatsink sensor: MAX6690. */ + +#define MAX6690_INT_TEMP 0x0 +#define MAX6690_EXT_TEMP 0x1 +#define MAX6690_EEXT_TEMP 0x10 +#define MAX6690_IEXT_TEMP 0x11 +#define MAX6690_TEMP_MASK 0xe0 + +struct max6690_sensor { + int id; + char location[32]; +}; + +/* Regular bus attachment functions */ +static int max6690_probe(device_t); +static int max6690_attach(device_t); + +/* Utility functions */ +static int max6690_sensor_sysctl(SYSCTL_HANDLER_ARGS); +static void max6690_start(void *xdev); +static int max6690_read_1(device_t dev, uint32_t addr, uint8_t reg, + uint8_t *data); + +struct max6690_softc { + device_t sc_dev; + struct intr_config_hook enum_hook; + uint32_t sc_addr; + struct max6690_sensor *sc_sensors; + int sc_nsensors; +}; +static device_method_t max6690_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, max6690_probe), + DEVMETHOD(device_attach, max6690_attach), + { 0, 0 }, +}; + +static driver_t max6690_driver = { + "max6690", + max6690_methods, + sizeof(struct max6690_softc) +}; + +static devclass_t max6690_devclass; + +DRIVER_MODULE(max6690, iicbus, max6690_driver, max6690_devclass, 0, 0); +MALLOC_DEFINE(M_MAX6690, "max6690", "Temp-Monitor MAX6690"); + +static int +max6690_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 +max6690_probe(device_t dev) +{ + const char *name, *compatible; + struct max6690_softc *sc; + + name = ofw_bus_get_name(dev); + compatible = ofw_bus_get_compat(dev); + + if (!name) + return (ENXIO); + + if (strcmp(name, "temp-monitor") != 0 || + strcmp(compatible, "max6690") != 0) + return (ENXIO); + + sc = device_get_softc(dev); + sc->sc_dev = dev; + sc->sc_addr = iicbus_get_addr(dev); + + device_set_desc(dev, "Temp-Monitor MAX6690"); + + return (0); +} + +/* + * This function returns the number of sensors. If we call it the second time + * and we have allocated memory for sc->sc_sensors, we fill in the properties. + */ +static int +max6690_fill_sensor_prop(device_t dev) +{ + phandle_t child; + struct max6690_softc *sc; + u_int id[8]; + char location[96]; + int i = 0, j, len = 0, prop_len, prev_len = 0; + + sc = device_get_softc(dev); + + child = ofw_bus_get_node(dev); + + /* Fill the sensor location property. */ + prop_len = OF_getprop(child, "hwsensor-location", location, + sizeof(location)); + while (len < prop_len) { + if (sc->sc_sensors != NULL) + strcpy(sc->sc_sensors[i].location, location + len); + prev_len = strlen(location + len) + 1; + len += prev_len; + i++; + } + if (sc->sc_sensors == NULL) + return (i); + + /* Fill the sensor id property. */ + prop_len = OF_getprop(child, "hwsensor-id", id, sizeof(id)); + for (j = 0; j < i; j++) + sc->sc_sensors[j].id = (id[j] & 0xf); + + return (i); +} +static int +max6690_attach(device_t dev) +{ + struct max6690_softc *sc; + + sc = device_get_softc(dev); + + sc->enum_hook.ich_func = max6690_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 +max6690_start(void *xdev) +{ + phandle_t child; + struct max6690_softc *sc; + struct sysctl_oid *oid, *sensroot_oid; + struct sysctl_ctx_list *ctx; + char sysctl_name[32]; + int i, j; + + device_t dev = (device_t)xdev; + + sc = device_get_softc(dev); + + sc->sc_nsensors = 0; + + child = ofw_bus_get_node(dev); + + /* Count the actual number of sensors. */ + sc->sc_nsensors = max6690_fill_sensor_prop(dev); + + device_printf(dev, "%d sensors detected.\n", sc->sc_nsensors); + + if (sc->sc_nsensors == 0) + device_printf(dev, "WARNING: No MAX6690 sensors detected!\n"); + + sc->sc_sensors = malloc (sc->sc_nsensors * sizeof(struct max6690_sensor), + M_MAX6690, M_WAITOK | M_ZERO); + + ctx = device_get_sysctl_ctx(dev); + sensroot_oid = SYSCTL_ADD_NODE(ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor", + CTLFLAG_RD, 0, "MAX6690 Sensor Information"); + + /* Now we can fill the properties into the allocated struct. */ + sc->sc_nsensors = max6690_fill_sensor_prop(dev); + + /* Add sysctls for the sensors. */ + for (i = 0; i < sc->sc_nsensors; i++) { + for (j = 0; j < strlen(sc->sc_sensors[i].location); j++) { + sysctl_name[j] = tolower(sc->sc_sensors[i].location[j]); + if (isspace(sysctl_name[j])) + sysctl_name[j] = '_'; + } + sysctl_name[j] = 0; + + oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(sensroot_oid), + OID_AUTO, + sysctl_name, CTLFLAG_RD, 0, + "Sensor Information"); + /* I use i to pass the sensor id. */ + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "temp", + CTLTYPE_INT | CTLFLAG_RD, dev, i % 2, + max6690_sensor_sysctl, "IK", + "Sensor Temp in °C"); + + } + /* Dump sensor location & ID. */ + if (bootverbose) { + device_printf(dev, "Sensors\n"); + for (i = 0; i < sc->sc_nsensors; i++) { + device_printf(dev, "Location : %s ID: %d\n", + sc->sc_sensors[i].location, + sc->sc_sensors[i].id); + } + } + + config_intrhook_disestablish(&sc->enum_hook); +} + +static int +max6690_sensor_read(device_t dev, struct max6690_sensor *sens, int *temp) +{ + uint8_t reg_int = 0, reg_ext = 0; + uint8_t integer; + uint8_t fraction; + struct max6690_softc *sc; + + sc = device_get_softc(dev); + + /* The internal sensor id's are even, the external ar odd. */ + if ((sens->id % 2) == 0) { + reg_int = MAX6690_INT_TEMP; + reg_ext = MAX6690_IEXT_TEMP; + } else { + reg_int = MAX6690_EXT_TEMP; + reg_ext = MAX6690_EEXT_TEMP; + } + + max6690_read_1(sc->sc_dev, sc->sc_addr, reg_int, &integer); + + max6690_read_1(sc->sc_dev, sc->sc_addr, reg_ext, &fraction); + + fraction &= MAX6690_TEMP_MASK; + + /* The temperature is in tenth kelvin, the fractional part resolution + is 0.125. + */ + *temp = (integer * 10) + (fraction >> 5) * 10 / 8; + + return (0); +} + +static int +max6690_sensor_sysctl(SYSCTL_HANDLER_ARGS) +{ + device_t dev; + struct max6690_softc *sc; + struct max6690_sensor *sens; + int value = 0; + int error; + unsigned int temp; + + dev = arg1; + sc = device_get_softc(dev); + sens = &sc->sc_sensors[arg2]; + + error = max6690_sensor_read(dev, sens, &value); + if (error != 0) + return (error); + + temp = value + FCU_ZERO_C_TO_K; + + error = sysctl_handle_int(oidp, &temp, 0, req); + + return (error); +} diff --git a/sys/powerpc/conf/GENERIC b/sys/powerpc/conf/GENERIC index 2779bd0..c23e9ac 100644 --- a/sys/powerpc/conf/GENERIC +++ b/sys/powerpc/conf/GENERIC @@ -173,6 +173,9 @@ device sbp # SCSI over FireWire (Requires scbus and da) device fwe # Ethernet over FireWire (non-standard!) # Misc +device ds1775 # PowerMac7,2 temperature sensor +device fcu # Apple Fan Control Unit +device max6690 # PowerMac7,2 temperature sensor device powermac_nvram # Open Firmware configuration NVRAM device smu # Apple System Management Unit diff --git a/sys/powerpc/conf/GENERIC64 b/sys/powerpc/conf/GENERIC64 index 5dc66eb..b861e51 100644 --- a/sys/powerpc/conf/GENERIC64 +++ b/sys/powerpc/conf/GENERIC64 @@ -170,6 +170,9 @@ device sbp # SCSI over FireWire (Requires scbus and da) device fwe # Ethernet over FireWire (non-standard!) # Misc +device ds1775 # PowerMac7,2 temperature sensor +device fcu # Apple Fan Control Unit +device max6690 # PowerMac7,2 temperature sensor device powermac_nvram # Open Firmware configuration NVRAM device smu # Apple System Management Unit diff --git a/sys/powerpc/conf/NOTES b/sys/powerpc/conf/NOTES index d943761..7248eab 100644 --- a/sys/powerpc/conf/NOTES +++ b/sys/powerpc/conf/NOTES @@ -36,6 +36,9 @@ device kiic # Apple Keywest I2C Controller device ofwd # Open Firmware disks device adb # Apple Desktop Bus device cuda # VIA-CUDA ADB interface +device ds1775 # PowerMac7,2 temperature sensor +device fcu # Apple Fan Control Unit +device max6690 # PowerMac7,2 temperature sensor device pmu # Apple Power Management Unit device smu # Apple System Management Unit device snd_ai2s # Apple I2S Audio diff --git a/sys/powerpc/powermac/fcu.c b/sys/powerpc/powermac/fcu.c new file mode 100644 index 0000000..7318f1e --- /dev/null +++ b/sys/powerpc/powermac/fcu.c @@ -0,0 +1,506 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/callout.h> +#include <sys/conf.h> +#include <sys/cpu.h> +#include <sys/ctype.h> +#include <sys/kernel.h> +#include <sys/reboot.h> +#include <sys/rman.h> +#include <sys/sysctl.h> +#include <sys/limits.h> + +#include <machine/bus.h> +#include <machine/md_var.h> + +#include <dev/iicbus/iicbus.h> +#include <dev/iicbus/iiconf.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> + +/* 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_READ(x) 0x31 + (x) * 2 +#define FCU_PWM_SET(x) 0x30 + (x) * 2 + +struct fcu_fan { + int id; + cell_t min_rpm; + cell_t max_rpm; + char location[32]; + enum { + FCU_FAN_RPM, + FCU_FAN_PWM + } type; + int setpoint; +}; + +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; +}; + +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_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); + rpm = min(fan->max_rpm, rpm); + + if (fan->type == FCU_FAN_RPM) { + reg = FCU_RPM_SET(fan->id); + fan->setpoint = rpm; + } else if (fan->type == FCU_FAN_PWM) { + reg = FCU_PWM_SET(fan->id); + if (rpm > 3500) + rpm = 3500; + if (rpm < 500) + rpm = 500; + fan->setpoint = rpm; + /* PWM 30: 550 rpm, PWM 255: 3400 rpm. */ + rpm = (rpm * 255) / 3500; + } else { + device_printf(dev, "Unknown fan type: %d\n", fan->type); + return (EIO); + } + + if (fan->type == FCU_FAN_RPM) { + buf[0] = rpm >> (8 - fcu_rpm_shift); + buf[1] = rpm << fcu_rpm_shift; + fcu_write(sc->sc_dev, sc->sc_addr, reg, buf, 2); + } else { + buf[0] = rpm; + fcu_write(sc->sc_dev, sc->sc_addr, reg, buf, 1); + } + + 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 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_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); +} + +/* + * 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, error; + + fcu = arg1; + sc = device_get_softc(fcu); + fan = &sc->sc_fans[arg2]; + fcu_fan_get_rpm(fcu, fan, &rpm); + error = sysctl_handle_int(oidp, &rpm, 0, req); + + if (error || !req->newptr) + return (error); + + return (fcu_fan_set_rpm(fcu, fan, rpm)); +} + +static void +fcu_attach_fans(device_t dev) +{ + struct fcu_softc *sc; + struct sysctl_oid *oid, *fanroot_oid; + struct sysctl_ctx_list *ctx; + phandle_t child; + char sysctl_name[32]; + int i, j; + + sc = device_get_softc(dev); + + sc->sc_nfans = 0; + + child = ofw_bus_get_node(dev); + + /* 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; + + sc->sc_fans[i].min_rpm = 2400 >> fcu_rpm_shift; + sc->sc_fans[i].max_rpm = 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_rpm), 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_rpm), 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"); + } + + /* 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].setpoint); + } + } +} |