#include <sys/param.h>
#include <sys/signalvar.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <machine/bus.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpidev.h>
#include <dev/acpi/amltypes.h>
#include <dev/acpi/dsdt.h>
#include <sys/sensors.h>
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 *);
#define EC_STAT_SMI_EVT 0x40
#define EC_STAT_SCI_EVT 0x20
#define EC_STAT_BURST 0x10
#define EC_STAT_CMD 0x08
#define EC_STAT_IBF 0x02
#define EC_STAT_OBF 0x01
#define EC_CMD_RD 0x80
#define EC_CMD_WR 0x81
#define EC_CMD_BE 0x82
#define EC_CMD_BD 0x83
#define EC_CMD_QR 0x84
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;
int tries = 0;
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) || tries++ < 300)
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;
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;
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;
if (ecdt &&
!memcmp(ecdt->hdr.signature, ECDT_SIG, sizeof(ECDT_SIG) - 1))
return (1);
if (acpisc->sc_ec)
return (0);
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;
}
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);
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);
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;
}
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;
if (ecdt) {
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;
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);
}
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);
}