Re: [PATCH] HID: switchcon: add nintendo switch controller driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Daniel,

Thanks for sharing your driver. Benjamin already shared some feedback,
since I didn't want to divert from his message, let me reply
separately.

I'm not sure if this is your first driver or not, but the driver was a
bit hard to review due to the amount of code. One suggestion would be
to split up the driver in more patches. For example Pro controller
could have been its own patch (it is a new device), maybe even
calibration could have been its own patch on top of your "base"
driver.

As Benjamin pointed out one big discussion item based on the other
thread is on the number of input devices. The pairing logic looks a
bit tricky. Should it really be managed by the kernel?

The Joycons have LEDs, I would expose these through sysfs through LED
class. Other drivers e.g. hid-sony do the same. Allows userspace to
override whatever "player" number. You can of course default to a
player id based on connection order. Hypothetical applications
supporting DualShock4, Xbox 360 gamepad and Switch controllers at the
same time, may want to set cross gamepad controller numbers.

At this point the design of the driver is most important, but there
are some style issues as well. Some of the ones I noticed:
- spacing between variable and assignment "int a = 1234;" instead of
"int a     = 1234;"
- mixedCase variables (newVal)

Thanks,
Roderick

On Sun, Jan 13, 2019 at 5:04 PM Daniel J. Ogorchock
<djogorchock@xxxxxxxxx> wrote:
>
> The switchcon driver supports the Nintendo Switch Pro Controllers and
> the Joy-Cons. The Pro Controllers can be used over USB or Bluetooth. The
> Joy-Cons can be used as a pair or individually in their horizontal
> mode.
>
> Signed-off-by: Daniel J. Ogorchock <djogorchock@xxxxxxxxx>
> ---
>  MAINTAINERS                 |    6 +
>  drivers/hid/Kconfig         |   13 +
>  drivers/hid/Makefile        |    1 +
>  drivers/hid/hid-ids.h       |    3 +
>  drivers/hid/hid-quirks.c    |    6 +
>  drivers/hid/hid-switchcon.c | 1156 +++++++++++++++++++++++++++++++++++
>  6 files changed, 1185 insertions(+)
>  create mode 100644 drivers/hid/hid-switchcon.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 39e75bbefc3d..9fc55efd63bc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14572,6 +14572,12 @@ F:     kernel/dma/swiotlb.c
>  F:     arch/*/kernel/pci-swiotlb.c
>  F:     include/linux/swiotlb.h
>
> +SWITCHCON HID DRIVER
> +M:     Daniel J. Ogorchock <djogorchock@xxxxxxxxx>
> +L:     linux-input@xxxxxxxxxxxxxxx
> +S:     Maintained
> +F:     drivers/hid/hid-switchcon*
> +
>  SWITCHDEV
>  M:     Jiri Pirko <jiri@xxxxxxxxxxx>
>  M:     Ivan Vecera <ivecera@xxxxxxxxxx>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 41e9935fc584..3360267eb5b0 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -943,6 +943,19 @@ config SMARTJOYPLUS_FF
>         Say Y here if you have a SmartJoy PLUS PS2/USB adapter and want to
>         enable force feedback support for it.
>
> +config HID_SWITCHCON
> +       tristate "Nintendo Joy-Con and Pro Controller support"
> +       depends on HID
> +       depends on USB_HID
> +       depends on BT_HIDP
> +       help
> +       Adds support for the Nintendo Switch Joy-Cons and Pro Controller.
> +       All controllers support bluetooth, and the Pro Controller also supports
> +       its USB mode.
> +
> +       To compile this driver as a module, choose M here: the
> +       module will be called hid-switchcon.
> +
>  config HID_TIVO
>         tristate "TiVo Slide Bluetooth remote control support"
>         depends on HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 896a51ce7ce0..e708e682c4b8 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -103,6 +103,7 @@ obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o
>  obj-$(CONFIG_HID_STEAM)                += hid-steam.o
>  obj-$(CONFIG_HID_STEELSERIES)  += hid-steelseries.o
>  obj-$(CONFIG_HID_SUNPLUS)      += hid-sunplus.o
> +obj-$(CONFIG_HID_SWITCHCON)    += hid-switchcon.o
>  obj-$(CONFIG_HID_GREENASIA)    += hid-gaff.o
>  obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o
>  obj-$(CONFIG_HID_TIVO)         += hid-tivo.o
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 518fa76414f5..cd41e971801e 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -848,6 +848,9 @@
>  #define USB_VENDOR_ID_NINTENDO         0x057e
>  #define USB_DEVICE_ID_NINTENDO_WIIMOTE 0x0306
>  #define USB_DEVICE_ID_NINTENDO_WIIMOTE2        0x0330
> +#define USB_DEVICE_ID_NINTENDO_JOYCONL 0x2006
> +#define USB_DEVICE_ID_NINTENDO_JOYCONR  0x2007
> +#define USB_DEVICE_ID_NINTENDO_PROCON   0x2009
>
>  #define USB_VENDOR_ID_NOVATEK          0x0603
>  #define USB_DEVICE_ID_NOVATEK_PCT      0x0600
> diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
> index 94088c0ed68a..c22adffa1233 100644
> --- a/drivers/hid/hid-quirks.c
> +++ b/drivers/hid/hid-quirks.c
> @@ -512,6 +512,12 @@ static const struct hid_device_id hid_have_special_driver[] = {
>         { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE) },
>         { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE2) },
>  #endif
> +#if IS_ENABLED(CONFIG_HID_SWITCHCON)
> +       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_JOYCONL) },
> +       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_JOYCONR) },
> +       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON) },
> +       { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON) },
> +#endif
>  #if IS_ENABLED(CONFIG_HID_NTI)
>         { HID_USB_DEVICE(USB_VENDOR_ID_NTI, USB_DEVICE_ID_USB_SUN) },
>  #endif
> diff --git a/drivers/hid/hid-switchcon.c b/drivers/hid/hid-switchcon.c
> new file mode 100644
> index 000000000000..126f3b39cbfe
> --- /dev/null
> +++ b/drivers/hid/hid-switchcon.c
> @@ -0,0 +1,1156 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * HID driver for Nintendo Switch Joy-Cons and Pro Controllers
> + *
> + * Copyright (c) 2019 Daniel J. Ogorchock <djogorchock@xxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the Free
> + * Software Foundation; either version 2 of the License, or (at your option)
> + * any later version.
> + *
> + * The following resources/projects were referenced for this driver:
> + *   https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
> + *   https://gitlab.com/pjranki/joycon-linux-kernel (Peter Rankin)
> + *   https://github.com/FrotBot/SwitchProConLinuxUSB
> + *   https://github.com/MTCKC/ProconXInput
> + *   hid-wiimote kernel hid driver
> + *
> + * This driver supports the Nintendo Switch Joy-Cons and Pro Controllers. The
> + * Pro Controllers can either be used over USB or Bluetooth. The Joy-Cons can
> + * be used either as a pair or individually in their horizontal mode.
> + *
> + * When a Joy-Con is paired, its LEDs will begin flashing to signal that it is
> + * in its "searching" mode. By holding the triggers of two Joy-Cons at the same
> + * time, they will be combined into one input. To use a Joy-Con alone in its
> + * horizontal mode, hold SL and SR at the same time while it is in the
> + * "searching" mode.
> + *
> + * The driver will retrieve the factory calibration info from the controllers,
> + * so little to no user calibration should be required.
> + *
> + */
> +
> +#include "hid-ids.h"
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/spinlock.h>
> +
> +/* Reference the url below for the following HID report defines:
> + * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
> + */
> +
> +/* Output Reports */
> +#define SC_OUTPUT_RUMBLE_AND_SUBCMD    0x01
> +#define SC_OUTPUT_FW_UPDATE_PKT                0x03
> +#define        SC_OUTPUT_RUMBLE_ONLY           0x10
> +#define SC_OUTPUT_MCU_DATA             0x11
> +#define SC_OUTPUT_USB_CMD              0x80
> +
> +/* Subcommand IDs */
> +#define SC_SUBCMD_STATE                        0x00
> +#define SC_SUBCMD_MANUAL_BT_PAIRING    0x01
> +#define SC_SUBCMD_REQ_DEV_INFO         0x02
> +#define SC_SUBCMD_SET_REPORT_MODE      0x03
> +#define SC_SUBCMD_TRIGGERS_ELAPSED     0x04
> +#define SC_SUBCMD_GET_PAGE_LIST_STATE  0x05
> +#define SC_SUBCMD_SET_HCI_STATE                0x06
> +#define SC_SUBCMD_RESET_PAIRING_INFO   0x07
> +#define SC_SUBCMD_LOW_POWER_MODE       0x08
> +#define SC_SUBCMD_SPI_FLASH_READ       0x10
> +#define SC_SUBCMD_SPI_FLASH_WRITE      0x11
> +#define SC_SUBCMD_RESET_MCU            0x20
> +#define SC_SUBCMD_SET_MCU_CONFIG       0x21
> +#define SC_SUBCMD_SET_MCU_STATE                0x22
> +#define SC_SUBCMD_SET_PLAYER_LIGHTS    0x30
> +#define SC_SUBCMD_GET_PLAYER_LIGHTS    0x31
> +#define SC_SUBCMD_SET_HOME_LIGHT       0x38
> +#define SC_SUBCMD_ENABLE_IMU           0x40
> +#define SC_SUBCMD_SET_IMU_SENSITIVITY  0x41
> +#define SC_SUBCMD_WRITE_IMU_REG                0x42
> +#define SC_SUBCMD_READ_IMU_REG         0x43
> +#define SC_SUBCMD_ENABLE_VIBRATION     0x48
> +#define SC_SUBCMD_GET_REGULATED_VOLTAGE        0x50
> +
> +/* Input Reports */
> +#define SC_INPUT_BUTTON_EVENT          0x3F
> +#define SC_INPUT_SUBCMD_REPLY          0x21
> +#define SC_INPUT_IMU_DATA              0x30
> +#define SC_INPUT_MCU_DATA              0x31
> +#define SC_INPUT_USB_RESPONSE          0x81
> +
> +/* Feature Reports */
> +#define SC_FEATURE_LAST_SUBCMD         0x02
> +#define SC_FEATURE_OTA_FW_UPGRADE      0x70
> +#define SC_FEATURE_SETUP_MEM_READ      0x71
> +#define SC_FEATURE_MEM_READ            0x72
> +#define SC_FEATURE_ERASE_MEM_SECTOR    0x73
> +#define SC_FEATURE_MEM_WRITE           0x74
> +#define SC_FEATURE_LAUNCH              0x75
> +
> +/* USB Commands */
> +#define SC_USB_CMD_CONN_STATUS         0x01
> +#define SC_USB_CMD_HANDSHAKE           0x02
> +#define SC_USB_CMD_BAUDRATE_3M         0x03
> +#define SC_USB_CMD_NO_TIMEOUT          0x04
> +#define SC_USB_CMD_EN_TIMEOUT          0x05
> +#define SC_USB_RESET                   0x06
> +#define SC_USB_PRE_HANDSHAKE           0x91
> +#define SC_USB_SEND_UART               0x92
> +
> +/* SPI storage addresses of factory calibration data */
> +#define        SC_CAL_DATA_START               0x603d
> +#define        SC_CAL_DATA_END                 0x604e
> +#define SC_CAL_DATA_SIZE       (SC_CAL_DATA_END - SC_CAL_DATA_START + 1)
> +
> +
> +/* The raw analog joystick values will be mapped in terms of this magnitude */
> +#define SC_MAX_STICK_MAG       32767
> +#define SC_STICK_FUZZ          250
> +#define SC_STICK_FLAT          500
> +
> +struct switchcon_ctlr;
> +struct switchcon_input;
> +
> +/* States for controller state machine */
> +enum switchcon_ctlr_state {
> +       SWITCHCON_CTLR_STATE_INIT,
> +       SWITCHCON_CTLR_STATE_USB_SET_BAUD,
> +       SWITCHCON_CTLR_STATE_USB_HANDSHAKE,
> +       SWITCHCON_CTLR_STATE_CALIBRATION,
> +       SWITCHCON_CTLR_STATE_POST_CALIBRATION,
> +       SWITCHCON_CTLR_STATE_SEARCH,
> +       SWITCHCON_CTLR_STATE_READ,
> +};
> +
> +/* Function pointers will differ based on controller type */
> +struct switchcon_impl {
> +       int (*init)(struct switchcon_ctlr *ctlr);
> +       void (*deinit)(struct switchcon_ctlr *ctlr);
> +       int (*handle_event)(struct switchcon_ctlr *ctlr, u8 *data, int size);
> +};
> +
> +struct switchcon_output {
> +       u8      *data;
> +       u8      size;
> +};
> +
> +struct switchcon_stick_cal {
> +       s32     x_max;
> +       s32     x_min;
> +       s32     x_center;
> +       s32     y_max;
> +       s32     y_min;
> +       s32     y_center;
> +};
> +
> +#define SC_OUTPUT_BUF_SIZE     50
> +/* Each physical controller is associated with a switchcon_ctlr struct */
> +struct switchcon_ctlr {
> +       struct hid_device               *hdev;
> +       struct switchcon_input          *switchcon_in;
> +       const struct switchcon_impl     *impl;
> +       bool                            is_right_joycon;
> +       bool                            searching;
> +       enum switchcon_ctlr_state       ctlr_state;
> +       u8                              subcmd_num;
> +       struct switchcon_output         output_buf[SC_OUTPUT_BUF_SIZE];
> +       u8                              output_head;
> +       u8                              output_tail;
> +       spinlock_t                      output_lock;
> +       struct work_struct              output_worker;
> +       struct work_struct              create_input_worker;
> +       struct work_struct              detect_pair_worker;
> +       struct work_struct              create_horiz_worker;
> +       struct mutex                    mutex;
> +
> +       /* factory calibration data */
> +       struct switchcon_stick_cal      left_stick_cal;
> +       struct switchcon_stick_cal      right_stick_cal;
> +
> +       /* buttons/sticks */
> +       u16     left_stick_x;
> +       u16     left_stick_y;
> +       u16     right_stick_x;
> +       u16     right_stick_y;
> +       bool    but_y;
> +       bool    but_x;
> +       bool    but_b;
> +       bool    but_a;
> +       bool    but_sr_right_jc;
> +       bool    but_sl_right_jc;
> +       bool    but_r;
> +       bool    but_zr;
> +       bool    but_l;
> +       bool    but_zl;
> +       bool    but_minus;
> +       bool    but_plus;
> +       bool    but_rstick;
> +       bool    but_lstick;
> +       bool    but_home;
> +       bool    but_capture;
> +       bool    but_down;
> +       bool    but_up;
> +       bool    but_right;
> +       bool    but_left;
> +       bool    but_sr_left_jc;
> +       bool    but_sl_left_jc;
> +
> +};
> +
> +enum switchcon_input_type {
> +       SWITCHCON_INPUT_TYPE_PROCON,
> +       SWITCHCON_INPUT_TYPE_JOYCONS,
> +       SWITCHCON_INPUT_TYPE_JOYCON_H,
> +};
> +
> +static const char * const switchcon_input_names[] = {
> +       "Nintendo Switch Pro Controller",
> +       "Nintendo Switch Joy-Cons",
> +       "Nintendo Switch Joy-Con Horizontal",
> +};
> +
> +/* Each linux input device is associated with a switchcon_input */
> +struct switchcon_input {
> +       enum switchcon_input_type       type;
> +       struct input_dev        *input;
> +       struct switchcon_ctlr   *ctlr_left; /* left joycon or pro controller */
> +       struct switchcon_ctlr   *ctlr_right; /* only used for joycons */
> +       struct mutex            mutex;
> +       struct work_struct      input_worker;
> +};
> +
> +static int switchcon_ctlr_init(struct switchcon_ctlr *ctlr)
> +{
> +       if (ctlr->impl && ctlr->impl->init)
> +               return ctlr->impl->init(ctlr);
> +       else
> +               return -EINVAL;
> +}
> +
> +static void switchcon_ctlr_deinit(struct switchcon_ctlr *ctlr)
> +{
> +       if (ctlr->impl && ctlr->impl->deinit)
> +               ctlr->impl->deinit(ctlr);
> +}
> +
> +static int switchcon_ctlr_handle_event(struct switchcon_ctlr *ctlr, u8 *data,
> +                                                                   int size)
> +{
> +       if (ctlr->impl && ctlr->impl->handle_event)
> +               return ctlr->impl->handle_event(ctlr, data, size);
> +       else
> +               return -EINVAL;
> +}
> +
> +static int switchcon_hid_queue_send(struct switchcon_ctlr *ctlr,
> +                                   const u8 *buffer, size_t count)
> +{
> +       struct switchcon_output output;
> +
> +       output.size = count;
> +       output.data = kmemdup(buffer, count, GFP_ATOMIC);
> +       if (!output.data)
> +               return -ENOMEM;
> +
> +       spin_lock(&ctlr->output_lock);
> +
> +       ctlr->output_buf[ctlr->output_head++] = output;
> +       if (ctlr->output_head == SC_OUTPUT_BUF_SIZE)
> +               ctlr->output_head = 0;
> +       schedule_work(&ctlr->output_worker);
> +
> +       spin_unlock(&ctlr->output_lock);
> +       return 0;
> +}
> +
> +static int switchcon_queue_subcommand(struct switchcon_ctlr *ctlr,
> +                                     const u8 *buffer, size_t count)
> +{
> +       /* header including packet number and empty rumble data */
> +       u8 header[10] = {SC_OUTPUT_RUMBLE_AND_SUBCMD, ctlr->subcmd_num};
> +       struct switchcon_output output;
> +       u8 *buf;
> +
> +       /* the packet number loops after 0xF */
> +       ctlr->subcmd_num++;
> +       if (ctlr->subcmd_num > 0xF)
> +               ctlr->subcmd_num = 0;
> +
> +       buf = kzalloc(sizeof(header) + count, GFP_KERNEL);
> +       if (!buf)
> +               return -ENOMEM;
> +
> +       memcpy(buf, header, sizeof(header));
> +       memcpy(buf + sizeof(header), buffer, count);
> +       output.data = buf;
> +       output.size = count + sizeof(header);
> +
> +       spin_lock(&ctlr->output_lock);
> +
> +       ctlr->output_buf[ctlr->output_head++] = output;
> +       if (ctlr->output_head == SC_OUTPUT_BUF_SIZE)
> +               ctlr->output_head = 0;
> +       schedule_work(&ctlr->output_worker);
> +
> +       spin_unlock(&ctlr->output_lock);
> +       return 0;
> +}
> +
> +static void switchcon_set_player_leds(struct switchcon_ctlr *ctlr,
> +                                     u8 flash, u8 on)
> +{
> +       u8 buffer[] = {SC_SUBCMD_SET_PLAYER_LIGHTS, (flash << 4) | on};
> +
> +       switchcon_queue_subcommand(ctlr, buffer, sizeof(buffer));
> +}
> +
> +static void switchcon_request_calibration(struct switchcon_ctlr *ctlr)
> +{
> +       u8 cal_subcmd[]         = {SC_SUBCMD_SPI_FLASH_READ,
> +                                  0xFF & SC_CAL_DATA_START,
> +                                  0xFF & (SC_CAL_DATA_START >> 8),
> +                                  0, 0, SC_CAL_DATA_SIZE};
> +
> +       switchcon_queue_subcommand(ctlr, cal_subcmd, sizeof(cal_subcmd));
> +       hid_dbg(ctlr->hdev, "requested cal data\n");
> +}
> +
> +static void switchcon_request_report_mode(struct switchcon_ctlr *ctlr)
> +{
> +       u8 inputmode_subcmd[]   = {SC_SUBCMD_SET_REPORT_MODE, 0x30};
> +
> +       switchcon_queue_subcommand(ctlr, inputmode_subcmd,
> +                                  sizeof(inputmode_subcmd));
> +       hid_dbg(ctlr->hdev, "requested 0x30 report mode\n");
> +}
> +
> +static int switchcon_map_x_stick_val(struct switchcon_stick_cal *cal, s32 val)
> +{
> +       s32 center      = cal->x_center;
> +       s32 min         = cal->x_min;
> +       s32 max         = cal->x_max;
> +       s64 newVal;
> +
> +       if (val > center) {
> +               newVal = (val - center) * SC_MAX_STICK_MAG;
> +               do_div(newVal, max - center);
> +       } else {
> +               newVal = (center - val) * SC_MAX_STICK_MAG;
> +               do_div(newVal, center - min);
> +               newVal *= -1;
> +       }
> +       if (newVal > SC_MAX_STICK_MAG)
> +               newVal = SC_MAX_STICK_MAG;
> +       if (newVal < -SC_MAX_STICK_MAG)
> +               newVal = -SC_MAX_STICK_MAG;
> +       return (int)newVal;
> +}
> +
> +static int switchcon_map_y_stick_val(struct switchcon_stick_cal *cal, s32 val)
> +{
> +       s32 center      = cal->y_center;
> +       s32 min         = cal->y_min;
> +       s32 max         = cal->y_max;
> +       s64 newVal;
> +
> +       if (val > center) {
> +               newVal = (val - center) * SC_MAX_STICK_MAG;
> +               do_div(newVal, max - center);
> +       } else {
> +               newVal = (center - val) * SC_MAX_STICK_MAG;
> +               do_div(newVal, center - min);
> +               newVal *= -1;
> +       }
> +       if (newVal > SC_MAX_STICK_MAG)
> +               newVal = SC_MAX_STICK_MAG;
> +       if (newVal < -SC_MAX_STICK_MAG)
> +               newVal = -SC_MAX_STICK_MAG;
> +       return (int)newVal;
> +}
> +
> +static void switchcon_update_input_handler(struct work_struct *ws)
> +{
> +       struct switchcon_input *sc_input = container_of(ws,
> +                                       struct switchcon_input, input_worker);
> +       struct input_dev *dev = sc_input->input;
> +       struct switchcon_ctlr *ctlr_l = sc_input->ctlr_left;
> +       struct switchcon_ctlr *ctlr_r = sc_input->ctlr_right;
> +       int s_l_x;
> +       int s_l_y;
> +       int s_r_x;
> +       int s_r_y;
> +
> +       if (!ctlr_r)
> +               ctlr_r = ctlr_l;
> +
> +       mutex_lock(&sc_input->mutex);
> +       mutex_lock(&ctlr_l->mutex);
> +       if (sc_input->ctlr_right)
> +               mutex_lock(&sc_input->ctlr_right->mutex);
> +
> +       /* map the stick values */
> +       s_l_x = switchcon_map_x_stick_val(&ctlr_l->left_stick_cal,
> +                                         ctlr_l->left_stick_x);
> +       s_l_y = switchcon_map_y_stick_val(&ctlr_l->left_stick_cal,
> +                                         ctlr_l->left_stick_y);
> +       s_r_x = switchcon_map_x_stick_val(&ctlr_r->right_stick_cal,
> +                                         ctlr_r->right_stick_x);
> +       s_r_y = switchcon_map_y_stick_val(&ctlr_r->right_stick_cal,
> +                                         ctlr_r->right_stick_y);
> +
> +       if (sc_input->type == SWITCHCON_INPUT_TYPE_JOYCON_H) {
> +               if (ctlr_l->is_right_joycon) {
> +                       /* report stick */
> +                       input_report_abs(dev, ABS_X, s_r_y);
> +                       input_report_abs(dev, ABS_Y, -s_r_x);
> +
> +                       /* report buttons */
> +                       input_report_key(dev, BTN_WEST,   ctlr_r->but_b);
> +                       input_report_key(dev, BTN_NORTH,  ctlr_r->but_y);
> +                       input_report_key(dev, BTN_EAST,   ctlr_r->but_x);
> +                       input_report_key(dev, BTN_SOUTH,  ctlr_r->but_a);
> +                       input_report_key(dev, BTN_THUMBL, ctlr_r->but_rstick);
> +                       input_report_key(dev, BTN_START,  ctlr_r->but_plus);
> +                       input_report_key(dev, BTN_MODE,   ctlr_r->but_home);
> +                       input_report_key(dev, BTN_TR,
> +                                        ctlr_r->but_sr_right_jc);
> +                       input_report_key(dev, BTN_TL,
> +                                        ctlr_r->but_sl_right_jc);
> +               } else {
> +                       /* report stick */
> +                       input_report_abs(dev, ABS_X, -s_l_y);
> +                       input_report_abs(dev, ABS_Y, s_l_x);
> +
> +                       /* report buttons */
> +                       input_report_key(dev, BTN_WEST,   ctlr_l->but_up);
> +                       input_report_key(dev, BTN_NORTH,  ctlr_l->but_right);
> +                       input_report_key(dev, BTN_EAST,   ctlr_l->but_down);
> +                       input_report_key(dev, BTN_SOUTH,  ctlr_l->but_left);
> +                       input_report_key(dev, BTN_THUMBL, ctlr_l->but_lstick);
> +                       input_report_key(dev, BTN_START,  ctlr_l->but_minus);
> +                       input_report_key(dev, BTN_MODE,   ctlr_l->but_capture);
> +                       input_report_key(dev, BTN_TR,
> +                                        ctlr_l->but_sr_left_jc);
> +                       input_report_key(dev, BTN_TL,
> +                                        ctlr_l->but_sl_left_jc);
> +               }
> +       } else {
> +               /* report sticks */
> +               input_report_abs(dev, ABS_X, s_l_x);
> +               input_report_abs(dev, ABS_Y, s_l_y);
> +               input_report_abs(dev, ABS_RX, s_r_x);
> +               input_report_abs(dev, ABS_RY, s_r_y);
> +
> +               /* report buttons */
> +               input_report_key(dev, BTN_WEST,         ctlr_r->but_y);
> +               input_report_key(dev, BTN_NORTH,        ctlr_r->but_x);
> +               input_report_key(dev, BTN_EAST,         ctlr_r->but_a);
> +               input_report_key(dev, BTN_SOUTH,        ctlr_r->but_b);
> +               input_report_key(dev, BTN_TR,           ctlr_r->but_r);
> +               input_report_key(dev, BTN_TR2,          ctlr_r->but_zr);
> +               input_report_key(dev, BTN_TL,           ctlr_l->but_l);
> +               input_report_key(dev, BTN_TL2,          ctlr_l->but_zl);
> +               input_report_key(dev, BTN_SELECT,       ctlr_l->but_minus);
> +               input_report_key(dev, BTN_START,        ctlr_r->but_plus);
> +               input_report_key(dev, BTN_THUMBL,       ctlr_l->but_lstick);
> +               input_report_key(dev, BTN_THUMBR,       ctlr_r->but_rstick);
> +               input_report_key(dev, BTN_MODE,         ctlr_r->but_home);
> +               input_report_key(dev, BTN_Z,            ctlr_l->but_capture);
> +               input_report_key(dev, BTN_DPAD_DOWN,    ctlr_l->but_down);
> +               input_report_key(dev, BTN_DPAD_UP,      ctlr_l->but_up);
> +               input_report_key(dev, BTN_DPAD_RIGHT,   ctlr_l->but_right);
> +               input_report_key(dev, BTN_DPAD_LEFT,    ctlr_l->but_left);
> +       }
> +
> +       input_sync(dev);
> +
> +       mutex_unlock(&ctlr_l->mutex);
> +       if (sc_input->ctlr_right)
> +               mutex_unlock(&sc_input->ctlr_right->mutex);
> +       mutex_unlock(&sc_input->mutex);
> +}
> +
> +static void switchcon_input_destroy(struct switchcon_input *sc_input)
> +{
> +       hid_dbg(sc_input->ctlr_left->hdev, "destroying input\n");
> +       mutex_lock(&sc_input->mutex);
> +
> +       sc_input->ctlr_left->switchcon_in = NULL;
> +       mutex_lock(&sc_input->ctlr_left->mutex);
> +       if (sc_input->ctlr_right) {
> +               mutex_lock(&sc_input->ctlr_right->mutex);
> +               sc_input->ctlr_right->switchcon_in = NULL;
> +       }
> +       /* set the joycon states to searching */
> +       sc_input->ctlr_left->ctlr_state = SWITCHCON_CTLR_STATE_POST_CALIBRATION;
> +       if (sc_input->ctlr_right)
> +               sc_input->ctlr_right->ctlr_state =
> +                                         SWITCHCON_CTLR_STATE_POST_CALIBRATION;
> +
> +       input_unregister_device(sc_input->input);
> +
> +       mutex_unlock(&sc_input->ctlr_left->mutex);
> +       if (sc_input->ctlr_right)
> +               mutex_unlock(&sc_input->ctlr_right->mutex);
> +       mutex_unlock(&sc_input->mutex);
> +       mutex_destroy(&sc_input->mutex);
> +       kfree(sc_input);
> +}
> +
> +static const unsigned int switchcon_button_inputs[] = {
> +       BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST,
> +       BTN_TL, BTN_TR, BTN_START, BTN_MODE, BTN_THUMBL,
> +       /* the following buttons do not apply to a single, horizontal joycon */
> +       BTN_TL2, BTN_TR2, BTN_SELECT, BTN_Z, BTN_THUMBR,
> +       BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
> +       0 /* 0 signals end of array */
> +};
> +
> +DEFINE_MUTEX(switchcon_input_num_mutex);
> +static struct switchcon_input *switchcon_input_create(
> +                                       enum switchcon_input_type type,
> +                                       struct switchcon_ctlr *ctlr_l,
> +                                       struct switchcon_ctlr *ctlr_r)
> +{
> +       struct switchcon_input *sc_input;
> +       struct hid_device *hdev;
> +       static int input_num = 1;
> +       int i;
> +       int ret;
> +
> +       if (!ctlr_l)
> +               return ERR_PTR(-EINVAL);
> +       hdev = ctlr_l->hdev;
> +       sc_input = kzalloc(sizeof(*sc_input), GFP_KERNEL);
> +       if (!sc_input)
> +               return ERR_PTR(-ENOMEM);
> +
> +       sc_input->type          = type;
> +       sc_input->ctlr_left     = ctlr_l;
> +       sc_input->ctlr_right    = ctlr_r;
> +       if (!ctlr_r)
> +               ctlr_r = ctlr_l;
> +
> +       sc_input->input = input_allocate_device();
> +       if (!sc_input->input) {
> +               ret = -ENOMEM;
> +               goto err;
> +       }
> +       sc_input->input->dev.parent     = &hdev->dev;
> +       sc_input->input->id.bustype     = hdev->bus;
> +       sc_input->input->id.vendor      = hdev->vendor;
> +       sc_input->input->id.product     = hdev->product;
> +       sc_input->input->id.version     = hdev->version;
> +       sc_input->input->name = switchcon_input_names[sc_input->type];
> +       input_set_drvdata(sc_input->input, sc_input);
> +
> +       /* set up button inputs */
> +       for (i = 0; switchcon_button_inputs[i] > 0; i++) {
> +               input_set_capability(sc_input->input, EV_KEY,
> +                                    switchcon_button_inputs[i]);
> +               if (switchcon_button_inputs[i] == BTN_THUMBL
> +                && sc_input->type == SWITCHCON_INPUT_TYPE_JOYCON_H)
> +                       break;
> +       }
> +
> +       /* set up sticks */
> +       input_set_abs_params(sc_input->input, ABS_X,
> +                            -SC_MAX_STICK_MAG, SC_MAX_STICK_MAG,
> +                            SC_STICK_FUZZ, SC_STICK_FLAT);
> +       input_set_abs_params(sc_input->input, ABS_Y,
> +                            -SC_MAX_STICK_MAG, SC_MAX_STICK_MAG,
> +                            SC_STICK_FUZZ, SC_STICK_FLAT);
> +       /* set up right stick if necessary for controller type */
> +       if (sc_input->type != SWITCHCON_INPUT_TYPE_JOYCON_H) {
> +               input_set_abs_params(sc_input->input, ABS_RX,
> +                                    -SC_MAX_STICK_MAG, SC_MAX_STICK_MAG,
> +                                    SC_STICK_FUZZ, SC_STICK_FLAT);
> +               input_set_abs_params(sc_input->input, ABS_RY,
> +                                    -SC_MAX_STICK_MAG, SC_MAX_STICK_MAG,
> +                                    SC_STICK_FUZZ, SC_STICK_FLAT);
> +       }
> +
> +       ret = input_register_device(sc_input->input);
> +       if (ret)
> +               goto err_input;
> +
> +       mutex_init(&sc_input->mutex);
> +       INIT_WORK(&sc_input->input_worker, switchcon_update_input_handler);
> +
> +       /* Set the controller player leds based on input number */
> +       mutex_lock(&switchcon_input_num_mutex);
> +       /* Need to send multiple times to make sure it's not ignored */
> +       for (i = 0; i < 5; i++) {
> +               switchcon_set_player_leds(ctlr_l, 0, 0xF >> (4 - input_num));
> +               switchcon_set_player_leds(ctlr_r, 0, 0xF >> (4 - input_num));
> +       }
> +       if (++input_num > 4)
> +               input_num = 1;
> +       mutex_unlock(&switchcon_input_num_mutex);
> +
> +       return sc_input;
> +err_input:
> +       input_free_device(sc_input->input);
> +err:
> +       kfree(sc_input);
> +       return ERR_PTR(ret);
> +}
> +
> +static void switchcon_create_procon_input_handler(struct work_struct *ws)
> +{
> +       struct switchcon_ctlr *ctlr = container_of(ws, struct switchcon_ctlr,
> +                                                       create_input_worker);
> +       hid_dbg(ctlr->hdev, "create_procon_input_handler\n");
> +
> +       mutex_lock(&ctlr->mutex);
> +
> +       ctlr->switchcon_in = switchcon_input_create(SWITCHCON_INPUT_TYPE_PROCON,
> +                                                                   ctlr, NULL);
> +       if (IS_ERR(ctlr->switchcon_in)) {
> +               hid_err(ctlr->hdev, "failed to create input\n");
> +               ctlr->switchcon_in = NULL;
> +       }
> +
> +       mutex_unlock(&ctlr->mutex);
> +}
> +
> +DEFINE_MUTEX(switchcon_detect_pair_mutex);
> +static void switchcon_detect_pair_handler(struct work_struct *ws)
> +{
> +       struct switchcon_ctlr *ctlr = container_of(ws, struct switchcon_ctlr,
> +                                                       detect_pair_worker);
> +       static struct switchcon_ctlr *ctlr_l;
> +       static struct switchcon_ctlr *ctlr_r;
> +
> +       mutex_lock(&switchcon_detect_pair_mutex);
> +       mutex_lock(&ctlr->mutex);
> +       /* Check if this is a controller no longer searching for partner */
> +       if (!ctlr->searching) {
> +               if (ctlr == ctlr_l)
> +                       ctlr_l = NULL;
> +               if (ctlr == ctlr_r)
> +                       ctlr_r = NULL;
> +       } else {
> +               if (!ctlr->is_right_joycon && ctlr_l == NULL)
> +                       ctlr_l = ctlr;
> +               if (ctlr->is_right_joycon && ctlr_r == NULL)
> +                       ctlr_r = ctlr;
> +
> +               /* see if there's a pair */
> +               if (ctlr_l && ctlr_r) {
> +                       if (ctlr == ctlr_l)
> +                               mutex_lock(&ctlr_r->mutex);
> +                       else
> +                               mutex_lock(&ctlr_l->mutex);
> +                       hid_info(ctlr->hdev, "Joy-Con pair found\n");
> +
> +                       ctlr_l->switchcon_in = switchcon_input_create(
> +                                                  SWITCHCON_INPUT_TYPE_JOYCONS,
> +                                                  ctlr_l, ctlr_r);
> +                       ctlr_r->switchcon_in = ctlr_l->switchcon_in;
> +                       ctlr_l->ctlr_state = SWITCHCON_CTLR_STATE_READ;
> +                       ctlr_r->ctlr_state = SWITCHCON_CTLR_STATE_READ;
> +
> +                       if (IS_ERR(ctlr_l->switchcon_in)) {
> +                               hid_err(ctlr->hdev, "Failed creating input\n");
> +                               ctlr_l->switchcon_in = NULL;
> +                               ctlr_r->switchcon_in = NULL;
> +                       }
> +
> +                       if (ctlr == ctlr_l)
> +                               mutex_unlock(&ctlr_r->mutex);
> +                       else
> +                               mutex_unlock(&ctlr_l->mutex);
> +                       ctlr_l = NULL;
> +                       ctlr_r = NULL;
> +               }
> +       }
> +
> +       mutex_unlock(&ctlr->mutex);
> +       mutex_unlock(&switchcon_detect_pair_mutex);
> +}
> +
> +static void switchcon_create_horiz_handler(struct work_struct *ws)
> +{
> +       struct switchcon_ctlr *ctlr = container_of(ws, struct switchcon_ctlr,
> +                                                       create_horiz_worker);
> +
> +       mutex_lock(&ctlr->mutex);
> +
> +       ctlr->switchcon_in = switchcon_input_create(
> +                               SWITCHCON_INPUT_TYPE_JOYCON_H, ctlr, NULL);
> +       if (IS_ERR(ctlr->switchcon_in)) {
> +               hid_err(ctlr->hdev, "failed to create input\n");
> +               ctlr->switchcon_in = NULL;
> +       }
> +
> +       mutex_unlock(&ctlr->mutex);
> +}
> +
> +static void switchcon_send_work_handler(struct work_struct *ws)
> +{
> +       struct switchcon_ctlr *ctlr = container_of(ws, struct switchcon_ctlr,
> +                                                               output_worker);
> +       struct switchcon_output output;
> +       struct hid_device *hdev = ctlr->hdev;
> +       int ret;
> +
> +       spin_lock(&ctlr->output_lock);
> +       while (ctlr->output_tail != ctlr->output_head) {
> +               output = ctlr->output_buf[ctlr->output_tail];
> +               ctlr->output_buf[ctlr->output_tail].data = NULL;
> +               ctlr->output_tail++;
> +               if (ctlr->output_tail == SC_OUTPUT_BUF_SIZE)
> +                       ctlr->output_tail = 0;
> +               spin_unlock(&ctlr->output_lock);
> +               if (!output.data) {
> +                       hid_warn(ctlr->hdev, "invalid output in buffer\n");
> +               } else {
> +                       ret = hid_hw_output_report(hdev, output.data,
> +                                                        output.size);
> +                       if (ret < 0)
> +                               hid_warn(hdev, "failed output report ret=%d",
> +                                                                       ret);
> +                       kfree(output.data);
> +
> +               }
> +               spin_lock(&ctlr->output_lock);
> +       }
> +       spin_unlock(&ctlr->output_lock);
> +}
> +
> +static int switchcon_hid_event(struct hid_device *hdev,
> +                       struct hid_report *report, u8 *raw_data, int size)
> +{
> +       struct switchcon_ctlr *ctlr = hid_get_drvdata(hdev);
> +
> +       if (size < 1)
> +               return -EINVAL;
> +
> +       return switchcon_ctlr_handle_event(ctlr, raw_data, size);
> +}
> +
> +/* data input must have at least 9 bytes */
> +static void switchcon_parse_lstick_calibration(u8 *data,
> +                                              struct switchcon_stick_cal *cal)
> +{
> +       s32 x_max_above;
> +       s32 x_min_below;
> +       s32 y_max_above;
> +       s32 y_min_below;
> +
> +       x_max_above     = ((data[1] << 8) & 0xF00) | data[0];
> +       y_max_above     = (data[2] << 4) | (data[1] >> 4);
> +       x_min_below     = ((data[7] << 8) & 0xF00) | data[6];
> +       y_min_below     = (data[8] << 4) | (data[7] >> 4);
> +       cal->x_center   = ((data[4] << 8) & 0xF00) | data[3];
> +       cal->y_center   = (data[5] << 4) | (data[4] >> 4);
> +       cal->x_max      = cal->x_center + x_max_above;
> +       cal->x_min      = cal->x_center - x_min_below;
> +       cal->y_max      = cal->y_center + y_max_above;
> +       cal->y_min      = cal->y_center - y_min_below;
> +}
> +
> +/* data input must have at least 9 bytes */
> +static void switchcon_parse_rstick_calibration(u8 *data,
> +                                              struct switchcon_stick_cal *cal)
> +{
> +       s32 x_max_above;
> +       s32 x_min_below;
> +       s32 y_max_above;
> +       s32 y_min_below;
> +
> +       cal->x_center   = ((data[1] << 8) & 0xF00) | data[0];
> +       cal->y_center   = (data[2] << 4) | (data[1] >> 4);
> +       x_max_above     = ((data[7] << 8) & 0xF00) | data[6];
> +       y_max_above     = (data[8] << 4) | (data[7] >> 4);
> +       x_min_below     = ((data[4] << 8) & 0xF00) | data[3];
> +       y_min_below     = (data[5] << 4) | (data[4] >> 4);
> +       cal->x_max      = cal->x_center + x_max_above;
> +       cal->x_min      = cal->x_center - x_min_below;
> +       cal->y_max      = cal->y_center + y_max_above;
> +       cal->y_min      = cal->y_center - y_min_below;
> +}
> +
> +/* Common handler for parsing inputs */
> +static int switchcon_ctlr_read_handler(struct switchcon_ctlr *ctlr,
> +                                               u8 *data, int size)
> +{
> +       int ret = 0;
> +
> +       switch (data[0]) {
> +       case SC_INPUT_SUBCMD_REPLY:
> +       case SC_INPUT_IMU_DATA:
> +       case SC_INPUT_MCU_DATA:
> +               if (size < 12) /* make sure it contains the input report */
> +                       break;
> +
> +               /* Parse analog sticks */
> +               ctlr->left_stick_x      = data[6] | ((data[7] & 0xF) << 8);
> +               ctlr->left_stick_y      = (data[7] >> 4) | (data[8] << 4);
> +               ctlr->right_stick_x     = data[9] | ((data[10] & 0xF) << 8);
> +               ctlr->right_stick_y     = (data[10] >> 4) | (data[11] << 4);
> +
> +               /* Parse digital buttons */
> +               ctlr->but_y             = 0x01 & data[3];
> +               ctlr->but_x             = 0x02 & data[3];
> +               ctlr->but_b             = 0x04 & data[3];
> +               ctlr->but_a             = 0x08 & data[3];
> +               ctlr->but_sr_right_jc   = 0x10 & data[3];
> +               ctlr->but_sl_right_jc   = 0x20 & data[3];
> +               ctlr->but_r             = 0x40 & data[3];
> +               ctlr->but_zr            = 0x80 & data[3];
> +               ctlr->but_l             = 0x40 & data[5];
> +               ctlr->but_zl            = 0x80 & data[5];
> +               ctlr->but_minus         = 0x01 & data[4];
> +               ctlr->but_plus          = 0x02 & data[4];
> +               ctlr->but_rstick        = 0x04 & data[4];
> +               ctlr->but_lstick        = 0x08 & data[4];
> +               ctlr->but_home          = 0x10 & data[4];
> +               ctlr->but_capture       = 0x20 & data[4];
> +               ctlr->but_down          = 0x01 & data[5];
> +               ctlr->but_up            = 0x02 & data[5];
> +               ctlr->but_right         = 0x04 & data[5];
> +               ctlr->but_left          = 0x08 & data[5];
> +               ctlr->but_sr_left_jc    = 0x10 & data[5];
> +               ctlr->but_sl_left_jc    = 0x20 & data[5];
> +
> +               break;
> +       case SC_INPUT_BUTTON_EVENT:
> +               /* the controller is in wrong request mode */
> +               switchcon_request_report_mode(ctlr);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +               break;
> +       }
> +
> +       return ret;
> +}
> +
> +static int switchcon_ctlr_procon_init(struct switchcon_ctlr *ctlr)
> +{
> +       int ret;
> +       u8 baud_cmd[] = {SC_OUTPUT_USB_CMD, SC_USB_CMD_BAUDRATE_3M};
> +
> +       mutex_lock(&ctlr->mutex);
> +       ctlr->ctlr_state = SWITCHCON_CTLR_STATE_USB_SET_BAUD;
> +       ret = switchcon_hid_queue_send(ctlr, baud_cmd, sizeof(baud_cmd));
> +       mutex_unlock(&ctlr->mutex);
> +       return ret;
> +}
> +
> +static int switchcon_ctlr_joyconl_init(struct switchcon_ctlr *ctlr)
> +{
> +       mutex_lock(&ctlr->mutex);
> +       switchcon_request_calibration(ctlr);
> +       ctlr->ctlr_state = SWITCHCON_CTLR_STATE_CALIBRATION;
> +       ctlr->is_right_joycon = false;
> +       mutex_unlock(&ctlr->mutex);
> +       return 0;
> +}
> +
> +static int switchcon_ctlr_joyconr_init(struct switchcon_ctlr *ctlr)
> +{
> +       mutex_lock(&ctlr->mutex);
> +       switchcon_request_calibration(ctlr);
> +       ctlr->ctlr_state = SWITCHCON_CTLR_STATE_CALIBRATION;
> +       ctlr->is_right_joycon = true;
> +       switchcon_request_calibration(ctlr);
> +       mutex_unlock(&ctlr->mutex);
> +       return 0;
> +}
> +
> +void switchcon_ctlr_general_deinit(struct switchcon_ctlr *ctlr)
> +{
> +       if (ctlr->switchcon_in)
> +               switchcon_input_destroy(ctlr->switchcon_in);
> +       mutex_lock(&ctlr->mutex);
> +       flush_work(&ctlr->output_worker);
> +       mutex_unlock(&ctlr->mutex);
> +       mutex_destroy(&ctlr->mutex);
> +}
> +
> +static int switchcon_ctlr_general_handle_event(struct switchcon_ctlr *ctlr,
> +                                                       u8 *data, int size)
> +{
> +       mutex_lock(&ctlr->mutex);
> +       switch (ctlr->ctlr_state) {
> +       case SWITCHCON_CTLR_STATE_CALIBRATION:
> +               if (data[0] != SC_INPUT_SUBCMD_REPLY || size < 38
> +                || data[13] != 0x90 || data[14] != 0x10) {
> +                       /* resend request if it wasn't received */
> +                       switchcon_request_calibration(ctlr);
> +                       break;
> +               }
> +               switchcon_parse_lstick_calibration(data + 20,
> +                                                 &ctlr->left_stick_cal);
> +               switchcon_parse_rstick_calibration(data + 29,
> +                                                 &ctlr->right_stick_cal);
> +
> +               hid_info(ctlr->hdev, "l_x_c=%d l_x_max=%d l_x_min=%d\n",
> +                                               ctlr->left_stick_cal.x_center,
> +                                               ctlr->left_stick_cal.x_max,
> +                                               ctlr->left_stick_cal.x_min);
> +               hid_info(ctlr->hdev, "l_y_c=%d l_y_max=%d l_y_min=%d\n",
> +                                               ctlr->left_stick_cal.y_center,
> +                                               ctlr->left_stick_cal.y_max,
> +                                               ctlr->left_stick_cal.y_min);
> +               hid_info(ctlr->hdev, "r_x_c=%d r_x_max=%d r_x_min=%d\n",
> +                                               ctlr->right_stick_cal.x_center,
> +                                               ctlr->right_stick_cal.x_max,
> +                                               ctlr->right_stick_cal.x_min);
> +               hid_info(ctlr->hdev, "r_y_c=%d r_y_max=%d r_y_min=%d\n",
> +                                               ctlr->right_stick_cal.y_center,
> +                                               ctlr->right_stick_cal.y_max,
> +                                               ctlr->right_stick_cal.y_min);
> +               ctlr->ctlr_state = SWITCHCON_CTLR_STATE_POST_CALIBRATION;
> +               switchcon_request_report_mode(ctlr);
> +               break;
> +       case SWITCHCON_CTLR_STATE_READ:
> +               switchcon_ctlr_read_handler(ctlr, data, size);
> +               if (ctlr->switchcon_in)
> +                       schedule_work(&ctlr->switchcon_in->input_worker);
> +               break;
> +       default:
> +               break;
> +       }
> +       mutex_unlock(&ctlr->mutex);
> +
> +       return 0;
> +}
> +
> +static int switchcon_ctlr_procon_handle_event(struct switchcon_ctlr *ctlr,
> +                                                       u8 *data, int size)
> +{
> +       int ret;
> +       u8 handshake_cmd[]      = {SC_OUTPUT_USB_CMD, SC_USB_CMD_HANDSHAKE};
> +       u8 timeout_cmd[]        = {SC_OUTPUT_USB_CMD, SC_USB_CMD_NO_TIMEOUT};
> +
> +       mutex_lock(&ctlr->mutex);
> +       switch (ctlr->ctlr_state) {
> +       case SWITCHCON_CTLR_STATE_USB_SET_BAUD:
> +               if (size < 2 || data[0] != SC_INPUT_USB_RESPONSE
> +                   || data[1] != SC_USB_CMD_BAUDRATE_3M) {
> +                       hid_dbg(ctlr->hdev, "No usb response, assume ble\n");
> +                       ctlr->ctlr_state = SWITCHCON_CTLR_STATE_CALIBRATION;
> +                       switchcon_request_calibration(ctlr);
> +                       break;
> +               }
> +
> +               ret = switchcon_hid_queue_send(ctlr, handshake_cmd,
> +                                               sizeof(handshake_cmd));
> +               if (!ret) {
> +                       ctlr->ctlr_state = SWITCHCON_CTLR_STATE_USB_HANDSHAKE;
> +                       hid_dbg(ctlr->hdev, "sent handshake\n");
> +               }
> +               break;
> +       case SWITCHCON_CTLR_STATE_USB_HANDSHAKE:
> +               if (size < 2 || data[0] != SC_INPUT_USB_RESPONSE
> +                   || data[1] != SC_USB_CMD_HANDSHAKE)
> +                       break;
> +               ret = switchcon_hid_queue_send(ctlr, timeout_cmd,
> +                                               sizeof(timeout_cmd));
> +               if (!ret) {
> +                       hid_dbg(ctlr->hdev, "sent timeout disable\n");
> +                       ctlr->ctlr_state = SWITCHCON_CTLR_STATE_CALIBRATION;
> +                       switchcon_request_calibration(ctlr);
> +               }
> +               break;
> +       case SWITCHCON_CTLR_STATE_POST_CALIBRATION:
> +               schedule_work(&ctlr->create_input_worker);
> +               ctlr->ctlr_state = SWITCHCON_CTLR_STATE_READ;
> +               break;
> +       default:
> +               mutex_unlock(&ctlr->mutex);
> +               return switchcon_ctlr_general_handle_event(ctlr, data, size);
> +       }
> +       mutex_unlock(&ctlr->mutex);
> +       return 0;
> +}
> +
> +static int switchcon_ctlr_joycon_handle_event(struct switchcon_ctlr *ctlr,
> +                                                       u8 *data, int size)
> +{
> +       bool last_searching;
> +       int i;
> +
> +       mutex_lock(&ctlr->mutex);
> +       switch (ctlr->ctlr_state) {
> +       case SWITCHCON_CTLR_STATE_POST_CALIBRATION:
> +               ctlr->ctlr_state = SWITCHCON_CTLR_STATE_SEARCH;
> +               /* flashing leds to indicate searching */
> +               for (i = 0; i < 10; i++)
> +                       /* command multiple times to ensure it works */
> +                       switchcon_set_player_leds(ctlr, 0b1111, 0);
> +               /* intentional fall-through */
> +       case SWITCHCON_CTLR_STATE_SEARCH:
> +               last_searching = ctlr->searching;
> +               switchcon_ctlr_read_handler(ctlr, data, size);
> +               ctlr->searching =  ctlr->but_r || ctlr->but_zr
> +                               || ctlr->but_l || ctlr->but_zl;
> +               if (ctlr->searching != last_searching) {
> +                       schedule_work(&ctlr->detect_pair_worker);
> +               } else if ((ctlr->but_sr_left_jc && ctlr->but_sl_left_jc)
> +                       || (ctlr->but_sr_right_jc && ctlr->but_sl_right_jc)) {
> +                       schedule_work(&ctlr->create_horiz_worker);
> +                       ctlr->ctlr_state = SWITCHCON_CTLR_STATE_READ;
> +               }
> +               break;
> +       default:
> +               mutex_unlock(&ctlr->mutex);
> +               return switchcon_ctlr_general_handle_event(ctlr, data, size);
> +       }
> +       mutex_unlock(&ctlr->mutex);
> +       return 0;
> +}
> +
> +/* Implementations for each supported controller type */
> +static const struct switchcon_impl switchcon_impl_procon = {
> +       .init           = switchcon_ctlr_procon_init,
> +       .deinit         = switchcon_ctlr_general_deinit,
> +       .handle_event   = switchcon_ctlr_procon_handle_event,
> +};
> +
> +static const struct switchcon_impl switchcon_impl_joycon_l = {
> +       .init           = switchcon_ctlr_joyconl_init,
> +       .deinit         = switchcon_ctlr_general_deinit,
> +       .handle_event   = switchcon_ctlr_joycon_handle_event,
> +};
> +
> +static const struct switchcon_impl switchcon_impl_joycon_r = {
> +       .init           = switchcon_ctlr_joyconr_init,
> +       .deinit         = switchcon_ctlr_general_deinit,
> +       .handle_event   = switchcon_ctlr_joycon_handle_event,
> +};
> +
> +static struct switchcon_ctlr *switchcon_ctlr_create(struct hid_device *hdev)
> +{
> +       struct switchcon_ctlr *ctlr;
> +
> +       ctlr = devm_kzalloc(&hdev->dev, sizeof(*ctlr), GFP_KERNEL);
> +       if (!ctlr)
> +               return ERR_PTR(-ENOMEM);
> +
> +       switch (hdev->product) {
> +       case USB_DEVICE_ID_NINTENDO_PROCON:
> +               hid_info(hdev, "detected pro controller\n");
> +               ctlr->impl = &switchcon_impl_procon;
> +               break;
> +       case USB_DEVICE_ID_NINTENDO_JOYCONL:
> +               hid_info(hdev, "detected left joy-con\n");
> +               ctlr->impl = &switchcon_impl_joycon_l;
> +               break;
> +       case USB_DEVICE_ID_NINTENDO_JOYCONR:
> +               hid_info(hdev, "detected right joy-con\n");
> +               ctlr->impl = &switchcon_impl_joycon_r;
> +               break;
> +       default:
> +               hid_err(hdev, "unknown product id\n");
> +               return ERR_PTR(-EINVAL);
> +       }
> +       ctlr->hdev = hdev;
> +       ctlr->ctlr_state = SWITCHCON_CTLR_STATE_INIT;
> +       hid_set_drvdata(hdev, ctlr);
> +       spin_lock_init(&ctlr->output_lock);
> +       mutex_init(&ctlr->mutex);
> +       INIT_WORK(&ctlr->output_worker, switchcon_send_work_handler);
> +       INIT_WORK(&ctlr->create_input_worker,
> +                 switchcon_create_procon_input_handler);
> +       INIT_WORK(&ctlr->detect_pair_worker, switchcon_detect_pair_handler);
> +       INIT_WORK(&ctlr->create_horiz_worker, switchcon_create_horiz_handler);
> +       return ctlr;
> +}
> +
> +static int switchcon_hid_probe(struct hid_device *hdev,
> +                              const struct hid_device_id *id)
> +{
> +       int ret;
> +       struct switchcon_ctlr *ctlr;
> +
> +       hid_dbg(hdev, "probe - start\n");
> +       hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
> +
> +       ctlr = switchcon_ctlr_create(hdev);
> +       if (IS_ERR(ctlr)) {
> +               hid_err(hdev, "Failed to create new controller\n");
> +               ret = PTR_ERR(ctlr);
> +               goto err;
> +       }
> +
> +       ret = hid_parse(hdev);
> +       if (ret) {
> +               hid_err(hdev, "HID parse failed\n");
> +               goto err;
> +       }
> +
> +       ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> +       if (ret) {
> +               hid_err(hdev, "HW start failed\n");
> +               goto err;
> +       }
> +
> +       ret = hid_hw_open(hdev);
> +       if (ret) {
> +               hid_err(hdev, "cannot start hardware I/O\n");
> +               goto err_stop;
> +       }
> +
> +       ret = switchcon_ctlr_init(ctlr);
> +       if (ret) {
> +               hid_err(hdev, "failed to initialize ctlr\n");
> +               goto err_close;
> +       }
> +
> +       hid_dbg(hdev, "probe - success\n");
> +       return 0;
> +
> +err_close:
> +       switchcon_ctlr_deinit(ctlr);
> +       hid_hw_close(hdev);
> +err_stop:
> +       hid_hw_stop(hdev);
> +err:
> +       hid_err(hdev, "probe - fail = %d\n", ret);
> +       return ret;
> +}
> +
> +static void switchcon_hid_remove(struct hid_device *hdev)
> +{
> +       struct switchcon_ctlr *ctlr = hid_get_drvdata(hdev);
> +
> +       hid_dbg(hdev, "remove\n");
> +       hid_hw_close(hdev);
> +       hid_hw_stop(hdev);
> +       switchcon_ctlr_deinit(ctlr);
> +}
> +
> +static const struct hid_device_id switchcon_hid_devices[] = {
> +       { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> +                        USB_DEVICE_ID_NINTENDO_PROCON) },
> +       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
> +                        USB_DEVICE_ID_NINTENDO_PROCON) },
> +       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
> +                        USB_DEVICE_ID_NINTENDO_JOYCONL) },
> +       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
> +                        USB_DEVICE_ID_NINTENDO_JOYCONR) },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(hid, switchcon_hid_devices);
> +
> +static struct hid_driver switchcon_hid_driver = {
> +       .name           = "switchcon",
> +       .id_table       = switchcon_hid_devices,
> +       .probe          = switchcon_hid_probe,
> +       .remove         = switchcon_hid_remove,
> +       .raw_event      = switchcon_hid_event,
> +};
> +module_hid_driver(switchcon_hid_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@xxxxxxxxx>");
> +MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");
> --
> 2.20.1
>




[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux