/* $NetBSD: adm1030.c,v 1.11 2007/11/17 08:30:35 kefren Exp $ */ /*- * Copyright (C) 2005 Michael Lorenz. * * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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. */ /* * a driver fot the ADM1030 environmental controller found in some iBook G3 * and probably other Apple machines */ #include __KERNEL_RCSID(0, "$NetBSD: adm1030.c,v 1.11 2007/11/17 08:30:35 kefren Exp $"); #include #include #include #include #include #include #include #include #include #include "sysmon_envsys.h" #include static void adm1030c_attach(struct device *, struct device *, void *); static int adm1030c_match(struct device *, struct cfdata *, void *); static uint8_t adm1030c_readreg(struct adm1030c_softc *, uint8_t); static void adm1030c_writereg(struct adm1030c_softc *, uint8_t, uint8_t); static int adm1030c_temp2muk(uint8_t); static int adm1030c_reg2rpm(uint8_t); static void adm1030c_refresh(struct sysmon_envsys *, envsys_data_t *); CFATTACH_DECL(adm1030c, sizeof(struct adm1030c_softc), adm1030c_match, adm1030c_attach, NULL, NULL); static int adm1030c_match(parent, cf, aux) struct device *parent; struct cfdata *cf; void *aux; { /* no probing when we're attaching to iic */ return 1; } static void adm1030c_attach(parent, self, aux) struct device *parent, *self; void *aux; { struct adm1030c_softc *sc = device_private(self); struct i2c_attach_args *args = aux; sc->parent = parent; sc->address = args->ia_addr; printf(" ADM1030 thermal monitor and fan controller\n"); sc->sc_i2c = (struct i2c_controller *)args->ia_tag; adm1030c_setup(sc); } static uint8_t adm1030c_readreg(struct adm1030c_softc *sc, uint8_t reg) { uint8_t data = 0; iic_acquire_bus(sc->sc_i2c,0); iic_exec(sc->sc_i2c, I2C_OP_READ, sc->address, ®, 1, &data, 1, 0); iic_release_bus(sc->sc_i2c, 0); return data; } static void adm1030c_writereg(struct adm1030c_softc *sc, uint8_t reg, uint8_t data) { uint8_t mdata[2]={reg, data}; iic_acquire_bus(sc->sc_i2c, 0); iic_exec(sc->sc_i2c, I2C_OP_WRITE, sc->address, &mdata, 2, NULL, 0, 0); iic_release_bus(sc->sc_i2c, 0); } #if NSYSMON_ENVSYS > 0 /* convert temperature read from the chip to micro kelvin */ static inline int adm1030c_temp2muk(uint8_t t) { int temp=t; return temp * 1000000 + 273150000U; } static inline int adm1030c_reg2rpm(uint8_t r) { if (r == 0xff) return 0; return (11250 * 60) / (2 * (int)r); } SYSCTL_SETUP(sysctl_adm1030c_setup, "sysctl ADM1030M subtree setup") { #ifdef ADM1030_DEBUG printf("node setup\n"); #endif sysctl_createv(NULL, 0, NULL, NULL, CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL, NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL); } static int sysctl_adm1030c_temp(SYSCTLFN_ARGS) { struct sysctlnode node = *rnode; struct adm1030c_softc *sc=(struct adm1030c_softc *)node.sysctl_data; int reg = 12345, nd=0; const int *np = newp; uint8_t chipreg = (uint8_t)(node.sysctl_idata & 0xff); reg = (uint32_t)adm1030c_readreg(sc, chipreg); reg = (reg & 0xf8) >> 1; node.sysctl_idata = reg; if (np) { /* we're asked to write */ nd = *np; node.sysctl_data = ® if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { int8_t new_reg; new_reg = (int8_t)(max(30, min(85, node.sysctl_idata))); new_reg = ((new_reg & 0x7c) << 1); /* 5C range */ adm1030c_writereg(sc, chipreg, new_reg); return 0; } return EINVAL; } else { node.sysctl_size = 4; return (sysctl_lookup(SYSCTLFN_CALL(&node))); } } void adm1030c_setup(struct adm1030c_softc *sc) { int error; int ret; struct sysctlnode *me = NULL, *node = NULL; sc->sc_sme = sysmon_envsys_create(); sc->sc_sensor = malloc(sizeof(envsys_data_t) * 3, M_DEVBUF, M_WAITOK | M_ZERO); ret=sysctl_createv(NULL, 0, NULL, (const struct sysctlnode **)&me, CTLFLAG_READWRITE, CTLTYPE_NODE, sc->sc_dev.dv_xname, NULL, NULL, 0, NULL, 0, CTL_MACHDEP, CTL_CREATE, CTL_EOL); (void)strlcpy(sc->sc_sensor[0].desc, "case temperature", sizeof(sc->sc_sensor[0].desc)); sc->sc_sensor[0].units = ENVSYS_STEMP; if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[0])) goto out; sc->regs[0] = 0x0a; /* remote temperature register */ ret=sysctl_createv(NULL, 0, NULL, (const struct sysctlnode **)&node, CTLFLAG_READWRITE | CTLFLAG_OWNDESC | CTLFLAG_IMMEDIATE, CTLTYPE_INT, "temp0", sc->sc_sensor[0].desc, sysctl_adm1030c_temp, 0x25, NULL, 0, CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL); if (node != NULL) { node->sysctl_data = sc; } (void)strlcpy(sc->sc_sensor[1].desc, "CPU temperature", sizeof(sc->sc_sensor[1].desc)); sc->sc_sensor[1].units = ENVSYS_STEMP; if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[1])) goto out; sc->regs[1] = 0x0b; /* built-in temperature register */ ret=sysctl_createv(NULL, 0, NULL, (const struct sysctlnode **)&node, CTLFLAG_READWRITE | CTLFLAG_OWNDESC | CTLFLAG_IMMEDIATE, CTLTYPE_INT, "temp1", sc->sc_sensor[1].desc, sysctl_adm1030c_temp,0x24, NULL, 0, CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL); if(node!=NULL) { node->sysctl_data = sc; } (void)strlcpy(sc->sc_sensor[2].desc, "fan speed", sizeof(sc->sc_sensor[2].desc)); sc->sc_sensor[2].units = ENVSYS_SFANRPM; sc->regs[2] = 0x08; /* fan rpm */ if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[2])) goto out; sc->sc_sme->sme_name = sc->sc_dev.dv_xname; sc->sc_sme->sme_cookie = sc; sc->sc_sme->sme_refresh = adm1030c_refresh; if ((error = sysmon_envsys_register(sc->sc_sme)) != 0) { aprint_error("%s: unable to register with sysmon (%d)\n", sc->sc_dev.dv_xname, error); goto out; } return; out: sysmon_envsys_destroy(sc->sc_sme); } static void adm1030c_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) { struct adm1030c_softc *sc = sme->sme_cookie; int i; uint8_t reg; i = edata->sensor; reg = sc->regs[i]; switch (edata->units) { case ENVSYS_STEMP: edata->value_cur = adm1030c_temp2muk(adm1030c_readreg(sc, reg)); break; case ENVSYS_SFANRPM: { uint8_t blah = adm1030c_readreg(sc,reg); edata->value_cur = adm1030c_reg2rpm(blah); } break; } edata->state = ENVSYS_SVALID; } #endif /* NSYSMON_ENVSYS > 0 */