Changeset View
Standalone View
sys/dev/gpio/gpiokeys.c
Show All 23 Lines | |||||
* SUCH DAMAGE. | * SUCH DAMAGE. | ||||
*/ | */ | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include "opt_platform.h" | #include "opt_platform.h" | ||||
#include "opt_kbd.h" | #include "opt_kbd.h" | ||||
#include "opt_evdev.h" | |||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/systm.h> | #include <sys/systm.h> | ||||
#include <sys/bus.h> | #include <sys/bus.h> | ||||
#include <sys/gpio.h> | #include <sys/gpio.h> | ||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/lock.h> | #include <sys/lock.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
Show All 11 Lines | |||||
#include <dev/fdt/fdt_common.h> | #include <dev/fdt/fdt_common.h> | ||||
#include <dev/ofw/ofw_bus.h> | #include <dev/ofw/ofw_bus.h> | ||||
#include <dev/ofw/ofw_bus_subr.h> | #include <dev/ofw/ofw_bus_subr.h> | ||||
#include <dev/gpio/gpiobusvar.h> | #include <dev/gpio/gpiobusvar.h> | ||||
#include <dev/gpio/gpiokeys.h> | #include <dev/gpio/gpiokeys.h> | ||||
#ifdef EVDEV_SUPPORT | |||||
#include <dev/evdev/evdev.h> | |||||
#include <dev/evdev/input.h> | |||||
#endif | |||||
#define KBD_DRIVER_NAME "gpiokeys" | #define KBD_DRIVER_NAME "gpiokeys" | ||||
#define GPIOKEYS_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) | #define GPIOKEYS_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) | ||||
#define GPIOKEYS_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) | #define GPIOKEYS_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) | ||||
#define GPIOKEYS_LOCK_INIT(_sc) \ | #define GPIOKEYS_LOCK_INIT(_sc) \ | ||||
mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ | mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ | ||||
"gpiokeys", MTX_DEF) | "gpiokeys", MTX_DEF) | ||||
#define GPIOKEYS_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx); | #define GPIOKEYS_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx); | ||||
Show All 27 Lines | |||||
struct gpiokey | struct gpiokey | ||||
{ | { | ||||
struct gpiokeys_softc *parent_sc; | struct gpiokeys_softc *parent_sc; | ||||
gpio_pin_t pin; | gpio_pin_t pin; | ||||
int irq_rid; | int irq_rid; | ||||
struct resource *irq_res; | struct resource *irq_res; | ||||
void *intr_hl; | void *intr_hl; | ||||
struct mtx mtx; | struct mtx mtx; | ||||
#ifdef EVDEV_SUPPORT | |||||
struct evdev_dev *evdev; | |||||
uint32_t evcode; | |||||
#endif | |||||
uint32_t keycode; | uint32_t keycode; | ||||
int autorepeat; | int autorepeat; | ||||
struct callout debounce_callout; | struct callout debounce_callout; | ||||
struct callout repeat_callout; | struct callout repeat_callout; | ||||
int repeat_delay; | int repeat_delay; | ||||
int repeat; | int repeat; | ||||
int debounce_interval; | int debounce_interval; | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | if (sc->sc_inputtail >= GPIOKEYS_GLOBAL_IN_BUF_SIZE) { | ||||
sc->sc_inputtail = 0; | sc->sc_inputtail = 0; | ||||
} | } | ||||
} else { | } else { | ||||
device_printf(sc->sc_dev, "input buffer is full\n"); | device_printf(sc->sc_dev, "input buffer is full\n"); | ||||
} | } | ||||
} | } | ||||
static void | static void | ||||
gpiokeys_key_event(struct gpiokeys_softc *sc, uint16_t keycode, int pressed) | gpiokeys_key_event(struct gpiokeys_softc *sc, struct gpiokey *key, int pressed) | ||||
{ | { | ||||
uint32_t key; | uint32_t code; | ||||
#ifdef EVDEV_SUPPORT | |||||
if (key->evcode != GPIOKEY_NONE) { | |||||
wulf: evdev_rcpt_mask global variable should be checked here for EVDEV_RCPT_HW_KBD bit set. Or events… | |||||
avgAuthorUnsubmitted Done Inline ActionsWill do! avg: Will do! | |||||
evdev_push_key(key->evdev, key->evcode, pressed); | |||||
evdev_sync(key->evdev); /* XXX */ | |||||
} | |||||
#endif | |||||
if (key->keycode == GPIOKEY_NONE) | |||||
return; | |||||
key = keycode & SCAN_KEYCODE_MASK; | code = key->keycode & SCAN_KEYCODE_MASK; | ||||
if (!pressed) | if (!pressed) | ||||
key |= KEY_RELEASE; | code |= KEY_RELEASE; | ||||
GPIOKEYS_LOCK(sc); | GPIOKEYS_LOCK(sc); | ||||
if (keycode & SCAN_PREFIX_E0) | if (key->keycode & SCAN_PREFIX_E0) | ||||
gpiokeys_put_key(sc, 0xe0); | gpiokeys_put_key(sc, 0xe0); | ||||
else if (keycode & SCAN_PREFIX_E1) | else if (key->keycode & SCAN_PREFIX_E1) | ||||
gpiokeys_put_key(sc, 0xe1); | gpiokeys_put_key(sc, 0xe1); | ||||
gpiokeys_put_key(sc, key); | gpiokeys_put_key(sc, code); | ||||
GPIOKEYS_UNLOCK(sc); | GPIOKEYS_UNLOCK(sc); | ||||
gpiokeys_event_keyinput(sc); | gpiokeys_event_keyinput(sc); | ||||
} | } | ||||
static void | static void | ||||
gpiokey_autorepeat(void *arg) | gpiokey_autorepeat(void *arg) | ||||
{ | { | ||||
struct gpiokey *key; | struct gpiokey *key; | ||||
key = arg; | key = arg; | ||||
if (key->keycode == GPIOKEY_NONE) | gpiokeys_key_event(key->parent_sc, key, 1); | ||||
return; | |||||
gpiokeys_key_event(key->parent_sc, key->keycode, 1); | |||||
callout_reset(&key->repeat_callout, key->repeat, | callout_reset(&key->repeat_callout, key->repeat, | ||||
gpiokey_autorepeat, key); | gpiokey_autorepeat, key); | ||||
} | } | ||||
static void | static void | ||||
gpiokey_debounced_intr(void *arg) | gpiokey_debounced_intr(void *arg) | ||||
{ | { | ||||
struct gpiokey *key; | struct gpiokey *key; | ||||
bool active; | bool active; | ||||
key = arg; | key = arg; | ||||
if (key->keycode == GPIOKEY_NONE) | |||||
return; | |||||
gpio_pin_is_active(key->pin, &active); | gpio_pin_is_active(key->pin, &active); | ||||
if (active) { | if (active) { | ||||
gpiokeys_key_event(key->parent_sc, key->keycode, 1); | gpiokeys_key_event(key->parent_sc, key, 1); | ||||
if (key->autorepeat) { | if (key->autorepeat) { | ||||
wulfUnsubmitted Not Done Inline ActionsAs driver has internal support for autorepeats, it is good to map evdev typematics ioctl to kbd ones. See evdev_set_methods() usage in e.g. kbdmux.c wulf: As driver has internal support for autorepeats, it is good to map evdev typematics ioctl to kbd… | |||||
avgAuthorUnsubmitted Done Inline ActionsI am very new to keyboards and evdev, so I am not sure what that does and how to do it properly in gpiokeys. By the way, it seems that most / real keyboards have global autorepeat settings. Also, I am confused by the fact that gpiokeys now has some per-key autorepeat handling (based on parameters in FDT) and also supports keyboard ioctl-s that set kb_delay1 / kb_delay2 variables. avg: I am very new to keyboards and evdev, so I am not sure what that does and how to do it properly… | |||||
wulfUnsubmitted Not Done Inline ActionsIndeed, autorepeat support looks incomplete. I see no visible connection between kb_delayN and repeat_delay variables. wulf: Indeed, autorepeat support looks incomplete. I see no visible connection between kb_delayN and… | |||||
callout_reset(&key->repeat_callout, key->repeat_delay, | callout_reset(&key->repeat_callout, key->repeat_delay, | ||||
gpiokey_autorepeat, key); | gpiokey_autorepeat, key); | ||||
} | } | ||||
} | } | ||||
else { | else { | ||||
if (key->autorepeat && | if (key->autorepeat && | ||||
callout_pending(&key->repeat_callout)) | callout_pending(&key->repeat_callout)) | ||||
callout_stop(&key->repeat_callout); | callout_stop(&key->repeat_callout); | ||||
gpiokeys_key_event(key->parent_sc, key->keycode, 0); | gpiokeys_key_event(key->parent_sc, key, 0); | ||||
} | } | ||||
} | } | ||||
static void | static void | ||||
gpiokey_intr(void *arg) | gpiokey_intr(void *arg) | ||||
{ | { | ||||
struct gpiokey *key; | struct gpiokey *key; | ||||
int debounce_ticks; | int debounce_ticks; | ||||
key = arg; | key = arg; | ||||
GPIOKEY_LOCK(key); | GPIOKEY_LOCK(key); | ||||
debounce_ticks = (hz * key->debounce_interval) / 1000; | debounce_ticks = (hz * key->debounce_interval) / 1000; | ||||
if (debounce_ticks == 0) | if (debounce_ticks == 0) | ||||
debounce_ticks = 1; | debounce_ticks = 1; | ||||
if (!callout_pending(&key->debounce_callout)) | if (!callout_pending(&key->debounce_callout)) | ||||
callout_reset(&key->debounce_callout, debounce_ticks, | callout_reset(&key->debounce_callout, debounce_ticks, | ||||
gpiokey_debounced_intr, key); | gpiokey_debounced_intr, key); | ||||
GPIOKEY_UNLOCK(key); | GPIOKEY_UNLOCK(key); | ||||
} | } | ||||
#ifdef EVDEV_SUPPORT | |||||
static void | static void | ||||
gpiokeys_evdev_register_key(struct gpiokey *key, const char *name, | |||||
uint32_t evcode) | |||||
{ | |||||
struct evdev_dev *evdev; | |||||
int error; | |||||
evdev = evdev_alloc(); | |||||
evdev_set_name(evdev, name); | |||||
evdev_set_phys(evdev, | |||||
device_get_nameunit(key->parent_sc->sc_dev));/* XXX */ | |||||
evdev_set_id(evdev, BUS_VIRTUAL, 0, 0, 0); /* XXX */ | |||||
evdev_support_prop(evdev, INPUT_PROP_DIRECT); /* XXX */ | |||||
wulfUnsubmitted Not Done Inline ActionsINPUT_PROP_DIRECT is not applicable here. It means that device reported coordinates can be directly mapped to display surface. I.e. device is touchscreen. wulf: INPUT_PROP_DIRECT is not applicable here. It means that device reported coordinates can be… | |||||
avgAuthorUnsubmitted Done Inline ActionsOkay. avg: Okay. | |||||
evdev_support_event(evdev, EV_SYN); | |||||
evdev_support_event(evdev, EV_KEY); | |||||
evdev_support_key(evdev, evcode); | |||||
error = evdev_register_mtx(evdev, &key->mtx); | |||||
if (error == 0) { | |||||
key->evdev = evdev; | |||||
key->evcode = evcode; | |||||
} else { | |||||
evdev_free(evdev); | |||||
device_printf(key->parent_sc->sc_dev, "failed to register " | |||||
"evdev for key %s\n", name); | |||||
} | |||||
} | |||||
#endif | |||||
static void | |||||
gpiokeys_attach_key(struct gpiokeys_softc *sc, phandle_t node, | gpiokeys_attach_key(struct gpiokeys_softc *sc, phandle_t node, | ||||
struct gpiokey *key) | struct gpiokey *key) | ||||
{ | { | ||||
pcell_t prop; | pcell_t prop; | ||||
char *name; | char *name; | ||||
uint32_t code; | uint32_t code; | ||||
int err; | int err; | ||||
const char *key_name; | const char *key_name; | ||||
Show All 30 Lines | gpiokeys_attach_key(struct gpiokeys_softc *sc, phandle_t node, | ||||
if ((OF_getprop(node, "freebsd,code", &prop, sizeof(prop))) > 0) | if ((OF_getprop(node, "freebsd,code", &prop, sizeof(prop))) > 0) | ||||
key->keycode = fdt32_to_cpu(prop); | key->keycode = fdt32_to_cpu(prop); | ||||
else if ((OF_getprop(node, "linux,code", &prop, sizeof(prop))) > 0) { | else if ((OF_getprop(node, "linux,code", &prop, sizeof(prop))) > 0) { | ||||
code = fdt32_to_cpu(prop); | code = fdt32_to_cpu(prop); | ||||
key->keycode = gpiokey_map_linux_code(code); | key->keycode = gpiokey_map_linux_code(code); | ||||
if (key->keycode == GPIOKEY_NONE) | if (key->keycode == GPIOKEY_NONE) | ||||
device_printf(sc->sc_dev, "<%s> failed to map linux,code value 0x%x\n", | device_printf(sc->sc_dev, "<%s> failed to map linux,code value 0x%x\n", | ||||
key_name, code); | key_name, code); | ||||
#ifdef EVDEV_SUPPORT | |||||
gpiokeys_evdev_register_key(key, key_name, code); | |||||
wulfUnsubmitted Not Done Inline Actionsonly evdev_support_key() should be left here. Other parts gpiokeys_evdev_register_key() belongs gpiokeys_attach() to not create evdev foreach button as @manu has stated. wulf: only evdev_support_key() should be left here. Other parts gpiokeys_evdev_register_key() belongs… | |||||
avgAuthorUnsubmitted Done Inline ActionsYes, I got it. avg: Yes, I got it. | |||||
#endif | |||||
} | } | ||||
else | else | ||||
device_printf(sc->sc_dev, "<%s> no linux,code or freebsd,code property\n", | device_printf(sc->sc_dev, "<%s> no linux,code or freebsd,code property\n", | ||||
key_name); | key_name); | ||||
err = gpio_pin_get_by_ofw_idx(sc->sc_dev, node, 0, &key->pin); | err = gpio_pin_get_by_ofw_idx(sc->sc_dev, node, 0, &key->pin); | ||||
if (err) { | if (err) { | ||||
device_printf(sc->sc_dev, "<%s> failed to map pin\n", key_name); | device_printf(sc->sc_dev, "<%s> failed to map pin\n", key_name); | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | bus_release_resource(sc->sc_dev, SYS_RES_IRQ, | ||||
key->irq_rid, key->irq_res); | key->irq_rid, key->irq_res); | ||||
if (callout_pending(&key->repeat_callout)) | if (callout_pending(&key->repeat_callout)) | ||||
callout_drain(&key->repeat_callout); | callout_drain(&key->repeat_callout); | ||||
if (callout_pending(&key->debounce_callout)) | if (callout_pending(&key->debounce_callout)) | ||||
callout_drain(&key->debounce_callout); | callout_drain(&key->debounce_callout); | ||||
if (key->pin) | if (key->pin) | ||||
gpio_pin_release(key->pin); | gpio_pin_release(key->pin); | ||||
GPIOKEY_UNLOCK(key); | GPIOKEY_UNLOCK(key); | ||||
#ifdef EVDEV_SUPPORT | |||||
evdev_unregister(key->evdev); | |||||
wulfUnsubmitted Not Done Inline Actionsevdev_free() must be used in gpiokeys_detach(). evdev_unregister() destroys cdev only. wulf: evdev_free() must be used in gpiokeys_detach(). evdev_unregister() destroys cdev only. | |||||
avgAuthorUnsubmitted Done Inline ActionsOkay. avg: Okay. | |||||
#endif | |||||
GPIOKEY_LOCK_DESTROY(key); | GPIOKEY_LOCK_DESTROY(key); | ||||
} | } | ||||
static int | static int | ||||
gpiokeys_probe(device_t dev) | gpiokeys_probe(device_t dev) | ||||
{ | { | ||||
if (!ofw_bus_is_compatible(dev, "gpio-keys")) | if (!ofw_bus_is_compatible(dev, "gpio-keys")) | ||||
return (ENXIO); | return (ENXIO); | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | if (bootverbose) { | ||||
kbdd_diag(kbd, 1); | kbdd_diag(kbd, 1); | ||||
} | } | ||||
total_keys = 0; | total_keys = 0; | ||||
/* Traverse the 'gpio-keys' node and count keys */ | /* Traverse the 'gpio-keys' node and count keys */ | ||||
for (child = OF_child(keys); child != 0; child = OF_peer(child)) { | for (child = OF_child(keys); child != 0; child = OF_peer(child)) { | ||||
if (!OF_hasprop(child, "gpios")) | if (!OF_hasprop(child, "gpios")) | ||||
continue; | continue; | ||||
Not Done Inline ActionsTypically, Linux set sysfs path as phys property and we set result of device_get_nameunit(dev) to expose newbus device backing the evdev character device to userland. wulf: Typically, Linux set sysfs path as phys property and we set result of device_get_nameunit(dev)… | |||||
total_keys++; | total_keys++; | ||||
} | } | ||||
if (total_keys) { | if (total_keys) { | ||||
sc->sc_keys = malloc(sizeof(struct gpiokey) * total_keys, | sc->sc_keys = malloc(sizeof(struct gpiokey) * total_keys, | ||||
M_DEVBUF, M_WAITOK | M_ZERO); | M_DEVBUF, M_WAITOK | M_ZERO); | ||||
sc->sc_total_keys = 0; | sc->sc_total_keys = 0; | ||||
▲ Show 20 Lines • Show All 563 Lines • Show Last 20 Lines |
evdev_rcpt_mask global variable should be checked here for EVDEV_RCPT_HW_KBD bit set. Or events will be duplicated if kbdmux evdev is enabled.