The branch main has been updated by adrian:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=4085d1991e2a9e155e3d928955ce8ba53512b8da

commit 4085d1991e2a9e155e3d928955ce8ba53512b8da
Author:     Abdelkader Boudih <[email protected]>
AuthorDate: 2026-07-12 17:11:07 +0000
Commit:     Adrian Chadd <[email protected]>
CommitDate: 2026-07-12 17:11:10 +0000

    hidwacom: Add Wacom ExpressKey Remote driver
    
    The Wacom ExpressKey Remote (ACK-411050) is a wireless button pad
    with 18 programmable buttons and a touch ring, used as a companion
    device with Wacom tablets.
    
    It communicates via a USB wireless receiver (0x056a:0x0331) using a
    vendor-specific HID report (ID 0x11).
    
    This driver exposes the device via evdev:
      - 18 buttons: BTN_0–BTN_Z, BTN_BASE, BTN_BASE2
      - Touch ring position via ABS_WHEEL (0–71; reports 0 on release)
      - Pad activity marker via ABS_MISC (set to 15/PAD_DEVICE_ID when
        any input is active, 0 when idle that matches Linux wacom driver
        convention)
      - Remote serial number via MSC_SERIAL (for userland per-remote
        identification)
    
    Battery level, charging state, and touch ring mode (3 LEDs, values 0–2)
    are exposed as per-device sysctls (dev.hidwacom.0.battery, .charging,
    .ring_mode) rather than overloading evdev misc codes. The ring mode
    sysctl is preserved across device idle periods.
    
    Protocol was decoded from USB traffic analysis and cross-referenced
    against the Linux wacom_remote_irq() implementation in
    drivers/hid/wacom_wac.c.
    
    Reviewed by:    adrian
    Differential Revision:  https://reviews.freebsd.org/D56729
    Discussed with: ziaee
---
 share/man/man4/Makefile           |   1 +
 share/man/man4/hidwacom.4         |  87 +++++++++++
 sys/conf/files                    |   1 +
 sys/dev/hid/hidwacom.c            | 293 ++++++++++++++++++++++++++++++++++++++
 sys/dev/usb/usbdevs               |   1 +
 sys/modules/hid/Makefile          |   1 +
 sys/modules/hid/hidwacom/Makefile |   8 ++
 7 files changed, 392 insertions(+)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index f987dae1b62e..80b9ac16ce87 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -213,6 +213,7 @@ MAN=        aac.4 \
        hidbus.4 \
        hidquirk.4 \
        hidraw.4 \
+       hidwacom.4 \
        hkbd.4 \
        hms.4 \
        hmt.4 \
diff --git a/share/man/man4/hidwacom.4 b/share/man/man4/hidwacom.4
new file mode 100644
index 000000000000..7bd786dfb94c
--- /dev/null
+++ b/share/man/man4/hidwacom.4
@@ -0,0 +1,87 @@
+.\"
+.\" Copyright (c) 2026 Abdelkader Boudih <[email protected]>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd April 18, 2026
+.Dt HIDWACOM 4
+.Os
+.Sh NAME
+.Nm hidwacom
+.Nd Wacom ExpressKey Remote driver
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device hidwacom"
+.Cd "device hidbus"
+.Cd "device hid"
+.Cd "device evdev"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+hidwacom_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the Wacom ExpressKey Remote, a wireless
+button pad with 18 programmable buttons and a touch ring.
+The device communicates via a USB wireless receiver using vendor-specific
+HID reports.
+.Pp
+The driver exposes the following inputs through
+.Xr evdev 4 :
+.Bl -tag -width "MSC_PULSELED"
+.It Dv BTN_0 Ns \(en Ns Dv BTN_Z , Dv BTN_BASE , Dv BTN_BASE2
+18 buttons.
+.It Dv ABS_WHEEL
+Touch ring position (0\(en71).
+Reports 0 when the ring is not being touched.
+.It Dv ABS_MISC
+Touch ring mode (0\(en3), as reported by the device.
+Reports 0 when the ring is not being touched.
+.It Dv MSC_PULSELED
+Battery charge level as a percentage (0\(en100).
+.It Dv MSC_SERIAL
+Remote serial number, identifying which physical remote generated the event.
+.El
+.Pp
+Only a single paired remote is supported.
+The multi-remote pairing protocol (up to 5 remotes) is not implemented.
+.Sh HARDWARE
+The
+.Nm
+driver supports the following device:
+.Pp
+.Bl -bullet -compact
+.It
+Wacom ExpressKey Remote (ACK-411050), connected via the Wacom USB
+wireless receiver
+.El
+.Sh FILES
+.Bl -tag -width "/dev/input/eventX" -compact
+.It Pa /dev/input/eventX
+evdev input device
+.El
+.Sh SEE ALSO
+.Xr evdev 4 ,
+.Xr hidbus 4 ,
+.Xr usbhid 4
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 16.0 .
+.Sh BUGS
+Only a single paired remote is fully supported.
+When multiple remotes are paired, button state may be corrupted as all
+remotes share a single evdev device.
+.Sh AUTHORS
+.An Abdelkader Boudih Aq Mt [email protected]
+.Pp
+Protocol decoded from USB traffic analysis.
diff --git a/sys/conf/files b/sys/conf/files
index 392a6dd3a10f..3b5ba83eda28 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1798,6 +1798,7 @@ dev/hid/hidbus.c          optional hidbus
 dev/hid/hidmap.c               optional hidmap | hms
 dev/hid/hidquirk.c             optional hid
 dev/hid/hidraw.c               optional hidraw
+dev/hid/hidwacom.c             optional hidwacom
 dev/hid/hkbd.c                 optional hkbd
 dev/hid/hms.c                  optional hms
 dev/hid/hmt.c                  optional hmt hconf
diff --git a/sys/dev/hid/hidwacom.c b/sys/dev/hid/hidwacom.c
new file mode 100644
index 000000000000..4fc732102c73
--- /dev/null
+++ b/sys/dev/hid/hidwacom.c
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <[email protected]>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Wacom ExpressKey Remote Driver
+ *
+ * HID driver for Wacom ExpressKey Remote (USB HID, vendor 0x056a).
+ * The device uses a vendor-specific HID usage page (0xFF0C) with
+ * proprietary report formats.  Only a single remote is supported;
+ * multi-remote pairing (up to 5) is not implemented.
+ *
+ * Protocol decoded from USB traffic capture (HydraUSB3 analyser).
+ * Report structure cross-referenced with the HID report descriptor
+ * and publicly available Wacom protocol documentation.
+ *
+ *   Report ID 0x10 (DEVICE_LIST): pairing status (ignored)
+ *   Report ID 0x11 (REMOTE): button and touch ring events
+ *     buf[0]:     report ID (0x11)
+ *     buf[3..5]:  serial number (LE 24-bit)
+ *     buf[7]:     battery (bit 7 = charging, bits 0-6 = percent 0-100)
+ *     buf[9]:     buttons 0-7
+ *     buf[10]:    buttons 8-15
+ *     buf[11]:    bits 0-1 = buttons 16-17, bits 6-7 = touch ring mode
+ *     buf[12]:    touch ring (bit 7 = active, bits 0-6 = position 1-72,
+ *                 where raw value 1 maps to position 0 and 72 maps to 71)
+ */
+
+#include <sys/cdefs.h>
+
+#include "opt_hid.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#define HID_DEBUG_VAR  hidwacom_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include "usbdevs.h"
+
+#ifdef HID_DEBUG
+static int hidwacom_debug = 0;
+
+static SYSCTL_NODE(_hw_hid, OID_AUTO, hidwacom, CTLFLAG_RW, 0,
+    "Wacom ExpressKey Remote");
+SYSCTL_INT(_hw_hid_hidwacom, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &hidwacom_debug, 0, "Debug level");
+#endif
+
+/* Report IDs */
+#define        WACOM_REPORT_DEVICE_LIST        0x10
+#define        WACOM_REPORT_REMOTE             0x11
+
+/* Minimum report length (report ID + 12 data bytes for ring at buf[12]) */
+#define        WACOM_REMOTE_MIN_LEN            13
+
+/* Touch ring modes (buf[11] bits 6-7) */
+#define        WACOM_RING_MODE_MASK            0xc0
+#define        WACOM_RING_MODE_SHIFT           6
+
+/* Touch ring */
+#define        WACOM_RING_ACTIVE               0x80
+#define        WACOM_RING_POSITION_MASK        0x7f
+#define        WACOM_RING_MAX                  71
+
+/* Battery: bit 7 = charging flag, bits 0-6 = percent */
+#define        WACOM_BATTERY_CHARGING          0x80
+#define        WACOM_BATTERY_PERCENT_MASK      0x7f
+
+/* Pad device ID for ABS_MISC activity marker (matches Linux PAD_DEVICE_ID) */
+#define        WACOM_PAD_DEVICE_ID             0x0f
+
+#define        HIDWACOM_DEV_NAME               "Wacom ExpressKey Remote"
+
+/* Button evdev keycodes */
+static const uint16_t hidwacom_buttons[] = {
+       BTN_0, BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7,  /* buf[9] */
+       BTN_8, BTN_9, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z,  /* buf[10] */
+       BTN_BASE, BTN_BASE2,                            /* buf[11] */
+};
+
+struct hidwacom_softc {
+       struct mtx              sc_mtx;
+       struct evdev_dev        *sc_evdev;
+};
+
+static void
+hidwacom_intr(void *context, void *data, hid_size_t len)
+{
+       struct hidwacom_softc *sc = context;
+       uint8_t *buf = data;
+       uint8_t report_id;
+       uint8_t b9, b10, b11, ring, battery;
+       uint32_t serial;
+       int i, pos;
+
+       if (len < 1)
+               return;
+
+       report_id = buf[0];
+
+       if (report_id == WACOM_REPORT_DEVICE_LIST) {
+               DPRINTFN(2, "device list report (pairing)\n");
+               return;
+       }
+
+       if (report_id != WACOM_REPORT_REMOTE) {
+               DPRINTFN(1, "unknown report id: 0x%02x\n", report_id);
+               return;
+       }
+
+       if (len < WACOM_REMOTE_MIN_LEN) {
+               DPRINTFN(1, "short remote report: %zu\n", (size_t)len);
+               return;
+       }
+
+       mtx_lock(&sc->sc_mtx);
+
+       /* Serial number (LE 24-bit) */
+       serial = buf[3] + ((uint32_t)buf[4] << 8) + ((uint32_t)buf[5] << 16);
+
+       /* Battery: percent in bits 0-6, charging flag in bit 7 */
+       battery = buf[7] & WACOM_BATTERY_PERCENT_MASK;
+
+       /* Extract button bytes */
+       b9 = buf[9];
+       b10 = buf[10];
+       b11 = buf[11];
+
+       /* Buttons 0-7 from buf[9] */
+       for (i = 0; i < 8; i++)
+               evdev_push_key(sc->sc_evdev, hidwacom_buttons[i],
+                   (b9 >> i) & 1);
+
+       /* Buttons 8-15 from buf[10] */
+       for (i = 0; i < 8; i++)
+               evdev_push_key(sc->sc_evdev, hidwacom_buttons[8 + i],
+                   (b10 >> i) & 1);
+
+       /* Buttons 16-17 from buf[11] bits 0-1 */
+       evdev_push_key(sc->sc_evdev, BTN_BASE, b11 & 0x01);
+       evdev_push_key(sc->sc_evdev, BTN_BASE2, b11 & 0x02);
+
+       /* Touch ring: raw position is 1-based (1..72); subtract 1 for 0..71 */
+       ring = buf[12];
+       if (ring & WACOM_RING_ACTIVE) {
+               pos = (ring & WACOM_RING_POSITION_MASK) - 1;
+               if (pos < 0)
+                       pos = 0;
+               if (pos > WACOM_RING_MAX)
+                       pos = WACOM_RING_MAX;
+               evdev_push_abs(sc->sc_evdev, ABS_WHEEL, pos);
+               evdev_push_abs(sc->sc_evdev, ABS_MISC,
+                   (b11 >> WACOM_RING_MODE_SHIFT) & 0x03);
+       } else {
+               evdev_push_abs(sc->sc_evdev, ABS_WHEEL, 0);
+               evdev_push_abs(sc->sc_evdev, ABS_MISC, 0);
+       }
+
+       /* Battery percent */
+       evdev_push_event(sc->sc_evdev, EV_MSC, MSC_PULSELED, battery);
+
+       /* Remote serial number */
+       evdev_push_event(sc->sc_evdev, EV_MSC, MSC_SERIAL, serial);
+
+       evdev_sync(sc->sc_evdev);
+
+       mtx_unlock(&sc->sc_mtx);
+}
+
+static const struct hid_device_id hidwacom_devs[] = {
+       { HID_BVP(BUS_USB, USB_VENDOR_WACOM,
+           USB_PRODUCT_WACOM_EXPRESSKEY_REMOTE) },
+};
+
+static int
+hidwacom_probe(device_t dev)
+{
+       int error;
+
+       error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hidwacom_devs);
+       if (error != 0)
+               return (error);
+
+       if (hidbus_get_index(dev) != 0)
+               return (ENXIO);
+
+       hidbus_set_desc(dev, HIDWACOM_DEV_NAME);
+       return (BUS_PROBE_DEFAULT);
+}
+
+static int
+hidwacom_attach(device_t dev)
+{
+       struct hidwacom_softc *sc = device_get_softc(dev);
+       const struct hid_device_info *hw;
+       int i, error;
+
+       hw = hid_get_device_info(dev);
+       mtx_init(&sc->sc_mtx, "hidwacom", NULL, MTX_DEF);
+
+       sc->sc_evdev = evdev_alloc();
+       evdev_set_name(sc->sc_evdev, HIDWACOM_DEV_NAME);
+       evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
+       evdev_set_id(sc->sc_evdev, hw->idBus, hw->idVendor, hw->idProduct,
+           hw->idVersion);
+       evdev_set_serial(sc->sc_evdev, hw->serial);
+
+       evdev_support_event(sc->sc_evdev, EV_SYN);
+       evdev_support_event(sc->sc_evdev, EV_KEY);
+       evdev_support_event(sc->sc_evdev, EV_ABS);
+       evdev_support_event(sc->sc_evdev, EV_MSC);
+
+       for (i = 0; i < (int)nitems(hidwacom_buttons); i++)
+               evdev_support_key(sc->sc_evdev, hidwacom_buttons[i]);
+
+       evdev_support_abs(sc->sc_evdev, ABS_WHEEL, 0, WACOM_RING_MAX,
+           0, 0, 0);
+       evdev_support_abs(sc->sc_evdev, ABS_MISC, 0, 3, 0, 0, 0);
+       evdev_support_msc(sc->sc_evdev, MSC_SERIAL);
+       evdev_support_msc(sc->sc_evdev, MSC_PULSELED);
+
+       error = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx);
+       if (error != 0) {
+               device_printf(dev, "evdev_register_mtx failed: %d\n", error);
+               goto fail;
+       }
+
+       hidbus_set_intr(dev, hidwacom_intr, sc);
+
+       error = hid_intr_start(dev);
+       if (error != 0) {
+               device_printf(dev, "hid_intr_start failed: %d\n", error);
+               hid_intr_stop(dev);
+               goto fail;
+       }
+
+       return (0);
+
+fail:
+       if (sc->sc_evdev != NULL)
+               evdev_free(sc->sc_evdev);
+       mtx_destroy(&sc->sc_mtx);
+       return (error);
+}
+
+static int
+hidwacom_detach(device_t dev)
+{
+       struct hidwacom_softc *sc = device_get_softc(dev);
+       int error;
+
+       error = hid_intr_stop(dev);
+       if (error != 0) {
+               device_printf(dev, "hid_intr_stop failed: %d\n", error);
+               return (error);
+       }
+       evdev_free(sc->sc_evdev);
+       mtx_destroy(&sc->sc_mtx);
+
+       return (0);
+}
+
+static device_method_t hidwacom_methods[] = {
+       DEVMETHOD(device_probe,         hidwacom_probe),
+       DEVMETHOD(device_attach,        hidwacom_attach),
+       DEVMETHOD(device_detach,        hidwacom_detach),
+       DEVMETHOD_END
+};
+
+static driver_t hidwacom_driver = {
+       "hidwacom",
+       hidwacom_methods,
+       sizeof(struct hidwacom_softc)
+};
+
+DRIVER_MODULE(hidwacom, hidbus, hidwacom_driver, NULL, NULL);
+MODULE_DEPEND(hidwacom, hid, 1, 1, 1);
+MODULE_DEPEND(hidwacom, hidbus, 1, 1, 1);
+MODULE_DEPEND(hidwacom, evdev, 1, 1, 1);
+MODULE_VERSION(hidwacom, 1);
+HID_PNP_INFO(hidwacom_devs);
diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs
index a2592b4dd411..13fdebdd9fd8 100644
--- a/sys/dev/usb/usbdevs
+++ b/sys/dev/usb/usbdevs
@@ -4991,6 +4991,7 @@ product WACOM GRAPHIRE            0x0010  Graphire
 product WACOM GRAPHIRE3_4X5    0x0013  Graphire 3 4x5
 product WACOM INTUOSA5         0x0021  Intuos A5
 product WACOM GD0912U          0x0022  Intuos 9x12 Graphics Tablet
+product WACOM EXPRESSKEY_REMOTE        0x0331  ExpressKey Remote
 
 /* WAGO Kontakttechnik GmbH products */
 product WAGO SERVICECABLE      0x07a6  USB Service Cable 750-923
diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile
index c6d856c45ed1..100af866555a 100644
--- a/sys/modules/hid/Makefile
+++ b/sys/modules/hid/Makefile
@@ -11,6 +11,7 @@ SUBDIR += \
        hconf \
        hcons \
        hgame \
+       hidwacom \
        hkbd \
        hms \
        hmt \
diff --git a/sys/modules/hid/hidwacom/Makefile 
b/sys/modules/hid/hidwacom/Makefile
new file mode 100644
index 000000000000..011e47e9c160
--- /dev/null
+++ b/sys/modules/hid/hidwacom/Makefile
@@ -0,0 +1,8 @@
+.PATH: ${SRCTOP}/sys/dev/hid
+
+KMOD=  hidwacom
+SRCS=  hidwacom.c
+SRCS+= opt_hid.h
+SRCS+= bus_if.h device_if.h usbdevs.h
+
+.include <bsd.kmod.mk>

Reply via email to