diff options
Diffstat (limited to 'sys/dev/hid/hrfkill.c')
| -rw-r--r-- | sys/dev/hid/hrfkill.c | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/sys/dev/hid/hrfkill.c b/sys/dev/hid/hrfkill.c new file mode 100644 index 000000000000..279d04c54f6e --- /dev/null +++ b/sys/dev/hid/hrfkill.c @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 The FreeBSD Foundation + * Copyright (c) 2026 Framework Computer Inc + * + * This software was developed by Aymeric Wibo <obiwac@freebsd.org> + * under sponsorship from the FreeBSD Foundation. + * + * Portions of this software were developed by Daniel Schaefer + * <git@danielschaefer.me> under sponsorship from Framework Computer Inc. + */ + +#include "opt_hid.h" + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> + +#include <dev/evdev/evdev.h> +#include <dev/evdev/input.h> + +#include <dev/hid/hid.h> +#include <dev/hid/hidbus.h> +#include <dev/hid/hidmap.h> + +/* + * Reference document: + * https://www.usb.org/sites/default/files/hutrr40radiohidusagesfinal_0.pdf + */ + +static hidmap_cb_t hrfkill_cb; + +static const struct hidmap_item hrfkill_map[] = { + { HIDMAP_REL_CB(HUP_GENERIC_DESKTOP, HUG_RADIO_BUTTON, &hrfkill_cb) }, +}; + +static const struct hid_device_id hrfkill_devs[] = { + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_RADIO_CONTROL) }, +}; + +/* + * Synthesize key-down + key-up for the wireless radio button, + * which only reports a relative pulse (value=1) on press. + * The firmware latches value=1 and never resets it, so we + * track the last value via HIDMAP_CB_UDATA64 and only fire + * on the 0 -> non-zero transition. + */ +static int +hrfkill_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + int32_t last; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + evdev_support_event(evdev, EV_KEY); + evdev_support_key(evdev, KEY_RFKILL); + HIDMAP_CB_UDATA64 = 0; + break; + case HIDMAP_CB_IS_RUNNING: + last = (int32_t)HIDMAP_CB_UDATA64; + HIDMAP_CB_UDATA64 = (uint64_t)ctx.data; + if (ctx.data == 0 || ctx.data == last) + return (ENOMSG); + evdev_push_key(evdev, KEY_RFKILL, 1); + evdev_push_key(evdev, KEY_RFKILL, 0); + break; + default: + break; + } + + return (0); +} + +static int +hrfkill_probe(device_t dev) +{ + return (HIDMAP_PROBE(device_get_softc(dev), dev, + hrfkill_devs, hrfkill_map, "RFKILL Switch")); +} + +static int +hrfkill_attach(device_t dev) +{ + return (hidmap_attach(device_get_softc(dev))); +} + +static int +hrfkill_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + +static device_method_t hrfkill_methods[] = { + DEVMETHOD(device_probe, hrfkill_probe), + DEVMETHOD(device_attach, hrfkill_attach), + DEVMETHOD(device_detach, hrfkill_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hrfkill, hrfkill_driver, hrfkill_methods, sizeof(struct hidmap)); +DRIVER_MODULE(hrfkill, hidbus, hrfkill_driver, NULL, NULL); +MODULE_DEPEND(hrfkill, hid, 1, 1, 1); +MODULE_DEPEND(hrfkill, hidbus, 1, 1, 1); +MODULE_DEPEND(hrfkill, hidmap, 1, 1, 1); +MODULE_DEPEND(hrfkill, evdev, 1, 1, 1); +MODULE_VERSION(hrfkill, 1); +HID_PNP_INFO(hrfkill_devs); |
