Skip to content

Commit

Permalink
HID: nintendo: Add support for the Nintendo Switch SNES controller.
Browse files Browse the repository at this point in the history
It basically acts like a Pro controller but without analog sticks.
A separate mapping has been added to correctly represent its button
layout and report the d-pad inputs as a hat instead ofseparate buttons.

A macro to determine whether or not the controller has a USB port has also
been added to make the code clearer when this is checked since there are
now more possible devices.

Signed-off-by: Nadia Holmquist Pedersen <nadia@nhp.sh>
  • Loading branch information
nadiaholmquist committed Jan 31, 2020
1 parent 684a84a commit bdf4626
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 18 deletions.
1 change: 1 addition & 0 deletions drivers/hid/hid-ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,7 @@
#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_VENDOR_ID_NOVATEK 0x0603
#define USB_DEVICE_ID_NOVATEK_PCT 0x0600
Expand Down
113 changes: 95 additions & 18 deletions drivers/hid/hid-nintendo.c
Original file line number Diff line number Diff line change
Expand Up @@ -412,16 +412,26 @@ struct joycon_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_snescon(ctlr) \
(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_SNESCON)

/* Does this controller have inputs associated with left joycon? */
#define jc_type_has_left(ctlr) \
(ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCL || \
ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO)
((ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCL || \
ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO) && \
!jc_type_is_snescon(ctlr))

/* Does this controller have inputs associated with right joycon? */
#define jc_type_has_right(ctlr) \
(ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR || \
ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO)
((ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR || \
ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO) && \
!jc_type_is_snescon(ctlr))

/* 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))

static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
{
Expand Down Expand Up @@ -918,6 +928,37 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
input_report_key(dev, BTN_SOUTH, btns & JC_BTN_B);
}

if (jc_type_is_snescon(ctlr)) {
s8 x = 0;
s8 y = 0;

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_TL, btns & JC_BTN_L);
input_report_key(dev, BTN_TL2, btns & JC_BTN_ZL);
input_report_key(dev, BTN_TR, btns & JC_BTN_R);
input_report_key(dev, BTN_TR2, btns & JC_BTN_ZR);
input_report_key(dev, BTN_SELECT, btns & JC_BTN_MINUS);
input_report_key(dev, BTN_START, btns & JC_BTN_PLUS);

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_NORTH, btns & JC_BTN_X);
input_report_key(dev, BTN_WEST, btns & JC_BTN_Y);
}

input_sync(dev);

/*
Expand All @@ -933,7 +974,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_type_is_snescon(ctlr)) {
struct joycon_imu_data *imu_data = rep->imu_report.data;
struct input_dev *idev = ctlr->imu_input;
int i;
Expand Down Expand Up @@ -1154,6 +1195,12 @@ static const unsigned int joycon_button_inputs_r[] = {
0 /* 0 signals end of array */
};

static const unsigned int snescon_button_inputs[] = {
BTN_SELECT, BTN_START, BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST,
BTN_TL, BTN_TL2, BTN_TR, BTN_TR2,
0 /* 0 signals end of array */
};

static int joycon_input_create(struct joycon_ctlr *ctlr)
{
struct hid_device *hdev;
Expand Down Expand Up @@ -1186,6 +1233,10 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
name = "Nintendo Switch Right Joy-Con";
imu_name = "Nintendo Switch Right Joy-Con IMU";
break;
case USB_DEVICE_ID_NINTENDO_SNESCON:
name = "Nintendo Switch SNES Controller";
imu_name = NULL;
break;
default: /* Should be impossible */
hid_err(hdev, "Invalid hid product\n");
return -EINVAL;
Expand Down Expand Up @@ -1228,6 +1279,23 @@ 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_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 IS_ENABLED(CONFIG_NINTENDO_FF)
/* set up rumble */
Expand Down Expand Up @@ -1706,8 +1774,8 @@ 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 Down Expand Up @@ -1759,18 +1827,23 @@ static int nintendo_hid_probe(struct hid_device *hdev,
goto err_mutex;
}

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

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

/* Enable the IMU */
ret = joycon_enable_imu(ctlr, true);
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 @@ -1849,6 +1922,10 @@ static const struct hid_device_id nintendo_hid_devices[] = {
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) },
{ }
};
MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
Expand Down

0 comments on commit bdf4626

Please sign in to comment.