Merge branch 'for-6.8/nintendo' into for-linus
authorJiri Kosina <jkosina@suse.com>
Mon, 8 Jan 2024 20:09:48 +0000 (21:09 +0100)
committerJiri Kosina <jkosina@suse.com>
Mon, 8 Jan 2024 20:09:48 +0000 (21:09 +0100)
- support for Nintendo NSO controllers -- SNES, Genesis
  and N64 (Ryan McClelland)

1  2 
drivers/hid/Kconfig
drivers/hid/hid-ids.h
drivers/hid/hid-nintendo.c

diff --combined drivers/hid/Kconfig
index 03e7fc3d05a51a71cfe6ca9f4e945fe3e8c98bf0,347c284fb27ee9ca94c6848acd6417816874f53e..4c682c65070408ee2a06ab4ba7677cdc760c87d4
@@@ -761,14 -761,15 +761,15 @@@ config HID_MULTITOUC
          module will be called hid-multitouch.
  
  config HID_NINTENDO
-       tristate "Nintendo Joy-Con and Pro Controller support"
+       tristate "Nintendo Joy-Con, NSO, and Pro Controller support"
        depends on NEW_LEDS
        depends on LEDS_CLASS
        select POWER_SUPPLY
        help
-       Adds support for the Nintendo Switch Joy-Cons and Pro Controller.
+       Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
        All controllers support bluetooth, and the Pro Controller also supports
-       its USB mode.
+       its USB mode. This also includes support for the Nintendo Switch Online
+       Controllers which include the Genesis, SNES, and N64 controllers.
  
        To compile this driver as a module, choose M here: the
        module will be called hid-nintendo.
@@@ -779,9 -780,9 +780,9 @@@ config NINTENDO_F
        select INPUT_FF_MEMLESS
        help
        Say Y here if you have a Nintendo Switch controller and want to enable
-       force feedback support for it. This works for both joy-cons and the pro
-       controller. For the pro controller, both rumble motors can be controlled
-       individually.
+       force feedback support for it. This works for both joy-cons, the pro
+       controller, and the NSO N64 controller. For the pro controller, both
+       rumble motors can be controlled individually.
  
  config HID_NTI
        tristate "NTI keyboard adapters"
@@@ -1296,15 -1297,6 +1297,15 @@@ config HID_ALP
        Say Y here if you have a Alps touchpads over i2c-hid or usbhid
        and want support for its special functionalities.
  
 +config HID_MCP2200
 +      tristate "Microchip MCP2200 HID USB-to-GPIO bridge"
 +      depends on USB_HID && GPIOLIB
 +      help
 +        Provides GPIO functionality over USB-HID through MCP2200 device.
 +
 +        To compile this driver as a module, choose M here: the module
 +        will be called hid-mcp2200.ko.
 +
  config HID_MCP2221
        tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support"
        depends on USB_HID && I2C
diff --combined drivers/hid/hid-ids.h
index ad32b650a45dbc9cf35389de1df5a4218523cbee,a90aa3c31dd0ce7e2fba5d6c55720c59a840e2a4..fb30e228d35f9a91b6bb395845584d9f073be26c
  
  #define USB_VENDOR_ID_LABTEC          0x1020
  #define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD        0x0006
 +#define USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE  0x8888
  
  #define USB_VENDOR_ID_LAVIEW          0x22D4
  #define USB_DEVICE_ID_GLORIOUS_MODEL_I        0x1503
  #define USB_DEVICE_ID_PICK16F1454     0x0042
  #define USB_DEVICE_ID_PICK16F1454_V2  0xf2f7
  #define USB_DEVICE_ID_LUXAFOR         0xf372
 +#define USB_DEVICE_ID_MCP2200         0x00df
  #define USB_DEVICE_ID_MCP2221         0x00dd
  
  #define USB_VENDOR_ID_MICROSOFT               0x045e
  #define USB_DEVICE_ID_NINTENDO_JOYCONL        0x2006
  #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_CHRGGRIP       0x200e
+ #define USB_DEVICE_ID_NINTENDO_SNESCON        0x2017
+ #define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
+ #define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
  
  #define USB_VENDOR_ID_NOVATEK         0x0603
  #define USB_DEVICE_ID_NOVATEK_PCT     0x0600
index 997c3a1adacab265bbb9b08ae98964f36ca60113,47af111ef3a208991b361db5590d6ed7701e5457..ccc4032fb2b03f4fbae705b9dc8b256978b92966
@@@ -3,6 -3,9 +3,9 @@@
   * HID driver for Nintendo Switch Joy-Cons and Pro Controllers
   *
   * Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com>
+  * Portions Copyright (c) 2020 Nadia Holmquist Pedersen <nadia@nhp.sh>
+  * Copyright (c) 2022 Emily Strickland <linux@emily.st>
+  * Copyright (c) 2023 Ryan McClelland <rymcclel@gmail.com>
   *
   * The following resources/projects were referenced for this driver:
   *   https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
@@@ -17,6 -20,9 +20,9 @@@
   * This driver supports the Nintendo Switch Joy-Cons and Pro Controllers. The
   * Pro Controllers can either be used over USB or Bluetooth.
   *
+  * This driver also incorporates support for Nintendo Switch Online controllers
+  * for the NES, SNES, Sega Genesis, and N64.
+  *
   * The driver will retrieve the factory calibration info from the controllers,
   * so little to no user calibration should be required.
   *
