OpenBSD-4.6/sys/dev/acpi/acpithinkpad.c
/* $OpenBSD: acpithinkpad.c,v 1.20 2009/04/26 02:59:05 cnst Exp $ */
/*
* Copyright (c) 2008 joshua stein <jcs@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
* 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 <sys/param.h>
#include <sys/systm.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>
#define THINKPAD_HKEY_VERSION 0x0100
#define THINKPAD_CMOS_VOLUME_DOWN 0x00
#define THINKPAD_CMOS_VOLUME_UP 0x01
#define THINKPAD_CMOS_VOLUME_MUTE 0x02
#define THINKPAD_CMOS_BRIGHTNESS_UP 0x04
#define THINKPAD_CMOS_BRIGHTNESS_DOWN 0x05
#define THINKPAD_BLUETOOTH_PRESENT 0x01
#define THINKPAD_BLUETOOTH_ENABLED 0x02
/* wan (not wifi) card */
#define THINKPAD_WAN_PRESENT 0x01
#define THINKPAD_WAN_ENABLED 0x02
/* type 1 events */
#define THINKPAD_BUTTON_FN_F1 0x001
#define THINKPAD_BUTTON_LOCK_SCREEN 0x002
#define THINKPAD_BUTTON_BATTERY_INFO 0x003
#define THINKPAD_BUTTON_SUSPEND 0x004
#define THINKPAD_BUTTON_WIRELESS 0x005
#define THINKPAD_BUTTON_FN_F6 0x006
#define THINKPAD_BUTTON_EXTERNAL_SCREEN 0x007
#define THINKPAD_BUTTON_POINTER_SWITCH 0x008
#define THINKPAD_BUTTON_EJECT 0x009
#define THINKPAD_BUTTON_BRIGHTNESS_UP 0x010
#define THINKPAD_BUTTON_BRIGHTNESS_DOWN 0x011
#define THINKPAD_BUTTON_THINKLIGHT 0x012
#define THINKPAD_BUTTON_FN_SPACE 0x014
#define THINKPAD_BUTTON_VOLUME_UP 0x015
#define THINKPAD_BUTTON_VOLUME_DOWN 0x016
#define THINKPAD_BUTTON_VOLUME_MUTE 0x017
#define THINKPAD_BUTTON_THINKVANTAGE 0x018
#define THINKPAD_BUTTON_FN_F11 0x00b
#define THINKPAD_BUTTON_HIBERNATE 0x00c
/* type 5 events */
#define THINKPAD_LID_OPEN 0x001
#define THINKPAD_LID_CLOSED 0x002
#define THINKPAD_TABLET_SCREEN_NORMAL 0x00a
#define THINKPAD_TABLET_SCREEN_ROTATED 0x009
#define THINKPAD_BRIGHTNESS_CHANGED 0x010
#define THINKPAD_TABLET_PEN_INSERTED 0x00b
#define THINKPAD_TABLET_PEN_REMOVED 0x00c
/* type 6 events */
#define THINKPAD_POWER_CHANGED 0x030
/* type 7 events */
#define THINKPAD_SWITCH_WIRELESS 0x000
#define THINKPAD_NSENSORS 9
#define THINKPAD_NTEMPSENSORS 8
#define THINKPAD_ECOFFSET_FANLO 0x84
#define THINKPAD_ECOFFSET_FANHI 0x85
struct acpithinkpad_softc {
struct device sc_dev;
struct acpiec_softc *sc_ec;
struct acpi_softc *sc_acpi;
struct aml_node *sc_devnode;
struct ksensor sc_sens[THINKPAD_NSENSORS];
struct ksensordev sc_sensdev;
};
extern void acpiec_read(struct acpiec_softc *, u_int8_t, int, u_int8_t *);
int thinkpad_match(struct device *, void *, void *);
void thinkpad_attach(struct device *, struct device *, void *);
int thinkpad_hotkey(struct aml_node *, int, void *);
int thinkpad_enable_events(struct acpithinkpad_softc *);
int thinkpad_toggle_bluetooth(struct acpithinkpad_softc *);
int thinkpad_toggle_wan(struct acpithinkpad_softc *);
int thinkpad_cmos(struct acpithinkpad_softc *sc, uint8_t);
int thinkpad_volume_down(struct acpithinkpad_softc *);
int thinkpad_volume_up(struct acpithinkpad_softc *);
int thinkpad_volume_mute(struct acpithinkpad_softc *);
int thinkpad_brightness_up(struct acpithinkpad_softc *);
int thinkpad_brightness_down(struct acpithinkpad_softc *);
void thinkpad_sensor_attach(struct acpithinkpad_softc *sc);
void thinkpad_sensor_refresh(void *);
struct cfattach acpithinkpad_ca = {
sizeof(struct acpithinkpad_softc), thinkpad_match, thinkpad_attach
};
struct cfdriver acpithinkpad_cd = {
NULL, "acpithinkpad", DV_DULL
};
const char *acpithinkpad_hids[] = { ACPI_DEV_THINKPAD, 0 };
int
thinkpad_match(struct device *parent, void *match, void *aux)
{
struct acpi_attach_args *aa = aux;
struct cfdata *cf = match;
int64_t res;
int rv = 0;
if (!acpi_matchhids(aa, acpithinkpad_hids, cf->cf_driver->cd_name))
return (0);
if (aml_evalinteger((struct acpi_softc *)parent, aa->aaa_node,
"MHKV", 0, NULL, &res))
return (0);
if (res == THINKPAD_HKEY_VERSION)
rv = 1;
return (rv);
}
void
thinkpad_sensor_attach(struct acpithinkpad_softc *sc)
{
int i;
if (sc->sc_acpi->sc_ec == NULL)
return;
sc->sc_ec = sc->sc_acpi->sc_ec;
/* Add temperature probes */
strlcpy(sc->sc_sensdev.xname, DEVNAME(sc),
sizeof(sc->sc_sensdev.xname));
for (i=0; i<THINKPAD_NTEMPSENSORS; i++) {
sc->sc_sens[i].type = SENSOR_TEMP;
sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]);
}
/* Add fan probe */
sc->sc_sens[i].type = SENSOR_FANRPM;
sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]);
sensordev_install(&sc->sc_sensdev);
}
void
thinkpad_sensor_refresh(void *arg)
{
struct acpithinkpad_softc *sc = arg;
u_int8_t lo, hi, i;
int64_t tmp;
char sname[5];
/* Refresh sensor readings */
for (i=0; i<THINKPAD_NTEMPSENSORS; i++) {
snprintf(sname, sizeof(sname), "TMP%d", i);
aml_evalinteger(sc->sc_acpi, sc->sc_ec->sc_devnode,
sname, 0, 0, &tmp);
sc->sc_sens[i].value = (tmp * 1000000) + 273150000;
if (tmp > 127 || tmp < -127)
sc->sc_sens[i].flags = SENSOR_FINVALID;
}
/* Read fan RPM */
acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANLO, 1, &lo);
acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANHI, 1, &hi);
sc->sc_sens[i].value = ((hi << 8L) + lo);
}
void
thinkpad_attach(struct device *parent, struct device *self, void *aux)
{
struct acpithinkpad_softc *sc = (struct acpithinkpad_softc *)self;
struct acpi_attach_args *aa = aux;
sc->sc_acpi = (struct acpi_softc *)parent;
sc->sc_devnode = aa->aaa_node;
printf("\n");
/* set event mask to receive everything */
thinkpad_enable_events(sc);
thinkpad_sensor_attach(sc);
/* run thinkpad_hotkey on button presses */
aml_register_notify(sc->sc_devnode, aa->aaa_dev,
thinkpad_hotkey, sc, ACPIDEV_POLL);
}
int
thinkpad_enable_events(struct acpithinkpad_softc *sc)
{
struct aml_value arg, args[2];
int64_t mask;
int i, rv = 1;
/* get the supported event mask */
if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKA", 0, NULL, &mask)) {
printf("%s: no MHKA\n", DEVNAME(sc));
goto fail;
}
/* update hotkey mask */
bzero(args, sizeof(args));
args[0].type = args[1].type = AML_OBJTYPE_INTEGER;
for (i = 0; i < 32; i++) {
args[0].v_integer = i + 1;
args[1].v_integer = (((1 << i) & mask) != 0);
if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "MHKM", 2, args,
NULL)) {
printf("%s: couldn't toggle MHKM\n", DEVNAME(sc));
goto fail;
}
}
/* enable hotkeys */
bzero(&arg, sizeof(arg));
arg.type = AML_OBJTYPE_INTEGER;
arg.v_integer = 1;
if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "MHKC", 1, &arg, NULL)) {
printf("%s: couldn't enable hotkeys\n", DEVNAME(sc));
goto fail;
}
rv = 0;
fail:
return (rv);
}
int
thinkpad_hotkey(struct aml_node *node, int notify_type, void *arg)
{
struct acpithinkpad_softc *sc = arg;
int type, event, handled, rv = 1, tot = 0;
int64_t val;
if (notify_type == 0x00) {
/* poll sensors */
thinkpad_sensor_refresh(sc);
return 0;
}
if (notify_type != 0x80)
goto fail;
for (;;) {
if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKP", 0, NULL,
&val))
goto done;
if (val == 0)
goto done;
type = (val & 0xf000) >> 12;
event = val & 0x0fff;
handled = 0;
switch (type) {
case 1:
switch (event) {
case THINKPAD_BUTTON_BRIGHTNESS_UP:
thinkpad_brightness_up(sc);
handled = 1;
break;
case THINKPAD_BUTTON_BRIGHTNESS_DOWN:
thinkpad_brightness_down(sc);
handled = 1;
break;
case THINKPAD_BUTTON_WIRELESS:
thinkpad_toggle_bluetooth(sc);
handled = 1;
break;
case THINKPAD_BUTTON_SUSPEND:
handled = 1;
/*
acpi_enter_sleep_state(sc->sc_acpi,
ACPI_STATE_S3);
*/
break;
case THINKPAD_BUTTON_HIBERNATE:
case THINKPAD_BUTTON_FN_F1:
case THINKPAD_BUTTON_LOCK_SCREEN:
case THINKPAD_BUTTON_BATTERY_INFO:
case THINKPAD_BUTTON_FN_F6:
case THINKPAD_BUTTON_EXTERNAL_SCREEN:
case THINKPAD_BUTTON_POINTER_SWITCH:
case THINKPAD_BUTTON_EJECT:
case THINKPAD_BUTTON_THINKLIGHT:
case THINKPAD_BUTTON_FN_SPACE:
handled = 1;
break;
case THINKPAD_BUTTON_VOLUME_DOWN:
thinkpad_volume_down(sc);
handled = 1;
break;
case THINKPAD_BUTTON_VOLUME_UP:
thinkpad_volume_up(sc);
handled = 1;
break;
case THINKPAD_BUTTON_VOLUME_MUTE:
thinkpad_volume_mute(sc);
handled = 1;
break;
case THINKPAD_BUTTON_THINKVANTAGE:
case THINKPAD_BUTTON_FN_F11:
handled = 1;
break;
}
break;
case 5:
switch (event) {
case THINKPAD_LID_OPEN:
case THINKPAD_LID_CLOSED:
case THINKPAD_TABLET_SCREEN_NORMAL:
case THINKPAD_TABLET_SCREEN_ROTATED:
case THINKPAD_BRIGHTNESS_CHANGED:
case THINKPAD_TABLET_PEN_INSERTED:
case THINKPAD_TABLET_PEN_REMOVED:
handled = 1;
break;
}
break;
case 6:
switch (event) {
case THINKPAD_POWER_CHANGED:
handled = 1;
break;
}
break;
case 7:
switch (event) {
case THINKPAD_SWITCH_WIRELESS:
handled = 1;
break;
}
break;
}
if (handled)
tot++;
else
printf("%s: unknown type %d event 0x%03x\n",
DEVNAME(sc), type, event);
}
done:
if (tot)
rv = 0;
fail:
return (rv);
}
int
thinkpad_toggle_bluetooth(struct acpithinkpad_softc *sc)
{
struct aml_value arg;
int rv = 1;
int64_t bluetooth;
if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "GBDC", 0, NULL, &bluetooth))
goto fail;
if (!(bluetooth & THINKPAD_BLUETOOTH_PRESENT))
goto fail;
bzero(&arg, sizeof(arg));
arg.type = AML_OBJTYPE_INTEGER;
arg.v_integer = bluetooth ^= THINKPAD_BLUETOOTH_ENABLED;
if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "SBDC", 1, &arg, NULL)) {
printf("%s: couldn't toggle bluetooth\n", DEVNAME(sc));
goto fail;
}
rv = 0;
fail:
return (rv);
}
int
thinkpad_toggle_wan(struct acpithinkpad_softc *sc)
{
struct aml_value arg;
int rv = 1;
int64_t wan;
if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "GWAN", 0, NULL, &wan))
goto fail;
if (!(wan & THINKPAD_WAN_PRESENT))
goto fail;
bzero(&arg, sizeof(arg));
arg.type = AML_OBJTYPE_INTEGER;
arg.v_integer = (wan ^= THINKPAD_WAN_ENABLED);
if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "SWAN", 1, &arg, NULL)) {
printf("%s: couldn't toggle wan\n", DEVNAME(sc));
goto fail;
}
rv = 0;
fail:
return (rv);
}
int
thinkpad_cmos(struct acpithinkpad_softc *sc, uint8_t cmd)
{
struct aml_value arg;
bzero(&arg, sizeof(arg));
arg.type = AML_OBJTYPE_INTEGER;
arg.v_integer = cmd;
if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "\\UCMS", 1, &arg,
NULL)) {
printf("%s: cmos command 0x%x failed\n", DEVNAME(sc), cmd);
return (1);
}
return (0);
}
int
thinkpad_volume_down(struct acpithinkpad_softc *sc)
{
return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_DOWN));
}
int
thinkpad_volume_up(struct acpithinkpad_softc *sc)
{
return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_UP));
}
int
thinkpad_volume_mute(struct acpithinkpad_softc *sc)
{
return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_MUTE));
}
int
thinkpad_brightness_up(struct acpithinkpad_softc *sc)
{
return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_UP));
}
int
thinkpad_brightness_down(struct acpithinkpad_softc *sc)
{
return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_DOWN));
}