1/* $OpenBSD: ccpmic.c,v 1.3 2022/04/06 18:59:27 naddy Exp $ */ 2/* 3 * Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <sys/param.h> 19#include <sys/systm.h> 20#include <sys/device.h> 21#include <sys/kernel.h> 22#include <sys/malloc.h> 23 24#include <dev/acpi/acpireg.h> 25#include <dev/acpi/acpivar.h> 26#include <dev/acpi/acpidev.h> 27#include <dev/acpi/amltypes.h> 28#include <dev/acpi/dsdt.h> 29 30#include <dev/i2c/i2cvar.h> 31 32#define CCPMIC_GPIO0P0CTLO 0x2b 33#define CCPMIC_GPIO0P0CTLI 0x33 34#define CCPMIC_GPIO1P0CTLO 0x3b 35#define CCPMIC_GPIO1P0CTLI 0x43 36#define CCPMIC_GPIOPANELCTL 0x52 37#define CCPMIC_GPIOCTLO_RVAL_2KUP (1 << 1) 38#define CCPMIC_GPIOCTLO_DRV_REN (1 << 3) 39#define CCPMIC_GPIOCTLO_DIR_OUT (1 << 5) 40#define CCPMIC_GPIOCTLI_VALUE (1 << 0) 41 42#define CCPMIC_GPIOCTLO_INPUT \ 43 (CCPMIC_GPIOCTLO_DRV_REN | CCPMIC_GPIOCTLO_RVAL_2KUP) 44#define CCPMIC_GPIOCTLO_OUTPUT \ 45 (CCPMIC_GPIOCTLO_INPUT | CCPMIC_GPIOCTLO_DIR_OUT) 46 47#define CCPMIC_V1P8SX 0x5d 48#define CCPMIC_V1P2SX 0x61 49#define CCPMIC_V2P85SX 0x66 50#define CCPMIC_PWR_ON (1 << 0) 51#define CCPMIC_PWR_SEL (1 << 1) 52#define CCPMIC_SYS2_THRM_RSLT_H 0x78 53#define CCPMIC_SYS2_THRM_RSLT_L 0x79 54 55#define CCPMIC_REGIONSPACE_THERMAL 0x8c 56#define CCPMIC_REGIONSPACE_POWER 0x8d 57 58#define CCPMIC_NPINS 16 59 60struct acpi_lpat { 61 int32_t temp; 62 int32_t raw; 63}; 64 65struct ccpmic_softc { 66 struct device sc_dev; 67 struct acpi_softc *sc_acpi; 68 struct aml_node *sc_node; 69 i2c_tag_t sc_tag; 70 i2c_addr_t sc_addr; 71 72 struct acpi_lpat *sc_lpat; 73 size_t sc_lpat_len; 74 75 struct acpi_gpio sc_gpio; 76}; 77 78int ccpmic_match(struct device *, void *, void *); 79void ccpmic_attach(struct device *, struct device *, void *); 80 81const struct cfattach ccpmic_ca = { 82 sizeof(struct ccpmic_softc), ccpmic_match, ccpmic_attach 83}; 84 85struct cfdriver ccpmic_cd = { 86 NULL, "ccpmic", DV_DULL 87}; 88 89uint8_t ccpmic_read_1(struct ccpmic_softc *, uint8_t, int); 90void ccpmic_write_1(struct ccpmic_softc *, uint8_t, uint8_t, int); 91void ccpmic_get_lpat(struct ccpmic_softc *); 92int32_t ccpmic_raw_to_temp(struct ccpmic_softc *, int32_t); 93int ccpmic_thermal_opreg_handler(void *, int, uint64_t, int, uint64_t *); 94int ccpmic_power_opreg_handler(void *, int, uint64_t, int, uint64_t *); 95int ccpmic_read_pin(void *, int); 96void ccpmic_write_pin(void *, int, int); 97 98int 99ccpmic_match(struct device *parent, void *match, void *aux) 100{ 101 struct i2c_attach_args *ia = aux; 102 103 return (strcmp(ia->ia_name, "INT33FD") == 0); 104} 105 106void 107ccpmic_attach(struct device *parent, struct device *self, void *aux) 108{ 109 struct ccpmic_softc *sc = (struct ccpmic_softc *)self; 110 struct i2c_attach_args *ia = aux; 111 112 sc->sc_tag = ia->ia_tag; 113 sc->sc_addr = ia->ia_addr; 114 sc->sc_acpi = acpi_softc; 115 sc->sc_node = ia->ia_cookie; 116 117 printf("\n"); 118 119 ccpmic_get_lpat(sc); 120 if (sc->sc_lpat == NULL) 121 return; 122 123 sc->sc_gpio.cookie = sc; 124 sc->sc_gpio.read_pin = ccpmic_read_pin; 125 sc->sc_gpio.write_pin = ccpmic_write_pin; 126 sc->sc_node->gpio = &sc->sc_gpio; 127 acpi_register_gpio(sc->sc_acpi, sc->sc_node); 128 129 /* Register OEM defined address space. */ 130 aml_register_regionspace(sc->sc_node, CCPMIC_REGIONSPACE_THERMAL, 131 sc, ccpmic_thermal_opreg_handler); 132 aml_register_regionspace(sc->sc_node, CCPMIC_REGIONSPACE_POWER, 133 sc, ccpmic_power_opreg_handler); 134} 135 136uint8_t 137ccpmic_read_1(struct ccpmic_softc *sc, uint8_t reg, int flags) 138{ 139 uint8_t val; 140 int error; 141 142 iic_acquire_bus(sc->sc_tag, flags); 143 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 144 ®, sizeof(reg), &val, sizeof(val), flags); 145 iic_release_bus(sc->sc_tag, flags); 146 147 if (error) { 148 printf("%s: can't read register 0x%02x\n", 149 sc->sc_dev.dv_xname, reg); 150 val = 0xff; 151 } 152 153 return val; 154} 155 156void 157ccpmic_write_1(struct ccpmic_softc *sc, uint8_t reg, uint8_t val, int flags) 158{ 159 int error; 160 161 iic_acquire_bus(sc->sc_tag, flags); 162 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 163 ®, sizeof(reg), &val, sizeof(val), flags); 164 iic_release_bus(sc->sc_tag, flags); 165 166 if (error) { 167 printf("%s: can't write register 0x%02x\n", 168 sc->sc_dev.dv_xname, reg); 169 } 170} 171 172void 173ccpmic_get_lpat(struct ccpmic_softc *sc) 174{ 175 struct aml_value res; 176 int i; 177 178 if (aml_evalname(sc->sc_acpi, sc->sc_node, "LPAT", 0, NULL, &res)) 179 return; 180 if (res.type != AML_OBJTYPE_PACKAGE) 181 goto out; 182 if (res.length < 4 || (res.length % 2) != 0) 183 goto out; 184 185 sc->sc_lpat_len = res.length / 2; 186 sc->sc_lpat = mallocarray(sc->sc_lpat_len, sizeof(struct acpi_lpat), 187 M_DEVBUF, M_WAITOK); 188 189 for (i = 0; i < sc->sc_lpat_len; i++) { 190 sc->sc_lpat[i].temp = aml_val2int(res.v_package[2 * i]); 191 sc->sc_lpat[i].raw = aml_val2int(res.v_package[2 * i + 1]); 192 } 193 194out: 195 aml_freevalue(&res); 196} 197 198int32_t 199ccpmic_raw_to_temp(struct ccpmic_softc *sc, int32_t raw) 200{ 201 struct acpi_lpat *lpat = sc->sc_lpat; 202 int32_t raw0, delta_raw; 203 int32_t temp0, delta_temp; 204 int i; 205 206 for (i = 1; i < sc->sc_lpat_len; i++) { 207 /* Coefficient can be positive or negative. */ 208 if (raw >= lpat[i - 1].raw && raw <= lpat[i].raw) 209 break; 210 if (raw <= lpat[i - 1].raw && raw >= lpat[i].raw) 211 break; 212 } 213 if (i == sc->sc_lpat_len) 214 return -1; 215 216 raw0 = lpat[i - 1].raw; 217 temp0 = lpat[i - 1].temp; 218 delta_raw = lpat[i].raw - raw0; 219 delta_temp = lpat[i].temp - temp0; 220 221 return temp0 + (raw - raw0) * delta_temp / delta_raw; 222} 223 224struct ccpmic_regmap { 225 uint8_t address; 226 uint8_t hi, lo; 227}; 228 229struct ccpmic_regmap ccpmic_thermal_regmap[] = { 230 { 0x18, CCPMIC_SYS2_THRM_RSLT_H, CCPMIC_SYS2_THRM_RSLT_L }, /* TMP2 */ 231}; 232 233int 234ccpmic_thermal_opreg_handler(void *cookie, int iodir, uint64_t address, 235 int size, uint64_t *value) 236{ 237 struct ccpmic_softc *sc = cookie; 238 int32_t temp; 239 uint16_t raw; 240 uint8_t lo, hi; 241 int i; 242 243 /* Only allow 32-bit read access. */ 244 if (size != 4 || iodir != ACPI_IOREAD) 245 return -1; 246 247 for (i = 0; i < nitems(ccpmic_thermal_regmap); i++) { 248 if (address == ccpmic_thermal_regmap[i].address) 249 break; 250 } 251 if (i == nitems(ccpmic_thermal_regmap)) { 252 printf("%s: addr 0x%02llx\n", __func__, address); 253 return -1; 254 } 255 256 lo = ccpmic_thermal_regmap[i].lo; 257 hi = ccpmic_thermal_regmap[i].hi; 258 raw = ccpmic_read_1(sc, lo, 0); 259 raw |= (ccpmic_read_1(sc, hi, 0) & 0x03) << 8; 260 261 temp = ccpmic_raw_to_temp(sc, raw); 262 if (temp < 0) 263 return -1; 264 265 *value = temp; 266 return 0; 267} 268 269struct ccpmic_regmap ccpmic_power_regmap[] = { 270 { 0x24, CCPMIC_V2P85SX }, /* X285 */ 271 { 0x48, CCPMIC_V1P8SX }, /* V18X */ 272 { 0x50, CCPMIC_V1P2SX }, /* V12X */ 273}; 274 275int 276ccpmic_power_opreg_handler(void *cookie, int iodir, uint64_t address, 277 int size, uint64_t *value) 278{ 279 struct ccpmic_softc *sc = cookie; 280 uint8_t reg, val; 281 int i; 282 283 /* Only allow 32-bit access. */ 284 if (size != 4) 285 return -1; 286 287 for (i = 0; i < nitems(ccpmic_power_regmap); i++) { 288 if (address == ccpmic_power_regmap[i].address) 289 break; 290 } 291 if (i == nitems(ccpmic_power_regmap)) { 292 printf("%s: addr 0x%02llx\n", __func__, address); 293 return -1; 294 } 295 296 reg = ccpmic_power_regmap[i].hi; 297 val = ccpmic_read_1(sc, reg, 0); 298 if (iodir == ACPI_IOREAD) { 299 if ((val & CCPMIC_PWR_SEL) && (val & CCPMIC_PWR_ON)) 300 *value = 1; 301 else 302 *value = 0; 303 } else { 304 if (*value) 305 val |= CCPMIC_PWR_ON; 306 else 307 val &= ~CCPMIC_PWR_ON; 308 ccpmic_write_1(sc, reg, val | CCPMIC_PWR_SEL, 0); 309 } 310 311 return 0; 312} 313 314/* 315 * We have 16 real GPIOs and a bunch of virtual ones. The virtual 316 * ones are mostly there to deal with a limitation of Microsoft 317 * Windows. We only implement the "panel" control GPIO, which 318 * actually maps onto a real GPIO. 319 */ 320 321int 322ccpmic_read_pin(void *cookie, int pin) 323{ 324 struct ccpmic_softc *sc = cookie; 325 uint8_t reg; 326 327 if (pin >= CCPMIC_NPINS) 328 return 0; 329 330 reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLO : CCPMIC_GPIO1P0CTLO) + pin % 8; 331 ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_INPUT, 0); 332 reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLI : CCPMIC_GPIO1P0CTLI) + pin % 8; 333 return ccpmic_read_1(sc, reg, 0) & CCPMIC_GPIOCTLI_VALUE; 334} 335 336void 337ccpmic_write_pin(void *cookie, int pin, int value) 338{ 339 struct ccpmic_softc *sc = cookie; 340 uint8_t reg; 341 342 if (pin == 0x5e) { 343 reg = CCPMIC_GPIOPANELCTL; 344 ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_OUTPUT | !!value, 0); 345 return; 346 } 347 348 if (pin >= CCPMIC_NPINS) 349 return; 350 351 reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLO : CCPMIC_GPIO1P0CTLO) + pin % 8; 352 ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_OUTPUT | !!value, 0); 353} 354