@@@ -305,9 -311,14 +311,14 @@@ enum joycon_ctlr_state 
  
  /* Controller type received as part of device info */
  enum joycon_ctlr_type {
-       JOYCON_CTLR_TYPE_JCL = 0x01,
-       JOYCON_CTLR_TYPE_JCR = 0x02,
-       JOYCON_CTLR_TYPE_PRO = 0x03,
+       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,
+       JOYCON_CTLR_TYPE_N64  = 0x0C,
  };
  
  struct joycon_stick_cal {
@@@ -325,29 -336,160 +336,160 @@@ struct joycon_imu_cal 
   * All the controller's button values are stored in a u32.
   * They can be accessed with bitwise ANDs.
   */
 -static const u32 JC_BTN_Y     = BIT(0);
 -static const u32 JC_BTN_X     = BIT(1);
 -static const u32 JC_BTN_B     = BIT(2);
 -static const u32 JC_BTN_A     = BIT(3);
 -static const u32 JC_BTN_SR_R  = BIT(4);
 -static const u32 JC_BTN_SL_R  = BIT(5);
 -static const u32 JC_BTN_R     = BIT(6);
 -static const u32 JC_BTN_ZR    = BIT(7);
 -static const u32 JC_BTN_MINUS = BIT(8);
 -static const u32 JC_BTN_PLUS  = BIT(9);
 -static const u32 JC_BTN_RSTICK        = BIT(10);
 -static const u32 JC_BTN_LSTICK        = BIT(11);
 -static const u32 JC_BTN_HOME  = BIT(12);
 -static const u32 JC_BTN_CAP   = BIT(13); /* capture button */
 -static const u32 JC_BTN_DOWN  = BIT(16);
 -static const u32 JC_BTN_UP    = BIT(17);
 -static const u32 JC_BTN_RIGHT = BIT(18);
 -static const u32 JC_BTN_LEFT  = BIT(19);
 -static const u32 JC_BTN_SR_L  = BIT(20);
 -static const u32 JC_BTN_SL_L  = BIT(21);
 -static const u32 JC_BTN_L     = BIT(22);
 -static const u32 JC_BTN_ZL    = BIT(23);
 +#define JC_BTN_Y       BIT(0)
 +#define JC_BTN_X       BIT(1)
 +#define JC_BTN_B       BIT(2)
 +#define JC_BTN_A       BIT(3)
 +#define JC_BTN_SR_R    BIT(4)
 +#define JC_BTN_SL_R    BIT(5)
 +#define JC_BTN_R       BIT(6)
 +#define JC_BTN_ZR      BIT(7)
 +#define JC_BTN_MINUS   BIT(8)
 +#define JC_BTN_PLUS    BIT(9)
 +#define JC_BTN_RSTICK  BIT(10)
 +#define JC_BTN_LSTICK  BIT(11)
 +#define JC_BTN_HOME    BIT(12)
 +#define JC_BTN_CAP     BIT(13) /* capture button */
 +#define JC_BTN_DOWN    BIT(16)
 +#define JC_BTN_UP      BIT(17)
 +#define JC_BTN_RIGHT   BIT(18)
 +#define JC_BTN_LEFT    BIT(19)
 +#define JC_BTN_SR_L    BIT(20)
 +#define JC_BTN_SL_L    BIT(21)
 +#define JC_BTN_L       BIT(22)
 +#define JC_BTN_ZL      BIT(23)
  
+ struct joycon_ctlr_button_mapping {
+       u32 code;
+       u32 bit;
+ };
+ /*
+  * D-pad is configured as buttons for the left Joy-Con only!
+  */
+ static const struct joycon_ctlr_button_mapping left_joycon_button_mappings[] = {
+       { BTN_TL,               JC_BTN_L,       },
+       { BTN_TL2,              JC_BTN_ZL,      },
+       { BTN_SELECT,           JC_BTN_MINUS,   },
+       { BTN_THUMBL,           JC_BTN_LSTICK,  },
+       { BTN_DPAD_UP,          JC_BTN_UP,      },
+       { BTN_DPAD_DOWN,        JC_BTN_DOWN,    },
+       { BTN_DPAD_LEFT,        JC_BTN_LEFT,    },
+       { BTN_DPAD_RIGHT,       JC_BTN_RIGHT,   },
+       { BTN_Z,                JC_BTN_CAP,     },
+       { /* sentinel */ },
+ };
+ /*
+  * The unused *right*-side triggers become the SL/SR triggers for the *left*
+  * Joy-Con, if and only if we're not using a charging grip.
+  */
+ static const struct joycon_ctlr_button_mapping left_joycon_s_button_mappings[] = {
+       { BTN_TR,       JC_BTN_SL_L,    },
+       { BTN_TR2,      JC_BTN_SR_L,    },
+       { /* sentinel */ },
+ };
+ static const struct joycon_ctlr_button_mapping right_joycon_button_mappings[] = {
+       { BTN_EAST,     JC_BTN_A,       },
+       { BTN_SOUTH,    JC_BTN_B,       },
+       { BTN_NORTH,    JC_BTN_X,       },
+       { BTN_WEST,     JC_BTN_Y,       },
+       { BTN_TR,       JC_BTN_R,       },
+       { BTN_TR2,      JC_BTN_ZR,      },
+       { BTN_START,    JC_BTN_PLUS,    },
+       { BTN_THUMBR,   JC_BTN_RSTICK,  },
+       { BTN_MODE,     JC_BTN_HOME,    },
+       { /* sentinel */ },
+ };
+ /*
+  * The unused *left*-side triggers become the SL/SR triggers for the *right*
+  * Joy-Con, if and only if we're not using a charging grip.
+  */
+ static const struct joycon_ctlr_button_mapping right_joycon_s_button_mappings[] = {
+       { BTN_TL,       JC_BTN_SL_R,    },
+       { BTN_TL2,      JC_BTN_SR_R,    },
+       { /* sentinel */ },
+ };
+ static const struct joycon_ctlr_button_mapping procon_button_mappings[] = {
+       { BTN_EAST,     JC_BTN_A,       },
+       { BTN_SOUTH,    JC_BTN_B,       },
+       { BTN_NORTH,    JC_BTN_X,       },
+       { BTN_WEST,     JC_BTN_Y,       },
+       { 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,       },
+       { BTN_TL,       JC_BTN_L,       },
+       { BTN_TR,       JC_BTN_R,       },
+       { BTN_SELECT,   JC_BTN_MINUS,   },
+       { BTN_START,    JC_BTN_PLUS,    },
+       { /* sentinel */ },
+ };
+ static const struct joycon_ctlr_button_mapping snescon_button_mappings[] = {
+       { BTN_EAST,     JC_BTN_A,       },
+       { BTN_SOUTH,    JC_BTN_B,       },
+       { BTN_NORTH,    JC_BTN_X,       },
+       { BTN_WEST,     JC_BTN_Y,       },
+       { 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,    },
+       { /* sentinel */ },
+ };
+ /*
+  * "A", "B", and "C" are mapped positionally, rather than by label (e.g., "A"
+  * gets assigned to BTN_EAST instead of BTN_A).
+  */
+ static const struct joycon_ctlr_button_mapping gencon_button_mappings[] = {
+       { BTN_SOUTH,    JC_BTN_A,       },
+       { BTN_EAST,     JC_BTN_B,       },
+       { BTN_WEST,     JC_BTN_R,       },
+       { BTN_SELECT,   JC_BTN_ZR,      },
+       { BTN_START,    JC_BTN_PLUS,    },
+       { BTN_MODE,     JC_BTN_HOME,    },
+       { BTN_Z,        JC_BTN_CAP,     },
+       { /* sentinel */ },
+ };
+ /*
+  * N64's C buttons get assigned to d-pad directions and registered as buttons.
+  */
+ static const struct joycon_ctlr_button_mapping n64con_button_mappings[] = {
+       { BTN_A,                JC_BTN_A,       },
+       { BTN_B,                JC_BTN_B,       },
+       { BTN_TL2,              JC_BTN_ZL,      }, /* Z */
+       { BTN_TL,               JC_BTN_L,       },
+       { BTN_TR,               JC_BTN_R,       },
+       { BTN_TR2,              JC_BTN_LSTICK,  }, /* ZR */
+       { BTN_START,            JC_BTN_PLUS,    },
+       { BTN_FORWARD,          JC_BTN_Y,       }, /* C UP */
+       { BTN_BACK,             JC_BTN_ZR,      }, /* C DOWN */
+       { BTN_LEFT,             JC_BTN_X,       }, /* C LEFT */
+       { BTN_RIGHT,            JC_BTN_MINUS,   }, /* C RIGHT */
+       { BTN_MODE,             JC_BTN_HOME,    },
+       { BTN_Z,                JC_BTN_CAP,     },
+       { /* sentinel */ },
+ };
  enum joycon_msg_type {
        JOYCON_MSG_TYPE_NONE,
        JOYCON_MSG_TYPE_USB,
@@@ -506,13 -648,182 +648,182 @@@ struct joycon_ctlr 
  /* 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_PRO || \
+        ctlr->ctlr_type == JOYCON_CTLR_TYPE_N64)
  
  /* 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)
  
+ /*
+  * Controller device helpers
+  *
+  * These look at the device ID known to the HID subsystem to identify a device,
+  * but take caution: some NSO devices lie about themselves (NES Joy-Cons and
+  * Sega Genesis controller). See type helpers below.
+  *
+  * These helpers are most useful early during the HID probe or in conjunction
+  * with the capability helpers below.
+  */
+ static inline bool joycon_device_is_left_joycon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL;
+ }
+ static inline bool joycon_device_is_right_joycon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR;
+ }
+ static inline bool joycon_device_is_procon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_PROCON;
+ }
+ static inline bool joycon_device_is_chrggrip(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP;
+ }
+ static inline bool joycon_device_is_snescon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_SNESCON;
+ }
+ static inline bool joycon_device_is_gencon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_GENCON;
+ }
+ static inline bool joycon_device_is_n64con(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_N64CON;
+ }
+ static inline bool joycon_device_has_usb(struct joycon_ctlr *ctlr)
+ {
+       return joycon_device_is_procon(ctlr) ||
+              joycon_device_is_chrggrip(ctlr) ||
+              joycon_device_is_snescon(ctlr) ||
+              joycon_device_is_gencon(ctlr) ||
+              joycon_device_is_n64con(ctlr);
+ }
+ /*
+  * Controller type helpers
+  *
+  * These are slightly different than the device-ID-based helpers above. They are
+  * generally more reliable, since they can distinguish between, e.g., Genesis
+  * versus SNES, or NES Joy-Cons versus regular Switch Joy-Cons. They're most
+  * useful for reporting available inputs. For other kinds of distinctions, see
+  * the capability helpers below.
+  *
+  * They have two major drawbacks: (1) they're not available until after we set
+  * the reporting method and then request the device info; (2) they can't
+  * distinguish all controllers (like the Charging Grip from the Pro controller.)
+  */
+ static inline bool joycon_type_is_left_joycon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCL;
+ }
+ static inline bool joycon_type_is_right_joycon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR;
+ }
+ static inline bool joycon_type_is_procon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO;
+ }
+ static inline bool joycon_type_is_snescon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->ctlr_type == JOYCON_CTLR_TYPE_SNES;
+ }
+ static inline bool joycon_type_is_gencon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->ctlr_type == JOYCON_CTLR_TYPE_GEN;
+ }
+ static inline bool joycon_type_is_n64con(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->ctlr_type == JOYCON_CTLR_TYPE_N64;
+ }
+ static inline bool joycon_type_is_left_nescon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESL;
+ }
+ static inline bool joycon_type_is_right_nescon(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESR;
+ }
+ static inline bool joycon_type_has_left_controls(struct joycon_ctlr *ctlr)
+ {
+       return joycon_type_is_left_joycon(ctlr) ||
+              joycon_type_is_procon(ctlr);
+ }
+ static inline bool joycon_type_has_right_controls(struct joycon_ctlr *ctlr)
+ {
+       return joycon_type_is_right_joycon(ctlr) ||
+              joycon_type_is_procon(ctlr);
+ }
+ static inline bool joycon_type_is_any_joycon(struct joycon_ctlr *ctlr)
+ {
+       return joycon_type_is_left_joycon(ctlr) ||
+              joycon_type_is_right_joycon(ctlr) ||
+              joycon_device_is_chrggrip(ctlr);
+ }
+ static inline bool joycon_type_is_any_nescon(struct joycon_ctlr *ctlr)
+ {
+       return joycon_type_is_left_nescon(ctlr) ||
+              joycon_type_is_right_nescon(ctlr);
+ }
+ /*
+  * Controller capability helpers
+  *
+  * These helpers combine the use of the helpers above to detect certain
+  * capabilities during initialization. They are always accurate but (since they
+  * use type helpers) cannot be used early in the HID probe.
+  */
+ static inline bool joycon_has_imu(struct joycon_ctlr *ctlr)
+ {
+       return joycon_device_is_chrggrip(ctlr) ||
+              joycon_type_is_any_joycon(ctlr) ||
+              joycon_type_is_procon(ctlr);
+ }
+ static inline bool joycon_has_joysticks(struct joycon_ctlr *ctlr)
+ {
+       return joycon_device_is_chrggrip(ctlr) ||
+              joycon_type_is_any_joycon(ctlr) ||
+              joycon_type_is_procon(ctlr) ||
+              joycon_type_is_n64con(ctlr);
+ }
+ static inline bool joycon_has_rumble(struct joycon_ctlr *ctlr)
+ {
+       return joycon_device_is_chrggrip(ctlr) ||
+              joycon_type_is_any_joycon(ctlr) ||
+              joycon_type_is_procon(ctlr) ||
+              joycon_type_is_n64con(ctlr);
+ }
+ static inline bool joycon_using_usb(struct joycon_ctlr *ctlr)
+ {
+       return ctlr->hdev->bus == BUS_USB;
+ }
  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
  {
        u8 *buf;
@@@ -927,27 -1238,14 +1238,27 @@@ static int joycon_request_calibration(s
   */
  static void joycon_calc_imu_cal_divisors(struct joycon_ctlr *ctlr)
  {
 -      int i;
 +      int i, divz = 0;
  
        for (i = 0; i < 3; i++) {
                ctlr->imu_cal_accel_divisor[i] = ctlr->accel_cal.scale[i] -
                                                ctlr->accel_cal.offset[i];
                ctlr->imu_cal_gyro_divisor[i] = ctlr->gyro_cal.scale[i] -
                                                ctlr->gyro_cal.offset[i];
 +
 +              if (ctlr->imu_cal_accel_divisor[i] == 0) {
 +                      ctlr->imu_cal_accel_divisor[i] = 1;
 +                      divz++;
 +              }
 +
 +              if (ctlr->imu_cal_gyro_divisor[i] == 0) {
 +                      ctlr->imu_cal_gyro_divisor[i] = 1;
 +                      divz++;
 +              }
        }
 +
 +      if (divz)
 +              hid_warn(ctlr->hdev, "inaccurate IMU divisors (%d)\n", divz);
  }
  
  static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
@@@ -1176,16 -1474,16 +1487,16 @@@ static void joycon_parse_imu_report(str
                    JC_IMU_SAMPLES_PER_DELTA_AVG) {
                        ctlr->imu_avg_delta_ms = ctlr->imu_delta_samples_sum /
                                                 ctlr->imu_delta_samples_count;
 -                      /* don't ever want divide by zero shenanigans */
 -                      if (ctlr->imu_avg_delta_ms == 0) {
 -                              ctlr->imu_avg_delta_ms = 1;
 -                              hid_warn(ctlr->hdev,
 -                                       "calculated avg imu delta of 0\n");
 -                      }
                        ctlr->imu_delta_samples_count = 0;
                        ctlr->imu_delta_samples_sum = 0;
                }
  
 +              /* don't ever want divide by zero shenanigans */
 +              if (ctlr->imu_avg_delta_ms == 0) {
 +                      ctlr->imu_avg_delta_ms = 1;
 +                      hid_warn(ctlr->hdev, "calculated avg imu delta of 0\n");
 +              }
 +
                /* useful for debugging IMU sample rate */
                hid_dbg(ctlr->hdev,
                        "imu_report: ms=%u last_ms=%u delta=%u avg_delta=%u\n",
        }
  }
  
- static void joycon_parse_report(struct joycon_ctlr *ctlr,
-                               struct joycon_input_report *rep)
+ static void joycon_handle_rumble_report(struct joycon_ctlr *ctlr, struct joycon_input_report *rep)
  {
-       struct input_dev *dev = ctlr->input;
        unsigned long flags;
-       u8 tmp;
-       u32 btns;
        unsigned long msecs = jiffies_to_msecs(jiffies);
-       unsigned long report_delta_ms = msecs - ctlr->last_input_report_msecs;
  
        spin_lock_irqsave(&ctlr->lock, flags);
        if (IS_ENABLED(CONFIG_NINTENDO_FF) && rep->vibrator_report &&
                queue_work(ctlr->rumble_queue, &ctlr->rumble_worker);
        }
  
-       /* Parse the battery status */
+       spin_unlock_irqrestore(&ctlr->lock, flags);
+ }
+ static void joycon_parse_battery_status(struct joycon_ctlr *ctlr, struct joycon_input_report *rep)
+ {
+       u8 tmp;
+       unsigned long flags;
+       spin_lock_irqsave(&ctlr->lock, flags);
        tmp = rep->bat_con;
        ctlr->host_powered = tmp & BIT(0);
        ctlr->battery_charging = tmp & BIT(4);
        tmp = tmp >> 5;
        switch (tmp) {
        case 0: /* empty */
                ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
                hid_warn(ctlr->hdev, "Invalid battery status\n");
                break;
        }
        spin_unlock_irqrestore(&ctlr->lock, flags);
+ }
  
-       /* Parse the buttons and sticks */
-       btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24);
-       if (jc_type_has_left(ctlr)) {
-               u16 raw_x;
-               u16 raw_y;
-               s32 x;
-               s32 y;
-               /* get raw stick values */
-               raw_x = hid_field_extract(ctlr->hdev, rep->left_stick, 0, 12);
-               raw_y = hid_field_extract(ctlr->hdev,
-                                         rep->left_stick + 1, 4, 12);
-               /* map the stick values */
-               x = joycon_map_stick_val(&ctlr->left_stick_cal_x, raw_x);
-               y = -joycon_map_stick_val(&ctlr->left_stick_cal_y, raw_y);
-               /* report sticks */
-               input_report_abs(dev, ABS_X, x);
-               input_report_abs(dev, ABS_Y, 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_SELECT, btns & JC_BTN_MINUS);
-               input_report_key(dev, BTN_THUMBL, btns & JC_BTN_LSTICK);
-               input_report_key(dev, BTN_Z, btns & JC_BTN_CAP);
-               if (jc_type_is_joycon(ctlr)) {
-                       /* Report the S buttons as the non-existent triggers */
-                       input_report_key(dev, BTN_TR, btns & JC_BTN_SL_L);
-                       input_report_key(dev, BTN_TR2, btns & JC_BTN_SR_L);
-                       /* Report d-pad as digital buttons for the joy-cons */
-                       input_report_key(dev, BTN_DPAD_DOWN,
-                                        btns & JC_BTN_DOWN);
-                       input_report_key(dev, BTN_DPAD_UP, btns & JC_BTN_UP);
-                       input_report_key(dev, BTN_DPAD_RIGHT,
-                                        btns & JC_BTN_RIGHT);
-                       input_report_key(dev, BTN_DPAD_LEFT,
-                                        btns & JC_BTN_LEFT);
-               } else {
-                       int hatx = 0;
-                       int haty = 0;
-                       /* d-pad x */
-                       if (btns & JC_BTN_LEFT)
-                               hatx = -1;
-                       else if (btns & JC_BTN_RIGHT)
-                               hatx = 1;
-                       input_report_abs(dev, ABS_HAT0X, hatx);
-                       /* d-pad y */
-                       if (btns & JC_BTN_UP)
-                               haty = -1;
-                       else if (btns & JC_BTN_DOWN)
-                               haty = 1;
-                       input_report_abs(dev, ABS_HAT0Y, haty);
-               }
-       }
-       if (jc_type_has_right(ctlr)) {
-               u16 raw_x;
-               u16 raw_y;
-               s32 x;
-               s32 y;
-               /* get raw stick values */
-               raw_x = hid_field_extract(ctlr->hdev, rep->right_stick, 0, 12);
-               raw_y = hid_field_extract(ctlr->hdev,
-                                         rep->right_stick + 1, 4, 12);
-               /* map stick values */
-               x = joycon_map_stick_val(&ctlr->right_stick_cal_x, raw_x);
-               y = -joycon_map_stick_val(&ctlr->right_stick_cal_y, raw_y);
-               /* report sticks */
-               input_report_abs(dev, ABS_RX, x);
-               input_report_abs(dev, ABS_RY, y);
-               /* report buttons */
-               input_report_key(dev, BTN_TR, btns & JC_BTN_R);
-               input_report_key(dev, BTN_TR2, btns & JC_BTN_ZR);
-               if (jc_type_is_joycon(ctlr)) {
-                       /* Report the S buttons as the non-existent triggers */
-                       input_report_key(dev, BTN_TL, btns & JC_BTN_SL_R);
-                       input_report_key(dev, BTN_TL2, btns & JC_BTN_SR_R);
-               }
-               input_report_key(dev, BTN_START, btns & JC_BTN_PLUS);
-               input_report_key(dev, BTN_THUMBR, btns & JC_BTN_RSTICK);
-               input_report_key(dev, BTN_MODE, btns & JC_BTN_HOME);
-               input_report_key(dev, BTN_WEST, btns & JC_BTN_Y);
-               input_report_key(dev, BTN_NORTH, btns & JC_BTN_X);
-               input_report_key(dev, BTN_EAST, btns & JC_BTN_A);
-               input_report_key(dev, BTN_SOUTH, btns & JC_BTN_B);
+ static void joycon_report_left_stick(struct joycon_ctlr *ctlr,
+                                    struct joycon_input_report *rep)
+ {
+       u16 raw_x;
+       u16 raw_y;
+       s32 x;
+       s32 y;
+       raw_x = hid_field_extract(ctlr->hdev, rep->left_stick, 0, 12);
+       raw_y = hid_field_extract(ctlr->hdev, rep->left_stick + 1, 4, 12);
+       x = joycon_map_stick_val(&ctlr->left_stick_cal_x, raw_x);
+       y = -joycon_map_stick_val(&ctlr->left_stick_cal_y, raw_y);
+       input_report_abs(ctlr->input, ABS_X, x);
+       input_report_abs(ctlr->input, ABS_Y, y);
+ }
+ static void joycon_report_right_stick(struct joycon_ctlr *ctlr,
+                                     struct joycon_input_report *rep)
+ {
+       u16 raw_x;
+       u16 raw_y;
+       s32 x;
+       s32 y;
+       raw_x = hid_field_extract(ctlr->hdev, rep->right_stick, 0, 12);
+       raw_y = hid_field_extract(ctlr->hdev, rep->right_stick + 1, 4, 12);
+       x = joycon_map_stick_val(&ctlr->right_stick_cal_x, raw_x);
+       y = -joycon_map_stick_val(&ctlr->right_stick_cal_y, raw_y);
+       input_report_abs(ctlr->input, ABS_RX, x);
+       input_report_abs(ctlr->input, ABS_RY, y);
+ }
+ static void joycon_report_dpad(struct joycon_ctlr *ctlr,
+                              struct joycon_input_report *rep)
+ {
+       int hatx = 0;
+       int haty = 0;
+       u32 btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24);
+       if (btns & JC_BTN_LEFT)
+               hatx = -1;
+       else if (btns & JC_BTN_RIGHT)
+               hatx = 1;
+       if (btns & JC_BTN_UP)
+               haty = -1;
+       else if (btns & JC_BTN_DOWN)
+               haty = 1;
+       input_report_abs(ctlr->input, ABS_HAT0X, hatx);
+       input_report_abs(ctlr->input, ABS_HAT0Y, haty);
+ }
+ static void joycon_report_buttons(struct joycon_ctlr *ctlr,
+                                 struct joycon_input_report *rep,
+                                 const struct joycon_ctlr_button_mapping button_mappings[])
+ {
+       const struct joycon_ctlr_button_mapping *button;
+       u32 status = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24);
+       for (button = button_mappings; button->code; button++)
+               input_report_key(ctlr->input, button->code, status & button->bit);
+ }
+ static void joycon_parse_report(struct joycon_ctlr *ctlr,
+                               struct joycon_input_report *rep)
+ {
+       unsigned long flags;
+       unsigned long msecs = jiffies_to_msecs(jiffies);
+       unsigned long report_delta_ms = msecs - ctlr->last_input_report_msecs;
+       if (joycon_has_rumble(ctlr))
+               joycon_handle_rumble_report(ctlr, rep);
+       joycon_parse_battery_status(ctlr, rep);
+       if (joycon_type_is_left_joycon(ctlr)) {
+               joycon_report_left_stick(ctlr, rep);
+               joycon_report_buttons(ctlr, rep, left_joycon_button_mappings);
+               if (!joycon_device_is_chrggrip(ctlr))
+                       joycon_report_buttons(ctlr, rep, left_joycon_s_button_mappings);
+       } else if (joycon_type_is_right_joycon(ctlr)) {
+               joycon_report_right_stick(ctlr, rep);
+               joycon_report_buttons(ctlr, rep, right_joycon_button_mappings);
+               if (!joycon_device_is_chrggrip(ctlr))
+                       joycon_report_buttons(ctlr, rep, right_joycon_s_button_mappings);
+       } else if (joycon_type_is_procon(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);
+       } else if (joycon_type_is_any_nescon(ctlr)) {
+               joycon_report_dpad(ctlr, rep);
+               joycon_report_buttons(ctlr, rep, nescon_button_mappings);
+       } else if (joycon_type_is_snescon(ctlr)) {
+               joycon_report_dpad(ctlr, rep);
+               joycon_report_buttons(ctlr, rep, snescon_button_mappings);
+       } else if (joycon_type_is_gencon(ctlr)) {
+               joycon_report_dpad(ctlr, rep);
+               joycon_report_buttons(ctlr, rep, gencon_button_mappings);
+       } else if (joycon_type_is_n64con(ctlr)) {
+               joycon_report_left_stick(ctlr, rep);
+               joycon_report_dpad(ctlr, rep);
+               joycon_report_buttons(ctlr, rep, n64con_button_mappings);
        }
  
-       input_sync(dev);
+       input_sync(ctlr->input);
  
        spin_lock_irqsave(&ctlr->lock, flags);
        ctlr->last_input_report_msecs = msecs;
        }
  
        /* parse IMU data if present */
