Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preliminary support for Nintendo Switch Online controllers #35

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions drivers/hid/hid-ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,8 @@
#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_VENDOR_ID_NOVATEK 0x0603
#define USB_DEVICE_ID_NOVATEK_PCT 0x0600
Expand Down
256 changes: 212 additions & 44 deletions drivers/hid/hid-nintendo.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ enum joycon_ctlr_type {
JOYCON_CTLR_TYPE_JCL = 0x01,
JOYCON_CTLR_TYPE_JCR = 0x02,
JOYCON_CTLR_TYPE_PRO = 0x03,
JOYCON_CTLR_TYPE_NESL = 0x09,
JOYCON_CTLR_TYPE_NESR = 0x0A,
JOYCON_CTLR_TYPE_SNES = 0x0B,
JOYCON_CTLR_TYPE_GEN = 0x0D,
};

struct joycon_stick_cal {
Expand Down Expand Up @@ -480,13 +484,22 @@ struct joycon_ctlr {

/* Helper macros for checking controller type */
#define jc_type_is_joycon(ctlr) \
(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL || \
ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR || \
ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP)
((ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL || \
ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR || \
ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP) && \
!jc_type_is_nescon(ctlr))
#define jc_type_is_procon(ctlr) \
(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_PROCON)
#define jc_type_is_chrggrip(ctlr) \
(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP)
#define jc_type_is_nescon(ctlr) \
(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR && \
(ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESL || \
ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESR))
#define jc_type_is_snescon(ctlr) \
(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_SNESCON)
#define jc_type_is_gencon(ctlr) \
(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_GENCON)

/* Does this controller have inputs associated with left joycon? */
#define jc_type_has_left(ctlr) \
Expand All @@ -498,6 +511,16 @@ struct joycon_ctlr {
(ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR || \
ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO)

/* Can this controller be connected via USB */
#define jc_has_usb(ctlr) \
(jc_type_is_procon(ctlr) || \
jc_type_is_chrggrip(ctlr) || \
jc_type_is_snescon(ctlr) || \
jc_type_is_gencon(ctlr))

#define jc_has_imu(ctlr) \
(jc_type_is_joycon(ctlr) || jc_type_is_procon(ctlr))

static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
{
u8 *buf;
Expand Down Expand Up @@ -1354,6 +1377,44 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
input_report_key(dev, BTN_EAST, btns & JC_BTN_A);
input_report_key(dev, BTN_SOUTH, btns & JC_BTN_B);
}
if (jc_type_is_nescon(ctlr) || \
jc_type_is_snescon(ctlr) || \
jc_type_is_gencon(ctlr)) {
Comment on lines +1380 to +1382
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am undecided on if I should define a helper macro to the effect of jc_type_is_nso to include all the NSO controllers at once. That said, they share a few characteristics that would make such a helper macro reasonable (lack of motion and rumble, for example).

Happy to get input on this.

s8 x;
s8 y;

if (btns & JC_BTN_LEFT)
x = -1;
else if (btns & JC_BTN_RIGHT)
x = 1;

if (btns & JC_BTN_UP)
y = -1;
else if (btns & JC_BTN_DOWN)
y = 1;

input_report_abs(dev, ABS_HAT0X, x);
input_report_abs(dev, ABS_HAT0Y, y);

/* report buttons */
input_report_key(dev, BTN_EAST, btns & JC_BTN_A);
input_report_key(dev, BTN_SOUTH, btns & JC_BTN_B);
input_report_key(dev, BTN_SELECT, btns & JC_BTN_MINUS);
input_report_key(dev, BTN_START, btns & JC_BTN_PLUS);

if (jc_type_is_snescon(ctlr)) {
input_report_key(dev, BTN_TL, btns & JC_BTN_L);
input_report_key(dev, BTN_TR, btns & JC_BTN_R);
input_report_key(dev, BTN_TL2, btns & JC_BTN_ZL);
input_report_key(dev, BTN_TR2, btns & JC_BTN_ZR);
input_report_key(dev, BTN_NORTH, btns & JC_BTN_X);
input_report_key(dev, BTN_WEST, btns & JC_BTN_Y);
}
if (jc_type_is_gencon(ctlr)) {
input_report_key(dev, BTN_C, btns & JC_BTN_R);
input_report_key(dev, BTN_MODE, btns & JC_BTN_ZR);
}
}

input_sync(dev);

Expand All @@ -1370,7 +1431,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
}

/* parse IMU data if present */
if (rep->id == JC_INPUT_IMU_DATA)
if (rep->id == JC_INPUT_IMU_DATA && jc_has_imu(ctlr))
joycon_parse_imu_report(ctlr, rep);
}

Expand Down Expand Up @@ -1586,6 +1647,39 @@ static const unsigned int joycon_dpad_inputs_jc[] = {
BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
};

static const unsigned int nescon_button_inputs[] = {
BTN_SELECT, /* "Select" */
BTN_START, /* "Start" */
BTN_SOUTH, /* "B" */
BTN_EAST, /* "A" */
BTN_TL, /* "L" */
BTN_TR, /* "R" */
0 /* 0 signals end of array */
};

static const unsigned int snescon_button_inputs[] = {
BTN_SELECT, /* "Select" */
BTN_START, /* "Start" */
BTN_SOUTH, /* "B" */
BTN_EAST, /* "A" */
BTN_NORTH, /* "X" */
BTN_WEST, /* "Y" */
BTN_TL, /* "L" */
BTN_TL2, /* "ZL" */
BTN_TR, /* "R" */
BTN_TR2, /* "ZR" */
0 /* 0 signals end of array */
};

static const unsigned int gencon_button_inputs[] = {
BTN_START, /* "Start" */
BTN_EAST, /* "A" */
BTN_SOUTH, /* "B" */
BTN_C, /* "C" */
BTN_MODE, /* "Mode" */
0 /* 0 signals end of array */
};

static int joycon_input_create(struct joycon_ctlr *ctlr)
{
struct hid_device *hdev;
Expand Down Expand Up @@ -1615,8 +1709,23 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
imu_name = "Nintendo Switch Left Joy-Con IMU";
break;
case USB_DEVICE_ID_NINTENDO_JOYCONR:
name = "Nintendo Switch Right Joy-Con";
imu_name = "Nintendo Switch Right Joy-Con IMU";
if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESL) {
name = "Nintendo Controller for Nintendo Switch Online (L)";
imu_name = NULL;
} else if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESR) {
name = "Nintendo Controller for Nintendo Switch Online (R)";
imu_name = NULL;
} else {
name = "Nintendo Switch Right Joy-Con";
imu_name = "Nintendo Switch Right Joy-Con IMU";
}
case USB_DEVICE_ID_NINTENDO_SNESCON:
name = "Super Nintendo Controller for Nintendo Switch Online";
imu_name = NULL;
break;
case USB_DEVICE_ID_NINTENDO_GENCON:
name = "SEGA Genesis Control Pad for Nintendo Switch Online";
imu_name = NULL;
break;
default: /* Should be impossible */
hid_err(hdev, "Invalid hid product\n");
Expand Down Expand Up @@ -1673,6 +1782,54 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
input_set_capability(ctlr->input, EV_KEY,
joycon_button_inputs_r[i]);
}
if (jc_type_is_nescon(ctlr)) {
/* set up dpad hat */
input_set_abs_params(ctlr->input, ABS_HAT0X, -1, 1, 0, 0);
input_set_abs_params(ctlr->input, ABS_HAT0Y, -1, 1, 0, 0);

/* set up buttons */
for (i = 0; nescon_button_inputs[i] > 0; i++)
input_set_capability(ctlr->input, EV_KEY, nescon_button_inputs[i]);

/* register the device here, we don't need any more setup */
ret = input_register_device(ctlr->input);
if (ret)
return ret;

return 0;
}
if (jc_type_is_snescon(ctlr)) {
/* set up dpad hat */
input_set_abs_params(ctlr->input, ABS_HAT0X, -1, 1, 0, 0);
input_set_abs_params(ctlr->input, ABS_HAT0Y, -1, 1, 0, 0);

/* set up buttons */
for (i = 0; snescon_button_inputs[i] > 0; i++)
input_set_capability(ctlr->input, EV_KEY, snescon_button_inputs[i]);

/* register the device here, we don't need any more setup */
ret = input_register_device(ctlr->input);
if (ret)
return ret;

return 0;
}
if (jc_type_is_gencon(ctlr)) {
/* set up dpad hat */
input_set_abs_params(ctlr->input, ABS_HAT0X, -1, 1, 0, 0);
input_set_abs_params(ctlr->input, ABS_HAT0Y, -1, 1, 0, 0);

/* set up buttons */
for (i = 0; gencon_button_inputs[i] > 0; i++)
input_set_capability(ctlr->input, EV_KEY, gencon_button_inputs[i]);

/* register the device here, we don't need any more setup */
ret = input_register_device(ctlr->input);
if (ret)
return ret;

return 0;
}

/* Let's report joy-con S triggers separately */
if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL) {
Expand Down Expand Up @@ -2157,8 +2314,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
/* Initialize the controller */
mutex_lock(&ctlr->output_mutex);
/* if handshake command fails, assume ble pro controller */
if ((jc_type_is_procon(ctlr) || jc_type_is_chrggrip(ctlr)) &&
!joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) {
if (jc_has_usb(ctlr) && !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) {
hid_dbg(hdev, "detected USB controller\n");
/* set baudrate for improved latency */
ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M, HZ);
Expand All @@ -2183,45 +2339,49 @@ static int nintendo_hid_probe(struct hid_device *hdev,
goto err_mutex;
}

/* get controller calibration data, and parse it */
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");
}

/* get IMU calibration data, and parse it */
ret = joycon_request_imu_calibration(ctlr);
if (ret) {
/*
* We can function with default calibration, but it may be
* inaccurate. Provide a warning, and continue on.
*/
hid_warn(hdev, "Unable to read IMU calibration data\n");
}

/* Set the reporting mode to 0x30, which is the full report mode */
ret = joycon_set_report_mode(ctlr);
if (ret) {
hid_err(hdev, "Failed to set report mode; ret=%d\n", ret);
goto err_mutex;
}

/* Enable rumble */
ret = joycon_enable_rumble(ctlr);
if (ret) {
hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
goto err_mutex;
}
if (!jc_type_is_nescon(ctlr) &&
!jc_type_is_snescon(ctlr) &&
!jc_type_is_gencon(ctlr)) {
/* get controller calibration data, and parse it */
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");
}

/* Enable the IMU */
ret = joycon_enable_imu(ctlr);
if (ret) {
hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
goto err_mutex;
/* get IMU calibration data, and parse it */
ret = joycon_request_imu_calibration(ctlr);
if (ret) {
/*
* We can function with default calibration, but it may be
* inaccurate. Provide a warning, and continue on.
*/
hid_warn(hdev, "Unable to read IMU calibration data\n");
}

/* Enable rumble */
ret = joycon_enable_rumble(ctlr);
if (ret) {
hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
goto err_mutex;
}

/* Enable the IMU */
ret = joycon_enable_imu(ctlr);
if (ret) {
hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
goto err_mutex;
}
}

ret = joycon_read_info(ctlr);
Expand Down Expand Up @@ -2291,15 +2451,23 @@ static void nintendo_hid_remove(struct hid_device *hdev)

static const struct hid_device_id nintendo_hid_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_PROCON) },
USB_DEVICE_ID_NINTENDO_PROCON) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_PROCON) },
USB_DEVICE_ID_NINTENDO_PROCON) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_CHRGGRIP) },
USB_DEVICE_ID_NINTENDO_CHRGGRIP) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_JOYCONL) },
USB_DEVICE_ID_NINTENDO_JOYCONL) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_JOYCONR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_SNESCON) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_SNESCON) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_GENCON) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_JOYCONR) },
USB_DEVICE_ID_NINTENDO_GENCON) },
{ }
};
MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
Expand Down