/* $OpenBSD: acpiec.c,v 1.1 2006/05/29 00:54:23 canacar 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 #include #include #include int acpiec_match(struct device *, void *, void *); void acpiec_attach(struct device *, struct device *, void *); int acpiec_notify(struct aml_node *, int, void *); u_int8_t acpiec_status(struct acpiec_softc *); u_int8_t acpiec_read_data(struct acpiec_softc *); void acpiec_write_cmd(struct acpiec_softc *, u_int8_t); void acpiec_write_data(struct acpiec_softc *, u_int8_t); void acpiec_burst_enable(struct acpiec_softc *sc); u_int8_t acpiec_read_1(struct acpiec_softc *, u_int8_t); void acpiec_write_1(struct acpiec_softc *, u_int8_t, u_int8_t); void acpiec_read(struct acpiec_softc *, u_int8_t, int, u_int8_t *); void acpiec_write(struct acpiec_softc *, u_int8_t, int, u_int8_t *); int acpiec_getcrs(struct acpiec_softc *, struct acpi_attach_args *); int acpiec_getregister(const u_int8_t *, int, int *, bus_size_t *); void acpiec_wait(struct acpiec_softc *, u_int8_t, u_int8_t); void acpiec_wait_nosleep(struct acpiec_softc *, u_int8_t, u_int8_t); void acpiec_sci_event(struct acpiec_softc *); void acpiec_get_events(struct acpiec_softc *); struct aml_node *aml_find_name(struct acpi_softc *, struct aml_node *, const char *); /* EC Staus 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 */ #define ACPIEC_MAX_EVENTS 256 struct acpiec_event { struct aml_node *event; int active; }; struct acpiec_softc { struct device sc_dev; /* command/status register */ bus_space_tag_t sc_cmd_bt; bus_space_handle_t sc_cmd_bh; /* data register */ bus_space_tag_t sc_data_bt; bus_space_handle_t sc_data_bh; struct acpi_softc *sc_acpi; struct aml_node *sc_devnode; u_int32_t sc_gpe; struct acpiec_event sc_events[ACPIEC_MAX_EVENTS]; struct rwlock sc_lock; int sc_locked; int sc_pending; }; int acpiec_reg(struct acpiec_softc *); struct cfattach acpiec_ca = { sizeof(struct acpiec_softc), acpiec_match, acpiec_attach }; struct cfdriver acpiec_cd = { NULL, "acpiec", DV_DULL }; int acpiec_intr(struct acpiec_softc *sc) { u_int8_t stat; 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_IBF | EC_STAT_OBF | EC_STAT_BURST )) wakeup(sc); if ((stat & EC_STAT_SCI_EVT) != 0 && sc->sc_locked == 0) acpiec_sci_event(sc); return (sc->sc_pending); } void acpiec_wait(struct acpiec_softc *sc, u_int8_t mask, u_int8_t val) { u_int8_t stat; dnprintf(40, "%s: EC wait for: %b == %02x\n", DEVNAME(sc), (int)mask, "\20\x8IGN\x7SMI\x6SCI\05BURST\04CMD\03IGN\02IBF\01OBF", (int)val); for (;;) { if (((stat = acpiec_status(sc)) & mask) == val) break; tsleep(sc, PWAIT, "acpiec", 10); } dnprintf(40, "%s: EC wait, stat: %b\n", DEVNAME(sc), (int) stat, "\20\x8IGN\x7SMI\x6SCI\05BURST\04CMD\03IGN\02IBF\01OBF"); } void acpiec_wait_nosleep(struct acpiec_softc *sc, u_int8_t mask, u_int8_t val) { u_int8_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); for (;;) { if (((stat = acpiec_status(sc)) & mask) == val) break; delay(1); } dnprintf(40, "%s: EC wait_ns, stat: %b\n", DEVNAME(sc), (int)stat, "\20\x8IGN\x7SMI\x6SCI\05BURST\04CMD\03IGN\02IBF\01OBF"); } u_int8_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, u_int8_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, u_int8_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); } u_int8_t acpiec_read_data(struct acpiec_softc *sc) { u_int8_t val; acpiec_wait(sc, EC_STAT_OBF, EC_STAT_OBF); dnprintf(40, "acpiec: read_data\n", (int) val); val = bus_space_read_1(sc->sc_data_bt, sc->sc_data_bh, 0); return val; } void acpiec_sci_event(struct acpiec_softc *sc) { u_int8_t evt; acpiec_wait_nosleep(sc, EC_STAT_IBF, 0); bus_space_write_1(sc->sc_cmd_bt, sc->sc_cmd_bh, 0, EC_CMD_QR); acpiec_wait_nosleep(sc, EC_STAT_OBF, EC_STAT_OBF); evt = bus_space_read_1(sc->sc_data_bt, sc->sc_data_bh, 0); dnprintf(10, "%s: sci_event: 0x%02x\n", DEVNAME(sc), (int) evt); sc->sc_events[evt].active = 1; sc->sc_pending = 1; sc->sc_acpi->sc_wakeup = 0; wakeup(sc->sc_acpi); } u_int8_t acpiec_read_1(struct acpiec_softc *sc, u_int8_t addr) { u_int8_t val; if ((acpiec_status(sc) & EC_STAT_SCI_EVT) == EC_STAT_SCI_EVT) acpiec_sci_event(sc); 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, u_int8_t addr, u_int8_t data) { if ((acpiec_status(sc) & EC_STAT_SCI_EVT) == EC_STAT_SCI_EVT) acpiec_sci_event(sc); 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) { acpiec_write_cmd(sc, EC_CMD_BE); acpiec_read_data(sc); } void acpiec_read(struct acpiec_softc *sc, u_int8_t addr, int len, u_int8_t *buffer) { int reg; rw_enter_write(&sc->sc_lock); sc->sc_locked = 1; acpiec_burst_enable(sc); dnprintf(20, "%s: read %d, %d\n", DEVNAME(sc), (int) addr, len); for (reg = 0; reg < len; reg++) buffer[reg] = acpiec_read_1(sc, addr + reg); sc->sc_locked = 0; rw_exit_write(&sc->sc_lock); } void acpiec_write(struct acpiec_softc *sc, u_int8_t addr, int len, u_int8_t *buffer) { int reg; rw_enter_write(&sc->sc_lock); sc->sc_locked = 1; acpiec_burst_enable(sc); dnprintf(20, "%s: write %d, %d\n", DEVNAME(sc), (int) addr, len); for (reg = 0; reg < len; reg++) acpiec_write_1(sc, addr + reg, buffer[reg]); sc->sc_locked = 0; rw_exit_write(&sc->sc_lock); } int acpiec_match(struct device *parent, void *match, void *aux) { struct acpi_attach_args *aa = aux; struct cfdata *cf = match; /* sanity */ if (aa->aaa_name == NULL || strcmp(aa->aaa_name, cf->cf_driver->cd_name) != 0 || aa->aaa_table != NULL) return (0); return (1); } 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; sc->sc_acpi = (struct acpi_softc *)parent; sc->sc_devnode = aa->aaa_node->child; rw_init(&sc->sc_lock, "acpi_ec"); sc->sc_locked = 0; if (sc->sc_acpi->sc_ec != NULL) { printf(": Only single EC is supported!\n"); return; } if (acpiec_getcrs(sc, aa)) { printf(": Failed to read resource settings\n"); return; } if (acpiec_reg(sc)) { printf(": Failed to register address space\n"); return; } acpiec_get_events(sc); sc->sc_acpi->sc_ec = sc; sc->sc_acpi->sc_ec_gpemask = (1L << sc->sc_gpe); dnprintf(10, "%s: GPE: %d (%x)\n", DEVNAME(sc), sc->sc_gpe, sc->sc_acpi->sc_ec_gpemask); /* Enable EC interrupt */ acpi_enable_gpe(sc->sc_acpi, sc->sc_acpi->sc_ec_gpemask); printf(": %s\n", sc->sc_devnode->parent->name); } 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); } } void acpiec_handle_events(struct acpiec_softc *sc) { struct aml_value res; int idx; if (sc->sc_pending == 0) return; sc->sc_pending = 0; for (idx = 0; idx < ACPIEC_MAX_EVENTS; idx++) { if (sc->sc_events[idx].active == 0 || sc->sc_events[idx].event == NULL) continue; dnprintf(10, "%s: handling event 0x%02x\n", DEVNAME(sc), idx); aml_eval_object(sc->sc_acpi, sc->sc_events[idx].event, &res, 0, NULL); sc->sc_events[idx].active = 0; } } /* parse the resource buffer to get a 'register' value */ int acpiec_getregister(const u_int8_t *buf, int size, int *type, bus_size_t *addr) { int len, hlen; #define RES_TYPE_MASK 0x80 #define RES_LENGTH_MASK 0x07 #define RES_TYPE_IOPORT 0x47 #define RES_TYPE_ENDTAG 0x79 if (size <= 0) return (0); if (*buf & RES_TYPE_MASK) { /* large resource */ if (size < 3) return (1); len = (int) buf[1] + 256 * (int) buf[2]; hlen = 3; } else { /* small resource */ len = buf[0] & RES_LENGTH_MASK; hlen = 1; } /* XXX todo: decode other types */ if (*buf != RES_TYPE_IOPORT) return (0); if (size < hlen + len) return (0); /* XXX validate? */ *type = GAS_SYSTEM_IOSPACE; *addr = (int) buf[2] + 256 * (int) buf[3]; return (hlen + len); } int acpiec_getcrs(struct acpiec_softc *sc, struct acpi_attach_args *aa) { struct aml_value res, env; bus_size_t ec_sc, ec_data; int type1, type2; char *buf; int size, ret; memset(&res, 0, sizeof(res)); memset(&env, 0, sizeof(env)); if (aml_eval_name(sc->sc_acpi, sc->sc_devnode, "_GPE", &res, &env)) { dnprintf(10, "%s: no _GPE\n", DEVNAME(sc)); return (1); } sc->sc_gpe = aml_val2int(NULL, &res); if (aml_eval_name(sc->sc_acpi, sc->sc_devnode, "_CRS", &res, &env)) { 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); return (1); } size = res.length; buf = res.v_buffer; ret = acpiec_getregister(buf, size, &type1, &ec_data); if (ret <= 0) { dnprintf(10, "%s: failed to read DATA from _CRS\n", DEVNAME(sc)); return (1); } buf += ret; size -= ret; ret = acpiec_getregister(buf, size, &type2, &ec_sc); if (ret <= 0) { dnprintf(10, "%s: failed to read S/C from _CRS\n", DEVNAME(sc)); return (1); } buf += ret; size -= ret; if (size != 2 || *buf != RES_TYPE_ENDTAG) { dnprintf(10, "%s: no _CRS end tag\n", DEVNAME(sc)); return (1); } /* XXX: todo - validate _CRS checksum? */ dnprintf(10, "%s: Data: 0x%x, S/C: 0x%x\n", DEVNAME(sc), ec_data, ec_sc); if (type1 == GAS_SYSTEM_IOSPACE) sc->sc_cmd_bt = aa->aaa_iot; else sc->sc_cmd_bt = aa->aaa_memt; if (bus_space_map(sc->sc_cmd_bt, ec_sc, 1, 0, &sc->sc_cmd_bh)) { dnprintf(10, "%s: failed to map S/C reg.\n", DEVNAME(sc)); return (1); } if (type2 == GAS_SYSTEM_IOSPACE) sc->sc_data_bt = aa->aaa_iot; else sc->sc_data_bt = aa->aaa_memt; if (bus_space_map(sc->sc_data_bt, ec_data, 1, 0, &sc->sc_data_bh)) { 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 res, arg[2]; struct aml_node *root; memset(&res, 0, sizeof(res)); memset(&arg, 0, sizeof(arg)); #define REG_TYPE_EC 3 arg[0].type = AML_OBJTYPE_INTEGER; arg[0].v_integer = REG_TYPE_EC; arg[1].type = AML_OBJTYPE_INTEGER; arg[1].v_integer = 1; root = aml_find_name(sc->sc_acpi, sc->sc_devnode, "_REG"); if (root == NULL) { dnprintf(10, "%s: no _REG method\n", DEVNAME(sc)); return (1); } if (aml_eval_object(sc->sc_acpi, root, &res, 2, arg)) { dnprintf(10, "%s: evaluating method _REG failed.\n", DEVNAME(sc)); return (1); } return (0); }