1 /* 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2026 The FreeBSD Foundation 5 * Copyright (c) 2026 Framework Computer Inc 6 * 7 * This software was developed by Aymeric Wibo <obiwac@freebsd.org> 8 * under sponsorship from the FreeBSD Foundation. 9 * 10 * Portions of this software were developed by Daniel Schaefer 11 * <git@danielschaefer.me> under sponsorship from Framework Computer Inc. 12 */ 13 14 #include "opt_hid.h" 15 16 #include <sys/param.h> 17 #include <sys/bus.h> 18 #include <sys/kernel.h> 19 #include <sys/module.h> 20 21 #include <dev/evdev/evdev.h> 22 #include <dev/evdev/input.h> 23 24 #include <dev/hid/hid.h> 25 #include <dev/hid/hidbus.h> 26 #include <dev/hid/hidmap.h> 27 28 /* 29 * Reference document: 30 * https://www.usb.org/sites/default/files/hutrr40radiohidusagesfinal_0.pdf 31 */ 32 33 static hidmap_cb_t hrfkill_cb; 34 35 static const struct hidmap_item hrfkill_map[] = { 36 { HIDMAP_REL_CB(HUP_GENERIC_DESKTOP, HUG_RADIO_BUTTON, &hrfkill_cb) }, 37 }; 38 39 static const struct hid_device_id hrfkill_devs[] = { 40 { HID_TLC(HUP_GENERIC_DESKTOP, HUG_RADIO_CONTROL) }, 41 }; 42 43 /* 44 * Synthesize key-down + key-up for the wireless radio button, 45 * which only reports a relative pulse (value=1) on press. 46 * The firmware latches value=1 and never resets it, so we 47 * track the last value via HIDMAP_CB_UDATA64 and only fire 48 * on the 0 -> non-zero transition. 49 */ 50 static int 51 hrfkill_cb(HIDMAP_CB_ARGS) 52 { 53 struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); 54 int32_t last; 55 56 switch (HIDMAP_CB_GET_STATE()) { 57 case HIDMAP_CB_IS_ATTACHING: 58 evdev_support_event(evdev, EV_KEY); 59 evdev_support_key(evdev, KEY_RFKILL); 60 HIDMAP_CB_UDATA64 = 0; 61 break; 62 case HIDMAP_CB_IS_RUNNING: 63 last = (int32_t)HIDMAP_CB_UDATA64; 64 HIDMAP_CB_UDATA64 = (uint64_t)ctx.data; 65 if (ctx.data == 0 || ctx.data == last) 66 return (ENOMSG); 67 evdev_push_key(evdev, KEY_RFKILL, 1); 68 evdev_push_key(evdev, KEY_RFKILL, 0); 69 break; 70 default: 71 break; 72 } 73 74 return (0); 75 } 76 77 static int 78 hrfkill_probe(device_t dev) 79 { 80 return (HIDMAP_PROBE(device_get_softc(dev), dev, 81 hrfkill_devs, hrfkill_map, "RFKILL Switch")); 82 } 83 84 static int 85 hrfkill_attach(device_t dev) 86 { 87 return (hidmap_attach(device_get_softc(dev))); 88 } 89 90 static int 91 hrfkill_detach(device_t dev) 92 { 93 return (hidmap_detach(device_get_softc(dev))); 94 } 95 96 static device_method_t hrfkill_methods[] = { 97 DEVMETHOD(device_probe, hrfkill_probe), 98 DEVMETHOD(device_attach, hrfkill_attach), 99 DEVMETHOD(device_detach, hrfkill_detach), 100 101 DEVMETHOD_END 102 }; 103 104 DEFINE_CLASS_0(hrfkill, hrfkill_driver, hrfkill_methods, sizeof(struct hidmap)); 105 DRIVER_MODULE(hrfkill, hidbus, hrfkill_driver, NULL, NULL); 106 MODULE_DEPEND(hrfkill, hid, 1, 1, 1); 107 MODULE_DEPEND(hrfkill, hidbus, 1, 1, 1); 108 MODULE_DEPEND(hrfkill, hidmap, 1, 1, 1); 109 MODULE_DEPEND(hrfkill, evdev, 1, 1, 1); 110 MODULE_VERSION(hrfkill, 1); 111 HID_PNP_INFO(hrfkill_devs); 112