/* $OpenBSD: tipmic.c,v 1.1 2018/05/20 19:30:21 kettenis Exp $ */ /* * Copyright (c) 2018 Mark Kettenis * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #define TIPMIC_INTR_STAT 0x01 #define TIPMIC_INTR_STAT_ADC (1 << 2) #define TIPMIC_INTR_MASK 0x02 #define TIPMIC_INTR_MASK_ADC (1 << 2) #define TIPMIC_INTR_MASK_ALL 0xff #define TIPMIC_ADC_CTRL 0x50 #define TIPMIC_ADC_CTRL_START (1 << 0) #define TIPMIC_ADC_CTRL_CH_MASK (3 << 1) #define TIPMIC_ADC_CTRL_CH_PMICTEMP (1 << 1) #define TIPMIC_ADC_CTRL_CH_BATTEMP (2 << 1) #define TIPMIC_ADC_CTRL_CH_SYSTEMP (3 << 1) #define TIPMIC_ADC_CTRL_EN (1 << 5) #define TIPMIC_PMICTEMP_HI 0x56 #define TIPMIC_PMICTEMP_LO 0x57 #define TIPMIC_BATTEMP_HI 0x58 #define TIPMIC_BATTEMP_LO 0x59 #define TIPMIC_SYSTEMP_HI 0x5a #define TIPMIC_SYSTEMP_LO 0x5b #define TIPMIC_REGIONSPACE_THERMAL 0x8c struct acpi_lpat { int32_t temp; int32_t raw; }; struct tipmic_softc { struct device sc_dev; struct acpi_softc *sc_acpi; struct aml_node *sc_node; i2c_tag_t sc_tag; i2c_addr_t sc_addr; void *sc_ih; volatile int sc_stat_adc; struct acpi_lpat *sc_lpat; size_t sc_lpat_len; struct acpi_gpio sc_gpio; }; int tipmic_match(struct device *, void *, void *); void tipmic_attach(struct device *, struct device *, void *); struct cfattach tipmic_ca = { sizeof(struct tipmic_softc), tipmic_match, tipmic_attach }; struct cfdriver tipmic_cd = { NULL, "tipmic", DV_DULL }; uint8_t tipmic_read_1(struct tipmic_softc *, uint8_t, int); void tipmic_write_1(struct tipmic_softc *, uint8_t, uint8_t, int); int tipmic_intr(void *); void tipmic_get_lpat(struct tipmic_softc *); int32_t tipmic_raw_to_temp(struct tipmic_softc *, int32_t); int tipmic_thermal_opreg_handler(void *, int, uint64_t, int, uint64_t *); int tipmic_read_pin(void *, int); void tipmic_write_pin(void *, int, int); int tipmic_match(struct device *parent, void *match, void *aux) { struct i2c_attach_args *ia = aux; return (strcmp(ia->ia_name, "INT33F5") == 0); } void tipmic_attach(struct device *parent, struct device *self, void *aux) { struct tipmic_softc *sc = (struct tipmic_softc *)self; struct i2c_attach_args *ia = aux; sc->sc_tag = ia->ia_tag; sc->sc_addr = ia->ia_addr; sc->sc_acpi = acpi_softc; sc->sc_node = ia->ia_cookie; if (ia->ia_intr == NULL) { printf(": no interrupt\n"); return; } /* Mask all interrupts before we install our interrupt handler. */ tipmic_write_1(sc, TIPMIC_INTR_MASK, TIPMIC_INTR_MASK_ALL, I2C_F_POLL); printf(" %s", iic_intr_string(sc->sc_tag, ia->ia_intr)); sc->sc_ih = iic_intr_establish(sc->sc_tag, ia->ia_intr, IPL_BIO, tipmic_intr, sc, sc->sc_dev.dv_xname); if (sc->sc_ih == NULL) { printf(": can't establish interrupt\n"); return; } printf("\n"); tipmic_get_lpat(sc); if (sc->sc_lpat == NULL) return; sc->sc_gpio.cookie = sc; sc->sc_gpio.read_pin = tipmic_read_pin; sc->sc_gpio.write_pin = tipmic_write_pin; sc->sc_node->gpio = &sc->sc_gpio; acpi_register_gpio(sc->sc_acpi, sc->sc_node); /* Register OEM defined address space. */ aml_register_regionspace(sc->sc_node, TIPMIC_REGIONSPACE_THERMAL, sc, tipmic_thermal_opreg_handler); } uint8_t tipmic_read_1(struct tipmic_softc *sc, uint8_t reg, int flags) { uint8_t val; int error; iic_acquire_bus(sc->sc_tag, flags); error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, ®, sizeof(reg), &val, sizeof(val), flags); iic_release_bus(sc->sc_tag, flags); if (error) { printf("%s: can't read register 0x%02x\n", sc->sc_dev.dv_xname, reg); val = 0xff; } return val; } void tipmic_write_1(struct tipmic_softc *sc, uint8_t reg, uint8_t val, int flags) { int error; iic_acquire_bus(sc->sc_tag, flags); error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, ®, sizeof(reg), &val, sizeof(val), flags); iic_release_bus(sc->sc_tag, flags); if (error) { printf("%s: can't write register 0x%02x\n", sc->sc_dev.dv_xname, reg); } } int tipmic_intr(void *arg) { struct tipmic_softc *sc = arg; int handled = 0; uint8_t stat; stat = tipmic_read_1(sc, TIPMIC_INTR_STAT, I2C_F_POLL); tipmic_write_1(sc, TIPMIC_INTR_STAT, stat, I2C_F_POLL); if (stat & TIPMIC_INTR_STAT_ADC) { sc->sc_stat_adc = 1; wakeup(&sc->sc_stat_adc); handled = 1; } return handled; } void tipmic_get_lpat(struct tipmic_softc *sc) { struct aml_value res; int i; if (aml_evalname(sc->sc_acpi, sc->sc_node, "LPAT", 0, NULL, &res)) return; if (res.type != AML_OBJTYPE_PACKAGE) goto out; if (res.length < 4 || (res.length % 2) != 0) goto out; sc->sc_lpat_len = res.length / 2; sc->sc_lpat = mallocarray(sc->sc_lpat_len, sizeof(struct acpi_lpat), M_DEVBUF, M_WAITOK); for (i = 0; i < sc->sc_lpat_len; i++) { sc->sc_lpat[i].temp = aml_val2int(res.v_package[2 * i]); sc->sc_lpat[i].raw = aml_val2int(res.v_package[2 * i + 1]); } out: aml_freevalue(&res); } int32_t tipmic_raw_to_temp(struct tipmic_softc *sc, int32_t raw) { struct acpi_lpat *lpat = sc->sc_lpat; int32_t raw0, delta_raw; int32_t temp0, delta_temp; int i; for (i = 1; i < sc->sc_lpat_len; i++) { /* Coefficient can be positive or negative. */ if (raw >= lpat[i - 1].raw && raw <= lpat[i].raw) break; if (raw <= lpat[i - 1].raw && raw >= lpat[i].raw) break; } if (i == sc->sc_lpat_len) return -1; raw0 = lpat[i - 1].raw; temp0 = lpat[i - 1].temp; delta_raw = lpat[i].raw - raw0; delta_temp = lpat[i].temp - temp0; return temp0 + (raw - raw0) * delta_temp / delta_raw; } struct tipmic_regmap { uint8_t address; uint8_t hi, lo; }; struct tipmic_regmap tipmic_thermal_regmap[] = { { 0x18, TIPMIC_SYSTEMP_HI, TIPMIC_SYSTEMP_LO } }; int tipmic_thermal_opreg_handler(void *cookie, int iodir, uint64_t address, int size, uint64_t *value) { struct tipmic_softc *sc = cookie; int32_t temp; uint16_t raw; uint8_t hi, lo; uint8_t reg; int i, s; /* Only allow 32-bit read access. */ if (size != 4 || iodir != ACPI_IOREAD) return -1; for (i = 0; i < nitems(tipmic_thermal_regmap); i++) { if (address == tipmic_thermal_regmap[i].address) break; } if (i == nitems(tipmic_thermal_regmap)) return -1; /* Turn ADC on and select the appropriate channel. */ reg = tipmic_read_1(sc, TIPMIC_ADC_CTRL, 0); reg |= TIPMIC_ADC_CTRL_EN; tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0); switch (tipmic_thermal_regmap[i].hi) { case TIPMIC_SYSTEMP_HI: reg |= TIPMIC_ADC_CTRL_CH_SYSTEMP; break; default: panic("%s: unsupported channel", sc->sc_dev.dv_xname); } tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0); /* Need to wait 50us before starting the conversion. */ delay(50); /* Start conversion. */ sc->sc_stat_adc = 0; reg |= TIPMIC_ADC_CTRL_START; tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0); /* * Block interrupts to prevent I2C access from the interrupt * handler during the completion of the write that unmasks the * ADC interrupt. */ s = splbio(); reg = tipmic_read_1(sc, TIPMIC_INTR_MASK, I2C_F_POLL); reg &= ~TIPMIC_INTR_MASK_ADC; tipmic_write_1(sc, TIPMIC_INTR_MASK, reg, I2C_F_POLL); splx(s); while (sc->sc_stat_adc == 0) { if (tsleep(&sc->sc_stat_adc, PRIBIO, "tipmic", hz)) { printf("%s: ADC timeout\n", sc->sc_dev.dv_xname); break; } } /* Mask ADC interrupt again. */ s = splbio(); reg = tipmic_read_1(sc, TIPMIC_INTR_MASK, I2C_F_POLL); reg |= TIPMIC_INTR_MASK_ADC; tipmic_write_1(sc, TIPMIC_INTR_MASK, reg, I2C_F_POLL); splx(s); hi = tipmic_thermal_regmap[i].hi; lo = tipmic_thermal_regmap[i].lo; raw = (tipmic_read_1(sc, hi, 0) & 0x03) << 8; raw |= tipmic_read_1(sc, lo, 0); /* Turn ADC off. */ reg = tipmic_read_1(sc, TIPMIC_ADC_CTRL, 0); reg &= ~(TIPMIC_ADC_CTRL_EN | TIPMIC_ADC_CTRL_CH_MASK); tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0); temp = tipmic_raw_to_temp(sc, raw); if (temp < 0) return -1; *value = temp; return 0; } /* * Allegdly the GPIOs are virtual and only there to deal with a * limitation of Microsoft Windows. */ int tipmic_read_pin(void *cookie, int pin) { return 0; } void tipmic_write_pin(void *cookie, int pin, int value) { }