/* $OpenBSD: sch311x.c,v 1.5 2009/03/25 20:41:31 mk Exp $ */ /* * Copyright (c) 2008 Mark Kettenis * Copyright (c) 2009 Michael Knudsen * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * SMSC SCH3112, SCH3114, and SCH3116 LPC Super I/O driver. */ #include #include #include #include #include #include #include #include #include /* Device identifiers */ #define SCHSIO_ID_SCH3112 0x7c #define SCHSIO_ID_SCH3114 0x7d #define SCHSIO_ID_SCH3116 0x7f #define SCHSIO_IOSIZE 0x02 #define SCHSIO_PORT_CONFIG 0x00 /* These are used in configuration mode */ #define SCHSIO_PORT_INDEX 0x00 #define SCHSIO_PORT_DATA 0x01 #define SCHSIO_CONFIG_ENTER 0x55 #define SCHSIO_CONFIG_LEAVE 0xaa /* Register definitions */ #define SCHSIO_IDX_LDEVSEL 0x07 /* Logical device select */ #define SCHSIO_IDX_DEVICE 0x20 /* Device ID */ #define SCHSIO_IDX_REV 0x21 /* Device revision */ #define SCHSIO_IDX_BASE_HI 0x60 /* Configuration base address */ #define SCHSIO_IDX_BASE_LO 0x61 /* Logical devices */ #define SCHSIO_LDEV_RUNTIME 0x0a /* holds wdog and sensors */ #define SCHSIO_LDEV_RUNTIME_SZ 0x100 /* Hardware monitor */ #define SCHSIO_HWM_INTERVAL 5 /* seconds */ /* Register access */ #define SCHSIO_HWM_IOSIZE 0x100 #define SCHSIO_HWM_OFFSET 0x70 #define SCHSIO_HWM_INDEX 0x70 #define SCHSIO_HWM_DATA 0x71 /* Sensor definitions */ /* Voltage */ #define SCHSIO_HWM_VOLT1 0x20 #define SCHSIO_HWM_VOLT2 0x21 #define SCHSIO_HWM_VOLT3 0x22 #define SCHSIO_HWM_VOLT4 0x23 #define SCHSIO_HWM_VOLT5 0x24 #define SCHSIO_HWM_VOLT6 0x99 #define SCHSIO_HWM_VOLT7 0x9a /* Temperature */ #define SCHSIO_HWM_TEMP1 0x26 #define SCHSIO_HWM_TEMP2 0x25 #define SCHSIO_HWM_TEMP3 0x27 /* Fan speed */ #define SCHSIO_HWM_TACH1_L 0x28 #define SCHSIO_HWM_TACH1_U 0x29 #define SCHSIO_HWM_TACH2_L 0x2a #define SCHSIO_HWM_TACH2_U 0x2b #define SCHSIO_HWM_TACH3_L 0x2c #define SCHSIO_HWM_TACH3_U 0x2d /* 11111 = 90kHz * 10^9 */ #define SCHSIO_FAN_RPM(x) (1000000000 / ((x) * 11111) * 60) #define SCHSIO_CONV_VOLT1 66400 #define SCHSIO_CONV_VOLT2 20000 #define SCHSIO_CONV_VOLT3 43800 #define SCHSIO_CONV_VOLT4 66400 #define SCHSIO_CONV_VOLT5 160000 #define SCHSIO_CONV_VOLT6 43800 #define SCHSIO_CONV_VOLT7 43800 #define SCHSIO_VOLT_MUV(x, k) (1000000 * (x) / 2560000 * (k)) #define SCHSIO_TEMP_MUK(x) (((x) + 273) * 1000000) #define SCHSIO_SENSORS 13 #define SCHSIO_SENSOR_FAN1 0 #define SCHSIO_SENSOR_FAN2 1 #define SCHSIO_SENSOR_FAN3 2 #define SCHSIO_SENSOR_VOLT1 3 #define SCHSIO_SENSOR_VOLT2 4 #define SCHSIO_SENSOR_VOLT3 5 #define SCHSIO_SENSOR_VOLT4 6 #define SCHSIO_SENSOR_VOLT5 7 #define SCHSIO_SENSOR_VOLT6 8 #define SCHSIO_SENSOR_VOLT7 9 #define SCHSIO_SENSOR_TEMP1 10 #define SCHSIO_SENSOR_TEMP2 11 #define SCHSIO_SENSOR_TEMP3 12 /* Watchdog */ /* Register access */ #define SCHSIO_WDT_GPIO 0x47 #define SCHSIO_WDT_TIMEOUT 0x65 #define SCHSIO_WDT_VAL 0x66 #define SCHSIO_WDT_CFG 0x67 #define SCHSIO_WDT_CTRL 0x68 /* Bits */ #define SCHSIO_WDT_GPIO_MASK 0x0f #define SCHSIO_WDT_GPIO_OUT 0x0e #define SCHSIO_WDT_TO_SECONDS (1 << 7) #define SCHSIO_WDT_CTRL_TRIGGERED (1 << 0) #define SCHSIO_WDT_CFG_KBDEN (1 << 1) #define SCHSIO_WDT_CFG_MSEN (1 << 2) /* autoconf(9) flags etc. */ #define SCHSIO_CFFLAGS_WDTEN (1 << 0) #define DEVNAME(x) ((x)->sc_dev.dv_xname) struct schsio_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; bus_space_handle_t sc_ioh_rr; struct ksensordev sc_sensordev; struct ksensor sc_sensor[SCHSIO_SENSORS]; }; int schsio_probe(struct device *, void *, void *); void schsio_attach(struct device *, struct device *, void *); int schsio_print(void *, const char *); static __inline void schsio_config_enable(bus_space_tag_t iot, bus_space_handle_t ioh); static __inline void schsio_config_disable(bus_space_tag_t iot, bus_space_handle_t ioh); u_int8_t schsio_config_read(bus_space_tag_t iot, bus_space_handle_t ioh, int reg); void schsio_config_write(bus_space_tag_t iot, bus_space_handle_t ioh, int reg, u_int8_t val); /* HWM prototypes */ void schsio_hwm_init(struct schsio_softc *sc); void schsio_hwm_update(void *arg); u_int8_t schsio_hwm_read(struct schsio_softc *sc, int reg); /* Watchdog prototypes */ void schsio_wdt_init(struct schsio_softc *sc); int schsio_wdt_cb(void *arg, int period); struct cfattach schsio_ca = { sizeof(struct schsio_softc), schsio_probe, schsio_attach }; struct cfdriver schsio_cd = { NULL, "schsio", DV_DULL }; static __inline void schsio_config_enable(bus_space_tag_t iot, bus_space_handle_t ioh) { bus_space_write_1(iot, ioh, SCHSIO_PORT_CONFIG, SCHSIO_CONFIG_ENTER); } static __inline void schsio_config_disable(bus_space_tag_t iot, bus_space_handle_t ioh) { bus_space_write_1(iot, ioh, SCHSIO_PORT_CONFIG, SCHSIO_CONFIG_LEAVE); } u_int8_t schsio_config_read(bus_space_tag_t iot, bus_space_handle_t ioh, int reg) { bus_space_write_1(iot, ioh, SCHSIO_PORT_INDEX, reg); return (bus_space_read_1(iot, ioh, SCHSIO_PORT_DATA)); } void schsio_config_write(bus_space_tag_t iot, bus_space_handle_t ioh, int reg, u_int8_t val) { bus_space_write_1(iot, ioh, SCHSIO_PORT_INDEX, reg); bus_space_write_1(iot, ioh, SCHSIO_PORT_DATA, val); } int schsio_probe(struct device *parent, void *match, void *aux) { struct isa_attach_args *ia = aux; bus_space_tag_t iot; bus_space_handle_t ioh; u_int8_t reg; /* Match by device ID */ iot = ia->ia_iot; if (bus_space_map(iot, ia->ipa_io[0].base, SCHSIO_IOSIZE, 0, &ioh)) return (0); schsio_config_enable(iot, ioh); reg = schsio_config_read(iot, ioh, SCHSIO_IDX_DEVICE); schsio_config_disable(iot, ioh); bus_space_unmap(iot, ia->ipa_io[0].base, SCHSIO_IOSIZE); switch (reg) { case SCHSIO_ID_SCH3112: case SCHSIO_ID_SCH3114: case SCHSIO_ID_SCH3116: ia->ipa_nio = 1; ia->ipa_io[0].length = SCHSIO_IOSIZE; ia->ipa_nmem = 0; ia->ipa_nirq = 0; ia->ipa_ndrq = 0; return (1); break; } return (0); } void schsio_attach(struct device *parent, struct device *self, void *aux) { struct schsio_softc *sc = (void *)self; struct isa_attach_args *ia = aux; u_int16_t iobase; u_int8_t reg0, reg1; /* Map ISA I/O space */ sc->sc_iot = ia->ia_iot; if (bus_space_map(sc->sc_iot, ia->ipa_io[0].base, SCHSIO_IOSIZE, 0, &sc->sc_ioh)) { printf(": can't map I/O space\n"); return; } /* Enter configuration mode */ schsio_config_enable(sc->sc_iot, sc->sc_ioh); /* Read device ID */ reg0 = schsio_config_read(sc->sc_iot, sc->sc_ioh, SCHSIO_IDX_DEVICE); switch (reg0) { case SCHSIO_ID_SCH3112: printf(": SCH3112"); break; case SCHSIO_ID_SCH3114: printf(": SCH3114"); break; case SCHSIO_ID_SCH3116: printf(": SCH3116"); break; } /* Read device revision */ reg0 = schsio_config_read(sc->sc_iot, sc->sc_ioh, SCHSIO_IDX_REV); printf(" rev 0x%02x", reg0); /* Select runtime registers logical device */ schsio_config_write(sc->sc_iot, sc->sc_ioh, SCHSIO_IDX_LDEVSEL, SCHSIO_LDEV_RUNTIME); reg0 = schsio_config_read(sc->sc_iot, sc->sc_ioh, SCHSIO_IDX_BASE_HI); reg1 = schsio_config_read(sc->sc_iot, sc->sc_ioh, SCHSIO_IDX_BASE_LO); iobase = (reg0 << 8) | reg1; if (bus_space_map(sc->sc_iot, iobase, SCHSIO_LDEV_RUNTIME_SZ, 0, &sc->sc_ioh_rr)) { printf(": can't map I/O space\n"); return; } schsio_wdt_init(sc); schsio_hwm_init(sc); printf("\n"); /* Escape from configuration mode */ schsio_config_disable(sc->sc_iot, sc->sc_ioh); } void schsio_hwm_init(struct schsio_softc *sc) { int i; /* Set up sensors */ for (i = SCHSIO_SENSOR_FAN1; i < SCHSIO_SENSOR_FAN3 + 1; i++) sc->sc_sensor[i].type = SENSOR_FANRPM; strlcpy(sc->sc_sensor[SCHSIO_SENSOR_VOLT1].desc, "+2.5V", sizeof(sc->sc_sensor[SCHSIO_SENSOR_VOLT1].desc)); strlcpy(sc->sc_sensor[SCHSIO_SENSOR_VOLT2].desc, "+1.5V (Vccp)", sizeof(sc->sc_sensor[SCHSIO_SENSOR_VOLT2].desc)); strlcpy(sc->sc_sensor[SCHSIO_SENSOR_VOLT3].desc, "+3.3V (VCC)", sizeof(sc->sc_sensor[SCHSIO_SENSOR_VOLT3].desc)); strlcpy(sc->sc_sensor[SCHSIO_SENSOR_VOLT4].desc, "+5V", sizeof(sc->sc_sensor[SCHSIO_SENSOR_VOLT4].desc)); strlcpy(sc->sc_sensor[SCHSIO_SENSOR_VOLT5].desc, "+12V", sizeof(sc->sc_sensor[SCHSIO_SENSOR_VOLT5].desc)); strlcpy(sc->sc_sensor[SCHSIO_SENSOR_VOLT6].desc, "+3.3V (VTR)", sizeof(sc->sc_sensor[SCHSIO_SENSOR_VOLT6].desc)); strlcpy(sc->sc_sensor[SCHSIO_SENSOR_VOLT7].desc, "+3V (Vbat)", sizeof(sc->sc_sensor[SCHSIO_SENSOR_VOLT7].desc)); for (i = SCHSIO_SENSOR_VOLT1; i < SCHSIO_SENSOR_VOLT7 + 1; i++) sc->sc_sensor[i].type = SENSOR_VOLTS_DC; strlcpy(sc->sc_sensor[SCHSIO_SENSOR_TEMP1].desc, "Internal", sizeof(sc->sc_sensor[SCHSIO_SENSOR_TEMP1].desc)); strlcpy(sc->sc_sensor[SCHSIO_SENSOR_TEMP2].desc, "Remote", sizeof(sc->sc_sensor[SCHSIO_SENSOR_TEMP2].desc)); strlcpy(sc->sc_sensor[SCHSIO_SENSOR_TEMP3].desc, "Remote", sizeof(sc->sc_sensor[SCHSIO_SENSOR_TEMP3].desc)); for (i = SCHSIO_SENSOR_TEMP1; i < SCHSIO_SENSOR_TEMP3 + 1; i++) sc->sc_sensor[i].type = SENSOR_TEMP; strlcpy(sc->sc_sensordev.xname, DEVNAME(sc), sizeof(sc->sc_sensordev.xname)); for (i = 0; i < SCHSIO_SENSORS; i++) sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]); if (sensor_task_register(sc, schsio_hwm_update, SCHSIO_HWM_INTERVAL) == NULL) { printf(": unable to register update task"); return; } sensordev_install(&sc->sc_sensordev); } void schsio_hwm_update(void *arg) { struct schsio_softc *sc; u_int16_t tach; int8_t temp; u_int8_t volt; u_int8_t reg0, reg1; sc = (struct schsio_softc *)arg; reg0 = schsio_hwm_read(sc, SCHSIO_HWM_TACH1_L); reg1 = schsio_hwm_read(sc, SCHSIO_HWM_TACH1_U); tach = (reg1 << 8) | reg0; sc->sc_sensor[SCHSIO_SENSOR_FAN1].value = SCHSIO_FAN_RPM(tach); sc->sc_sensor[SCHSIO_SENSOR_FAN1].flags = (tach == 0xffff) ? SENSOR_FINVALID : 0; reg0 = schsio_hwm_read(sc, SCHSIO_HWM_TACH2_L); reg1 = schsio_hwm_read(sc, SCHSIO_HWM_TACH2_U); tach = (reg1 << 8) | reg0; sc->sc_sensor[SCHSIO_SENSOR_FAN2].value = SCHSIO_FAN_RPM(tach); sc->sc_sensor[SCHSIO_SENSOR_FAN2].flags = (tach == 0xffff) ? SENSOR_FINVALID : 0; reg0 = schsio_hwm_read(sc, SCHSIO_HWM_TACH3_L); reg1 = schsio_hwm_read(sc, SCHSIO_HWM_TACH3_U); tach = (reg1 << 8) | reg0; sc->sc_sensor[SCHSIO_SENSOR_FAN3].value = SCHSIO_FAN_RPM(tach); sc->sc_sensor[SCHSIO_SENSOR_FAN3].flags = (tach == 0xffff) ? SENSOR_FINVALID : 0; volt = schsio_hwm_read(sc, SCHSIO_HWM_VOLT1); sc->sc_sensor[SCHSIO_SENSOR_VOLT1].value = SCHSIO_VOLT_MUV(volt, SCHSIO_CONV_VOLT1); volt = schsio_hwm_read(sc, SCHSIO_HWM_VOLT2); sc->sc_sensor[SCHSIO_SENSOR_VOLT2].value = SCHSIO_VOLT_MUV(volt, SCHSIO_CONV_VOLT2); volt = schsio_hwm_read(sc, SCHSIO_HWM_VOLT3); sc->sc_sensor[SCHSIO_SENSOR_VOLT3].value = SCHSIO_VOLT_MUV(volt, SCHSIO_CONV_VOLT3); volt = schsio_hwm_read(sc, SCHSIO_HWM_VOLT4); sc->sc_sensor[SCHSIO_SENSOR_VOLT4].value = SCHSIO_VOLT_MUV(volt, SCHSIO_CONV_VOLT4); volt = schsio_hwm_read(sc, SCHSIO_HWM_VOLT5); sc->sc_sensor[SCHSIO_SENSOR_VOLT5].value = SCHSIO_VOLT_MUV(volt, SCHSIO_CONV_VOLT5); volt = schsio_hwm_read(sc, SCHSIO_HWM_VOLT6); sc->sc_sensor[SCHSIO_SENSOR_VOLT6].value = SCHSIO_VOLT_MUV(volt, SCHSIO_CONV_VOLT6); volt = schsio_hwm_read(sc, SCHSIO_HWM_VOLT7); sc->sc_sensor[SCHSIO_SENSOR_VOLT7].value = SCHSIO_VOLT_MUV(volt, SCHSIO_CONV_VOLT7); temp = schsio_hwm_read(sc, SCHSIO_HWM_TEMP1); sc->sc_sensor[SCHSIO_SENSOR_TEMP1].value = SCHSIO_TEMP_MUK(temp); sc->sc_sensor[SCHSIO_SENSOR_TEMP1].flags = ((uint8_t)temp == 0x80) ? SENSOR_FINVALID : 0; temp = schsio_hwm_read(sc, SCHSIO_HWM_TEMP2); sc->sc_sensor[SCHSIO_SENSOR_TEMP2].value = SCHSIO_TEMP_MUK(temp); sc->sc_sensor[SCHSIO_SENSOR_TEMP2].flags = ((uint8_t)temp == 0x80) ? SENSOR_FINVALID : 0; temp = schsio_hwm_read(sc, SCHSIO_HWM_TEMP3); sc->sc_sensor[SCHSIO_SENSOR_TEMP3].value = SCHSIO_TEMP_MUK(temp); sc->sc_sensor[SCHSIO_SENSOR_TEMP3].flags = ((uint8_t)temp == 0x80) ? SENSOR_FINVALID : 0; } u_int8_t schsio_hwm_read(struct schsio_softc *sc, int reg) { bus_space_write_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_HWM_INDEX, reg); return (bus_space_read_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_HWM_DATA)); } void schsio_wdt_init(struct schsio_softc *sc) { u_int8_t reg; reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_GPIO); if ((reg & SCHSIO_WDT_GPIO_MASK) != SCHSIO_WDT_GPIO_OUT) { if (sc->sc_dev.dv_cfdata->cf_flags & SCHSIO_CFFLAGS_WDTEN) { reg &= ~0x0f; reg |= SCHSIO_WDT_GPIO_OUT; bus_space_write_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_GPIO, reg); } else { printf(", watchdog disabled"); return; } } /* First of all, make sure the wdt is disabled */ bus_space_write_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_VAL, 0); /* Clear triggered status */ reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_CTRL); if (reg & SCHSIO_WDT_CTRL_TRIGGERED) { printf(", warning: watchdog triggered"); reg &= ~SCHSIO_WDT_CTRL_TRIGGERED; bus_space_write_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_CTRL, reg); } /* Disable wdt reset by mouse and kbd */ reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_CFG); reg &= ~(SCHSIO_WDT_CFG_MSEN | SCHSIO_WDT_CFG_MSEN); bus_space_write_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_CFG, reg); wdog_register(sc, schsio_wdt_cb); } int schsio_wdt_cb(void *arg, int period) { struct schsio_softc *sc; uint8_t val, minute, reg; sc = (struct schsio_softc *)arg; if (period > 255) { val = period / 60; minute = 1; } else { val = period; minute = 0; } /* Set unit */ reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_TIMEOUT); if (!minute) reg |= SCHSIO_WDT_TO_SECONDS; else reg &= ~SCHSIO_WDT_TO_SECONDS; bus_space_write_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_TIMEOUT, reg); /* Set value */ bus_space_write_1(sc->sc_iot, sc->sc_ioh_rr, SCHSIO_WDT_VAL, val); if (!minute) return val; else return val * 60; }