#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/sensors.h>
#include <bus/isa/isavar.h>
#if defined(APSDEBUG)
#define DPRINTF(x) do { kprintf x; } while (0)
#else
#define DPRINTF(x)
#endif
#define APS_STR3 0x04
#define APS_STR3_IBF3B 0x80
#define APS_STR3_OBF3B 0x40
#define APS_STR3_MWMF 0x20
#define APS_STR3_SWMF 0x10
#define APS_TWR_BASE 0x10
#define APS_TWR_RET 0x1f
#define APS_CMD 0x00
#define APS_ARG1 0x01
#define APS_ARG2 0x02
#define APS_ARG3 0x03
#define APS_RET 0x0f
#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
#define APS_WRITE_0 0x0001
#define APS_WRITE_1 0x0003
#define APS_WRITE_2 0x0007
#define APS_WRITE_3 0x000f
#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
#define APS_INPUT_KB (1 << 5)
#define APS_INPUT_MS (1 << 6)
#define APS_INPUT_LIDOPEN (1 << 7)
#define APS_ADDR_BASE 0x1600
#define APS_ADDR_SIZE 0x1f
struct aps_sensor_rec {
u_int8_t state;
u_int16_t x_accel;
u_int16_t y_accel;
u_int8_t temp1;
u_int16_t x_var;
u_int16_t y_var;
u_int8_t temp2;
u_int8_t unk;
u_int8_t input;
};
#define APS_NUM_SENSORS 9
#define APS_SENSOR_XACCEL 0
#define APS_SENSOR_YACCEL 1
#define APS_SENSOR_XVAR 2
#define APS_SENSOR_YVAR 3
#define APS_SENSOR_TEMP1 4
#define APS_SENSOR_TEMP2 5
#define APS_SENSOR_KBACT 6
#define APS_SENSOR_MSACT 7
#define APS_SENSOR_LIDOPEN 8
struct aps_softc {
device_t sc_dev;
struct resource *sc_iores;
int sc_iorid;
struct ksensor sensors[APS_NUM_SENSORS];
struct ksensordev sensordev;
struct aps_sensor_rec aps_data;
};
static void aps_identify(driver_t *, device_t);
static int aps_probe(device_t);
static int aps_attach(device_t);
static int aps_detach(device_t);
static int aps_resume(device_t);
static int aps_suspend(device_t);
static int aps_init(struct resource *);
static int aps_read_data(struct aps_softc *);
static void aps_refresh_sensor_data(struct aps_softc *);
static void aps_refresh(void *);
static int aps_do_io(struct resource *, unsigned char *, int, int);
static device_method_t aps_methods[] = {
DEVMETHOD(device_identify, aps_identify),
DEVMETHOD(device_probe, aps_probe),
DEVMETHOD(device_attach, aps_attach),
DEVMETHOD(device_detach, aps_detach),
DEVMETHOD(device_resume, aps_resume),
DEVMETHOD(device_suspend, aps_suspend),
{ NULL, NULL }
};
static driver_t aps_driver = {
"aps",
aps_methods,
sizeof(struct aps_softc)
};
static devclass_t aps_devclass;
DRIVER_MODULE(aps, isa, aps_driver, aps_devclass, NULL, NULL);
static int
aps_do_io(struct resource *iores, unsigned char *buf, int wmask, int rmask)
{
bus_space_tag_t iot = rman_get_bustag(iores);
bus_space_handle_t ioh = rman_get_bushandle(iores);
int bp, stat, n;
DPRINTF(("aps_do_io: CMD: 0x%02x, wmask: 0x%04x, rmask: 0x%04x\n",
buf[0], wmask, rmask));
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;
DRIVERSLEEP(1);
}
if (n == 100) {
DPRINTF(("aps_do_io: Failed to get bus\n"));
return (1);
}
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;
DRIVERSLEEP(500);
}
if (n == 100) {
DPRINTF(("aps_do_io: timeout waiting response\n"));
return (1);
}
rmask |= APS_READ_RET;
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);
}
static void
aps_identify(driver_t *driver, device_t parent)
{
device_t child;
child = device_find_child(parent, driver->name, -1);
if (child != NULL) {
if (isa_get_portsize(child) == 0) {
if (bootverbose)
kprintf("%s: will specify the port\n",
__func__);
} else if (isa_get_port(child) != APS_ADDR_BASE)
kprintf("%s: will overwrite specified port\n",
__func__);
else {
if (isa_get_portsize(child) == APS_ADDR_SIZE) {
kprintf("%s: already have been invoked\n",
__func__);
return;
} else
kprintf("%s: will amend the portsize\n",
__func__);
}
} else {
kprintf("%s: creating a new %s\n",
__func__, driver->name);
child = BUS_ADD_CHILD(parent, parent, ISA_ORDER_PNP,
driver->name, -1);
if (child == NULL) {
kprintf("%s: cannot add child\n", __func__);
return;
}
}
if (bus_set_resource(child, SYS_RES_IOPORT, 0,
APS_ADDR_BASE, APS_ADDR_SIZE, -1))
kprintf("%s: cannot set resource\n", __func__);
}
static int
aps_probe(device_t dev)
{
struct resource *iores;
int iorid = 0;
u_int8_t cr;
unsigned char iobuf[16];
#if defined(APSDEBUG) || defined(KLD_MODULE)
device_printf(dev, "%s: port 0x%x\n", __func__, isa_get_port(dev));
#endif
if (device_get_unit(dev) != 0)
return ENXIO;
iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &iorid, RF_ACTIVE);
if (iores == NULL) {
DPRINTF(("aps: can't map i/o space\n"));
return ENXIO;
}
iobuf[APS_CMD] = 0x13;
if (aps_do_io(iores, iobuf, APS_WRITE_0, APS_READ_1)) {
bus_release_resource(dev, SYS_RES_IOPORT, iorid, iores);
return ENXIO;
}
cr = iobuf[APS_ARG1];
bus_release_resource(dev, SYS_RES_IOPORT, iorid, iores);
DPRINTF(("aps: state register 0x%x\n", cr));
if (iobuf[APS_RET] != 0 || cr < 1 || cr > 5) {
DPRINTF(("aps: unsupported state %d\n", cr));
return ENXIO;
}
device_set_desc(dev, "ThinkPad Active Protection System");
return 0;
}
static int
aps_attach(device_t dev)
{
struct aps_softc *sc = device_get_softc(dev);
sc->sc_dev = dev;
sc->sc_iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
&sc->sc_iorid, RF_ACTIVE);
if (sc->sc_iores == NULL) {
device_printf(dev, "can't map i/o space\n");
return ENXIO;
}
if (aps_init(sc->sc_iores)) {
device_printf(dev, "failed to initialise\n");
bus_release_resource(dev, SYS_RES_IOPORT, sc->sc_iorid, sc->sc_iores);
return ENXIO;
}
sc->sensors[APS_SENSOR_XACCEL].type = SENSOR_INTEGER;
ksnprintf(sc->sensors[APS_SENSOR_XACCEL].desc,
sizeof(sc->sensors[APS_SENSOR_XACCEL].desc), "X_ACCEL");
sc->sensors[APS_SENSOR_YACCEL].type = SENSOR_INTEGER;
ksnprintf(sc->sensors[APS_SENSOR_YACCEL].desc,
sizeof(sc->sensors[APS_SENSOR_YACCEL].desc), "Y_ACCEL");
sc->sensors[APS_SENSOR_TEMP1].type = SENSOR_TEMP;
sc->sensors[APS_SENSOR_TEMP2].type = SENSOR_TEMP;
sc->sensors[APS_SENSOR_XVAR].type = SENSOR_INTEGER;
ksnprintf(sc->sensors[APS_SENSOR_XVAR].desc,
sizeof(sc->sensors[APS_SENSOR_XVAR].desc), "X_VAR");
sc->sensors[APS_SENSOR_YVAR].type = SENSOR_INTEGER;
ksnprintf(sc->sensors[APS_SENSOR_YVAR].desc,
sizeof(sc->sensors[APS_SENSOR_YVAR].desc), "Y_VAR");
sc->sensors[APS_SENSOR_KBACT].type = SENSOR_INDICATOR;
ksnprintf(sc->sensors[APS_SENSOR_KBACT].desc,
sizeof(sc->sensors[APS_SENSOR_KBACT].desc), "Keyboard Active");
sc->sensors[APS_SENSOR_MSACT].type = SENSOR_INDICATOR;
ksnprintf(sc->sensors[APS_SENSOR_MSACT].desc,
sizeof(sc->sensors[APS_SENSOR_MSACT].desc), "Mouse Active");
sc->sensors[APS_SENSOR_LIDOPEN].type = SENSOR_INDICATOR;
ksnprintf(sc->sensors[APS_SENSOR_LIDOPEN].desc,
sizeof(sc->sensors[APS_SENSOR_LIDOPEN].desc), "Lid Open");
strlcpy(sc->sensordev.xname, device_get_nameunit(dev),
sizeof(sc->sensordev.xname));
for (int i = 0; i < APS_NUM_SENSORS ; i++)
sensor_attach(&sc->sensordev, &sc->sensors[i]);
sensor_task_register(sc, aps_refresh, 1);
sensordev_install(&sc->sensordev);
return 0;
}
static int
aps_detach(device_t dev)
{
struct aps_softc *sc = device_get_softc(dev);
sensordev_deinstall(&sc->sensordev);
sensor_task_unregister(sc);
return bus_release_resource(dev, SYS_RES_IOPORT,
sc->sc_iorid, sc->sc_iores);
}
static int
aps_init(struct resource *iores)
{
unsigned char iobuf[16];
iobuf[APS_CMD] = 0x17;
iobuf[APS_ARG1] = 0x81;
if (aps_do_io(iores, iobuf, APS_WRITE_1, APS_READ_3))
return (1);
if (iobuf[APS_RET] != 0 ||iobuf[APS_ARG3] != 0)
return (1);
if ((iobuf[APS_ARG1] != 0 || iobuf[APS_ARG2] != 0x60) &&
(iobuf[APS_ARG1] != 1 || iobuf[APS_ARG2] != 0))
return (1);
iobuf[APS_CMD] = 0x14;
iobuf[APS_ARG1] = 0x01;
if (aps_do_io(iores, iobuf, APS_WRITE_1, APS_READ_0))
return (1);
if (iobuf[APS_RET] != 0)
return (1);
iobuf[APS_CMD] = 0x10;
iobuf[APS_ARG1] = 0xc8;
iobuf[APS_ARG2] = 0x00;
iobuf[APS_ARG3] = 0x02;
if (aps_do_io(iores, iobuf, APS_WRITE_3, APS_READ_0))
return (1);
if (iobuf[APS_RET] != 0)
return (1);
iobuf[APS_CMD] = 0x11;
if (aps_do_io(iores, iobuf, APS_WRITE_0, APS_READ_1))
return (1);
if (iobuf[APS_ARG1] != 0)
return (1);
return (0);
}
static int
aps_read_data(struct aps_softc *sc)
{
unsigned char iobuf[16];
iobuf[APS_CMD] = 0x11;
if (aps_do_io(sc->sc_iores, 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];
return (0);
}
static void
aps_refresh_sensor_data(struct aps_softc *sc)
{
int64_t temp;
if (aps_read_data(sc)) {
for (int i = 0; i < APS_NUM_SENSORS; i++)
sc->sensors[i].flags |= SENSOR_FINVALID;
return;
}
sc->sensors[APS_SENSOR_XACCEL].value = sc->aps_data.x_accel;
sc->sensors[APS_SENSOR_YACCEL].value = sc->aps_data.y_accel;
temp = sc->aps_data.temp1 * 1000000;
temp += 273150000;
sc->sensors[APS_SENSOR_TEMP1].value = temp;
temp = sc->aps_data.temp2 * 1000000;
temp += 273150000;
sc->sensors[APS_SENSOR_TEMP2].value = temp;
sc->sensors[APS_SENSOR_XVAR].value = sc->aps_data.x_var;
sc->sensors[APS_SENSOR_YVAR].value = sc->aps_data.y_var;
sc->sensors[APS_SENSOR_KBACT].value =
(sc->aps_data.input & APS_INPUT_KB) ? 1 : 0;
sc->sensors[APS_SENSOR_MSACT].value =
(sc->aps_data.input & APS_INPUT_MS) ? 1 : 0;
sc->sensors[APS_SENSOR_LIDOPEN].value =
(sc->aps_data.input & APS_INPUT_LIDOPEN) ? 1 : 0;
for (int i = 0; i < APS_NUM_SENSORS; i++)
sc->sensors[i].flags &= ~SENSOR_FINVALID;
}
static void
aps_refresh(void *arg)
{
struct aps_softc *sc = (struct aps_softc *)arg;
aps_refresh_sensor_data(sc);
}
static int
aps_resume(device_t dev)
{
struct aps_softc *sc = device_get_softc(dev);
unsigned char iobuf[16];
iobuf[APS_CMD] = 0x13;
if (aps_do_io(sc->sc_iores, iobuf, APS_WRITE_0, APS_READ_1)
|| aps_init(sc->sc_iores)) {
device_printf(sc->sc_dev, "failed to wake up\n");
return EIO;
}
sensor_task_register(sc, aps_refresh, 1);
return 0;
}
static int
aps_suspend(device_t dev)
{
struct aps_softc *sc = device_get_softc(dev);
for (int i = 0; i < APS_NUM_SENSORS; i++)
sc->sensors[i].flags |= SENSOR_FINVALID;
sensor_task_unregister(sc);
return 0;
}