Linux Input/HID development
 help / color / mirror / Atom feed
* [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


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox