#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/gpio.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/gpio/gpiovar.h>
#include <dev/ofw/ofw_gpio.h>
#include <dev/ofw/ofw_pinctrl.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/fdt.h>
#include <sys/sensors.h>
#ifdef SUSPEND
extern int cpu_suspended;
#endif
extern int lid_action;
extern void (*simplefb_burn_hook)(u_int);
#define DEVNAME(_s) ((_s)->sc_dev.dv_xname)
enum gpiokeys_event_type {
GPIOKEYS_EV_KEY = 1,
GPIOKEYS_EV_SW = 5,
};
enum gpiokeys_switch_event {
GPIOKEYS_SW_LID = 0,
};
enum gpiokeys_key_event {
GPIOKEYS_KEY_POWER = 116,
};
struct gpiokeys_key {
uint32_t *key_pin;
uint32_t key_input_type;
uint32_t key_code;
int key_state;
struct ksensor key_sensor;
SLIST_ENTRY(gpiokeys_key) key_next;
void (*key_func)(void *);
void *key_ih;
int key_wakeup;
};
struct gpiokeys_softc {
struct device sc_dev;
struct ksensordev sc_sensordev;
SLIST_HEAD(, gpiokeys_key) sc_keys;
};
int gpiokeys_match(struct device *, void *, void *);
void gpiokeys_attach(struct device *, struct device *, void *);
const struct cfattach gpiokeys_ca = {
sizeof (struct gpiokeys_softc), gpiokeys_match, gpiokeys_attach
};
struct cfdriver gpiokeys_cd = {
NULL, "gpiokeys", DV_DULL
};
void gpiokeys_update_key(void *);
void gpiokeys_power_button(void *);
int gpiokeys_intr(void *);
int
gpiokeys_match(struct device *parent, void *match, void *aux)
{
const struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "gpio-keys") ||
OF_is_compatible(faa->fa_node, "gpio-keys-polled");
}
void
gpiokeys_attach(struct device *parent, struct device *self, void *aux)
{
struct gpiokeys_softc *sc = (struct gpiokeys_softc *)self;
struct fdt_attach_args *faa = aux;
struct gpiokeys_key *key;
char *label;
uint32_t code;
int node, len, gpios_len;
int have_labels = 0, have_sensors = 0;
SLIST_INIT(&sc->sc_keys);
pinctrl_byname(faa->fa_node, "default");
for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
code = OF_getpropint(node, "linux,code", -1);
if (code == -1)
continue;
gpios_len = OF_getproplen(node, "gpios");
if (gpios_len <= 0)
continue;
label = NULL;
len = OF_getproplen(node, "label");
if (len > 0) {
label = malloc(len, M_TEMP, M_WAITOK);
if (OF_getprop(node, "label", label, len) != len) {
free(label, M_TEMP, len);
continue;
}
}
key = malloc(sizeof(*key), M_DEVBUF, M_WAITOK | M_ZERO);
key->key_input_type = OF_getpropint(node, "linux,input-type",
GPIOKEYS_EV_KEY);
key->key_code = code;
key->key_pin = malloc(gpios_len, M_DEVBUF, M_WAITOK);
OF_getpropintarray(node, "gpios", key->key_pin, gpios_len);
gpio_controller_config_pin(key->key_pin, GPIO_CONFIG_INPUT);
switch (key->key_input_type) {
case GPIOKEYS_EV_SW:
switch (key->key_code) {
case GPIOKEYS_SW_LID:
strlcpy(key->key_sensor.desc, "lid open",
sizeof(key->key_sensor.desc));
key->key_sensor.type = SENSOR_INDICATOR;
sensor_attach(&sc->sc_sensordev,
&key->key_sensor);
key->key_func = gpiokeys_update_key;
key->key_wakeup = 1;
have_sensors = 1;
break;
}
break;
case GPIOKEYS_EV_KEY:
switch (key->key_code) {
case GPIOKEYS_KEY_POWER:
key->key_func = gpiokeys_power_button;
break;
}
break;
}
if (label) {
printf("%s \"%s\"", have_labels ? "," : ":", label);
free(label, M_TEMP, len);
have_labels = 1;
}
SLIST_INSERT_HEAD(&sc->sc_keys, key, key_next);
}
SLIST_FOREACH(key, &sc->sc_keys, key_next) {
if (!key->key_func)
continue;
if (OF_is_compatible(faa->fa_node, "gpio-keys")) {
int wakeup = key->key_wakeup ? IPL_WAKEUP : 0;
key->key_ih =
gpio_controller_intr_establish(key->key_pin,
IPL_BIO | wakeup, NULL, gpiokeys_intr, key,
DEVNAME(sc));
}
#ifdef SUSPEND
if (key->key_wakeup && key->key_ih)
device_register_wakeup(&sc->sc_dev);
#endif
if (key->key_ih == NULL)
sensor_task_register(key, gpiokeys_update_key, 1);
else
gpiokeys_update_key(key);
}
if (have_sensors) {
strlcpy(sc->sc_sensordev.xname, DEVNAME(sc),
sizeof(sc->sc_sensordev.xname));
sensordev_install(&sc->sc_sensordev);
}
if (SLIST_EMPTY(&sc->sc_keys))
printf(": no keys");
printf("\n");
}
void
gpiokeys_update_key(void *arg)
{
struct gpiokeys_key *key = arg;
int val;
val = gpio_controller_get_pin(key->key_pin);
switch (key->key_input_type) {
case GPIOKEYS_EV_SW:
switch (key->key_code) {
case GPIOKEYS_SW_LID:
#ifdef SUSPEND
if (cpu_suspended) {
if (!val)
cpu_suspended = 0;
return;
}
#endif
key->key_sensor.value = !val;
switch (lid_action) {
case 0:
if (simplefb_burn_hook)
simplefb_burn_hook(!val);
break;
case 1:
#ifdef SUSPEND
if (val)
request_sleep(SLEEP_SUSPEND);
#endif
break;
case 2:
break;
}
break;
}
break;
}
}
void
gpiokeys_power_button(void *arg)
{
struct gpiokeys_key *key = arg;
int state;
state = gpio_controller_get_pin(key->key_pin);
if (state != key->key_state) {
if (!state)
powerbutton_event();
key->key_state = state;
}
}
int
gpiokeys_intr(void *arg)
{
struct gpiokeys_key *key = arg;
key->key_func(key);
return 1;
}