-       if (rep->id == JC_INPUT_IMU_DATA)
+       if ((rep->id == JC_INPUT_IMU_DATA) && joycon_has_imu(ctlr))
                joycon_parse_imu_report(ctlr, rep);
  }
  
@@@ -1697,123 -2019,65 +2032,65 @@@ static int joycon_play_effect(struct in
  }
  #endif /* IS_ENABLED(CONFIG_NINTENDO_FF) */
  
- static const unsigned int joycon_button_inputs_l[] = {
-       BTN_SELECT, BTN_Z, BTN_THUMBL,
-       BTN_TL, BTN_TL2,
-       0 /* 0 signals end of array */
- };
- static const unsigned int joycon_button_inputs_r[] = {
-       BTN_START, BTN_MODE, BTN_THUMBR,
-       BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST,
-       BTN_TR, BTN_TR2,
-       0 /* 0 signals end of array */
- };
- /* We report joy-con d-pad inputs as buttons and pro controller as a hat. */
- static const unsigned int joycon_dpad_inputs_jc[] = {
-       BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
-       0 /* 0 signals end of array */
- };
- static int joycon_input_create(struct joycon_ctlr *ctlr)
+ static void joycon_config_left_stick(struct input_dev *idev)
  {
-       struct hid_device *hdev;
-       const char *name;
-       const char *imu_name;
-       int ret;
-       int i;
-       hdev = ctlr->hdev;
+       input_set_abs_params(idev,
+                            ABS_X,
+                            -JC_MAX_STICK_MAG,
+                            JC_MAX_STICK_MAG,
+                            JC_STICK_FUZZ,
+                            JC_STICK_FLAT);
+       input_set_abs_params(idev,
+                            ABS_Y,
+                            -JC_MAX_STICK_MAG,
+                            JC_MAX_STICK_MAG,
+                            JC_STICK_FUZZ,
+                            JC_STICK_FLAT);
+ }
  
