diff options
author | andreast <andreast@FreeBSD.org> | 2010-10-15 20:08:16 +0000 |
---|---|---|
committer | andreast <andreast@FreeBSD.org> | 2010-10-15 20:08:16 +0000 |
commit | 1cef14c1d4d84dd65444d4db2df33c2521d3a5a8 (patch) | |
tree | f6df6d5436125b508decaf3fb6bad9631107e798 | |
parent | 22560ed4e732f4b4b229efd7f3ac9a545a873852 (diff) | |
download | FreeBSD-src-1cef14c1d4d84dd65444d4db2df33c2521d3a5a8.zip FreeBSD-src-1cef14c1d4d84dd65444d4db2df33c2521d3a5a8.tar.gz |
Add three new drivers for fan control and temperature reading on the
PowerMac7,2.
- The fcu driver lets us read and write the fan RPMs for all fans in the
PowerMac7,2. This driver is PowerMac specific.
- The ds1775 is a driver to read the temperature for the drive bay sensor.
- The max6690 is another driver to read temperatures. Here it is used to
read the inlet, the backside and the U3 heatsink temperature.
An additional driver, the ad7417, will follow later.
Thanks to nwhitehorn for guiding me through this driver development.
Approved by: nwhitehorn (mentor)
-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); + } + } +} |