[PATCH v8 3/3] HID: nintendo: Add unified report format support
From: Vicki Pfau <hidden>
Date: 2026-07-01 03:16:19
Subsystem:
hid core layer, nintendo hid driver, the rest · Maintainers:
Jiri Kosina, Benjamin Tissoires, Daniel J. Ogorchock, Linus Torvalds
This adds support for the "unified" report format that all controllers also support, which has overlapping fields for like buttons and axes between them. Signed-off-by: Vicki Pfau <redacted> --- drivers/hid/hid-nintendo.c | 151 +++++++++++++++++++++++++++++++++++-- 1 file changed, 146 insertions(+), 5 deletions(-)
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 00ab4bee3dab..0bddfd84fd17 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c@@ -2872,6 +2872,36 @@ static int joycon_suspend(struct hid_device *hdev, pm_message_t message) #define NS2_BTN3_SR BIT(6) #define NS2_BTN3_SL BIT(7) +#define NS2_BTN_U1_Y BIT(0) +#define NS2_BTN_U1_X BIT(1) +#define NS2_BTN_U1_B BIT(2) +#define NS2_BTN_U1_A BIT(3) +#define NS2_BTN_U1_SR BIT(4) +#define NS2_BTN_U1_SL BIT(5) +#define NS2_BTN_U1_R BIT(6) +#define NS2_BTN_U1_ZR BIT(7) + +#define NS2_BTN_U2_MINUS BIT(0) +#define NS2_BTN_U2_PLUS BIT(1) +#define NS2_BTN_U2_RS BIT(2) +#define NS2_BTN_U2_LS BIT(3) +#define NS2_BTN_U2_HOME BIT(4) +#define NS2_BTN_U2_CAPTURE BIT(5) +#define NS2_BTN_U2_C BIT(6) + +#define NS2_BTN_U3_DOWN BIT(0) +#define NS2_BTN_U3_UP BIT(1) +#define NS2_BTN_U3_RIGHT BIT(2) +#define NS2_BTN_U3_LEFT BIT(3) +#define NS2_BTN_U3_SR BIT(4) +#define NS2_BTN_U3_SL BIT(5) +#define NS2_BTN_U3_L BIT(6) +#define NS2_BTN_U3_ZL BIT(7) + +#define NS2_BTN_U4_GR BIT(0) +#define NS2_BTN_U4_GL BIT(1) +#define NS2_BTN_U4_HEADSET BIT(5) + #define NS2_BTN_JCR_HOME BIT(0) #define NS2_BTN_JCR_GR BIT(2) #define NS2_BTN_JCR_C NS2_BTN3_C
@@ -3120,6 +3150,22 @@ static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_mappings[ { /* sentinel */ }, }; +static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_unified_mappings[] = { + { BTN_DPAD_LEFT, 2, NS2_BTN_U3_LEFT, }, + { BTN_DPAD_UP, 2, NS2_BTN_U3_UP, }, + { BTN_DPAD_DOWN, 2, NS2_BTN_U3_DOWN, }, + { BTN_DPAD_RIGHT, 2, NS2_BTN_U3_RIGHT, }, + { BTN_TL, 2, NS2_BTN_U3_L, }, + { BTN_TL2, 2, NS2_BTN_U3_ZL, }, + { BTN_SELECT, 1, NS2_BTN_U2_MINUS, }, + { BTN_THUMBL, 1, NS2_BTN_U2_LS, }, + { KEY_RECORD, 1, NS2_BTN_U2_CAPTURE, }, + { BTN_GRIPR, 2, NS2_BTN_U3_SL, }, + { BTN_GRIPR2, 2, NS2_BTN_U3_SR, }, + { BTN_GRIPL, 3, NS2_BTN_U4_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, },
@@ -3137,6 +3183,23 @@ static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings { /* sentinel */ }, }; +static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_unified_mappings[] = { + { BTN_SOUTH, 0, NS2_BTN_U1_A, }, + { BTN_EAST, 0, NS2_BTN_U1_B, }, + { BTN_NORTH, 0, NS2_BTN_U1_X, }, + { BTN_WEST, 0, NS2_BTN_U1_Y, }, + { BTN_TR, 0, NS2_BTN_U1_R, }, + { BTN_TR2, 0, NS2_BTN_U1_ZR }, + { BTN_START, 1, NS2_BTN_U2_PLUS, }, + { BTN_THUMBR, 1, NS2_BTN_U2_RS, }, + { BTN_C, 1, NS2_BTN_U2_C, }, + { BTN_MODE, 1, NS2_BTN_U2_HOME, }, + { BTN_GRIPL2, 0, NS2_BTN_U1_SL, }, + { BTN_GRIPL, 0, NS2_BTN_U1_SR, }, + { BTN_GRIPR, 3, NS2_BTN_U4_GR, }, + { /* sentinel */ }, +}; + static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = { { BTN_SOUTH, 0, NS2_BTNR_A, }, { BTN_EAST, 0, NS2_BTNR_B, },
@@ -3158,6 +3221,27 @@ static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = { { /* sentinel */ }, }; +static const struct switch2_ctlr_button_mapping ns2_procon_unified_mappings[] = { + { BTN_SOUTH, 0, NS2_BTN_U1_A, }, + { BTN_EAST, 0, NS2_BTN_U1_B, }, + { BTN_NORTH, 0, NS2_BTN_U1_X, }, + { BTN_WEST, 0, NS2_BTN_U1_Y, }, + { BTN_TL, 2, NS2_BTN_U3_L, }, + { BTN_TR, 0, NS2_BTN_U1_R, }, + { BTN_TL2, 2, NS2_BTN_U3_ZL, }, + { BTN_TR2, 0, NS2_BTN_U1_ZR, }, + { BTN_SELECT, 1, NS2_BTN_U2_MINUS, }, + { BTN_START, 1, NS2_BTN_U2_PLUS, }, + { BTN_THUMBL, 1, NS2_BTN_U2_LS, }, + { BTN_THUMBR, 1, NS2_BTN_U2_RS, }, + { BTN_MODE, 1, NS2_BTN_U2_HOME }, + { KEY_RECORD, 1, NS2_BTN_U2_CAPTURE }, + { BTN_GRIPR, 3, NS2_BTN_U4_GR }, + { BTN_GRIPL, 3, NS2_BTN_U4_GL }, + { BTN_C, 1, NS2_BTN_U2_C }, + { /* sentinel */ }, +}; + static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = { { BTN_SOUTH, 0, NS2_BTNR_A, }, { BTN_EAST, 0, NS2_BTNR_B, },
@@ -3175,6 +3259,23 @@ static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = { { /* sentinel */ }, }; +static const struct switch2_ctlr_button_mapping ns2_gccon_unified_mappings[] = { + { BTN_SOUTH, 0, NS2_BTN_U1_A, }, + { BTN_EAST, 0, NS2_BTN_U1_B, }, + { BTN_NORTH, 0, NS2_BTN_U1_X, }, + { BTN_WEST, 0, NS2_BTN_U1_Y, }, + { BTN_TL2, 2, NS2_BTN_U3_L, }, + { BTN_TR2, 0, NS2_BTN_U1_R, }, + { BTN_TL, 2, NS2_BTN_U3_ZL }, + { BTN_TR, 0, NS2_BTN_U1_ZR }, + { BTN_SELECT, 1, NS2_BTN_U2_MINUS, }, + { BTN_START, 1, NS2_BTN_U2_PLUS, }, + { BTN_MODE, 1, NS2_BTN_U2_HOME }, + { KEY_RECORD, 1, NS2_BTN_U2_CAPTURE }, + { BTN_C, 1, NS2_BTN_U2_C }, + { /* sentinel */ }, +}; + static const uint8_t switch2_init_cmd_data[] = { /* * The last 6 bytes of this packet are the MAC address of
@@ -3787,11 +3888,51 @@ static int switch2_event(struct hid_device *hdev, struct hid_report *report, uin 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. - */ + if (size < 0x3f) + return -EINVAL; + + switch (ns2->ctlr_type) { + case NS2_CTLR_TYPE_JCL: + switch2_report_stick(input, &ns2->stick_calib[0], + ABS_X, false, ABS_Y, true, &raw_data[11]); + switch2_report_buttons(input, &raw_data[5], + ns2_left_joycon_button_unified_mappings); + break; + case NS2_CTLR_TYPE_JCR: + switch2_report_stick(input, &ns2->stick_calib[0], + ABS_X, false, ABS_Y, true, &raw_data[14]); + switch2_report_buttons(input, &raw_data[5], + ns2_right_joycon_button_unified_mappings); + break; + case NS2_CTLR_TYPE_GC: + input_report_abs(input, ABS_HAT0X, + !!(raw_data[7] & NS2_BTN_U3_RIGHT) - + !!(raw_data[7] & NS2_BTN_U3_LEFT)); + input_report_abs(input, ABS_HAT0Y, + !!(raw_data[7] & NS2_BTN_U3_DOWN) - + !!(raw_data[7] & NS2_BTN_U3_UP)); + switch2_report_buttons(input, &raw_data[5], ns2_gccon_unified_mappings); + switch2_report_stick(input, &ns2->stick_calib[0], + ABS_X, false, ABS_Y, true, &raw_data[11]); + switch2_report_stick(input, &ns2->stick_calib[1], + ABS_RX, false, ABS_RY, true, &raw_data[14]); + switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[0x3d]); + switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[0x3e]); + break; + case NS2_CTLR_TYPE_PRO: + input_report_abs(input, ABS_HAT0X, + !!(raw_data[7] & NS2_BTN_U3_RIGHT) - + !!(raw_data[7] & NS2_BTN_U3_LEFT)); + input_report_abs(input, ABS_HAT0Y, + !!(raw_data[7] & NS2_BTN_U3_DOWN) - + !!(raw_data[7] & NS2_BTN_U3_UP)); + switch2_report_buttons(input, &raw_data[5], ns2_procon_unified_mappings); + switch2_report_stick(input, &ns2->stick_calib[0], + ABS_X, false, ABS_Y, true, &raw_data[11]); + switch2_report_stick(input, &ns2->stick_calib[1], + ABS_RX, false, ABS_RY, true, &raw_data[14]); + break; + } break; case NS2_REPORT_JCL: switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
--
2.54.0