-       switch (hdev->product) {
-       case USB_DEVICE_ID_NINTENDO_PROCON:
-               name = "Nintendo Switch Pro Controller";
-               imu_name = "Nintendo Switch Pro Controller IMU";
-               break;
-       case USB_DEVICE_ID_NINTENDO_CHRGGRIP:
-               if (jc_type_has_left(ctlr)) {
-                       name = "Nintendo Switch Left Joy-Con (Grip)";
-                       imu_name = "Nintendo Switch Left Joy-Con IMU (Grip)";
-               } else {
-                       name = "Nintendo Switch Right Joy-Con (Grip)";
-                       imu_name = "Nintendo Switch Right Joy-Con IMU (Grip)";
-               }
-               break;
-       case USB_DEVICE_ID_NINTENDO_JOYCONL:
-               name = "Nintendo Switch Left Joy-Con";
-               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";
-               break;
-       default: /* Should be impossible */
-               hid_err(hdev, "Invalid hid product\n");
-               return -EINVAL;
-       }
+ static void joycon_config_right_stick(struct input_dev *idev)
+ {
+       input_set_abs_params(idev,
+                            ABS_RX,
+                            -JC_MAX_STICK_MAG,
+                            JC_MAX_STICK_MAG,
+                            JC_STICK_FUZZ,
+                            JC_STICK_FLAT);
+       input_set_abs_params(idev,
+                            ABS_RY,
+                            -JC_MAX_STICK_MAG,
+                            JC_MAX_STICK_MAG,
+                            JC_STICK_FUZZ,
+                            JC_STICK_FLAT);
+ }
  
-       ctlr->input = devm_input_allocate_device(&hdev->dev);
-       if (!ctlr->input)
-               return -ENOMEM;
-       ctlr->input->id.bustype = hdev->bus;
-       ctlr->input->id.vendor = hdev->vendor;
-       ctlr->input->id.product = hdev->product;
-       ctlr->input->id.version = hdev->version;
-       ctlr->input->uniq = ctlr->mac_addr_str;
-       ctlr->input->name = name;
-       ctlr->input->phys = hdev->phys;
-       input_set_drvdata(ctlr->input, ctlr);
+ static void joycon_config_dpad(struct input_dev *idev)
+ {
+       input_set_abs_params(idev,
+                            ABS_HAT0X,
+                            -JC_MAX_DPAD_MAG,
+                            JC_MAX_DPAD_MAG,
+                            JC_DPAD_FUZZ,
+                            JC_DPAD_FLAT);
+       input_set_abs_params(idev,
+                            ABS_HAT0Y,
+                            -JC_MAX_DPAD_MAG,
+                            JC_MAX_DPAD_MAG,
+                            JC_DPAD_FUZZ,
+                            JC_DPAD_FLAT);
+ }
  
-       /* set up sticks and buttons */
-       if (jc_type_has_left(ctlr)) {
-               input_set_abs_params(ctlr->input, ABS_X,
-                                    -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG,
-                                    JC_STICK_FUZZ, JC_STICK_FLAT);
-               input_set_abs_params(ctlr->input, ABS_Y,
-                                    -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG,
-                                    JC_STICK_FUZZ, JC_STICK_FLAT);
-               for (i = 0; joycon_button_inputs_l[i] > 0; i++)
-                       input_set_capability(ctlr->input, EV_KEY,
-                                            joycon_button_inputs_l[i]);
-               /* configure d-pad differently for joy-con vs pro controller */
-               if (hdev->product != USB_DEVICE_ID_NINTENDO_PROCON) {
-                       for (i = 0; joycon_dpad_inputs_jc[i] > 0; i++)
-                               input_set_capability(ctlr->input, EV_KEY,
-                                                    joycon_dpad_inputs_jc[i]);
-               } else {
-                       input_set_abs_params(ctlr->input, ABS_HAT0X,
-                                            -JC_MAX_DPAD_MAG, JC_MAX_DPAD_MAG,
-                                            JC_DPAD_FUZZ, JC_DPAD_FLAT);
-                       input_set_abs_params(ctlr->input, ABS_HAT0Y,
-                                            -JC_MAX_DPAD_MAG, JC_MAX_DPAD_MAG,
-                                            JC_DPAD_FUZZ, JC_DPAD_FLAT);
-               }
-       }
-       if (jc_type_has_right(ctlr)) {
-               input_set_abs_params(ctlr->input, ABS_RX,
-                                    -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG,
-                                    JC_STICK_FUZZ, JC_STICK_FLAT);
-               input_set_abs_params(ctlr->input, ABS_RY,
-                                    -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG,
-                                    JC_STICK_FUZZ, JC_STICK_FLAT);
-               for (i = 0; joycon_button_inputs_r[i] > 0; i++)
-                       input_set_capability(ctlr->input, EV_KEY,
-                                            joycon_button_inputs_r[i]);
-       }
+ static void joycon_config_buttons(struct input_dev *idev,
+                const struct joycon_ctlr_button_mapping button_mappings[])
+ {
+       const struct joycon_ctlr_button_mapping *button;
  
-       /* Let's report joy-con S triggers separately */
-       if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL) {
-               input_set_capability(ctlr->input, EV_KEY, BTN_TR);
-               input_set_capability(ctlr->input, EV_KEY, BTN_TR2);
-       } else if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR) {
-               input_set_capability(ctlr->input, EV_KEY, BTN_TL);
-               input_set_capability(ctlr->input, EV_KEY, BTN_TL2);
-       }
+       for (button = button_mappings; button->code; button++)
+               input_set_capability(idev, EV_KEY, button->code);
+ }
  
