1 /*
2 * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7 /*
8 * Wacom ExpressKey Remote Driver
9 *
10 * HID driver for Wacom ExpressKey Remote (USB HID, vendor 0x056a).
11 * The device uses a vendor-specific HID usage page (0xFF0C) with
12 * proprietary report formats. Only a single remote is supported;
13 * multi-remote pairing (up to 5) is not implemented.
14 *
15 * Protocol decoded from USB traffic capture (HydraUSB3 analyser).
16 * Report structure cross-referenced with the HID report descriptor
17 * and publicly available Wacom protocol documentation.
18 *
19 * Report ID 0x10 (DEVICE_LIST): pairing status (ignored)
20 * Report ID 0x11 (REMOTE): button and touch ring events
21 * buf[0]: report ID (0x11)
22 * buf[3..5]: serial number (LE 24-bit)
23 * buf[7]: battery (bit 7 = charging, bits 0-6 = percent 0-100)
24 * buf[9]: buttons 0-7
25 * buf[10]: buttons 8-15
26 * buf[11]: bits 0-1 = buttons 16-17, bits 6-7 = touch ring mode
27 * buf[12]: touch ring (bit 7 = active, bits 0-6 = position 1-72,
28 * where raw value 1 maps to position 0 and 72 maps to 71)
29 */
30
31 #include <sys/cdefs.h>
32
33 #include "opt_hid.h"
34
35 #include <sys/param.h>
36 #include <sys/bus.h>
37 #include <sys/kernel.h>
38 #include <sys/lock.h>
39 #include <sys/malloc.h>
40 #include <sys/module.h>
41 #include <sys/mutex.h>
42 #include <sys/sysctl.h>
43
44 #include <dev/evdev/input.h>
45 #include <dev/evdev/evdev.h>
46
47 #define HID_DEBUG_VAR hidwacom_debug
48 #include <dev/hid/hid.h>
49 #include <dev/hid/hidbus.h>
50 #include "usbdevs.h"
51
52 #ifdef HID_DEBUG
53 static int hidwacom_debug = 0;
54
55 static SYSCTL_NODE(_hw_hid, OID_AUTO, hidwacom, CTLFLAG_RW, 0,
56 "Wacom ExpressKey Remote");
57 SYSCTL_INT(_hw_hid_hidwacom, OID_AUTO, debug, CTLFLAG_RWTUN,
58 &hidwacom_debug, 0, "Debug level");
59 #endif
60
61 /* Report IDs */
62 #define WACOM_REPORT_DEVICE_LIST 0x10
63 #define WACOM_REPORT_REMOTE 0x11
64
65 /* Minimum report length (report ID + 12 data bytes for ring at buf[12]) */
66 #define WACOM_REMOTE_MIN_LEN 13
67
68 /* Touch ring modes (buf[11] bits 6-7) */
69 #define WACOM_RING_MODE_MASK 0xc0
70 #define WACOM_RING_MODE_SHIFT 6
71
72 /* Touch ring */
73 #define WACOM_RING_ACTIVE 0x80
74 #define WACOM_RING_POSITION_MASK 0x7f
75 #define WACOM_RING_MAX 71
76
77 /* Battery: bit 7 = charging flag, bits 0-6 = percent */
78 #define WACOM_BATTERY_CHARGING 0x80
79 #define WACOM_BATTERY_PERCENT_MASK 0x7f
80
81 /* Pad device ID for ABS_MISC activity marker (matches Linux PAD_DEVICE_ID) */
82 #define WACOM_PAD_DEVICE_ID 0x0f
83
84 #define HIDWACOM_DEV_NAME "Wacom ExpressKey Remote"
85
86 /* Button evdev keycodes */
87 static const uint16_t hidwacom_buttons[] = {
88 BTN_0, BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7, /* buf[9] */
89 BTN_8, BTN_9, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, /* buf[10] */
90 BTN_BASE, BTN_BASE2, /* buf[11] */
91 };
92
93 struct hidwacom_softc {
94 struct mtx sc_mtx;
95 struct evdev_dev *sc_evdev;
96 };
97
98 static void
hidwacom_intr(void * context,void * data,hid_size_t len)99 hidwacom_intr(void *context, void *data, hid_size_t len)
100 {
101 struct hidwacom_softc *sc = context;
102 uint8_t *buf = data;
103 uint8_t report_id;
104 uint8_t b9, b10, b11, ring, battery;
105 uint32_t serial;
106 int i, pos;
107
108 if (len < 1)
109 return;
110
111 report_id = buf[0];
112
113 if (report_id == WACOM_REPORT_DEVICE_LIST) {
114 DPRINTFN(2, "device list report (pairing)\n");
115 return;
116 }
117
118 if (report_id != WACOM_REPORT_REMOTE) {
119 DPRINTFN(1, "unknown report id: 0x%02x\n", report_id);
120 return;
121 }
122
123 if (len < WACOM_REMOTE_MIN_LEN) {
124 DPRINTFN(1, "short remote report: %zu\n", (size_t)len);
125 return;
126 }
127
128 mtx_lock(&sc->sc_mtx);
129
130 /* Serial number (LE 24-bit) */
131 serial = buf[3] + ((uint32_t)buf[4] << 8) + ((uint32_t)buf[5] << 16);
132
133 /* Battery: percent in bits 0-6, charging flag in bit 7 */
134 battery = buf[7] & WACOM_BATTERY_PERCENT_MASK;
135
136 /* Extract button bytes */
137 b9 = buf[9];
138 b10 = buf[10];
139 b11 = buf[11];
140
141 /* Buttons 0-7 from buf[9] */
142 for (i = 0; i < 8; i++)
143 evdev_push_key(sc->sc_evdev, hidwacom_buttons[i],
144 (b9 >> i) & 1);
145
146 /* Buttons 8-15 from buf[10] */
147 for (i = 0; i < 8; i++)
148 evdev_push_key(sc->sc_evdev, hidwacom_buttons[8 + i],
149 (b10 >> i) & 1);
150
151 /* Buttons 16-17 from buf[11] bits 0-1 */
152 evdev_push_key(sc->sc_evdev, BTN_BASE, b11 & 0x01);
153 evdev_push_key(sc->sc_evdev, BTN_BASE2, b11 & 0x02);
154
155 /* Touch ring: raw position is 1-based (1..72); subtract 1 for 0..71 */
156 ring = buf[12];
157 if (ring & WACOM_RING_ACTIVE) {
158 pos = (ring & WACOM_RING_POSITION_MASK) - 1;
159 if (pos < 0)
160 pos = 0;
161 if (pos > WACOM_RING_MAX)
162 pos = WACOM_RING_MAX;
163 evdev_push_abs(sc->sc_evdev, ABS_WHEEL, pos);
164 evdev_push_abs(sc->sc_evdev, ABS_MISC,
165 (b11 >> WACOM_RING_MODE_SHIFT) & 0x03);
166 } else {
167 evdev_push_abs(sc->sc_evdev, ABS_WHEEL, 0);
168 evdev_push_abs(sc->sc_evdev, ABS_MISC, 0);
169 }
170
171 /* Battery percent */
172 evdev_push_event(sc->sc_evdev, EV_MSC, MSC_PULSELED, battery);
173
174 /* Remote serial number */
175 evdev_push_event(sc->sc_evdev, EV_MSC, MSC_SERIAL, serial);
176
177 evdev_sync(sc->sc_evdev);
178
179 mtx_unlock(&sc->sc_mtx);
180 }
181
182 static const struct hid_device_id hidwacom_devs[] = {
183 { HID_BVP(BUS_USB, USB_VENDOR_WACOM,
184 USB_PRODUCT_WACOM_EXPRESSKEY_REMOTE) },
185 };
186
187 static int
hidwacom_probe(device_t dev)188 hidwacom_probe(device_t dev)
189 {
190 int error;
191
192 error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hidwacom_devs);
193 if (error != 0)
194 return (error);
195
196 if (hidbus_get_index(dev) != 0)
197 return (ENXIO);
198
199 hidbus_set_desc(dev, HIDWACOM_DEV_NAME);
200 return (BUS_PROBE_DEFAULT);
201 }
202
203 static int
hidwacom_attach(device_t dev)204 hidwacom_attach(device_t dev)
205 {
206 struct hidwacom_softc *sc = device_get_softc(dev);
207 const struct hid_device_info *hw;
208 int i, error;
209
210 hw = hid_get_device_info(dev);
211 mtx_init(&sc->sc_mtx, "hidwacom", NULL, MTX_DEF);
212
213 sc->sc_evdev = evdev_alloc();
214 evdev_set_name(sc->sc_evdev, HIDWACOM_DEV_NAME);
215 evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
216 evdev_set_id(sc->sc_evdev, hw->idBus, hw->idVendor, hw->idProduct,
217 hw->idVersion);
218 evdev_set_serial(sc->sc_evdev, hw->serial);
219
220 evdev_support_event(sc->sc_evdev, EV_SYN);
221 evdev_support_event(sc->sc_evdev, EV_KEY);
222 evdev_support_event(sc->sc_evdev, EV_ABS);
223 evdev_support_event(sc->sc_evdev, EV_MSC);
224
225 for (i = 0; i < (int)nitems(hidwacom_buttons); i++)
226 evdev_support_key(sc->sc_evdev, hidwacom_buttons[i]);
227
228 evdev_support_abs(sc->sc_evdev, ABS_WHEEL, 0, WACOM_RING_MAX,
229 0, 0, 0);
230 evdev_support_abs(sc->sc_evdev, ABS_MISC, 0, 3, 0, 0, 0);
231 evdev_support_msc(sc->sc_evdev, MSC_SERIAL);
232 evdev_support_msc(sc->sc_evdev, MSC_PULSELED);
233
234 error = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx);
235 if (error != 0) {
236 device_printf(dev, "evdev_register_mtx failed: %d\n", error);
237 goto fail;
238 }
239
240 hidbus_set_intr(dev, hidwacom_intr, sc);
241
242 error = hid_intr_start(dev);
243 if (error != 0) {
244 device_printf(dev, "hid_intr_start failed: %d\n", error);
245 hid_intr_stop(dev);
246 goto fail;
247 }
248
249 return (0);
250
251 fail:
252 if (sc->sc_evdev != NULL)
253 evdev_free(sc->sc_evdev);
254 mtx_destroy(&sc->sc_mtx);
255 return (error);
256 }
257
258 static int
hidwacom_detach(device_t dev)259 hidwacom_detach(device_t dev)
260 {
261 struct hidwacom_softc *sc = device_get_softc(dev);
262 int error;
263
264 error = hid_intr_stop(dev);
265 if (error != 0) {
266 device_printf(dev, "hid_intr_stop failed: %d\n", error);
267 return (error);
268 }
269 evdev_free(sc->sc_evdev);
270 mtx_destroy(&sc->sc_mtx);
271
272 return (0);
273 }
274
275 static device_method_t hidwacom_methods[] = {
276 DEVMETHOD(device_probe, hidwacom_probe),
277 DEVMETHOD(device_attach, hidwacom_attach),
278 DEVMETHOD(device_detach, hidwacom_detach),
279 DEVMETHOD_END
280 };
281
282 static driver_t hidwacom_driver = {
283 "hidwacom",
284 hidwacom_methods,
285 sizeof(struct hidwacom_softc)
286 };
287
288 DRIVER_MODULE(hidwacom, hidbus, hidwacom_driver, NULL, NULL);
289 MODULE_DEPEND(hidwacom, hid, 1, 1, 1);
290 MODULE_DEPEND(hidwacom, hidbus, 1, 1, 1);
291 MODULE_DEPEND(hidwacom, evdev, 1, 1, 1);
292 MODULE_VERSION(hidwacom, 1);
293 HID_PNP_INFO(hidwacom_devs);
294