* [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
* Re: [PATCH v2] HID: nintendo: add support for HORI Wireless Switch Pad
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
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-05-27 16:51 UTC (permalink / raw)
To: Hector Zelaya; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Incompatible SPI flash data is parsed as IMU calibration for licensed controllers, leading to invalid sensor scaling and broken IMU output.
- [Medium] The controller implicitly and falsely advertises force-feedback (rumble) capabilities to user-space, despite physically lacking a vibration motor.
- [Low] Bypassing stick calibration triggers a misleading error warning on every connection.
Pre-existing issues:
- [High] The IMU report parser reads out of bounds if a truncated `JC_INPUT_IMU_DATA` report is received.
--
commit 376e2cc446988e8ea86a066e021e9f1adc2e0081
Author: Hector Zelaya <hector@hectorzelaya.dev>
Subject: HID: nintendo: add support for HORI Wireless Switch Pad
This commit adds support for the HORI Wireless Switch Pad (vendor 0x0f0d,
product 0x00f6). It configures quirks for the third-party controller, such as
handling incompatible SPI flash calibration data, swapped X/Y buttons, and the
lack of hardware vibration.
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index 29008c2cc5304..2d37ddeffdb6e 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
[ ... ]
> @@ -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;
> }
[Severity: Medium]
Since the commit message states there is "no vibration motor in hardware",
does returning true here incorrectly advertise force-feedback capabilities to
userspace?
Because joycon_type_is_procon() returns true, joycon_has_rumble() will also
return true. This causes joycon_input_create() to call joycon_config_rumble(),
which registers an FF_RUMBLE capability with the input subsystem, creating a
phantom rumble device.
[ ... ]
> @@ -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)) {
[Severity: High]
This is a pre-existing issue, but can parsing the IMU data from this
controller lead to an out-of-bounds read if a truncated report is received?
In joycon_ctlr_read_handler(), the length check only verifies size >= 12
before calling joycon_parse_report(). If the report ID is JC_INPUT_IMU_DATA,
joycon_parse_report() delegates to joycon_parse_imu_report().
The IMU parsing logic assumes 36 bytes of IMU data starting at offset 13.
Processing a truncated IMU report over Bluetooth could lead to an
out-of-bounds read from the socket buffer.
[ ... ]
> @@ -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);
[Severity: Low]
Does this trigger a misleading warning on every connection?
joycon_use_default_calibration() unconditionally prints "Failed to read ...
stick cal, using defaults; e=%d" using hid_warn(). Calling it directly with
ret=0 will print a confusing failure message even though no read was actually
attempted.
> + 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)
[Severity: High]
Should we also bypass joycon_request_imu_calibration() for licensed
controllers?
The patch acknowledges the SPI flash layout is incompatible and bypasses
analog stick calibration. However, joycon_has_imu() will return true for
LIC_PRO, meaning joycon_request_imu_calibration() is still called right
before this hunk.
If the SPI read succeeds but returns unexpected data or zeroes from the
incompatible flash, the IMU calibration scales and offsets could be populated
with garbage. If the read scales are parsed as 0, the computed divisors are
adjusted to 1, and the resulting IMU values in joycon_parse_imu_report() will
always evaluate to 0, breaking IMU functionality.
> /* 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;
> + }
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260527-hori-support-v2-1-195c2f832112@hectorzelaya.dev?part=1
^ permalink raw reply [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