+ static void joycon_config_rumble(struct joycon_ctlr *ctlr)
+ {
  #if IS_ENABLED(CONFIG_NINTENDO_FF)
        /* set up rumble */
        input_set_capability(ctlr->input, EV_FF, FF_RUMBLE);
        joycon_set_rumble(ctlr, 0, 0, false);
        ctlr->rumble_msecs = jiffies_to_msecs(jiffies);
  #endif
+ }
  
-       ret = input_register_device(ctlr->input);
-       if (ret)
-               return ret;
+ static int joycon_imu_input_create(struct joycon_ctlr *ctlr)
+ {
+       struct hid_device *hdev;
+       const char *imu_name;
+       int ret;
+       hdev = ctlr->hdev;
  
        /* configure the imu input device */
        ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
        ctlr->imu_input->id.product = hdev->product;
        ctlr->imu_input->id.version = hdev->version;
        ctlr->imu_input->uniq = ctlr->mac_addr_str;
-       ctlr->imu_input->name = imu_name;
        ctlr->imu_input->phys = hdev->phys;
+       imu_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s (IMU)", ctlr->input->name);
+       if (!imu_name)
+               return -ENOMEM;
+       ctlr->imu_input->name = imu_name;
        input_set_drvdata(ctlr->imu_input, ctlr);
  
        /* configure imu axes */
        return 0;
  }
  
