/* $OpenBSD: ccpmic.c,v 1.2 2018/05/21 15:00:25 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 CCPMIC_GPIO0P0CTLO 0x2b #define CCPMIC_GPIO0P0CTLI 0x33 #define CCPMIC_GPIO1P0CTLO 0x3b #define CCPMIC_GPIO1P0CTLI 0x43 #define CCPMIC_GPIOPANELCTL 0x52 #define CCPMIC_GPIOCTLO_RVAL_2KUP (1 << 1) #define CCPMIC_GPIOCTLO_DRV_REN (1 << 3) #define CCPMIC_GPIOCTLO_DIR_OUT (1 << 5) #define CCPMIC_GPIOCTLI_VALUE (1 << 0) #define CCPMIC_GPIOCTLO_INPUT \ (CCPMIC_GPIOCTLO_DRV_REN | CCPMIC_GPIOCTLO_RVAL_2KUP) #define CCPMIC_GPIOCTLO_OUTPUT \ (CCPMIC_GPIOCTLO_INPUT | CCPMIC_GPIOCTLO_DIR_OUT) #define CCPMIC_V1P8SX 0x5d #define CCPMIC_V1P2SX 0x61 #define CCPMIC_V2P85SX 0x66 #define CCPMIC_PWR_ON (1 << 0) #define CCPMIC_PWR_SEL (1 << 1) #define CCPMIC_SYS2_THRM_RSLT_H 0x78 #define CCPMIC_SYS2_THRM_RSLT_L 0x79 #define CCPMIC_REGIONSPACE_THERMAL 0x8c #define CCPMIC_REGIONSPACE_POWER 0x8d #define CCPMIC_NPINS 16 struct acpi_lpat { int32_t temp; int32_t raw; }; struct ccpmic_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; struct acpi_lpat *sc_lpat; size_t sc_lpat_len; struct acpi_gpio sc_gpio; }; int ccpmic_match(struct device *, void *, void *); void ccpmic_attach(struct device *, struct device *, void *); struct cfattach ccpmic_ca = { sizeof(struct ccpmic_softc), ccpmic_match, ccpmic_attach }; struct cfdriver ccpmic_cd = { NULL, "ccpmic", DV_DULL }; uint8_t ccpmic_read_1(struct ccpmic_softc *, uint8_t, int); void ccpmic_write_1(struct ccpmic_softc *, uint8_t, uint8_t, int); void ccpmic_get_lpat(struct ccpmic_softc *); int32_t ccpmic_raw_to_temp(struct ccpmic_softc *, int32_t); int ccpmic_thermal_opreg_handler(void *, int, uint64_t, int, uint64_t *); int ccpmic_power_opreg_handler(void *, int, uint64_t, int, uint64_t *); int ccpmic_read_pin(void *, int); void ccpmic_write_pin(void *, int, int); int ccpmic_match(struct device *parent, void *match, void *aux) { struct i2c_attach_args *ia = aux; return (strcmp(ia->ia_name, "INT33FD") == 0); } void ccpmic_attach(struct device *parent, struct device *self, void *aux) { struct ccpmic_softc *sc = (struct ccpmic_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; printf("\n"); ccpmic_get_lpat(sc); if (sc->sc_lpat == NULL) return; sc->sc_gpio.cookie = sc; sc->sc_gpio.read_pin = ccpmic_read_pin; sc->sc_gpio.write_pin = ccpmic_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, CCPMIC_REGIONSPACE_THERMAL, sc, ccpmic_thermal_opreg_handler); aml_register_regionspace(sc->sc_node, CCPMIC_REGIONSPACE_POWER, sc, ccpmic_power_opreg_handler); } uint8_t ccpmic_read_1(struct ccpmic_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 ccpmic_write_1(struct ccpmic_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); } } void ccpmic_get_lpat(struct ccpmic_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 ccpmic_raw_to_temp(struct ccpmic_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 ccpmic_regmap { uint8_t address; uint8_t hi, lo; }; struct ccpmic_regmap ccpmic_thermal_regmap[] = { { 0x18, CCPMIC_SYS2_THRM_RSLT_H, CCPMIC_SYS2_THRM_RSLT_L }, /* TMP2 */ }; int ccpmic_thermal_opreg_handler(void *cookie, int iodir, uint64_t address, int size, uint64_t *value) { struct ccpmic_softc *sc = cookie; int32_t temp; uint16_t raw; uint8_t lo, hi; int i; /* Only allow 32-bit read access. */ if (size != 4 || iodir != ACPI_IOREAD) return -1; for (i = 0; i < nitems(ccpmic_thermal_regmap); i++) { if (address == ccpmic_thermal_regmap[i].address) break; } if (i == nitems(ccpmic_thermal_regmap)) { printf("%s: addr 0x%02llx\n", __func__, address); return -1; } lo = ccpmic_thermal_regmap[i].lo; hi = ccpmic_thermal_regmap[i].hi; raw = ccpmic_read_1(sc, lo, 0); raw |= (ccpmic_read_1(sc, hi, 0) & 0x03) << 8; temp = ccpmic_raw_to_temp(sc, raw); if (temp < 0) return -1; *value = temp; return 0; } struct ccpmic_regmap ccpmic_power_regmap[] = { { 0x24, CCPMIC_V2P85SX }, /* X285 */ { 0x48, CCPMIC_V1P8SX }, /* V18X */ { 0x50, CCPMIC_V1P2SX }, /* V12X */ }; int ccpmic_power_opreg_handler(void *cookie, int iodir, uint64_t address, int size, uint64_t *value) { struct ccpmic_softc *sc = cookie; uint8_t reg, val; int i; /* Only allow 32-bit access. */ if (size != 4) return -1; for (i = 0; i < nitems(ccpmic_power_regmap); i++) { if (address == ccpmic_power_regmap[i].address) break; } if (i == nitems(ccpmic_power_regmap)) { printf("%s: addr 0x%02llx\n", __func__, address); return -1; } reg = ccpmic_power_regmap[i].hi; val = ccpmic_read_1(sc, reg, 0); if (iodir == ACPI_IOREAD) { if ((val & CCPMIC_PWR_SEL) && (val & CCPMIC_PWR_ON)) *value = 1; else *value = 0; } else { if (*value) val |= CCPMIC_PWR_ON; else val &= ~CCPMIC_PWR_ON; ccpmic_write_1(sc, reg, val | CCPMIC_PWR_SEL, 0); } return 0; } /* * We have 16 real GPIOs and a bunch of virtual ones. The virtual * ones are mostly there to deal with a limitation of Microsoft * Windows. We only implement the "panel" control GPIO, which * actually maps onto a real GPIO. */ int ccpmic_read_pin(void *cookie, int pin) { struct ccpmic_softc *sc = cookie; uint8_t reg; if (pin >= CCPMIC_NPINS) return 0; reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLO : CCPMIC_GPIO1P0CTLO) + pin % 8; ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_INPUT, 0); reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLI : CCPMIC_GPIO1P0CTLI) + pin % 8; return ccpmic_read_1(sc, reg, 0) & CCPMIC_GPIOCTLI_VALUE; } void ccpmic_write_pin(void *cookie, int pin, int value) { struct ccpmic_softc *sc = cookie; uint8_t reg; if (pin == 0x5e) { reg = CCPMIC_GPIOPANELCTL; ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_OUTPUT | !!value, 0); return; } if (pin >= CCPMIC_NPINS) return; reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLO : CCPMIC_GPIO1P0CTLO) + pin % 8; ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_OUTPUT | !!value, 0); }