Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH v2] HID: nintendo: add support for HORI Wireless Switch Pad
@ 2026-05-27 16:01 Hector Zelaya
  2026-05-27 16:51 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Hector Zelaya @ 2026-05-27 16:01 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Daniel J. Ogorchock
  Cc: linux-input, linux-kernel, Hector Zelaya, Joshua Peisach

Add support for the HORI Wireless Switch Pad (vendor 0x0f0d, product
0x00f6), a licensed third-party Nintendo Switch Pro Controller.

The controller reports controller type 0x06 (vs 0x03 for first-party
Pro Controllers) and has the following quirks:

 - SPI flash calibration data is incompatible; use default stick
   calibration values instead.
 - X and Y button bits are swapped compared to first-party controllers;
   add a dedicated button mapping table.
 - Rumble and IMU enable may timeout (no vibration motor in hardware);
   treat as non-fatal for licensed controllers.

Tested over Bluetooth on NixOS with kernel 7.0.5 and 7.0.10:
 - All 14 buttons map correctly
 - Player LED sets on connect
 - Sticks report correctly with default calibration
 - IMU/gyro data streams at 60Hz
 - D-pad reports on ABS_HAT0X/HAT0Y

Device information:
  Bluetooth name: Lic Pro Controller
  Bluetooth HID:  0005:0F0D:00F6

Assisted-by: Kiro:Auto [Amazon Kiro IDE]
Signed-off-by: Hector Zelaya <hector@hectorzelaya.dev>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
Changes in v2:
- Clear ret = 0 after non-fatal IMU/rumble timeouts to prevent
  unintentional probe failure.
- Link to v1: https://patch.msgid.link/20260526-hori-support-v1-1-1861c0abc2e0@hectorzelaya.dev

To: Jiri Kosina <jikos@kernel.org>
To: Benjamin Tissoires <bentiss@kernel.org>
To: "Daniel J. Ogorchock" <djogorchock@gmail.com>
Cc: linux-input@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
---
 drivers/hid/hid-ids.h      |  3 ++
 drivers/hid/hid-nintendo.c | 80 +++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 71 insertions(+), 12 deletions(-)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index a1cfa436344a..3b0767cc47fd 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -683,6 +683,9 @@
 #define USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD	        0x3330
 #define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE	0x3338
 