+ static int joycon_input_create(struct joycon_ctlr *ctlr)
+ {
+       struct hid_device *hdev;
+       int ret;
+       hdev = ctlr->hdev;
+       ctlr->input = devm_input_allocate_device(&hdev->dev);
+       if (!ctlr->input)
+               return -ENOMEM;
+       ctlr->input->id.bustype = hdev->bus;
+       ctlr->input->id.vendor = hdev->vendor;
+       ctlr->input->id.product = hdev->product;
+       ctlr->input->id.version = hdev->version;
+       ctlr->input->uniq = ctlr->mac_addr_str;
+       ctlr->input->name = hdev->name;
+       ctlr->input->phys = hdev->phys;
+       input_set_drvdata(ctlr->input, ctlr);
+       ret = input_register_device(ctlr->input);
+       if (ret)
+               return ret;
+       if (joycon_type_is_right_joycon(ctlr)) {
+               joycon_config_right_stick(ctlr->input);
+               joycon_config_buttons(ctlr->input, right_joycon_button_mappings);
+               if (!joycon_device_is_chrggrip(ctlr))
+                       joycon_config_buttons(ctlr->input, right_joycon_s_button_mappings);
+       } else if (joycon_type_is_left_joycon(ctlr)) {
+               joycon_config_left_stick(ctlr->input);
+               joycon_config_buttons(ctlr->input, left_joycon_button_mappings);
+               if (!joycon_device_is_chrggrip(ctlr))
+                       joycon_config_buttons(ctlr->input, left_joycon_s_button_mappings);
+       } else if (joycon_type_is_procon(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);
+       } else if (joycon_type_is_any_nescon(ctlr)) {
+               joycon_config_dpad(ctlr->input);
+               joycon_config_buttons(ctlr->input, nescon_button_mappings);
+       } else if (joycon_type_is_snescon(ctlr)) {
+               joycon_config_dpad(ctlr->input);
+               joycon_config_buttons(ctlr->input, snescon_button_mappings);
+       } else if (joycon_type_is_gencon(ctlr)) {
+               joycon_config_dpad(ctlr->input);
+               joycon_config_buttons(ctlr->input, gencon_button_mappings);
+       } else if (joycon_type_is_n64con(ctlr)) {
+               joycon_config_dpad(ctlr->input);
+               joycon_config_left_stick(ctlr->input);
+               joycon_config_buttons(ctlr->input, n64con_button_mappings);
+       }
+       if (joycon_has_imu(ctlr)) {
+               ret = joycon_imu_input_create(ctlr);
+               if (ret)
+                       return ret;
+       }
+       if (joycon_has_rumble(ctlr))
+               joycon_config_rumble(ctlr);
+       return 0;
+ }
  /* Because the subcommand sets all the leds at once, the brightness argument is ignored */
  static int joycon_player_led_brightness_set(struct led_classdev *led,
                                            enum led_brightness brightness)
