#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <machine/intr.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>
#ifdef ACPIWMI_DEBUG
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif
struct acpiwmi_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
struct acpi_softc *sc_acpi;
struct aml_node *sc_node;
SLIST_HEAD(, wmihandler) sc_handlers;
};
struct wmihandler {
SLIST_ENTRY(wmihandler) w_next;
struct acpiwmi_softc *w_sc;
int (*w_event)(struct wmihandler *, int);
char w_method[5];
};
int acpiwmi_match(struct device *, void *, void *);
void acpiwmi_attach(struct device *, struct device *, void *);
int acpiwmi_notify(struct aml_node *, int, void *);
const struct cfattach acpiwmi_ca = {
sizeof (struct acpiwmi_softc), acpiwmi_match, acpiwmi_attach,
NULL, NULL
};
struct cfdriver acpiwmi_cd = {
NULL, "acpiwmi", DV_DULL
};
const char *acpiwmi_hids[] = {
"PNP0C14",
NULL
};
int
acpiwmi_match(struct device *parent, void *match, void *aux)
{
struct acpi_attach_args *aaa = aux;
struct cfdata *cf = match;
return acpi_matchhids(aaa, acpiwmi_hids, cf->cf_driver->cd_name);
}
#define GUID_SIZE 16
struct guidinfo {
uint8_t guid[GUID_SIZE];
uint8_t oid[2];
uint8_t maxinstance;
uint8_t flags;
};
#define WMI_EVENT_MASK 0xffff
#define WMI_EXPENSIVE 1
#define WMI_METHOD 2
#define WMI_STRING 4
#define WMI_EVENT 8
#ifdef ACPIWMI_DEBUG
static char *
guid_string(const uint8_t *guid, char *buf)
{
size_t space = 64;
char *p = buf;
int i = 0;
for (i = 0; i < 4; i++) {
snprintf(p, space, "%02X", guid[0 + 3 - i]);
p += 2;
}
*p++ = '-';
for (i = 0; i < 2; i++) {
snprintf(p, space, "%02X", guid[4 + 1 - i]);
p += 2;
}
*p++ = '-';
for (i = 0; i < 2; i++) {
snprintf(p, space, "%02X", guid[6 + 1 - i]);
p += 2;
}
*p++ = '-';
for (i = 0; i < 2; i++) {
snprintf(p, space, "%02X", guid[8 + i]);
p += 2;
}
*p++ = '-';
for (i = 0; i < 6; i++) {
snprintf(p, space, "%02X", guid[10 + i]);
p += 2;
}
return buf;
}
#endif
static int wmi_asus_init(struct acpiwmi_softc *, struct guidinfo *);
static int wmi_asus_event(struct wmihandler *, int);
struct wmi_target {
uint8_t guid[GUID_SIZE];
int (*init)(struct acpiwmi_softc *, struct guidinfo *);
};
static struct wmi_target targets[] = {
{
{ 0xd0, 0x5e, 0x84, 0x97, 0x6d, 0x4e, 0xde, 0x11,
0x8a, 0x39, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 },
wmi_asus_init,
},
};
void
acpiwmi_attach(struct device *parent, struct device *self, void *aux)
{
struct acpiwmi_softc *sc = (struct acpiwmi_softc *)self;
struct acpi_attach_args *aaa = aux;
struct aml_value res;
sc->sc_acpi = (struct acpi_softc *)parent;
sc->sc_node = aaa->aaa_node;
SLIST_INIT(&sc->sc_handlers);
printf(": %s", aaa->aaa_node->name);
if (aml_evalname(sc->sc_acpi, sc->sc_node, "_WDG", 0, NULL, &res) == -1) {
printf(": error\n");
return;
}
printf("\n");
int num = res.length / sizeof(struct guidinfo);
struct guidinfo *guids = (struct guidinfo *)res.v_buffer;
for (int i = 0; i < num; i++) {
#ifdef ACPIWMI_DEBUG
char buf[64];
DPRINTF(("method: %s\n", guid_string(guids[i].guid, buf)));
#endif
for (int j = 0; j < 1; j++) {
if (memcmp(guids[i].guid, targets[j].guid, GUID_SIZE) == 0)
targets[j].init(sc, &guids[i]);
}
}
aml_freevalue(&res);
if (!SLIST_EMPTY(&sc->sc_handlers)) {
aml_register_notify(sc->sc_node, aaa->aaa_dev,
acpiwmi_notify, sc, ACPIDEV_NOPOLL);
}
}
int
acpiwmi_notify(struct aml_node *node, int note, void *arg)
{
struct aml_value input, res;
struct acpiwmi_softc *sc = arg;
struct wmihandler *wh;
int code;
memset(&input, 0, sizeof(input));
input.type = AML_OBJTYPE_INTEGER;
input.v_integer = note;
if (aml_evalname(sc->sc_acpi, sc->sc_node, "_WED", 1, &input, &res) == -1) {
printf("%s: failed to evaluate _WED\n", DEVNAME(sc));
return 0;
}
if (res.type != AML_OBJTYPE_INTEGER) {
aml_freevalue(&res);
return 0;
}
code = res.v_integer & WMI_EVENT_MASK;
aml_freevalue(&res);
SLIST_FOREACH(wh, &sc->sc_handlers, w_next)
wh->w_event(wh, code);
return 0;
}
static int
wmi_eval_method(struct wmihandler *wh, int32_t instance, uint32_t methodid,
int arg0, int arg1)
{
int args[6] = { arg0, arg1, 0 };
struct aml_value params[3], res;
struct acpiwmi_softc *sc = wh->w_sc;
int rv;
memset(¶ms, 0, sizeof(params));
params[0].type = AML_OBJTYPE_INTEGER;
params[0].v_integer = instance;
params[1].type = AML_OBJTYPE_INTEGER;
params[1].v_integer = methodid;
params[2].type = AML_OBJTYPE_BUFFER;
params[2].length = sizeof(args);
params[2].v_buffer = (void *)args;
if (aml_evalname(sc->sc_acpi, sc->sc_node, wh->w_method, 3, params, &res) == -1)
return -1;
rv = res.v_integer;
aml_freevalue(&res);
return rv;
}
struct wmiasus {
struct wmihandler w_wmi;
int w_kbdlight;
int w_fnlock;
int w_perf;
int w_perfid;
};
#define ASUS_DSTS_PRESENCE 0x00010000
#define ASUS_METHOD_INIT 0x54494E49
#define ASUS_METHOD_DGET 0x53545344
#define ASUS_METHOD_DSET 0x53564544
#define ASUS_DEV_BATTERY 0x00120057
#define ASUS_DEV_ALS 0x00050001
#define ASUS_DEV_PERF 0x00120075
#define ASUS_DEV_PERF_2 0x00110019
#define ASUS_DEV_KBDLIGHT 0x00050021
#define ASUS_DEV_SCRLIGHT 0x00050011
#define ASUS_DEV_FNLOCK 0x00100023
#define ASUS_DEV_CAMLED 0x00060079
#define ASUS_DEV_MICLED 0x00040017
#define ASUS_FNLOCK_BIOS_DISABLED 0x1
#define ASUS_EVENT_AC_OFF 0x57
#define ASUS_EVENT_AC_ON 0x58
#define ASUS_EVENT_KBDLIGHT_UP 0xc4
#define ASUS_EVENT_KBDLIGHT_DOWN 0xc5
#define ASUS_EVENT_KBDLIGHT_TOGGLE 0xc7
#define ASUS_EVENT_FNLOCK 0x4e
#define ASUS_EVENT_PERF 0x9d
#define ASUS_EVENT_MIC 124
#define ASUS_EVENT_CAM 133
static int
asus_dev_get(struct wmiasus *wh, uint32_t devid)
{
return wmi_eval_method(&wh->w_wmi, 0, ASUS_METHOD_DGET, devid, 0);
}
static int
asus_dev_set(struct wmiasus *wh, uint32_t devid, uint32_t val)
{
return wmi_eval_method(&wh->w_wmi, 0, ASUS_METHOD_DSET, devid, val);
}
static void
asus_toggle(struct wmiasus *wh, int devid, int *val)
{
int maxval = 1, mask = 0;
switch (devid) {
case ASUS_DEV_KBDLIGHT:
maxval = 3;
mask = 0x80;
break;
case ASUS_DEV_PERF:
case ASUS_DEV_PERF_2:
maxval = 2;
break;
}
*val += 1;
if (*val > maxval)
*val = 0;
asus_dev_set(wh, devid, *val | mask);
}
static int
wmi_asus_init(struct acpiwmi_softc *sc, struct guidinfo *ginfo)
{
struct wmiasus *wh;
struct aml_value on;
char method[5];
int res;
wh = malloc(sizeof(*wh), M_DEVBUF, M_NOWAIT | M_ZERO);
if (!wh)
return -1;
wh->w_wmi.w_sc = sc;
snprintf(wh->w_wmi.w_method, sizeof(wh->w_wmi.w_method), "WM%c%c",
ginfo->oid[0], ginfo->oid[1]);
if (wmi_eval_method(&wh->w_wmi, 0, ASUS_METHOD_INIT, 0, 0) != 1)
return -1;
wh->w_wmi.w_event = wmi_asus_event;
SLIST_INSERT_HEAD(&sc->sc_handlers, &wh->w_wmi, w_next);
memset(&on, 0, sizeof(on));
on.type = AML_OBJTYPE_INTEGER;
on.v_integer = 1;
snprintf(method, sizeof(method), "WC%c%c", ginfo->oid[0], ginfo->oid[1]);
aml_evalname(sc->sc_acpi, sc->sc_node, method, 1, &on, NULL);
res = asus_dev_get(wh, ASUS_DEV_PERF);
if (res >= 0)
wh->w_perfid = ASUS_DEV_PERF;
res = asus_dev_get(wh, ASUS_DEV_PERF_2);
if (res >= 0)
wh->w_perfid = ASUS_DEV_PERF_2;
asus_toggle(wh, ASUS_DEV_KBDLIGHT, &wh->w_kbdlight);
res = asus_dev_get(wh, ASUS_DEV_FNLOCK);
if ((res & ASUS_DSTS_PRESENCE) &&
!(res & ASUS_FNLOCK_BIOS_DISABLED)) {
asus_toggle(wh, ASUS_DEV_FNLOCK, &wh->w_fnlock);
}
return 0;
}
int
wmi_asus_event(struct wmihandler *wmi, int code)
{
struct wmiasus *wh = (struct wmiasus *)wmi;
switch (code) {
case ASUS_EVENT_AC_OFF:
case ASUS_EVENT_AC_ON:
break;
case ASUS_EVENT_KBDLIGHT_UP:
case ASUS_EVENT_KBDLIGHT_DOWN:
case ASUS_EVENT_KBDLIGHT_TOGGLE:
asus_toggle(wh, ASUS_DEV_KBDLIGHT, &wh->w_kbdlight);
break;
case ASUS_EVENT_FNLOCK:
asus_toggle(wh, ASUS_DEV_FNLOCK, &wh->w_fnlock);
break;
case ASUS_EVENT_PERF:
asus_toggle(wh, wh->w_perfid, &wh->w_perf);
DPRINTF(("toggle perf %d\n", wh->w_perf));
break;
default:
DPRINTF(("asus button %d\n", code));
break;
}
return 0;
}