xref: /freebsd/sys/dev/hid/hrfkill.c (revision e2d56447b3a9d2ef9bd0b80c3aaa69e681f4be0f)
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
hrfkill_cb(HIDMAP_CB_ARGS)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
hrfkill_probe(device_t dev)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
hrfkill_attach(device_t dev)85 hrfkill_attach(device_t dev)
86 {
87 	return (hidmap_attach(device_get_softc(dev)));
88 }
89 
90 static int
hrfkill_detach(device_t dev)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