@@@ -2120,9 -2460,7 +2473,7 @@@ static int joycon_read_info(struct joyc
        struct joycon_input_report *report;
  
        req.subcmd_id = JC_SUBCMD_REQ_DEV_INFO;
-       mutex_lock(&ctlr->output_mutex);
        ret = joycon_send_subcmd(ctlr, &req, 0, HZ);
-       mutex_unlock(&ctlr->output_mutex);
        if (ret) {
                hid_err(ctlr->hdev, "Failed to get joycon info; ret=%d\n", ret);
                return ret;
                return -ENOMEM;
        hid_info(ctlr->hdev, "controller MAC = %s\n", ctlr->mac_addr_str);
  
-       /* Retrieve the type so we can distinguish for charging grip */
+       /*
+        * Retrieve the type so we can distinguish the controller type
+        * Unfortantly the hdev->product can't always be used due to a ?bug?
+        * with the NSO Genesis controller. Over USB, it will report the
+        * PID as 0x201E, but over bluetooth it will report the PID as 0x2017
+        * which is the same as the NSO SNES controller. This is different from
+        * the rest of the controllers which will report the same PID over USB
+        * and bluetooth.
+        */
        ctlr->ctlr_type = report->subcmd_reply.data[2];
+       hid_dbg(ctlr->hdev, "controller type = 0x%02X\n", ctlr->ctlr_type);
  
        return 0;
  }
@@@ -2158,8 -2505,7 +2518,7 @@@ static int joycon_init(struct hid_devic
  
        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 (joycon_using_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);
                goto out_unlock;
        }
  
-       /* get controller calibration data, and parse it */
-       ret = joycon_request_calibration(ctlr);
+       /* needed to retrieve the controller type */
+       ret = joycon_read_info(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");
+               hid_err(hdev, "Failed to retrieve controller info; ret=%d\n",
+                       ret);
+               goto out_unlock;
        }
  
-       /* 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");
+       if (joycon_has_joysticks(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");
+               }
+       }
+       if (joycon_has_imu(ctlr)) {
+               /* 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 the IMU */
+               ret = joycon_enable_imu(ctlr);
+               if (ret) {
+                       hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
+                       goto out_unlock;
+               }
        }
  
        /* Set the reporting mode to 0x30, which is the full report mode */
                goto out_unlock;
        }
  
-       /* Enable rumble */
-       ret = joycon_enable_rumble(ctlr);
-       if (ret) {
-               hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
-               goto out_unlock;
-       }
-       /* 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 (joycon_has_rumble(ctlr)) {
+               /* Enable rumble */
+               ret = joycon_enable_rumble(ctlr);
+               if (ret) {
+                       hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
+                       goto out_unlock;
+               }
        }
  
  out_unlock:
@@@ -2367,13 -2727,6 +2740,6 @@@ static int nintendo_hid_probe(struct hi
                goto err_close;
        }
  
-       ret = joycon_read_info(ctlr);
-       if (ret) {
-               hid_err(hdev, "Failed to retrieve controller info; ret=%d\n",
-                       ret);
-               goto err_close;
-       }
        /* Initialize the leds */
        ret = joycon_leds_create(ctlr);
        if (ret) {
@@@ -2445,6 -2798,12 +2811,12 @@@ static int nintendo_hid_resume(struct h
  static const struct hid_device_id nintendo_hid_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
                         USB_DEVICE_ID_NINTENDO_PROCON) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+                        USB_DEVICE_ID_NINTENDO_SNESCON) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+                        USB_DEVICE_ID_NINTENDO_GENCON) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+                        USB_DEVICE_ID_NINTENDO_N64CON) },
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
                         USB_DEVICE_ID_NINTENDO_PROCON) },
        { HID_USB_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_SNESCON) },
+       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+                        USB_DEVICE_ID_NINTENDO_GENCON) },
+       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+                        USB_DEVICE_ID_NINTENDO_N64CON) },
        { }
  };
  MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
@@@ -2471,6 -2836,7 +2849,7 @@@ static struct hid_driver nintendo_hid_d
  module_hid_driver(nintendo_hid_driver);
  
  MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
+ MODULE_AUTHOR("Emily Strickland <linux@emily.st>");
  MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>");
  MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");