* [PATCH 3/8] HID: bpf: add helper macros for LE/BE conversion
From: Benjamin Tissoires @ 2026-04-03 16:12 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input, linux-kernel, Benjamin Tissoires, Peter Hutterer
In-Reply-To: <20260403-wip-sync-udev-hid-bpf-2026-04-v1-0-978cedb9a074@kernel.org>
From: Peter Hutterer <peter.hutterer@who-t.net>
BPF has bpf_htons and friends but those only work with data in Big
Endian format. HID is little endian so we need our own macros.
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/221
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/hid_bpf_helpers.h | 67 +++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h
index 5e3ffca1ed7b..f9071444c938 100644
--- a/drivers/hid/bpf/progs/hid_bpf_helpers.h
+++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h
@@ -7,6 +7,7 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
#include <linux/errno.h>
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
@@ -263,4 +264,70 @@ DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock);
_EXPAND(_ARG, __VA_ARGS__) \
} _device_ids SEC(".hid_bpf_config")
+
+/* Equivalency macros for bpf_htons and friends which are
+ * Big Endian only - HID needs little endian so these are the
+ * corresponding macros for that. See bpf/bpf_endian.h
+ */
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+# define __hid_bpf_le16_to_cpu(x) (x)
+# define __hid_bpf_le32_to_cpu(x) (x)
+# define __hid_bpf_le64_to_cpu(x) (x)
+# define __hid_bpf_cpu_to_le16(x) (x)
+# define __hid_bpf_cpu_to_le32(x) (x)
+# define __hid_bpf_cpu_to_le64(x) (x)
+# define __hid_bpf_constant_le16_to_cpu(x) (x)
+# define __hid_bpf_constant_le32_to_cpu(x) (x)
+# define __hid_bpf_constant_le64_to_cpu(x) (x)
+# define __hid_bpf_constant_cpu_to_le16(x) (x)
+# define __hid_bpf_constant_cpu_to_le32(x) (x)
+# define __hid_bpf_constant_cpu_to_le64(x) (x)
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# define __hid_bpf_le16_to_cpu(x) __builtin_bswap16(x)
+# define __hid_bpf_le32_to_cpu(x) __builtin_bswap32(x)
+# define __hid_bpf_le64_to_cpu(x) __builtin_bswap64(x)
+# define __hid_bpf_cpu_to_le16(x) __builtin_bswap16(x)
+# define __hid_bpf_cpu_to_le32(x) __builtin_bswap32(x)
+# define __hid_bpf_cpu_to_le64(x) __builtin_bswap64(x)
+# define __hid_bpf_constant_le16_to_cpu(x) __bpf_swab16(x)
+# define __hid_bpf_constant_le32_to_cpu(x) __bpf_swab32(x)
+# define __hid_bpf_constant_le64_to_cpu(x) __bpf_swab64(x)
+# define __hid_bpf_constant_cpu_to_le16(x) __bpf_swab16(x)
+# define __hid_bpf_constant_cpu_to_le32(x) __bpf_swab32(x)
+# define __hid_bpf_constant_cpu_to_le64(x) __bpf_swab64(x)
+#else
+# error "Invalid __BYTE_ORDER__"
+#endif
+
+#define hid_bpf_le16_to_cpu(x) \
+ (__builtin_constant_p(x) ? \
+ __hid_bpf_constant_le16_to_cpu(x) : __hid_bpf_le16_to_cpu(x))
+
+#define hid_bpf_le32_to_cpu(x) \
+ (__builtin_constant_p(x) ? \
+ __hid_bpf_constant_le32_to_cpu(x) : __hid_bpf_le32_to_cpu(x))
+
+#define hid_bpf_le64_to_cpu(x) \
+ (__builtin_constant_p(x) ? \
+ __hid_bpf_constant_le64_to_cpu(x) : __hid_bpf_le64_to_cpu(x))
+
+#define hid_bpf_cpu_to_le16(x) \
+ (__builtin_constant_p(x) ? \
+ __hid_bpf_constant_cpu_to_le16(x) : __hid_bpf_cpu_to_le16(x))
+
+#define hid_bpf_cpu_to_le32(x) \
+ (__builtin_constant_p(x) ? \
+ __hid_bpf_constant_cpu_to_le32(x) : __hid_bpf_cpu_to_le32(x))
+
+#define hid_bpf_cpu_to_le64(x) \
+ (__builtin_constant_p(x) ? \
+ __hid_bpf_constant_cpu_to_le64(x) : __hid_bpf_cpu_to_le64(x))
+
+#define hid_bpf_be16_to_cpu(x) bpf_ntohs(x)
+#define hid_bpf_be32_to_cpu(x) bpf_ntohl(x)
+#define hid_bpf_be64_to_cpu(x) bpf_be64_to_cpu(x)
+#define hid_bpf_cpu_to_be16(x) bpf_htons(x)
+#define hid_bpf_cpu_to_be32(x) bpf_htonl(x)
+#define hid_bpf_cpu_to_be64(x) bpf_cpu_to_be64(x)
+
#endif /* __HID_BPF_HELPERS_H */
--
2.53.0
^ permalink raw reply related
* [PATCH 2/8] HID: bpf: hid_bpf_helpers: provide a cleanup functions
From: Benjamin Tissoires @ 2026-04-03 16:12 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input, linux-kernel, Benjamin Tissoires
In-Reply-To: <20260403-wip-sync-udev-hid-bpf-2026-04-v1-0-978cedb9a074@kernel.org>
Combination of 2 udev-hid-bpf commits:
bpf: hid_bpf_helpers: provide a cleanup function for hid_bpf_release_context
bpf: helpers: add guard(bpf_spin) macro
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/221
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/hid_bpf_async.h | 36 +++++++--------
drivers/hid/bpf/progs/hid_bpf_helpers.h | 80 +++++++++++++++++++++++++++++++++
2 files changed, 98 insertions(+), 18 deletions(-)
diff --git a/drivers/hid/bpf/progs/hid_bpf_async.h b/drivers/hid/bpf/progs/hid_bpf_async.h
index 9ab585434239..877bb7e81f03 100644
--- a/drivers/hid/bpf/progs/hid_bpf_async.h
+++ b/drivers/hid/bpf/progs/hid_bpf_async.h
@@ -116,15 +116,14 @@ static int hid_bpf_async_find_empty_key(void)
if (!elem)
return -ENOMEM; /* should never happen */
- bpf_spin_lock(&elem->lock);
+ {
+ guard(bpf_spin)(&elem->lock);
- if (elem->state == HID_BPF_ASYNC_STATE_UNSET) {
- elem->state = HID_BPF_ASYNC_STATE_INITIALIZING;
- bpf_spin_unlock(&elem->lock);
- return i;
+ if (elem->state == HID_BPF_ASYNC_STATE_UNSET) {
+ elem->state = HID_BPF_ASYNC_STATE_INITIALIZING;
+ return i;
+ }
}
-
- bpf_spin_unlock(&elem->lock);
}
return -EINVAL;
@@ -175,18 +174,19 @@ static int hid_bpf_async_delayed_call(struct hid_bpf_ctx *hctx, u64 milliseconds
if (!elem)
return -EINVAL;
- bpf_spin_lock(&elem->lock);
- /* The wq must be:
- * - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called
- * - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself
- */
- if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED &&
- elem->state != HID_BPF_ASYNC_STATE_RUNNING) {
- bpf_spin_unlock(&elem->lock);
- return -EINVAL;
+ {
+ guard(bpf_spin)(&elem->lock);
+
+ /* The wq must be:
+ * - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called
+ * - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself
+ */
+ if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED &&
+ elem->state != HID_BPF_ASYNC_STATE_RUNNING)
+ return -EINVAL;
+
+ elem->state = HID_BPF_ASYNC_STATE_STARTING;
}
- elem->state = HID_BPF_ASYNC_STATE_STARTING;
- bpf_spin_unlock(&elem->lock);
elem->hid = hctx->hid->id;
diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h
index 228f8d787567..5e3ffca1ed7b 100644
--- a/drivers/hid/bpf/progs/hid_bpf_helpers.h
+++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h
@@ -40,6 +40,86 @@ extern int bpf_wq_set_callback(struct bpf_wq *wq,
#define HID_MAX_DESCRIPTOR_SIZE 4096
#define HID_IGNORE_EVENT -1
+/**
+ * Use: _cleanup_(somefunction) struct foo *bar;
+ */
+#define _cleanup_(_x) __attribute__((cleanup(_x)))
+
+/**
+ * Use: _release_(foo) *bar;
+ *
+ * This requires foo_releasep() to be present, use DEFINE_RELEASE_CLEANUP_FUNC.
+ */
+#define _release_(_type) struct _type __attribute__((cleanup(_type##_releasep)))
+
+/**
+ * Define a cleanup function for the struct type foo with a matching
+ * foo_release(). Use:
+ * DEFINE_RELEASE_CLEANUP_FUNC(foo)
+ * _unref_(foo) struct foo *bar;
+ */
+#define DEFINE_RELEASE_CLEANUP_FUNC(_type) \
+ static inline void _type##_releasep(struct _type **_p) { \
+ if (*_p) \
+ _type##_release(*_p); \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+/* for being able to have a cleanup function */
+#define hid_bpf_ctx_release hid_bpf_release_context
+DEFINE_RELEASE_CLEANUP_FUNC(hid_bpf_ctx);
+
+/*
+ * Kernel-style guard macros adapted for BPF
+ * Based on include/linux/cleanup.h from the Linux kernel
+ *
+ * These provide automatic lock/unlock using __attribute__((cleanup))
+ * similar to how _release_() works for contexts.
+ */
+
+/**
+ * DEFINE_GUARD(name, type, lock, unlock):
+ * Define a guard for automatic lock/unlock using the same pattern as _release_()
+ * @name: identifier for the guard (e.g., bpf_spin)
+ * @type: lock variable type (e.g., struct bpf_spin_lock)
+ * @lock: lock function name (e.g., bpf_spin_lock)
+ * @unlock: unlock function name (e.g., bpf_spin_unlock)
+ *
+ * guard(name):
+ * Declare and lock in one statement - lock held until end of scope
+ *
+ * Example:
+ * DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock)
+ *
+ * void foo(struct bpf_spin_lock *lock) {
+ * guard(bpf_spin)(lock);
+ * // lock held until end of scope
+ * }
+ */
+
+/* Guard helper struct - stores lock pointer for cleanup */
+#define DEFINE_GUARD(_name, _type, _lock, _unlock) \
+struct _name##_guard { \
+ _type *lock; \
+}; \
+static inline void _name##_guard_cleanup(struct _name##_guard *g) { \
+ if (g && g->lock) \
+ _unlock(g->lock); \
+} \
+static inline struct _name##_guard _name##_guard_init(_type *l) { \
+ if (l) \
+ _lock(l); \
+ return (struct _name##_guard){.lock = l}; \
+} \
+struct __useless_struct_to_allow_trailing_semicolon__
+
+#define guard(_name) \
+ struct _name##_guard COMBINE(guard, __LINE__) __attribute__((cleanup(_name##_guard_cleanup))) = \
+ _name##_guard_init
+
+/* Define BPF spinlock guard */
+DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock);
+
/* extracted from <linux/input.h> */
#define BUS_ANY 0x00
#define BUS_PCI 0x01
--
2.53.0
^ permalink raw reply related
* [PATCH 1/8] HID: bpf: fix some signed vs unsigned compiler warnings
From: Benjamin Tissoires @ 2026-04-03 16:12 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input, linux-kernel, Benjamin Tissoires, Peter Hutterer
In-Reply-To: <20260403-wip-sync-udev-hid-bpf-2026-04-v1-0-978cedb9a074@kernel.org>
From: Peter Hutterer <peter.hutterer@who-t.net>
On udev-hid-bpf, we are now getting warnings here, shut them off.
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/227
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c | 3 ++-
drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c | 2 +-
drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c | 2 +-
drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c | 2 +-
4 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c b/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c
index ec360d71130f..c562c2d684fe 100644
--- a/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c
+++ b/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c
@@ -462,7 +462,8 @@ int BPF_PROG(k20_fix_events, struct hid_bpf_ctx *hctx)
__u32 buttons;
__u8 wheel;
} __attribute__((packed)) *pad_report;
- int i, b;
+ int i;
+ size_t b;
__u8 modifiers = data[1];
__u32 buttons = 0;
diff --git a/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c
index 82f1950445dd..253b96458c58 100644
--- a/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c
+++ b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c
@@ -34,7 +34,7 @@ int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
if (data[3] != 0x06)
return 0;
- for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
+ for (size_t idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
u8 offset = offsets[idx];
/* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */
diff --git a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c
index 2da680bc4e11..ed60a660cc1a 100644
--- a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c
+++ b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c
@@ -148,7 +148,7 @@ int probe(struct hid_bpf_probe_args *ctx)
{
struct hid_bpf_ctx *hid_ctx;
__u16 pid;
- int i;
+ size_t i;
/* get a struct hid_device to access the actual pid of the device */
hid_ctx = hid_bpf_allocate_context(ctx->hid);
diff --git a/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c b/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c
index 46d5c459d0c9..ac07216f5b67 100644
--- a/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c
+++ b/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c
@@ -173,7 +173,7 @@ int BPF_PROG(hid_device_event_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 8 /* size */);
__u8 button_mask = 0;
- int d, b;
+ size_t d, b;
if (!data)
return 0; /* EPERM check */
--
2.53.0
^ permalink raw reply related
* [PATCH 0/8] HID: bpf: sync up programs with udev-hid-bpf
From: Benjamin Tissoires @ 2026-04-03 16:12 UTC (permalink / raw)
To: Jiri Kosina
Cc: linux-input, linux-kernel, Benjamin Tissoires, Peter Hutterer,
muhammed Rishal, Benjamin Tissoires
Not a full sync of udev-hid-bpf as the remaining .h syncs are not used
in merged HID-BPF progs (namely Logitech Bolt support).
I've tried to re-apply the header changes by merging the resulting code
and put references where I could.
We now gain support for:
- Huion KeyDial K20 over bluetooth
- Trust Philips SPK6327 keyboard
- A small helper to add a udev property to touchpads not running v6.18
yet
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
Benjamin Tissoires (8):
HID: bpf: fix some signed vs unsigned compiler warnings
HID: bpf: hid_bpf_helpers: provide a cleanup functions
HID: bpf: add helper macros for LE/BE conversion
HID: bpf: handle injected report descriptor in HID-BPF
hid: bpf: hid_bpf_helpers: add helper for having read/write udev properties
HID: bpf: add a BPF to get the touchpad type
HID: bpf: Add support for the Huion KeyDial K20 over bluetooth
bpf: Add fix for Trust Philips SPK6327 (145f:024b) modifier keys
drivers/hid/bpf/progs/Generic__touchpad.bpf.c | 90 +
.../bpf/progs/Huion__KeydialK20-Bluetooth.bpf.c | 492 ++++
drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c | 3 +-
.../hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c | 2 +-
drivers/hid/bpf/progs/Trust__Philips-SPK6327.bpf.c | 49 +
drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c | 2 +-
drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c | 2 +-
drivers/hid/bpf/progs/hid_bpf_async.h | 36 +-
drivers/hid/bpf/progs/hid_bpf_helpers.h | 321 +++
.../hid/bpf/progs/hid_report_descriptor_helpers.h | 80 +
drivers/hid/bpf/progs/hid_usages.h | 2810 ++++++++++++++++++++
11 files changed, 3865 insertions(+), 22 deletions(-)
---
base-commit: 26639c5427d32a90301b31bc8ab82719629c1864
change-id: 20260403-wip-sync-udev-hid-bpf-2026-04-7bcea43616cc
Best regards,
--
Benjamin Tissoires <bentiss@kernel.org>
^ permalink raw reply
* [PATCH 1/3] Add Apple T2 HID identifiers
From: deqrocks @ 2026-04-03 13:06 UTC (permalink / raw)
To: jikos, benjamin.tissoires; +Cc: linux-input
In-Reply-To: <20260403130620.91999-1-andre@negmaster.com>
Add the Apple T2 HID identifiers used by the Touch Bar and related\ndevices so the follow-up resume handling can bind to the affected\ninterfaces.
Signed-off-by: deqrocks <andre@negmaster.com>
---
hid-ids.h | 1574 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1574 insertions(+)
create mode 100644 hid-ids.h
diff --git a/hid-ids.h b/hid-ids.h
new file mode 100644
index 0000000..e170944
--- /dev/null
+++ b/hid-ids.h
@@ -0,0 +1,1574 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * USB HID quirks support for Linux
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ * Copyright (c) 2006-2007 Jiri Kosina
+ */
+
+/*
+ */
+
+#ifndef HID_IDS_H_FILE
+#define HID_IDS_H_FILE
+
+#define USB_VENDOR_ID_258A 0x258a
+#define USB_DEVICE_ID_258A_6A88 0x6a88
+
+#define USB_VENDOR_ID_3M 0x0596
+#define USB_DEVICE_ID_3M1968 0x0500
+#define USB_DEVICE_ID_3M2256 0x0502
+#define USB_DEVICE_ID_3M3266 0x0506
+
+#define USB_VENDOR_ID_A4TECH 0x09da
+#define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006
+#define USB_DEVICE_ID_A4TECH_X5_005D 0x000a
+#define USB_DEVICE_ID_A4TECH_RP_649 0x001a
+#define USB_DEVICE_ID_A4TECH_NB_95 0x022b
+
+#define USB_VENDOR_ID_AASHIMA 0x06d6
+#define USB_DEVICE_ID_AASHIMA_GAMEPAD 0x0025
+#define USB_DEVICE_ID_AASHIMA_PREDATOR 0x0026
+
+#define USB_VENDOR_ID_ACECAD 0x0460
+#define USB_DEVICE_ID_ACECAD_FLAIR 0x0004
+#define USB_DEVICE_ID_ACECAD_302 0x0008
+
+#define USB_VENDOR_ID_ACRUX 0x1a34
+
+#define USB_VENDOR_ID_ACTIONSTAR 0x2101
+#define USB_DEVICE_ID_ACTIONSTAR_1011 0x1011
+
+#define USB_VENDOR_ID_ADATA_XPG 0x125f
+#define USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE 0x7505
+#define USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE_DONGLE 0x7506
+
+#define USB_VENDOR_ID_ADS_TECH 0x06e1
+#define USB_DEVICE_ID_ADS_TECH_RADIO_SI470X 0xa155
+
+#define USB_VENDOR_ID_AFATECH 0x15a4
+#define USB_DEVICE_ID_AFATECH_AF9016 0x9016
+
+#define USB_VENDOR_ID_AIPTEK 0x08ca
+#define USB_DEVICE_ID_AIPTEK_01 0x0001
+#define USB_DEVICE_ID_AIPTEK_10 0x0010
+#define USB_DEVICE_ID_AIPTEK_20 0x0020
+#define USB_DEVICE_ID_AIPTEK_21 0x0021
+#define USB_DEVICE_ID_AIPTEK_22 0x0022
+#define USB_DEVICE_ID_AIPTEK_23 0x0023
+#define USB_DEVICE_ID_AIPTEK_24 0x0024
+
+#define USB_VENDOR_ID_AIRCABLE 0x16CA
+#define USB_DEVICE_ID_AIRCABLE1 0x1502
+
+#define USB_VENDOR_ID_AIREN 0x1a2c
+#define USB_DEVICE_ID_AIREN_SLIMPLUS 0x0002
+
+#define USB_VENDOR_ID_AKAI 0x2011
+#define USB_DEVICE_ID_AKAI_MPKMINI2 0x0715
+
+#define USB_VENDOR_ID_AKAI_09E8 0x09E8
+#define USB_DEVICE_ID_AKAI_09E8_MIDIMIX 0x0031
+
+#define USB_VENDOR_ID_ALCOR 0x058f
+#define USB_DEVICE_ID_ALCOR_USBRS232 0x9720
+#define USB_DEVICE_ID_ALCOR_MALTRON_KB 0x9410
+
+#define USB_VENDOR_ID_ALPS 0x0433
+#define USB_DEVICE_ID_IBM_GAMEPAD 0x1101
+
+#define USB_VENDOR_ID_ALPS_JP 0x044E
+#define HID_DEVICE_ID_ALPS_U1_DUAL 0x120B
+#define HID_DEVICE_ID_ALPS_U1 0x1215
+#define HID_DEVICE_ID_ALPS_U1_UNICORN_LEGACY 0x121E
+#define HID_DEVICE_ID_ALPS_T4_BTNLESS 0x120C
+
+#define USB_VENDOR_ID_AMI 0x046b
+#define USB_DEVICE_ID_AMI_VIRT_KEYBOARD_AND_MOUSE 0xff10
+
+#define USB_VENDOR_ID_ANTON 0x1130
+#define USB_DEVICE_ID_ANTON_TOUCH_PAD 0x3101
+
+#define USB_VENDOR_ID_APPLE 0x05ac
+#define BT_VENDOR_ID_APPLE 0x004c
+#define SPI_VENDOR_ID_APPLE 0x05ac
+#define HOST_VENDOR_ID_APPLE 0x05ac
+#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304
+#define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
+#define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269
+#define USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC 0x0323
+#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e
+#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 0x0265
+#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC 0x0324
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f
+#define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214
+#define USB_DEVICE_ID_APPLE_GEYSER_ISO 0x0215
+#define USB_DEVICE_ID_APPLE_GEYSER_JIS 0x0216
+#define USB_DEVICE_ID_APPLE_GEYSER3_ANSI 0x0217
+#define USB_DEVICE_ID_APPLE_GEYSER3_ISO 0x0218
+#define USB_DEVICE_ID_APPLE_GEYSER3_JIS 0x0219
+#define USB_DEVICE_ID_APPLE_GEYSER4_ANSI 0x021a
+#define USB_DEVICE_ID_APPLE_GEYSER4_ISO 0x021b
+#define USB_DEVICE_ID_APPLE_GEYSER4_JIS 0x021c
+#define USB_DEVICE_ID_APPLE_ALU_MINI_ANSI 0x021d
+#define USB_DEVICE_ID_APPLE_ALU_MINI_ISO 0x021e
+#define USB_DEVICE_ID_APPLE_ALU_MINI_JIS 0x021f
+#define USB_DEVICE_ID_APPLE_ALU_ANSI 0x0220
+#define USB_DEVICE_ID_APPLE_ALU_ISO 0x0221
+#define USB_DEVICE_ID_APPLE_ALU_JIS 0x0222
+#define USB_DEVICE_ID_APPLE_WELLSPRING_ANSI 0x0223
+#define USB_DEVICE_ID_APPLE_WELLSPRING_ISO 0x0224
+#define USB_DEVICE_ID_APPLE_WELLSPRING_JIS 0x0225
+#define USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI 0x0229
+#define USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO 0x022a
+#define USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS 0x022b
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI 0x022c
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO 0x022d
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS 0x022e
+#define USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI 0x0230
+#define USB_DEVICE_ID_APPLE_WELLSPRING2_ISO 0x0231
+#define USB_DEVICE_ID_APPLE_WELLSPRING2_JIS 0x0232
+#define USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI 0x0236
+#define USB_DEVICE_ID_APPLE_WELLSPRING3_ISO 0x0237
+#define USB_DEVICE_ID_APPLE_WELLSPRING3_JIS 0x0238
+#define USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI 0x023f
+#define USB_DEVICE_ID_APPLE_WELLSPRING4_ISO 0x0240
+#define USB_DEVICE_ID_APPLE_WELLSPRING4_JIS 0x0241
+#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI 0x0242
+#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO 0x0243
+#define USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS 0x0244
+#define USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI 0x0245
+#define USB_DEVICE_ID_APPLE_WELLSPRING5_ISO 0x0246
+#define USB_DEVICE_ID_APPLE_WELLSPRING5_JIS 0x0247
+#define USB_DEVICE_ID_APPLE_ALU_REVB_ANSI 0x024f
+#define USB_DEVICE_ID_APPLE_ALU_REVB_ISO 0x0250
+#define USB_DEVICE_ID_APPLE_ALU_REVB_JIS 0x0251
+#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI 0x0252
+#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO 0x0253
+#define USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS 0x0254
+#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI 0x0259
+#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO 0x025a
+#define USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS 0x025b
+#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI 0x0249
+#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO 0x024a
+#define USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS 0x024b
+#define USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI 0x024c
+#define USB_DEVICE_ID_APPLE_WELLSPRING6_ISO 0x024d
+#define USB_DEVICE_ID_APPLE_WELLSPRING6_JIS 0x024e
+#define USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI 0x0262
+#define USB_DEVICE_ID_APPLE_WELLSPRING7_ISO 0x0263
+#define USB_DEVICE_ID_APPLE_WELLSPRING7_JIS 0x0264
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI 0x0239
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO 0x023a
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS 0x023b
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI 0x0255
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO 0x0256
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS 0x0257
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015 0x0267
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015 0x026c
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 0x029c
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 0x029a
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021 0x029f
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024 0x0320
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2024 0x0321
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2024 0x0322
+#define USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI 0x0290
+#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO 0x0291
+#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS 0x0292
+#define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI 0x0272
+#define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO 0x0273
+#define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS 0x0274
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K 0x027a
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 0x027b
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 0x027c
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT 0x0278
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213 0x027d
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K 0x027e
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 0x027f
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K 0x0280
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F 0x0340
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a
+#define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b
+#define USB_DEVICE_ID_APPLE_IRCONTROL 0x8240
+#define USB_DEVICE_ID_APPLE_IRCONTROL2 0x1440
+#define USB_DEVICE_ID_APPLE_IRCONTROL3 0x8241
+#define USB_DEVICE_ID_APPLE_IRCONTROL4 0x8242
+#define USB_DEVICE_ID_APPLE_IRCONTROL5 0x8243
+#define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102
+#define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302
+#define SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020 0x0281
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020 0x0341
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021 0x0342
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021 0x0343
+#define HOST_DEVICE_ID_APPLE_MACBOOK_AIR13_2022 0x0351
+#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO14_2023 0x0352
+#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO16_2023 0x0353
+#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022 0x0354
+
+#define USB_VENDOR_ID_ASETEK 0x2433
+#define USB_DEVICE_ID_ASETEK_INVICTA 0xf300
+#define USB_DEVICE_ID_ASETEK_FORTE 0xf301
+#define USB_DEVICE_ID_ASETEK_LA_PRIMA 0xf303
+#define USB_DEVICE_ID_ASETEK_TONY_KANAAN 0xf306
+
+#define USB_VENDOR_ID_ASUS 0x0486
+#define USB_DEVICE_ID_ASUS_T91MT 0x0185
+#define USB_DEVICE_ID_ASUSTEK_MULTITOUCH_YFO 0x0186
+
+#define USB_VENDOR_ID_ASUSTEK 0x0b05
+#define USB_DEVICE_ID_ASUSTEK_LCM 0x1726
+#define USB_DEVICE_ID_ASUSTEK_LCM2 0x175b
+#define USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD 0x17e0
+#define USB_DEVICE_ID_ASUSTEK_T100TAF_KEYBOARD 0x1807
+#define USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD 0x8502
+#define USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD 0x183d
+#define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD 0x184a
+#define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD 0x8585
+#define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD 0x0101
+#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
+#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
+#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866
+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6
+#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO 0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6
+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe
+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c
+#define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b
+#define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869
+
+#define USB_VENDOR_ID_ATEN 0x0557
+#define USB_DEVICE_ID_ATEN_UC100KM 0x2004
+#define USB_DEVICE_ID_ATEN_CS124U 0x2202
+#define USB_DEVICE_ID_ATEN_2PORTKVM 0x2204
+#define USB_DEVICE_ID_ATEN_4PORTKVM 0x2205
+#define USB_DEVICE_ID_ATEN_4PORTKVMC 0x2208
+#define USB_DEVICE_ID_ATEN_CS682 0x2213
+#define USB_DEVICE_ID_ATEN_CS692 0x8021
+#define USB_DEVICE_ID_ATEN_CS1758 0x2220
+
+#define USB_VENDOR_ID_ATMEL 0x03eb
+#define USB_DEVICE_ID_ATMEL_MULTITOUCH 0x211c
+#define USB_DEVICE_ID_ATMEL_MXT_DIGITIZER 0x2118
+#define USB_VENDOR_ID_ATMEL_V_USB 0x16c0
+#define USB_DEVICE_ID_ATMEL_V_USB 0x05df
+
+#define USB_VENDOR_ID_AUREAL 0x0755
+#define USB_DEVICE_ID_AUREAL_W01RN 0x2626
+
+#define USB_VENDOR_ID_AVERMEDIA 0x07ca
+#define USB_DEVICE_ID_AVER_FM_MR800 0xb800
+
+#define USB_VENDOR_ID_AXENTIA 0x12cf
+#define USB_DEVICE_ID_AXENTIA_FM_RADIO 0x7111
+
+#define USB_VENDOR_ID_BAANTO 0x2453
+#define USB_DEVICE_ID_BAANTO_MT_190W2 0x0100
+
+#define USB_VENDOR_ID_BELKIN 0x050d
+#define USB_DEVICE_ID_FLIP_KVM 0x3201
+
+#define USB_VENDOR_ID_BERKSHIRE 0x0c98
+#define USB_DEVICE_ID_BERKSHIRE_PCWD 0x1140
+
+#define USB_VENDOR_ID_BETOP_2185BFM 0x11c2
+#define USB_VENDOR_ID_BETOP_2185PC 0x11c0
+#define USB_VENDOR_ID_BETOP_2185V2PC 0x8380
+#define USB_VENDOR_ID_BETOP_2185V2BFM 0x20bc
+
+#define USB_VENDOR_ID_BIGBEN 0x146b
+#define USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD 0x0902
+
+#define USB_VENDOR_ID_BTC 0x046e
+#define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578
+#define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2 0x5577
+
+#define USB_VENDOR_ID_CAMMUS 0x3416
+#define USB_DEVICE_ID_CAMMUS_C5 0x0301
+#define USB_DEVICE_ID_CAMMUS_C12 0x0302
+
+#define USB_VENDOR_ID_CANDO 0x2087
+#define USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH 0x0703
+#define USB_DEVICE_ID_CANDO_MULTI_TOUCH 0x0a01
+#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_10_1 0x0a02
+#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6 0x0b03
+#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_15_6 0x0f01
+
+#define USB_VENDOR_ID_CH 0x068e
+#define USB_DEVICE_ID_CH_PRO_THROTTLE 0x00f1
+#define USB_DEVICE_ID_CH_PRO_PEDALS 0x00f2
+#define USB_DEVICE_ID_CH_FIGHTERSTICK 0x00f3
+#define USB_DEVICE_ID_CH_COMBATSTICK 0x00f4
+#define USB_DEVICE_ID_CH_FLIGHT_SIM_ECLIPSE_YOKE 0x0051
+#define USB_DEVICE_ID_CH_FLIGHT_SIM_YOKE 0x00ff
+#define USB_DEVICE_ID_CH_3AXIS_5BUTTON_STICK 0x00d3
+#define USB_DEVICE_ID_CH_AXIS_295 0x001c
+
+#define USB_VENDOR_ID_CHERRY 0x046a
+#define USB_DEVICE_ID_CHERRY_CYMOTION 0x0023
+#define USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR 0x0027
+
+#define USB_VENDOR_ID_CHIC 0x05fe
+#define USB_DEVICE_ID_CHIC_GAMEPAD 0x0014
+
+#define USB_VENDOR_ID_CHICONY 0x04f2
+#define USB_DEVICE_ID_CHICONY_TACTICAL_PAD 0x0418
+#define USB_DEVICE_ID_CHICONY_MULTI_TOUCH 0xb19d
+#define USB_DEVICE_ID_CHICONY_WIRELESS 0x0618
+#define USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE 0x1053
+#define USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2 0x0939
+#define USB_DEVICE_ID_CHICONY_WIRELESS2 0x1123
+#define USB_DEVICE_ID_CHICONY_WIRELESS3 0x1236
+#define USB_DEVICE_ID_ASUS_AK1D 0x1125
+#define USB_DEVICE_ID_CHICONY_TOSHIBA_WT10A 0x1408
+#define USB_DEVICE_ID_CHICONY_ACER_SWITCH12 0x1421
+#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA 0xb824
+#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2 0xb82c
+#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA3 0xb882
+
+#define USB_VENDOR_ID_CHUNGHWAT 0x2247
+#define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001
+
+#define USB_VENDOR_ID_CIDC 0x1677
+
+#define I2C_VENDOR_ID_CIRQUE 0x0488
+#define I2C_PRODUCT_ID_CIRQUE_1063 0x1063
+
+#define USB_VENDOR_ID_CJTOUCH 0x24b8
+#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0020 0x0020
+#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0040 0x0040
+
+#define USB_VENDOR_ID_CLAY_LOGIC 0x20a0
+#define USB_DEVICE_ID_NITROKEY_U2F 0x4287
+
+#define USB_VENDOR_ID_CMEDIA 0x0d8c
+#define USB_DEVICE_ID_CM109 0x000e
+#define USB_DEVICE_ID_CMEDIA_HS100B 0x0014
+#define USB_DEVICE_ID_CM6533 0x0022
+
+#define USB_VENDOR_ID_CODEMERCS 0x07c0
+#define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500
+#define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff
+
+#define USB_VENDOR_ID_COOLER_MASTER 0x2516
+#define USB_DEVICE_ID_COOLER_MASTER_MICE_DONGLE 0x01b7
+
+#define USB_VENDOR_ID_CORSAIR 0x1b1c
+#define USB_DEVICE_ID_CORSAIR_K90 0x1b02
+#define USB_DEVICE_ID_CORSAIR_K70R 0x1b09
+#define USB_DEVICE_ID_CORSAIR_K95RGB 0x1b11
+#define USB_DEVICE_ID_CORSAIR_M65RGB 0x1b12
+#define USB_DEVICE_ID_CORSAIR_K70RGB 0x1b13
+#define USB_DEVICE_ID_CORSAIR_STRAFE 0x1b15
+#define USB_DEVICE_ID_CORSAIR_K65RGB 0x1b17
+#define USB_DEVICE_ID_CORSAIR_GLAIVE_RGB 0x1b34
+#define USB_DEVICE_ID_CORSAIR_K70RGB_RAPIDFIRE 0x1b38
+#define USB_DEVICE_ID_CORSAIR_K65RGB_RAPIDFIRE 0x1b39
+#define USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB 0x1b3e
+
+#define USB_VENDOR_ID_CREATIVELABS 0x041e
+#define USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51 0x322c
+#define USB_DEVICE_ID_PRODIKEYS_PCMIDI 0x2801
+#define USB_DEVICE_ID_CREATIVE_SB0540 0x3100
+
+#define USB_VENDOR_ID_CVTOUCH 0x1ff7
+#define USB_DEVICE_ID_CVTOUCH_SCREEN 0x0013
+
+#define USB_VENDOR_ID_CYGNAL 0x10c4
+#define USB_DEVICE_ID_CYGNAL_RADIO_SI470X 0x818a
+#define USB_DEVICE_ID_FOCALTECH_FTXXXX_MULTITOUCH 0x81b9
+#define USB_DEVICE_ID_CYGNAL_CP2112 0xea90
+#define USB_DEVICE_ID_U2F_ZERO 0x8acf
+
+#define USB_DEVICE_ID_CYGNAL_RADIO_SI4713 0x8244
+
+#define USB_VENDOR_ID_CYPRESS 0x04b4
+#define USB_DEVICE_ID_CYPRESS_MOUSE 0x0001
+#define USB_DEVICE_ID_CYPRESS_HIDCOM 0x5500
+#define USB_DEVICE_ID_CYPRESS_ULTRAMOUSE 0x7417
+#define USB_DEVICE_ID_CYPRESS_BARCODE_1 0xde61
+#define USB_DEVICE_ID_CYPRESS_BARCODE_2 0xde64
+#define USB_DEVICE_ID_CYPRESS_BARCODE_3 0xbca1
+#define USB_DEVICE_ID_CYPRESS_BARCODE_4 0xed81
+#define USB_DEVICE_ID_CYPRESS_TRUETOUCH 0xc001
+
+#define USB_DEVICE_ID_CYPRESS_VARMILO_VA104M_07B1 0X07b1
+
+#define USB_VENDOR_ID_DATA_MODUL 0x7374
+#define USB_VENDOR_ID_DATA_MODUL_EASYMAXTOUCH 0x1201
+
+#define USB_VENDOR_ID_DEALEXTREAME 0x10c5
+#define USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701 0x819a
+
+#define USB_VENDOR_ID_DELCOM 0x0fc5
+#define USB_DEVICE_ID_DELCOM_VISUAL_IND 0xb080
+
+#define USB_VENDOR_ID_DELL 0x413c
+#define USB_DEVICE_ID_DELL_PIXART_USB_OPTICAL_MOUSE 0x301a
+#define USB_DEVICE_ID_DELL_PRO_WIRELESS_KM5221W 0x4503
+
+#define USB_VENDOR_ID_DELORME 0x1163
+#define USB_DEVICE_ID_DELORME_EARTHMATE 0x0100
+#define USB_DEVICE_ID_DELORME_EM_LT20 0x0200
+
+#define USB_VENDOR_ID_DMI 0x0c0b
+#define USB_DEVICE_ID_DMI_ENC 0x5fab
+
+#define USB_VENDOR_ID_DRAGONRISE 0x0079
+#define USB_DEVICE_ID_REDRAGON_SEYMUR2 0x0006
+#define USB_DEVICE_ID_DRAGONRISE_WIIU 0x1800
+#define USB_DEVICE_ID_DRAGONRISE_PS3 0x1801
+#define USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR 0x1803
+#define USB_DEVICE_ID_DRAGONRISE_GAMECUBE1 0x1843
+#define USB_DEVICE_ID_DRAGONRISE_GAMECUBE2 0x1844
+#define USB_DEVICE_ID_DRAGONRISE_GAMECUBE3 0x1846
+
+#define USB_VENDOR_ID_DWAV 0x0eef
+#define USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER 0x0001
+#define USB_DEVICE_ID_DWAV_TOUCHCONTROLLER 0x0002
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480D 0x480d
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480E 0x480e
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7207 0x7207
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_720C 0x720c
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7224 0x7224
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_722A 0x722A
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_725E 0x725e
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7262 0x7262
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_726B 0x726b
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72A1 0x72a1
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72AA 0x72aa
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72C4 0x72c4
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72D0 0x72d0
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72FA 0x72fa
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7302 0x7302
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349 0x7349
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7 0x73f7
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001 0xa001
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000 0xc000
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002 0xc002
+
+#define USB_VENDOR_ID_EDIFIER 0x2d99
+#define USB_DEVICE_ID_EDIFIER_QR30 0xa101 /* EDIFIER Hal0 2.0 SE */
+
+#define USB_VENDOR_ID_ELAN 0x04f3
+#define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401
+#define USB_DEVICE_ID_HP_X2 0x074d
+#define USB_DEVICE_ID_HP_X2_10_COVER 0x0755
+#define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544
+#define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706
+#define I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM 0x2F81
+
+#define USB_VENDOR_ID_ELECOM 0x056e
+#define USB_DEVICE_ID_ELECOM_BM084 0x0061
+#define USB_DEVICE_ID_ELECOM_M_XGL20DLBK 0x00e6
+#define USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB 0x00fb
+#define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f
+#define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc
+#define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c
+#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd
+#define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe
+#define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff
+#define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d
+#define USB_DEVICE_ID_ELECOM_M_HT1URBK_010C 0x010c
+#define USB_DEVICE_ID_ELECOM_M_HT1URBK_019B 0x019b
+#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D 0x010d
+#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C 0x011c
+#define USB_DEVICE_ID_ELECOM_M_HT1MRBK 0x01aa
+#define USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB 0x01ab
+#define USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC 0x01ac
+
+#define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34
+#define USB_DEVICE_ID_DREAM_CHEEKY_WN 0x0004
+#define USB_DEVICE_ID_DREAM_CHEEKY_FA 0x000a
+
+#define USB_VENDOR_ID_ELITEGROUP 0x03fc
+#define USB_DEVICE_ID_ELITEGROUP_05D8 0x05d8
+
+#define USB_VENDOR_ID_ELO 0x04E7
+#define USB_DEVICE_ID_ELO_TS2515 0x0022
+#define USB_DEVICE_ID_ELO_TS2700 0x0020
+#define USB_DEVICE_ID_ELO_ACCUTOUCH_2216 0x0050
+
+#define USB_VENDOR_ID_EMS 0x2006
+#define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118
+
+#define USB_VENDOR_ID_EVISION 0x320f
+#define USB_DEVICE_ID_EV_TELINK_RECEIVER 0x226f
+#define USB_DEVICE_ID_EVISION_ICL01 0x5041
+
+#define USB_VENDOR_ID_FFBEAST 0x045b
+#define USB_DEVICE_ID_FFBEAST_JOYSTICK 0x58f9
+#define USB_DEVICE_ID_FFBEAST_RUDDER 0x5968
+#define USB_DEVICE_ID_FFBEAST_WHEEL 0x59d7
+
+#define USB_VENDOR_ID_FLATFROG 0x25b5
+#define USB_DEVICE_ID_MULTITOUCH_3200 0x0002
+
+#define USB_VENDOR_ID_FUTABA 0x0547
+#define USB_DEVICE_ID_LED_DISPLAY 0x7000
+
+#define USB_VENDOR_ID_FUTURE_TECHNOLOGY 0x0403
+#define USB_DEVICE_ID_RETRODE2 0x97c1
+#define USB_DEVICE_ID_FT260 0x6030
+
+#define USB_VENDOR_ID_ESSENTIAL_REALITY 0x0d7f
+#define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100
+
+#define USB_VENDOR_ID_ETT 0x0664
+#define USB_DEVICE_ID_TC5UH 0x0309
+#define USB_DEVICE_ID_TC4UM 0x0306
+
+#define USB_VENDOR_ID_ETURBOTOUCH 0x22b9
+#define USB_DEVICE_ID_ETURBOTOUCH 0x0006
+#define USB_DEVICE_ID_ETURBOTOUCH_2968 0x2968
+
+#define USB_VENDOR_ID_EZKEY 0x0518
+#define USB_DEVICE_ID_BTC_8193 0x0002
+
+#define USB_VENDOR_ID_FORMOSA 0x147a
+#define USB_DEVICE_ID_FORMOSA_IR_RECEIVER 0xe03e
+
+#define USB_VENDOR_ID_FREESCALE 0x15A2
+#define USB_DEVICE_ID_FREESCALE_MX28 0x004F
+
+#define USB_VENDOR_ID_FRUCTEL 0x25B6
+#define USB_DEVICE_ID_GAMETEL_MT_MODE 0x0002
+
+#define USB_VENDOR_ID_GAMEVICE 0x27F8
+#define USB_DEVICE_ID_GAMEVICE_GV186 0x0BBE
+#define USB_DEVICE_ID_GAMEVICE_KISHI 0x0BBF
+
+#define USB_VENDOR_ID_GAMERON 0x0810
+#define USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR 0x0001
+#define USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR 0x0002
+
+#define USB_VENDOR_ID_GEMBIRD 0x11ff
+#define USB_DEVICE_ID_GEMBIRD_JPD_DUALFORCE2 0x3331
+
+#define USB_VENDOR_ID_GENERAL_TOUCH 0x0dfc
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS 0x0003
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PWT_TENFINGERS 0x0100
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0101 0x0101
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0102 0x0102
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0106 0x0106
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_010A 0x010a
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_E100 0xe100
+
+#define I2C_VENDOR_ID_GOODIX 0x27c6
+#define I2C_DEVICE_ID_GOODIX_01E8 0x01e8
+#define I2C_DEVICE_ID_GOODIX_01E9 0x01e9
+#define I2C_DEVICE_ID_GOODIX_01F0 0x01f0
+#define I2C_DEVICE_ID_GOODIX_0D42 0x0d42
+
+#define USB_VENDOR_ID_GOODTOUCH 0x1aad
+#define USB_DEVICE_ID_GOODTOUCH_000f 0x000f
+
+#define USB_VENDOR_ID_GOOGLE 0x18d1
+#define USB_DEVICE_ID_GOOGLE_HAMMER 0x5022
+#define USB_DEVICE_ID_GOOGLE_TOUCH_ROSE 0x5028
+#define USB_DEVICE_ID_GOOGLE_STAFF 0x502b
+#define USB_DEVICE_ID_GOOGLE_WAND 0x502d
+#define USB_DEVICE_ID_GOOGLE_WHISKERS 0x5030
+#define USB_DEVICE_ID_GOOGLE_MASTERBALL 0x503c
+#define USB_DEVICE_ID_GOOGLE_MAGNEMITE 0x503d
+#define USB_DEVICE_ID_GOOGLE_MOONBALL 0x5044
+#define USB_DEVICE_ID_GOOGLE_DON 0x5050
+#define USB_DEVICE_ID_GOOGLE_EEL 0x5057
+#define USB_DEVICE_ID_GOOGLE_JEWEL 0x5061
+#define USB_DEVICE_ID_GOOGLE_STADIA 0x9400
+
+#define USB_VENDOR_ID_GOTOP 0x08f2
+#define USB_DEVICE_ID_SUPER_Q2 0x007f
+#define USB_DEVICE_ID_GOGOPEN 0x00ce
+#define USB_DEVICE_ID_PENPOWER 0x00f4
+
+#define USB_VENDOR_ID_GREENASIA 0x0e8f
+#define USB_DEVICE_ID_GREENASIA_DUAL_SAT_ADAPTOR 0x3010
+#define USB_DEVICE_ID_GREENASIA_DUAL_USB_JOYPAD 0x3013
+
+#define USB_VENDOR_ID_GRETAGMACBETH 0x0971
+#define USB_DEVICE_ID_GRETAGMACBETH_HUEY 0x2005
+
+#define USB_VENDOR_ID_GRIFFIN 0x077d
+#define USB_DEVICE_ID_POWERMATE 0x0410
+#define USB_DEVICE_ID_SOUNDKNOB 0x04AA
+#define USB_DEVICE_ID_RADIOSHARK 0x627a
+
+#define USB_VENDOR_ID_GTCO 0x078c
+#define USB_DEVICE_ID_GTCO_90 0x0090
+#define USB_DEVICE_ID_GTCO_100 0x0100
+#define USB_DEVICE_ID_GTCO_101 0x0101
+#define USB_DEVICE_ID_GTCO_103 0x0103
+#define USB_DEVICE_ID_GTCO_104 0x0104
+#define USB_DEVICE_ID_GTCO_105 0x0105
+#define USB_DEVICE_ID_GTCO_106 0x0106
+#define USB_DEVICE_ID_GTCO_107 0x0107
+#define USB_DEVICE_ID_GTCO_108 0x0108
+#define USB_DEVICE_ID_GTCO_200 0x0200
+#define USB_DEVICE_ID_GTCO_201 0x0201
+#define USB_DEVICE_ID_GTCO_202 0x0202
+#define USB_DEVICE_ID_GTCO_203 0x0203
+#define USB_DEVICE_ID_GTCO_204 0x0204
+#define USB_DEVICE_ID_GTCO_205 0x0205
+#define USB_DEVICE_ID_GTCO_206 0x0206
+#define USB_DEVICE_ID_GTCO_207 0x0207
+#define USB_DEVICE_ID_GTCO_300 0x0300
+#define USB_DEVICE_ID_GTCO_301 0x0301
+#define USB_DEVICE_ID_GTCO_302 0x0302
+#define USB_DEVICE_ID_GTCO_303 0x0303
+#define USB_DEVICE_ID_GTCO_304 0x0304
+#define USB_DEVICE_ID_GTCO_305 0x0305
+#define USB_DEVICE_ID_GTCO_306 0x0306
+#define USB_DEVICE_ID_GTCO_307 0x0307
+#define USB_DEVICE_ID_GTCO_308 0x0308
+#define USB_DEVICE_ID_GTCO_309 0x0309
+#define USB_DEVICE_ID_GTCO_400 0x0400
+#define USB_DEVICE_ID_GTCO_401 0x0401
+#define USB_DEVICE_ID_GTCO_402 0x0402
+#define USB_DEVICE_ID_GTCO_403 0x0403
+#define USB_DEVICE_ID_GTCO_404 0x0404
+#define USB_DEVICE_ID_GTCO_405 0x0405
+#define USB_DEVICE_ID_GTCO_500 0x0500
+#define USB_DEVICE_ID_GTCO_501 0x0501
+#define USB_DEVICE_ID_GTCO_502 0x0502
+#define USB_DEVICE_ID_GTCO_503 0x0503
+#define USB_DEVICE_ID_GTCO_504 0x0504
+#define USB_DEVICE_ID_GTCO_1000 0x1000
+#define USB_DEVICE_ID_GTCO_1001 0x1001
+#define USB_DEVICE_ID_GTCO_1002 0x1002
+#define USB_DEVICE_ID_GTCO_1003 0x1003
+#define USB_DEVICE_ID_GTCO_1004 0x1004
+#define USB_DEVICE_ID_GTCO_1005 0x1005
+#define USB_DEVICE_ID_GTCO_1006 0x1006
+#define USB_DEVICE_ID_GTCO_1007 0x1007
+
+#define USB_VENDOR_ID_GYRATION 0x0c16
+#define USB_DEVICE_ID_GYRATION_REMOTE 0x0002
+#define USB_DEVICE_ID_GYRATION_REMOTE_2 0x0003
+#define USB_DEVICE_ID_GYRATION_REMOTE_3 0x0008
+
+#define I2C_VENDOR_ID_HANTICK 0x0911
+#define I2C_PRODUCT_ID_HANTICK_5288 0x5288
+
+#define USB_VENDOR_ID_HANWANG 0x0b57
+#define USB_DEVICE_ID_HANWANG_TABLET_FIRST 0x5000
+#define USB_DEVICE_ID_HANWANG_TABLET_LAST 0x8fff
+
+#define USB_VENDOR_ID_HANVON 0x20b3
+#define USB_DEVICE_ID_HANVON_MULTITOUCH 0x0a18
+
+#define USB_VENDOR_ID_HANVON_ALT 0x22ed
+#define USB_DEVICE_ID_HANVON_ALT_MULTITOUCH 0x1010
+
+#define USB_VENDOR_ID_HAPP 0x078b
+#define USB_DEVICE_ID_UGCI_DRIVING 0x0010
+#define USB_DEVICE_ID_UGCI_FLYING 0x0020
+#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
+
+#define USB_VENDOR_ID_HP 0x03f0
+#define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
+#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
+#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A 0x0b4a
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE 0x134a
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A 0x094a
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941 0x0941
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a
+
+#define USB_VENDOR_ID_HUION 0x256c
+#define USB_DEVICE_ID_HUION_TABLET 0x006e
+#define USB_DEVICE_ID_HUION_TABLET2 0x006d
+
+#define USB_VENDOR_ID_IBM 0x04b3
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_III 0x3100
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_PRO 0x3103
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL 0x3105
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL 0x3108
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO 0x3109
+
+#define USB_VENDOR_ID_IDEACOM 0x1cb6
+#define USB_DEVICE_ID_IDEACOM_IDC6650 0x6650
+#define USB_DEVICE_ID_IDEACOM_IDC6651 0x6651
+#define USB_DEVICE_ID_IDEACOM_IDC6680 0x6680
+
+#define USB_VENDOR_ID_ILITEK 0x222a
+#define USB_DEVICE_ID_ILITEK_MULTITOUCH 0x0001
+
+#define USB_VENDOR_ID_INTEL_0 0x8086
+#define USB_VENDOR_ID_INTEL_1 0x8087
+#define USB_DEVICE_ID_INTEL_HID_SENSOR_0 0x09fa
+#define USB_DEVICE_ID_INTEL_HID_SENSOR_1 0x0a04
+
+#define USB_VENDOR_ID_STM_0 0x0483
+#define USB_DEVICE_ID_STM_HID_SENSOR 0x91d1
+#define USB_DEVICE_ID_STM_HID_SENSOR_1 0x9100
+
+#define USB_VENDOR_ID_ION 0x15e4
+#define USB_DEVICE_ID_ICADE 0x0132
+
+#define USB_VENDOR_ID_HOLTEK 0x1241
+#define USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP 0x5015
+
+#define USB_VENDOR_ID_HOLTEK_ALT 0x04d9
+#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD 0xa055
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A04A 0xa04a
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A067 0xa067
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070 0xa070
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072 0xa072
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081 0xa081
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2 0xa0c2
+#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096 0xa096
+#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A293 0xa293
+
+#define USB_VENDOR_ID_IMATION 0x0718
+#define USB_DEVICE_ID_DISC_STAKKA 0xd000
+
+#define USB_VENDOR_ID_IRTOUCHSYSTEMS 0x6615
+#define USB_DEVICE_ID_IRTOUCH_INFRARED_USB 0x0070
+
+#define USB_VENDOR_ID_INNOMEDIA 0x1292
+#define USB_DEVICE_ID_INNEX_GENESIS_ATARI 0x4745
+
+#define USB_VENDOR_ID_ITE 0x048d
+#define I2C_VENDOR_ID_ITE 0x103c
+#define I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15 0x184f
+#define USB_DEVICE_ID_ITE_LENOVO_YOGA 0x8386
+#define USB_DEVICE_ID_ITE_LENOVO_YOGA2 0x8350
+#define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a
+#define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396
+#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987
+#define USB_DEVICE_ID_ITE8595 0x8595
+#define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50
+
+#define USB_VENDOR_ID_JABRA 0x0b0e
+#define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412
+#define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420
+#define USB_DEVICE_ID_JABRA_GN9350E 0x9350
+
+#define USB_VENDOR_ID_JESS 0x0c45
+#define USB_DEVICE_ID_JESS_YUREX 0x1010
+#define USB_DEVICE_ID_ASUS_MD_5112 0x5112
+#define USB_DEVICE_ID_REDRAGON_ASURA 0x760b
+
+#define USB_VENDOR_ID_JESS2 0x0f30
+#define USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD 0x0111
+
+#define USB_VENDOR_ID_KBGEAR 0x084e
+#define USB_DEVICE_ID_KBGEAR_JAMSTUDIO 0x1001
+
+#define USB_VENDOR_ID_KENSINGTON 0x047d
+#define USB_DEVICE_ID_KS_SLIMBLADE 0x2041
+
+#define USB_VENDOR_ID_KWORLD 0x1b80
+#define USB_DEVICE_ID_KWORLD_RADIO_FM700 0xd700
+
+#define USB_VENDOR_ID_KEYTOUCH 0x0926
+#define USB_DEVICE_ID_KEYTOUCH_IEC 0x3333
+
+#define USB_VENDOR_ID_KYE 0x0458
+#define USB_DEVICE_ID_KYE_ERGO_525V 0x0087
+#define USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE 0x0138
+#define USB_DEVICE_ID_GENIUS_MANTICORE 0x0153
+#define USB_DEVICE_ID_GENIUS_GX_IMPERATOR 0x4018
+#define USB_DEVICE_ID_KYE_GPEN_560 0x5003
+#define USB_DEVICE_ID_KYE_EASYPEN_M406 0x5005
+#define USB_DEVICE_ID_KYE_EASYPEN_M506 0x500F
+#define USB_DEVICE_ID_KYE_EASYPEN_I405X 0x5010
+#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X 0x5011
+#define USB_DEVICE_ID_KYE_EASYPEN_M406W 0x5012
+#define USB_DEVICE_ID_KYE_EASYPEN_M610X 0x5013
+#define USB_DEVICE_ID_KYE_EASYPEN_340 0x5014
+#define USB_DEVICE_ID_KYE_PENSKETCH_M912 0x5015
+#define USB_DEVICE_ID_KYE_MOUSEPEN_M508WX 0x5016
+#define USB_DEVICE_ID_KYE_MOUSEPEN_M508X 0x5017
+#define USB_DEVICE_ID_KYE_EASYPEN_M406XE 0x5019
+#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2 0x501A
+#define USB_DEVICE_ID_KYE_PENSKETCH_T609A 0x501B
+
+#define USB_VENDOR_ID_KYSONA 0x3554
+#define USB_DEVICE_ID_KYSONA_M600_DONGLE 0xF57C
+#define USB_DEVICE_ID_KYSONA_M600_WIRED 0xF57D
+
+#define USB_VENDOR_ID_LABTEC 0x1020
+#define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
+#define USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE 0x8888
+
+#define USB_VENDOR_ID_LAVIEW 0x22D4
+#define USB_DEVICE_ID_GLORIOUS_MODEL_I 0x1503
+
+#define USB_VENDOR_ID_LCPOWER 0x1241
+#define USB_DEVICE_ID_LCPOWER_LC1000 0xf767
+
+#define USB_VENDOR_ID_LD 0x0f11
+#define USB_DEVICE_ID_LD_CASSY 0x1000
+#define USB_DEVICE_ID_LD_CASSY2 0x1001
+#define USB_DEVICE_ID_LD_POCKETCASSY 0x1010
+#define USB_DEVICE_ID_LD_POCKETCASSY2 0x1011
+#define USB_DEVICE_ID_LD_MOBILECASSY 0x1020
+#define USB_DEVICE_ID_LD_MOBILECASSY2 0x1021
+#define USB_DEVICE_ID_LD_MICROCASSYVOLTAGE 0x1031
+#define USB_DEVICE_ID_LD_MICROCASSYCURRENT 0x1032
+#define USB_DEVICE_ID_LD_MICROCASSYTIME 0x1033
+#define USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE 0x1035
+#define USB_DEVICE_ID_LD_MICROCASSYPH 0x1038
+#define USB_DEVICE_ID_LD_POWERANALYSERCASSY 0x1040
+#define USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY 0x1042
+#define USB_DEVICE_ID_LD_MACHINETESTCASSY 0x1043
+#define USB_DEVICE_ID_LD_JWM 0x1080
+#define USB_DEVICE_ID_LD_DMMP 0x1081
+#define USB_DEVICE_ID_LD_UMIP 0x1090
+#define USB_DEVICE_ID_LD_UMIC 0x10A0
+#define USB_DEVICE_ID_LD_UMIB 0x10B0
+#define USB_DEVICE_ID_LD_XRAY 0x1100
+#define USB_DEVICE_ID_LD_XRAY2 0x1101
+#define USB_DEVICE_ID_LD_XRAYCT 0x1110
+#define USB_DEVICE_ID_LD_VIDEOCOM 0x1200
+#define USB_DEVICE_ID_LD_MOTOR 0x1210
+#define USB_DEVICE_ID_LD_COM3LAB 0x2000
+#define USB_DEVICE_ID_LD_TELEPORT 0x2010
+#define USB_DEVICE_ID_LD_NETWORKANALYSER 0x2020
+#define USB_DEVICE_ID_LD_POWERCONTROL 0x2030
+#define USB_DEVICE_ID_LD_MACHINETEST 0x2040
+#define USB_DEVICE_ID_LD_MOSTANALYSER 0x2050
+#define USB_DEVICE_ID_LD_MOSTANALYSER2 0x2051
+#define USB_DEVICE_ID_LD_ABSESP 0x2060
+#define USB_DEVICE_ID_LD_AUTODATABUS 0x2070
+#define USB_DEVICE_ID_LD_MCT 0x2080
+#define USB_DEVICE_ID_LD_HYBRID 0x2090
+#define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0
+
+#define USB_VENDOR_ID_LENOVO 0x17ef
+#define USB_DEVICE_ID_LENOVO_TPKBD 0x6009
+#define USB_DEVICE_ID_LENOVO_CUSBKBD 0x6047
+#define USB_DEVICE_ID_LENOVO_TPIIUSBKBD 0x60ee
+#define USB_DEVICE_ID_LENOVO_CBTKBD 0x6048
+#define USB_DEVICE_ID_LENOVO_TPIIBTKBD 0x60e1
+#define USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL 0x6049
+#define USB_DEVICE_ID_LENOVO_TP10UBKBD 0x6062
+#define USB_DEVICE_ID_LENOVO_TPPRODOCK 0x6067
+#define USB_DEVICE_ID_LENOVO_X1_COVER 0x6085
+#define USB_DEVICE_ID_LENOVO_X1_TAB 0x60a3
+#define USB_DEVICE_ID_LENOVO_X1_TAB2 0x60a4
+#define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5
+#define USB_DEVICE_ID_LENOVO_X12_TAB 0x60fe
+#define USB_DEVICE_ID_LENOVO_X12_TAB2 0x61ae
+#define USB_DEVICE_ID_LENOVO_YOGABOOK9I 0x6161
+#define USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E 0x600e
+#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d
+#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019 0x6019
+#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e
+#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093
+#define USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT 0x6184
+#define USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT 0x61ed
+
+#define USB_VENDOR_ID_LETSKETCH 0x6161
+#define USB_DEVICE_ID_WP9620N 0x4d15
+
+#define USB_VENDOR_ID_LG 0x1fd2
+#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
+#define USB_DEVICE_ID_LG_MELFAS_MT 0x6007
+#define I2C_DEVICE_ID_LG_8001 0x8001
+#define I2C_DEVICE_ID_LG_7010 0x7010
+
+#define USB_VENDOR_ID_LITE_STAR 0x11ff
+#define USB_DEVICE_ID_PXN_V10 0x3245
+#define USB_DEVICE_ID_PXN_V12 0x1212
+#define USB_DEVICE_ID_PXN_V12_LITE 0x1112
+#define USB_DEVICE_ID_PXN_V12_LITE_2 0x1211
+#define USB_DEVICE_ID_LITE_STAR_GT987 0x2141
+
+#define USB_VENDOR_ID_LOGITECH 0x046d
+#define USB_DEVICE_ID_LOGITECH_Z_10_SPK 0x0a07
+#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
+#define USB_DEVICE_ID_LOGITECH_T651 0xb00c
+#define USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD 0xb309
+#define USB_DEVICE_ID_LOGITECH_CASA_TOUCHPAD 0xbb00
+#define USB_DEVICE_ID_LOGITECH_C007 0xc007
+#define USB_DEVICE_ID_LOGITECH_C077 0xc077
+#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101
+#define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110
+#define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f
+#define USB_DEVICE_ID_LOGITECH_HARMONY_PS3 0x0306
+#define USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS 0xc24d
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C01A 0xc01a
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C05A 0xc05a
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C06A 0xc06a
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD 0xc20a
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD 0xc211
+#define USB_DEVICE_ID_LOGITECH_EXTREME_3D 0xc215
+#define USB_DEVICE_ID_LOGITECH_DUAL_ACTION 0xc216
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2 0xc218
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219
+#define USB_DEVICE_ID_LOGITECH_G13 0xc21c
+#define USB_DEVICE_ID_LOGITECH_G15_LCD 0xc222
+#define USB_DEVICE_ID_LOGITECH_G11 0xc225
+#define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227
+#define USB_DEVICE_ID_LOGITECH_G510 0xc22d
+#define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e
+#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f
+#define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262
+#define USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL 0xc26e
+#define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283
+#define USB_DEVICE_ID_LOGITECH_FORCE3D_PRO 0xc286
+#define USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940 0xc287
+#define USB_DEVICE_ID_LOGITECH_WINGMAN_FG 0xc20e
+#define USB_DEVICE_ID_LOGITECH_WINGMAN_FFG 0xc293
+#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294
+#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL 0xc295
+#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298
+#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299
+#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a
+#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b
+#define USB_DEVICE_ID_LOGITECH_WII_WHEEL 0xc29c
+#define USB_DEVICE_ID_LOGITECH_ELITE_KBD 0xc30a
+#define USB_DEVICE_ID_LOGITECH_GROUP_AUDIO 0x0882
+#define USB_DEVICE_ID_S510_RECEIVER 0xc50c
+#define USB_DEVICE_ID_S510_RECEIVER_2 0xc517
+#define USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500 0xc512
+#define USB_DEVICE_ID_MX3000_RECEIVER 0xc513
+#define USB_DEVICE_ID_LOGITECH_27MHZ_MOUSE_RECEIVER 0xc51b
+#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER 0xc52b
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER 0xc52f
+#define USB_DEVICE_ID_LOGITECH_G700_RECEIVER 0xc531
+#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2 0xc532
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2 0xc534
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc539
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc543
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xc547
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4 0xc54d
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a
+#define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER 0xc548
+#define USB_DEVICE_ID_SPACETRAVELLER 0xc623
+#define USB_DEVICE_ID_SPACENAVIGATOR 0xc626
+#define USB_DEVICE_ID_DINOVO_DESKTOP 0xc704
+#define USB_DEVICE_ID_MX5000_RECEIVER_MOUSE_DEV 0xc70a
+#define USB_DEVICE_ID_MX5000_RECEIVER_KBD_DEV 0xc70e
+#define USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_KBD_DEV 0xc713
+#define USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_MOUSE_DEV 0xc714
+#define USB_DEVICE_ID_MX5500_RECEIVER_KBD_DEV 0xc71b
+#define USB_DEVICE_ID_MX5500_RECEIVER_MOUSE_DEV 0xc71c
+#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_KBD_DEV 0xc71e
+#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_MOUSE_DEV 0xc71f
+#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2 0xca03
+#define USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL 0xca04
+
+#define USB_VENDOR_ID_LUMIO 0x202e
+#define USB_DEVICE_ID_CRYSTALTOUCH 0x0006
+#define USB_DEVICE_ID_CRYSTALTOUCH_DUAL 0x0007
+
+#define USB_VENDOR_ID_MADCATZ 0x0738
+#define USB_DEVICE_ID_MADCATZ_BEATPAD 0x4540
+#define USB_DEVICE_ID_MADCATZ_RAT5 0x1705
+#define USB_DEVICE_ID_MADCATZ_RAT9 0x1709
+#define USB_DEVICE_ID_MADCATZ_MMO7 0x1713
+
+#define USB_VENDOR_ID_MCC 0x09db
+#define USB_DEVICE_ID_MCC_PMD1024LS 0x0076
+#define USB_DEVICE_ID_MCC_PMD1208LS 0x007a
+
+#define USB_VENDOR_ID_MCS 0x16d0
+#define USB_DEVICE_ID_MCS_GAMEPADBLOCK 0x0bcc
+
+#define USB_VENDOR_MEGAWORLD 0x07b5
+#define USB_DEVICE_ID_MEGAWORLD_GAMEPAD 0x0312
+
+#define USB_VENDOR_ID_MGE 0x0463
+#define USB_DEVICE_ID_MGE_UPS 0xffff
+#define USB_DEVICE_ID_MGE_UPS1 0x0001
+
+#define USB_VENDOR_ID_MICROCHIP 0x04d8
+#define USB_DEVICE_ID_PICKIT1 0x0032
+#define USB_DEVICE_ID_PICKIT2 0x0033
+#define USB_DEVICE_ID_PICOLCD 0xc002
+#define USB_DEVICE_ID_PICOLCD_BOOTLOADER 0xf002
+#define USB_DEVICE_ID_PICK16F1454 0x0042
+#define USB_DEVICE_ID_PICK16F1454_V2 0xf2f7
+#define USB_DEVICE_ID_LUXAFOR 0xf372
+#define USB_DEVICE_ID_MCP2200 0x00df
+#define USB_DEVICE_ID_MCP2221 0x00dd
+
+#define USB_VENDOR_ID_MICROSOFT 0x045e
+#define USB_DEVICE_ID_SIDEWINDER_GV 0x003b
+#define USB_DEVICE_ID_MS_OFFICE_KB 0x0048
+#define USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0 0x009d
+#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_7K 0x00b4
+#define USB_DEVICE_ID_MS_NE4K 0x00db
+#define USB_DEVICE_ID_MS_NE4K_JP 0x00dc
+#define USB_DEVICE_ID_MS_LK6K 0x00f9
+#define USB_DEVICE_ID_MS_PRESENTER_8K_BT 0x0701
+#define USB_DEVICE_ID_MS_PRESENTER_8K_USB 0x0713
+#define USB_DEVICE_ID_MS_NE7K 0x071d
+#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K 0x0730
+#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_3KV1 0x0732
+#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_600 0x0750
+#define USB_DEVICE_ID_MS_COMFORT_MOUSE_4500 0x076c
+#define USB_DEVICE_ID_MS_COMFORT_KEYBOARD 0x00e3
+#define USB_DEVICE_ID_MS_SURFACE_PRO_2 0x0799
+#define USB_DEVICE_ID_MS_TOUCH_COVER_2 0x07a7
+#define USB_DEVICE_ID_MS_TYPE_COVER_2 0x07a9
+#define USB_DEVICE_ID_MS_POWER_COVER 0x07da
+#define USB_DEVICE_ID_MS_SURFACE3_COVER 0x07de
+/*
+ * For a description of the Xbox controller models, refer to:
+ * https://en.wikipedia.org/wiki/Xbox_Wireless_Controller#Summary
+ */
+#define USB_DEVICE_ID_MS_XBOX_CONTROLLER_MODEL_1708 0x02fd
+#define USB_DEVICE_ID_MS_XBOX_CONTROLLER_MODEL_1708_BLE 0x0b20
+#define USB_DEVICE_ID_MS_XBOX_CONTROLLER_MODEL_1914 0x0b13
+#define USB_DEVICE_ID_MS_XBOX_CONTROLLER_MODEL_1797 0x0b05
+#define USB_DEVICE_ID_MS_XBOX_CONTROLLER_MODEL_1797_BLE 0x0b22
+#define USB_DEVICE_ID_MS_PIXART_MOUSE 0x00cb
+#define USB_DEVICE_ID_8BITDO_SN30_PRO_PLUS 0x02e0
+#define USB_DEVICE_ID_MS_MOUSE_0783 0x0783
+
+#define USB_VENDOR_ID_MOJO 0x8282
+#define USB_DEVICE_ID_RETRO_ADAPTER 0x3201
+
+#define USB_VENDOR_ID_MONTEREY 0x0566
+#define USB_DEVICE_ID_GENIUS_KB29E 0x3004
+
+#define USB_VENDOR_ID_MOZA 0x346e
+#define USB_DEVICE_ID_MOZA_R3 0x0005
+#define USB_DEVICE_ID_MOZA_R3_2 0x0015
+#define USB_DEVICE_ID_MOZA_R5 0x0004
+#define USB_DEVICE_ID_MOZA_R5_2 0x0014
+#define USB_DEVICE_ID_MOZA_R9 0x0002
+#define USB_DEVICE_ID_MOZA_R9_2 0x0012
+#define USB_DEVICE_ID_MOZA_R12 0x0006
+#define USB_DEVICE_ID_MOZA_R12_2 0x0016
+#define USB_DEVICE_ID_MOZA_R16_R21 0x0000
+#define USB_DEVICE_ID_MOZA_R16_R21_2 0x0010
+
+#define USB_VENDOR_ID_MSI 0x1770
+#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
+
+#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
+#define USB_DEVICE_ID_N_S_HARMONY 0xc359
+
+#define USB_VENDOR_ID_NATSU 0x08b7
+#define USB_DEVICE_ID_NATSU_GAMEPAD 0x0001
+
+#define USB_VENDOR_ID_NCR 0x0404
+#define USB_DEVICE_ID_NCR_FIRST 0x0300
+#define USB_DEVICE_ID_NCR_LAST 0x03ff
+
+#define USB_VENDOR_ID_NEC 0x073e
+#define USB_DEVICE_ID_NEC_USB_GAME_PAD 0x0301
+
+#define USB_VENDOR_ID_NEXIO 0x1870
+#define USB_DEVICE_ID_NEXIO_MULTITOUCH_420 0x010d
+#define USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750 0x0110
+
+#define USB_VENDOR_ID_NEXTWINDOW 0x1926
+#define USB_DEVICE_ID_NEXTWINDOW_TOUCHSCREEN 0x0003
+
+#define USB_VENDOR_ID_NINTENDO 0x057e
+#define USB_DEVICE_ID_NINTENDO_WIIMOTE 0x0306
+#define USB_DEVICE_ID_NINTENDO_WIIMOTE2 0x0330
+#define USB_DEVICE_ID_NINTENDO_JOYCONL 0x2006
+#define USB_DEVICE_ID_NINTENDO_JOYCONR 0x2007
+#define USB_DEVICE_ID_NINTENDO_PROCON 0x2009
+#define USB_DEVICE_ID_NINTENDO_CHRGGRIP 0x200e
+#define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017
+#define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
+#define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
+
+#define USB_VENDOR_ID_NOVATEK 0x0603
+#define USB_DEVICE_ID_NOVATEK_PCT 0x0600
+#define USB_DEVICE_ID_NOVATEK_MOUSE 0x1602
+
+#define USB_VENDOR_ID_NTI 0x0757
+#define USB_DEVICE_ID_USB_SUN 0x0a00
+
+#define USB_VENDOR_ID_NTRIG 0x1b96
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN 0x0001
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1 0x0003
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2 0x0004
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_3 0x0005
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_4 0x0006
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_5 0x0007
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_6 0x0008
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_7 0x0009
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_8 0x000A
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_9 0x000B
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_10 0x000C
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_11 0x000D
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_12 0x000E
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_13 0x000F
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_14 0x0010
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_15 0x0011
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_16 0x0012
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_17 0x0013
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_18 0x0014
+#define USB_DEVICE_ID_NTRIG_DUOSENSE 0x1500
+
+#define USB_VENDOR_ID_NVIDIA 0x0955
+#define USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER 0x7214
+
+#define USB_VENDOR_ID_ONTRAK 0x0a07
+#define USB_DEVICE_ID_ONTRAK_ADU100 0x0064
+
+#define USB_VENDOR_ID_ORTEK 0x05a4
+#define USB_DEVICE_ID_ORTEK_PKB1700 0x1700
+#define USB_DEVICE_ID_ORTEK_WKB2000 0x2000
+#define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S 0x8003
+
+#define USB_VENDOR_ID_PLANTRONICS 0x047f
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3210_SERIES 0xc055
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES 0xc056
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3215_SERIES 0xc057
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3225_SERIES 0xc058
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3325_SERIES 0x430c
+#define USB_DEVICE_ID_PLANTRONICS_ENCOREPRO_500_SERIES 0x431e
+
+#define USB_VENDOR_ID_PANASONIC 0x04da
+#define USB_DEVICE_ID_PANABOARD_UBT780 0x1044
+#define USB_DEVICE_ID_PANABOARD_UBT880 0x104d
+
+#define USB_VENDOR_ID_PANJIT 0x134c
+
+#define USB_VENDOR_ID_PANTHERLORD 0x0810
+#define USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK 0x0001
+
+#define USB_VENDOR_ID_PENMOUNT 0x14e1
+#define USB_DEVICE_ID_PENMOUNT_PCI 0x3500
+#define USB_DEVICE_ID_PENMOUNT_1610 0x1610
+#define USB_DEVICE_ID_PENMOUNT_1640 0x1640
+#define USB_DEVICE_ID_PENMOUNT_6000 0x6000
+
+#define USB_VENDOR_ID_PETALYNX 0x18b1
+#define USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE 0x0037
+
+#define USB_VENDOR_ID_PETZL 0x2122
+#define USB_DEVICE_ID_PETZL_HEADLAMP 0x1234
+
+#define USB_VENDOR_ID_PHILIPS 0x0471
+#define USB_DEVICE_ID_PHILIPS_IEEE802154_DONGLE 0x0617
+
+#define USB_VENDOR_ID_PI_ENGINEERING 0x05f3
+#define USB_DEVICE_ID_PI_ENGINEERING_VEC_USB_FOOTPEDAL 0xff
+
+#define USB_VENDOR_ID_PIXART 0x093a
+#define USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2 0x0137
+#define USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE 0x2510
+#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN 0x8001
+#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1 0x8002
+#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2 0x8003
+
+#define USB_VENDOR_ID_PLAYDOTCOM 0x0b43
+#define USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII 0x0003
+
+#define USB_VENDOR_ID_POWERCOM 0x0d9f
+#define USB_DEVICE_ID_POWERCOM_UPS 0x0002
+
+#define USB_VENDOR_ID_PRODIGE 0x05af
+#define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062
+
+#define I2C_VENDOR_ID_QTEC 0x6243
+
+#define USB_VENDOR_ID_QUANTA 0x0408
+#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH 0x3000
+#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001 0x3001
+#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3003 0x3003
+#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008 0x3008
+#define USB_DEVICE_ID_QUANTA_HP_5MP_CAMERA_5473 0x5473
+
+#define I2C_VENDOR_ID_RAYDIUM 0x2386
+#define I2C_PRODUCT_ID_RAYDIUM_4B33 0x4b33
+#define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118
+
+#define USB_VENDOR_ID_RAZER 0x1532
+#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE 0x010D
+#define USB_DEVICE_ID_RAZER_BLACKWIDOW 0x010e
+#define USB_DEVICE_ID_RAZER_BLACKWIDOW_CLASSIC 0x011b
+#define USB_DEVICE_ID_RAZER_BLADE_14 0x011D
+
+#define USB_VENDOR_ID_REALTEK 0x0bda
+#define USB_DEVICE_ID_REALTEK_READER 0x0152
+
+#define USB_VENDOR_ID_REDOCTANE 0x1430
+#define USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE 0x474c
+#define USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE 0x07bb
+
+#define USB_VENDOR_ID_RETROUSB 0xf000
+#define USB_DEVICE_ID_RETROUSB_SNES_RETROPAD 0x0003
+#define USB_DEVICE_ID_RETROUSB_SNES_RETROPORT 0x00f1
+
+#define USB_VENDOR_ID_ROCCAT 0x1e7d
+#define USB_DEVICE_ID_ROCCAT_ARVO 0x30d4
+#define USB_DEVICE_ID_ROCCAT_ISKU 0x319c
+#define USB_DEVICE_ID_ROCCAT_ISKUFX 0x3264
+#define USB_DEVICE_ID_ROCCAT_KONE 0x2ced
+#define USB_DEVICE_ID_ROCCAT_KONEPLUS 0x2d51
+#define USB_DEVICE_ID_ROCCAT_KONEPURE 0x2dbe
+#define USB_DEVICE_ID_ROCCAT_KONEPURE_OPTICAL 0x2db4
+#define USB_DEVICE_ID_ROCCAT_KONEXTD 0x2e22
+#define USB_DEVICE_ID_ROCCAT_KOVAPLUS 0x2d50
+#define USB_DEVICE_ID_ROCCAT_LUA 0x2c2e
+#define USB_DEVICE_ID_ROCCAT_PYRA_WIRED 0x2c24
+#define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS 0x2cf6
+#define USB_DEVICE_ID_ROCCAT_RYOS_MK 0x3138
+#define USB_DEVICE_ID_ROCCAT_RYOS_MK_GLOW 0x31ce
+#define USB_DEVICE_ID_ROCCAT_RYOS_MK_PRO 0x3232
+#define USB_DEVICE_ID_ROCCAT_SAVU 0x2d5a
+
+#define USB_VENDOR_ID_SAI 0x17dd
+
+#define USB_VENDOR_ID_SAITEK 0x06a3
+#define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17
+#define USB_DEVICE_ID_SAITEK_PS1000 0x0621
+#define USB_DEVICE_ID_SAITEK_RAT7_OLD 0x0ccb
+#define USB_DEVICE_ID_SAITEK_RAT7_CONTAGION 0x0ccd
+#define USB_DEVICE_ID_SAITEK_RAT7 0x0cd7
+#define USB_DEVICE_ID_SAITEK_RAT9 0x0cfa
+#define USB_DEVICE_ID_SAITEK_MMO7 0x0cd0
+#define USB_DEVICE_ID_SAITEK_X52 0x075c
+#define USB_DEVICE_ID_SAITEK_X52_2 0x0255
+#define USB_DEVICE_ID_SAITEK_X52_PRO 0x0762
+#define USB_DEVICE_ID_SAITEK_X65 0x0b6a
+
+#define USB_VENDOR_ID_SAMSUNG 0x0419
+#define USB_VENDOR_ID_SAMSUNG_ELECTRONICS 0x04e8
+#define USB_DEVICE_ID_SAMSUNG_IR_REMOTE 0x0001
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE 0x0600
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD 0x7021
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_GAMEPAD 0xa000
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_ACTIONMOUSE 0xa004
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_BOOKCOVER 0xa005
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064
+
+#define USB_VENDOR_ID_SEMICO 0x1a2c
+#define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023
+#define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027
+
+#define USB_VENDOR_ID_SEMITEK 0x1ea7
+#define USB_DEVICE_ID_SEMITEK_KEYBOARD 0x0907
+
+#define USB_VENDOR_ID_SENNHEISER 0x1395
+#define USB_DEVICE_ID_SENNHEISER_BTD500USB 0x002c
+
+#define USB_VENDOR_ID_SIGMA_MICRO 0x1c4f
+#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002
+#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059
+
+#define USB_VENDOR_ID_SIGMATEL 0x066F
+#define USB_DEVICE_ID_SIGMATEL_STMP3780 0x3780
+
+#define USB_VENDOR_ID_SINOWEALTH 0x258a
+#define USB_DEVICE_ID_GLORIOUS_MODEL_D 0x0033
+#define USB_DEVICE_ID_GLORIOUS_MODEL_O 0x0036
+
+#define USB_VENDOR_ID_SIS_TOUCH 0x0457
+#define USB_DEVICE_ID_SIS9200_TOUCH 0x9200
+#define USB_DEVICE_ID_SIS817_TOUCH 0x0817
+#define USB_DEVICE_ID_SIS_TS 0x1013
+#define USB_DEVICE_ID_SIS1030_TOUCH 0x1030
+
+#define USB_VENDOR_ID_SKYCABLE 0x1223
+#define USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER 0x3F07
+
+#define USB_VENDOR_ID_SMK 0x0609
+#define USB_DEVICE_ID_SMK_PS3_BDREMOTE 0x0306
+#define USB_DEVICE_ID_SMK_NSG_MR5U_REMOTE 0x0368
+#define USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE 0x0369
+
+#define USB_VENDOR_ID_SONY 0x054c
+#define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b
+#define USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE 0x0374
+#define USB_DEVICE_ID_SONY_PS3_BDREMOTE 0x0306
+#define USB_DEVICE_ID_SONY_PS3_CONTROLLER 0x0268
+#define USB_DEVICE_ID_SONY_PS4_CONTROLLER 0x05c4
+#define USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 0x09cc
+#define USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE 0x0ba0
+#define USB_DEVICE_ID_SONY_PS5_CONTROLLER 0x0ce6
+#define USB_DEVICE_ID_SONY_PS5_CONTROLLER_2 0x0df2
+#define USB_DEVICE_ID_SONY_MOTION_CONTROLLER 0x03d5
+#define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f
+#define USB_DEVICE_ID_SONY_BUZZ_CONTROLLER 0x0002
+#define USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER 0x1000
+
+#define USB_VENDOR_ID_SONY_RHYTHM 0x12ba
+#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b
+#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100
+
+#define USB_VENDOR_ID_SINO_LITE 0x1345
+#define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008
+
+#define USB_VENDOR_ID_SOLID_YEAR 0x060b
+#define USB_DEVICE_ID_MACALLY_IKEY_KEYBOARD 0x0001
+#define USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD 0x500a
+#define USB_DEVICE_ID_COUGAR_700K_GAMING_KEYBOARD 0x700a
+
+#define USB_VENDOR_ID_SOUNDGRAPH 0x15c2
+#define USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST 0x0034
+#define USB_DEVICE_ID_SOUNDGRAPH_IMON_LAST 0x0046
+
+#define USB_VENDOR_ID_STANTUM 0x1f87
+#define USB_DEVICE_ID_MTP 0x0002
+
+#define USB_VENDOR_ID_STANTUM_STM 0x0483
+#define USB_DEVICE_ID_MTP_STM 0x3261
+
+#define USB_VENDOR_ID_STANTUM_SITRONIX 0x1403
+#define USB_DEVICE_ID_MTP_SITRONIX 0x5001
+
+#define USB_VENDOR_ID_VALVE 0x28de
+#define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102
+#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142
+#define USB_DEVICE_ID_STEAM_DECK 0x1205
+
+#define USB_VENDOR_ID_STEELSERIES 0x1038
+#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+
+#define USB_VENDOR_ID_SUN 0x0430
+#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
+
+#define USB_VENDOR_ID_SUNPLUS 0x04fc
+#define USB_DEVICE_ID_SUNPLUS_WDESKTOP 0x05d8
+
+#define USB_VENDOR_ID_SYMBOL 0x05e0
+#define USB_DEVICE_ID_SYMBOL_SCANNER_1 0x0800
+#define USB_DEVICE_ID_SYMBOL_SCANNER_2 0x1300
+#define USB_DEVICE_ID_SYMBOL_SCANNER_3 0x1200
+
+#define I2C_VENDOR_ID_SYNAPTICS 0x06cb
+#define I2C_PRODUCT_ID_SYNAPTICS_SYNA2393 0x7a13
+
+#define USB_VENDOR_ID_SYNAPTICS 0x06cb
+#define USB_DEVICE_ID_SYNAPTICS_TP 0x0001
+#define USB_DEVICE_ID_SYNAPTICS_INT_TP 0x0002
+#define USB_DEVICE_ID_SYNAPTICS_CPAD 0x0003
+#define USB_DEVICE_ID_SYNAPTICS_TS 0x0006
+#define USB_DEVICE_ID_SYNAPTICS_STICK 0x0007
+#define USB_DEVICE_ID_SYNAPTICS_WP 0x0008
+#define USB_DEVICE_ID_SYNAPTICS_COMP_TP 0x0009
+#define USB_DEVICE_ID_SYNAPTICS_WTP 0x0010
+#define USB_DEVICE_ID_SYNAPTICS_DPAD 0x0013
+#define USB_DEVICE_ID_SYNAPTICS_LTS1 0x0af8
+#define USB_DEVICE_ID_SYNAPTICS_LTS2 0x1d10
+#define USB_DEVICE_ID_SYNAPTICS_HD 0x0ac3
+#define USB_DEVICE_ID_SYNAPTICS_QUAD_HD 0x1ac3
+#define USB_DEVICE_ID_SYNAPTICS_DELL_K12A 0x2819
+#define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5_012 0x2968
+#define USB_DEVICE_ID_SYNAPTICS_TP_V103 0x5710
+#define USB_DEVICE_ID_SYNAPTICS_DELL_K15A 0x6e21
+#define USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1002 0x73f4
+#define USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1003 0x73f5
+#define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5_017 0x73f6
+#define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5 0x81a7
+
+#define USB_VENDOR_ID_TEXAS_INSTRUMENTS 0x2047
+#define USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA 0x0855
+
+#define USB_VENDOR_ID_THINGM 0x27b8
+#define USB_DEVICE_ID_BLINK1 0x01ed
+
+#define USB_VENDOR_ID_THQ 0x20d6
+#define USB_DEVICE_ID_THQ_PS3_UDRAW 0xcb17
+
+#define USB_VENDOR_ID_THRUSTMASTER 0x044f
+
+#define USB_VENDOR_ID_TIVO 0x150a
+#define USB_DEVICE_ID_TIVO_SLIDE_BT 0x1200
+#define USB_DEVICE_ID_TIVO_SLIDE 0x1201
+#define USB_DEVICE_ID_TIVO_SLIDE_PRO 0x1203
+
+#define USB_VENDOR_ID_TOPRE 0x0853
+#define USB_DEVICE_ID_TOPRE_REALFORCE_R2_108 0x0148
+#define USB_DEVICE_ID_TOPRE_REALFORCE_R2_87 0x0146
+#define USB_DEVICE_ID_TOPRE_REALFORCE_R3S_87 0x0313
+
+#define USB_VENDOR_ID_TOPSEED 0x0766
+#define USB_DEVICE_ID_TOPSEED_CYBERLINK 0x0204
+
+#define USB_VENDOR_ID_TOPSEED2 0x1784
+#define USB_DEVICE_ID_TOPSEED2_RF_COMBO 0x0004
+#define USB_DEVICE_ID_TOPSEED2_PERIPAD_701 0x0016
+
+#define USB_VENDOR_ID_TOPMAX 0x0663
+#define USB_DEVICE_ID_TOPMAX_COBRAPAD 0x0103
+
+#define USB_VENDOR_ID_TOUCH_INTL 0x1e5e
+#define USB_DEVICE_ID_TOUCH_INTL_MULTI_TOUCH 0x0313
+
+#define USB_VENDOR_ID_TOUCHPACK 0x1bfd
+#define USB_DEVICE_ID_TOUCHPACK_RTS 0x1688
+
+#define USB_VENDOR_ID_TPV 0x25aa
+#define USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8882 0x8882
+#define USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8883 0x8883
+
+#define USB_VENDOR_ID_TRUST 0x145f
+#define USB_DEVICE_ID_TRUST_PANORA_TABLET 0x0212
+
+#define USB_VENDOR_ID_TURBOX 0x062a
+#define USB_DEVICE_ID_TURBOX_KEYBOARD 0x0201
+#define USB_DEVICE_ID_ASUS_MD_5110 0x5110
+#define USB_DEVICE_ID_TURBOX_TOUCHSCREEN_MOSART 0x7100
+
+#define USB_VENDOR_ID_TWINHAN 0x6253
+#define USB_DEVICE_ID_TWINHAN_IR_REMOTE 0x0100
+
+#define USB_VENDOR_ID_UCLOGIC 0x5543
+#define USB_DEVICE_ID_UCLOGIC_TABLET_PF1209 0x0042
+#define USB_DEVICE_ID_UCLOGIC_TABLET_KNA5 0x6001
+#define USB_DEVICE_ID_UCLOGIC_TABLET_TWA60 0x0064
+#define USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U 0x0003
+#define USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U 0x0004
+#define USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U 0x0005
+#define USB_DEVICE_ID_UCLOGIC_TABLET_WP1062 0x0064
+#define USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850 0x0522
+#define USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60 0x0781
+#define USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3 0x3031
+#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81 0x0081
+#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45 0x0045
+#define USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_47 0x0047
+#define USB_DEVICE_ID_YIYNOVA_TABLET 0x004d
+
+#define USB_VENDOR_ID_UGEE 0x28bd
+#define USB_DEVICE_ID_UGEE_PARBLO_A610_PRO 0x1903
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540 0x0075
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640 0x0094
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2 0x0905
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L 0x0935
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW 0x0934
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S 0x0909
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW 0x0933
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO 0x091b
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO 0x092d
+#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
+#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071
+#define USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720 0x0055
+
+#define USB_VENDOR_ID_UNITEC 0x227d
+#define USB_DEVICE_ID_UNITEC_USB_TOUCH_0709 0x0709
+#define USB_DEVICE_ID_UNITEC_USB_TOUCH_0A19 0x0a19
+
+#define USB_VENDOR_ID_VELLEMAN 0x10cf
+#define USB_DEVICE_ID_VELLEMAN_K8055_FIRST 0x5500
+#define USB_DEVICE_ID_VELLEMAN_K8055_LAST 0x5503
+#define USB_DEVICE_ID_VELLEMAN_K8061_FIRST 0x8061
+#define USB_DEVICE_ID_VELLEMAN_K8061_LAST 0x8068
+
+#define USB_VENDOR_ID_VRS 0x0483
+#define USB_DEVICE_ID_VRS_DFP 0xa355
+#define USB_DEVICE_ID_VRS_R295 0xa44c
+
+#define USB_VENDOR_ID_VTL 0x0306
+#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f
+
+#define USB_VENDOR_ID_WACOM 0x056a
+#define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81
+#define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0x00BD
+
+#define USB_VENDOR_ID_WALTOP 0x172f
+#define USB_DEVICE_ID_WALTOP_SLIM_TABLET_5_8_INCH 0x0032
+#define USB_DEVICE_ID_WALTOP_SLIM_TABLET_12_1_INCH 0x0034
+#define USB_DEVICE_ID_WALTOP_Q_PAD 0x0037
+#define USB_DEVICE_ID_WALTOP_PID_0038 0x0038
+#define USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH 0x0501
+#define USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH 0x0500
+#define USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET 0x0502
+
+#define USB_VENDOR_ID_WEIDA 0x2575
+#define USB_DEVICE_ID_WEIDA_8752 0xC300
+#define USB_DEVICE_ID_WEIDA_8755 0xC301
+
+#define USB_VENDOR_ID_WINBOND 0x0416
+#define USB_DEVICE_ID_TSTP_MTOUCH 0xc168
+
+#define USB_VENDOR_ID_WISEGROUP 0x0925
+#define USB_DEVICE_ID_SMARTJOY_PLUS 0x0005
+#define USB_DEVICE_ID_SUPER_JOY_BOX_3 0x8888
+#define USB_DEVICE_ID_QUAD_USB_JOYPAD 0x8800
+#define USB_DEVICE_ID_DUAL_USB_JOYPAD 0x8866
+
+#define USB_VENDOR_ID_WISEGROUP_LTD 0x6666
+#define USB_VENDOR_ID_WISEGROUP_LTD2 0x6677
+#define USB_DEVICE_ID_SMARTJOY_DUAL_PLUS 0x8802
+#define USB_DEVICE_ID_SUPER_JOY_BOX_3_PRO 0x8801
+#define USB_DEVICE_ID_SUPER_DUAL_BOX_PRO 0x8802
+#define USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO 0x8804
+
+#define USB_VENDOR_ID_WISTRON 0x0fb8
+#define USB_DEVICE_ID_WISTRON_OPTICAL_TOUCH 0x1109
+
+#define USB_VENDOR_ID_X_TENSIONS 0x1ae7
+#define USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE 0x9001
+
+#define USB_VENDOR_ID_XAT 0x2505
+#define USB_DEVICE_ID_XAT_CSR 0x0220
+
+#define USB_VENDOR_ID_XIAOMI 0x2717
+#define USB_DEVICE_ID_MI_SILENT_MOUSE 0x5014
+
+#define USB_VENDOR_ID_XIN_MO 0x16c0
+#define USB_DEVICE_ID_XIN_MO_DUAL_ARCADE 0x05e1
+#define USB_DEVICE_ID_THT_2P_ARCADE 0x75e1
+
+#define USB_VENDOR_ID_XIROKU 0x1477
+#define USB_DEVICE_ID_XIROKU_SPX 0x1006
+#define USB_DEVICE_ID_XIROKU_MPX 0x1007
+#define USB_DEVICE_ID_XIROKU_CSR 0x100e
+#define USB_DEVICE_ID_XIROKU_SPX1 0x1021
+#define USB_DEVICE_ID_XIROKU_CSR1 0x1022
+#define USB_DEVICE_ID_XIROKU_MPX1 0x1023
+#define USB_DEVICE_ID_XIROKU_SPX2 0x1024
+#define USB_DEVICE_ID_XIROKU_CSR2 0x1025
+#define USB_DEVICE_ID_XIROKU_MPX2 0x1026
+
+#define USB_VENDOR_ID_YEALINK 0x6993
+#define USB_DEVICE_ID_YEALINK_P1K_P4K_B2K 0xb001
+
+#define USB_VENDOR_ID_ZEROPLUS 0x0c12
+
+#define USB_VENDOR_ID_ZYDACRON 0x13EC
+#define USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL 0x0006
+
+#define USB_VENDOR_ID_ZYTRONIC 0x14c8
+#define USB_DEVICE_ID_ZYTRONIC_ZXY100 0x0005
+
+#define USB_VENDOR_ID_PRIMAX 0x0461
+#define USB_DEVICE_ID_PRIMAX_MOUSE_4D22 0x4d22
+#define USB_DEVICE_ID_PRIMAX_MOUSE_4E2A 0x4e2a
+#define USB_DEVICE_ID_PRIMAX_KEYBOARD 0x4e05
+#define USB_DEVICE_ID_PRIMAX_REZEL 0x4e72
+#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D0F 0x4d0f
+#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D65 0x4d65
+#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4E22 0x4e22
+
+#define USB_VENDOR_ID_RISO_KAGAKU 0x1294 /* Riso Kagaku Corp. */
+#define USB_DEVICE_ID_RI_KA_WEBMAIL 0x1320 /* Webmail Notifier */
+
+#define USB_VENDOR_ID_MULTIPLE_1781 0x1781
+#define USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD 0x0a9d
+#define USB_DEVICE_ID_PHOENIXRC 0x0898
+
+#define USB_VENDOR_ID_DRACAL_RAPHNET 0x289b
+#define USB_DEVICE_ID_RAPHNET_2NES2SNES 0x0002
+#define USB_DEVICE_ID_RAPHNET_4NES4SNES 0x0003
+
+#define USB_VENDOR_ID_UGTIZER 0x2179
+#define USB_DEVICE_ID_UGTIZER_TABLET_GP0610 0x0053
+#define USB_DEVICE_ID_UGTIZER_TABLET_GT5040 0x0077
+#define USB_DEVICE_ID_UGTIZER_TABLET_WP5540 0x0004
+
+#define USB_VENDOR_ID_VIEWSONIC 0x0543
+#define USB_DEVICE_ID_VIEWSONIC_PD1011 0xe621
+
+#define USB_VENDOR_ID_SIGNOTEC 0x2133
+#define USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011 0x0018
+
+#define USB_VENDOR_ID_JIELI_SDK_DEFAULT 0x4c4a
+#define USB_DEVICE_ID_JIELI_SDK_4155 0x4155
+
+#endif
--
2.53.0
^ permalink raw reply related
* [PATCH 2/3] hid-apple: add pm path to 8102
From: deqrocks @ 2026-04-03 13:06 UTC (permalink / raw)
To: jikos, benjamin.tissoires; +Cc: linux-input
In-Reply-To: <20260403130620.91999-1-andre@negmaster.com>
Add PM handling for the Apple 8102 path so the driver can\nparticipate in suspend and resume on T2 systems.
Signed-off-by: deqrocks <andre@negmaster.com>
---
hid-apple.c | 1328 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1328 insertions(+)
create mode 100644 hid-apple.c
diff --git a/hid-apple.c b/hid-apple.c
new file mode 100644
index 0000000..c620890
--- /dev/null
+++ b/hid-apple.c
@@ -0,0 +1,1328 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * USB HID quirks support for Linux
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ * Copyright (c) 2006-2007 Jiri Kosina
+ * Copyright (c) 2008 Jiri Slaby <jirislaby@gmail.com>
+ * Copyright (c) 2019 Paul Pawlowski <paul@mrarm.io>
+ * Copyright (c) 2023 Orlando Chamberlain <orlandoch.dev@gmail.com>
+ * Copyright (c) 2024 Aditya Garg <gargaditya08@live.com>
+ */
+
+/*
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/leds.h>
+#include <dt-bindings/leds/common.h>
+
+#include "hid-ids.h"
+
+#define APPLE_RDESC_JIS BIT(0)
+#define APPLE_IGNORE_MOUSE BIT(1)
+#define APPLE_HAS_FN BIT(2)
+/* BIT(3) reserved, was: APPLE_HIDDEV */
+#define APPLE_ISO_TILDE_QUIRK BIT(4)
+#define APPLE_MIGHTYMOUSE BIT(5)
+#define APPLE_INVERT_HWHEEL BIT(6)
+/* BIT(7) reserved, was: APPLE_IGNORE_HIDINPUT */
+#define APPLE_NUMLOCK_EMULATION BIT(8)
+#define APPLE_RDESC_BATTERY BIT(9)
+#define APPLE_BACKLIGHT_CTL BIT(10)
+#define APPLE_IS_NON_APPLE BIT(11)
+#define APPLE_MAGIC_BACKLIGHT BIT(12)
+#define APPLE_DISABLE_FKEYS BIT(13)
+
+#define APPLE_FLAG_FKEY BIT(0)
+#define APPLE_FLAG_TB_FKEY BIT(1)
+
+#define HID_COUNTRY_INTERNATIONAL_ISO 13
+#define APPLE_BATTERY_TIMEOUT_SEC 60
+
+#define HID_USAGE_MAGIC_BL 0xff00000f
+#define APPLE_MAGIC_REPORT_ID_POWER 3
+#define APPLE_MAGIC_REPORT_ID_BRIGHTNESS 1
+
+static unsigned int fnmode = 3;
+module_param(fnmode, uint, 0644);
+MODULE_PARM_DESC(fnmode, "Mode of fn key on Apple keyboards (0 = disabled, "
+ "1 = fkeyslast, 2 = fkeysfirst, [3] = auto, 4 = fkeysdisabled)");
+
+static int iso_layout = -1;
+module_param(iso_layout, int, 0644);
+MODULE_PARM_DESC(iso_layout, "Swap the backtick/tilde and greater-than/less-than keys. "
+ "([-1] = auto, 0 = disabled, 1 = enabled)");
+
+static unsigned int swap_opt_cmd;
+module_param(swap_opt_cmd, uint, 0644);
+MODULE_PARM_DESC(swap_opt_cmd, "Swap the Option (\"Alt\") and Command (\"Flag\") keys. "
+ "(For people who want to keep Windows PC keyboard muscle memory. "
+ "[0] = as-is, Mac layout. 1 = swapped, Windows layout., 2 = swapped, Swap only left side)");
+
+static unsigned int swap_ctrl_cmd;
+module_param(swap_ctrl_cmd, uint, 0644);
+MODULE_PARM_DESC(swap_ctrl_cmd, "Swap the Control (\"Ctrl\") and Command (\"Flag\") keys. "
+ "(For people who are used to Mac shortcuts involving Command instead of Control. "
+ "[0] = No change. 1 = Swapped.)");
+
+static unsigned int swap_fn_leftctrl;
+module_param(swap_fn_leftctrl, uint, 0644);
+MODULE_PARM_DESC(swap_fn_leftctrl, "Swap the Fn and left Control keys. "
+ "(For people who want to keep PC keyboard muscle memory. "
+ "[0] = as-is, Mac layout, 1 = swapped, PC layout)");
+
+struct apple_non_apple_keyboard {
+ char *name;
+};
+
+struct apple_sc_backlight {
+ struct led_classdev cdev;
+ struct hid_device *hdev;
+ u16 current_brightness;
+ u16 saved_brightness;
+};
+
+struct apple_backlight_config_report {
+ u8 report_id;
+ u8 version;
+ u16 backlight_off, backlight_on_min, backlight_on_max;
+};
+
+struct apple_backlight_set_report {
+ u8 report_id;
+ u8 version;
+ u16 backlight;
+ u16 rate;
+};
+
+struct apple_magic_backlight {
+ struct led_classdev cdev;
+ struct hid_report *brightness;
+ struct hid_report *power;
+ u16 current_brightness;
+ u16 saved_brightness;
+};
+
+struct apple_sc {
+ struct hid_device *hdev;
+ unsigned long quirks;
+ unsigned int fn_on;
+ unsigned int fn_found;
+ DECLARE_BITMAP(pressed_numlock, KEY_CNT);
+ struct timer_list battery_timer;
+ struct apple_sc_backlight *backlight;
+ struct apple_magic_backlight *magic_backlight;
+};
+
+struct apple_key_translation {
+ u16 from;
+ u16 to;
+ unsigned long flags;
+};
+
+static const struct apple_key_translation magic_keyboard_alu_fn_keys[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
+ { KEY_F6, KEY_NUMLOCK, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation magic_keyboard_2015_fn_keys[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation magic_keyboard_2021_and_2024_fn_keys[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_SEARCH, APPLE_FLAG_FKEY },
+ { KEY_F5, KEY_MICMUTE, APPLE_FLAG_FKEY },
+ { KEY_F6, KEY_SLEEP, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation macbookair_fn_keys[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
+ { KEY_F6, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_NEXTSONG, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F11, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_F12, KEY_EJECTCD, APPLE_FLAG_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation macbookpro_no_esc_fn_keys[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_GRAVE, KEY_ESC, APPLE_FLAG_TB_FKEY },
+ { KEY_1, KEY_F1, APPLE_FLAG_TB_FKEY },
+ { KEY_2, KEY_F2, APPLE_FLAG_TB_FKEY },
+ { KEY_3, KEY_F3, APPLE_FLAG_TB_FKEY },
+ { KEY_4, KEY_F4, APPLE_FLAG_TB_FKEY },
+ { KEY_5, KEY_F5, APPLE_FLAG_TB_FKEY },
+ { KEY_6, KEY_F6, APPLE_FLAG_TB_FKEY },
+ { KEY_7, KEY_F7, APPLE_FLAG_TB_FKEY },
+ { KEY_8, KEY_F8, APPLE_FLAG_TB_FKEY },
+ { KEY_9, KEY_F9, APPLE_FLAG_TB_FKEY },
+ { KEY_0, KEY_F10, APPLE_FLAG_TB_FKEY },
+ { KEY_MINUS, KEY_F11, APPLE_FLAG_TB_FKEY },
+ { KEY_EQUAL, KEY_F12, APPLE_FLAG_TB_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation macbookpro_dedicated_esc_fn_keys[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_1, KEY_F1, APPLE_FLAG_TB_FKEY },
+ { KEY_2, KEY_F2, APPLE_FLAG_TB_FKEY },
+ { KEY_3, KEY_F3, APPLE_FLAG_TB_FKEY },
+ { KEY_4, KEY_F4, APPLE_FLAG_TB_FKEY },
+ { KEY_5, KEY_F5, APPLE_FLAG_TB_FKEY },
+ { KEY_6, KEY_F6, APPLE_FLAG_TB_FKEY },
+ { KEY_7, KEY_F7, APPLE_FLAG_TB_FKEY },
+ { KEY_8, KEY_F8, APPLE_FLAG_TB_FKEY },
+ { KEY_9, KEY_F9, APPLE_FLAG_TB_FKEY },
+ { KEY_0, KEY_F10, APPLE_FLAG_TB_FKEY },
+ { KEY_MINUS, KEY_F11, APPLE_FLAG_TB_FKEY },
+ { KEY_EQUAL, KEY_F12, APPLE_FLAG_TB_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation apple_fn_keys[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
+ { KEY_F5, KEY_KBDILLUMDOWN, APPLE_FLAG_FKEY },
+ { KEY_F6, KEY_KBDILLUMUP, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation powerbook_fn_keys[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F5, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_F6, KEY_NUMLOCK, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_SWITCHVIDEOMODE, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_KBDILLUMTOGGLE, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_KBDILLUMDOWN, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_KBDILLUMUP, APPLE_FLAG_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation powerbook_numlock_keys[] = {
+ { KEY_J, KEY_KP1 },
+ { KEY_K, KEY_KP2 },
+ { KEY_L, KEY_KP3 },
+ { KEY_U, KEY_KP4 },
+ { KEY_I, KEY_KP5 },
+ { KEY_O, KEY_KP6 },
+ { KEY_7, KEY_KP7 },
+ { KEY_8, KEY_KP8 },
+ { KEY_9, KEY_KP9 },
+ { KEY_M, KEY_KP0 },
+ { KEY_DOT, KEY_KPDOT },
+ { KEY_SLASH, KEY_KPPLUS },
+ { KEY_SEMICOLON, KEY_KPMINUS },
+ { KEY_P, KEY_KPASTERISK },
+ { KEY_MINUS, KEY_KPEQUAL },
+ { KEY_0, KEY_KPSLASH },
+ { KEY_F6, KEY_NUMLOCK },
+ { KEY_KPENTER, KEY_KPENTER },
+ { KEY_BACKSPACE, KEY_BACKSPACE },
+ { }
+};
+
+static const struct apple_key_translation apple_iso_keyboard[] = {
+ { KEY_GRAVE, KEY_102ND },
+ { KEY_102ND, KEY_GRAVE },
+ { }
+};
+
+static const struct apple_key_translation swapped_option_cmd_keys[] = {
+ { KEY_LEFTALT, KEY_LEFTMETA },
+ { KEY_LEFTMETA, KEY_LEFTALT },
+ { KEY_RIGHTALT, KEY_RIGHTMETA },
+ { KEY_RIGHTMETA, KEY_RIGHTALT },
+ { }
+};
+
+static const struct apple_key_translation swapped_option_cmd_left_keys[] = {
+ { KEY_LEFTALT, KEY_LEFTMETA },
+ { KEY_LEFTMETA, KEY_LEFTALT },
+ { }
+};
+
+static const struct apple_key_translation swapped_ctrl_cmd_keys[] = {
+ { KEY_LEFTCTRL, KEY_LEFTMETA },
+ { KEY_LEFTMETA, KEY_LEFTCTRL },
+ { KEY_RIGHTCTRL, KEY_RIGHTMETA },
+ { KEY_RIGHTMETA, KEY_RIGHTCTRL },
+ { }
+};
+
+static const struct apple_key_translation swapped_fn_leftctrl_keys[] = {
+ { KEY_FN, KEY_LEFTCTRL },
+ { KEY_LEFTCTRL, KEY_FN },
+ { }
+};
+
+static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
+ { "SONiX KN85 Keyboard" },
+ { "SONiX USB DEVICE" },
+ { "SONiX AK870 PRO" },
+ { "Keychron" },
+ { "AONE" },
+ { "GANSS" },
+ { "Hailuck" },
+ { "Jamesdonkey" },
+ { "A3R" },
+ { "hfd.cn" },
+ { "WKB603" },
+};
+
+static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(non_apple_keyboards); i++) {
+ char *non_apple = non_apple_keyboards[i].name;
+
+ if (strncmp(hdev->name, non_apple, strlen(non_apple)) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+static bool apple_is_omoton_kb066(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI &&
+ strcmp(hdev->name, "Bluetooth Keyboard") == 0;
+}
+
+static inline void apple_setup_key_translation(struct input_dev *input,
+ const struct apple_key_translation *table)
+{
+ const struct apple_key_translation *trans;
+
+ for (trans = table; trans->from; trans++)
+ set_bit(trans->to, input->keybit);
+}
+
+static const struct apple_key_translation *apple_find_translation(
+ const struct apple_key_translation *table, u16 from)
+{
+ const struct apple_key_translation *trans;
+
+ /* Look for the translation */
+ for (trans = table; trans->from; trans++)
+ if (trans->from == from)
+ return trans;
+
+ return NULL;
+}
+
+static void input_event_with_scancode(struct input_dev *input,
+ __u8 type, __u16 code, unsigned int hid, __s32 value)
+{
+ if (type == EV_KEY &&
+ (!test_bit(code, input->key)) == value)
+ input_event(input, EV_MSC, MSC_SCAN, hid);
+ input_event(input, type, code, value);
+}
+
+static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
+ struct hid_usage *usage, __s32 value)
+{
+ struct apple_sc *asc = hid_get_drvdata(hid);
+ const struct apple_key_translation *trans, *table;
+ bool do_translate;
+ u16 code = usage->code;
+ unsigned int real_fnmode;
+
+ if (fnmode == 3) {
+ if (asc->quirks & APPLE_DISABLE_FKEYS)
+ real_fnmode = 4;
+ else if (asc->quirks & APPLE_IS_NON_APPLE)
+ real_fnmode = 2;
+ else
+ real_fnmode = 1;
+ } else {
+ real_fnmode = fnmode;
+ }
+
+ if (swap_fn_leftctrl) {
+ trans = apple_find_translation(swapped_fn_leftctrl_keys, code);
+
+ if (trans)
+ code = trans->to;
+ }
+
+ if (iso_layout > 0 || (iso_layout < 0 && (asc->quirks & APPLE_ISO_TILDE_QUIRK) &&
+ hid->country == HID_COUNTRY_INTERNATIONAL_ISO)) {
+ trans = apple_find_translation(apple_iso_keyboard, code);
+
+ if (trans)
+ code = trans->to;
+ }
+
+ if (swap_opt_cmd) {
+ if (swap_opt_cmd == 2)
+ trans = apple_find_translation(swapped_option_cmd_left_keys, code);
+ else
+ trans = apple_find_translation(swapped_option_cmd_keys, code);
+
+ if (trans)
+ code = trans->to;
+ }
+
+ if (swap_ctrl_cmd) {
+ trans = apple_find_translation(swapped_ctrl_cmd_keys, code);
+
+ if (trans)
+ code = trans->to;
+ }
+
+ if (code == KEY_FN)
+ asc->fn_on = !!value;
+
+ if (real_fnmode) {
+ switch (hid->product) {
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS:
+ table = magic_keyboard_alu_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015:
+ table = magic_keyboard_2015_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2024:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2024:
+ table = magic_keyboard_2021_and_2024_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT:
+ table = macbookpro_no_esc_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223:
+ table = macbookpro_dedicated_esc_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K:
+ table = apple_fn_keys;
+ break;
+ default:
+ if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
+ hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
+ table = macbookair_fn_keys;
+ else if (hid->product < 0x21d || hid->product >= 0x300)
+ table = powerbook_fn_keys;
+ else if (hid->bus == BUS_HOST || hid->bus == BUS_SPI)
+ switch (hid->product) {
+ case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020:
+ case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022:
+ table = macbookpro_dedicated_esc_fn_keys;
+ break;
+ default:
+ table = magic_keyboard_2021_and_2024_fn_keys;
+ break;
+ }
+ else
+ table = apple_fn_keys;
+ }
+
+ trans = apple_find_translation(table, code);
+
+ if (trans) {
+ bool from_is_set = test_bit(trans->from, input->key);
+ bool to_is_set = test_bit(trans->to, input->key);
+
+ if (from_is_set)
+ code = trans->from;
+ else if (to_is_set)
+ code = trans->to;
+
+ if (!(from_is_set || to_is_set)) {
+ if (trans->flags & APPLE_FLAG_FKEY) {
+ switch (real_fnmode) {
+ case 1:
+ do_translate = !asc->fn_on;
+ break;
+ case 2:
+ do_translate = asc->fn_on;
+ break;
+ default:
+ /* case 4 */
+ do_translate = false;
+ }
+ } else if (trans->flags & APPLE_FLAG_TB_FKEY) {
+ switch (real_fnmode) {
+ case 4:
+ do_translate = false;
+ break;
+ default:
+ do_translate = asc->fn_on;
+ }
+ } else {
+ do_translate = asc->fn_on;
+ }
+
+ if (do_translate)
+ code = trans->to;
+ }
+ }
+
+ if (asc->quirks & APPLE_NUMLOCK_EMULATION &&
+ (test_bit(code, asc->pressed_numlock) ||
+ test_bit(LED_NUML, input->led))) {
+ trans = apple_find_translation(powerbook_numlock_keys, code);
+
+ if (trans) {
+ if (value)
+ set_bit(code, asc->pressed_numlock);
+ else
+ clear_bit(code, asc->pressed_numlock);
+
+ code = trans->to;
+ }
+ }
+ }
+
+ if (usage->code != code) {
+ input_event_with_scancode(input, usage->type, code, usage->hid, value);
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static int apple_event(struct hid_device *hdev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+
+ if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
+ !usage->type)
+ return 0;
+
+ if ((asc->quirks & APPLE_INVERT_HWHEEL) &&
+ usage->code == REL_HWHEEL) {
+ input_event_with_scancode(field->hidinput->input, usage->type,
+ usage->code, usage->hid, -value);
+ return 1;
+ }
+
+ if ((asc->quirks & APPLE_HAS_FN) &&
+ hidinput_apple_event(hdev, field->hidinput->input,
+ usage, value))
+ return 1;
+
+
+ return 0;
+}
+
+static int apple_fetch_battery(struct hid_device *hdev)
+{
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+ struct hid_report_enum *report_enum;
+ struct hid_report *report;
+
+ if (!(asc->quirks & APPLE_RDESC_BATTERY) || !hdev->battery)
+ return -1;
+
+ report_enum = &hdev->report_enum[hdev->battery_report_type];
+ report = report_enum->report_id_hash[hdev->battery_report_id];
+
+ if (!report || report->maxfield < 1)
+ return -1;
+
+ if (hdev->battery_capacity == hdev->battery_max)
+ return -1;
+
+ hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+static void apple_battery_timer_tick(struct timer_list *t)
+{
+ struct apple_sc *asc = timer_container_of(asc, t, battery_timer);
+ struct hid_device *hdev = asc->hdev;
+
+ if (apple_fetch_battery(hdev) == 0) {
+ mod_timer(&asc->battery_timer,
+ jiffies + secs_to_jiffies(APPLE_BATTERY_TIMEOUT_SEC));
+ }
+}
+
+/*
+ * MacBook JIS keyboard has wrong logical maximum
+ * Magic Keyboard JIS has wrong logical maximum
+ */
+static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+
+ if (*rsize >= 71 && rdesc[70] == 0x65 && rdesc[64] == 0x65) {
+ hid_info(hdev,
+ "fixing up Magic Keyboard JIS report descriptor\n");
+ rdesc[64] = rdesc[70] = 0xe7;
+ }
+
+ if ((asc->quirks & APPLE_RDESC_JIS) && *rsize >= 60 &&
+ rdesc[53] == 0x65 && rdesc[59] == 0x65) {
+ hid_info(hdev,
+ "fixing up MacBook JIS keyboard report descriptor\n");
+ rdesc[53] = rdesc[59] = 0xe7;
+ }
+
+ /*
+ * Change the usage from:
+ * 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 0
+ * 0x09, 0x0b, // Usage (Vendor Usage 0x0b) 3
+ * To:
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * 0x09, 0x06, // Usage (Keyboard) 2
+ */
+ if ((asc->quirks & APPLE_RDESC_BATTERY) && *rsize == 83 &&
+ rdesc[46] == 0x84 && rdesc[58] == 0x85) {
+ hid_info(hdev,
+ "fixing up Magic Keyboard battery report descriptor\n");
+ *rsize = *rsize - 1;
+ rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
+ if (!rdesc)
+ return NULL;
+
+ rdesc[0] = 0x05;
+ rdesc[1] = 0x01;
+ rdesc[2] = 0x09;
+ rdesc[3] = 0x06;
+ }
+
+ return rdesc;
+}
+
+static void apple_setup_input(struct input_dev *input)
+{
+ set_bit(KEY_NUMLOCK, input->keybit);
+
+ /* Enable all needed keys */
+ apple_setup_key_translation(input, apple_fn_keys);
+ apple_setup_key_translation(input, powerbook_fn_keys);
+ apple_setup_key_translation(input, powerbook_numlock_keys);
+ apple_setup_key_translation(input, apple_iso_keyboard);
+ apple_setup_key_translation(input, magic_keyboard_alu_fn_keys);
+ apple_setup_key_translation(input, magic_keyboard_2015_fn_keys);
+ apple_setup_key_translation(input, magic_keyboard_2021_and_2024_fn_keys);
+ apple_setup_key_translation(input, macbookpro_no_esc_fn_keys);
+ apple_setup_key_translation(input, macbookpro_dedicated_esc_fn_keys);
+}
+
+static int apple_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+
+ if (usage->hid == (HID_UP_CUSTOM | 0x0003) ||
+ usage->hid == (HID_UP_MSVENDOR | 0x0003) ||
+ usage->hid == (HID_UP_HPVENDOR2 | 0x0003)) {
+ /* The fn key on Apple USB keyboards */
+ set_bit(EV_REP, hi->input->evbit);
+ hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_FN);
+ asc->fn_found = true;
+ apple_setup_input(hi->input);
+ return 1;
+ }
+
+ /* we want the hid layer to go through standard path (set and ignore) */
+ return 0;
+}
+
+static int apple_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+
+ if (asc->quirks & APPLE_MIGHTYMOUSE) {
+ if (usage->hid == HID_GD_Z)
+ hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
+ else if (usage->code == BTN_1)
+ hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_2);
+ else if (usage->code == BTN_2)
+ hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_1);
+ }
+
+ return 0;
+}
+
+static int apple_input_configured(struct hid_device *hdev,
+ struct hid_input *hidinput)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+
+ if (((asc->quirks & APPLE_HAS_FN) && !asc->fn_found) || apple_is_omoton_kb066(hdev)) {
+ hid_info(hdev, "Fn key not found (Apple Wireless Keyboard clone?), disabling Fn key handling\n");
+ asc->quirks &= ~APPLE_HAS_FN;
+ }
+
+ if (apple_is_non_apple_keyboard(hdev)) {
+ hid_info(hdev, "Non-apple keyboard detected; function keys will default to fnmode=2 behavior\n");
+ asc->quirks |= APPLE_IS_NON_APPLE;
+ }
+
+ return 0;
+}
+
+static bool apple_backlight_check_support(struct hid_device *hdev)
+{
+ int i;
+ unsigned int hid;
+ struct hid_report *report;
+
+ list_for_each_entry(report, &hdev->report_enum[HID_INPUT_REPORT].report_list, list) {
+ for (i = 0; i < report->maxfield; i++) {
+ hid = report->field[i]->usage->hid;
+ if ((hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR && (hid & HID_USAGE) == 0xf)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int apple_backlight_set(struct hid_device *hdev, u16 value, u16 rate)
+{
+ int ret = 0;
+ struct apple_backlight_set_report *rep;
+
+ rep = kmalloc(sizeof(*rep), GFP_KERNEL);
+ if (rep == NULL)
+ return -ENOMEM;
+
+ rep->report_id = 0xB0;
+ rep->version = 1;
+ rep->backlight = value;
+ rep->rate = rate;
+
+ ret = hid_hw_raw_request(hdev, 0xB0u, (u8 *) rep, sizeof(*rep),
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+
+ kfree(rep);
+ return ret;
+}
+
+static int apple_backlight_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct apple_sc_backlight *backlight = container_of(led_cdev,
+ struct apple_sc_backlight, cdev);
+ int ret;
+
+ ret = apple_backlight_set(backlight->hdev, brightness, 0);
+ if (!ret)
+ backlight->current_brightness = brightness;
+ return ret;
+}
+
+static int apple_backlight_init(struct hid_device *hdev)
+{
+ int ret;
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+ struct apple_backlight_config_report *rep;
+
+ if (!apple_backlight_check_support(hdev))
+ return -EINVAL;
+
+ rep = kmalloc(0x200, GFP_KERNEL);
+ if (rep == NULL)
+ return -ENOMEM;
+
+ ret = hid_hw_raw_request(hdev, 0xBFu, (u8 *) rep, sizeof(*rep),
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+ if (ret < 0) {
+ hid_err(hdev, "backlight request failed: %d\n", ret);
+ goto cleanup_and_exit;
+ }
+ if (ret < 8 || rep->version != 1) {
+ hid_err(hdev, "backlight config struct: bad version %i\n", rep->version);
+ ret = -EINVAL;
+ goto cleanup_and_exit;
+ }
+
+ hid_dbg(hdev, "backlight config: off=%u, on_min=%u, on_max=%u\n",
+ rep->backlight_off, rep->backlight_on_min, rep->backlight_on_max);
+
+ asc->backlight = devm_kzalloc(&hdev->dev, sizeof(*asc->backlight), GFP_KERNEL);
+ if (!asc->backlight) {
+ ret = -ENOMEM;
+ goto cleanup_and_exit;
+ }
+
+ asc->backlight->hdev = hdev;
+ asc->backlight->cdev.name = "apple::kbd_backlight";
+ asc->backlight->cdev.max_brightness = rep->backlight_on_max;
+ asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set;
+ asc->backlight->current_brightness = 0;
+ asc->backlight->saved_brightness = 0;
+
+ ret = apple_backlight_set(hdev, 0, 0);
+ if (ret < 0) {
+ hid_err(hdev, "backlight set request failed: %d\n", ret);
+ goto cleanup_and_exit;
+ }
+
+ ret = devm_led_classdev_register(&hdev->dev, &asc->backlight->cdev);
+
+cleanup_and_exit:
+ kfree(rep);
+ return ret;
+}
+
+static void apple_magic_backlight_report_set(struct hid_report *rep, s32 value, u8 rate)
+{
+ rep->field[0]->value[0] = value;
+ rep->field[1]->value[0] = 0x5e; /* Mimic Windows */
+ rep->field[1]->value[0] |= rate << 8;
+
+ hid_hw_request(rep->device, rep, HID_REQ_SET_REPORT);
+}
+
+static void apple_magic_backlight_set(struct apple_magic_backlight *backlight,
+ int brightness, char rate)
+{
+ apple_magic_backlight_report_set(backlight->power, brightness ? 1 : 0, rate);
+ if (brightness)
+ apple_magic_backlight_report_set(backlight->brightness, brightness, rate);
+}
+
+static int apple_magic_backlight_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct apple_magic_backlight *backlight = container_of(led_cdev,
+ struct apple_magic_backlight, cdev);
+
+ apple_magic_backlight_set(backlight, brightness, 1);
+ backlight->current_brightness = brightness;
+ return 0;
+}
+
+static int apple_magic_backlight_init(struct hid_device *hdev)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+ struct apple_magic_backlight *backlight;
+ struct hid_report_enum *report_enum;
+
+ /*
+ * Ensure this usb endpoint is for the keyboard backlight, not touchbar
+ * backlight.
+ */
+ if (hdev->collection[0].usage != HID_USAGE_MAGIC_BL)
+ return -ENODEV;
+
+ backlight = devm_kzalloc(&hdev->dev, sizeof(*backlight), GFP_KERNEL);
+ if (!backlight)
+ return -ENOMEM;
+
+ report_enum = &hdev->report_enum[HID_FEATURE_REPORT];
+ backlight->brightness = report_enum->report_id_hash[APPLE_MAGIC_REPORT_ID_BRIGHTNESS];
+ backlight->power = report_enum->report_id_hash[APPLE_MAGIC_REPORT_ID_POWER];
+
+ if (!backlight->brightness || backlight->brightness->maxfield < 2 ||
+ !backlight->power || backlight->power->maxfield < 2)
+ return -ENODEV;
+
+ backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
+ backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum;
+ backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set;
+ backlight->current_brightness = 0;
+ backlight->saved_brightness = 0;
+ asc->magic_backlight = backlight;
+
+ apple_magic_backlight_set(backlight, 0, 0);
+
+ return devm_led_classdev_register(&hdev->dev, &backlight->cdev);
+
+}
+
+static int apple_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ unsigned long quirks = id->driver_data;
+ struct apple_sc *asc;
+ int ret;
+
+ if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE &&
+ hdev->type != HID_TYPE_SPI_KEYBOARD)
+ return -ENODEV;
+
+ if (quirks & APPLE_IGNORE_MOUSE && hdev->type == HID_TYPE_USBMOUSE)
+ return -ENODEV;
+
+ asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL);
+ if (asc == NULL) {
+ hid_err(hdev, "can't alloc apple descriptor\n");
+ return -ENOMEM;
+ }
+
+ asc->hdev = hdev;
+ asc->quirks = quirks;
+
+ hid_set_drvdata(hdev, asc);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ if (quirks & APPLE_RDESC_BATTERY) {
+ timer_setup(&asc->battery_timer, apple_battery_timer_tick, 0);
+ mod_timer(&asc->battery_timer,
+ jiffies + secs_to_jiffies(APPLE_BATTERY_TIMEOUT_SEC));
+ apple_fetch_battery(hdev);
+ }
+
+ if (quirks & APPLE_BACKLIGHT_CTL)
+ apple_backlight_init(hdev);
+
+ if (quirks & APPLE_MAGIC_BACKLIGHT) {
+ ret = apple_magic_backlight_init(hdev);
+ if (ret)
+ goto out_err;
+ }
+
+ return 0;
+
+out_err:
+ if (quirks & APPLE_RDESC_BATTERY)
+ timer_delete_sync(&asc->battery_timer);
+
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void apple_remove(struct hid_device *hdev)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+
+ if (asc->quirks & APPLE_RDESC_BATTERY)
+ timer_delete_sync(&asc->battery_timer);
+
+ hid_hw_stop(hdev);
+}
+
+#ifdef CONFIG_PM
+static int apple_suspend(struct hid_device *hdev, pm_message_t msg)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+
+ if (asc->backlight) {
+ asc->backlight->saved_brightness = asc->backlight->current_brightness;
+ apple_backlight_set(hdev, 0, 0);
+ asc->backlight->current_brightness = 0;
+ }
+
+ if (asc->magic_backlight) {
+ asc->magic_backlight->saved_brightness = asc->magic_backlight->current_brightness;
+ apple_magic_backlight_set(asc->magic_backlight, 0, 0);
+ asc->magic_backlight->current_brightness = 0;
+ }
+
+ return 0;
+}
+
+static int apple_resume(struct hid_device *hdev)
+{
+ struct apple_sc *asc = hid_get_drvdata(hdev);
+ int ret = 0;
+
+ if (asc->backlight && asc->backlight->saved_brightness) {
+ ret = apple_backlight_set(hdev, asc->backlight->saved_brightness, 0);
+ if (!ret)
+ asc->backlight->current_brightness = asc->backlight->saved_brightness;
+ }
+
+ if (asc->magic_backlight && asc->magic_backlight->saved_brightness) {
+ apple_magic_backlight_set(asc->magic_backlight,
+ asc->magic_backlight->saved_brightness, 0);
+ asc->magic_backlight->current_brightness = asc->magic_backlight->saved_brightness;
+ }
+
+ return ret;
+}
+#endif
+
+static const struct hid_device_id apple_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE),
+ .driver_data = APPLE_MIGHTYMOUSE | APPLE_INVERT_HWHEEL },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_JIS),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ANSI),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_JIS),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ANSI),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ISO),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_JIS),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ISO),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_JIS),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ISO),
+ .driver_data = APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ISO),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_JIS),
+ .driver_data = APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_ISO_TILDE_QUIRK },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_ISO_TILDE_QUIRK },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+ USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+ USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS),
+ .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K),
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_IGNORE_MOUSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132),
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_DISABLE_FKEYS | APPLE_IGNORE_MOUSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680),
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_DISABLE_FKEYS | APPLE_IGNORE_MOUSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT),
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_DISABLE_FKEYS | APPLE_IGNORE_MOUSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213),
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_DISABLE_FKEYS | APPLE_IGNORE_MOUSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_DISABLE_FKEYS |
+ APPLE_IGNORE_MOUSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_DISABLE_FKEYS |
+ APPLE_IGNORE_MOUSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_IGNORE_MOUSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_DISABLE_FKEYS |
+ APPLE_IGNORE_MOUSE },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_ISO_TILDE_QUIRK },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT),
+ .driver_data = APPLE_MAGIC_BACKLIGHT },
+
+ { }
+};
+MODULE_DEVICE_TABLE(hid, apple_devices);
+
+static struct hid_driver apple_driver = {
+ .name = "apple",
+ .id_table = apple_devices,
+ .report_fixup = apple_report_fixup,
+ .probe = apple_probe,
+ .remove = apple_remove,
+ .event = apple_event,
+ .input_mapping = apple_input_mapping,
+ .input_mapped = apple_input_mapped,
+ .input_configured = apple_input_configured,
+#ifdef CONFIG_PM
+ .suspend = apple_suspend,
+ .resume = apple_resume,
+ .reset_resume = apple_resume,
+#endif
+};
+module_hid_driver(apple_driver);
+
+MODULE_DESCRIPTION("Apple USB HID quirks support for Linux");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH 3/3] Add Touch Bar and backlight reprobe support
From: deqrocks @ 2026-04-03 13:06 UTC (permalink / raw)
To: jikos, benjamin.tissoires; +Cc: linux-input
In-Reply-To: <20260403130620.91999-1-andre@negmaster.com>
Drop stale device state across suspend and let the Touch Bar\nrelated drivers tear down and reinitialize through reprobe after\nresume.
Signed-off-by: deqrocks <andre@negmaster.com>
---
hid-apple.c | 50 +++++++++++++++++++--
hid-appletb-bl.c | 44 ++++++++++++++++++-
hid-appletb-kbd.c | 109 ++++++++++++++++++++++++++++++++++++----------
3 files changed, 174 insertions(+), 29 deletions(-)
diff --git a/hid-apple.c b/hid-apple.c
index c620890..dc9d244 100644
--- a/hid-apple.c
+++ b/hid-apple.c
@@ -114,6 +114,15 @@ struct apple_magic_backlight {
u16 saved_brightness;
};
+static u16 apple_saved_kbd_backlight_brightness;
+
+static u16 apple_initial_kbd_backlight_brightness(u16 max_brightness)
+{
+ if (apple_saved_kbd_backlight_brightness)
+ return apple_saved_kbd_backlight_brightness;
+ return max_t(u16, max_brightness / 2, 1);
+}
+
struct apple_sc {
struct hid_device *hdev;
unsigned long quirks;
@@ -123,6 +132,7 @@ struct apple_sc {
struct timer_list battery_timer;
struct apple_sc_backlight *backlight;
struct apple_magic_backlight *magic_backlight;
+ bool suspend_preparing_remove;
};
struct apple_key_translation {
@@ -833,8 +843,10 @@ static int apple_backlight_led_set(struct led_classdev *led_cdev,
int ret;
ret = apple_backlight_set(backlight->hdev, brightness, 0);
- if (!ret)
+ if (!ret) {
backlight->current_brightness = brightness;
+ apple_saved_kbd_backlight_brightness = brightness;
+ }
return ret;
}
@@ -878,12 +890,19 @@ static int apple_backlight_init(struct hid_device *hdev)
asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set;
asc->backlight->current_brightness = 0;
asc->backlight->saved_brightness = 0;
+ asc->backlight->cdev.brightness = 0;
+
+ asc->backlight->cdev.brightness =
+ apple_initial_kbd_backlight_brightness(rep->backlight_on_max);
- ret = apple_backlight_set(hdev, 0, 0);
+ ret = apple_backlight_set(hdev, asc->backlight->cdev.brightness, 0);
if (ret < 0) {
hid_err(hdev, "backlight set request failed: %d\n", ret);
goto cleanup_and_exit;
}
+ asc->backlight->current_brightness = asc->backlight->cdev.brightness;
+ asc->backlight->saved_brightness = asc->backlight->current_brightness;
+ apple_saved_kbd_backlight_brightness = asc->backlight->current_brightness;
ret = devm_led_classdev_register(&hdev->dev, &asc->backlight->cdev);
@@ -917,6 +936,7 @@ static int apple_magic_backlight_led_set(struct led_classdev *led_cdev,
apple_magic_backlight_set(backlight, brightness, 1);
backlight->current_brightness = brightness;
+ apple_saved_kbd_backlight_brightness = brightness;
return 0;
}
@@ -950,9 +970,16 @@ static int apple_magic_backlight_init(struct hid_device *hdev)
backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set;
backlight->current_brightness = 0;
backlight->saved_brightness = 0;
+ backlight->cdev.brightness = 0;
asc->magic_backlight = backlight;
- apple_magic_backlight_set(backlight, 0, 0);
+ backlight->cdev.brightness =
+ apple_initial_kbd_backlight_brightness(backlight->cdev.max_brightness);
+ backlight->current_brightness = backlight->cdev.brightness;
+ backlight->saved_brightness = backlight->current_brightness;
+ apple_saved_kbd_backlight_brightness = backlight->current_brightness;
+
+ apple_magic_backlight_set(backlight, backlight->current_brightness, 0);
return devm_led_classdev_register(&hdev->dev, &backlight->cdev);
@@ -1028,6 +1055,17 @@ static void apple_remove(struct hid_device *hdev)
if (asc->quirks & APPLE_RDESC_BATTERY)
timer_delete_sync(&asc->battery_timer);
+ /* Only tear down LEDs on suspend-driven remove. */
+ if (asc->suspend_preparing_remove && asc->backlight) {
+ devm_led_classdev_unregister(&hdev->dev, &asc->backlight->cdev);
+ asc->backlight = NULL;
+ }
+
+ if (asc->suspend_preparing_remove && asc->magic_backlight) {
+ devm_led_classdev_unregister(&hdev->dev, &asc->magic_backlight->cdev);
+ asc->magic_backlight = NULL;
+ }
+
hid_hw_stop(hdev);
}
@@ -1036,14 +1074,18 @@ static int apple_suspend(struct hid_device *hdev, pm_message_t msg)
{
struct apple_sc *asc = hid_get_drvdata(hdev);
+ asc->suspend_preparing_remove = true;
+
if (asc->backlight) {
asc->backlight->saved_brightness = asc->backlight->current_brightness;
+ apple_saved_kbd_backlight_brightness = asc->backlight->current_brightness;
apple_backlight_set(hdev, 0, 0);
asc->backlight->current_brightness = 0;
}
if (asc->magic_backlight) {
asc->magic_backlight->saved_brightness = asc->magic_backlight->current_brightness;
+ apple_saved_kbd_backlight_brightness = asc->magic_backlight->current_brightness;
apple_magic_backlight_set(asc->magic_backlight, 0, 0);
asc->magic_backlight->current_brightness = 0;
}
@@ -1056,6 +1098,8 @@ static int apple_resume(struct hid_device *hdev)
struct apple_sc *asc = hid_get_drvdata(hdev);
int ret = 0;
+ asc->suspend_preparing_remove = false;
+
if (asc->backlight && asc->backlight->saved_brightness) {
ret = apple_backlight_set(hdev, asc->backlight->saved_brightness, 0);
if (!ret)
diff --git a/hid-appletb-bl.c b/hid-appletb-bl.c
index bad2aea..5fc5a00 100644
--- a/hid-appletb-bl.c
+++ b/hid-appletb-bl.c
@@ -36,6 +36,7 @@ struct appletb_bl {
struct backlight_device *bdev;
bool full_on;
+ bool suspend_preparing_remove;
};
static const u8 appletb_bl_brightness_map[] = {
@@ -44,6 +45,15 @@ static const u8 appletb_bl_brightness_map[] = {
APPLETB_BL_ON,
};
+static int appletb_bl_default_brightness_index(void)
+{
+ if (appletb_bl_def_brightness < 0)
+ return 0;
+ if (appletb_bl_def_brightness >= ARRAY_SIZE(appletb_bl_brightness_map))
+ return ARRAY_SIZE(appletb_bl_brightness_map) - 1;
+ return appletb_bl_def_brightness;
+}
+
static int appletb_bl_set_brightness(struct appletb_bl *bl, u8 brightness)
{
struct hid_report *report = bl->brightness_field->report;
@@ -142,7 +152,7 @@ static int appletb_bl_probe(struct hid_device *hdev, const struct hid_device_id
bl->brightness_field = brightness_field;
ret = appletb_bl_set_brightness(bl,
- appletb_bl_brightness_map[(appletb_bl_def_brightness > 2) ? 2 : appletb_bl_def_brightness]);
+ appletb_bl_brightness_map[appletb_bl_default_brightness_index()]);
if (ret) {
dev_err_probe(dev, ret, "Failed to set default touch bar brightness to %d\n",
@@ -177,12 +187,39 @@ static void appletb_bl_remove(struct hid_device *hdev)
{
struct appletb_bl *bl = hid_get_drvdata(hdev);
- appletb_bl_set_brightness(bl, APPLETB_BL_OFF);
+ /* Only tear down the backlight on suspend-driven remove. */
+ if (bl && bl->suspend_preparing_remove)
+ appletb_bl_set_brightness(bl, APPLETB_BL_OFF);
+
+ if (bl && bl->suspend_preparing_remove && bl->bdev) {
+ devm_backlight_device_unregister(&hdev->dev, bl->bdev);
+ bl->bdev = NULL;
+ }
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
+static int appletb_bl_suspend(struct hid_device *hdev, pm_message_t msg)
+{
+ struct appletb_bl *bl = hid_get_drvdata(hdev);
+
+ if (bl)
+ bl->suspend_preparing_remove = true;
+
+ return 0;
+}
+
+static int appletb_bl_resume(struct hid_device *hdev)
+{
+ struct appletb_bl *bl = hid_get_drvdata(hdev);
+
+ if (bl)
+ bl->suspend_preparing_remove = false;
+
+ return 0;
+}
+
static const struct hid_device_id appletb_bl_hid_ids[] = {
/* MacBook Pro's 2018, 2019, with T2 chip: iBridge DFR Brightness */
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
@@ -195,6 +232,9 @@ static struct hid_driver appletb_bl_hid_driver = {
.id_table = appletb_bl_hid_ids,
.probe = appletb_bl_probe,
.remove = appletb_bl_remove,
+ .suspend = pm_ptr(appletb_bl_suspend),
+ .resume = pm_ptr(appletb_bl_resume),
+ .reset_resume = pm_ptr(appletb_bl_resume),
};
module_hid_driver(appletb_bl_hid_driver);
diff --git a/hid-appletb-kbd.c b/hid-appletb-kbd.c
index 0fdc096..63238ab 100644
--- a/hid-appletb-kbd.c
+++ b/hid-appletb-kbd.c
@@ -65,10 +65,25 @@ struct appletb_kbd {
struct timer_list inactivity_timer;
bool has_dimmed;
bool has_turned_off;
+ bool suspend_preparing_remove;
+ bool fn_down;
+ unsigned long last_mode_jiffies;
u8 saved_mode;
u8 current_mode;
};
+#define APPLETB_MODE_SUBMIT_INTERVAL_MS 50
+
+static void appletb_kbd_reset_state(struct appletb_kbd *kbd)
+{
+ kbd->saved_mode = APPLETB_KBD_MODE_OFF;
+ kbd->current_mode = APPLETB_KBD_MODE_OFF;
+ kbd->has_dimmed = false;
+ kbd->has_turned_off = false;
+ kbd->fn_down = false;
+ kbd->last_mode_jiffies = 0;
+}
+
static const struct key_entry appletb_kbd_keymap[] = {
{ KE_KEY, KEY_ESC, { KEY_ESC } },
{ KE_KEY, KEY_F1, { KEY_BRIGHTNESSDOWN } },
@@ -92,6 +107,18 @@ static int appletb_kbd_set_mode(struct appletb_kbd *kbd, u8 mode)
struct hid_device *hdev = report->device;
int ret;
+ if (kbd->suspend_preparing_remove && mode != APPLETB_KBD_MODE_OFF)
+ return -EBUSY;
+
+ if (kbd->current_mode == mode)
+ return 0;
+
+ if (kbd->last_mode_jiffies &&
+ time_before(jiffies, kbd->last_mode_jiffies +
+ msecs_to_jiffies(APPLETB_MODE_SUBMIT_INTERVAL_MS))) {
+ return -EAGAIN;
+ }
+
ret = hid_hw_power(hdev, PM_HINT_FULLON);
if (ret) {
hid_err(hdev, "Device didn't resume (%pe)\n", ERR_PTR(ret));
@@ -105,8 +132,8 @@ static int appletb_kbd_set_mode(struct appletb_kbd *kbd, u8 mode)
}
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
-
kbd->current_mode = mode;
+ kbd->last_mode_jiffies = jiffies;
power_normal:
hid_hw_power(hdev, PM_HINT_NORMAL);
@@ -114,6 +141,22 @@ power_normal:
return ret;
}
+static void appletb_kbd_teardown(struct hid_device *hdev, struct appletb_kbd *kbd,
+ bool send_mode_off)
+{
+ if (send_mode_off)
+ appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
+
+ input_unregister_handler(&kbd->inp_handler);
+ if (kbd->backlight_dev) {
+ put_device(&kbd->backlight_dev->dev);
+ kbd->backlight_dev = NULL;
+ timer_delete_sync(&kbd->inactivity_timer);
+ }
+
+ appletb_kbd_reset_state(kbd);
+}
+
static ssize_t mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -238,18 +281,35 @@ static void appletb_kbd_inp_event(struct input_handle *handle, unsigned int type
reset_inactivity_timer(kbd);
- if (type == EV_KEY && code == KEY_FN && appletb_tb_fn_toggle &&
- (kbd->current_mode == APPLETB_KBD_MODE_SPCL ||
- kbd->current_mode == APPLETB_KBD_MODE_FN)) {
- if (value == 1) {
- kbd->saved_mode = kbd->current_mode;
- appletb_kbd_set_mode(kbd, kbd->current_mode == APPLETB_KBD_MODE_SPCL
- ? APPLETB_KBD_MODE_FN : APPLETB_KBD_MODE_SPCL);
- } else if (value == 0) {
- if (kbd->saved_mode != kbd->current_mode)
- appletb_kbd_set_mode(kbd, kbd->saved_mode);
- }
+ if (type != EV_KEY || code != KEY_FN || !appletb_tb_fn_toggle)
+ return;
+
+ if (kbd->current_mode != APPLETB_KBD_MODE_SPCL &&
+ kbd->current_mode != APPLETB_KBD_MODE_FN)
+ return;
+
+ if (value == 2)
+ return;
+
+ if (value == 1) {
+ if (kbd->fn_down)
+ return;
+
+ kbd->fn_down = true;
+ kbd->saved_mode = kbd->current_mode;
+ appletb_kbd_set_mode(kbd,
+ kbd->current_mode == APPLETB_KBD_MODE_SPCL
+ ? APPLETB_KBD_MODE_FN
+ : APPLETB_KBD_MODE_SPCL);
+ return;
}
+
+ if (value != 0 || !kbd->fn_down)
+ return;
+
+ kbd->fn_down = false;
+ if (kbd->saved_mode != kbd->current_mode)
+ appletb_kbd_set_mode(kbd, kbd->saved_mode);
}
static int appletb_kbd_inp_connect(struct input_handler *handler,
@@ -392,6 +452,8 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id
return -ENOMEM;
kbd->mode_field = mode_field;
+ kbd->suspend_preparing_remove = false;
+ appletb_kbd_reset_state(kbd);
ret = hid_hw_start(hdev, HID_CONNECT_HIDINPUT);
if (ret)
@@ -405,7 +467,9 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id
kbd->backlight_dev = backlight_device_get_by_name("appletb_backlight");
if (!kbd->backlight_dev) {
- dev_err_probe(dev, -ENODEV, "Failed to get backlight device\n");
+ ret = dev_err_probe(dev, -EPROBE_DEFER,
+ "Backlight device not ready, deferring probe\n");
+ goto close_hw;
} else {
backlight_device_set_brightness(kbd->backlight_dev, 2);
timer_setup(&kbd->inactivity_timer, appletb_inactivity_timer, 0);
@@ -453,14 +517,14 @@ stop_hw:
static void appletb_kbd_remove(struct hid_device *hdev)
{
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
+ bool send_mode_off = false;
- appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
+ /* Only force MODE_OFF on suspend-driven remove. */
+ if (kbd)
+ send_mode_off = kbd->suspend_preparing_remove &&
+ kbd->current_mode != APPLETB_KBD_MODE_OFF;
- input_unregister_handler(&kbd->inp_handler);
- if (kbd->backlight_dev) {
- put_device(&kbd->backlight_dev->dev);
- timer_delete_sync(&kbd->inactivity_timer);
- }
+ appletb_kbd_teardown(hdev, kbd, send_mode_off);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -470,9 +534,7 @@ static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg)
{
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
- kbd->saved_mode = kbd->current_mode;
- appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
-
+ kbd->suspend_preparing_remove = true;
return 0;
}
@@ -480,8 +542,7 @@ static int appletb_kbd_resume(struct hid_device *hdev)
{
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
- appletb_kbd_set_mode(kbd, kbd->saved_mode);
-
+ kbd->suspend_preparing_remove = false;
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH 0/3] HID: apple: reinitialize T2 HID devices after resume
From: deqrocks @ 2026-04-03 13:06 UTC (permalink / raw)
To: jikos, benjamin.tissoires; +Cc: linux-input
This series improves suspend and resume handling for Apple T2 based Macs.
On affected systems, Apple T2 HID-backed devices can disappear across suspend and resume and come back with freshly enumerated interfaces. Reusing the pre-suspend device state leaves parts of the stack non-functional after resume, especially keyboard backlight and Touch Bar related devices.
This series adds the required Apple T2 HID identifiers, wires up PM handling for the relevant Apple HID path, and reworks teardown and reprobe handling so stale state is discarded and devices are initialized again after resume.
Tested on: MacBookAir9,1, MacBookPro15,1, MacBookPro16,1, MacBookPro16,2, MacBookPro16,4
deqrocks (3):
Add Apple T2 HID identifiers
hid-apple: add pm path to 8102
Add Touch Bar and backlight reprobe support
hid-apple.c | 1372 +++++++++++++++++++++++++++++++++++++++
hid-appletb-bl.c | 44 +-
hid-appletb-kbd.c | 109 +++-
hid-ids.h | 1574 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 3073 insertions(+), 26 deletions(-)
create mode 100644 hid-apple.c
create mode 100644 hid-ids.h
--
2.53.0
^ permalink raw reply
* Re: [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification
From: Benjamin Tissoires @ 2026-04-03 13:02 UTC (permalink / raw)
To: Dave Carey; +Cc: jikos, linux-input, linux-kernel
In-Reply-To: <20260402182937.388847-1-carvsdriver@gmail.com>
Hi Dave,
On Apr 02 2026, Dave Carey wrote:
> The Lenovo Yoga Book 9 14IAH10 (83KJ) uses a composite USB HID device
> (17EF:6161) where three descriptor quirks combine to cause hid-multitouch
> to incorrectly set INPUT_PROP_BUTTONPAD on both touchscreen nodes, making
> libinput treat them as indirect clickpads rather than direct touchscreens.
>
> Quirk 1: The HID_DG_TOUCHSCREEN application collection contains
> HID_UP_BUTTON usages (stylus barrel buttons). The generic heuristic in
> mt_touch_input_mapping() treats any touchscreen-with-buttons as a
> touchpad, setting INPUT_MT_POINTER.
>
> Quirk 2: A HID_DG_TOUCHPAD collection ("Emulated Touchpad") sets
> INPUT_MT_POINTER unconditionally in mt_allocate_application().
>
> Quirk 3: The HID_DG_BUTTONTYPE feature report (0x51) returns
> MT_BUTTONTYPE_CLICKPAD, directly setting td->is_buttonpad = true.
>
> These combine to produce INPUT_PROP_BUTTONPAD on the touchscreen input
> nodes. libinput treats the devices as indirect clickpads and suppresses
> direct touch events, leaving the touchscreens non-functional under
> KDE/Wayland.
This looks like a completely borked report descriptor. Out of curiosity,
do you know if there is a specific Windows driver for it or if it's
using the plain generic driver there.
The reasoning is that if it's using the generic win driver, we are
probably doing something wrong, and we need to fix it in a more generic
way.
>
> Additionally, the firmware resets if any USB control request is received
> during the CDC ACM initialization window. The existing GET_REPORT call
> in mt_check_input_mode() during probe triggers this reset.
Ouch, even better :(
>
> Fix by extending MT_QUIRK_YOGABOOK9I (already defined for the earlier
> Yoga Book 9i) to guard all three BUTTONPAD heuristics and skip the
> HID_DG_BUTTONTYPE GET_REPORT during probe for this device.
Really not a big fan of the approach taken here: We are sprinkling the
code with special quirks for one particular device and that makes
everything worse.
I would much prefer a report descriptor fixup where:
- we drop the HID_UP_BUTTON
- we drop the HID_DG_TOUCHPAD collection entirely
- we drop the HID_DG_BUTTONTYPE feature entirely
- we drop the Win8 blob feature as well to prevent queries during
initialization.
For ease of development I would recomend working with a separate HID-BPF
program instead of a in-kernel fix, but we already have a .report_fixup
here, so I wouldn't mind having the fix here as well.
Cheers,
Benjamin
>
> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
> Tested-by: Dave Carey <carvsdriver@gmail.com>
> ---
> drivers/hid/hid-multitouch.c | 34 +++++++++++++++++++++++++++-------
> 1 file changed, 27 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
> index e82a3c4e5..1bef32b1d 100644
> --- a/drivers/hid/hid-multitouch.c
> +++ b/drivers/hid/hid-multitouch.c
> @@ -549,7 +549,14 @@ static void mt_feature_mapping(struct hid_device *hdev,
>
> switch (usage->hid) {
> case HID_DG_CONTACTMAX:
> - mt_get_feature(hdev, field->report);
> + /*
> + * Yoga Book 9: skip GET_REPORT during probe; the firmware
> + * resets if it receives any control request before the init
> + * Output report is sent (within ~1.18s of USB enumeration).
> + * Logical maximum from the descriptor is used as the fallback.
> + */
> + if (!(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I))
> + mt_get_feature(hdev, field->report);
>
> td->maxcontacts = field->value[0];
> if (!td->maxcontacts &&
> @@ -566,6 +573,10 @@ static void mt_feature_mapping(struct hid_device *hdev,
> break;
> }
>
> + /* Yoga Book 9 reports Clickpad but is a direct touchscreen */
> + if (td->mtclass.quirks & MT_QUIRK_YOGABOOK9I)
> + break;
> +
> mt_get_feature(hdev, field->report);
> switch (field->value[usage->usage_index]) {
> case MT_BUTTONTYPE_CLICKPAD:
> @@ -579,7 +590,9 @@ static void mt_feature_mapping(struct hid_device *hdev,
> break;
> case 0xff0000c5:
> /* Retrieve the Win8 blob once to enable some devices */
> - if (usage->usage_index == 0)
> + /* Yoga Book 9: skip; firmware resets before init if queried */
> + if (usage->usage_index == 0 &&
> + !(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I))
> mt_get_feature(hdev, field->report);
> break;
> }
> @@ -644,8 +657,11 @@ static struct mt_application *mt_allocate_application(struct mt_device *td,
>
> /*
> * Model touchscreens providing buttons as touchpads.
> + * Yoga Book 9 has an emulated touchpad but its touch surfaces
> + * are direct screens, not indirect pointers.
> */
> - if (application == HID_DG_TOUCHPAD) {
> + if (application == HID_DG_TOUCHPAD &&
> + !(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I)) {
> mt_application->mt_flags |= INPUT_MT_POINTER;
> td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
> }
> @@ -802,11 +818,15 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
>
> /*
> * Model touchscreens providing buttons as touchpads.
> + * Skip for Yoga Book 9 which has stylus buttons inside
> + * touchscreen collections, not physical touchpad buttons.
> */
> if (field->application == HID_DG_TOUCHSCREEN &&
> (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
> - app->mt_flags |= INPUT_MT_POINTER;
> - td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
> + if (!(app->quirks & MT_QUIRK_YOGABOOK9I)) {
> + app->mt_flags |= INPUT_MT_POINTER;
> + td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
> + }
> }
>
> /* count the buttons on touchpads */
> @@ -1420,7 +1440,6 @@ static int mt_touch_input_configured(struct hid_device *hdev,
> */
> if (cls->quirks & MT_QUIRK_APPLE_TOUCHBAR)
> app->mt_flags |= INPUT_MT_DIRECT;
> -
> if (cls->is_indirect)
> app->mt_flags |= INPUT_MT_POINTER;
>
> @@ -1432,7 +1451,8 @@ static int mt_touch_input_configured(struct hid_device *hdev,
>
> /* check for clickpads */
> if ((app->mt_flags & INPUT_MT_POINTER) &&
> - (app->buttons_count == 1))
> + (app->buttons_count == 1) &&
> + !(app->quirks & MT_QUIRK_YOGABOOK9I))
> td->is_buttonpad = true;
>
> if (td->is_buttonpad)
> --
> 2.53.0
>
>
^ permalink raw reply
* Re: [PATCH v3 3/3] input: touchscreen: st1232: add system wakeup support
From: Bui Duc Phuc @ 2026-04-03 11:39 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Magnus Damm, Wolfram Sang, Jeff LaBundy, Bastian Hecht,
Javier Carrasco, linux-input, devicetree, linux-renesas-soc,
linux-kernel
In-Reply-To: <CAMuHMdW6y4MkCYR-rgn=FA38ZUE_X=3oQWNOvfdyMo=D5_xoxA@mail.gmail.com>
Hi Dmitry, Geert,
Thank you, Dmitry, for the review and the explanation. You are
absolutely right; I realized the I2C core handles this automatically,
which is tại sao I dropped those changes in the v4 series [1] as Geert
mentioned.
Thank you, Geert, for pointing that out and for your support.
While working on this, I also noticed similar redundant wakeup
handling in the mpr121 driver and sent a cleanup patch to remove
it [2].
[1] https://lore.kernel.org/20260309000319.74880-1-phucduc.bui@gmail.com
[2] https://lore.kernel.org/all/20260309071413.92709-1-phucduc.bui@gmail.com/
Thanks,
Phuc
On Thu, Apr 2, 2026 at 1:56 PM Geert Uytterhoeven <geert@linux-m68k.org> wrote:
>
> Hi Dmitry,
>
> On Thu, 2 Apr 2026 at 07:17, Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
> > On Fri, Mar 06, 2026 at 06:19:12PM +0700, phucduc.bui@gmail.com wrote:
> > > From: bui duc phuc <phucduc.bui@gmail.com>
> > >
> > > The ST1232 touchscreen controller can generate an interrupt when the
> > > panel is touched, which may be used as a wakeup source for the system.
> > >
> > > Add support for system wakeup by initializing the device wakeup
> > > capability in probe() based on the "wakeup-source" device property.
> > > When wakeup is enabled, the driver enables IRQ wake during suspend
> > > so that touch events can wake the system.
> > >
> > > If wakeup is not enabled, the driver retains the existing behavior of
> > > disabling the IRQ and powering down the controller during suspend.
> >
> > I do not believe this patch is needed: i2c core already handles
> > "wakeup-source" property and manages wakeup IRQ.
>
> No, it is not needed, as mentioned in the cover letter of v4[1],
> and as tested by me[2].
>
> [1] https://lore.kernel.org/20260309000319.74880-1-phucduc.bui@gmail.com
> [2] https://lore.kernel.org/CAMuHMdUqiaP=COTkKU_jK6Hdii+YJ5+zXnxFkOOnhLri5NakTw@mail.gmail.com
>
> Gr{oetje,eeting}s,
>
> Geert
>
> --
> Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
>
> In personal conversations with technical people, I call myself a hacker. But
> when I'm talking to journalists I just say "programmer" or something like that.
> -- Linus Torvalds
^ permalink raw reply
* [PATCH 2/2] HID: multitouch: add support for Dell Pro Rugged 12 Tablet RA02260
From: buingoc67 @ 2026-04-03 9:28 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel, hmtheboy154
In-Reply-To: <20260403092848.10223-1-buingoc67@gmail.com>
From: hmtheboy154 <buingoc67@gmail.com>
Tested both multitouch & stylus.
Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
---
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-multitouch.c | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index afcee13bad61..2d40d33a6a90 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -447,6 +447,7 @@
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001 0xa001
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000 0xc000
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002 0xc002
+#define I2C_DEVICE_ID_DELL_PRO_RUGGED_12_RA02260 0xc005
#define USB_VENDOR_ID_EDIFIER 0x2d99
#define USB_DEVICE_ID_EDIFIER_QR30 0xa101 /* EDIFIER Hal0 2.0 SE */
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 9e46028746fd..228fcffc3423 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -2250,6 +2250,10 @@ static const struct hid_device_id mt_devices[] = {
HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8,
USB_VENDOR_ID_DWAV,
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002) },
+ { .driver_data = MT_CLS_EGALAX_WIN8,
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_DWAV,
+ I2C_DEVICE_ID_DELL_PRO_RUGGED_12_RA02260) },
/* Elan devices */
{ .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
--
2.53.0
^ permalink raw reply related
* [PATCH 1/2] HID: multitouch: rename MT_CLS_EGALAX_P80H84 to MT_CLS_EGALAX_WIN8
From: buingoc67 @ 2026-04-03 9:28 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel, hmtheboy154
In-Reply-To: <20260403092848.10223-1-buingoc67@gmail.com>
From: hmtheboy154 <buingoc67@gmail.com>
The Dell Pro Rugged 12 Tablet RA02260 uses an i2c touchscreen from
eGalax that exposes the same HID_GROUP_MULTITOUCH_WIN_8 as the
P80H84 and also requires the same quirks to make multitouch work.
Rename the class to MT_CLS_EGALAX_WIN8 for more general use.
Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
---
drivers/hid/hid-multitouch.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index e82a3c4e5b44..9e46028746fd 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -235,7 +235,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app);
#define MT_CLS_SMART_TECH 0x0113
#define MT_CLS_APPLE_TOUCHBAR 0x0114
#define MT_CLS_YOGABOOK9I 0x0115
-#define MT_CLS_EGALAX_P80H84 0x0116
+#define MT_CLS_EGALAX_WIN8 0x0116
#define MT_CLS_SIS 0x0457
#define MT_DEFAULT_MAXCONTACT 10
@@ -450,7 +450,7 @@ static const struct mt_class mt_classes[] = {
MT_QUIRK_YOGABOOK9I,
.export_all_inputs = true
},
- { .name = MT_CLS_EGALAX_P80H84,
+ { .name = MT_CLS_EGALAX_WIN8,
.quirks = MT_QUIRK_ALWAYS_VALID |
MT_QUIRK_IGNORE_DUPLICATES |
MT_QUIRK_CONTACT_CNT_ACCURATE,
@@ -2246,7 +2246,7 @@ static const struct hid_device_id mt_devices[] = {
{ .driver_data = MT_CLS_EGALAX_SERIAL,
MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000) },
- { .driver_data = MT_CLS_EGALAX_P80H84,
+ { .driver_data = MT_CLS_EGALAX_WIN8,
HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8,
USB_VENDOR_ID_DWAV,
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002) },
--
2.53.0
^ permalink raw reply related
* [PATCH 0/2] HID: multitouch: Add support for Dell Pro Rugged 12 Tablet RA02260
From: buingoc67 @ 2026-04-03 9:28 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel, hmtheboy154
From: hmtheboy154 <buingoc67@gmail.com>
This patch series adds multitouch and stylus support for the eGalax
I2C touchscreen found in the Dell Pro Rugged 12 Tablet RA02260.
hmtheboy154 (2):
HID: multitouch: rename MT_CLS_EGALAX_P80H84 to MT_CLS_EGALAX_WIN8
HID: multitouch: add support for Dell Pro Rugged 12 Tablet RA02260
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-multitouch.c | 10 +++++++---
2 files changed, 8 insertions(+), 3 deletions(-)
--
2.53.0
^ permalink raw reply
* Re: [PATCH v4 3/4] Input: aw86938 - add driver for Awinic AW86938
From: Luca Weiss @ 2026-04-03 8:23 UTC (permalink / raw)
To: Dmitry Torokhov, Luca Weiss
Cc: Griffin Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-input,
devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <ac1DclNOl3ZA5bUg@google.com>
On Wed Apr 1, 2026 at 6:11 PM CEST, Dmitry Torokhov wrote:
> On Wed, Apr 01, 2026 at 04:44:47PM +0200, Luca Weiss wrote:
>> Hi Dmitry,
>>
>> On Wed Mar 4, 2026 at 5:56 AM CET, Dmitry Torokhov wrote:
>> > On Mon, Mar 02, 2026 at 11:50:27AM +0100, Griffin Kroah-Hartman wrote:
>> >> Add support for the I2C-connected Awinic AW86938 LRA haptic driver.
>> >>
>> >> The AW86938 has a similar but slightly different register layout. In
>> >> particular, the boost mode register values.
>> >> The AW86938 also has some extra features that aren't implemented
>> >> in this driver yet.
>> >>
>> >> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
>> >
>> > Applied, thank you.
>>
>> I'm curious, where did you apply these patches? linux-next doesn't have
>> it and I don't see it in your kernel.org repo either.
>> https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git/
>>
>> Did this slip through the cracks or will these patches still appear
>> there?
>
> My bad, I think there was a conflict with Dan Carpenter's patch and as a
> result the series got stuck in my internal queue. My apologies.
>
> Should be out in 'next' branch now.
>
> Thanks.
Thanks, appreciate it!
Regards
Luca
^ permalink raw reply
* [PATCH v4] dt-bindings: input: touchscreen: ti,tsc2005: Add wakeup-source
From: phucduc.bui @ 2026-04-03 4:07 UTC (permalink / raw)
To: robh
Cc: conor+dt, conor, devicetree, dmitry.torokhov, krzk+dt, krzk,
linux-input, linux-kernel, marex, mingo, phucduc.bui, tglx
From: bui duc phuc <phucduc.bui@gmail.com>
Document the "wakeup-source" property for the ti,tsc2005 touchscreen
controllers to allow the device to wake the system from suspend.
Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>
---
changes:
v4: Drop redundant "type: boolean" for wakeup-source to use the core
definition from dt-schema (as suggested by Rob Herring).
v3: Remove blank lines (suggested by Conor).
v2: Revise the commit content and remove patch1 related to I2C and SPI
wakeup handling
.../devicetree/bindings/input/touchscreen/ti,tsc2005.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/ti,tsc2005.yaml b/Documentation/devicetree/bindings/input/touchscreen/ti,tsc2005.yaml
index 7187c390b2f5..c06a85b74533 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/ti,tsc2005.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/ti,tsc2005.yaml
@@ -55,6 +55,8 @@ properties:
touchscreen-size-x: true
touchscreen-size-y: true
+ wakeup-source: true
+
allOf:
- $ref: touchscreen.yaml#
- if:
@@ -97,6 +99,7 @@ examples:
ti,x-plate-ohms = <280>;
ti,esd-recovery-timeout-ms = <8000>;
+ wakeup-source;
};
};
- |
@@ -124,5 +127,6 @@ examples:
ti,x-plate-ohms = <280>;
ti,esd-recovery-timeout-ms = <8000>;
+ wakeup-source;
};
};
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v3] dt-bindings: input: touchscreen: ti,tsc2005: Add wakeup-source
From: Bui Duc Phuc @ 2026-04-03 3:35 UTC (permalink / raw)
To: Rob Herring
Cc: conor+dt, krzk+dt, conor, devicetree, dmitry.torokhov, krzk,
linux-input, linux-kernel, marex, mingo, tglx
In-Reply-To: <20260326141551.GA2304345-robh@kernel.org>
Hi Rob, Krzysztof, Conor,
Thanks for the discussion.
The core schema already defines "wakeup-source" as a common property
(using oneOf for boolean or phandle-array), so defining the type here is
redundant.
https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/wakeup-source.yaml
I'll update it to "wakeup-source: true" in v4.
Best regards,
Phuc
On Thu, Mar 26, 2026 at 9:15 PM Rob Herring <robh@kernel.org> wrote:
>
> On Wed, Mar 18, 2026 at 03:31:24PM +0700, phucduc.bui@gmail.com wrote:
> > From: bui duc phuc <phucduc.bui@gmail.com>
> >
> > Document the "wakeup-source" property for the ti,tsc2005 touchscreen
> > controllers to allow the device to wake the system from suspend.
> >
> > Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>
> > ---
> >
> > changes:
> > v3: Remove blank lines
> > v2: Revise the commit content and remove patch1 related to I2C and SPI
> > wakeup handling
> >
> > .../devicetree/bindings/input/touchscreen/ti,tsc2005.yaml | 5 +++++
> > 1 file changed, 5 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/input/touchscreen/ti,tsc2005.yaml b/Documentation/devicetree/bindings/input/touchscreen/ti,tsc2005.yaml
> > index 7187c390b2f5..a9842509c1fe 100644
> > --- a/Documentation/devicetree/bindings/input/touchscreen/ti,tsc2005.yaml
> > +++ b/Documentation/devicetree/bindings/input/touchscreen/ti,tsc2005.yaml
> > @@ -55,6 +55,9 @@ properties:
> > touchscreen-size-x: true
> > touchscreen-size-y: true
> >
> > + wakeup-source:
> > + type: boolean
>
> wakeup-source already has a defined type.
>
> wakeup-source: true
>
> > +
> > allOf:
> > - $ref: touchscreen.yaml#
> > - if:
> > @@ -97,6 +100,7 @@ examples:
> >
> > ti,x-plate-ohms = <280>;
> > ti,esd-recovery-timeout-ms = <8000>;
> > + wakeup-source;
> > };
> > };
> > - |
> > @@ -124,5 +128,6 @@ examples:
> >
> > ti,x-plate-ohms = <280>;
> > ti,esd-recovery-timeout-ms = <8000>;
> > + wakeup-source;
> > };
> > };
> > --
> > 2.43.0
> >
^ permalink raw reply
* Re: [PATCH v2 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
From: Vicki Pfau @ 2026-04-03 1:17 UTC (permalink / raw)
To: Silvan Jegen
Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <3GWQPE79MJ7Y0.2LOOIA8A83N7R@homearch.localdomain>
Hi,
Replies inline
On 4/2/26 12:09 PM, Silvan Jegen wrote:
> Hi
>
> Thanks for the patch!
>
> Just some comments and questions inline below.
>
> Vicki Pfau <vi@endrift.com> wrote:
>> This adds a new driver for the Switch 2 controllers. The Switch 2 uses an
>> unusual split-interface design such that input and rumble occur on the main
>> HID interface, but all other communication occurs over a "configuration"
>> interface. This is the case on both USB and Bluetooth, so this new driver
>> uses a split-driver design with the HID interface being the "main" driver
>> and the configuration interface is a secondary driver that looks up to the
>> HID interface, sharing resources on a common struct.
>>
>> Due to using a non-standard pairing interface as well as Bluetooth
>> communications being extremely limited in the kernel, a custom interface
>> between userspace and the kernel will need to be design, along with bringup
>> in BlueZ. That is beyond the scope of this initial patch, which only
>> contains the generic HID and USB configuration interface drivers.
>>
>> This initial work supports general input for the Joy-Con 2, Pro Controller
>> 2, and GameCube NSO controllers. IMU, rumble and battery support is not yet
>> present.
>>
>> Signed-off-by: Vicki Pfau <vi@endrift.com>
>> ---
>> MAINTAINERS | 1 +
>> drivers/hid/Kconfig | 11 +-
>> drivers/hid/hid-ids.h | 4 +
>> drivers/hid/hid-nintendo.c | 1194 ++++++++++++++++-
>> drivers/hid/hid-nintendo.h | 72 +
>> drivers/input/joystick/Kconfig | 11 +
>> drivers/input/joystick/Makefile | 1 +
>> drivers/input/joystick/nintendo-switch2-usb.c | 353 +++++
>> 8 files changed, 1637 insertions(+), 10 deletions(-)
>> create mode 100644 drivers/hid/hid-nintendo.h
>> create mode 100644 drivers/input/joystick/nintendo-switch2-usb.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 7b277d5bf3d12..4d1a28df5fd24 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -18743,6 +18743,7 @@ F: drivers/scsi/nsp32*
>>
>> NINTENDO HID DRIVER
>> M: Daniel J. Ogorchock <djogorchock@gmail.com>
>> +M: Vicki Pfau <vi@endrift.com>
>> L: linux-input@vger.kernel.org
>> S: Maintained
>> F: drivers/hid/hid-nintendo*
>> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
>> index c1d9f7c6a5f23..1a293a6c02c26 100644
>> --- a/drivers/hid/Kconfig
>> +++ b/drivers/hid/Kconfig
>> @@ -826,10 +826,13 @@ config HID_NINTENDO
>> depends on LEDS_CLASS
>> select POWER_SUPPLY
>> help
>> - Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
>> - All controllers support bluetooth, and the Pro Controller also supports
>> - its USB mode. This also includes support for the Nintendo Switch Online
>> - Controllers which include the NES, Genesis, SNES, and N64 controllers.
>> + Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller, as
>> + well as Nintendo Switch 2 Joy-Cons, Pro Controller, and NSO GameCube
>> + controllers. All Switch controllers support bluetooth, and the Pro
>> + Controller also supports its USB mode. This also includes support for
>> + the Nintendo Switch Online Controllers which include the NES, Genesis,
>> + SNES, and N64 controllers. Switch 2 controllers currently only support
>> + USB mode.
>>
>> To compile this driver as a module, choose M here: the
>> module will be called hid-nintendo.
>> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
>> index 4ab7640b119ac..a794dad7980f3 100644
>> --- a/drivers/hid/hid-ids.h
>> +++ b/drivers/hid/hid-ids.h
>> @@ -1073,6 +1073,10 @@
>> #define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017
>> #define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
>> #define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
>> +#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONR 0x2066
>> +#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONL 0x2067
>> +#define USB_DEVICE_ID_NINTENDO_NS2_PROCON 0x2069
>> +#define USB_DEVICE_ID_NINTENDO_NS2_GCCON 0x2073
>>
>> #define USB_VENDOR_ID_NOVATEK 0x0603
>> #define USB_DEVICE_ID_NOVATEK_PCT 0x0600
>> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
>> index 29008c2cc5304..4ab8d4e7558a1 100644
>> --- a/drivers/hid/hid-nintendo.c
>> +++ b/drivers/hid/hid-nintendo.c
>> @@ -1,11 +1,13 @@
>> // SPDX-License-Identifier: GPL-2.0+
>> /*
>> - * HID driver for Nintendo Switch Joy-Cons and Pro Controllers
>> + * HID driver for Nintendo Switch Joy-Cons and Pro Controllers, as well as
>> + * Nintendo Switch 2 Joy-Cons, Pro Controller, and GameCube Controller
>> *
>> * Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com>
>> * Portions Copyright (c) 2020 Nadia Holmquist Pedersen <nadia@nhp.sh>
>> * Copyright (c) 2022 Emily Strickland <linux@emily.st>
>> * Copyright (c) 2023 Ryan McClelland <rymcclel@gmail.com>
>> + * Copyright (c) 2026 Valve Software
>> *
>> * The following resources/projects were referenced for this driver:
>> * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
>> @@ -13,6 +15,8 @@
>> * https://github.com/FrotBot/SwitchProConLinuxUSB
>> * https://github.com/MTCKC/ProconXInput
>> * https://github.com/Davidobot/BetterJoyForCemu
>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
>> + * https://github.com/ndeadly/switch2_controller_research
>> * hid-wiimote kernel hid driver
>> * hid-logitech-hidpp driver
>> * hid-sony driver
>> @@ -29,6 +33,7 @@
>> */
>>
>> #include "hid-ids.h"
>> +#include "hid-nintendo.h"
>> #include <linux/unaligned.h>
>> #include <linux/delay.h>
>> #include <linux/device.h>
>> @@ -41,6 +46,8 @@
>> #include <linux/module.h>
>> #include <linux/power_supply.h>
>> #include <linux/spinlock.h>
>> +#include <linux/usb.h>
>> +#include "usbhid/usbhid.h"
>>
>> /*
>> * Reference the url below for the following HID report defines:
>> @@ -2614,7 +2621,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
>> return ret;
>> }
>>
>> -static int nintendo_hid_event(struct hid_device *hdev,
>> +static int joycon_event(struct hid_device *hdev,
>> struct hid_report *report, u8 *raw_data, int size)
>> {
>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>> @@ -2625,7 +2632,7 @@ static int nintendo_hid_event(struct hid_device *hdev,
>> return joycon_ctlr_handle_event(ctlr, raw_data, size);
>> }
>>
>> -static int nintendo_hid_probe(struct hid_device *hdev,
>> +static int joycon_probe(struct hid_device *hdev,
>> const struct hid_device_id *id)
>> {
>> int ret;
>> @@ -2729,7 +2736,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
>> return ret;
>> }
>>
>> -static void nintendo_hid_remove(struct hid_device *hdev)
>> +static void joycon_remove(struct hid_device *hdev)
>> {
>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>> unsigned long flags;
>> @@ -2748,7 +2755,9 @@ static void nintendo_hid_remove(struct hid_device *hdev)
>> hid_hw_stop(hdev);
>> }
>>
>> -static int nintendo_hid_resume(struct hid_device *hdev)
>> +#ifdef CONFIG_PM
>> +
>> +static int joycon_resume(struct hid_device *hdev)
>> {
>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>> int ret;
>> @@ -2771,7 +2780,7 @@ static int nintendo_hid_resume(struct hid_device *hdev)
>> return ret;
>> }
>>
>> -static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
>> +static int joycon_suspend(struct hid_device *hdev, pm_message_t message)
>> {
>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>>
>> @@ -2790,7 +2799,1120 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
>> return 0;
>> }
>>
>> +#endif
>> +
>> +/*
>> + * =============================================================================
>> + * Switch 2 support
>> + * =============================================================================
>> + */
>> +#define NS2_BTNR_B BIT(0)
>> +#define NS2_BTNR_A BIT(1)
>> +#define NS2_BTNR_Y BIT(2)
>> +#define NS2_BTNR_X BIT(3)
>> +#define NS2_BTNR_R BIT(4)
>> +#define NS2_BTNR_ZR BIT(5)
>> +#define NS2_BTNR_PLUS BIT(6)
>> +#define NS2_BTNR_RS BIT(7)
>> +
>> +#define NS2_BTNL_DOWN BIT(0)
>> +#define NS2_BTNL_RIGHT BIT(1)
>> +#define NS2_BTNL_LEFT BIT(2)
>> +#define NS2_BTNL_UP BIT(3)
>> +#define NS2_BTNL_L BIT(4)
>> +#define NS2_BTNL_ZL BIT(5)
>> +#define NS2_BTNL_MINUS BIT(6)
>> +#define NS2_BTNL_LS BIT(7)
>> +
>> +#define NS2_BTN3_C BIT(4)
>> +#define NS2_BTN3_SR BIT(6)
>> +#define NS2_BTN3_SL BIT(7)
>> +
>> +#define NS2_BTN_JCR_HOME BIT(0)
>> +#define NS2_BTN_JCR_GR BIT(2)
>> +#define NS2_BTN_JCR_C NS2_BTN3_C
>> +#define NS2_BTN_JCR_SR NS2_BTN3_SR
>> +#define NS2_BTN_JCR_SL NS2_BTN3_SL
>> +
>> +#define NS2_BTN_JCL_CAPTURE BIT(0)
>> +#define NS2_BTN_JCL_GL BIT(2)
>> +#define NS2_BTN_JCL_SR NS2_BTN3_SR
>> +#define NS2_BTN_JCL_SL NS2_BTN3_SL
>> +
>> +#define NS2_BTN_PRO_HOME BIT(0)
>> +#define NS2_BTN_PRO_CAPTURE BIT(1)
>> +#define NS2_BTN_PRO_GR BIT(2)
>> +#define NS2_BTN_PRO_GL BIT(3)
>> +#define NS2_BTN_PRO_C NS2_BTN3_C
>> +
>> +#define NS2_BTN_GC_HOME BIT(0)
>> +#define NS2_BTN_GC_CAPTURE BIT(1)
>> +#define NS2_BTN_GC_C NS2_BTN3_C
>> +
>> +#define NS2_TRIGGER_RANGE 4095
>> +#define NS2_AXIS_MIN -32768
>> +#define NS2_AXIS_MAX 32767
>> +
>> +#define NS2_MAX_PLAYER_ID 8
>> +
>> +#define NS2_MAX_INIT_RETRIES 4
>> +
>> +#define NS2_FLASH_ADDR_SERIAL 0x13002
>> +#define NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB 0x130a8
>> +#define NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB 0x130e8
>> +#define NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB 0x13140
>> +#define NS2_FLASH_ADDR_USER_PRIMARY_CALIB 0x1fc040
>> +#define NS2_FLASH_ADDR_USER_SECONDARY_CALIB 0x1fc080
>> +
>> +#define NS2_FLASH_SIZE_SERIAL 0x10
>> +#define NS2_FLASH_SIZE_FACTORY_AXIS_CALIB 9
>> +#define NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB 2
>> +#define NS2_FLASH_SIZE_USER_AXIS_CALIB 11
>> +
>> +#define NS2_USER_CALIB_MAGIC 0xa1b2
>> +
>> +#define NS2_FEATURE_BUTTONS BIT(0)
>> +#define NS2_FEATURE_ANALOG BIT(1)
>> +#define NS2_FEATURE_IMU BIT(2)
>> +#define NS2_FEATURE_MOUSE BIT(4)
>> +#define NS2_FEATURE_RUMBLE BIT(5)
>> +#define NS2_FEATURE_MAGNETO BIT(7)
>> +
>> +enum switch2_subcmd_flash {
>> + NS2_SUBCMD_FLASH_READ_BLOCK = 0x01,
>> + NS2_SUBCMD_FLASH_WRITE_BLOCK = 0x02,
>> + NS2_SUBCMD_FLASH_ERASE_BLOCK = 0x03,
>> + NS2_SUBCMD_FLASH_READ = 0x04,
>> + NS2_SUBCMD_FLASH_WRITE = 0x05,
>> +};
>> +
>> +enum switch2_subcmd_init {
>> + NS2_SUBCMD_INIT_SELECT_REPORT = 0xa,
>> + NS2_SUBCMD_INIT_USB = 0xd,
>> +};
>> +
>> +enum switch2_subcmd_feature_select {
>> + NS2_SUBCMD_FEATSEL_GET_INFO = 0x1,
>> + NS2_SUBCMD_FEATSEL_SET_MASK = 0x2,
>> + NS2_SUBCMD_FEATSEL_CLEAR_MASK = 0x3,
>> + NS2_SUBCMD_FEATSEL_ENABLE = 0x4,
>> + NS2_SUBCMD_FEATSEL_DISABLE = 0x5,
>> +};
>> +
>> +enum switch2_subcmd_grip {
>> + NS2_SUBCMD_GRIP_GET_INFO = 0x1,
>> + NS2_SUBCMD_GRIP_ENABLE_BUTTONS = 0x2,
>> + NS2_SUBCMD_GRIP_GET_INFO_EXT = 0x3,
>> +};
>> +
>> +enum switch2_subcmd_led {
>> + NS2_SUBCMD_LED_P1 = 0x1,
>> + NS2_SUBCMD_LED_P2 = 0x2,
>> + NS2_SUBCMD_LED_P3 = 0x3,
>> + NS2_SUBCMD_LED_P4 = 0x4,
>> + NS2_SUBCMD_LED_ALL_ON = 0x5,
>> + NS2_SUBCMD_LED_ALL_OFF = 0x6,
>> + NS2_SUBCMD_LED_PATTERN = 0x7,
>> + NS2_SUBCMD_LED_BLINK = 0x8,
>> +};
>> +
>> +enum switch2_subcmd_fw_info {
>> + NS2_SUBCMD_FW_INFO_GET = 0x1,
>> +};
>> +
>> +enum switch2_ctlr_type {
>> + NS2_CTLR_TYPE_JCL = 0x00,
>> + NS2_CTLR_TYPE_JCR = 0x01,
>> + NS2_CTLR_TYPE_PRO = 0x02,
>> + NS2_CTLR_TYPE_GC = 0x03,
>> +};
>> +
>> +enum switch2_report_id {
>> + NS2_REPORT_UNIFIED = 0x05,
>> + NS2_REPORT_JCL = 0x07,
>> + NS2_REPORT_JCR = 0x08,
>> + NS2_REPORT_PRO = 0x09,
>> + NS2_REPORT_GC = 0x0a,
>> +};
>> +
>> +enum switch2_init_step {
>> + NS2_INIT_READ_SERIAL,
>> + NS2_INIT_GET_FIRMWARE_INFO,
>> + NS2_INIT_READ_FACTORY_PRIMARY_CALIB,
>> + NS2_INIT_READ_FACTORY_SECONDARY_CALIB,
>> + NS2_INIT_READ_FACTORY_TRIGGER_CALIB,
>> + NS2_INIT_READ_USER_PRIMARY_CALIB,
>> + NS2_INIT_READ_USER_SECONDARY_CALIB,
>> + NS2_INIT_SET_FEATURE_MASK,
>> + NS2_INIT_ENABLE_FEATURES,
>> + NS2_INIT_GRIP_BUTTONS,
>> + NS2_INIT_REPORT_FORMAT,
>> + NS2_INIT_SET_PLAYER_LEDS,
>> + NS2_INIT_INPUT,
>> + NS2_INIT_FINISH,
>> + NS2_INIT_DONE,
>> +};
>> +
>> +struct switch2_version_info {
>> + uint8_t major;
>> + uint8_t minor;
>> + uint8_t patch;
>> + uint8_t ctlr_type;
>> + __le32 unk;
>> + int8_t dsp_major;
>> + int8_t dsp_minor;
>> + int8_t dsp_patch;
>> + int8_t dsp_type;
>> +};
>> +
>> +struct switch2_axis_calibration {
>> + uint16_t neutral;
>> + uint16_t negative;
>> + uint16_t positive;
>> +};
>> +
>> +struct switch2_stick_calibration {
>> + struct switch2_axis_calibration x;
>> + struct switch2_axis_calibration y;
>> +};
>> +
>> +struct switch2_controller {
>> + struct hid_device *hdev;
>> + struct switch2_cfg_intf *cfg;
>> +
>> + char name[64];
>> + char phys[64];
>> + struct list_head entry;
>> + struct mutex lock;
>> +
>> + enum switch2_ctlr_type ctlr_type;
>> + enum switch2_init_step init_step;
>> + struct input_dev __rcu *input;
>> + char serial[NS2_FLASH_SIZE_SERIAL + 1];
>> + struct switch2_version_info version;
>> +
>> + struct switch2_stick_calibration stick_calib[2];
>> + uint8_t lt_zero;
>> + uint8_t rt_zero;
>> +
>> + uint32_t player_id;
>> + struct led_classdev leds[4];
>> +};
>> +
>> +static DEFINE_MUTEX(switch2_controllers_lock);
>> +static LIST_HEAD(switch2_controllers);
>> +
>> +struct switch2_ctlr_button_mapping {
>> + uint32_t code;
>> + int byte;
>> + uint32_t bit;
>> +};
>> +
>> +static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_mappings[] = {
>> + { BTN_DPAD_LEFT, 0, NS2_BTNL_LEFT, },
>> + { BTN_DPAD_UP, 0, NS2_BTNL_UP, },
>> + { BTN_DPAD_DOWN, 0, NS2_BTNL_DOWN, },
>> + { BTN_DPAD_RIGHT, 0, NS2_BTNL_RIGHT, },
>> + { BTN_TL, 0, NS2_BTNL_L, },
>> + { BTN_TL2, 0, NS2_BTNL_ZL, },
>> + { BTN_SELECT, 0, NS2_BTNL_MINUS, },
>> + { BTN_THUMBL, 0, NS2_BTNL_LS, },
>> + { KEY_RECORD, 1, NS2_BTN_JCL_CAPTURE, },
>> + { BTN_GRIPR, 1, NS2_BTN_JCL_SL, },
>> + { BTN_GRIPR2, 1, NS2_BTN_JCL_SR, },
>> + { BTN_GRIPL, 1, NS2_BTN_JCL_GL, },
>> + { /* sentinel */ },
>> +};
>> +
>> +static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings[] = {
>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
>> + { BTN_EAST, 0, NS2_BTNR_B, },
>> + { BTN_NORTH, 0, NS2_BTNR_X, },
>> + { BTN_WEST, 0, NS2_BTNR_Y, },
>> + { BTN_TR, 0, NS2_BTNR_R, },
>> + { BTN_TR2, 0, NS2_BTNR_ZR, },
>> + { BTN_START, 0, NS2_BTNR_PLUS, },
>> + { BTN_THUMBR, 0, NS2_BTNR_RS, },
>> + { BTN_C, 1, NS2_BTN_JCR_C, },
>> + { BTN_MODE, 1, NS2_BTN_JCR_HOME, },
>> + { BTN_GRIPL2, 1, NS2_BTN_JCR_SL, },
>> + { BTN_GRIPL, 1, NS2_BTN_JCR_SR, },
>> + { BTN_GRIPR, 1, NS2_BTN_JCR_GR, },
>> + { /* sentinel */ },
>> +};
>> +
>> +static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
>> + { BTN_EAST, 0, NS2_BTNR_B, },
>> + { BTN_NORTH, 0, NS2_BTNR_X, },
>> + { BTN_WEST, 0, NS2_BTNR_Y, },
>> + { BTN_TL, 1, NS2_BTNL_L, },
>> + { BTN_TR, 0, NS2_BTNR_R, },
>> + { BTN_TL2, 1, NS2_BTNL_ZL, },
>> + { BTN_TR2, 0, NS2_BTNR_ZR, },
>> + { BTN_SELECT, 1, NS2_BTNL_MINUS, },
>> + { BTN_START, 0, NS2_BTNR_PLUS, },
>> + { BTN_THUMBL, 1, NS2_BTNL_LS, },
>> + { BTN_THUMBR, 0, NS2_BTNR_RS, },
>> + { BTN_MODE, 2, NS2_BTN_PRO_HOME },
>> + { KEY_RECORD, 2, NS2_BTN_PRO_CAPTURE },
>> + { BTN_GRIPR, 2, NS2_BTN_PRO_GR },
>> + { BTN_GRIPL, 2, NS2_BTN_PRO_GL },
>> + { BTN_C, 2, NS2_BTN_PRO_C },
>> + { /* sentinel */ },
>> +};
>> +
>> +static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
>> + { BTN_EAST, 0, NS2_BTNR_B, },
>> + { BTN_NORTH, 0, NS2_BTNR_X, },
>> + { BTN_WEST, 0, NS2_BTNR_Y, },
>> + { BTN_TL2, 1, NS2_BTNL_L, },
>> + { BTN_TR2, 0, NS2_BTNR_R, },
>> + { BTN_TL, 1, NS2_BTNL_ZL, },
>> + { BTN_TR, 0, NS2_BTNR_ZR, },
>> + { BTN_SELECT, 1, NS2_BTNL_MINUS, },
>> + { BTN_START, 0, NS2_BTNR_PLUS, },
>> + { BTN_MODE, 2, NS2_BTN_GC_HOME },
>> + { KEY_RECORD, 2, NS2_BTN_GC_CAPTURE },
>> + { BTN_C, 2, NS2_BTN_GC_C },
>> + { /* sentinel */ },
>> +};
>> +
>> +static const uint8_t switch2_init_cmd_data[] = {
>> + /*
>> + * The last 6 bytes of this packet are the MAC address of
>> + * the console, but we don't need that for USB
>> + */
>> + 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
>> +};
>> +
>> +static const uint8_t switch2_one_data[] = { 0x01, 0x00, 0x00, 0x00 };
>> +
>> +static const uint8_t switch2_feature_mask[] = {
>> + NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG | NS2_FEATURE_IMU,
>> + 0x00, 0x00, 0x00
>> +};
>> +
>> +static void switch2_init_step_done(struct switch2_controller *ns2, enum switch2_init_step step)
>> +{
>> + if (ns2->init_step != step)
>> + return;
>> +
>> + ns2->init_step++;
>> +}
>> +
>> +static inline bool switch2_ctlr_is_joycon(enum switch2_ctlr_type type)
>> +{
>> + return type == NS2_CTLR_TYPE_JCL || type == NS2_CTLR_TYPE_JCR;
>> +}
>> +
>> +static int switch2_set_leds(struct switch2_controller *ns2)
>> +{
>> + int i;
>> + uint8_t message[8] = { 0 };
>> +
>> + for (i = 0; i < JC_NUM_LEDS; i++)
>> + message[0] |= (!!ns2->leds[i].brightness) << i;
>> +
>> + if (!ns2->cfg)
>> + return -ENOTCONN;
>> + return ns2->cfg->send_command(NS2_CMD_LED, NS2_SUBCMD_LED_PATTERN,
>> + &message, sizeof(message),
>> + ns2->cfg);
>> +}
>> +
>> +static int switch2_player_led_brightness_set(struct led_classdev *led,
>> + enum led_brightness brightness)
>> +{
>> + struct device *dev = led->dev->parent;
>> + struct hid_device *hdev = to_hid_device(dev);
>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
>> +
>> + if (!ns2)
>> + return -ENODEV;
>> +
>> + guard(mutex)(&ns2->lock);
>> + return switch2_set_leds(ns2);
>> +}
>> +
>> +static void switch2_leds_create(struct switch2_controller *ns2)
>> +{
>> + struct hid_device *hdev = ns2->hdev;
>> + struct led_classdev *led;
>> + int i;
>> + int player_led_pattern;
>> +
>> + player_led_pattern = ns2->player_id % JC_NUM_LED_PATTERNS;
>> + hid_dbg(hdev, "assigned player %d led pattern", player_led_pattern + 1);
>> +
>> + for (i = 0; i < JC_NUM_LEDS; i++) {
>> + led = &ns2->leds[i];
>> + led->brightness = joycon_player_led_patterns[player_led_pattern][i];
>> + led->max_brightness = 1;
>> + led->brightness_set_blocking = switch2_player_led_brightness_set;
>> + led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE;
>> + }
>> +}
>> +
>> +static void switch2_config_buttons(struct input_dev *idev,
>> + const struct switch2_ctlr_button_mapping button_mappings[])
>> +{
>> + const struct switch2_ctlr_button_mapping *button;
>> +
>> + for (button = button_mappings; button->code; button++)
>> + input_set_capability(idev, EV_KEY, button->code);
>> +}
>> +
>> +static int switch2_init_input(struct switch2_controller *ns2)
>> +{
>> + struct input_dev *input;
>> + struct hid_device *hdev = ns2->hdev;
>> + int i;
>> + int ret;
>> +
>> + switch2_init_step_done(ns2, NS2_INIT_FINISH);
>> +
>> + rcu_read_lock();
>> + input = rcu_dereference(ns2->input);
>> + rcu_read_unlock();
>> +
>> + if (input)
>> + return 0;
>> +
>> + input = devm_input_allocate_device(&hdev->dev);
>> + if (!input)
>> + return -ENOMEM;
>> +
>> + input_set_drvdata(input, ns2);
>> + input->dev.parent = &hdev->dev;
>> + input->id.bustype = hdev->bus;
>> + input->id.vendor = hdev->vendor;
>> + input->id.product = hdev->product;
>> + input->id.version = hdev->version;
>> + input->uniq = ns2->serial;
>> + input->name = ns2->name;
>> + input->phys = hdev->phys;
>> +
>> + switch (ns2->ctlr_type) {
>> + case NS2_CTLR_TYPE_JCL:
>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + switch2_config_buttons(input, ns2_left_joycon_button_mappings);
>> + break;
>> + case NS2_CTLR_TYPE_JCR:
>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + switch2_config_buttons(input, ns2_right_joycon_button_mappings);
>> + break;
>> + case NS2_CTLR_TYPE_GC:
>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_Z, 0, NS2_TRIGGER_RANGE, 32, 128);
>> + input_set_abs_params(input, ABS_RZ, 0, NS2_TRIGGER_RANGE, 32, 128);
>> + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
>> + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
>> + switch2_config_buttons(input, ns2_gccon_mappings);
>> + break;
>> + case NS2_CTLR_TYPE_PRO:
>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>> + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
>> + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
>> + switch2_config_buttons(input, ns2_procon_mappings);
>> + break;
>> + default:
>> + input_free_device(input);
>> + return -EINVAL;
>> + }
>> +
>> + hid_info(ns2->hdev, "Firmware version %u.%u.%u (type %i)\n", ns2->version.major,
>> + ns2->version.minor, ns2->version.patch, ns2->version.ctlr_type);
>> + if (ns2->version.dsp_type >= 0)
>> + hid_info(ns2->hdev, "DSP version %u.%u.%u\n", ns2->version.dsp_major,
>> + ns2->version.dsp_minor, ns2->version.dsp_patch);
>> +
>> + ret = input_register_device(input);
>> + if (ret < 0) {
>> + hid_err(ns2->hdev, "Failed to register input; ret=%d\n", ret);
>> + return ret;
>> + }
>> +
>> + for (i = 0; i < JC_NUM_LEDS; i++) {
>> + struct led_classdev *led = &ns2->leds[i];
>> + char *name = devm_kasprintf(&input->dev, GFP_KERNEL, "%s:%s:%s",
>> + dev_name(&input->dev),
>> + "green",
>> + joycon_player_led_names[i]);
>> +
>> + if (!name)
>> + return -ENOMEM;
>> +
>> + led->name = name;
>> + ret = devm_led_classdev_register(&input->dev, led);
>> + if (ret < 0) {
>> + dev_err(&input->dev, "Failed to register player %d LED; ret=%d\n",
>> + i + 1, ret);
>> + input_unregister_device(input);
>> + return ret;
>> + }
>> + }
>> +
>> + rcu_assign_pointer(ns2->input, input);
>> + synchronize_rcu();
>> + return 0;
>> +}
>> +
>> +static struct switch2_controller *switch2_get_controller(const char *phys)
>> +{
>> + struct switch2_controller *ns2;
>> +
>> + guard(mutex)(&switch2_controllers_lock);
>> + list_for_each_entry(ns2, &switch2_controllers, entry) {
>> + if (strncmp(ns2->phys, phys, sizeof(ns2->phys)) == 0)
>> + return ns2;
>> + }
>> + ns2 = kzalloc(sizeof(*ns2), GFP_KERNEL);
>> + if (!ns2)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + mutex_init(&ns2->lock);
>> + INIT_LIST_HEAD(&ns2->entry);
>> + list_add(&ns2->entry, &switch2_controllers);
>> + strscpy(ns2->phys, phys, sizeof(ns2->phys));
>> + return ns2;
>> +}
>> +
>> +static void switch2_controller_put(struct switch2_controller *ns2)
>> +{
>> + struct input_dev *input;
>> + bool do_free;
>> +
>> + guard(mutex)(&switch2_controllers_lock);
>> + mutex_lock(&ns2->lock);
>> +
>> + rcu_read_lock();
>> + input = rcu_dereference(ns2->input);
>> + rcu_read_unlock();
>> +
>> + rcu_assign_pointer(ns2->input, NULL);
>> + synchronize_rcu();
>> +
>> + ns2->init_step = 0;
>> + do_free = !ns2->hdev && !ns2->cfg;
>> + mutex_unlock(&ns2->lock);
>> +
>> + if (input)
>> + input_unregister_device(input);
>> +
>> + if (do_free) {
>> + list_del_init(&ns2->entry);
>> + mutex_destroy(&ns2->lock);
>> + kfree(ns2);
>> + }
>> +}
>> +
>> +static bool switch2_parse_stick_calibration(struct switch2_stick_calibration *calib,
>> + const uint8_t *data)
>> +{
>> + static const uint8_t UNCALIBRATED[9] = {
>> + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
>> + };
>> + if (memcmp(UNCALIBRATED, data, sizeof(UNCALIBRATED)) == 0)
>> + return false;
>> +
>> + calib->x.neutral = data[0];
>> + calib->x.neutral |= (data[1] & 0x0F) << 8;
>> +
>> + calib->y.neutral = data[1] >> 4;
>> + calib->y.neutral |= data[2] << 4;
>> +
>> + calib->x.positive = data[3];
>> + calib->x.positive |= (data[4] & 0x0F) << 8;
>> +
>> + calib->y.positive = data[4] >> 4;
>> + calib->y.positive |= data[5] << 4;
>> +
>> + calib->x.negative = data[6];
>> + calib->x.negative |= (data[7] & 0x0F) << 8;
>> +
>> + calib->y.negative = data[7] >> 4;
>> + calib->y.negative |= data[8] << 4;
>> +
>> + return true;
>> +}
>> +
>> +static void switch2_handle_flash_read(struct switch2_controller *ns2, uint8_t size,
>> + uint32_t address, const uint8_t *data)
>> +{
>> + bool ok;
>> +
>> + switch (address) {
>> + case NS2_FLASH_ADDR_SERIAL:
>> + if (size != NS2_FLASH_SIZE_SERIAL)
>> + return;
>> + memcpy(ns2->serial, data, size);
>> + switch2_init_step_done(ns2, NS2_INIT_READ_SERIAL);
>> + break;
>> + case NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB:
>> + if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
>> + return;
>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_PRIMARY_CALIB);
>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], data);
>> + if (ok) {
>> + hid_dbg(ns2->hdev, "Got factory primary stick calibration:\n");
>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
>> + ns2->stick_calib[0].x.negative,
>> + ns2->stick_calib[0].x.neutral,
>> + ns2->stick_calib[0].x.positive);
>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
>> + ns2->stick_calib[0].y.negative,
>> + ns2->stick_calib[0].y.neutral,
>> + ns2->stick_calib[0].y.positive);
>> + } else {
>> + hid_dbg(ns2->hdev, "Factory primary stick calibration not present\n");
>> + }
>> + break;
>> + case NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB:
>> + if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
>> + return;
>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], data);
>> + if (ok) {
>> + hid_dbg(ns2->hdev, "Got factory secondary stick calibration:\n");
>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
>> + ns2->stick_calib[1].x.negative,
>> + ns2->stick_calib[1].x.neutral,
>> + ns2->stick_calib[1].x.positive);
>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
>> + ns2->stick_calib[1].y.negative,
>> + ns2->stick_calib[1].y.neutral,
>> + ns2->stick_calib[1].y.positive);
>> + } else {
>> + hid_dbg(ns2->hdev, "Factory secondary stick calibration not present\n");
>> + }
>> + break;
>> + case NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB:
>> + if (size != NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB)
>> + return;
>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
>> + if (data[0] != 0xFF && data[1] != 0xFF) {
>> + ns2->lt_zero = data[0];
>> + ns2->rt_zero = data[1];
>> +
>> + hid_dbg(ns2->hdev, "Got factory trigger calibration:\n");
>> + hid_dbg(ns2->hdev, "Left zero point: %i\n", ns2->lt_zero);
>> + hid_dbg(ns2->hdev, "Right zero point: %i\n", ns2->rt_zero);
>> + } else {
>> + hid_dbg(ns2->hdev, "Factory trigger calibration not present\n");
>> + }
>> + break;
>> + case NS2_FLASH_ADDR_USER_PRIMARY_CALIB:
>> + if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
>> + return;
>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_PRIMARY_CALIB);
>> + if (__le16_to_cpu(*(__le16 *)data) != NS2_USER_CALIB_MAGIC) {
>> + hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
>> + break;
>> + }
>> +
>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], &data[2]);
>> + if (ok) {
>> + hid_dbg(ns2->hdev, "Got user primary stick calibration:\n");
>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
>> + ns2->stick_calib[0].x.negative,
>> + ns2->stick_calib[0].x.neutral,
>> + ns2->stick_calib[0].x.positive);
>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
>> + ns2->stick_calib[0].y.negative,
>> + ns2->stick_calib[0].y.neutral,
>> + ns2->stick_calib[0].y.positive);
>> + } else {
>> + hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
>> + }
>> + break;
>> + case NS2_FLASH_ADDR_USER_SECONDARY_CALIB:
>> + if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
>> + return;
>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
>> + if (__le16_to_cpu(*(__le16 *)data) != NS2_USER_CALIB_MAGIC) {
>> + hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
>> + break;
>> + }
>> +
>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], &data[2]);
>> + if (ok) {
>> + hid_dbg(ns2->hdev, "Got user secondary stick calibration:\n");
>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
>> + ns2->stick_calib[1].x.negative,
>> + ns2->stick_calib[1].x.neutral,
>> + ns2->stick_calib[1].x.positive);
>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
>> + ns2->stick_calib[1].y.negative,
>> + ns2->stick_calib[1].y.neutral,
>> + ns2->stick_calib[1].y.positive);
>> + } else {
>> + hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
>> + }
>> + break;
>> + }
>> +}
>> +
>> +static void switch2_report_buttons(struct input_dev *input, const uint8_t *bytes,
>> + const struct switch2_ctlr_button_mapping button_mappings[])
>> +{
>> + const struct switch2_ctlr_button_mapping *button;
>> +
>> + for (button = button_mappings; button->code; button++)
>> + input_report_key(input, button->code, bytes[button->byte] & button->bit);
>> +}
>> +
>> +static void switch2_report_axis(struct input_dev *input, struct switch2_axis_calibration *calib,
>> + int axis, bool invert, int value)
>> +{
>> + if (calib && calib->neutral && calib->negative && calib->positive) {
>> + value -= calib->neutral;
>> + value *= NS2_AXIS_MAX + 1;
>> + if (value < 0)
>> + value /= calib->negative;
>> + else
>> + value /= calib->positive;
>> + } else {
>> + value = (value - 2048) * 16;
>> + }
>> +
>> + if (invert)
>> + value = -value;
>> + input_report_abs(input, axis,
>> + clamp(value, NS2_AXIS_MIN, NS2_AXIS_MAX));
>> +}
>> +
>> +static void switch2_report_stick(struct input_dev *input, struct switch2_stick_calibration *calib,
>> + int x, bool invert_x, int y, bool invert_y, const uint8_t *data)
>> +{
>> + switch2_report_axis(input, &calib->x, x, invert_x, data[0] | ((data[1] & 0x0F) << 8));
>> + switch2_report_axis(input, &calib->y, y, invert_y, (data[1] >> 4) | (data[2] << 4));
>> +}
>> +
>> +static void switch2_report_trigger(struct input_dev *input, uint8_t zero, int abs, uint8_t data)
>> +{
>> + int value = (NS2_TRIGGER_RANGE + 1) * (data - zero) / (232 - zero);
>> +
>> + input_report_abs(input, abs, clamp(value, 0, NS2_TRIGGER_RANGE));
>> +}
>> +
>> +static int switch2_event(struct hid_device *hdev, struct hid_report *report, uint8_t *raw_data,
>> + int size)
>> +{
>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
>> + struct input_dev *input;
>> +
>> + if (report->type != HID_INPUT_REPORT)
>> + return 0;
>> +
>> + if (size < 15)
>> + return -EINVAL;
>> +
>> + guard(rcu)();
>> + input = rcu_dereference(ns2->input);
>> +
>> + if (!input)
>> + return 0;
>> +
>> + switch (report->id) {
>> + case NS2_REPORT_UNIFIED:
>> + /*
>> + * TODO
>> + * This won't be sent unless the report type gets changed via command
>> + * 03-0A, but we should support it at some point regardless.
>> + */
>> + break;
>> + case NS2_REPORT_JCL:
>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
>> + ABS_Y, true, &raw_data[6]);
>> + switch2_report_buttons(input, &raw_data[3], ns2_left_joycon_button_mappings);
>> + break;
>> + case NS2_REPORT_JCR:
>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
>> + ABS_Y, true, &raw_data[6]);
>> + switch2_report_buttons(input, &raw_data[3], ns2_right_joycon_button_mappings);
>> + break;
>> + case NS2_REPORT_GC:
>> + input_report_abs(input, ABS_HAT0X,
>> + !!(raw_data[4] & NS2_BTNL_RIGHT) -
>> + !!(raw_data[4] & NS2_BTNL_LEFT));
>> + input_report_abs(input, ABS_HAT0Y,
>> + !!(raw_data[4] & NS2_BTNL_DOWN) -
>> + !!(raw_data[4] & NS2_BTNL_UP));
>> + switch2_report_buttons(input, &raw_data[3], ns2_gccon_mappings);
>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
>> + ABS_Y, true, &raw_data[6]);
>> + switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
>> + ABS_RY, true, &raw_data[9]);
>> + switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[13]);
>> + switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[14]);
>> + break;
>> + case NS2_REPORT_PRO:
>> + input_report_abs(input, ABS_HAT0X,
>> + !!(raw_data[4] & NS2_BTNL_RIGHT) -
>> + !!(raw_data[4] & NS2_BTNL_LEFT));
>> + input_report_abs(input, ABS_HAT0Y,
>> + !!(raw_data[4] & NS2_BTNL_DOWN) -
>> + !!(raw_data[4] & NS2_BTNL_UP));
>> + switch2_report_buttons(input, &raw_data[3], ns2_procon_mappings);
>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
>> + ABS_Y, true, &raw_data[6]);
>> + switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
>> + ABS_RY, true, &raw_data[9]);
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + input_sync(input);
>> + return 0;
>> +}
>> +
>> +static int switch2_features_enable(struct switch2_controller *ns2, int features)
>> +{
>> + __le32 feature_bits = __cpu_to_le32(features);
>> +
>> + if (!ns2->cfg)
>> + return -ENOTCONN;
>> + return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_ENABLE,
>> + &feature_bits, sizeof(feature_bits),
>> + ns2->cfg);
>> +}
>> +
>> +static int switch2_read_flash(struct switch2_controller *ns2, uint32_t address,
>> + uint8_t size)
>> +{
>> + uint8_t message[8] = { size, 0x7e };
>> +
>> + if (!ns2->cfg)
>> + return -ENOTCONN;
>> + *(__le32 *)&message[4] = __cpu_to_le32(address);
>> + return ns2->cfg->send_command(NS2_CMD_FLASH, NS2_SUBCMD_FLASH_READ, message,
>> + sizeof(message), ns2->cfg);
>> +}
>> +
>> +static int switch2_set_player_id(struct switch2_controller *ns2, uint32_t player_id)
>> +{
>> + int i;
>> + int player_led_pattern = player_id % JC_NUM_LED_PATTERNS;
>> +
>> + for (i = 0; i < JC_NUM_LEDS; i++)
>> + ns2->leds[i].brightness = joycon_player_led_patterns[player_led_pattern][i];
>> +
>> + return switch2_set_leds(ns2);
>> +}
>> +
>> +static int switch2_set_report_format(struct switch2_controller *ns2, enum switch2_report_id fmt)
>> +{
>> + __le32 format_id = __cpu_to_le32(fmt);
>> +
>> + if (!ns2->cfg)
>> + return -ENOTCONN;
>> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_SELECT_REPORT,
>> + &format_id, sizeof(format_id),
>> + ns2->cfg);
>> +}
>> +
>> +static int switch2_init_controller(struct switch2_controller *ns2)
>
> This is now a recursive call while in v1 it wasn't. I think I preferred
> the non-recursive version as there was one place where init_step
> state was changed while now I am not sure where it happens (and whether
> there is a code path where we end up in an infinite recursion)
>
> What is the advantage of the recursive version compared to the
> non-recursive one?
>
The old version incremented the step regardless of whether or not it could confirm it had happened. Since the confirmation is now handled with an external step, calling into switch2_init_step_done, the loop condition would become somewhat complicated. I replaced it with explicit tail calls since that make the control flow simplier, and it is always matched with a call to switch2_init_step_done to ensure that the state is always advanced. As such, the number recursive calls is explicitly limited, and the fact that they're tail calls should mean that it doesn't increase the stack depth (unless there's something I don't know about how the kernel is compiled, which is possible in the wake of things like retpoline protections). I suppose I could replace it with a loop, but the condition would be the same so the only real difference would be a `while (ns2->init_step < NS2_INIT_DONE)` instead of the tail calls; the tail calls themselves would just become break statements. There would be no functional difference.
>> +{
>> + if (ns2->init_step == NS2_INIT_DONE)
>> + return 0;
>> +
>> + if (!ns2->cfg)
>> + return -ENOTCONN;
>> +
>> + switch (ns2->init_step) {
>> + case NS2_INIT_READ_SERIAL:
>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_SERIAL,
>> + NS2_FLASH_SIZE_SERIAL);
>> + case NS2_INIT_GET_FIRMWARE_INFO:
>> + return ns2->cfg->send_command(NS2_CMD_FW_INFO, NS2_SUBCMD_FW_INFO_GET,
>> + NULL, 0, ns2->cfg);
>> + case NS2_INIT_READ_FACTORY_PRIMARY_CALIB:
>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB,
>> + NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
>> + case NS2_INIT_READ_FACTORY_SECONDARY_CALIB:
>> + if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
>> + return switch2_init_controller(ns2);
>> + }
>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB,
>> + NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
>> + case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
>> + if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
>> + return switch2_init_controller(ns2);
>> + }
>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
>> + NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
>> + case NS2_INIT_READ_USER_PRIMARY_CALIB:
>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_PRIMARY_CALIB,
>> + NS2_FLASH_SIZE_USER_AXIS_CALIB);
>> + case NS2_INIT_READ_USER_SECONDARY_CALIB:
>> + if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
>> + return switch2_init_controller(ns2);
>> + }
>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_SECONDARY_CALIB,
>> + NS2_FLASH_SIZE_USER_AXIS_CALIB);
>> + case NS2_INIT_SET_FEATURE_MASK:
>> + return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_SET_MASK,
>> + switch2_feature_mask, sizeof(switch2_feature_mask), ns2->cfg);
>> + case NS2_INIT_ENABLE_FEATURES:
>> + return switch2_features_enable(ns2, NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG);
>> + case NS2_INIT_GRIP_BUTTONS:
>> + if (!switch2_ctlr_is_joycon(ns2->ctlr_type)) {
>> + switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
>> + return switch2_init_controller(ns2);
>> + }
>> + return ns2->cfg->send_command(NS2_CMD_GRIP, NS2_SUBCMD_GRIP_ENABLE_BUTTONS,
>> + switch2_one_data, sizeof(switch2_one_data),
>> + ns2->cfg);
>> + case NS2_INIT_REPORT_FORMAT:
>> + switch (ns2->ctlr_type) {
>> + case NS2_CTLR_TYPE_JCL:
>> + return switch2_set_report_format(ns2, NS2_REPORT_JCL);
>> + case NS2_CTLR_TYPE_JCR:
>> + return switch2_set_report_format(ns2, NS2_REPORT_JCR);
>> + case NS2_CTLR_TYPE_PRO:
>> + return switch2_set_report_format(ns2, NS2_REPORT_PRO);
>> + case NS2_CTLR_TYPE_GC:
>> + return switch2_set_report_format(ns2, NS2_REPORT_GC);
>> + default:
>> + switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
>> + return switch2_init_controller(ns2);
>> + }
>> + case NS2_INIT_SET_PLAYER_LEDS:
>> + return switch2_set_player_id(ns2, ns2->player_id);
>> + case NS2_INIT_INPUT:
>> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_USB,
>> + switch2_init_cmd_data, sizeof(switch2_init_cmd_data), ns2->cfg);
>> + case NS2_INIT_FINISH:
>> + if (ns2->hdev)
>
> If this is not set we skip the switch2_init_input call but don't error
> out. Is this intentional (are we expecting ns2->hdev to be populated at
> a later time and this step retried, perhaps)?
This is intentional, yes. There are a handful of places this can get called, including the function that sets ns2->hdev in the first place (switch2_probe). It's to ensure the steps start as soon as possible (when the cfg pointer gets set) but it can't finish until the hdev gets set, which can be either before or after, depending on the ordering the interfaces get enumerated.
>
>> + return switch2_init_input(ns2);
>> + break;
>> + default:
>> + WARN_ON_ONCE(1);
>> + break;
>> + }
>> + return 0;
>> +}
>> +
>> +int switch2_receive_command(struct switch2_controller *ns2,
>> + const uint8_t *message, size_t length)
>> +{
>> + const struct switch2_cmd_header *header;
>> + int ret = 0;
>> +
>> + if (length < 8)
>> + return -EINVAL;
>> +
>> + print_hex_dump_debug("got cmd: ", DUMP_PREFIX_OFFSET, 16, 1, message, length, false);
>> +
>> + guard(mutex)(&ns2->lock);
>> +
>> + header = (const struct switch2_cmd_header *)message;
>> + if (!(header->flags & NS2_FLAG_OK)) {
>> + ret = -EIO;
>> + goto exit;
>> + }
>> + message = &message[8];
>> + switch (header->command) {
>> + case NS2_CMD_FLASH:
>> + if (header->subcommand == NS2_SUBCMD_FLASH_READ) {
>> + uint8_t read_size;
>> + uint32_t read_address;
>> +
>> + if (length < 16) {
>> + ret = -EINVAL;
>> + goto exit;
>> + }
>> + read_size = message[0];
>> + read_address = __le32_to_cpu(*(__le32 *)&message[4]);
>> + if (length < read_size + 16) {
>> + ret = -EINVAL;
>> + goto exit;
>> + }
>> + switch2_handle_flash_read(ns2, read_size, read_address, &message[8]);
>> + }
>> + break;
>> + case NS2_CMD_INIT:
>> + if (header->subcommand == NS2_SUBCMD_INIT_USB)
>> + switch2_init_step_done(ns2, NS2_INIT_INPUT);
>> + else if (header->subcommand == NS2_SUBCMD_INIT_SELECT_REPORT)
>> + switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
>> + break;
>> + case NS2_CMD_GRIP:
>> + if (header->subcommand == NS2_SUBCMD_GRIP_ENABLE_BUTTONS)
>> + switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
>> + break;
>> + case NS2_CMD_LED:
>> + if (header->subcommand == NS2_SUBCMD_LED_PATTERN)
>> + switch2_init_step_done(ns2, NS2_INIT_SET_PLAYER_LEDS);
>> + break;
>> + case NS2_CMD_FEATSEL:
>> + if (header->subcommand == NS2_SUBCMD_FEATSEL_SET_MASK)
>> + switch2_init_step_done(ns2, NS2_INIT_SET_FEATURE_MASK);
>> + else if (header->subcommand == NS2_SUBCMD_FEATSEL_ENABLE)
>> + switch2_init_step_done(ns2, NS2_INIT_ENABLE_FEATURES);
>> + break;
>> + case NS2_CMD_FW_INFO:
>> + if (header->subcommand == NS2_SUBCMD_FW_INFO_GET) {
>> + if (length < sizeof(ns2->version)) {
>> + ret = -EINVAL;
>> + goto exit;
>> + }
>> + memcpy(&ns2->version, message, sizeof(ns2->version));
>> + ns2->ctlr_type = ns2->version.ctlr_type;
>> + switch2_init_step_done(ns2, NS2_INIT_GET_FIRMWARE_INFO);
>> + }
>> + break;
>> + default:
>> + break;
>> + }
>> +
>> +exit:
>> + if (ns2->init_step < NS2_INIT_DONE)
>> + switch2_init_controller(ns2);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(switch2_receive_command);
>> +
>> +int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg)
>> +{
>> + struct switch2_controller *ns2 = switch2_get_controller(phys);
>> +
>> + if (IS_ERR(ns2))
>> + return PTR_ERR(ns2);
>> +
>> + cfg->parent = ns2;
>> +
>> + guard(mutex)(&ns2->lock);
>> + WARN_ON(ns2->cfg);
>> + ns2->cfg = cfg;
>> +
>> + if (ns2->hdev)
>> + return switch2_init_controller(ns2);
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(switch2_controller_attach_cfg);
>> +
>> +void switch2_controller_detach_cfg(struct switch2_controller *ns2)
>> +{
>> + mutex_lock(&ns2->lock);
>> + WARN_ON(ns2 != ns2->cfg->parent);
>> + ns2->cfg = NULL;
>> + mutex_unlock(&ns2->lock);
>> + switch2_controller_put(ns2);
>> +}
>> +EXPORT_SYMBOL_GPL(switch2_controller_detach_cfg);
>> +
>> +static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id)
>> +{
>> + struct switch2_controller *ns2;
>> + struct usb_device *udev;
>> + char phys[64];
>> + int ret;
>> +
>> + if (!hid_is_usb(hdev))
>> + return -ENODEV;
>> +
>> + udev = hid_to_usb_dev(hdev);
>> + if (usb_make_path(udev, phys, sizeof(phys)) < 0)
>> + return -EINVAL;
>> +
>> + ret = hid_parse(hdev);
>> + if (ret) {
>> + hid_err(hdev, "parse failed %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
>> + if (ret) {
>> + hid_err(hdev, "hw_start failed %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = hid_hw_open(hdev);
>> + if (ret) {
>> + hid_err(hdev, "hw_open failed %d\n", ret);
>> + goto err_stop;
>> + }
>
> For the Switch 1 controllers we are calling hid_device_io_start after
> hid_hw_open. Is this not necessary in this case?
Since we don't do any HID I/O during probe it's not needed; the startup configuration is on the cfg interface instead.
>
>> +
>> + ns2 = switch2_get_controller(phys);
>> + if (!ns2) {
>
> switch2_get_controller returns an err pointer in case of ENOMEM, not
> NULL so I think this check has to be changed.
Fixed locally, thanks. I'll send that out with v4 (this was actually v3 but I thought it was v2, oops).
>
>> + ret = -ENOMEM;
>> + goto err_close;
>> + }
>> +
>> + guard(mutex)(&ns2->lock);
>> + WARN_ON(ns2->hdev);
>> + ns2->hdev = hdev;
>> + switch (hdev->product | (hdev->vendor << 16)) {
>> + default:
>> + strscpy(ns2->name, hdev->name, sizeof(ns2->name));
>> + break;
>> + /* Some controllers have slightly wrong names so we override them */
>> + case USB_DEVICE_ID_NINTENDO_NS2_JOYCONR | (USB_VENDOR_ID_NINTENDO << 16):
>> + /* Missing the "2" in the name */
>> + strscpy(ns2->name, "Nintendo Joy-Con 2 (R)", sizeof(ns2->name));
>> + break;
>> + case USB_DEVICE_ID_NINTENDO_NS2_GCCON | (USB_VENDOR_ID_NINTENDO << 16):
>> + /* Has "Nintendo" in the name twice */
>> + strscpy(ns2->name, "Nintendo GameCube Controller", sizeof(ns2->name));
>> + break;
>> + }
>> +
>> + ns2->player_id = U32_MAX;
>> + ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
>> + if (ret < 0)
>> + hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
>> + else
>> + ns2->player_id = ret;
>> +
>> + switch2_leds_create(ns2);
>> +
>> + hid_set_drvdata(hdev, ns2);
>> +
>> + if (ns2->cfg)
>> + return switch2_init_controller(ns2);
>> +
>> + return 0;
>> +
>> +err_close:
>> + hid_hw_close(hdev);
>> +err_stop:
>> + hid_hw_stop(hdev);
>> +
>> + return ret;
>> +}
>> +
>> +static void switch2_remove(struct hid_device *hdev)
>> +{
>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
>> +
>> + hid_hw_close(hdev);
>> + mutex_lock(&ns2->lock);
>> + WARN_ON(ns2->hdev != hdev);
>> + ns2->hdev = NULL;
>> + mutex_unlock(&ns2->lock);
>> + ida_free(&nintendo_player_id_allocator, ns2->player_id);
>> + switch2_controller_put(ns2);
>> + hid_hw_stop(hdev);
>> +}
>> +
>> static const struct hid_device_id nintendo_hid_devices[] = {
>> + /* Switch devices */
>> { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>> USB_DEVICE_ID_NINTENDO_PROCON) },
>> { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>> @@ -2813,10 +3935,69 @@ static const struct hid_device_id nintendo_hid_devices[] = {
>> USB_DEVICE_ID_NINTENDO_GENCON) },
>> { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
>> USB_DEVICE_ID_NINTENDO_N64CON) },
>> + /* Switch 2 devices */
>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>> + USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>> + USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>> + USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>> + USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
>> { }
>> };
>> MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
>>
>> +static bool nintendo_is_switch2(struct hid_device *hdev)
>> +{
>> + return hdev->vendor == USB_VENDOR_ID_NINTENDO &&
>> + hdev->product >= USB_DEVICE_ID_NINTENDO_NS2_JOYCONR;
>> +}
>> +
>> +static void nintendo_hid_remove(struct hid_device *hdev)
>> +{
>> + if (nintendo_is_switch2(hdev))
>> + switch2_remove(hdev);
>> + else
>> + joycon_remove(hdev);
>> +}
>> +
>> +static int nintendo_hid_event(struct hid_device *hdev,
>> + struct hid_report *report, u8 *raw_data, int size)
>> +{
>> + if (nintendo_is_switch2(hdev))
>> + return switch2_event(hdev, report, raw_data, size);
>> + else
>> + return joycon_event(hdev, report, raw_data, size);
>> +}
>> +
>> +static int nintendo_hid_probe(struct hid_device *hdev,
>> + const struct hid_device_id *id)
>> +{
>> + if (nintendo_is_switch2(hdev))
>> + return switch2_probe(hdev, id);
>> + else
>> + return joycon_probe(hdev, id);
>> +}
>> +
>> +#ifdef CONFIG_PM
>> +static int nintendo_hid_resume(struct hid_device *hdev)
>> +{
>> + if (nintendo_is_switch2(hdev))
>> + return 0;
>> + else
>> + return joycon_resume(hdev);
>> +}
>> +
>> +static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
>> +{
>> + if (nintendo_is_switch2(hdev))
>> + return 0;
>> + else
>> + return joycon_suspend(hdev, message);
>> +}
>> +#endif
>> +
>> static struct hid_driver nintendo_hid_driver = {
>> .name = "nintendo",
>> .id_table = nintendo_hid_devices,
>> @@ -2844,4 +4025,5 @@ MODULE_LICENSE("GPL");
>> MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
>> MODULE_AUTHOR("Emily Strickland <linux@emily.st>");
>> MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>");
>> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
>> MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");
>> diff --git a/drivers/hid/hid-nintendo.h b/drivers/hid/hid-nintendo.h
>> new file mode 100644
>> index 0000000000000..7aff22f302661
>> --- /dev/null
>> +++ b/drivers/hid/hid-nintendo.h
>> @@ -0,0 +1,72 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * HID driver for Nintendo Switch 2 controllers
>> + *
>> + * Copyright (c) 2025 Valve Software
>> + *
>> + * This driver is based on the following work:
>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
>> + * https://github.com/ndeadly/switch2_controller_research
>> + */
>> +
>> +#ifndef __HID_NINTENDO_H
>> +#define __HID_NINTENDO_H
>> +
>> +#include <linux/bits.h>
>> +
>> +#define NS2_FLAG_OK BIT(0)
>> +#define NS2_FLAG_NACK BIT(2)
>> +
>> +enum switch2_cmd {
>> + NS2_CMD_NFC = 0x01,
>> + NS2_CMD_FLASH = 0x02,
>> + NS2_CMD_INIT = 0x03,
>> + NS2_CMD_GRIP = 0x08,
>> + NS2_CMD_LED = 0x09,
>> + NS2_CMD_VIBRATE = 0x0a,
>> + NS2_CMD_BATTERY = 0x0b,
>> + NS2_CMD_FEATSEL = 0x0c,
>> + NS2_CMD_FW_UPD = 0x0d,
>> + NS2_CMD_FW_INFO = 0x10,
>> + NS2_CMD_BT_PAIR = 0x15,
>> +};
>> +
>> +enum switch2_direction {
>> + NS2_DIR_IN = 0x00,
>> + NS2_DIR_OUT = 0x90,
>> +};
>> +
>> +enum switch2_transport {
>> + NS2_TRANS_USB = 0x00,
>> + NS2_TRANS_BT = 0x01,
>> +};
>> +
>> +struct switch2_cmd_header {
>> + uint8_t command;
>> + uint8_t flags;
>> + uint8_t transport;
>> + uint8_t subcommand;
>> + uint8_t unk1;
>> + uint8_t length;
>> + uint16_t unk2;
>> +};
>> +static_assert(sizeof(struct switch2_cmd_header) == 8);
>> +
>> +struct device;
>> +struct switch2_controller;
>> +struct switch2_cfg_intf {
>> + struct switch2_controller *parent;
>> + struct device *dev;
>> +
>> + int (*send_command)(enum switch2_cmd command, uint8_t subcommand,
>> + const void *message, size_t length,
>> + struct switch2_cfg_intf *intf);
>> +};
>> +
>> +int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg);
>> +void switch2_controller_detach_cfg(struct switch2_controller *controller);
>> +
>> +int switch2_receive_command(struct switch2_controller *controller,
>> + const uint8_t *message, size_t length);
>> +
>> +#endif
>> diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
>> index 7755e5b454d2c..868262c6ccd9a 100644
>> --- a/drivers/input/joystick/Kconfig
>> +++ b/drivers/input/joystick/Kconfig
>> @@ -422,4 +422,15 @@ config JOYSTICK_SEESAW
>> To compile this driver as a module, choose M here: the module will be
>> called adafruit-seesaw.
>>
>> +config JOYSTICK_NINTENDO_SWITCH2_USB
>> + tristate "Wired Nintendo Switch 2 controller support"
>> + depends on HID_NINTENDO
>> + depends on USB
>> + help
>> + Say Y here if you want to enable support for wired Nintendo Switch 2
>> + controllers.
>> +
>> + To compile this driver as a module, choose M here: the
>> + module will be called nintendo-switch2-usb.
>> +
>> endif
>> diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
>> index 9976f596a9208..8f92900ae8856 100644
>> --- a/drivers/input/joystick/Makefile
>> +++ b/drivers/input/joystick/Makefile
>> @@ -34,6 +34,7 @@ obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o
>> obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o
>> obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o
>> obj-$(CONFIG_JOYSTICK_STINGER) += stinger.o
>> +obj-$(CONFIG_JOYSTICK_NINTENDO_SWITCH2_USB) += nintendo-switch2-usb.o
>> obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o
>> obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o
>> obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
>> diff --git a/drivers/input/joystick/nintendo-switch2-usb.c b/drivers/input/joystick/nintendo-switch2-usb.c
>> new file mode 100644
>> index 0000000000000..ebd89d852e21a
>> --- /dev/null
>> +++ b/drivers/input/joystick/nintendo-switch2-usb.c
>> @@ -0,0 +1,353 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * USB driver for Nintendo Switch 2 controllers configuration interface
>> + *
>> + * Copyright (c) 2025 Valve Software
>> + *
>> + * This driver is based on the following work:
>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
>> + * https://github.com/ndeadly/switch2_controller_research
>> + */
>> +
>> +#include "../../hid/hid-ids.h"
>> +#include "../../hid/hid-nintendo.h"
>> +#include <linux/module.h>
>> +#include <linux/usb/input.h>
>> +
>> +#define NS2_BULK_SIZE 64
>> +#define NS2_IN_URBS 2
>> +#define NS2_OUT_URBS 4
>> +
>> +static struct usb_driver switch2_usb;
>> +
>> +struct switch2_urb {
>> + struct urb *urb;
>> + uint8_t *data;
>> + bool active;
>> +};
>> +
>> +struct switch2_usb {
>> + struct switch2_cfg_intf cfg;
>> + struct usb_device *udev;
>> +
>> + struct switch2_urb bulk_in[NS2_IN_URBS];
>> + struct usb_anchor bulk_in_anchor;
>> + spinlock_t bulk_in_lock;
>> +
>> + struct switch2_urb bulk_out[NS2_OUT_URBS];
>> + struct usb_anchor bulk_out_anchor;
>> + spinlock_t bulk_out_lock;
>> +
>> + int message_in;
>> + struct work_struct message_in_work;
>> +};
>> +
>> +static void switch2_bulk_in(struct urb *urb)
>> +{
>> + struct switch2_usb *ns2_usb = urb->context;
>> + int i;
>> + bool schedule = false;
>> + unsigned long flags;
>> +
>> + switch (urb->status) {
>> + case 0:
>> + schedule = true;
>> + break;
>> + case -ECONNRESET:
>> + case -ENOENT:
>> + case -ESHUTDOWN:
>> + dev_dbg(&ns2_usb->udev->dev, "shutting down input urb: %d\n", urb->status);
>> + return;
>> + default:
>> + dev_dbg(&ns2_usb->udev->dev, "unknown input urb status: %d\n", urb->status);
>> + break;
>> + }
>> +
>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
>> + for (i = 0; i < NS2_IN_URBS; i++) {
>> + int err;
>> + struct switch2_urb *ns2_urb;
>> +
>> + if (ns2_usb->bulk_in[i].urb == urb) {
>> + ns2_usb->message_in = i;
>> + continue;
>> + }
>> +
>> + if (ns2_usb->bulk_in[i].active)
>> + continue;
>> +
>> + ns2_urb = &ns2_usb->bulk_in[i];
>> + usb_anchor_urb(ns2_urb->urb, &ns2_usb->bulk_in_anchor);
>> + err = usb_submit_urb(ns2_urb->urb, GFP_ATOMIC);
>> + if (err) {
>> + usb_unanchor_urb(ns2_urb->urb);
>> + dev_dbg(&ns2_usb->udev->dev, "failed to queue input urb: %d\n", err);
>> + } else {
>> + ns2_urb->active = true;
>> + }
>> + }
>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
>> +
>> + if (schedule)
>> + schedule_work(&ns2_usb->message_in_work);
>> +}
>> +
>> +static void switch2_bulk_out(struct urb *urb)
>> +{
>> + struct switch2_usb *ns2_usb = urb->context;
>> + int i;
>> +
>> + guard(spinlock_irqsave)(&ns2_usb->bulk_out_lock);
>> +
>> + switch (urb->status) {
>> + case 0:
>> + break;
>> + case -ECONNRESET:
>> + case -ENOENT:
>> + case -ESHUTDOWN:
>> + dev_dbg(&ns2_usb->udev->dev, "shutting down output urb: %d\n", urb->status);
>> + return;
>> + default:
>> + dev_dbg(&ns2_usb->udev->dev, "unknown output urb status: %d\n", urb->status);
>> + return;
>> + }
>> +
>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>> + if (ns2_usb->bulk_out[i].urb != urb)
>> + continue;
>> +
>> + ns2_usb->bulk_out[i].active = false;
>> + break;
>> + }
>> +}
>> +
>> +static int switch2_usb_send_cmd(enum switch2_cmd command, uint8_t subcommand,
>> + const void *message, size_t size, struct switch2_cfg_intf *cfg)
>> +{
>> + struct switch2_usb *ns2_usb = (struct switch2_usb *)cfg;
>> + struct switch2_urb *urb = NULL;
>> + int i;
>> + int ret;
>> + unsigned long flags;
>> +
>> + struct switch2_cmd_header header = {
>> + command, NS2_DIR_OUT | NS2_FLAG_OK, NS2_TRANS_USB, subcommand, 0, size
>> + };
>> +
>> + if (WARN_ON(size > 56))
>> + return -EINVAL;
>> +
>> + spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>> + if (ns2_usb->bulk_out[i].active)
>> + continue;
>> +
>> + urb = &ns2_usb->bulk_out[i];
>> + urb->active = true;
>> + break;
>> + }
>> + spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
>> +
>> + if (!urb) {
>> + dev_warn(&ns2_usb->udev->dev, "output queue full, dropping message\n");
>> + return -ENOBUFS;
>> + }
>> +
>> + memcpy(urb->data, &header, sizeof(header));
>> + if (message && size)
>> + memcpy(&urb->data[8], message, size);
>> + urb->urb->transfer_buffer_length = size + sizeof(header);
>> +
>> + print_hex_dump_debug("sending cmd: ", DUMP_PREFIX_OFFSET, 16, 1, urb->data,
>> + size + sizeof(header), false);
>> +
>> + usb_anchor_urb(urb->urb, &ns2_usb->bulk_out_anchor);
>> + ret = usb_submit_urb(urb->urb, GFP_ATOMIC);
>> + if (ret) {
>> + if (ret != -ENODEV)
>> + dev_warn(&ns2_usb->udev->dev, "failed to submit output urb: %i", ret);
>> + urb->active = false;
>> + usb_unanchor_urb(urb->urb);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void switch2_usb_message_in_work(struct work_struct *work)
>> +{
>> + struct switch2_usb *ns2_usb = container_of(work, struct switch2_usb, message_in_work);
>> + struct switch2_urb *urb;
>> + int err;
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
>> + urb = &ns2_usb->bulk_in[ns2_usb->message_in];
>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
>> +
>> + err = switch2_receive_command(ns2_usb->cfg.parent, urb->urb->transfer_buffer,
>> + urb->urb->actual_length);
>> + if (err)
>> + dev_dbg(&ns2_usb->udev->dev, "receive command failed: %d\n", err);
>> +
>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
>> + urb->active = false;
>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
>> +}
>> +
>> +static int switch2_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
>> +{
>> + struct switch2_usb *ns2_usb;
>> + struct usb_device *udev;
>> + struct usb_endpoint_descriptor *bulk_in, *bulk_out;
>> + char phys[64];
>> + int ret;
>> + int i;
>> +
>> + udev = interface_to_usbdev(intf);
>> + if (usb_make_path(udev, phys, sizeof(phys)) < 0)
>> + return -EINVAL;
>> +
>> + ret = usb_find_common_endpoints(intf->cur_altsetting, &bulk_in, &bulk_out, NULL, NULL);
>> + if (ret) {
>> + dev_err(&intf->dev, "failed to find bulk EPs\n");
>> + return ret;
>> + }
>> +
>> + ns2_usb = devm_kzalloc(&intf->dev, sizeof(*ns2_usb), GFP_KERNEL);
>> + if (!ns2_usb)
>> + return -ENOMEM;
>> +
>> + ns2_usb->udev = udev;
>> + for (i = 0; i < NS2_IN_URBS; i++) {
>> + ns2_usb->bulk_in[i].urb = usb_alloc_urb(0, GFP_KERNEL);
>> + if (!ns2_usb->bulk_in[i].urb) {
>> + ret = -ENOMEM;
>> + goto err_free_in;
>> + }
>> +
>> + ns2_usb->bulk_in[i].data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
>> + &ns2_usb->bulk_in[i].urb->transfer_dma);
>> + if (!ns2_usb->bulk_in[i].data) {
>> + ret = -ENOMEM;
>> + goto err_free_in;
>> + }
>> +
>> + usb_fill_bulk_urb(ns2_usb->bulk_in[i].urb, udev,
>> + usb_rcvbulkpipe(udev, bulk_in->bEndpointAddress),
>> + ns2_usb->bulk_in[i].data, NS2_BULK_SIZE, switch2_bulk_in, ns2_usb);
>> + ns2_usb->bulk_in[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
>> + }
>> +
>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>> + ns2_usb->bulk_out[i].urb = usb_alloc_urb(0, GFP_KERNEL);
>> + if (!ns2_usb->bulk_out[i].urb) {
>> + ret = -ENOMEM;
>> + goto err_free_out;
>> + }
>> +
>> + ns2_usb->bulk_out[i].data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
>> + &ns2_usb->bulk_out[i].urb->transfer_dma);
>> + if (!ns2_usb->bulk_out[i].data) {
>> + ret = -ENOMEM;
>> + goto err_free_out;
>> + }
>> +
>> + usb_fill_bulk_urb(ns2_usb->bulk_out[i].urb, udev,
>> + usb_sndbulkpipe(udev, bulk_out->bEndpointAddress),
>> + ns2_usb->bulk_out[i].data, NS2_BULK_SIZE, switch2_bulk_out, ns2_usb);
>> + ns2_usb->bulk_out[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
>> + }
>> +
>> + ns2_usb->bulk_in[0].active = true;
>> + ret = usb_submit_urb(ns2_usb->bulk_in[0].urb, GFP_ATOMIC);
>> + if (ret < 0)
>> + goto err_free_out;
>> +
>> + init_usb_anchor(&ns2_usb->bulk_out_anchor);
>> + spin_lock_init(&ns2_usb->bulk_out_lock);
>> + init_usb_anchor(&ns2_usb->bulk_in_anchor);
>> + spin_lock_init(&ns2_usb->bulk_in_lock);
>> + INIT_WORK(&ns2_usb->message_in_work, switch2_usb_message_in_work);
>> +
>> + usb_set_intfdata(intf, ns2_usb);
>> +
>> + ns2_usb->cfg.dev = &ns2_usb->udev->dev;
>> + ns2_usb->cfg.send_command = switch2_usb_send_cmd;
>> +
>> + ret = switch2_controller_attach_cfg(phys, &ns2_usb->cfg);
>> + if (ret < 0)
>> + goto err_kill_urb;
>> +
>> + return 0;
>> +
>> +err_kill_urb:
>> + usb_kill_urb(ns2_usb->bulk_in[0].urb);
>> +err_free_out:
>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_out[i].data,
>> + ns2_usb->bulk_out[i].urb->transfer_dma);
>> + usb_free_urb(ns2_usb->bulk_out[i].urb);
>> + }
>> +err_free_in:
>> + for (i = 0; i < NS2_IN_URBS; i++) {
>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_in[i].data,
>> + ns2_usb->bulk_in[i].urb->transfer_dma);
>> + usb_free_urb(ns2_usb->bulk_in[i].urb);
>> + }
>> + devm_kfree(&intf->dev, ns2_usb);
>> +
>> + return ret;
>> +}
>> +
>> +static void switch2_usb_disconnect(struct usb_interface *intf)
>> +{
>> + struct switch2_usb *ns2_usb = usb_get_intfdata(intf);
>> + unsigned long flags;
>> + int i;
>> +
>> + spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
>> + usb_kill_anchored_urbs(&ns2_usb->bulk_out_anchor);
>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_out[i].data,
>> + ns2_usb->bulk_out[i].urb->transfer_dma);
>> + usb_free_urb(ns2_usb->bulk_out[i].urb);
>> + }
>> + spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
>> +
>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
>> + usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor);
>> + cancel_work_sync(&ns2_usb->message_in_work);
>> + for (i = 0; i < NS2_IN_URBS; i++) {
>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_in[i].data,
>> + ns2_usb->bulk_in[i].urb->transfer_dma);
>> + usb_free_urb(ns2_usb->bulk_in[i].urb);
>> + }
>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
>> +
>> + switch2_controller_detach_cfg(ns2_usb->cfg.parent);
>
> As we have allocated ns2_usb with devm_kzalloc, don't we need to free it
> with devm_kfree again?
Devres will automatically free it when the device is freed. As this is the callback for the device going away, it's freed directly after this function exits. That's why I'm using devm_kzalloc instead of kzalloc.
>
> Cheers,
> Silvan
>
>> +}
>> +
>> +#define SWITCH2_CONTROLLER(vend, prod) \
>> + USB_DEVICE_AND_INTERFACE_INFO(vend, prod, USB_CLASS_VENDOR_SPEC, 0, 0)
>> +
>> +static const struct usb_device_id switch2_usb_devices[] = {
>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(usb, switch2_usb_devices);
>> +
>> +static struct usb_driver switch2_usb = {
>> + .name = "switch2",
>> + .id_table = switch2_usb_devices,
>> + .probe = switch2_usb_probe,
>> + .disconnect = switch2_usb_disconnect,
>> +};
>> +module_usb_driver(switch2_usb);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
>> +MODULE_DESCRIPTION("Driver for Nintendo Switch 2 Controllers");
>
>
Vicki
^ permalink raw reply
* [PATCH 2/2] PCI: Remove no_pci_devices
From: Heiner Kallweit @ 2026-04-02 22:18 UTC (permalink / raw)
To: Dmitry Torokhov, Bjorn Helgaas
Cc: open list:HID CORE LAYER, linux-pci@vger.kernel.org
In-Reply-To: <dd9ea7c1-44f5-4364-a3d5-885b1c99159f@gmail.com>
After having removed the last usage of no_pci_devices(), this function
can be removed.
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
---
drivers/pci/probe.c | 17 -----------------
include/linux/pci.h | 3 ---
2 files changed, 20 deletions(-)
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index bccc7a4bd..19d73f613 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -67,23 +67,6 @@ static struct resource *get_pci_domain_busn_res(int domain_nr)
return &r->res;
}
-/*
- * Some device drivers need know if PCI is initiated.
- * Basically, we think PCI is not initiated when there
- * is no device to be found on the pci_bus_type.
- */
-int no_pci_devices(void)
-{
- struct device *dev;
- int no_devices;
-
- dev = bus_find_next_device(&pci_bus_type, NULL);
- no_devices = (dev == NULL);
- put_device(dev);
- return no_devices;
-}
-EXPORT_SYMBOL(no_pci_devices);
-
/*
* PCI Bus Class
*/
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 8861eeb43..cc98713e3 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1201,8 +1201,6 @@ extern const struct bus_type pci_bus_type;
/* Do NOT directly access these two variables, unless you are arch-specific PCI
* code, or PCI core code. */
extern struct list_head pci_root_buses; /* List of all known PCI buses */
-/* Some device drivers need know if PCI is initiated */
-int no_pci_devices(void);
void pcibios_resource_survey_bus(struct pci_bus *bus);
void pcibios_bus_add_device(struct pci_dev *pdev);
@@ -2140,7 +2138,6 @@ static inline struct pci_dev *pci_get_base_class(unsigned int class,
static inline int pci_dev_present(const struct pci_device_id *ids)
{ return 0; }
-#define no_pci_devices() (1)
#define pci_dev_put(dev) do { } while (0)
static inline void pci_set_master(struct pci_dev *dev) { }
--
2.53.0
^ permalink raw reply related
* [PATCH 1/2] input: pc110pad: change PCI check to get rid of orphaned no_pci_devices
From: Heiner Kallweit @ 2026-04-02 22:17 UTC (permalink / raw)
To: Dmitry Torokhov, Bjorn Helgaas
Cc: open list:HID CORE LAYER, linux-pci@vger.kernel.org
In-Reply-To: <dd9ea7c1-44f5-4364-a3d5-885b1c99159f@gmail.com>
As a prerequisite for removing no_pci_devices(), replace its usage here
with an equivalent check for presence of a PCI bus.
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
---
drivers/input/mouse/pc110pad.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/mouse/pc110pad.c b/drivers/input/mouse/pc110pad.c
index efa58049f..c7a6df210 100644
--- a/drivers/input/mouse/pc110pad.c
+++ b/drivers/input/mouse/pc110pad.c
@@ -91,7 +91,7 @@ static int __init pc110pad_init(void)
{
int err;
- if (!no_pci_devices())
+ if (pci_find_next_bus(NULL))
return -ENODEV;
if (!request_region(pc110pad_io, 4, "pc110pad")) {
--
2.53.0
^ permalink raw reply related
* [PATCH 0/2] PCI: Remove no_pci_devices
From: Heiner Kallweit @ 2026-04-02 22:15 UTC (permalink / raw)
To: Dmitry Torokhov, Bjorn Helgaas
Cc: open list:HID CORE LAYER, linux-pci@vger.kernel.org
Remove the last user of no_pci_devices(), and then the function itself.
Heiner Kallweit (2):
input: pc110pad: change PCI check to get rid of orphaned
no_pci_devices
PCI: Remove no_pci_devices
drivers/input/mouse/pc110pad.c | 2 +-
drivers/pci/probe.c | 17 -----------------
include/linux/pci.h | 3 ---
3 files changed, 1 insertion(+), 21 deletions(-)
--
2.53.0
^ permalink raw reply
* [dtor-input:next] BUILD SUCCESS f13b7800929df0df0ba2407226ba1b627b482c92
From: kernel test robot @ 2026-04-02 19:39 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
branch HEAD: f13b7800929df0df0ba2407226ba1b627b482c92 Input: usbtouchscreen - refactor endpoint lookup
elapsed time: 742m
configs tested: 182
configs skipped: 2
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc defconfig gcc-15.2.0
arc randconfig-001-20260402 gcc-11.5.0
arc randconfig-002-20260402 gcc-11.5.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm defconfig gcc-15.2.0
arm orion5x_defconfig clang-23
arm randconfig-001-20260402 gcc-11.5.0
arm randconfig-002-20260402 gcc-11.5.0
arm randconfig-003-20260402 gcc-11.5.0
arm randconfig-004-20260402 gcc-11.5.0
arm spear13xx_defconfig gcc-15.2.0
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260402 gcc-15.2.0
arm64 randconfig-002-20260402 gcc-15.2.0
arm64 randconfig-003-20260402 gcc-15.2.0
arm64 randconfig-004-20260402 gcc-15.2.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260402 gcc-15.2.0
csky randconfig-002-20260402 gcc-15.2.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260402 clang-18
hexagon randconfig-001-20260403 clang-23
hexagon randconfig-002-20260402 clang-18
hexagon randconfig-002-20260403 clang-23
i386 allmodconfig clang-20
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001-20260402 clang-20
i386 buildonly-randconfig-001-20260403 gcc-14
i386 buildonly-randconfig-002-20260402 clang-20
i386 buildonly-randconfig-002-20260403 gcc-14
i386 buildonly-randconfig-003-20260402 clang-20
i386 buildonly-randconfig-003-20260403 gcc-14
i386 buildonly-randconfig-004-20260402 clang-20
i386 buildonly-randconfig-004-20260403 gcc-14
i386 buildonly-randconfig-005-20260402 clang-20
i386 buildonly-randconfig-005-20260403 gcc-14
i386 buildonly-randconfig-006-20260402 clang-20
i386 buildonly-randconfig-006-20260403 gcc-14
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260402 clang-20
i386 randconfig-002-20260402 clang-20
i386 randconfig-003-20260402 clang-20
i386 randconfig-004-20260402 clang-20
i386 randconfig-005-20260402 clang-20
i386 randconfig-006-20260402 clang-20
i386 randconfig-007-20260402 clang-20
i386 randconfig-011-20260402 clang-20
i386 randconfig-012-20260402 clang-20
i386 randconfig-013-20260402 clang-20
i386 randconfig-014-20260402 clang-20
i386 randconfig-015-20260402 clang-20
i386 randconfig-016-20260402 clang-20
i386 randconfig-017-20260402 clang-20
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260402 clang-18
loongarch randconfig-001-20260403 clang-23
loongarch randconfig-002-20260402 clang-18
loongarch randconfig-002-20260403 clang-23
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k defconfig clang-19
m68k stmark2_defconfig gcc-15.2.0
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001-20260402 clang-18
nios2 randconfig-001-20260403 clang-23
nios2 randconfig-002-20260402 clang-18
nios2 randconfig-002-20260403 clang-23
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260402 clang-20
parisc randconfig-002-20260402 clang-20
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc randconfig-001-20260402 clang-20
powerpc randconfig-002-20260402 clang-20
powerpc64 randconfig-001-20260402 clang-20
powerpc64 randconfig-002-20260402 clang-20
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260402 clang-23
riscv randconfig-002-20260402 clang-23
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260402 clang-23
s390 randconfig-002-20260402 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allyesconfig clang-19
sh defconfig gcc-14
sh randconfig-001-20260402 clang-23
sh randconfig-002-20260402 clang-23
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260402 gcc-14
sparc randconfig-002-20260402 gcc-14
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260402 gcc-14
sparc64 randconfig-002-20260402 gcc-14
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260402 gcc-14
um randconfig-002-20260402 gcc-14
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260402 clang-20
x86_64 buildonly-randconfig-002-20260402 clang-20
x86_64 buildonly-randconfig-003-20260402 clang-20
x86_64 buildonly-randconfig-004-20260402 clang-20
x86_64 buildonly-randconfig-005-20260402 clang-20
x86_64 buildonly-randconfig-006-20260402 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260402 gcc-14
x86_64 randconfig-002-20260402 gcc-14
x86_64 randconfig-003-20260402 gcc-14
x86_64 randconfig-004-20260402 gcc-14
x86_64 randconfig-005-20260402 gcc-14
x86_64 randconfig-006-20260402 gcc-14
x86_64 randconfig-011-20260402 clang-20
x86_64 randconfig-012-20260402 clang-20
x86_64 randconfig-013-20260402 clang-20
x86_64 randconfig-014-20260402 clang-20
x86_64 randconfig-015-20260402 clang-20
x86_64 randconfig-016-20260402 clang-20
x86_64 randconfig-071-20260402 clang-20
x86_64 randconfig-072-20260402 clang-20
x86_64 randconfig-073-20260402 clang-20
x86_64 randconfig-074-20260402 clang-20
x86_64 randconfig-075-20260402 clang-20
x86_64 randconfig-076-20260402 clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allyesconfig clang-23
xtensa randconfig-001-20260402 gcc-14
xtensa randconfig-002-20260402 gcc-14
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v2 2/3] HID: nintendo: Add rumble support for Switch 2 controllers
From: Silvan Jegen @ 2026-04-02 19:11 UTC (permalink / raw)
To: Vicki Pfau; +Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260318030850.289712-3-vi@endrift.com>
Hi!
Just one typo below.
Vicki Pfau <vi@endrift.com> wrote:
> This adds rumble support for both the "HD Rumble" linear resonant actuator
> type as used in the Joy-Cons and Pro Controller, as well as the eccentric
> rotating mass type used in the GameCube controller. Note that since there's
> currently no API for exposing full control of LRAs with evdev, it only
> simulates a basic rumble for now.
>
> Signed-off-by: Vicki Pfau <vi@endrift.com>
> ---
> drivers/hid/Kconfig | 8 +-
> drivers/hid/hid-nintendo.c | 179 ++++++++++++++++++++++++++++++++++++-
> 2 files changed, 181 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 1a293a6c02c26..d8ce7451d8578 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -842,10 +842,10 @@ config NINTENDO_FF
> depends on HID_NINTENDO
> select INPUT_FF_MEMLESS
> help
> - Say Y here if you have a Nintendo Switch controller and want to enable
> - force feedback support for it. This works for both joy-cons, the pro
> - controller, and the NSO N64 controller. For the pro controller, both
> - rumble motors can be controlled individually.
> + Say Y here if you have a Nintendo Switch or Switch 2 controller and want
> + to enable force feedback support for it. This works for Joy-Cons, the Pro
> + Controllers, and the NSO N64 and GameCube controller. For the Pro
> + Controller, both rumble motors can be controlled individually.
>
> config HID_NTI
> tristate "NTI keyboard adapters"
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index 4ab8d4e7558a1..73d732ceb7116 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
> @@ -2976,6 +2976,18 @@ struct switch2_stick_calibration {
> struct switch2_axis_calibration y;
> };
>
> +struct switch2_hd_rumble {
> + uint16_t hi_freq : 10;
> + uint16_t hi_amp : 10;
> + uint16_t lo_freq : 10;
> + uint16_t lo_amp : 10;
> +} __packed;
> +
> +struct switch2_erm_rumble {
> + uint16_t error;
> + uint16_t amplitude;
> +};
> +
> struct switch2_controller {
> struct hid_device *hdev;
> struct switch2_cfg_intf *cfg;
> @@ -2997,8 +3009,45 @@ struct switch2_controller {
>
> uint32_t player_id;
> struct led_classdev leds[4];
> +
> +#if IS_ENABLED(CONFIG_NINTENDO_FF)
> + spinlock_t rumble_lock;
> + uint8_t rumble_seq;
> + union {
> + struct switch2_hd_rumble hd;
> + struct switch2_erm_rumble sd;
> + } rumble;
> + unsigned long last_rumble_work;
> + struct delayed_work rumble_work;
> + uint8_t rumble_buffer[64];
> +#endif
> +};
> +
> +enum gc_rumble {
> + GC_RUMBLE_OFF = 0,
> + GC_RUMBLE_ON = 1,
> + GC_RUMBLE_STOP = 2,
> };
>
> +/*
> + * The highest rumble level for "HD Rumble" is strong enough to potentially damage the controller,
> + * and also leaves your hands feeling like melted jelly, so we set a semi-abitrary scaling factor
s/abitrary/arbitrary/
Cheers,
Silvan
^ permalink raw reply
* Re: [PATCH v2 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
From: Silvan Jegen @ 2026-04-02 19:09 UTC (permalink / raw)
To: Vicki Pfau; +Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260318030850.289712-2-vi@endrift.com>
Hi
Thanks for the patch!
Just some comments and questions inline below.
Vicki Pfau <vi@endrift.com> wrote:
> This adds a new driver for the Switch 2 controllers. The Switch 2 uses an
> unusual split-interface design such that input and rumble occur on the main
> HID interface, but all other communication occurs over a "configuration"
> interface. This is the case on both USB and Bluetooth, so this new driver
> uses a split-driver design with the HID interface being the "main" driver
> and the configuration interface is a secondary driver that looks up to the
> HID interface, sharing resources on a common struct.
>
> Due to using a non-standard pairing interface as well as Bluetooth
> communications being extremely limited in the kernel, a custom interface
> between userspace and the kernel will need to be design, along with bringup
> in BlueZ. That is beyond the scope of this initial patch, which only
> contains the generic HID and USB configuration interface drivers.
>
> This initial work supports general input for the Joy-Con 2, Pro Controller
> 2, and GameCube NSO controllers. IMU, rumble and battery support is not yet
> present.
>
> Signed-off-by: Vicki Pfau <vi@endrift.com>
> ---
> MAINTAINERS | 1 +
> drivers/hid/Kconfig | 11 +-
> drivers/hid/hid-ids.h | 4 +
> drivers/hid/hid-nintendo.c | 1194 ++++++++++++++++-
> drivers/hid/hid-nintendo.h | 72 +
> drivers/input/joystick/Kconfig | 11 +
> drivers/input/joystick/Makefile | 1 +
> drivers/input/joystick/nintendo-switch2-usb.c | 353 +++++
> 8 files changed, 1637 insertions(+), 10 deletions(-)
> create mode 100644 drivers/hid/hid-nintendo.h
> create mode 100644 drivers/input/joystick/nintendo-switch2-usb.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7b277d5bf3d12..4d1a28df5fd24 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18743,6 +18743,7 @@ F: drivers/scsi/nsp32*
>
> NINTENDO HID DRIVER
> M: Daniel J. Ogorchock <djogorchock@gmail.com>
> +M: Vicki Pfau <vi@endrift.com>
> L: linux-input@vger.kernel.org
> S: Maintained
> F: drivers/hid/hid-nintendo*
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index c1d9f7c6a5f23..1a293a6c02c26 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -826,10 +826,13 @@ config HID_NINTENDO
> depends on LEDS_CLASS
> select POWER_SUPPLY
> help
> - Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
> - All controllers support bluetooth, and the Pro Controller also supports
> - its USB mode. This also includes support for the Nintendo Switch Online
> - Controllers which include the NES, Genesis, SNES, and N64 controllers.
> + Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller, as
> + well as Nintendo Switch 2 Joy-Cons, Pro Controller, and NSO GameCube
> + controllers. All Switch controllers support bluetooth, and the Pro
> + Controller also supports its USB mode. This also includes support for
> + the Nintendo Switch Online Controllers which include the NES, Genesis,
> + SNES, and N64 controllers. Switch 2 controllers currently only support
> + USB mode.
>
> To compile this driver as a module, choose M here: the
> module will be called hid-nintendo.
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 4ab7640b119ac..a794dad7980f3 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -1073,6 +1073,10 @@
> #define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017
> #define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
> #define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
> +#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONR 0x2066
> +#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONL 0x2067
> +#define USB_DEVICE_ID_NINTENDO_NS2_PROCON 0x2069
> +#define USB_DEVICE_ID_NINTENDO_NS2_GCCON 0x2073
>
> #define USB_VENDOR_ID_NOVATEK 0x0603
> #define USB_DEVICE_ID_NOVATEK_PCT 0x0600
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index 29008c2cc5304..4ab8d4e7558a1 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
> @@ -1,11 +1,13 @@
> // SPDX-License-Identifier: GPL-2.0+
> /*
> - * HID driver for Nintendo Switch Joy-Cons and Pro Controllers
> + * HID driver for Nintendo Switch Joy-Cons and Pro Controllers, as well as
> + * Nintendo Switch 2 Joy-Cons, Pro Controller, and GameCube Controller
> *
> * Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com>
> * Portions Copyright (c) 2020 Nadia Holmquist Pedersen <nadia@nhp.sh>
> * Copyright (c) 2022 Emily Strickland <linux@emily.st>
> * Copyright (c) 2023 Ryan McClelland <rymcclel@gmail.com>
> + * Copyright (c) 2026 Valve Software
> *
> * The following resources/projects were referenced for this driver:
> * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
> @@ -13,6 +15,8 @@
> * https://github.com/FrotBot/SwitchProConLinuxUSB
> * https://github.com/MTCKC/ProconXInput
> * https://github.com/Davidobot/BetterJoyForCemu
> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
> + * https://github.com/ndeadly/switch2_controller_research
> * hid-wiimote kernel hid driver
> * hid-logitech-hidpp driver
> * hid-sony driver
> @@ -29,6 +33,7 @@
> */
>
> #include "hid-ids.h"
> +#include "hid-nintendo.h"
> #include <linux/unaligned.h>
> #include <linux/delay.h>
> #include <linux/device.h>
> @@ -41,6 +46,8 @@
> #include <linux/module.h>
> #include <linux/power_supply.h>
> #include <linux/spinlock.h>
> +#include <linux/usb.h>
> +#include "usbhid/usbhid.h"
>
> /*
> * Reference the url below for the following HID report defines:
> @@ -2614,7 +2621,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> return ret;
> }
>
> -static int nintendo_hid_event(struct hid_device *hdev,
> +static int joycon_event(struct hid_device *hdev,
> struct hid_report *report, u8 *raw_data, int size)
> {
> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
> @@ -2625,7 +2632,7 @@ static int nintendo_hid_event(struct hid_device *hdev,
> return joycon_ctlr_handle_event(ctlr, raw_data, size);
> }
>
> -static int nintendo_hid_probe(struct hid_device *hdev,
> +static int joycon_probe(struct hid_device *hdev,
> const struct hid_device_id *id)
> {
> int ret;
> @@ -2729,7 +2736,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> return ret;
> }
>
> -static void nintendo_hid_remove(struct hid_device *hdev)
> +static void joycon_remove(struct hid_device *hdev)
> {
> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
> unsigned long flags;
> @@ -2748,7 +2755,9 @@ static void nintendo_hid_remove(struct hid_device *hdev)
> hid_hw_stop(hdev);
> }
>
> -static int nintendo_hid_resume(struct hid_device *hdev)
> +#ifdef CONFIG_PM
> +
> +static int joycon_resume(struct hid_device *hdev)
> {
> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
> int ret;
> @@ -2771,7 +2780,7 @@ static int nintendo_hid_resume(struct hid_device *hdev)
> return ret;
> }
>
> -static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
> +static int joycon_suspend(struct hid_device *hdev, pm_message_t message)
> {
> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>
> @@ -2790,7 +2799,1120 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
> return 0;
> }
>
> +#endif
> +
> +/*
> + * =============================================================================
> + * Switch 2 support
> + * =============================================================================
> + */
> +#define NS2_BTNR_B BIT(0)
> +#define NS2_BTNR_A BIT(1)
> +#define NS2_BTNR_Y BIT(2)
> +#define NS2_BTNR_X BIT(3)
> +#define NS2_BTNR_R BIT(4)
> +#define NS2_BTNR_ZR BIT(5)
> +#define NS2_BTNR_PLUS BIT(6)
> +#define NS2_BTNR_RS BIT(7)
> +
> +#define NS2_BTNL_DOWN BIT(0)
> +#define NS2_BTNL_RIGHT BIT(1)
> +#define NS2_BTNL_LEFT BIT(2)
> +#define NS2_BTNL_UP BIT(3)
> +#define NS2_BTNL_L BIT(4)
> +#define NS2_BTNL_ZL BIT(5)
> +#define NS2_BTNL_MINUS BIT(6)
> +#define NS2_BTNL_LS BIT(7)
> +
> +#define NS2_BTN3_C BIT(4)
> +#define NS2_BTN3_SR BIT(6)
> +#define NS2_BTN3_SL BIT(7)
> +
> +#define NS2_BTN_JCR_HOME BIT(0)
> +#define NS2_BTN_JCR_GR BIT(2)
> +#define NS2_BTN_JCR_C NS2_BTN3_C
> +#define NS2_BTN_JCR_SR NS2_BTN3_SR
> +#define NS2_BTN_JCR_SL NS2_BTN3_SL
> +
> +#define NS2_BTN_JCL_CAPTURE BIT(0)
> +#define NS2_BTN_JCL_GL BIT(2)
> +#define NS2_BTN_JCL_SR NS2_BTN3_SR
> +#define NS2_BTN_JCL_SL NS2_BTN3_SL
> +
> +#define NS2_BTN_PRO_HOME BIT(0)
> +#define NS2_BTN_PRO_CAPTURE BIT(1)
> +#define NS2_BTN_PRO_GR BIT(2)
> +#define NS2_BTN_PRO_GL BIT(3)
> +#define NS2_BTN_PRO_C NS2_BTN3_C
> +
> +#define NS2_BTN_GC_HOME BIT(0)
> +#define NS2_BTN_GC_CAPTURE BIT(1)
> +#define NS2_BTN_GC_C NS2_BTN3_C
> +
> +#define NS2_TRIGGER_RANGE 4095
> +#define NS2_AXIS_MIN -32768
> +#define NS2_AXIS_MAX 32767
> +
> +#define NS2_MAX_PLAYER_ID 8
> +
> +#define NS2_MAX_INIT_RETRIES 4
> +
> +#define NS2_FLASH_ADDR_SERIAL 0x13002
> +#define NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB 0x130a8
> +#define NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB 0x130e8
> +#define NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB 0x13140
> +#define NS2_FLASH_ADDR_USER_PRIMARY_CALIB 0x1fc040
> +#define NS2_FLASH_ADDR_USER_SECONDARY_CALIB 0x1fc080
> +
> +#define NS2_FLASH_SIZE_SERIAL 0x10
> +#define NS2_FLASH_SIZE_FACTORY_AXIS_CALIB 9
> +#define NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB 2
> +#define NS2_FLASH_SIZE_USER_AXIS_CALIB 11
> +
> +#define NS2_USER_CALIB_MAGIC 0xa1b2
> +
> +#define NS2_FEATURE_BUTTONS BIT(0)
> +#define NS2_FEATURE_ANALOG BIT(1)
> +#define NS2_FEATURE_IMU BIT(2)
> +#define NS2_FEATURE_MOUSE BIT(4)
> +#define NS2_FEATURE_RUMBLE BIT(5)
> +#define NS2_FEATURE_MAGNETO BIT(7)
> +
> +enum switch2_subcmd_flash {
> + NS2_SUBCMD_FLASH_READ_BLOCK = 0x01,
> + NS2_SUBCMD_FLASH_WRITE_BLOCK = 0x02,
> + NS2_SUBCMD_FLASH_ERASE_BLOCK = 0x03,
> + NS2_SUBCMD_FLASH_READ = 0x04,
> + NS2_SUBCMD_FLASH_WRITE = 0x05,
> +};
> +
> +enum switch2_subcmd_init {
> + NS2_SUBCMD_INIT_SELECT_REPORT = 0xa,
> + NS2_SUBCMD_INIT_USB = 0xd,
> +};
> +
> +enum switch2_subcmd_feature_select {
> + NS2_SUBCMD_FEATSEL_GET_INFO = 0x1,
> + NS2_SUBCMD_FEATSEL_SET_MASK = 0x2,
> + NS2_SUBCMD_FEATSEL_CLEAR_MASK = 0x3,
> + NS2_SUBCMD_FEATSEL_ENABLE = 0x4,
> + NS2_SUBCMD_FEATSEL_DISABLE = 0x5,
> +};
> +
> +enum switch2_subcmd_grip {
> + NS2_SUBCMD_GRIP_GET_INFO = 0x1,
> + NS2_SUBCMD_GRIP_ENABLE_BUTTONS = 0x2,
> + NS2_SUBCMD_GRIP_GET_INFO_EXT = 0x3,
> +};
> +
> +enum switch2_subcmd_led {
> + NS2_SUBCMD_LED_P1 = 0x1,
> + NS2_SUBCMD_LED_P2 = 0x2,
> + NS2_SUBCMD_LED_P3 = 0x3,
> + NS2_SUBCMD_LED_P4 = 0x4,
> + NS2_SUBCMD_LED_ALL_ON = 0x5,
> + NS2_SUBCMD_LED_ALL_OFF = 0x6,
> + NS2_SUBCMD_LED_PATTERN = 0x7,
> + NS2_SUBCMD_LED_BLINK = 0x8,
> +};
> +
> +enum switch2_subcmd_fw_info {
> + NS2_SUBCMD_FW_INFO_GET = 0x1,
> +};
> +
> +enum switch2_ctlr_type {
> + NS2_CTLR_TYPE_JCL = 0x00,
> + NS2_CTLR_TYPE_JCR = 0x01,
> + NS2_CTLR_TYPE_PRO = 0x02,
> + NS2_CTLR_TYPE_GC = 0x03,
> +};
> +
> +enum switch2_report_id {
> + NS2_REPORT_UNIFIED = 0x05,
> + NS2_REPORT_JCL = 0x07,
> + NS2_REPORT_JCR = 0x08,
> + NS2_REPORT_PRO = 0x09,
> + NS2_REPORT_GC = 0x0a,
> +};
> +
> +enum switch2_init_step {
> + NS2_INIT_READ_SERIAL,
> + NS2_INIT_GET_FIRMWARE_INFO,
> + NS2_INIT_READ_FACTORY_PRIMARY_CALIB,
> + NS2_INIT_READ_FACTORY_SECONDARY_CALIB,
> + NS2_INIT_READ_FACTORY_TRIGGER_CALIB,
> + NS2_INIT_READ_USER_PRIMARY_CALIB,
> + NS2_INIT_READ_USER_SECONDARY_CALIB,
> + NS2_INIT_SET_FEATURE_MASK,
> + NS2_INIT_ENABLE_FEATURES,
> + NS2_INIT_GRIP_BUTTONS,
> + NS2_INIT_REPORT_FORMAT,
> + NS2_INIT_SET_PLAYER_LEDS,
> + NS2_INIT_INPUT,
> + NS2_INIT_FINISH,
> + NS2_INIT_DONE,
> +};
> +
> +struct switch2_version_info {
> + uint8_t major;
> + uint8_t minor;
> + uint8_t patch;
> + uint8_t ctlr_type;
> + __le32 unk;
> + int8_t dsp_major;
> + int8_t dsp_minor;
> + int8_t dsp_patch;
> + int8_t dsp_type;
> +};
> +
> +struct switch2_axis_calibration {
> + uint16_t neutral;
> + uint16_t negative;
> + uint16_t positive;
> +};
> +
> +struct switch2_stick_calibration {
> + struct switch2_axis_calibration x;
> + struct switch2_axis_calibration y;
> +};
> +
> +struct switch2_controller {
> + struct hid_device *hdev;
> + struct switch2_cfg_intf *cfg;
> +
> + char name[64];
> + char phys[64];
> + struct list_head entry;
> + struct mutex lock;
> +
> + enum switch2_ctlr_type ctlr_type;
> + enum switch2_init_step init_step;
> + struct input_dev __rcu *input;
> + char serial[NS2_FLASH_SIZE_SERIAL + 1];
> + struct switch2_version_info version;
> +
> + struct switch2_stick_calibration stick_calib[2];
> + uint8_t lt_zero;
> + uint8_t rt_zero;
> +
> + uint32_t player_id;
> + struct led_classdev leds[4];
> +};
> +
> +static DEFINE_MUTEX(switch2_controllers_lock);
> +static LIST_HEAD(switch2_controllers);
> +
> +struct switch2_ctlr_button_mapping {
> + uint32_t code;
> + int byte;
> + uint32_t bit;
> +};
> +
> +static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_mappings[] = {
> + { BTN_DPAD_LEFT, 0, NS2_BTNL_LEFT, },
> + { BTN_DPAD_UP, 0, NS2_BTNL_UP, },
> + { BTN_DPAD_DOWN, 0, NS2_BTNL_DOWN, },
> + { BTN_DPAD_RIGHT, 0, NS2_BTNL_RIGHT, },
> + { BTN_TL, 0, NS2_BTNL_L, },
> + { BTN_TL2, 0, NS2_BTNL_ZL, },
> + { BTN_SELECT, 0, NS2_BTNL_MINUS, },
> + { BTN_THUMBL, 0, NS2_BTNL_LS, },
> + { KEY_RECORD, 1, NS2_BTN_JCL_CAPTURE, },
> + { BTN_GRIPR, 1, NS2_BTN_JCL_SL, },
> + { BTN_GRIPR2, 1, NS2_BTN_JCL_SR, },
> + { BTN_GRIPL, 1, NS2_BTN_JCL_GL, },
> + { /* sentinel */ },
> +};
> +
> +static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings[] = {
> + { BTN_SOUTH, 0, NS2_BTNR_A, },
> + { BTN_EAST, 0, NS2_BTNR_B, },
> + { BTN_NORTH, 0, NS2_BTNR_X, },
> + { BTN_WEST, 0, NS2_BTNR_Y, },
> + { BTN_TR, 0, NS2_BTNR_R, },
> + { BTN_TR2, 0, NS2_BTNR_ZR, },
> + { BTN_START, 0, NS2_BTNR_PLUS, },
> + { BTN_THUMBR, 0, NS2_BTNR_RS, },
> + { BTN_C, 1, NS2_BTN_JCR_C, },
> + { BTN_MODE, 1, NS2_BTN_JCR_HOME, },
> + { BTN_GRIPL2, 1, NS2_BTN_JCR_SL, },
> + { BTN_GRIPL, 1, NS2_BTN_JCR_SR, },
> + { BTN_GRIPR, 1, NS2_BTN_JCR_GR, },
> + { /* sentinel */ },
> +};
> +
> +static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
> + { BTN_SOUTH, 0, NS2_BTNR_A, },
> + { BTN_EAST, 0, NS2_BTNR_B, },
> + { BTN_NORTH, 0, NS2_BTNR_X, },
> + { BTN_WEST, 0, NS2_BTNR_Y, },
> + { BTN_TL, 1, NS2_BTNL_L, },
> + { BTN_TR, 0, NS2_BTNR_R, },
> + { BTN_TL2, 1, NS2_BTNL_ZL, },
> + { BTN_TR2, 0, NS2_BTNR_ZR, },
> + { BTN_SELECT, 1, NS2_BTNL_MINUS, },
> + { BTN_START, 0, NS2_BTNR_PLUS, },
> + { BTN_THUMBL, 1, NS2_BTNL_LS, },
> + { BTN_THUMBR, 0, NS2_BTNR_RS, },
> + { BTN_MODE, 2, NS2_BTN_PRO_HOME },
> + { KEY_RECORD, 2, NS2_BTN_PRO_CAPTURE },
> + { BTN_GRIPR, 2, NS2_BTN_PRO_GR },
> + { BTN_GRIPL, 2, NS2_BTN_PRO_GL },
> + { BTN_C, 2, NS2_BTN_PRO_C },
> + { /* sentinel */ },
> +};
> +
> +static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
> + { BTN_SOUTH, 0, NS2_BTNR_A, },
> + { BTN_EAST, 0, NS2_BTNR_B, },
> + { BTN_NORTH, 0, NS2_BTNR_X, },
> + { BTN_WEST, 0, NS2_BTNR_Y, },
> + { BTN_TL2, 1, NS2_BTNL_L, },
> + { BTN_TR2, 0, NS2_BTNR_R, },
> + { BTN_TL, 1, NS2_BTNL_ZL, },
> + { BTN_TR, 0, NS2_BTNR_ZR, },
> + { BTN_SELECT, 1, NS2_BTNL_MINUS, },
> + { BTN_START, 0, NS2_BTNR_PLUS, },
> + { BTN_MODE, 2, NS2_BTN_GC_HOME },
> + { KEY_RECORD, 2, NS2_BTN_GC_CAPTURE },
> + { BTN_C, 2, NS2_BTN_GC_C },
> + { /* sentinel */ },
> +};
> +
> +static const uint8_t switch2_init_cmd_data[] = {
> + /*
> + * The last 6 bytes of this packet are the MAC address of
> + * the console, but we don't need that for USB
> + */
> + 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
> +};
> +
> +static const uint8_t switch2_one_data[] = { 0x01, 0x00, 0x00, 0x00 };
> +
> +static const uint8_t switch2_feature_mask[] = {
> + NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG | NS2_FEATURE_IMU,
> + 0x00, 0x00, 0x00
> +};
> +
> +static void switch2_init_step_done(struct switch2_controller *ns2, enum switch2_init_step step)
> +{
> + if (ns2->init_step != step)
> + return;
> +
> + ns2->init_step++;
> +}
> +
> +static inline bool switch2_ctlr_is_joycon(enum switch2_ctlr_type type)
> +{
> + return type == NS2_CTLR_TYPE_JCL || type == NS2_CTLR_TYPE_JCR;
> +}
> +
> +static int switch2_set_leds(struct switch2_controller *ns2)
> +{
> + int i;
> + uint8_t message[8] = { 0 };
> +
> + for (i = 0; i < JC_NUM_LEDS; i++)
> + message[0] |= (!!ns2->leds[i].brightness) << i;
> +
> + if (!ns2->cfg)
> + return -ENOTCONN;
> + return ns2->cfg->send_command(NS2_CMD_LED, NS2_SUBCMD_LED_PATTERN,
> + &message, sizeof(message),
> + ns2->cfg);
> +}
> +
> +static int switch2_player_led_brightness_set(struct led_classdev *led,
> + enum led_brightness brightness)
> +{
> + struct device *dev = led->dev->parent;
> + struct hid_device *hdev = to_hid_device(dev);
> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
> +
> + if (!ns2)
> + return -ENODEV;
> +
> + guard(mutex)(&ns2->lock);
> + return switch2_set_leds(ns2);
> +}
> +
> +static void switch2_leds_create(struct switch2_controller *ns2)
> +{
> + struct hid_device *hdev = ns2->hdev;
> + struct led_classdev *led;
> + int i;
> + int player_led_pattern;
> +
> + player_led_pattern = ns2->player_id % JC_NUM_LED_PATTERNS;
> + hid_dbg(hdev, "assigned player %d led pattern", player_led_pattern + 1);
> +
> + for (i = 0; i < JC_NUM_LEDS; i++) {
> + led = &ns2->leds[i];
> + led->brightness = joycon_player_led_patterns[player_led_pattern][i];
> + led->max_brightness = 1;
> + led->brightness_set_blocking = switch2_player_led_brightness_set;
> + led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE;
> + }
> +}
> +
> +static void switch2_config_buttons(struct input_dev *idev,
> + const struct switch2_ctlr_button_mapping button_mappings[])
> +{
> + const struct switch2_ctlr_button_mapping *button;
> +
> + for (button = button_mappings; button->code; button++)
> + input_set_capability(idev, EV_KEY, button->code);
> +}
> +
> +static int switch2_init_input(struct switch2_controller *ns2)
> +{
> + struct input_dev *input;
> + struct hid_device *hdev = ns2->hdev;
> + int i;
> + int ret;
> +
> + switch2_init_step_done(ns2, NS2_INIT_FINISH);
> +
> + rcu_read_lock();
> + input = rcu_dereference(ns2->input);
> + rcu_read_unlock();
> +
> + if (input)
> + return 0;
> +
> + input = devm_input_allocate_device(&hdev->dev);
> + if (!input)
> + return -ENOMEM;
> +
> + input_set_drvdata(input, ns2);
> + input->dev.parent = &hdev->dev;
> + input->id.bustype = hdev->bus;
> + input->id.vendor = hdev->vendor;
> + input->id.product = hdev->product;
> + input->id.version = hdev->version;
> + input->uniq = ns2->serial;
> + input->name = ns2->name;
> + input->phys = hdev->phys;
> +
> + switch (ns2->ctlr_type) {
> + case NS2_CTLR_TYPE_JCL:
> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + switch2_config_buttons(input, ns2_left_joycon_button_mappings);
> + break;
> + case NS2_CTLR_TYPE_JCR:
> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + switch2_config_buttons(input, ns2_right_joycon_button_mappings);
> + break;
> + case NS2_CTLR_TYPE_GC:
> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_Z, 0, NS2_TRIGGER_RANGE, 32, 128);
> + input_set_abs_params(input, ABS_RZ, 0, NS2_TRIGGER_RANGE, 32, 128);
> + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
> + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
> + switch2_config_buttons(input, ns2_gccon_mappings);
> + break;
> + case NS2_CTLR_TYPE_PRO:
> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
> + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
> + switch2_config_buttons(input, ns2_procon_mappings);
> + break;
> + default:
> + input_free_device(input);
> + return -EINVAL;
> + }
> +
> + hid_info(ns2->hdev, "Firmware version %u.%u.%u (type %i)\n", ns2->version.major,
> + ns2->version.minor, ns2->version.patch, ns2->version.ctlr_type);
> + if (ns2->version.dsp_type >= 0)
> + hid_info(ns2->hdev, "DSP version %u.%u.%u\n", ns2->version.dsp_major,
> + ns2->version.dsp_minor, ns2->version.dsp_patch);
> +
> + ret = input_register_device(input);
> + if (ret < 0) {
> + hid_err(ns2->hdev, "Failed to register input; ret=%d\n", ret);
> + return ret;
> + }
> +
> + for (i = 0; i < JC_NUM_LEDS; i++) {
> + struct led_classdev *led = &ns2->leds[i];
> + char *name = devm_kasprintf(&input->dev, GFP_KERNEL, "%s:%s:%s",
> + dev_name(&input->dev),
> + "green",
> + joycon_player_led_names[i]);
> +
> + if (!name)
> + return -ENOMEM;
> +
> + led->name = name;
> + ret = devm_led_classdev_register(&input->dev, led);
> + if (ret < 0) {
> + dev_err(&input->dev, "Failed to register player %d LED; ret=%d\n",
> + i + 1, ret);
> + input_unregister_device(input);
> + return ret;
> + }
> + }
> +
> + rcu_assign_pointer(ns2->input, input);
> + synchronize_rcu();
> + return 0;
> +}
> +
> +static struct switch2_controller *switch2_get_controller(const char *phys)
> +{
> + struct switch2_controller *ns2;
> +
> + guard(mutex)(&switch2_controllers_lock);
> + list_for_each_entry(ns2, &switch2_controllers, entry) {
> + if (strncmp(ns2->phys, phys, sizeof(ns2->phys)) == 0)
> + return ns2;
> + }
> + ns2 = kzalloc(sizeof(*ns2), GFP_KERNEL);
> + if (!ns2)
> + return ERR_PTR(-ENOMEM);
> +
> + mutex_init(&ns2->lock);
> + INIT_LIST_HEAD(&ns2->entry);
> + list_add(&ns2->entry, &switch2_controllers);
> + strscpy(ns2->phys, phys, sizeof(ns2->phys));
> + return ns2;
> +}
> +
> +static void switch2_controller_put(struct switch2_controller *ns2)
> +{
> + struct input_dev *input;
> + bool do_free;
> +
> + guard(mutex)(&switch2_controllers_lock);
> + mutex_lock(&ns2->lock);
> +
> + rcu_read_lock();
> + input = rcu_dereference(ns2->input);
> + rcu_read_unlock();
> +
> + rcu_assign_pointer(ns2->input, NULL);
> + synchronize_rcu();
> +
> + ns2->init_step = 0;
> + do_free = !ns2->hdev && !ns2->cfg;
> + mutex_unlock(&ns2->lock);
> +
> + if (input)
> + input_unregister_device(input);
> +
> + if (do_free) {
> + list_del_init(&ns2->entry);
> + mutex_destroy(&ns2->lock);
> + kfree(ns2);
> + }
> +}
> +
> +static bool switch2_parse_stick_calibration(struct switch2_stick_calibration *calib,
> + const uint8_t *data)
> +{
> + static const uint8_t UNCALIBRATED[9] = {
> + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
> + };
> + if (memcmp(UNCALIBRATED, data, sizeof(UNCALIBRATED)) == 0)
> + return false;
> +
> + calib->x.neutral = data[0];
> + calib->x.neutral |= (data[1] & 0x0F) << 8;
> +
> + calib->y.neutral = data[1] >> 4;
> + calib->y.neutral |= data[2] << 4;
> +
> + calib->x.positive = data[3];
> + calib->x.positive |= (data[4] & 0x0F) << 8;
> +
> + calib->y.positive = data[4] >> 4;
> + calib->y.positive |= data[5] << 4;
> +
> + calib->x.negative = data[6];
> + calib->x.negative |= (data[7] & 0x0F) << 8;
> +
> + calib->y.negative = data[7] >> 4;
> + calib->y.negative |= data[8] << 4;
> +
> + return true;
> +}
> +
> +static void switch2_handle_flash_read(struct switch2_controller *ns2, uint8_t size,
> + uint32_t address, const uint8_t *data)
> +{
> + bool ok;
> +
> + switch (address) {
> + case NS2_FLASH_ADDR_SERIAL:
> + if (size != NS2_FLASH_SIZE_SERIAL)
> + return;
> + memcpy(ns2->serial, data, size);
> + switch2_init_step_done(ns2, NS2_INIT_READ_SERIAL);
> + break;
> + case NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB:
> + if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
> + return;
> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_PRIMARY_CALIB);
> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], data);
> + if (ok) {
> + hid_dbg(ns2->hdev, "Got factory primary stick calibration:\n");
> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
> + ns2->stick_calib[0].x.negative,
> + ns2->stick_calib[0].x.neutral,
> + ns2->stick_calib[0].x.positive);
> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
> + ns2->stick_calib[0].y.negative,
> + ns2->stick_calib[0].y.neutral,
> + ns2->stick_calib[0].y.positive);
> + } else {
> + hid_dbg(ns2->hdev, "Factory primary stick calibration not present\n");
> + }
> + break;
> + case NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB:
> + if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
> + return;
> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], data);
> + if (ok) {
> + hid_dbg(ns2->hdev, "Got factory secondary stick calibration:\n");
> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
> + ns2->stick_calib[1].x.negative,
> + ns2->stick_calib[1].x.neutral,
> + ns2->stick_calib[1].x.positive);
> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
> + ns2->stick_calib[1].y.negative,
> + ns2->stick_calib[1].y.neutral,
> + ns2->stick_calib[1].y.positive);
> + } else {
> + hid_dbg(ns2->hdev, "Factory secondary stick calibration not present\n");
> + }
> + break;
> + case NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB:
> + if (size != NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB)
> + return;
> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
> + if (data[0] != 0xFF && data[1] != 0xFF) {
> + ns2->lt_zero = data[0];
> + ns2->rt_zero = data[1];
> +
> + hid_dbg(ns2->hdev, "Got factory trigger calibration:\n");
> + hid_dbg(ns2->hdev, "Left zero point: %i\n", ns2->lt_zero);
> + hid_dbg(ns2->hdev, "Right zero point: %i\n", ns2->rt_zero);
> + } else {
> + hid_dbg(ns2->hdev, "Factory trigger calibration not present\n");
> + }
> + break;
> + case NS2_FLASH_ADDR_USER_PRIMARY_CALIB:
> + if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
> + return;
> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_PRIMARY_CALIB);
> + if (__le16_to_cpu(*(__le16 *)data) != NS2_USER_CALIB_MAGIC) {
> + hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
> + break;
> + }
> +
> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], &data[2]);
> + if (ok) {
> + hid_dbg(ns2->hdev, "Got user primary stick calibration:\n");
> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
> + ns2->stick_calib[0].x.negative,
> + ns2->stick_calib[0].x.neutral,
> + ns2->stick_calib[0].x.positive);
> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
> + ns2->stick_calib[0].y.negative,
> + ns2->stick_calib[0].y.neutral,
> + ns2->stick_calib[0].y.positive);
> + } else {
> + hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
> + }
> + break;
> + case NS2_FLASH_ADDR_USER_SECONDARY_CALIB:
> + if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
> + return;
> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
> + if (__le16_to_cpu(*(__le16 *)data) != NS2_USER_CALIB_MAGIC) {
> + hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
> + break;
> + }
> +
> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], &data[2]);
> + if (ok) {
> + hid_dbg(ns2->hdev, "Got user secondary stick calibration:\n");
> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
> + ns2->stick_calib[1].x.negative,
> + ns2->stick_calib[1].x.neutral,
> + ns2->stick_calib[1].x.positive);
> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
> + ns2->stick_calib[1].y.negative,
> + ns2->stick_calib[1].y.neutral,
> + ns2->stick_calib[1].y.positive);
> + } else {
> + hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
> + }
> + break;
> + }
> +}
> +
> +static void switch2_report_buttons(struct input_dev *input, const uint8_t *bytes,
> + const struct switch2_ctlr_button_mapping button_mappings[])
> +{
> + const struct switch2_ctlr_button_mapping *button;
> +
> + for (button = button_mappings; button->code; button++)
> + input_report_key(input, button->code, bytes[button->byte] & button->bit);
> +}
> +
> +static void switch2_report_axis(struct input_dev *input, struct switch2_axis_calibration *calib,
> + int axis, bool invert, int value)
> +{
> + if (calib && calib->neutral && calib->negative && calib->positive) {
> + value -= calib->neutral;
> + value *= NS2_AXIS_MAX + 1;
> + if (value < 0)
> + value /= calib->negative;
> + else
> + value /= calib->positive;
> + } else {
> + value = (value - 2048) * 16;
> + }
> +
> + if (invert)
> + value = -value;
> + input_report_abs(input, axis,
> + clamp(value, NS2_AXIS_MIN, NS2_AXIS_MAX));
> +}
> +
> +static void switch2_report_stick(struct input_dev *input, struct switch2_stick_calibration *calib,
> + int x, bool invert_x, int y, bool invert_y, const uint8_t *data)
> +{
> + switch2_report_axis(input, &calib->x, x, invert_x, data[0] | ((data[1] & 0x0F) << 8));
> + switch2_report_axis(input, &calib->y, y, invert_y, (data[1] >> 4) | (data[2] << 4));
> +}
> +
> +static void switch2_report_trigger(struct input_dev *input, uint8_t zero, int abs, uint8_t data)
> +{
> + int value = (NS2_TRIGGER_RANGE + 1) * (data - zero) / (232 - zero);
> +
> + input_report_abs(input, abs, clamp(value, 0, NS2_TRIGGER_RANGE));
> +}
> +
> +static int switch2_event(struct hid_device *hdev, struct hid_report *report, uint8_t *raw_data,
> + int size)
> +{
> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
> + struct input_dev *input;
> +
> + if (report->type != HID_INPUT_REPORT)
> + return 0;
> +
> + if (size < 15)
> + return -EINVAL;
> +
> + guard(rcu)();
> + input = rcu_dereference(ns2->input);
> +
> + if (!input)
> + return 0;
> +
> + switch (report->id) {
> + case NS2_REPORT_UNIFIED:
> + /*
> + * TODO
> + * This won't be sent unless the report type gets changed via command
> + * 03-0A, but we should support it at some point regardless.
> + */
> + break;
> + case NS2_REPORT_JCL:
> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
> + ABS_Y, true, &raw_data[6]);
> + switch2_report_buttons(input, &raw_data[3], ns2_left_joycon_button_mappings);
> + break;
> + case NS2_REPORT_JCR:
> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
> + ABS_Y, true, &raw_data[6]);
> + switch2_report_buttons(input, &raw_data[3], ns2_right_joycon_button_mappings);
> + break;
> + case NS2_REPORT_GC:
> + input_report_abs(input, ABS_HAT0X,
> + !!(raw_data[4] & NS2_BTNL_RIGHT) -
> + !!(raw_data[4] & NS2_BTNL_LEFT));
> + input_report_abs(input, ABS_HAT0Y,
> + !!(raw_data[4] & NS2_BTNL_DOWN) -
> + !!(raw_data[4] & NS2_BTNL_UP));
> + switch2_report_buttons(input, &raw_data[3], ns2_gccon_mappings);
> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
> + ABS_Y, true, &raw_data[6]);
> + switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
> + ABS_RY, true, &raw_data[9]);
> + switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[13]);
> + switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[14]);
> + break;
> + case NS2_REPORT_PRO:
> + input_report_abs(input, ABS_HAT0X,
> + !!(raw_data[4] & NS2_BTNL_RIGHT) -
> + !!(raw_data[4] & NS2_BTNL_LEFT));
> + input_report_abs(input, ABS_HAT0Y,
> + !!(raw_data[4] & NS2_BTNL_DOWN) -
> + !!(raw_data[4] & NS2_BTNL_UP));
> + switch2_report_buttons(input, &raw_data[3], ns2_procon_mappings);
> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
> + ABS_Y, true, &raw_data[6]);
> + switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
> + ABS_RY, true, &raw_data[9]);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + input_sync(input);
> + return 0;
> +}
> +
> +static int switch2_features_enable(struct switch2_controller *ns2, int features)
> +{
> + __le32 feature_bits = __cpu_to_le32(features);
> +
> + if (!ns2->cfg)
> + return -ENOTCONN;
> + return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_ENABLE,
> + &feature_bits, sizeof(feature_bits),
> + ns2->cfg);
> +}
> +
> +static int switch2_read_flash(struct switch2_controller *ns2, uint32_t address,
> + uint8_t size)
> +{
> + uint8_t message[8] = { size, 0x7e };
> +
> + if (!ns2->cfg)
> + return -ENOTCONN;
> + *(__le32 *)&message[4] = __cpu_to_le32(address);
> + return ns2->cfg->send_command(NS2_CMD_FLASH, NS2_SUBCMD_FLASH_READ, message,
> + sizeof(message), ns2->cfg);
> +}
> +
> +static int switch2_set_player_id(struct switch2_controller *ns2, uint32_t player_id)
> +{
> + int i;
> + int player_led_pattern = player_id % JC_NUM_LED_PATTERNS;
> +
> + for (i = 0; i < JC_NUM_LEDS; i++)
> + ns2->leds[i].brightness = joycon_player_led_patterns[player_led_pattern][i];
> +
> + return switch2_set_leds(ns2);
> +}
> +
> +static int switch2_set_report_format(struct switch2_controller *ns2, enum switch2_report_id fmt)
> +{
> + __le32 format_id = __cpu_to_le32(fmt);
> +
> + if (!ns2->cfg)
> + return -ENOTCONN;
> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_SELECT_REPORT,
> + &format_id, sizeof(format_id),
> + ns2->cfg);
> +}
> +
> +static int switch2_init_controller(struct switch2_controller *ns2)
This is now a recursive call while in v1 it wasn't. I think I preferred
the non-recursive version as there was one place where init_step
state was changed while now I am not sure where it happens (and whether
there is a code path where we end up in an infinite recursion)
What is the advantage of the recursive version compared to the
non-recursive one?
> +{
> + if (ns2->init_step == NS2_INIT_DONE)
> + return 0;
> +
> + if (!ns2->cfg)
> + return -ENOTCONN;
> +
> + switch (ns2->init_step) {
> + case NS2_INIT_READ_SERIAL:
> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_SERIAL,
> + NS2_FLASH_SIZE_SERIAL);
> + case NS2_INIT_GET_FIRMWARE_INFO:
> + return ns2->cfg->send_command(NS2_CMD_FW_INFO, NS2_SUBCMD_FW_INFO_GET,
> + NULL, 0, ns2->cfg);
> + case NS2_INIT_READ_FACTORY_PRIMARY_CALIB:
> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB,
> + NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
> + case NS2_INIT_READ_FACTORY_SECONDARY_CALIB:
> + if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
> + return switch2_init_controller(ns2);
> + }
> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB,
> + NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
> + case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
> + if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
> + return switch2_init_controller(ns2);
> + }
> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
> + NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
> + case NS2_INIT_READ_USER_PRIMARY_CALIB:
> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_PRIMARY_CALIB,
> + NS2_FLASH_SIZE_USER_AXIS_CALIB);
> + case NS2_INIT_READ_USER_SECONDARY_CALIB:
> + if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
> + return switch2_init_controller(ns2);
> + }
> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_SECONDARY_CALIB,
> + NS2_FLASH_SIZE_USER_AXIS_CALIB);
> + case NS2_INIT_SET_FEATURE_MASK:
> + return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_SET_MASK,
> + switch2_feature_mask, sizeof(switch2_feature_mask), ns2->cfg);
> + case NS2_INIT_ENABLE_FEATURES:
> + return switch2_features_enable(ns2, NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG);
> + case NS2_INIT_GRIP_BUTTONS:
> + if (!switch2_ctlr_is_joycon(ns2->ctlr_type)) {
> + switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
> + return switch2_init_controller(ns2);
> + }
> + return ns2->cfg->send_command(NS2_CMD_GRIP, NS2_SUBCMD_GRIP_ENABLE_BUTTONS,
> + switch2_one_data, sizeof(switch2_one_data),
> + ns2->cfg);
> + case NS2_INIT_REPORT_FORMAT:
> + switch (ns2->ctlr_type) {
> + case NS2_CTLR_TYPE_JCL:
> + return switch2_set_report_format(ns2, NS2_REPORT_JCL);
> + case NS2_CTLR_TYPE_JCR:
> + return switch2_set_report_format(ns2, NS2_REPORT_JCR);
> + case NS2_CTLR_TYPE_PRO:
> + return switch2_set_report_format(ns2, NS2_REPORT_PRO);
> + case NS2_CTLR_TYPE_GC:
> + return switch2_set_report_format(ns2, NS2_REPORT_GC);
> + default:
> + switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
> + return switch2_init_controller(ns2);
> + }
> + case NS2_INIT_SET_PLAYER_LEDS:
> + return switch2_set_player_id(ns2, ns2->player_id);
> + case NS2_INIT_INPUT:
> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_USB,
> + switch2_init_cmd_data, sizeof(switch2_init_cmd_data), ns2->cfg);
> + case NS2_INIT_FINISH:
> + if (ns2->hdev)
If this is not set we skip the switch2_init_input call but don't error
out. Is this intentional (are we expecting ns2->hdev to be populated at
a later time and this step retried, perhaps)?
> + return switch2_init_input(ns2);
> + break;
> + default:
> + WARN_ON_ONCE(1);
> + break;
> + }
> + return 0;
> +}
> +
> +int switch2_receive_command(struct switch2_controller *ns2,
> + const uint8_t *message, size_t length)
> +{
> + const struct switch2_cmd_header *header;
> + int ret = 0;
> +
> + if (length < 8)
> + return -EINVAL;
> +
> + print_hex_dump_debug("got cmd: ", DUMP_PREFIX_OFFSET, 16, 1, message, length, false);
> +
> + guard(mutex)(&ns2->lock);
> +
> + header = (const struct switch2_cmd_header *)message;
> + if (!(header->flags & NS2_FLAG_OK)) {
> + ret = -EIO;
> + goto exit;
> + }
> + message = &message[8];
> + switch (header->command) {
> + case NS2_CMD_FLASH:
> + if (header->subcommand == NS2_SUBCMD_FLASH_READ) {
> + uint8_t read_size;
> + uint32_t read_address;
> +
> + if (length < 16) {
> + ret = -EINVAL;
> + goto exit;
> + }
> + read_size = message[0];
> + read_address = __le32_to_cpu(*(__le32 *)&message[4]);
> + if (length < read_size + 16) {
> + ret = -EINVAL;
> + goto exit;
> + }
> + switch2_handle_flash_read(ns2, read_size, read_address, &message[8]);
> + }
> + break;
> + case NS2_CMD_INIT:
> + if (header->subcommand == NS2_SUBCMD_INIT_USB)
> + switch2_init_step_done(ns2, NS2_INIT_INPUT);
> + else if (header->subcommand == NS2_SUBCMD_INIT_SELECT_REPORT)
> + switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
> + break;
> + case NS2_CMD_GRIP:
> + if (header->subcommand == NS2_SUBCMD_GRIP_ENABLE_BUTTONS)
> + switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
> + break;
> + case NS2_CMD_LED:
> + if (header->subcommand == NS2_SUBCMD_LED_PATTERN)
> + switch2_init_step_done(ns2, NS2_INIT_SET_PLAYER_LEDS);
> + break;
> + case NS2_CMD_FEATSEL:
> + if (header->subcommand == NS2_SUBCMD_FEATSEL_SET_MASK)
> + switch2_init_step_done(ns2, NS2_INIT_SET_FEATURE_MASK);
> + else if (header->subcommand == NS2_SUBCMD_FEATSEL_ENABLE)
> + switch2_init_step_done(ns2, NS2_INIT_ENABLE_FEATURES);
> + break;
> + case NS2_CMD_FW_INFO:
> + if (header->subcommand == NS2_SUBCMD_FW_INFO_GET) {
> + if (length < sizeof(ns2->version)) {
> + ret = -EINVAL;
> + goto exit;
> + }
> + memcpy(&ns2->version, message, sizeof(ns2->version));
> + ns2->ctlr_type = ns2->version.ctlr_type;
> + switch2_init_step_done(ns2, NS2_INIT_GET_FIRMWARE_INFO);
> + }
> + break;
> + default:
> + break;
> + }
> +
> +exit:
> + if (ns2->init_step < NS2_INIT_DONE)
> + switch2_init_controller(ns2);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(switch2_receive_command);
> +
> +int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg)
> +{
> + struct switch2_controller *ns2 = switch2_get_controller(phys);
> +
> + if (IS_ERR(ns2))
> + return PTR_ERR(ns2);
> +
> + cfg->parent = ns2;
> +
> + guard(mutex)(&ns2->lock);
> + WARN_ON(ns2->cfg);
> + ns2->cfg = cfg;
> +
> + if (ns2->hdev)
> + return switch2_init_controller(ns2);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(switch2_controller_attach_cfg);
> +
> +void switch2_controller_detach_cfg(struct switch2_controller *ns2)
> +{
> + mutex_lock(&ns2->lock);
> + WARN_ON(ns2 != ns2->cfg->parent);
> + ns2->cfg = NULL;
> + mutex_unlock(&ns2->lock);
> + switch2_controller_put(ns2);
> +}
> +EXPORT_SYMBOL_GPL(switch2_controller_detach_cfg);
> +
> +static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> + struct switch2_controller *ns2;
> + struct usb_device *udev;
> + char phys[64];
> + int ret;
> +
> + if (!hid_is_usb(hdev))
> + return -ENODEV;
> +
> + udev = hid_to_usb_dev(hdev);
> + if (usb_make_path(udev, phys, sizeof(phys)) < 0)
> + return -EINVAL;
> +
> + ret = hid_parse(hdev);
> + if (ret) {
> + hid_err(hdev, "parse failed %d\n", ret);
> + return ret;
> + }
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> + if (ret) {
> + hid_err(hdev, "hw_start failed %d\n", ret);
> + return ret;
> + }
> +
> + ret = hid_hw_open(hdev);
> + if (ret) {
> + hid_err(hdev, "hw_open failed %d\n", ret);
> + goto err_stop;
> + }
For the Switch 1 controllers we are calling hid_device_io_start after
hid_hw_open. Is this not necessary in this case?
> +
> + ns2 = switch2_get_controller(phys);
> + if (!ns2) {
switch2_get_controller returns an err pointer in case of ENOMEM, not
NULL so I think this check has to be changed.
> + ret = -ENOMEM;
> + goto err_close;
> + }
> +
> + guard(mutex)(&ns2->lock);
> + WARN_ON(ns2->hdev);
> + ns2->hdev = hdev;
> + switch (hdev->product | (hdev->vendor << 16)) {
> + default:
> + strscpy(ns2->name, hdev->name, sizeof(ns2->name));
> + break;
> + /* Some controllers have slightly wrong names so we override them */
> + case USB_DEVICE_ID_NINTENDO_NS2_JOYCONR | (USB_VENDOR_ID_NINTENDO << 16):
> + /* Missing the "2" in the name */
> + strscpy(ns2->name, "Nintendo Joy-Con 2 (R)", sizeof(ns2->name));
> + break;
> + case USB_DEVICE_ID_NINTENDO_NS2_GCCON | (USB_VENDOR_ID_NINTENDO << 16):
> + /* Has "Nintendo" in the name twice */
> + strscpy(ns2->name, "Nintendo GameCube Controller", sizeof(ns2->name));
> + break;
> + }
> +
> + ns2->player_id = U32_MAX;
> + ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
> + if (ret < 0)
> + hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
> + else
> + ns2->player_id = ret;
> +
> + switch2_leds_create(ns2);
> +
> + hid_set_drvdata(hdev, ns2);
> +
> + if (ns2->cfg)
> + return switch2_init_controller(ns2);
> +
> + return 0;
> +
> +err_close:
> + hid_hw_close(hdev);
> +err_stop:
> + hid_hw_stop(hdev);
> +
> + return ret;
> +}
> +
> +static void switch2_remove(struct hid_device *hdev)
> +{
> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
> +
> + hid_hw_close(hdev);
> + mutex_lock(&ns2->lock);
> + WARN_ON(ns2->hdev != hdev);
> + ns2->hdev = NULL;
> + mutex_unlock(&ns2->lock);
> + ida_free(&nintendo_player_id_allocator, ns2->player_id);
> + switch2_controller_put(ns2);
> + hid_hw_stop(hdev);
> +}
> +
> static const struct hid_device_id nintendo_hid_devices[] = {
> + /* Switch devices */
> { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> USB_DEVICE_ID_NINTENDO_PROCON) },
> { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> @@ -2813,10 +3935,69 @@ static const struct hid_device_id nintendo_hid_devices[] = {
> USB_DEVICE_ID_NINTENDO_GENCON) },
> { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
> USB_DEVICE_ID_NINTENDO_N64CON) },
> + /* Switch 2 devices */
> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> + USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> + USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> + USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> + USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
> { }
> };
> MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
>
> +static bool nintendo_is_switch2(struct hid_device *hdev)
> +{
> + return hdev->vendor == USB_VENDOR_ID_NINTENDO &&
> + hdev->product >= USB_DEVICE_ID_NINTENDO_NS2_JOYCONR;
> +}
> +
> +static void nintendo_hid_remove(struct hid_device *hdev)
> +{
> + if (nintendo_is_switch2(hdev))
> + switch2_remove(hdev);
> + else
> + joycon_remove(hdev);
> +}
> +
> +static int nintendo_hid_event(struct hid_device *hdev,
> + struct hid_report *report, u8 *raw_data, int size)
> +{
> + if (nintendo_is_switch2(hdev))
> + return switch2_event(hdev, report, raw_data, size);
> + else
> + return joycon_event(hdev, report, raw_data, size);
> +}
> +
> +static int nintendo_hid_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + if (nintendo_is_switch2(hdev))
> + return switch2_probe(hdev, id);
> + else
> + return joycon_probe(hdev, id);
> +}
> +
> +#ifdef CONFIG_PM
> +static int nintendo_hid_resume(struct hid_device *hdev)
> +{
> + if (nintendo_is_switch2(hdev))
> + return 0;
> + else
> + return joycon_resume(hdev);
> +}
> +
> +static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
> +{
> + if (nintendo_is_switch2(hdev))
> + return 0;
> + else
> + return joycon_suspend(hdev, message);
> +}
> +#endif
> +
> static struct hid_driver nintendo_hid_driver = {
> .name = "nintendo",
> .id_table = nintendo_hid_devices,
> @@ -2844,4 +4025,5 @@ MODULE_LICENSE("GPL");
> MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
> MODULE_AUTHOR("Emily Strickland <linux@emily.st>");
> MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>");
> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
> MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");
> diff --git a/drivers/hid/hid-nintendo.h b/drivers/hid/hid-nintendo.h
> new file mode 100644
> index 0000000000000..7aff22f302661
> --- /dev/null
> +++ b/drivers/hid/hid-nintendo.h
> @@ -0,0 +1,72 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * HID driver for Nintendo Switch 2 controllers
> + *
> + * Copyright (c) 2025 Valve Software
> + *
> + * This driver is based on the following work:
> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
> + * https://github.com/ndeadly/switch2_controller_research
> + */
> +
> +#ifndef __HID_NINTENDO_H
> +#define __HID_NINTENDO_H
> +
> +#include <linux/bits.h>
> +
> +#define NS2_FLAG_OK BIT(0)
> +#define NS2_FLAG_NACK BIT(2)
> +
> +enum switch2_cmd {
> + NS2_CMD_NFC = 0x01,
> + NS2_CMD_FLASH = 0x02,
> + NS2_CMD_INIT = 0x03,
> + NS2_CMD_GRIP = 0x08,
> + NS2_CMD_LED = 0x09,
> + NS2_CMD_VIBRATE = 0x0a,
> + NS2_CMD_BATTERY = 0x0b,
> + NS2_CMD_FEATSEL = 0x0c,
> + NS2_CMD_FW_UPD = 0x0d,
> + NS2_CMD_FW_INFO = 0x10,
> + NS2_CMD_BT_PAIR = 0x15,
> +};
> +
> +enum switch2_direction {
> + NS2_DIR_IN = 0x00,
> + NS2_DIR_OUT = 0x90,
> +};
> +
> +enum switch2_transport {
> + NS2_TRANS_USB = 0x00,
> + NS2_TRANS_BT = 0x01,
> +};
> +
> +struct switch2_cmd_header {
> + uint8_t command;
> + uint8_t flags;
> + uint8_t transport;
> + uint8_t subcommand;
> + uint8_t unk1;
> + uint8_t length;
> + uint16_t unk2;
> +};
> +static_assert(sizeof(struct switch2_cmd_header) == 8);
> +
> +struct device;
> +struct switch2_controller;
> +struct switch2_cfg_intf {
> + struct switch2_controller *parent;
> + struct device *dev;
> +
> + int (*send_command)(enum switch2_cmd command, uint8_t subcommand,
> + const void *message, size_t length,
> + struct switch2_cfg_intf *intf);
> +};
> +
> +int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg);
> +void switch2_controller_detach_cfg(struct switch2_controller *controller);
> +
> +int switch2_receive_command(struct switch2_controller *controller,
> + const uint8_t *message, size_t length);
> +
> +#endif
> diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
> index 7755e5b454d2c..868262c6ccd9a 100644
> --- a/drivers/input/joystick/Kconfig
> +++ b/drivers/input/joystick/Kconfig
> @@ -422,4 +422,15 @@ config JOYSTICK_SEESAW
> To compile this driver as a module, choose M here: the module will be
> called adafruit-seesaw.
>
> +config JOYSTICK_NINTENDO_SWITCH2_USB
> + tristate "Wired Nintendo Switch 2 controller support"
> + depends on HID_NINTENDO
> + depends on USB
> + help
> + Say Y here if you want to enable support for wired Nintendo Switch 2
> + controllers.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called nintendo-switch2-usb.
> +
> endif
> diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
> index 9976f596a9208..8f92900ae8856 100644
> --- a/drivers/input/joystick/Makefile
> +++ b/drivers/input/joystick/Makefile
> @@ -34,6 +34,7 @@ obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o
> obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o
> obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o
> obj-$(CONFIG_JOYSTICK_STINGER) += stinger.o
> +obj-$(CONFIG_JOYSTICK_NINTENDO_SWITCH2_USB) += nintendo-switch2-usb.o
> obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o
> obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o
> obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
> diff --git a/drivers/input/joystick/nintendo-switch2-usb.c b/drivers/input/joystick/nintendo-switch2-usb.c
> new file mode 100644
> index 0000000000000..ebd89d852e21a
> --- /dev/null
> +++ b/drivers/input/joystick/nintendo-switch2-usb.c
> @@ -0,0 +1,353 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * USB driver for Nintendo Switch 2 controllers configuration interface
> + *
> + * Copyright (c) 2025 Valve Software
> + *
> + * This driver is based on the following work:
> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
> + * https://github.com/ndeadly/switch2_controller_research
> + */
> +
> +#include "../../hid/hid-ids.h"
> +#include "../../hid/hid-nintendo.h"
> +#include <linux/module.h>
> +#include <linux/usb/input.h>
> +
> +#define NS2_BULK_SIZE 64
> +#define NS2_IN_URBS 2
> +#define NS2_OUT_URBS 4
> +
> +static struct usb_driver switch2_usb;
> +
> +struct switch2_urb {
> + struct urb *urb;
> + uint8_t *data;
> + bool active;
> +};
> +
> +struct switch2_usb {
> + struct switch2_cfg_intf cfg;
> + struct usb_device *udev;
> +
> + struct switch2_urb bulk_in[NS2_IN_URBS];
> + struct usb_anchor bulk_in_anchor;
> + spinlock_t bulk_in_lock;
> +
> + struct switch2_urb bulk_out[NS2_OUT_URBS];
> + struct usb_anchor bulk_out_anchor;
> + spinlock_t bulk_out_lock;
> +
> + int message_in;
> + struct work_struct message_in_work;
> +};
> +
> +static void switch2_bulk_in(struct urb *urb)
> +{
> + struct switch2_usb *ns2_usb = urb->context;
> + int i;
> + bool schedule = false;
> + unsigned long flags;
> +
> + switch (urb->status) {
> + case 0:
> + schedule = true;
> + break;
> + case -ECONNRESET:
> + case -ENOENT:
> + case -ESHUTDOWN:
> + dev_dbg(&ns2_usb->udev->dev, "shutting down input urb: %d\n", urb->status);
> + return;
> + default:
> + dev_dbg(&ns2_usb->udev->dev, "unknown input urb status: %d\n", urb->status);
> + break;
> + }
> +
> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> + for (i = 0; i < NS2_IN_URBS; i++) {
> + int err;
> + struct switch2_urb *ns2_urb;
> +
> + if (ns2_usb->bulk_in[i].urb == urb) {
> + ns2_usb->message_in = i;
> + continue;
> + }
> +
> + if (ns2_usb->bulk_in[i].active)
> + continue;
> +
> + ns2_urb = &ns2_usb->bulk_in[i];
> + usb_anchor_urb(ns2_urb->urb, &ns2_usb->bulk_in_anchor);
> + err = usb_submit_urb(ns2_urb->urb, GFP_ATOMIC);
> + if (err) {
> + usb_unanchor_urb(ns2_urb->urb);
> + dev_dbg(&ns2_usb->udev->dev, "failed to queue input urb: %d\n", err);
> + } else {
> + ns2_urb->active = true;
> + }
> + }
> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> + if (schedule)
> + schedule_work(&ns2_usb->message_in_work);
> +}
> +
> +static void switch2_bulk_out(struct urb *urb)
> +{
> + struct switch2_usb *ns2_usb = urb->context;
> + int i;
> +
> + guard(spinlock_irqsave)(&ns2_usb->bulk_out_lock);
> +
> + switch (urb->status) {
> + case 0:
> + break;
> + case -ECONNRESET:
> + case -ENOENT:
> + case -ESHUTDOWN:
> + dev_dbg(&ns2_usb->udev->dev, "shutting down output urb: %d\n", urb->status);
> + return;
> + default:
> + dev_dbg(&ns2_usb->udev->dev, "unknown output urb status: %d\n", urb->status);
> + return;
> + }
> +
> + for (i = 0; i < NS2_OUT_URBS; i++) {
> + if (ns2_usb->bulk_out[i].urb != urb)
> + continue;
> +
> + ns2_usb->bulk_out[i].active = false;
> + break;
> + }
> +}
> +
> +static int switch2_usb_send_cmd(enum switch2_cmd command, uint8_t subcommand,
> + const void *message, size_t size, struct switch2_cfg_intf *cfg)
> +{
> + struct switch2_usb *ns2_usb = (struct switch2_usb *)cfg;
> + struct switch2_urb *urb = NULL;
> + int i;
> + int ret;
> + unsigned long flags;
> +
> + struct switch2_cmd_header header = {
> + command, NS2_DIR_OUT | NS2_FLAG_OK, NS2_TRANS_USB, subcommand, 0, size
> + };
> +
> + if (WARN_ON(size > 56))
> + return -EINVAL;
> +
> + spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
> + for (i = 0; i < NS2_OUT_URBS; i++) {
> + if (ns2_usb->bulk_out[i].active)
> + continue;
> +
> + urb = &ns2_usb->bulk_out[i];
> + urb->active = true;
> + break;
> + }
> + spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> +
> + if (!urb) {
> + dev_warn(&ns2_usb->udev->dev, "output queue full, dropping message\n");
> + return -ENOBUFS;
> + }
> +
> + memcpy(urb->data, &header, sizeof(header));
> + if (message && size)
> + memcpy(&urb->data[8], message, size);
> + urb->urb->transfer_buffer_length = size + sizeof(header);
> +
> + print_hex_dump_debug("sending cmd: ", DUMP_PREFIX_OFFSET, 16, 1, urb->data,
> + size + sizeof(header), false);
> +
> + usb_anchor_urb(urb->urb, &ns2_usb->bulk_out_anchor);
> + ret = usb_submit_urb(urb->urb, GFP_ATOMIC);
> + if (ret) {
> + if (ret != -ENODEV)
> + dev_warn(&ns2_usb->udev->dev, "failed to submit output urb: %i", ret);
> + urb->active = false;
> + usb_unanchor_urb(urb->urb);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void switch2_usb_message_in_work(struct work_struct *work)
> +{
> + struct switch2_usb *ns2_usb = container_of(work, struct switch2_usb, message_in_work);
> + struct switch2_urb *urb;
> + int err;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> + urb = &ns2_usb->bulk_in[ns2_usb->message_in];
> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> + err = switch2_receive_command(ns2_usb->cfg.parent, urb->urb->transfer_buffer,
> + urb->urb->actual_length);
> + if (err)
> + dev_dbg(&ns2_usb->udev->dev, "receive command failed: %d\n", err);
> +
> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> + urb->active = false;
> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +}
> +
> +static int switch2_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
> +{
> + struct switch2_usb *ns2_usb;
> + struct usb_device *udev;
> + struct usb_endpoint_descriptor *bulk_in, *bulk_out;
> + char phys[64];
> + int ret;
> + int i;
> +
> + udev = interface_to_usbdev(intf);
> + if (usb_make_path(udev, phys, sizeof(phys)) < 0)
> + return -EINVAL;
> +
> + ret = usb_find_common_endpoints(intf->cur_altsetting, &bulk_in, &bulk_out, NULL, NULL);
> + if (ret) {
> + dev_err(&intf->dev, "failed to find bulk EPs\n");
> + return ret;
> + }
> +
> + ns2_usb = devm_kzalloc(&intf->dev, sizeof(*ns2_usb), GFP_KERNEL);
> + if (!ns2_usb)
> + return -ENOMEM;
> +
> + ns2_usb->udev = udev;
> + for (i = 0; i < NS2_IN_URBS; i++) {
> + ns2_usb->bulk_in[i].urb = usb_alloc_urb(0, GFP_KERNEL);
> + if (!ns2_usb->bulk_in[i].urb) {
> + ret = -ENOMEM;
> + goto err_free_in;
> + }
> +
> + ns2_usb->bulk_in[i].data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
> + &ns2_usb->bulk_in[i].urb->transfer_dma);
> + if (!ns2_usb->bulk_in[i].data) {
> + ret = -ENOMEM;
> + goto err_free_in;
> + }
> +
> + usb_fill_bulk_urb(ns2_usb->bulk_in[i].urb, udev,
> + usb_rcvbulkpipe(udev, bulk_in->bEndpointAddress),
> + ns2_usb->bulk_in[i].data, NS2_BULK_SIZE, switch2_bulk_in, ns2_usb);
> + ns2_usb->bulk_in[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> + }
> +
> + for (i = 0; i < NS2_OUT_URBS; i++) {
> + ns2_usb->bulk_out[i].urb = usb_alloc_urb(0, GFP_KERNEL);
> + if (!ns2_usb->bulk_out[i].urb) {
> + ret = -ENOMEM;
> + goto err_free_out;
> + }
> +
> + ns2_usb->bulk_out[i].data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
> + &ns2_usb->bulk_out[i].urb->transfer_dma);
> + if (!ns2_usb->bulk_out[i].data) {
> + ret = -ENOMEM;
> + goto err_free_out;
> + }
> +
> + usb_fill_bulk_urb(ns2_usb->bulk_out[i].urb, udev,
> + usb_sndbulkpipe(udev, bulk_out->bEndpointAddress),
> + ns2_usb->bulk_out[i].data, NS2_BULK_SIZE, switch2_bulk_out, ns2_usb);
> + ns2_usb->bulk_out[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> + }
> +
> + ns2_usb->bulk_in[0].active = true;
> + ret = usb_submit_urb(ns2_usb->bulk_in[0].urb, GFP_ATOMIC);
> + if (ret < 0)
> + goto err_free_out;
> +
> + init_usb_anchor(&ns2_usb->bulk_out_anchor);
> + spin_lock_init(&ns2_usb->bulk_out_lock);
> + init_usb_anchor(&ns2_usb->bulk_in_anchor);
> + spin_lock_init(&ns2_usb->bulk_in_lock);
> + INIT_WORK(&ns2_usb->message_in_work, switch2_usb_message_in_work);
> +
> + usb_set_intfdata(intf, ns2_usb);
> +
> + ns2_usb->cfg.dev = &ns2_usb->udev->dev;
> + ns2_usb->cfg.send_command = switch2_usb_send_cmd;
> +
> + ret = switch2_controller_attach_cfg(phys, &ns2_usb->cfg);
> + if (ret < 0)
> + goto err_kill_urb;
> +
> + return 0;
> +
> +err_kill_urb:
> + usb_kill_urb(ns2_usb->bulk_in[0].urb);
> +err_free_out:
> + for (i = 0; i < NS2_OUT_URBS; i++) {
> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_out[i].data,
> + ns2_usb->bulk_out[i].urb->transfer_dma);
> + usb_free_urb(ns2_usb->bulk_out[i].urb);
> + }
> +err_free_in:
> + for (i = 0; i < NS2_IN_URBS; i++) {
> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_in[i].data,
> + ns2_usb->bulk_in[i].urb->transfer_dma);
> + usb_free_urb(ns2_usb->bulk_in[i].urb);
> + }
> + devm_kfree(&intf->dev, ns2_usb);
> +
> + return ret;
> +}
> +
> +static void switch2_usb_disconnect(struct usb_interface *intf)
> +{
> + struct switch2_usb *ns2_usb = usb_get_intfdata(intf);
> + unsigned long flags;
> + int i;
> +
> + spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
> + usb_kill_anchored_urbs(&ns2_usb->bulk_out_anchor);
> + for (i = 0; i < NS2_OUT_URBS; i++) {
> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_out[i].data,
> + ns2_usb->bulk_out[i].urb->transfer_dma);
> + usb_free_urb(ns2_usb->bulk_out[i].urb);
> + }
> + spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> +
> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> + usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor);
> + cancel_work_sync(&ns2_usb->message_in_work);
> + for (i = 0; i < NS2_IN_URBS; i++) {
> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_in[i].data,
> + ns2_usb->bulk_in[i].urb->transfer_dma);
> + usb_free_urb(ns2_usb->bulk_in[i].urb);
> + }
> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> + switch2_controller_detach_cfg(ns2_usb->cfg.parent);
As we have allocated ns2_usb with devm_kzalloc, don't we need to free it
with devm_kfree again?
Cheers,
Silvan
> +}
> +
> +#define SWITCH2_CONTROLLER(vend, prod) \
> + USB_DEVICE_AND_INTERFACE_INFO(vend, prod, USB_CLASS_VENDOR_SPEC, 0, 0)
> +
> +static const struct usb_device_id switch2_usb_devices[] = {
> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
> + { }
> +};
> +MODULE_DEVICE_TABLE(usb, switch2_usb_devices);
> +
> +static struct usb_driver switch2_usb = {
> + .name = "switch2",
> + .id_table = switch2_usb_devices,
> + .probe = switch2_usb_probe,
> + .disconnect = switch2_usb_disconnect,
> +};
> +module_usb_driver(switch2_usb);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
> +MODULE_DESCRIPTION("Driver for Nintendo Switch 2 Controllers");
^ permalink raw reply
* [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification
From: Dave Carey @ 2026-04-02 18:29 UTC (permalink / raw)
To: jikos; +Cc: bentiss, linux-input, linux-kernel, Dave Carey
The Lenovo Yoga Book 9 14IAH10 (83KJ) uses a composite USB HID device
(17EF:6161) where three descriptor quirks combine to cause hid-multitouch
to incorrectly set INPUT_PROP_BUTTONPAD on both touchscreen nodes, making
libinput treat them as indirect clickpads rather than direct touchscreens.
Quirk 1: The HID_DG_TOUCHSCREEN application collection contains
HID_UP_BUTTON usages (stylus barrel buttons). The generic heuristic in
mt_touch_input_mapping() treats any touchscreen-with-buttons as a
touchpad, setting INPUT_MT_POINTER.
Quirk 2: A HID_DG_TOUCHPAD collection ("Emulated Touchpad") sets
INPUT_MT_POINTER unconditionally in mt_allocate_application().
Quirk 3: The HID_DG_BUTTONTYPE feature report (0x51) returns
MT_BUTTONTYPE_CLICKPAD, directly setting td->is_buttonpad = true.
These combine to produce INPUT_PROP_BUTTONPAD on the touchscreen input
nodes. libinput treats the devices as indirect clickpads and suppresses
direct touch events, leaving the touchscreens non-functional under
KDE/Wayland.
Additionally, the firmware resets if any USB control request is received
during the CDC ACM initialization window. The existing GET_REPORT call
in mt_check_input_mode() during probe triggers this reset.
Fix by extending MT_QUIRK_YOGABOOK9I (already defined for the earlier
Yoga Book 9i) to guard all three BUTTONPAD heuristics and skip the
HID_DG_BUTTONTYPE GET_REPORT during probe for this device.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
Tested-by: Dave Carey <carvsdriver@gmail.com>
---
drivers/hid/hid-multitouch.c | 34 +++++++++++++++++++++++++++-------
1 file changed, 27 insertions(+), 7 deletions(-)
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index e82a3c4e5..1bef32b1d 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -549,7 +549,14 @@ static void mt_feature_mapping(struct hid_device *hdev,
switch (usage->hid) {
case HID_DG_CONTACTMAX:
- mt_get_feature(hdev, field->report);
+ /*
+ * Yoga Book 9: skip GET_REPORT during probe; the firmware
+ * resets if it receives any control request before the init
+ * Output report is sent (within ~1.18s of USB enumeration).
+ * Logical maximum from the descriptor is used as the fallback.
+ */
+ if (!(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I))
+ mt_get_feature(hdev, field->report);
td->maxcontacts = field->value[0];
if (!td->maxcontacts &&
@@ -566,6 +573,10 @@ static void mt_feature_mapping(struct hid_device *hdev,
break;
}
+ /* Yoga Book 9 reports Clickpad but is a direct touchscreen */
+ if (td->mtclass.quirks & MT_QUIRK_YOGABOOK9I)
+ break;
+
mt_get_feature(hdev, field->report);
switch (field->value[usage->usage_index]) {
case MT_BUTTONTYPE_CLICKPAD:
@@ -579,7 +590,9 @@ static void mt_feature_mapping(struct hid_device *hdev,
break;
case 0xff0000c5:
/* Retrieve the Win8 blob once to enable some devices */
- if (usage->usage_index == 0)
+ /* Yoga Book 9: skip; firmware resets before init if queried */
+ if (usage->usage_index == 0 &&
+ !(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I))
mt_get_feature(hdev, field->report);
break;
}
@@ -644,8 +657,11 @@ static struct mt_application *mt_allocate_application(struct mt_device *td,
/*
* Model touchscreens providing buttons as touchpads.
+ * Yoga Book 9 has an emulated touchpad but its touch surfaces
+ * are direct screens, not indirect pointers.
*/
- if (application == HID_DG_TOUCHPAD) {
+ if (application == HID_DG_TOUCHPAD &&
+ !(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I)) {
mt_application->mt_flags |= INPUT_MT_POINTER;
td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
}
@@ -802,11 +818,15 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
/*
* Model touchscreens providing buttons as touchpads.
+ * Skip for Yoga Book 9 which has stylus buttons inside
+ * touchscreen collections, not physical touchpad buttons.
*/
if (field->application == HID_DG_TOUCHSCREEN &&
(usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
- app->mt_flags |= INPUT_MT_POINTER;
- td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
+ if (!(app->quirks & MT_QUIRK_YOGABOOK9I)) {
+ app->mt_flags |= INPUT_MT_POINTER;
+ td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
+ }
}
/* count the buttons on touchpads */
@@ -1420,7 +1440,6 @@ static int mt_touch_input_configured(struct hid_device *hdev,
*/
if (cls->quirks & MT_QUIRK_APPLE_TOUCHBAR)
app->mt_flags |= INPUT_MT_DIRECT;
-
if (cls->is_indirect)
app->mt_flags |= INPUT_MT_POINTER;
@@ -1432,7 +1451,8 @@ static int mt_touch_input_configured(struct hid_device *hdev,
/* check for clickpads */
if ((app->mt_flags & INPUT_MT_POINTER) &&
- (app->buttons_count == 1))
+ (app->buttons_count == 1) &&
+ !(app->quirks & MT_QUIRK_YOGABOOK9I))
td->is_buttonpad = true;
if (td->is_buttonpad)
--
2.53.0
^ permalink raw reply related
* [PATCH] HID: sony: update module description
From: Rosalie Wanders @ 2026-04-02 15:59 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit updates the hid-sony module description to make it correct
with the recent hid-sony changes alongside making it more consistent.
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/Kconfig | 12 +++++++-----
drivers/hid/hid-sony.c | 6 +++---
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index c1d9f7c6a5f2..b287023437ca 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1100,13 +1100,15 @@ config HID_SONY
help
Support for
- * Sony PS3 6-axis controllers
+ * Sixaxis controllers for PS3
* Buzz controllers
- * Sony PS3 Blue-ray Disk Remote Control (Bluetooth)
- * Logitech Harmony adapter for Sony Playstation 3 (Bluetooth)
- * Guitar Hero Live PS3, Wii U and PS4 guitar dongles
- * Guitar Hero PS3 and PC guitar dongles
+ * Blu-ray Disc Remote Control for PS3
+ * Logitech Harmony adapter for PS3
+ * Guitar Hero Live PS3, Wii U and PS4 guitars
+ * Guitar Hero PS3 and PC guitars
+ * Rock Band 1, 2 and 3 PS3 and Wii instruments
* Rock Band 4 PS4 and PS5 guitars
+ * DJ Hero Turntable for PS3
config SONY_FF
bool "Sony PS2/3/4 accessories force feedback support"
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 4b0992cfc8a7..bd4b4a470869 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * HID driver for Sony / PS2 / PS3 / PS4 / PS5 BD devices.
+ * HID driver for Sony / PS2 / PS3 BD / PS4 / PS5 devices.
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
@@ -68,7 +68,7 @@
#define RB4_GUITAR_PS4_BT BIT(19)
#define RB4_GUITAR_PS5 BIT(20)
#define RB3_PS3_PRO_INSTRUMENT BIT(21)
-#define PS3_DJH_TURNTABLE BIT(22)
+#define PS3_DJH_TURNTABLE BIT(22)
#define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
#define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
@@ -2616,5 +2616,5 @@ static void __exit sony_exit(void)
module_init(sony_init);
module_exit(sony_exit);
-MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 / PS4 / PS5 BD devices");
+MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 BD / PS4 / PS5 devices");
MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox