From 3b91a06c33092d681c3bfb8c0bf4c91bc72bdd80 Mon Sep 17 00:00:00 2001 From: Can Erkin Acar Date: Fri, 27 Jun 2008 06:08:44 +0000 Subject: Access the embedded controller according to the documentation referenced from the hdaps linux driver, and update model detection. Now works on newer thinkpad models. Suggestions from jsg@, tested by many. --- sys/dev/isa/aps.c | 343 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 220 insertions(+), 123 deletions(-) (limited to 'sys/dev') diff --git a/sys/dev/isa/aps.c b/sys/dev/isa/aps.c index 043b082618f..ca9a9a410db 100644 --- a/sys/dev/isa/aps.c +++ b/sys/dev/isa/aps.c @@ -1,6 +1,7 @@ -/* $OpenBSD: aps.c,v 1.16 2008/05/22 19:23:04 mk Exp $ */ +/* $OpenBSD: aps.c,v 1.17 2008/06/27 06:08:43 canacar Exp $ */ /* * Copyright (c) 2005 Jonathan Gray + * Copyright (c) 2008 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 @@ -37,23 +38,62 @@ #define DPRINTF(x) #endif -#define APS_ACCEL_STATE 0x04 -#define APS_INIT 0x10 -#define APS_STATE 0x11 -#define APS_XACCEL 0x12 -#define APS_YACCEL 0x14 -#define APS_TEMP 0x16 -#define APS_XVAR 0x17 -#define APS_YVAR 0x19 -#define APS_TEMP2 0x1b -#define APS_UNKNOWN 0x1c -#define APS_INPUT 0x1d -#define APS_CMD 0x1f -#define APS_STATE_NEWDATA 0x50 - -#define APS_CMD_START 0x01 +/* + * EC interface on Thinkpad Laptops, from Linux HDAPS driver notes. + * From Renesans H8S/2140B Group Hardware Manual + * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf + * + * EC uses LPC Channel 3 registers TWR0..15 + */ +/* STR3 status register */ +#define APS_STR3 0x04 + +#define APS_STR3_IBF3B 0x80 /* Input buffer full (host->slave) */ +#define APS_STR3_OBF3B 0x40 /* Output buffer full (slave->host)*/ +#define APS_STR3_MWMF 0x20 /* Master write mode */ +#define APS_STR3_SWMF 0x10 /* Slave write mode */ + + +/* Base address of TWR registers */ +#define APS_TWR_BASE 0x10 +#define APS_TWR_RET 0x1f + +/* TWR registers */ +#define APS_CMD 0x00 +#define APS_ARG1 0x01 +#define APS_ARG2 0x02 +#define APS_ARG3 0x03 +#define APS_RET 0x0f + +/* Sensor values */ +#define APS_STATE 0x01 +#define APS_XACCEL 0x02 +#define APS_YACCEL 0x04 +#define APS_TEMP 0x06 +#define APS_XVAR 0x07 +#define APS_YVAR 0x09 +#define APS_TEMP2 0x0b +#define APS_UNKNOWN 0x0c +#define APS_INPUT 0x0d + +/* write masks for I/O, send command + 0-3 arguments*/ +#define APS_WRITE_0 0x0001 +#define APS_WRITE_1 0x0003 +#define APS_WRITE_2 0x0007 +#define APS_WRITE_3 0x000f + +/* read masks for I/O, read 0-3 values (skip command byte) */ +#define APS_READ_0 0x0000 +#define APS_READ_1 0x0002 +#define APS_READ_2 0x0006 +#define APS_READ_3 0x000e + +#define APS_READ_RET 0x8000 +#define APS_READ_ALL 0xffff + +/* Bit definitions for APS_INPUT value */ #define APS_INPUT_KB (1 << 5) #define APS_INPUT_MS (1 << 6) #define APS_INPUT_LIDOPEN (1 << 7) @@ -101,11 +141,12 @@ int aps_match(struct device *, void *, void *); void aps_attach(struct device *, struct device *, void *); int aps_init(bus_space_tag_t, bus_space_handle_t); -u_int8_t aps_mem_read_1(bus_space_tag_t, bus_space_handle_t, int, u_int8_t); int aps_read_data(struct aps_softc *); -void aps_refresh_sensor_data(struct aps_softc *sc); +void aps_refresh_sensor_data(struct aps_softc *); void aps_refresh(void *); void aps_power(int, void *); +int aps_do_io(bus_space_tag_t, bus_space_handle_t, + unsigned char *, int, int); struct cfattach aps_ca = { sizeof(struct aps_softc), @@ -119,15 +160,87 @@ struct cfdriver aps_cd = { struct timeout aps_timeout; + + +/* properly communicate with the controller, writing a set of memory + * locations and reading back another set */ +int +aps_do_io(bus_space_tag_t iot, bus_space_handle_t ioh, + unsigned char *buf, int wmask, int rmask) +{ + int bp, stat, n; + + DPRINTF(("aps_do_io: CMD: 0x%02x, wmask: 0x%04x, rmask: 0x%04x\n", + buf[0], wmask, rmask)); + + /* write init byte using arbitration */ + for (n = 0; n < 100; n++) { + stat = bus_space_read_1(iot, ioh, APS_STR3); + if (stat & (APS_STR3_OBF3B | APS_STR3_SWMF)) { + bus_space_read_1(iot, ioh, APS_TWR_RET); + continue; + } + bus_space_write_1(iot, ioh, APS_TWR_BASE, buf[0]); + stat = bus_space_read_1(iot, ioh, APS_STR3); + if (stat & (APS_STR3_MWMF)) + break; + delay(1); + } + + if (n == 100) { + DPRINTF(("aps_do_io: Failed to get bus\n")); + return (1); + } + + /* write data bytes, init already sent */ + /* make sure last bye is always written as this will trigger slave */ + wmask |= APS_READ_RET; + buf[APS_RET] = 0x01; + + for (n = 1, bp = 2; n < 16; bp <<= 1, n++) { + if (wmask & bp) { + bus_space_write_1(iot, ioh, APS_TWR_BASE + n, buf[n]); + DPRINTF(("aps_do_io: write %2d 0x%02x\n", n, buf[n])); + } + } + + for (n = 0; n < 100; n++) { + stat = bus_space_read_1(iot, ioh, APS_STR3); + if (stat & (APS_STR3_OBF3B)) + break; + delay(5 * 100); + } + + if (n == 100) { + DPRINTF(("aps_do_io: timeout waiting response\n")); + return (1); + } + /* wait for data available */ + /* make sure to read the final byte to clear status */ + rmask |= APS_READ_RET; + + /* read cmd and data bytes */ + for (n = 0, bp = 1; n < 16; bp <<= 1, n++) { + if (rmask & bp) { + buf[n] = bus_space_read_1(iot, ioh, APS_TWR_BASE + n); + DPRINTF(("aps_do_io: read %2d 0x%02x\n", n, buf[n])); + } + } + + return (0); +} + int aps_match(struct device *parent, void *match, void *aux) { bus_space_tag_t iot; bus_space_handle_t ioh; struct isa_attach_args *ia = aux; - int iobase, i; + int iobase; u_int8_t cr; + char iobuf[16]; + iot = ia->ia_iot; iobase = ia->ipa_io[0].base; @@ -136,16 +249,12 @@ aps_match(struct device *parent, void *match, void *aux) return (0); } - /* See if this machine has APS */ - bus_space_write_1(iot, ioh, APS_INIT, 0x13); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - /* ask again as the X40 is slightly deaf in one ear */ - bus_space_read_1(iot, ioh, APS_CMD); - bus_space_write_1(iot, ioh, APS_INIT, 0x13); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); + /* See if this machine has APS */ - if (!aps_mem_read_1(iot, ioh, APS_CMD, 0x00)) { + /* get APS mode */ + iobuf[APS_CMD] = 0x13; + if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1)) { bus_space_unmap(iot, ioh, APS_ADDR_SIZE); return (0); } @@ -155,17 +264,15 @@ aps_match(struct device *parent, void *match, void *aux) * 0x01: T42 * 0x02: chip already initialised * 0x03: T41 + * 0x05: T61 */ - for (i = 0; i < 10; i++) { - cr = bus_space_read_1(iot, ioh, APS_STATE); - if (cr > 0 && cr < 6) - break; - delay(5 * 1000); - } - + + cr = iobuf[APS_ARG1]; + bus_space_unmap(iot, ioh, APS_ADDR_SIZE); DPRINTF(("aps: state register 0x%x\n", cr)); - if (cr < 1 || cr > 5) { + + if (iobuf[APS_RET] != 0 || cr < 1 || cr > 5) { DPRINTF(("aps0: unsupported state %d\n", cr)); return (0); } @@ -200,7 +307,7 @@ aps_attach(struct device *parent, struct device *self, void *aux) printf("\n"); - if (!aps_init(iot, ioh)) + if (aps_init(iot, ioh)) goto out; sc->sensors[APS_SENSOR_XACCEL].type = SENSOR_INTEGER; @@ -249,61 +356,60 @@ aps_attach(struct device *parent, struct device *self, void *aux) timeout_add(&aps_timeout, (5 * hz) / 10); return; out: - printf("%s: failed to initialise\n", sc->sc_dev.dv_xname); + printf("%s: failed to initialize\n", sc->sc_dev.dv_xname); return; } int aps_init(bus_space_tag_t iot, bus_space_handle_t ioh) { - bus_space_write_1(iot, ioh, APS_INIT, 0x17); - bus_space_write_1(iot, ioh, APS_STATE, 0x81); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - if (!aps_mem_read_1(iot, ioh, APS_CMD, 0x00)) - return (0); - if (!aps_mem_read_1(iot, ioh, APS_STATE, 0x00)) - return (0); - if (!aps_mem_read_1(iot, ioh, APS_XACCEL, 0x60)) - return (0); - if (!aps_mem_read_1(iot, ioh, APS_XACCEL + 1, 0x00)) - return (0); - bus_space_write_1(iot, ioh, APS_INIT, 0x14); - bus_space_write_1(iot, ioh, APS_STATE, 0x01); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - if (!aps_mem_read_1(iot, ioh, APS_CMD, 0x00)) - return (0); - bus_space_write_1(iot, ioh, APS_INIT, 0x10); - bus_space_write_1(iot, ioh, APS_STATE, 0xc8); - bus_space_write_1(iot, ioh, APS_XACCEL, 0x00); - bus_space_write_1(iot, ioh, APS_XACCEL + 1, 0x02); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - if (!aps_mem_read_1(iot, ioh, APS_CMD, 0x00)) - return (0); - /* refresh data */ - bus_space_write_1(iot, ioh, APS_INIT, 0x11); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - if (!aps_mem_read_1(iot, ioh, APS_ACCEL_STATE, 0x50)) - return (0); - if (!aps_mem_read_1(iot, ioh, APS_STATE, 0x00)) - return (0); + unsigned char iobuf[16]; - return (1); -} -u_int8_t -aps_mem_read_1(bus_space_tag_t iot, bus_space_handle_t ioh, int reg, - u_int8_t val) -{ - int i; - u_int8_t cr; - /* should take no longer than 50 microseconds */ - for (i = 0; i < 10; i++) { - cr = bus_space_read_1(iot, ioh, reg); - if (cr == val) - return (1); - delay(5 * 1000); - } - DPRINTF(("aps: reg 0x%x not val 0x%x!\n", reg, val)); + /* command 0x17/0x81: check EC */ + iobuf[APS_CMD] = 0x17; + iobuf[APS_ARG1] = 0x81; + + if (aps_do_io(iot, ioh, iobuf, APS_WRITE_1, APS_READ_3)) + return (1); + + if (iobuf[APS_RET] != 0 ||iobuf[APS_ARG3] != 0) + return (1); + + /* Test values from the Linux driver */ + if ((iobuf[APS_ARG1] != 0 || iobuf[APS_ARG2] != 0x60) && + (iobuf[APS_ARG1] != 1 || iobuf[APS_ARG2] != 0)) + return (1); + + /* command 0x14: set power */ + iobuf[APS_CMD] = 0x14; + iobuf[APS_ARG1] = 0x01; + + if (aps_do_io(iot, ioh, iobuf, APS_WRITE_1, APS_READ_0)) + return (1); + + if (iobuf[APS_RET] != 0) + return (1); + + /* command 0x10: set config (sample rate and order) */ + iobuf[APS_CMD] = 0x10; + iobuf[APS_ARG1] = 0xc8; + iobuf[APS_ARG2] = 0x00; + iobuf[APS_ARG3] = 0x02; + + if (aps_do_io(iot, ioh, iobuf, APS_WRITE_3, APS_READ_0)) + return (1); + + if (iobuf[APS_RET] != 0) + return (1); + + /* command 0x11: refresh data */ + iobuf[APS_CMD] = 0x11; + if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1)) + return (1); + if (iobuf[APS_ARG1] != 0) + return (1); + return (0); } @@ -312,39 +418,33 @@ aps_read_data(struct aps_softc *sc) { bus_space_tag_t iot = sc->aps_iot; bus_space_handle_t ioh = sc->aps_ioh; + unsigned char iobuf[16]; + + /* command 0x11: refresh data */ + iobuf[APS_CMD] = 0x11; + if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_ALL)) + return (1); + + sc->aps_data.state = iobuf[APS_STATE]; + sc->aps_data.x_accel = iobuf[APS_XACCEL] + 256 * iobuf[APS_XACCEL + 1]; + sc->aps_data.y_accel = iobuf[APS_YACCEL] + 256 * iobuf[APS_YACCEL + 1]; + sc->aps_data.temp1 = iobuf[APS_TEMP]; + sc->aps_data.x_var = iobuf[APS_XVAR] + 256 * iobuf[APS_XVAR + 1]; + sc->aps_data.y_var = iobuf[APS_YVAR] + 256 * iobuf[APS_YVAR + 1]; + sc->aps_data.temp2 = iobuf[APS_TEMP2]; + sc->aps_data.input = iobuf[APS_INPUT]; - sc->aps_data.state = bus_space_read_1(iot, ioh, APS_STATE); - sc->aps_data.x_accel = bus_space_read_2(iot, ioh, APS_XACCEL); - sc->aps_data.y_accel = bus_space_read_2(iot, ioh, APS_YACCEL); - sc->aps_data.temp1 = bus_space_read_1(iot, ioh, APS_TEMP); - sc->aps_data.x_var = bus_space_read_2(iot, ioh, APS_XVAR); - sc->aps_data.y_var = bus_space_read_2(iot, ioh, APS_YVAR); - sc->aps_data.temp2 = bus_space_read_1(iot, ioh, APS_TEMP2); - sc->aps_data.input = bus_space_read_1(iot, ioh, APS_INPUT); - - return (1); + return (0); } void aps_refresh_sensor_data(struct aps_softc *sc) { - bus_space_tag_t iot = sc->aps_iot; - bus_space_handle_t ioh = sc->aps_ioh; int64_t temp; int i; - /* ask for new data */ - bus_space_write_1(iot, ioh, APS_INIT, 0x11); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - if (!aps_mem_read_1(iot, ioh, APS_ACCEL_STATE, 0x50)) + if (aps_read_data(sc)) return; - aps_read_data(sc); - bus_space_write_1(iot, ioh, APS_INIT, 0x11); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - - /* tell accelerometer we're done reading from it */ - bus_space_read_1(iot, ioh, APS_CMD); - bus_space_read_1(iot, ioh, APS_ACCEL_STATE); for (i = 0; i < APS_NUM_SENSORS; i++) { sc->sensors[i].flags &= ~SENSOR_FINVALID; @@ -390,25 +490,22 @@ aps_power(int why, void *arg) struct aps_softc *sc = (struct aps_softc *)arg; bus_space_tag_t iot = sc->aps_iot; bus_space_handle_t ioh = sc->aps_ioh; + unsigned char iobuf[16]; if (why != PWR_RESUME) { timeout_del(&aps_timeout); - } else { - /* - * Redo the init sequence on resume, because APS is - * as forgetful as it is deaf. - */ - bus_space_write_1(iot, ioh, APS_INIT, 0x13); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - bus_space_read_1(iot, ioh, APS_CMD); - bus_space_write_1(iot, ioh, APS_INIT, 0x13); - bus_space_write_1(iot, ioh, APS_CMD, 0x01); - - if (aps_mem_read_1(iot, ioh, APS_CMD, 0x00) && - aps_init(iot, ioh)) - timeout_add(&aps_timeout, (5 * hz) / 10); - else - printf("aps: failed to wake up\n"); + return; } -} + /* + * Redo the init sequence on resume, because APS is + * as forgetful as it is deaf. + */ + /* get APS mode */ + iobuf[APS_CMD] = 0x13; + if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1) + || aps_init(iot, ioh)) + printf("aps: failed to wake up\n"); + else + timeout_add(&aps_timeout, (5 * hz) / 10); +} -- cgit v1.2.3