summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCan Erkin Acar <canacar@cvs.openbsd.org>2008-06-27 06:08:44 +0000
committerCan Erkin Acar <canacar@cvs.openbsd.org>2008-06-27 06:08:44 +0000
commit3b91a06c33092d681c3bfb8c0bf4c91bc72bdd80 (patch)
treea2a595f85a71a5017fec9275abe3228480190312
parent39edd764cbe0f389e80c11e104079e123f8a641e (diff)
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.
-rw-r--r--sys/dev/isa/aps.c343
1 files changed, 220 insertions, 123 deletions
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 <jsg@openbsd.org>
+ * Copyright (c) 2008 Can Erkin Acar <canacar@openbsd.org>
*
* 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);
+}