+#define USB_VENDOR_ID_HORI			0x0f0d
+#define USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD	0x00f6
+
 #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
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 29008c2cc530..2d37ddeffdb6 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -316,6 +316,7 @@ enum joycon_ctlr_type {
 	JOYCON_CTLR_TYPE_JCL  = 0x01,
 	JOYCON_CTLR_TYPE_JCR  = 0x02,
 	JOYCON_CTLR_TYPE_PRO  = 0x03,
+	JOYCON_CTLR_TYPE_LIC_PRO = 0x06,
 	JOYCON_CTLR_TYPE_NESL = 0x09,
 	JOYCON_CTLR_TYPE_NESR = 0x0A,
 	JOYCON_CTLR_TYPE_SNES = 0x0B,
@@ -433,6 +434,25 @@ static const struct joycon_ctlr_button_mapping procon_button_mappings[] = {
 	{ /* sentinel */ },
 };
 
+/* Licensed Pro Controllers (e.g. HORI) swap X/Y bits in the report */
+static const struct joycon_ctlr_button_mapping lic_procon_button_mappings[] = {
+	{ BTN_EAST,	JC_BTN_A,	},
+	{ BTN_SOUTH,	JC_BTN_B,	},
+	{ BTN_NORTH,	JC_BTN_Y,	},
+	{ BTN_WEST,	JC_BTN_X,	},
+	{ BTN_TL,	JC_BTN_L,	},
+	{ BTN_TR,	JC_BTN_R,	},
+	{ BTN_TL2,	JC_BTN_ZL,	},
+	{ BTN_TR2,	JC_BTN_ZR,	},
+	{ BTN_SELECT,	JC_BTN_MINUS,	},
+	{ BTN_START,	JC_BTN_PLUS,	},
+	{ BTN_THUMBL,	JC_BTN_LSTICK,	},
+	{ BTN_THUMBR,	JC_BTN_RSTICK,	},
+	{ BTN_MODE,	JC_BTN_HOME,	},
+	{ BTN_Z,	JC_BTN_CAP,	},
+	{ /* sentinel */ },
+};
+
 static const struct joycon_ctlr_button_mapping nescon_button_mappings[] = {
 	{ BTN_SOUTH,	JC_BTN_A,	},
 	{ BTN_EAST,	JC_BTN_B,	},
@@ -695,7 +715,8 @@ static inline bool joycon_type_is_right_joycon(struct joycon_ctlr *ctlr)
 
 static inline bool joycon_type_is_procon(struct joycon_ctlr *ctlr)
 {
-	return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO;
+	return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO ||
+	       ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO;
 }
 
 static inline bool joycon_type_is_snescon(struct joycon_ctlr *ctlr)
@@ -1710,7 +1731,10 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 		joycon_report_left_stick(ctlr, rep);
 		joycon_report_right_stick(ctlr, rep);
 		joycon_report_dpad(ctlr, rep);
-		joycon_report_buttons(ctlr, rep, procon_button_mappings);
+		if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO)
+			joycon_report_buttons(ctlr, rep, lic_procon_button_mappings);
+		else
+			joycon_report_buttons(ctlr, rep, procon_button_mappings);
 	} else if (joycon_type_is_any_nescon(ctlr)) {
 		joycon_report_dpad(ctlr, rep);
 		joycon_report_buttons(ctlr, rep, nescon_button_mappings);
@@ -2156,7 +2180,10 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 		joycon_config_left_stick(ctlr->input);
 		joycon_config_right_stick(ctlr->input);
 		joycon_config_dpad(ctlr->input);
-		joycon_config_buttons(ctlr->input, procon_button_mappings);
+		if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO)
+			joycon_config_buttons(ctlr->input, lic_procon_button_mappings);
+		else
+			joycon_config_buttons(ctlr->input, procon_button_mappings);
 	} else if (joycon_type_is_any_nescon(ctlr)) {
 		joycon_config_dpad(ctlr->input);
 		joycon_config_buttons(ctlr->input, nescon_button_mappings);
@@ -2503,13 +2530,30 @@ static int joycon_init(struct hid_device *hdev)
 
 	if (joycon_has_joysticks(ctlr)) {
 		/* get controller calibration data, and parse it */
-		ret = joycon_request_calibration(ctlr);
-		if (ret) {
+		if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) {
 			/*
-			 * We can function with default calibration, but it may be
-			 * inaccurate. Provide a warning, and continue on.
+			 * Licensed controllers may have incompatible SPI flash
+			 * layouts. Use default calibration values.
 			 */
-			hid_warn(hdev, "Analog stick positions may be inaccurate\n");
+			hid_info(hdev, "using default cal for licensed controller\n");
+			joycon_use_default_calibration(hdev,
+						       &ctlr->left_stick_cal_x,
+						       &ctlr->left_stick_cal_y,
+						       "left", 0);
+			joycon_use_default_calibration(hdev,
+						       &ctlr->right_stick_cal_x,
+						       &ctlr->right_stick_cal_y,
+						       "right", 0);
+		} else {
+			ret = joycon_request_calibration(ctlr);
+			if (ret) {
+				/*
+				 * We can function with default calibration, but
+				 * it may be inaccurate. Provide a warning, and
+				 * continue on.
+				 */
+				hid_warn(hdev, "Analog stick positions may be inaccurate\n");
+			}
 		}
 	}
 
@@ -2527,8 +2571,13 @@ static int joycon_init(struct hid_device *hdev)
 		/* Enable the IMU */
 		ret = joycon_enable_imu(ctlr);
 		if (ret) {
-			hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
-			goto out_unlock;
+			if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) {
+				hid_dbg(hdev, "IMU enable failed for licensed controller, continuing\n");
+				ret = 0;
+			} else {
+				hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
+				goto out_unlock;
+			}
 		}
 	}
 
@@ -2543,8 +2592,13 @@ static int joycon_init(struct hid_device *hdev)
 		/* Enable rumble */
 		ret = joycon_enable_rumble(ctlr);
 		if (ret) {
-			hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
-			goto out_unlock;
+			if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) {
+				hid_dbg(hdev, "rumble enable failed for licensed controller, continuing\n");
+				ret = 0;
+			} else {
+				hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
+				goto out_unlock;
+			}
 		}
 	}
 
@@ -2813,6 +2867,8 @@ 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) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_HORI,
+			 USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD) },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);

---
base-commit: e71bac24ec1f517f399a9eb471255b8f1c330b93
change-id: 20260526-hori-support-08b08bca40d8

Best regards,
--  
Hector Zelaya <hector@hectorzelaya.dev>


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-05-27 16:51 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-27 16:01 [PATCH v2] HID: nintendo: add support for HORI Wireless Switch Pad Hector Zelaya
2026-05-27 16:51 ` sashiko-bot

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