/* $OpenBSD: acpiec.c,v 1.66 2024/06/25 11:57:10 kettenis Exp $ */ /* * Copyright (c) 2006 Can Erkin Acar * * 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 int acpiec_match(struct device *, void *, void *); void acpiec_attach(struct device *, struct device *, void *); uint8_t acpiec_status(struct acpiec_softc *); uint8_t acpiec_read_data(struct acpiec_softc *); void acpiec_write_cmd(struct acpiec_softc *, uint8_t); void acpiec_write_data(struct acpiec_softc *, uint8_t); void acpiec_burst_enable(struct acpiec_softc *sc); void acpiec_burst_disable(struct acpiec_softc *sc); uint8_t acpiec_read_1(struct acpiec_softc *, uint8_t); void acpiec_write_1(struct acpiec_softc *, uint8_t, uint8_t); void acpiec_read(struct acpiec_softc *, uint8_t, int, uint8_t *); void acpiec_write(struct acpiec_softc *, uint8_t, int, uint8_t *); int acpiec_getcrs(struct acpiec_softc *, struct acpi_attach_args *); int acpiec_parse_resources(int, union acpi_resource *, void *); void acpiec_wait(struct acpiec_softc *, uint8_t, uint8_t); void acpiec_sci_event(struct acpiec_softc *); void acpiec_get_events(struct acpiec_softc *); int acpiec_gpehandler(struct acpi_softc *, int, void *); /* EC Status bits */ #define EC_STAT_SMI_EVT 0x40 /* SMI event pending */ #define EC_STAT_SCI_EVT 0x20 /* SCI event pending */ #define EC_STAT_BURST 0x10 /* Controller in burst mode */ #define EC_STAT_CMD 0x08 /* data is command */ #define EC_STAT_IBF 0x02 /* input buffer full */ #define EC_STAT_OBF 0x01 /* output buffer full */ /* EC Commands */ #define EC_CMD_RD 0x80 /* Read */ #define EC_CMD_WR 0x81 /* Write */ #define EC_CMD_BE 0x82 /* Burst Enable */ #define EC_CMD_BD 0x83 /* Burst Disable */ #define EC_CMD_QR 0x84 /* Query */ int acpiec_reg(struct acpiec_softc *); const struct cfattach acpiec_ca = { sizeof(struct acpiec_softc), acpiec_match, acpiec_attach }; struct cfdriver acpiec_cd = { NULL, "acpiec", DV_DULL }; const char *acpiec_hids[] = { ACPI_DEV_ECD, NULL }; void acpiec_wait(struct acpiec_softc *sc, uint8_t mask, uint8_t val) { static int acpiecnowait; uint8_t stat; dnprintf(40, "%s: EC wait_ns for: %b == %02x\n", DEVNAME(sc), (int)mask, "\20\x8IGN\x7SMI\x6SCI\05BURST\04CMD\03IGN\02IBF\01OBF", (int)val); while (((stat = acpiec_status(sc)) & mask) != val) { if (stat & EC_STAT_SCI_EVT) sc->sc_gotsci = 1; if (cold || (stat & EC_STAT_BURST)) delay(1); else tsleep(&acpiecnowait, PWAIT, "acpiec", 1); } dnprintf(40, "%s: EC wait_ns, stat: %b\n", DEVNAME(sc), (int)stat, "\20\x8IGN\x7SMI\x6SCI\05BURST\04CMD\03IGN\02IBF\01OBF"); } uint8_t acpiec_status(struct acpiec_softc *sc) { return (bus_space_read_1(sc->sc_cmd_bt, sc->sc_cmd_bh, 0)); } void acpiec_write_data(struct acpiec_softc *sc, uint8_t val) { acpiec_wait(sc, EC_STAT_IBF, 0); dnprintf(40, "acpiec: write_data -- %d\n", (int)val); bus_space_write_1(sc->sc_data_bt, sc->sc_data_bh, 0, val); } void acpiec_write_cmd(struct acpiec_softc *sc, uint8_t val) { acpiec_wait(sc, EC_STAT_IBF, 0); dnprintf(40, "acpiec: write_cmd -- %d\n", (int)val); bus_space_write_1(sc->sc_cmd_bt, sc->sc_cmd_bh, 0, val); } uint8_t acpiec_read_data(struct acpiec_softc *sc) { uint8_t val; acpiec_wait(sc, EC_STAT_OBF, EC_STAT_OBF); val = bus_space_read_1(sc->sc_data_bt, sc->sc_data_bh, 0); dnprintf(40, "acpiec: read_data %d\n", (int)val); return (val); } void acpiec_sci_event(struct acpiec_softc *sc) { uint8_t evt; sc->sc_gotsci = 0; acpiec_wait(sc, EC_STAT_IBF, 0); bus_space_write_1(sc->sc_cmd_bt, sc->sc_cmd_bh, 0, EC_CMD_QR); acpiec_wait(sc, EC_STAT_OBF, EC_STAT_OBF); evt = bus_space_read_1(sc->sc_data_bt, sc->sc_data_bh, 0); if (evt) { dnprintf(10, "%s: sci_event: 0x%02x\n", DEVNAME(sc), (int)evt); aml_evalnode(sc->sc_acpi, sc->sc_events[evt].event, 0, NULL, NULL); } } uint8_t acpiec_read_1(struct acpiec_softc *sc, uint8_t addr) { uint8_t val; if ((acpiec_status(sc) & EC_STAT_SCI_EVT) == EC_STAT_SCI_EVT) sc->sc_gotsci = 1; acpiec_write_cmd(sc, EC_CMD_RD); acpiec_write_data(sc, addr); val = acpiec_read_data(sc); return (val); } void acpiec_write_1(struct acpiec_softc *sc, uint8_t addr, uint8_t data) { if ((acpiec_status(sc) & EC_STAT_SCI_EVT) == EC_STAT_SCI_EVT) sc->sc_gotsci = 1; acpiec_write_cmd(sc, EC_CMD_WR); acpiec_write_data(sc, addr); acpiec_write_data(sc, data); } void acpiec_burst_enable(struct acpiec_softc *sc) { if (sc->sc_cantburst) return; acpiec_write_cmd(sc, EC_CMD_BE); acpiec_read_data(sc); } void acpiec_burst_disable(struct acpiec_softc *sc) { if (sc->sc_cantburst) return; if ((acpiec_status(sc) & EC_STAT_BURST) == EC_STAT_BURST) acpiec_write_cmd(sc, EC_CMD_BD); } void acpiec_read(struct acpiec_softc *sc, uint8_t addr, int len, uint8_t *buffer) { int reg; /* * this works because everything runs in the acpi thread context. * at some point add a lock to deal with concurrency so that a * transaction does not get interrupted. */ dnprintf(20, "%s: read %d, %d\n", DEVNAME(sc), (int)addr, len); sc->sc_ecbusy = 1; acpiec_burst_enable(sc); for (reg = 0; reg < len; reg++) buffer[reg] = acpiec_read_1(sc, addr + reg); acpiec_burst_disable(sc); sc->sc_ecbusy = 0; } void acpiec_write(struct acpiec_softc *sc, uint8_t addr, int len, uint8_t *buffer) { int reg; /* * this works because everything runs in the acpi thread context. * at some point add a lock to deal with concurrency so that a * transaction does not get interrupted. */ dnprintf(20, "%s: write %d, %d\n", DEVNAME(sc), (int)addr, len); sc->sc_ecbusy = 1; acpiec_burst_enable(sc); for (reg = 0; reg < len; reg++) acpiec_write_1(sc, addr + reg, buffer[reg]); acpiec_burst_disable(sc); sc->sc_ecbusy = 0; } int acpiec_match(struct device *parent, void *match, void *aux) { struct acpi_attach_args *aa = aux; struct cfdata *cf = match; struct acpi_ecdt *ecdt = aa->aaa_table; struct acpi_softc *acpisc = (struct acpi_softc *)parent; /* Check for early ECDT table attach */ if (ecdt && !memcmp(ecdt->hdr.signature, ECDT_SIG, sizeof(ECDT_SIG) - 1)) return (1); if (acpisc->sc_ec) return (0); /* sanity */ return (acpi_matchhids(aa, acpiec_hids, cf->cf_driver->cd_name)); } void acpiec_attach(struct device *parent, struct device *self, void *aux) { struct acpiec_softc *sc = (struct acpiec_softc *)self; struct acpi_attach_args *aa = aux; #ifndef SMALL_KERNEL struct acpi_wakeq *wq; #endif struct aml_value res; int64_t st; sc->sc_acpi = (struct acpi_softc *)parent; sc->sc_devnode = aa->aaa_node; sc->sc_cantburst = 0; if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "_STA", 0, NULL, &st)) st = STA_PRESENT | STA_ENABLED | STA_DEV_OK; if ((st & STA_PRESENT) == 0) { printf(": not present\n"); return; } printf("\n"); if (acpiec_getcrs(sc, aa)) { printf("%s: Failed to read resource settings\n", DEVNAME(sc)); return; } sc->sc_acpi->sc_ec = sc; if (acpiec_reg(sc)) { printf("%s: Failed to register address space\n", DEVNAME(sc)); return; } /* * Some Chromebooks using the Google EC do not support burst mode and * cause us to spin forever waiting for the acknowledgment. Don't use * burst mode at all on these machines. */ if (hw_vendor != NULL && hw_prod != NULL && strcmp(hw_vendor, "GOOGLE") == 0 && strcmp(hw_prod, "Samus") == 0) sc->sc_cantburst = 1; acpiec_get_events(sc); dnprintf(10, "%s: GPE: %d\n", DEVNAME(sc), sc->sc_gpe); #ifndef SMALL_KERNEL acpi_set_gpehandler(sc->sc_acpi, sc->sc_gpe, acpiec_gpehandler, sc, GPE_EDGE); /* * On many machines the EC is not listed as a wakeup device * but is necessary to wake up from S0i. */ wq = malloc(sizeof(struct acpi_wakeq), M_DEVBUF, M_WAITOK | M_ZERO); wq->q_node = sc->sc_devnode; wq->q_gpe = sc->sc_gpe; wq->q_state = ACPI_STATE_S0; wq->q_enabled = 1; SIMPLEQ_INSERT_TAIL(&sc->sc_acpi->sc_wakedevs, wq, q_next); #endif if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "_GLK", 0, NULL, &res)) sc->sc_glk = 0; else if (res.type != AML_OBJTYPE_INTEGER) sc->sc_glk = 0; else sc->sc_glk = res.v_integer ? 1 : 0; } void acpiec_get_events(struct acpiec_softc *sc) { int idx; char name[16]; memset(sc->sc_events, 0, sizeof(sc->sc_events)); for (idx = 0; idx < ACPIEC_MAX_EVENTS; idx++) { snprintf(name, sizeof(name), "_Q%02X", idx); sc->sc_events[idx].event = aml_searchname(sc->sc_devnode, name); if (sc->sc_events[idx].event != NULL) dnprintf(10, "%s: Found event %s\n", DEVNAME(sc), name); } } int acpiec_gpehandler(struct acpi_softc *acpi_sc, int gpe, void *arg) { struct acpiec_softc *sc = arg; uint8_t mask, stat, en; int s; KASSERT(sc->sc_ecbusy == 0); dnprintf(10, "ACPIEC: got gpe\n"); do { if (sc->sc_gotsci) acpiec_sci_event(sc); stat = acpiec_status(sc); dnprintf(40, "%s: EC interrupt, stat: %b\n", DEVNAME(sc), (int)stat, "\20\x8IGN\x7SMI\x6SCI\05BURST\04CMD\03IGN\02IBF\01OBF"); if (stat & EC_STAT_SCI_EVT) sc->sc_gotsci = 1; else sc->sc_gotsci = 0; } while (sc->sc_gotsci); /* Unmask the GPE which was blocked at interrupt time */ s = splbio(); mask = (1L << (gpe & 7)); en = acpi_read_pmreg(acpi_sc, ACPIREG_GPE_EN, gpe>>3); acpi_write_pmreg(acpi_sc, ACPIREG_GPE_EN, gpe>>3, en | mask); splx(s); return (0); } int acpiec_parse_resources(int crsidx, union acpi_resource *crs, void *arg) { struct acpiec_softc *sc = arg; int type = AML_CRSTYPE(crs); switch (crsidx) { case 0: if (type != SR_IOPORT) { printf("%s: Unexpected resource #%d type %d\n", DEVNAME(sc), crsidx, type); break; } sc->sc_data_bt = sc->sc_acpi->sc_iot; sc->sc_ec_data = crs->sr_ioport._max; break; case 1: if (type != SR_IOPORT) { printf("%s: Unexpected resource #%d type %d\n", DEVNAME(sc), crsidx, type); break; } sc->sc_cmd_bt = sc->sc_acpi->sc_iot; sc->sc_ec_sc = crs->sr_ioport._max; break; case 2: if (!sc->sc_acpi->sc_hw_reduced) { printf("%s: Not running on HW-Reduced ACPI type %d\n", DEVNAME(sc), type); break; } /* XXX: handle SCI GPIO */ break; default: printf("%s: invalid resource #%d type %d\n", DEVNAME(sc), crsidx, type); } return 0; } int acpiec_getcrs(struct acpiec_softc *sc, struct acpi_attach_args *aa) { struct aml_value res; int64_t gpe; struct acpi_ecdt *ecdt = aa->aaa_table; int rc; /* Check if this is ECDT initialization */ if (ecdt) { /* Get GPE, Data and Control segments */ sc->sc_gpe = ecdt->gpe_bit; if (ecdt->ec_control.address_space_id == GAS_SYSTEM_IOSPACE) sc->sc_cmd_bt = sc->sc_acpi->sc_iot; else sc->sc_cmd_bt = sc->sc_acpi->sc_memt; sc->sc_ec_sc = ecdt->ec_control.address; if (ecdt->ec_data.address_space_id == GAS_SYSTEM_IOSPACE) sc->sc_data_bt = sc->sc_acpi->sc_iot; else sc->sc_data_bt = sc->sc_acpi->sc_memt; sc->sc_ec_data = ecdt->ec_data.address; /* Get devnode from header */ sc->sc_devnode = aml_searchname(sc->sc_acpi->sc_root, ecdt->ec_id); goto ecdtdone; } rc = aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "_GPE", 0, NULL, &gpe); if (rc) { dnprintf(10, "%s: no _GPE\n", DEVNAME(sc)); return (1); } sc->sc_gpe = gpe; if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "_CRS", 0, NULL, &res)) { dnprintf(10, "%s: no _CRS\n", DEVNAME(sc)); return (1); } /* Parse CRS to get control and data registers */ if (res.type != AML_OBJTYPE_BUFFER) { dnprintf(10, "%s: unknown _CRS type %d\n", DEVNAME(sc), res.type); aml_freevalue(&res); return (1); } aml_parse_resource(&res, acpiec_parse_resources, sc); aml_freevalue(&res); if (sc->sc_ec_data == 0 || sc->sc_ec_sc == 0) { printf("%s: failed to read from _CRS\n", DEVNAME(sc)); return (1); } ecdtdone: dnprintf(10, "%s: Data: 0x%lx, S/C: 0x%lx\n", DEVNAME(sc), sc->sc_ec_data, sc->sc_ec_sc); if (bus_space_map(sc->sc_cmd_bt, sc->sc_ec_sc, 1, 0, &sc->sc_cmd_bh)) { dnprintf(10, "%s: failed to map S/C reg.\n", DEVNAME(sc)); return (1); } rc = bus_space_map(sc->sc_data_bt, sc->sc_ec_data, 1, 0, &sc->sc_data_bh); if (rc) { dnprintf(10, "%s: failed to map DATA reg.\n", DEVNAME(sc)); bus_space_unmap(sc->sc_cmd_bt, sc->sc_cmd_bh, 1); return (1); } return (0); } int acpiec_reg(struct acpiec_softc *sc) { struct aml_value arg[2]; struct aml_node *node; memset(&arg, 0, sizeof(arg)); arg[0].type = AML_OBJTYPE_INTEGER; arg[0].v_integer = ACPI_OPREG_EC; arg[1].type = AML_OBJTYPE_INTEGER; arg[1].v_integer = 1; node = aml_searchname(sc->sc_devnode, "_REG"); if (node && aml_evalnode(sc->sc_acpi, node, 2, arg, NULL)) { dnprintf(10, "%s: eval method _REG failed\n", DEVNAME(sc)); printf("acpiec _REG failed, broken BIOS\n"); } return (0); }