From owner-freebsd-ppc@FreeBSD.ORG Mon Oct 4 12:57:52 2010 Return-Path: Delivered-To: freebsd-ppc@FreeBSD.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 873181065673 for ; Mon, 4 Oct 2010 12:57:52 +0000 (UTC) (envelope-from andreast@FreeBSD.org) Received: from smtp.fgznet.ch (mail.fgznet.ch [81.92.96.47]) by mx1.freebsd.org (Postfix) with ESMTP id 64FF78FC0A for ; Mon, 4 Oct 2010 12:57:50 +0000 (UTC) Received: from deuterium.andreas.nets (dhclient-91-190-8-131.flashcable.ch [91.190.8.131]) by smtp.fgznet.ch (8.13.8/8.13.8/Submit_SMTPAUTH) with ESMTP id o94CvjIq061745 for ; Mon, 4 Oct 2010 14:57:49 +0200 (CEST) (envelope-from andreast@FreeBSD.org) Message-ID: <4CA9CF49.6030708@FreeBSD.org> Date: Mon, 04 Oct 2010 14:57:45 +0200 From: Andreas Tobler User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.9) Gecko/20100915 Lightning/1.0b2 Thunderbird/3.1.4 MIME-Version: 1.0 To: FreeBSD PowerPC ML Content-Type: multipart/mixed; boundary="------------000802030606020406070600" X-Scanned-By: MIMEDefang 2.64 on 81.92.96.47 Cc: Subject: [rfc] fcu driver review X-BeenThere: freebsd-ppc@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Porting FreeBSD to the PowerPC List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 04 Oct 2010 12:57:52 -0000 This is a multi-part message in MIME format. --------------000802030606020406070600 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Hello all, here I post the fcu driver again for review. The aim of this driver is fan control on certain PowerMac's, mainly PowerMac7,2. It would be great if I could get some feedback. Feedback in all areas please. The driver will go into the repo soon. But before I'd like to polish it up. TIA, Andreas --------------000802030606020406070600 Content-Type: text/plain; x-mac-type="0"; x-mac-creator="0"; name="fcu.c" Content-Transfer-Encoding: 8bit Content-Disposition: attachment; filename="fcu.c" /*- * 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_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 void fcu_start(void *xdev); /* Generic i2c read/write functions. */ static int write_i2c(device_t dev, uint32_t addr, uint8_t reg, uint8_t *buf, int len); static int read_i2c_1(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data); static int read_i2c_2(device_t dev, uint32_t addr, uint8_t reg, uint16_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"); /* Generic i2c read/write functions. */ static int write_i2c(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 read_i2c_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 read_i2c_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); } /* FCU starts here. */ 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. */ write_i2c(sc->sc_dev, sc->sc_addr, 0xe, buf, 1); write_i2c(sc->sc_dev, sc->sc_addr, 0x2e, buf, 1); read_i2c_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); } /* * Sensor and fan management */ 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; write_i2c(sc->sc_dev, sc->sc_addr, reg, buf, 2); } else { buf[0] = rpm; write_i2c(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; read_i2c_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; read_i2c_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; read_i2c_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; read_i2c_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; read_i2c_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; read_i2c_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. */ read_i2c_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; 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_getproplen(child, "hwctrl-location"); char location[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_getproplen(child, "hwctrl-type"); char type[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_getproplen(child, "hwctrl-id"); u_int id[prop_len / 4]; 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); } } } /* Temperature sensors stuff. */ #define FCU_ZERO_C_TO_K 2732 /* Drivebay sensor: LM75/DS1775. */ #define DS1775_TEMP 0x0 struct ds1775_sensor { char location[32]; }; static int ds1775_probe(device_t); static int ds1775_attach(device_t); static int ds1775_sensor_sysctl(SYSCTL_HANDLER_ARGS); static void ds1775_start(void *xdev); 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 = { "tm", ds1775_methods, sizeof(struct ds1775_softc) }; static devclass_t ds1775_devclass; DRIVER_MODULE(tm, iicbus, ds1775_driver, ds1775_devclass, 0, 0); MALLOC_DEFINE(M_TM, "tm", "Temp-Monitor DS1775"); 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; 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_TM, M_WAITOK | M_ZERO); sens = sc->sc_sensors; ctx = device_get_sysctl_ctx(dev); sensroot_oid = device_get_sysctl_tree(dev); char sysctl_name[40], sysctl_desc[40]; const char *units; 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); read_i2c_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); } /* 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]; }; static int max6690_probe(device_t); static int max6690_attach(device_t); static int max6690_sensor_sysctl(SYSCTL_HANDLER_ARGS); static void max6690_start(void *xdev); 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 = { "tm1", max6690_methods, sizeof(struct max6690_softc) }; static devclass_t max6690_devclass; DRIVER_MODULE(tm1, iicbus, max6690_driver, max6690_devclass, 0, 0); MALLOC_DEFINE(M_TM1, "tm1", "Temp-Monitor MAX6690"); 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; 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_getproplen(child, "hwsensor-location"); char location[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_getproplen(child, "hwsensor-id"); u_int id[prop_len / 4]; 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, "MAX6690 # sensors: %d\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_TM1, M_WAITOK | M_ZERO); ctx = device_get_sysctl_ctx(dev); /* XXXX better name for sensor root? */ 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; } read_i2c_1(sc->sc_dev, sc->sc_addr, reg_int, &integer); read_i2c_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); } /* CPU A/B sensors, temp and adc: AD7417. */ #define SUPPLY_MON_A_ID 0x58 #define SUPPLY_MON_B_ID 0x5a #define AD741X_TEMP 0x00 #define AD741X_CONFIG 0x01 #define AD741X_ADC 0x04 #define AD741X_CONFIG2 0x05 #define AD741X_CONFMASK 0xe0 uint8_t adc741x_config; struct ad741x_sensor { int id; char location[32]; enum { ADC741X_TEMP_SENSOR, ADC741X_ADC_SENSOR } type; }; static int ad741x_probe(device_t); static int ad741x_attach(device_t); static int ad741x_sensor_sysctl(SYSCTL_HANDLER_ARGS); static void ad741x_start(void *xdev); struct ad741x_softc { device_t sc_dev; struct intr_config_hook enum_hook; uint32_t sc_addr; struct ad741x_sensor *sc_sensors; int sc_nsensors; }; static device_method_t ad741x_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ad741x_probe), DEVMETHOD(device_attach, ad741x_attach), { 0, 0 }, }; static driver_t ad741x_driver = { "sm", ad741x_methods, sizeof(struct ad741x_softc) }; static devclass_t ad741x_devclass; DRIVER_MODULE(sm, iicbus, ad741x_driver, ad741x_devclass, 0, 0); MALLOC_DEFINE(M_SM, "sm", "Supply-Monitor AD741X"); static int ad741x_init_adc(device_t dev, uint32_t addr) { uint8_t buf; adc741x_config = 0; /* Clear Config2 */ buf = 0; write_i2c(dev, addr, AD741X_CONFIG2, &buf, 1); /* Read & cache Config1 */ buf = 0; write_i2c(dev, addr, AD741X_CONFIG, &buf, 1); read_i2c_1(dev, addr, AD741X_CONFIG, &buf); adc741x_config = (uint8_t)buf; /* Disable shutdown mode */ adc741x_config &= 0xfe; buf = adc741x_config; write_i2c(dev, addr, AD741X_CONFIG, &buf, 1); return (0); } static int ad741x_probe(device_t dev) { const char *name, *compatible; struct ad741x_softc *sc; name = ofw_bus_get_name(dev); compatible = ofw_bus_get_compat(dev); if (!name) return (ENXIO); if (strcmp(name, "supply-monitor") != 0 || strcmp(compatible, "ad7417") != 0) return (ENXIO); sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_addr = iicbus_get_addr(dev); device_set_desc(dev, "Supply-Monitor AD741X"); 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 ad741x_fill_sensor_prop(device_t dev) { phandle_t child; struct ad741x_softc *sc; 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_getproplen(child, "hwsensor-location"); char location[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 fan type property. */ len = 0; i = 0; prev_len = 0; prop_len = OF_getproplen(child, "hwsensor-type"); char type[prop_len]; OF_getprop(child, "hwsensor-type", type, sizeof(type)); while (len < prop_len) { if (strcmp(type + len, "temperature") == 0) sc->sc_sensors[i].type = ADC741X_TEMP_SENSOR; else sc->sc_sensors[i].type = ADC741X_ADC_SENSOR; prev_len = strlen(type + len) + 1; len += prev_len; i++; } /* Fill the sensor id property. Taken from OF. */ prop_len = OF_getproplen(child, "hwsensor-id"); u_int id[prop_len / 4]; OF_getprop(child, "hwsensor-id", id, sizeof(id)); for (j = 0; j < i; j++) sc->sc_sensors[j].id = id[j]; return (i); } static int ad741x_attach(device_t dev) { struct ad741x_softc *sc; sc = device_get_softc(dev); sc->enum_hook.ich_func = ad741x_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 ad741x_start(void *xdev) { phandle_t child; struct ad741x_softc *sc; struct sysctl_oid *oid, *sensroot_oid; struct sysctl_ctx_list *ctx; char sysctl_name[32]; int i, j; const char *unit; const char *desc; 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 = ad741x_fill_sensor_prop(dev); device_printf(dev, "AD741X # sensors: %d\n", sc->sc_nsensors); if (sc->sc_nsensors == 0) device_printf(dev, "WARNING: No AD741X sensors detected!\n"); sc->sc_sensors = malloc (sc->sc_nsensors * sizeof(struct ad741x_sensor), M_SM, M_WAITOK | M_ZERO); ctx = device_get_sysctl_ctx(dev); /* XXXX better name for sensor root? */ sensroot_oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor", CTLFLAG_RD, 0, "AD741X Sensor Information"); /* Now we can fill the properties into the allocated struct. */ sc->sc_nsensors = ad741x_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"); if (sc->sc_sensors[i].type == ADC741X_TEMP_SENSOR) { unit = "temp"; desc = "Sensor temp in C"; } else { unit = "volt"; desc = "Sensor Volt in V"; } /* I use i to pass the sensor id. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RD, dev, i, ad741x_sensor_sysctl, sc->sc_sensors[i].type == ADC741X_TEMP_SENSOR ? "IK" : "I", desc); } /* Dump sensor location, ID & type. */ if (bootverbose) { device_printf(dev, "Sensors\n"); for (i = 0; i < sc->sc_nsensors; i++) { device_printf(dev, "Location: %s ID: %d type: %d\n", sc->sc_sensors[i].location, sc->sc_sensors[i].id, sc->sc_sensors[i].type); } } config_intrhook_disestablish(&sc->enum_hook); } static int ad741x_get_temp(device_t dev, uint32_t addr, int *temp) { uint16_t buf[2]; uint16_t read; read_i2c_2(dev, addr, AD741X_TEMP, buf); read = *((int16_t*)buf); /* The ADC is 10 bit, the resolution is 0.25 C. The temperature is in tenth kelvin. */ *temp = (((int16_t)(read & 0xffc0)) >> 6) * 25 / 10; return (0); } static int ad741x_get_adc(device_t dev, uint32_t addr, unsigned int *value, uint8_t chan) { uint8_t cfg1, tmp; uint16_t read, buf[2]; read_i2c_1(dev, addr, AD741X_CONFIG, &cfg1); tmp = chan << 5; cfg1 = (cfg1 & ~AD741X_CONFMASK) | (tmp & AD741X_CONFMASK); write_i2c(dev, addr, AD741X_CONFIG, &cfg1, 1); read_i2c_2(dev, addr, AD741X_ADC, buf); read = *((uint16_t*)buf); *value = ((uint32_t)read) >> 6; return (0); } static int ad741x_sensor_read(device_t dev, struct ad741x_sensor *sens, int *temp) { struct ad741x_softc *sc; uint8_t chan; sc = device_get_softc(dev); /* Init the ADC. */ ad741x_init_adc(sc->sc_dev, sc->sc_addr); if (sens->type == ADC741X_TEMP_SENSOR) { ad741x_get_temp(sc->sc_dev, sc->sc_addr, temp); *temp += FCU_ZERO_C_TO_K; } else { switch (sens->id) { case 11: case 16: chan = 1; break; case 12: case 17: chan = 2; break; case 13: case 18: chan = 3; break; case 14: case 19: chan = 4; break; default: chan = 1; } ad741x_get_adc(sc->sc_dev, sc->sc_addr, temp, chan); } return (0); } static int ad741x_sensor_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; struct ad741x_softc *sc; struct ad741x_sensor *sens; int value = 0; int error; int temp; dev = arg1; sc = device_get_softc(dev); sens = &sc->sc_sensors[arg2]; error = ad741x_sensor_read(dev, sens, &value); if (error != 0) return (error); temp = value; error = sysctl_handle_int(oidp, &temp, 0, req); return (error); } --------------000802030606020406070600--