mbox series

[v12,00/14] HID: nintendo

Message ID 20200823044157.339677-1-djogorchock@gmail.com
Headers show
Series HID: nintendo | expand

Message

Daniel Ogorchock Aug. 23, 2020, 4:41 a.m. UTC
I've included round 2 for the IMU support patch. The documentation is
improved and the precision increased for the gyroscope values reported
to userspace.

There is still the important question of how to deal with userspace
implementing custom drivers using hidraw (i.e. Steam). I am resistant to
having hid-nintendo completely yield when userspace uses hidraw, since
that would prevent other applications from receiving the events from the
controllers (like maybe someone configures a voip client to use one of
the triggers as push-to-talk).

In my personal testing with steam, I don't see much issue since I
introduced the patch to not send rumble subcommands when no effect is
playing. Steam/hid-nintendo seem to fight when hid-nintendo is sending
lots of subcommands (e.g. rumble, setting LEDs). Others have reported
though that hid-nintendo/Steam are still fighting even with that patch.

I guess there's not much that can be done though to guarantee a
userspace and kernel driver coexisting at the same time, since one of
them could completely reconfigure the controller's reporting mode, IMU
resolution, etc.

The two extremes seem to be either having the hid drivers yield to
userspace completely when hidraw is in use (e.g. hid-steam with its
virtual hidraw dev) or mask out exposing the hidraw device when a kernel
hid driver is being used. It feels wrong to have things in the current
state where the HIDRAW device is being exposed, but it's not actually
supported.

Version 12 changes:
  - Added support for reading user calibration from the controller's
    SPI flash (written when someone calibrates the controller on the
    Nintendo switch).
  - Added patch to prevent sending rumble subcommands when no effect
    is being played. This turned out to drastically improve bluetooth
    connection reliability.
  - Set the battery description to POWER_SUPPLY_TYPE_BATTERY (was
    missing in previous revisions due to oversight). This fixes problems
    with desktop environments not handling the controller batteries
    properly.
  - Reintroduced IMU patch with improvements to documentation, packet
    drop handling, and increased precision for gyro readings. Also
    now blacklists the IMU input dev from joydev like hid-sony.

Version 11 changes:
  - Removed IMU patch for now, since it has some issues to work out.
  - Fixed bug introduced in v10 which led to the joy-cons' S-triggers
    not being configured as an input.
  - Changed the pro controller's d-pad input from buttons to a hat to be
    more in line with other controller drivers.

Version 10 changes:
  - Removed duplicate reporting of one of the triggers that Billy noticed
  - The joy-cons now only report having the buttons they actually have
    (they used to register the input devices with the buttons of the
    other joy-con as well).
  - The input device is now created after the LEDs/power supply.
  - The removed state handling bool has been removed, instead opting to
    add a new controller state (removed).
  - Eliminated a 1 second delay when probing a USB controller.
  - Added support for the IMU. This mostly consisted of merging in some
    work provided by Carl. I'm not incredibly familiar with proper
    gyro/accelerometer handling in linux, so this might need some
    tweaking. Preliminary tests in evtest show the gyro/accel values
    being reported.
  - Added support for the joy-con USB charging grip.

Version 9 changes:
  - Fixed compiler errors on gcc versions older than 8.2
  - Set input device's uniq value to the controller's MAC address

Version 8 changes:
  - Corrected the handshaking protocol with USB pro controllers. A
    handshake now occurs both prior and after the baudrate set. This
    doesn't appear to have a noticeable difference, but it more
    accurately follows documentation found online.
  - Fixed potential race condition which could lead to a slightly longer
    delay sending subcommands in rare circumstances.
  - Moved the rumble worker to its own workqueue, since it can block.
    This prevents it from having a negative impact on the default kernel
    workqueue. It also prevents dropped subcommands due to something
    else blocking the kernel workqueue. The benefit is most obvious when
    using multiple controllers at once, since the controller subcommand
    timings are very picky.
  - Added a patch to set the most significant bit of the hid hw version.
    Roderick had mentioned needing to probably do this awhile ago, but I
    had forgotten about it until now. This is the same thing hid-sony
    does. It allows SDL2 to have different mappings for the hid-nintendo
    driver vs the default hid mappings.

Version 7 changes:
  - Changed name to hid-nintendo to fit modern naming conventions
  - Removed joycon_ctlr_destroy(), since it wasn't needed an could
    potentially invalidate a mutex while it could be in use on other
    threads
  - Implemented minor code improvements suggested by Silvan
  - The driver now waits to send subcommands until after receiving an
    input report. This significantly reduces dropped subcommands.
  - Reduced the number of error messages when disconnecting a
    controller.

Version 6 changes:
  - Improved subcommand sending reliabilty
  - Decreased rumble period to 50ms
  - Added rumble queue to avoid missing ff_effects if sent too quickly
  - Code cleanup and minor refactoring
  - Added default analog stick calibration

Version 5 changes:
  - Removed sysfs interface to control motor frequencies.
  - Improved rumble reliability by using subcommands to set it.
  - Changed mapping of the SL/SR triggers on the joy-cons to map to
    whichever triggers they lack (e.g. a left joycon's sl/sr map to
    TR and TR2). This allows userspace to distinguish between the
    normal and S triggers.
  - Minor refactors

Version 4 changes:
  - Added support for the Home button LED for the pro controller and
    right joy-con
  - Changed name from hid-switchcon to hid-joycon
  - Added rumble support
  - Removed ctlr->type and use hdev->product instead
  - Use POWER_SUPPLY_CAPACITY_LEVEL enum instead of manually translating
    to capacity percentages
  - Misc. minor refactors based on v3 feedback

Version 3 changes:
  - Added led_classdev support for the 4 player LEDs
  - Added power_supply support for the controller's battery
  - Made the controller number mutex static
  - Minor refactoring/style fixes based on Roderick's feedback from v2

Version 2 changes:
  - Switched to using a synchronous method for configuring the
        controller.
  - Removed any pairing/orientation logic in the driver. Every
    controller now corresponds to its own input device.
  - Store controller button data as a single u32.
  - Style corrections

Daniel J. Ogorchock (14):
  HID: nintendo: add nintendo switch controller driver
  HID: nintendo: add player led support
  HID: nintendo: add power supply support
  HID: nintendo: add home led support
  HID: nintendo: add rumble support
  HID: nintendo: improve subcommand reliability
  HID: nintendo: send subcommands after receiving input report
  HID: nintendo: reduce device removal subcommand errors
  HID: nintendo: patch hw version for userspace HID mappings
  HID: nintendo: set controller uniq to MAC
  HID: nintendo: add support for charging grip
  HID: nintendo: add support for reading user calibration
  HID: nintendo: prevent needless queueing of the rumble worker
  HID: nintendo: add IMU support

 MAINTAINERS                |    6 +
 drivers/hid/Kconfig        |   24 +
 drivers/hid/Makefile       |    1 +
 drivers/hid/hid-ids.h      |    4 +
 drivers/hid/hid-nintendo.c | 2240 ++++++++++++++++++++++++++++++++++++
 drivers/input/joydev.c     |   10 +
 6 files changed, 2285 insertions(+)
 create mode 100644 drivers/hid/hid-nintendo.c

Comments

Silvan Jegen Feb. 15, 2021, 2:15 p.m. UTC | #1
Hi Daniel

Just some small things (inline below) that I found when reviewing
the code.

"Daniel J. Ogorchock" <djogorchock@gmail.com> wrote:
> The hid-nintendo driver supports the Nintendo Switch Pro Controllers and

> the Joy-Cons. The Pro Controllers can be used over USB or Bluetooth.

> 

> The Joy-Cons each create their own, independent input devices, so it is

> up to userspace to combine them if desired.

> 

> Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>

> ---

>  MAINTAINERS                |   6 +

>  drivers/hid/Kconfig        |  11 +

>  drivers/hid/Makefile       |   1 +

>  drivers/hid/hid-ids.h      |   3 +

>  drivers/hid/hid-nintendo.c | 871 +++++++++++++++++++++++++++++++++++++

>  5 files changed, 892 insertions(+)

>  create mode 100644 drivers/hid/hid-nintendo.c

> 

> diff --git a/MAINTAINERS b/MAINTAINERS

> index 4e2698cc7e23..47e5a2a04665 100644

> --- a/MAINTAINERS

> +++ b/MAINTAINERS

> @@ -12121,6 +12121,12 @@ W:	http://www.netlab.is.tsukuba.ac.jp/~yokota/izumi/ninja/

>  F:	Documentation/scsi/NinjaSCSI.rst

>  F:	drivers/scsi/nsp32*

>  

> +NINTENDO HID DRIVER

> +M:	Daniel J. Ogorchock <djogorchock@gmail.com>

> +L:	linux-input@vger.kernel.org

> +S:	Maintained

> +F:	drivers/hid/hid-nintendo*

> +

>  NIOS2 ARCHITECTURE

>  M:	Ley Foon Tan <ley.foon.tan@intel.com>

>  S:	Maintained

> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig

> index 45e87dc59d4e..b5cd42ed3b04 100644

> --- a/drivers/hid/Kconfig

> +++ b/drivers/hid/Kconfig

> @@ -710,6 +710,17 @@ config HID_MULTITOUCH

>  	  To compile this driver as a module, choose M here: the

>  	  module will be called hid-multitouch.

>  

> +config HID_NINTENDO

> +	tristate "Nintendo Joy-Con and Pro Controller support"

> +	depends on HID

> +	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-nintendo.

> +

>  config HID_NTI

>  	tristate "NTI keyboard adapters"

>  	help

> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile

> index d8ea4b8c95af..d0cc21b38251 100644

> --- a/drivers/hid/Makefile

> +++ b/drivers/hid/Makefile

> @@ -76,6 +76,7 @@ obj-$(CONFIG_HID_MAYFLASH)	+= hid-mf.o

>  obj-$(CONFIG_HID_MICROSOFT)	+= hid-microsoft.o

>  obj-$(CONFIG_HID_MONTEREY)	+= hid-monterey.o

>  obj-$(CONFIG_HID_MULTITOUCH)	+= hid-multitouch.o

> +obj-$(CONFIG_HID_NINTENDO)	+= hid-nintendo.o

>  obj-$(CONFIG_HID_NTI)			+= hid-nti.o

>  obj-$(CONFIG_HID_NTRIG)		+= hid-ntrig.o

>  obj-$(CONFIG_HID_ORTEK)		+= hid-ortek.o

> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h

> index 6f370e020feb..605c4bdaeb85 100644

> --- a/drivers/hid/hid-ids.h

> +++ b/drivers/hid/hid-ids.h

> @@ -878,6 +878,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-nintendo.c b/drivers/hid/hid-nintendo.c

> new file mode 100644

> index 000000000000..7273ddf033e2

> --- /dev/null

> +++ b/drivers/hid/hid-nintendo.c

> @@ -0,0 +1,871 @@

> +// SPDX-License-Identifier: GPL-2.0+

> +/*

> + * HID driver for Nintendo Switch Joy-Cons and Pro Controllers

> + *

> + * Copyright (c) 2019 Daniel J. Ogorchock <djogorchock@gmail.com>

> + *

> + * 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

> + *   hid-logitech-hidpp driver

> + *

> + * This driver supports the Nintendo Switch Joy-Cons and Pro Controllers. The

> + * Pro Controllers can either be used over USB or Bluetooth.

> + *

> + * 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>


This include doesn't seem to be used and can be removed.


> +#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 */

> +static const u8 JC_OUTPUT_RUMBLE_AND_SUBCMD	= 0x01;

> +static const u8 JC_OUTPUT_FW_UPDATE_PKT		= 0x03;

> +static const u8 JC_OUTPUT_RUMBLE_ONLY		= 0x10;

> +static const u8 JC_OUTPUT_MCU_DATA		= 0x11;

> +static const u8 JC_OUTPUT_USB_CMD		= 0x80;

> +

> +/* Subcommand IDs */

> +static const u8 JC_SUBCMD_STATE			/*= 0x00*/;

> +static const u8 JC_SUBCMD_MANUAL_BT_PAIRING	= 0x01;

> +static const u8 JC_SUBCMD_REQ_DEV_INFO		= 0x02;

> +static const u8 JC_SUBCMD_SET_REPORT_MODE	= 0x03;

> +static const u8 JC_SUBCMD_TRIGGERS_ELAPSED	= 0x04;

> +static const u8 JC_SUBCMD_GET_PAGE_LIST_STATE	= 0x05;

> +static const u8 JC_SUBCMD_SET_HCI_STATE		= 0x06;

> +static const u8 JC_SUBCMD_RESET_PAIRING_INFO	= 0x07;

> +static const u8 JC_SUBCMD_LOW_POWER_MODE	= 0x08;

> +static const u8 JC_SUBCMD_SPI_FLASH_READ	= 0x10;

> +static const u8 JC_SUBCMD_SPI_FLASH_WRITE	= 0x11;

> +static const u8 JC_SUBCMD_RESET_MCU		= 0x20;

> +static const u8 JC_SUBCMD_SET_MCU_CONFIG	= 0x21;

> +static const u8 JC_SUBCMD_SET_MCU_STATE		= 0x22;

> +static const u8 JC_SUBCMD_SET_PLAYER_LIGHTS	= 0x30;

> +static const u8 JC_SUBCMD_GET_PLAYER_LIGHTS	= 0x31;

> +static const u8 JC_SUBCMD_SET_HOME_LIGHT	= 0x38;

> +static const u8 JC_SUBCMD_ENABLE_IMU		= 0x40;

> +static const u8 JC_SUBCMD_SET_IMU_SENSITIVITY	= 0x41;

> +static const u8 JC_SUBCMD_WRITE_IMU_REG		= 0x42;

> +static const u8 JC_SUBCMD_READ_IMU_REG		= 0x43;

> +static const u8 JC_SUBCMD_ENABLE_VIBRATION	= 0x48;

> +static const u8 JC_SUBCMD_GET_REGULATED_VOLTAGE	= 0x50;

> +

> +/* Input Reports */

> +static const u8 JC_INPUT_BUTTON_EVENT		= 0x3F;

> +static const u8 JC_INPUT_SUBCMD_REPLY		= 0x21;

> +static const u8 JC_INPUT_IMU_DATA		= 0x30;

> +static const u8 JC_INPUT_MCU_DATA		= 0x31;

> +static const u8 JC_INPUT_USB_RESPONSE		= 0x81;

> +

> +/* Feature Reports */

> +static const u8 JC_FEATURE_LAST_SUBCMD		= 0x02;

> +static const u8 JC_FEATURE_OTA_FW_UPGRADE	= 0x70;

> +static const u8 JC_FEATURE_SETUP_MEM_READ	= 0x71;

> +static const u8 JC_FEATURE_MEM_READ		= 0x72;

> +static const u8 JC_FEATURE_ERASE_MEM_SECTOR	= 0x73;

> +static const u8 JC_FEATURE_MEM_WRITE		= 0x74;

> +static const u8 JC_FEATURE_LAUNCH		= 0x75;

> +

> +/* USB Commands */

> +static const u8 JC_USB_CMD_CONN_STATUS		= 0x01;

> +static const u8 JC_USB_CMD_HANDSHAKE		= 0x02;

> +static const u8 JC_USB_CMD_BAUDRATE_3M		= 0x03;

> +static const u8 JC_USB_CMD_NO_TIMEOUT		= 0x04;

> +static const u8 JC_USB_CMD_EN_TIMEOUT		= 0x05;

> +static const u8 JC_USB_RESET			= 0x06;

> +static const u8 JC_USB_PRE_HANDSHAKE		= 0x91;

> +static const u8 JC_USB_SEND_UART		= 0x92;

> +

> +/* SPI storage addresses of factory calibration data */

> +static const u16 JC_CAL_DATA_START		= 0x603d;

> +static const u16 JC_CAL_DATA_END		= 0x604e;

> +#define JC_CAL_DATA_SIZE	(JC_CAL_DATA_END - JC_CAL_DATA_START + 1)

> +

> +

> +/* The raw analog joystick values will be mapped in terms of this magnitude */

> +static const u16 JC_MAX_STICK_MAG		= 32767;

> +static const u16 JC_STICK_FUZZ			= 250;

> +static const u16 JC_STICK_FLAT			= 500;

> +

> +/* Hat values for pro controller's d-pad */

> +static const u16 JC_MAX_DPAD_MAG		= 1;

> +static const u16 JC_DPAD_FUZZ			/*= 0*/;

> +static const u16 JC_DPAD_FLAT			/*= 0*/;

> +

> +/* States for controller state machine */

> +enum joycon_ctlr_state {

> +	JOYCON_CTLR_STATE_INIT,

> +	JOYCON_CTLR_STATE_READ,

> +};

> +

> +struct joycon_stick_cal {

> +	s32 max;

> +	s32 min;

> +	s32 center;

> +};

> +

> +/*

> + * 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);

> +

> +enum joycon_msg_type {

> +	JOYCON_MSG_TYPE_NONE,

> +	JOYCON_MSG_TYPE_USB,

> +	JOYCON_MSG_TYPE_SUBCMD,

> +};

> +

> +struct joycon_subcmd_request {

> +	u8 output_id; /* must be 0x01 for subcommand, 0x10 for rumble only */

> +	u8 packet_num; /* incremented every send */

> +	u8 rumble_data[8];

> +	u8 subcmd_id;

> +	u8 data[0]; /* length depends on the subcommand */


Apparently zero length elements are deprecated:
Documentation/process/deprecated.rst

What is recommended to use instead is.
+       u8 data[]; /* length depends on the subcommand */


> +} __packed;

> +

> +struct joycon_subcmd_reply {

> +	u8 ack; /* MSB 1 for ACK, 0 for NACK */

> +	u8 id; /* id of requested subcmd */

> +	u8 data[0]; /* will be at most 35 bytes */


See above.


> +} __packed;

> +

> +struct joycon_input_report {

> +	u8 id;

> +	u8 timer;

> +	u8 bat_con; /* battery and connection info */

> +	u8 button_status[3];

> +	u8 left_stick[3];

> +	u8 right_stick[3];

> +	u8 vibrator_report;

> +

> +	/*

> +	 * If support for firmware updates, gyroscope data, and/or NFC/IR

> +	 * are added in the future, this can be swapped for a union.

> +	 */

> +	struct joycon_subcmd_reply reply;

> +} __packed;

> +

> +#define JC_MAX_RESP_SIZE	(sizeof(struct joycon_input_report) + 35)

> +

> +/* Each physical controller is associated with a joycon_ctlr struct */

> +struct joycon_ctlr {

> +	struct hid_device *hdev;

> +	struct input_dev *input;

> +	enum joycon_ctlr_state ctlr_state;

> +

> +	/* The following members are used for synchronous sends/receives */

> +	enum joycon_msg_type msg_type;

> +	u8 subcmd_num;

> +	struct mutex output_mutex;

> +	u8 input_buf[JC_MAX_RESP_SIZE];

> +	wait_queue_head_t wait;

> +	bool received_resp;

> +	u8 usb_ack_match;

> +	u8 subcmd_ack_match;

> +

> +	/* factory calibration data */

> +	struct joycon_stick_cal left_stick_cal_x;

> +	struct joycon_stick_cal left_stick_cal_y;

> +	struct joycon_stick_cal right_stick_cal_x;

> +	struct joycon_stick_cal right_stick_cal_y;

> +

> +};

> +

> +static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)

> +{

> +	u8 *buf;

> +	int ret;

> +

> +	buf = kmemdup(data, len, GFP_KERNEL);

> +	if (!buf)

> +		return -ENOMEM;

> +	ret = hid_hw_output_report(hdev, buf, len);

> +	kfree(buf);

> +	if (ret < 0)

> +		hid_dbg(hdev, "Failed to send output report ret=%d\n", ret);

> +	return ret;

> +}

> +

> +static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len)

> +{

> +	int ret;

> +

> +	ret = __joycon_hid_send(ctlr->hdev, data, len);

> +	if (ret < 0) {

> +		memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE);

> +		return ret;

> +	}

> +

> +	if (!wait_event_timeout(ctlr->wait, ctlr->received_resp, HZ)) {

> +		hid_dbg(ctlr->hdev, "synchronous send/receive timed out\n");

> +		memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE);

> +		return -ETIMEDOUT;

> +	}

> +

> +	ctlr->received_resp = false;

> +	return 0;

> +}

> +

> +static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 cmd)

> +{

> +	int ret;

> +	u8 buf[2] = {JC_OUTPUT_USB_CMD};

> +

> +	buf[1] = cmd;

> +	ctlr->usb_ack_match = cmd;

> +	ctlr->msg_type = JOYCON_MSG_TYPE_USB;

> +	ret = joycon_hid_send_sync(ctlr, buf, sizeof(buf));

> +	if (ret)

> +		hid_dbg(ctlr->hdev, "send usb command failed; ret=%d\n", ret);

> +	return ret;

> +}

> +

> +static int joycon_send_subcmd(struct joycon_ctlr *ctlr,

> +			      struct joycon_subcmd_request *subcmd,

> +			      size_t data_len)

> +{

> +	int ret;

> +

> +	subcmd->output_id = JC_OUTPUT_RUMBLE_AND_SUBCMD;

> +	subcmd->packet_num = ctlr->subcmd_num;

> +	if (++ctlr->subcmd_num > 0xF)

> +		ctlr->subcmd_num = 0;

> +	ctlr->subcmd_ack_match = subcmd->subcmd_id;

> +	ctlr->msg_type = JOYCON_MSG_TYPE_SUBCMD;

> +

> +	ret = joycon_hid_send_sync(ctlr, (u8 *)subcmd,

> +				   sizeof(*subcmd) + data_len);

> +	if (ret < 0)

> +		hid_dbg(ctlr->hdev, "send subcommand failed; ret=%d\n", ret);

> +	else

> +		ret = 0;

> +	return ret;

> +}

> +

> +/* Supply nibbles for flash and on. Ones correspond to active */

> +static int joycon_set_player_leds(struct joycon_ctlr *ctlr, u8 flash, u8 on)

> +{

> +	struct joycon_subcmd_request *req;

> +	u8 buffer[sizeof(*req) + 1] = { 0 };

> +

> +	req = (struct joycon_subcmd_request *)buffer;

> +	req->subcmd_id = JC_SUBCMD_SET_PLAYER_LIGHTS;

> +	req->data[0] = (flash << 4) | on;

> +

> +	hid_dbg(ctlr->hdev, "setting player leds\n");

> +	return joycon_send_subcmd(ctlr, req, 1);

> +}

> +

> +static const u16 DFLT_STICK_CAL_CEN = 2000;

> +static const u16 DFLT_STICK_CAL_MAX = 3500;

> +static const u16 DFLT_STICK_CAL_MIN = 500;

> +static int joycon_request_calibration(struct joycon_ctlr *ctlr)

> +{

> +	struct joycon_subcmd_request *req;

> +	u8 buffer[sizeof(*req) + 5] = { 0 };

> +	struct joycon_input_report *report;

> +	struct joycon_stick_cal *cal_x;

> +	struct joycon_stick_cal *cal_y;

> +	s32 x_max_above;

> +	s32 x_min_below;

> +	s32 y_max_above;

> +	s32 y_min_below;

> +	u8 *data;

> +	u8 *raw_cal;

> +	int ret;

> +

> +	req = (struct joycon_subcmd_request *)buffer;

> +	req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;

> +	data = req->data;

> +	data[0] = 0xFF & JC_CAL_DATA_START;

> +	data[1] = 0xFF & (JC_CAL_DATA_START >> 8);

> +	data[2] = 0xFF & (JC_CAL_DATA_START >> 16);

> +	data[3] = 0xFF & (JC_CAL_DATA_START >> 24);

> +	data[4] = JC_CAL_DATA_SIZE;

> +

> +	hid_dbg(ctlr->hdev, "requesting cal data\n");

> +	ret = joycon_send_subcmd(ctlr, req, 5);

> +	if (ret) {

> +		hid_warn(ctlr->hdev,

> +			 "Failed to read stick cal, using defaults; ret=%d\n",

> +			 ret);

> +

> +		ctlr->left_stick_cal_x.center = DFLT_STICK_CAL_CEN;

> +		ctlr->left_stick_cal_x.max = DFLT_STICK_CAL_MAX;

> +		ctlr->left_stick_cal_x.min = DFLT_STICK_CAL_MIN;

> +

> +		ctlr->left_stick_cal_y.center = DFLT_STICK_CAL_CEN;

> +		ctlr->left_stick_cal_y.max = DFLT_STICK_CAL_MAX;

> +		ctlr->left_stick_cal_y.min = DFLT_STICK_CAL_MIN;

> +

> +		ctlr->right_stick_cal_x.center = DFLT_STICK_CAL_CEN;

> +		ctlr->right_stick_cal_x.max = DFLT_STICK_CAL_MAX;

> +		ctlr->right_stick_cal_x.min = DFLT_STICK_CAL_MIN;

> +

> +		ctlr->right_stick_cal_y.center = DFLT_STICK_CAL_CEN;

> +		ctlr->right_stick_cal_y.max = DFLT_STICK_CAL_MAX;

> +		ctlr->right_stick_cal_y.min = DFLT_STICK_CAL_MIN;

> +

> +		return ret;

> +	}

> +

> +	report = (struct joycon_input_report *)ctlr->input_buf;

> +	raw_cal = &report->reply.data[5];

> +

> +	/* left stick calibration parsing */

> +	cal_x = &ctlr->left_stick_cal_x;

> +	cal_y = &ctlr->left_stick_cal_y;

> +

> +	x_max_above = hid_field_extract(ctlr->hdev, (raw_cal + 0), 0, 12);

> +	y_max_above = hid_field_extract(ctlr->hdev, (raw_cal + 1), 4, 12);

> +	cal_x->center = hid_field_extract(ctlr->hdev, (raw_cal + 3), 0, 12);

> +	cal_y->center = hid_field_extract(ctlr->hdev, (raw_cal + 4), 4, 12);

> +	x_min_below = hid_field_extract(ctlr->hdev, (raw_cal + 6), 0, 12);

> +	y_min_below = hid_field_extract(ctlr->hdev, (raw_cal + 7), 4, 12);

> +	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;

> +

> +	/* right stick calibration parsing */

> +	raw_cal += 9;

> +	cal_x = &ctlr->right_stick_cal_x;

> +	cal_y = &ctlr->right_stick_cal_y;

> +

> +	cal_x->center = hid_field_extract(ctlr->hdev, (raw_cal + 0), 0, 12);

> +	cal_y->center = hid_field_extract(ctlr->hdev, (raw_cal + 1), 4, 12);

> +	x_min_below = hid_field_extract(ctlr->hdev, (raw_cal + 3), 0, 12);

> +	y_min_below = hid_field_extract(ctlr->hdev, (raw_cal + 4), 4, 12);

> +	x_max_above = hid_field_extract(ctlr->hdev, (raw_cal + 6), 0, 12);

> +	y_max_above = hid_field_extract(ctlr->hdev, (raw_cal + 7), 4, 12);

> +	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;

> +

> +	hid_dbg(ctlr->hdev, "calibration:\n"

> +			    "l_x_c=%d l_x_max=%d l_x_min=%d\n"

> +			    "l_y_c=%d l_y_max=%d l_y_min=%d\n"

> +			    "r_x_c=%d r_x_max=%d r_x_min=%d\n"

> +			    "r_y_c=%d r_y_max=%d r_y_min=%d\n",

> +			    ctlr->left_stick_cal_x.center,

> +			    ctlr->left_stick_cal_x.max,

> +			    ctlr->left_stick_cal_x.min,

> +			    ctlr->left_stick_cal_y.center,

> +			    ctlr->left_stick_cal_y.max,

> +			    ctlr->left_stick_cal_y.min,

> +			    ctlr->right_stick_cal_x.center,

> +			    ctlr->right_stick_cal_x.max,

> +			    ctlr->right_stick_cal_x.min,

> +			    ctlr->right_stick_cal_y.center,

> +			    ctlr->right_stick_cal_y.max,

> +			    ctlr->right_stick_cal_y.min);

> +

> +	return 0;

> +}

> +

> +static int joycon_set_report_mode(struct joycon_ctlr *ctlr)

> +{

> +	struct joycon_subcmd_request *req;

> +	u8 buffer[sizeof(*req) + 1] = { 0 };

> +

> +	req = (struct joycon_subcmd_request *)buffer;

> +	req->subcmd_id = JC_SUBCMD_SET_REPORT_MODE;

> +	req->data[0] = 0x30; /* standard, full report mode */

> +

> +	hid_dbg(ctlr->hdev, "setting controller report mode\n");

> +	return joycon_send_subcmd(ctlr, req, 1);

> +}

> +

> +static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)

> +{

> +	s32 center = cal->center;

> +	s32 min = cal->min;

> +	s32 max = cal->max;

> +	s32 new_val;

> +

> +	if (val > center) {

> +		new_val = (val - center) * JC_MAX_STICK_MAG;

> +		new_val /= (max - center);

> +	} else {

> +		new_val = (center - val) * -JC_MAX_STICK_MAG;

> +		new_val /= (center - min);

> +	}

> +	new_val = clamp(new_val, (s32)-JC_MAX_STICK_MAG, (s32)JC_MAX_STICK_MAG);

> +	return new_val;

> +}

> +

> +static void joycon_parse_report(struct joycon_ctlr *ctlr,

> +				struct joycon_input_report *rep)

> +{

> +	struct input_dev *dev = ctlr->input;

> +	u32 btns;

> +	u32 id = ctlr->hdev->product;

> +

> +	btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24);

> +

> +	if (id != USB_DEVICE_ID_NINTENDO_JOYCONR) {

> +		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 (id != USB_DEVICE_ID_NINTENDO_PROCON) {

> +			/* 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 (id != USB_DEVICE_ID_NINTENDO_JOYCONL) {

> +		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 (id != USB_DEVICE_ID_NINTENDO_PROCON) {

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

> +	}

> +

> +	input_sync(dev);

> +}

> +

> +

> +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,

> +};

> +

> +static DEFINE_MUTEX(joycon_input_num_mutex);

> +static int joycon_input_create(struct joycon_ctlr *ctlr)

> +{

> +	struct hid_device *hdev;

> +	static int input_num = 1;

> +	const char *name;

> +	int ret;

> +	int i;

> +

> +	hdev = ctlr->hdev;

> +

> +	switch (hdev->product) {

> +	case USB_DEVICE_ID_NINTENDO_PROCON:

> +		name = "Nintendo Switch Pro Controller";

> +		break;

> +	case USB_DEVICE_ID_NINTENDO_JOYCONL:

> +		name = "Nintendo Switch Left Joy-Con";

> +		break;

> +	case USB_DEVICE_ID_NINTENDO_JOYCONR:

> +		name = "Nintendo Switch Right Joy-Con";

> +		break;

> +	default: /* Should be impossible */

> +		hid_err(hdev, "Invalid hid product\n");

> +		return -EINVAL;

> +	}

> +

> +	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->name = name;

> +	input_set_drvdata(ctlr->input, ctlr);

> +

> +

> +	/* set up sticks and buttons */

> +	if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONR) {

> +		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 (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) {

> +		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]);

> +	}

> +

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

> +	}

> +

> +	ret = input_register_device(ctlr->input);

> +	if (ret)

> +		return ret;

> +

> +	/* Set the default controller player leds based on controller number */

> +	mutex_lock(&joycon_input_num_mutex);

> +	mutex_lock(&ctlr->output_mutex);

> +	ret = joycon_set_player_leds(ctlr, 0, 0xF >> (4 - input_num));

> +	if (ret)

> +		hid_warn(ctlr->hdev, "Failed to set leds; ret=%d\n", ret);

> +	mutex_unlock(&ctlr->output_mutex);

> +	if (++input_num > 4)

> +		input_num = 1;

> +	mutex_unlock(&joycon_input_num_mutex);

> +

> +	return 0;

> +}

> +

> +/* Common handler for parsing inputs */

> +static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data,

> +							      int size)

> +{

> +	int ret = 0;

> +

> +	if (data[0] == JC_INPUT_SUBCMD_REPLY || data[0] == JC_INPUT_IMU_DATA ||

> +	    data[0] == JC_INPUT_MCU_DATA) {

> +		if (size >= 12) /* make sure it contains the input report */

> +			joycon_parse_report(ctlr,

> +					    (struct joycon_input_report *)data);

> +	}

> +

> +	return ret;


We could just return 0 here directly (a bot on the mailing list pointed
this out as well).


> +}

> +

> +static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,

> +							      int size)

> +{

> +	int ret = 0;

> +	bool match = false;

> +	struct joycon_input_report *report;

> +

> +	if (unlikely(mutex_is_locked(&ctlr->output_mutex)) &&

> +	    ctlr->msg_type != JOYCON_MSG_TYPE_NONE) {

> +		switch (ctlr->msg_type) {

> +		case JOYCON_MSG_TYPE_USB:

> +			if (size < 2)

> +				break;

> +			if (data[0] == JC_INPUT_USB_RESPONSE &&

> +			    data[1] == ctlr->usb_ack_match)

> +				match = true;

> +			break;

> +		case JOYCON_MSG_TYPE_SUBCMD:

> +			if (size < sizeof(struct joycon_input_report) ||

> +			    data[0] != JC_INPUT_SUBCMD_REPLY)

> +				break;

> +			report = (struct joycon_input_report *)data;

> +			if (report->reply.id == ctlr->subcmd_ack_match)

> +				match = true;

> +			break;

> +		default:

> +			break;


This break is not necessary but maybe this is a conscious choice since
there is a break in a few "default:" cases in the code?

Many thanks for the driver! Hope it will land in mainline in the near
future!


Cheers,

Silvan
Silvan Jegen Feb. 15, 2021, 2:33 p.m. UTC | #2
Hi Daniel

One comment below.

"Daniel J. Ogorchock" <djogorchock@gmail.com> wrote:
> This patch adds support for controller rumble.

> 

> The ff_effect weak magnitude is associated with the pro controller's

> right motor (or with a right joy-con). The strong magnitude is

> associated with the pro's left motor (or a left joy-con).

> 

> The rumble data is sent periodically (currently configured for every 50

> milliseconds). If the controller receives no rumble data for too long a

> time period, it will stop vibrating. The data is also sent every time

> joycon_set_rumble is called to avoid latency of up to 50ms.

> 

> Because the rumble subcommands are sent in a deferred workqueue (they

> can't be sent in the play_effect function due to the hid send sleeping),

> the effects are queued. This ensures that no rumble effect is missed due

> to them arriving in too quick of succession.

> 

> Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>

> ---

>  drivers/hid/Kconfig        |  10 ++

>  drivers/hid/hid-nintendo.c | 349 ++++++++++++++++++++++++++++++++++++-

>  2 files changed, 356 insertions(+), 3 deletions(-)

> 

> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig

> index c05bfb6ac577..2ed3ea91708a 100644

> --- a/drivers/hid/Kconfig

> +++ b/drivers/hid/Kconfig

> @@ -724,6 +724,16 @@ config HID_NINTENDO

>  	To compile this driver as a module, choose M here: the

>  	module will be called hid-nintendo.

>  

> +config NINTENDO_FF

> +	bool "Nintendo Switch controller force feedback support"

> +	depends on HID_NINTENDO

> +	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.

> +

>  config HID_NTI

>  	tristate "NTI keyboard adapters"

>  	help

> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c

> index e5afe360c676..21fa85802894 100644

> --- a/drivers/hid/hid-nintendo.c

> +++ b/drivers/hid/hid-nintendo.c

> @@ -9,6 +9,7 @@

>   *   https://gitlab.com/pjranki/joycon-linux-kernel (Peter Rankin)

>   *   https://github.com/FrotBot/SwitchProConLinuxUSB

>   *   https://github.com/MTCKC/ProconXInput

> + *   https://github.com/Davidobot/BetterJoyForCemu

>   *   hid-wiimote kernel hid driver

>   *   hid-logitech-hidpp driver

>   *   hid-sony driver

> @@ -26,6 +27,7 @@

>  #include <linux/device.h>

>  #include <linux/hid.h>

>  #include <linux/input.h>

> +#include <linux/jiffies.h>

>  #include <linux/leds.h>

>  #include <linux/module.h>

>  #include <linux/power_supply.h>

> @@ -110,6 +112,120 @@ static const u16 JC_MAX_DPAD_MAG		= 1;

>  static const u16 JC_DPAD_FUZZ			/*= 0*/;

>  static const u16 JC_DPAD_FLAT			/*= 0*/;

>  

> +/* frequency/amplitude tables for rumble */

> +struct joycon_rumble_freq_data {

> +	u16 high;

> +	u8 low;

> +	u16 freq; /* Hz*/

> +};

> +

> +struct joycon_rumble_amp_data {

> +	u8 high;

> +	u16 low;

> +	u16 amp;

> +};

> +

> +/*

> + * These tables are from

> + * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md

> + */

> +static const struct joycon_rumble_freq_data joycon_rumble_frequencies[] = {

> +	/* high, low, freq */

> +	{ 0x0000, 0x01,   41 }, { 0x0000, 0x02,   42 }, { 0x0000, 0x03,   43 },

> +	{ 0x0000, 0x04,   44 }, { 0x0000, 0x05,   45 }, { 0x0000, 0x06,   46 },

> +	{ 0x0000, 0x07,   47 }, { 0x0000, 0x08,   48 }, { 0x0000, 0x09,   49 },

> +	{ 0x0000, 0x0A,   50 }, { 0x0000, 0x0B,   51 }, { 0x0000, 0x0C,   52 },

> +	{ 0x0000, 0x0D,   53 }, { 0x0000, 0x0E,   54 }, { 0x0000, 0x0F,   55 },

> +	{ 0x0000, 0x10,   57 }, { 0x0000, 0x11,   58 }, { 0x0000, 0x12,   59 },

> +	{ 0x0000, 0x13,   60 }, { 0x0000, 0x14,   62 }, { 0x0000, 0x15,   63 },

> +	{ 0x0000, 0x16,   64 }, { 0x0000, 0x17,   66 }, { 0x0000, 0x18,   67 },

> +	{ 0x0000, 0x19,   69 }, { 0x0000, 0x1A,   70 }, { 0x0000, 0x1B,   72 },

> +	{ 0x0000, 0x1C,   73 }, { 0x0000, 0x1D,   75 }, { 0x0000, 0x1e,   77 },

> +	{ 0x0000, 0x1f,   78 }, { 0x0000, 0x20,   80 }, { 0x0400, 0x21,   82 },

> +	{ 0x0800, 0x22,   84 }, { 0x0c00, 0x23,   85 }, { 0x1000, 0x24,   87 },

> +	{ 0x1400, 0x25,   89 }, { 0x1800, 0x26,   91 }, { 0x1c00, 0x27,   93 },

> +	{ 0x2000, 0x28,   95 }, { 0x2400, 0x29,   97 }, { 0x2800, 0x2a,   99 },

> +	{ 0x2c00, 0x2b,  102 }, { 0x3000, 0x2c,  104 }, { 0x3400, 0x2d,  106 },

> +	{ 0x3800, 0x2e,  108 }, { 0x3c00, 0x2f,  111 }, { 0x4000, 0x30,  113 },

> +	{ 0x4400, 0x31,  116 }, { 0x4800, 0x32,  118 }, { 0x4c00, 0x33,  121 },

> +	{ 0x5000, 0x34,  123 }, { 0x5400, 0x35,  126 }, { 0x5800, 0x36,  129 },

> +	{ 0x5c00, 0x37,  132 }, { 0x6000, 0x38,  135 }, { 0x6400, 0x39,  137 },

> +	{ 0x6800, 0x3a,  141 }, { 0x6c00, 0x3b,  144 }, { 0x7000, 0x3c,  147 },

> +	{ 0x7400, 0x3d,  150 }, { 0x7800, 0x3e,  153 }, { 0x7c00, 0x3f,  157 },

> +	{ 0x8000, 0x40,  160 }, { 0x8400, 0x41,  164 }, { 0x8800, 0x42,  167 },

> +	{ 0x8c00, 0x43,  171 }, { 0x9000, 0x44,  174 }, { 0x9400, 0x45,  178 },

> +	{ 0x9800, 0x46,  182 }, { 0x9c00, 0x47,  186 }, { 0xa000, 0x48,  190 },

> +	{ 0xa400, 0x49,  194 }, { 0xa800, 0x4a,  199 }, { 0xac00, 0x4b,  203 },

> +	{ 0xb000, 0x4c,  207 }, { 0xb400, 0x4d,  212 }, { 0xb800, 0x4e,  217 },

> +	{ 0xbc00, 0x4f,  221 }, { 0xc000, 0x50,  226 }, { 0xc400, 0x51,  231 },

> +	{ 0xc800, 0x52,  236 }, { 0xcc00, 0x53,  241 }, { 0xd000, 0x54,  247 },

> +	{ 0xd400, 0x55,  252 }, { 0xd800, 0x56,  258 }, { 0xdc00, 0x57,  263 },

> +	{ 0xe000, 0x58,  269 }, { 0xe400, 0x59,  275 }, { 0xe800, 0x5a,  281 },

> +	{ 0xec00, 0x5b,  287 }, { 0xf000, 0x5c,  293 }, { 0xf400, 0x5d,  300 },

> +	{ 0xf800, 0x5e,  306 }, { 0xfc00, 0x5f,  313 }, { 0x0001, 0x60,  320 },

> +	{ 0x0401, 0x61,  327 }, { 0x0801, 0x62,  334 }, { 0x0c01, 0x63,  341 },

> +	{ 0x1001, 0x64,  349 }, { 0x1401, 0x65,  357 }, { 0x1801, 0x66,  364 },

> +	{ 0x1c01, 0x67,  372 }, { 0x2001, 0x68,  381 }, { 0x2401, 0x69,  389 },

> +	{ 0x2801, 0x6a,  397 }, { 0x2c01, 0x6b,  406 }, { 0x3001, 0x6c,  415 },

> +	{ 0x3401, 0x6d,  424 }, { 0x3801, 0x6e,  433 }, { 0x3c01, 0x6f,  443 },

> +	{ 0x4001, 0x70,  453 }, { 0x4401, 0x71,  462 }, { 0x4801, 0x72,  473 },

> +	{ 0x4c01, 0x73,  483 }, { 0x5001, 0x74,  494 }, { 0x5401, 0x75,  504 },

> +	{ 0x5801, 0x76,  515 }, { 0x5c01, 0x77,  527 }, { 0x6001, 0x78,  538 },

> +	{ 0x6401, 0x79,  550 }, { 0x6801, 0x7a,  562 }, { 0x6c01, 0x7b,  574 },

> +	{ 0x7001, 0x7c,  587 }, { 0x7401, 0x7d,  600 }, { 0x7801, 0x7e,  613 },

> +	{ 0x7c01, 0x7f,  626 }, { 0x8001, 0x00,  640 }, { 0x8401, 0x00,  654 },

> +	{ 0x8801, 0x00,  668 }, { 0x8c01, 0x00,  683 }, { 0x9001, 0x00,  698 },

> +	{ 0x9401, 0x00,  713 }, { 0x9801, 0x00,  729 }, { 0x9c01, 0x00,  745 },

> +	{ 0xa001, 0x00,  761 }, { 0xa401, 0x00,  778 }, { 0xa801, 0x00,  795 },

> +	{ 0xac01, 0x00,  812 }, { 0xb001, 0x00,  830 }, { 0xb401, 0x00,  848 },

> +	{ 0xb801, 0x00,  867 }, { 0xbc01, 0x00,  886 }, { 0xc001, 0x00,  905 },

> +	{ 0xc401, 0x00,  925 }, { 0xc801, 0x00,  945 }, { 0xcc01, 0x00,  966 },

> +	{ 0xd001, 0x00,  987 }, { 0xd401, 0x00, 1009 }, { 0xd801, 0x00, 1031 },

> +	{ 0xdc01, 0x00, 1053 }, { 0xe001, 0x00, 1076 }, { 0xe401, 0x00, 1100 },

> +	{ 0xe801, 0x00, 1124 }, { 0xec01, 0x00, 1149 }, { 0xf001, 0x00, 1174 },

> +	{ 0xf401, 0x00, 1199 }, { 0xf801, 0x00, 1226 }, { 0xfc01, 0x00, 1253 }

> +};

> +

> +#define joycon_max_rumble_amp	(1003)

> +static const struct joycon_rumble_amp_data joycon_rumble_amplitudes[] = {

> +	/* high, low, amp */

> +	{ 0x00, 0x0040,    0 },

> +	{ 0x02, 0x8040,   10 }, { 0x04, 0x0041,   12 }, { 0x06, 0x8041,   14 },

> +	{ 0x08, 0x0042,   17 }, { 0x0a, 0x8042,   20 }, { 0x0c, 0x0043,   24 },

> +	{ 0x0e, 0x8043,   28 }, { 0x10, 0x0044,   33 }, { 0x12, 0x8044,   40 },

> +	{ 0x14, 0x0045,   47 }, { 0x16, 0x8045,   56 }, { 0x18, 0x0046,   67 },

> +	{ 0x1a, 0x8046,   80 }, { 0x1c, 0x0047,   95 }, { 0x1e, 0x8047,  112 },

> +	{ 0x20, 0x0048,  117 }, { 0x22, 0x8048,  123 }, { 0x24, 0x0049,  128 },

> +	{ 0x26, 0x8049,  134 }, { 0x28, 0x004a,  140 }, { 0x2a, 0x804a,  146 },

> +	{ 0x2c, 0x004b,  152 }, { 0x2e, 0x804b,  159 }, { 0x30, 0x004c,  166 },

> +	{ 0x32, 0x804c,  173 }, { 0x34, 0x004d,  181 }, { 0x36, 0x804d,  189 },

> +	{ 0x38, 0x004e,  198 }, { 0x3a, 0x804e,  206 }, { 0x3c, 0x004f,  215 },

> +	{ 0x3e, 0x804f,  225 }, { 0x40, 0x0050,  230 }, { 0x42, 0x8050,  235 },

> +	{ 0x44, 0x0051,  240 }, { 0x46, 0x8051,  245 }, { 0x48, 0x0052,  251 },

> +	{ 0x4a, 0x8052,  256 }, { 0x4c, 0x0053,  262 }, { 0x4e, 0x8053,  268 },

> +	{ 0x50, 0x0054,  273 }, { 0x52, 0x8054,  279 }, { 0x54, 0x0055,  286 },

> +	{ 0x56, 0x8055,  292 }, { 0x58, 0x0056,  298 }, { 0x5a, 0x8056,  305 },

> +	{ 0x5c, 0x0057,  311 }, { 0x5e, 0x8057,  318 }, { 0x60, 0x0058,  325 },

> +	{ 0x62, 0x8058,  332 }, { 0x64, 0x0059,  340 }, { 0x66, 0x8059,  347 },

> +	{ 0x68, 0x005a,  355 }, { 0x6a, 0x805a,  362 }, { 0x6c, 0x005b,  370 },

> +	{ 0x6e, 0x805b,  378 }, { 0x70, 0x005c,  387 }, { 0x72, 0x805c,  395 },

> +	{ 0x74, 0x005d,  404 }, { 0x76, 0x805d,  413 }, { 0x78, 0x005e,  422 },

> +	{ 0x7a, 0x805e,  431 }, { 0x7c, 0x005f,  440 }, { 0x7e, 0x805f,  450 },

> +	{ 0x80, 0x0060,  460 }, { 0x82, 0x8060,  470 }, { 0x84, 0x0061,  480 },

> +	{ 0x86, 0x8061,  491 }, { 0x88, 0x0062,  501 }, { 0x8a, 0x8062,  512 },

> +	{ 0x8c, 0x0063,  524 }, { 0x8e, 0x8063,  535 }, { 0x90, 0x0064,  547 },

> +	{ 0x92, 0x8064,  559 }, { 0x94, 0x0065,  571 }, { 0x96, 0x8065,  584 },

> +	{ 0x98, 0x0066,  596 }, { 0x9a, 0x8066,  609 }, { 0x9c, 0x0067,  623 },

> +	{ 0x9e, 0x8067,  636 }, { 0xa0, 0x0068,  650 }, { 0xa2, 0x8068,  665 },

> +	{ 0xa4, 0x0069,  679 }, { 0xa6, 0x8069,  694 }, { 0xa8, 0x006a,  709 },

> +	{ 0xaa, 0x806a,  725 }, { 0xac, 0x006b,  741 }, { 0xae, 0x806b,  757 },

> +	{ 0xb0, 0x006c,  773 }, { 0xb2, 0x806c,  790 }, { 0xb4, 0x006d,  808 },

> +	{ 0xb6, 0x806d,  825 }, { 0xb8, 0x006e,  843 }, { 0xba, 0x806e,  862 },

> +	{ 0xbc, 0x006f,  881 }, { 0xbe, 0x806f,  900 }, { 0xc0, 0x0070,  920 },

> +	{ 0xc2, 0x8070,  940 }, { 0xc4, 0x0071,  960 }, { 0xc6, 0x8071,  981 },

> +	{ 0xc8, 0x0072, joycon_max_rumble_amp }

> +};

> +

>  /* States for controller state machine */

>  enum joycon_ctlr_state {

>  	JOYCON_CTLR_STATE_INIT,

> @@ -187,6 +303,12 @@ struct joycon_input_report {

>  

>  #define JC_MAX_RESP_SIZE	(sizeof(struct joycon_input_report) + 35)

>  #define JC_NUM_LEDS		4

> +#define JC_RUMBLE_DATA_SIZE	8

> +#define JC_RUMBLE_QUEUE_SIZE	8

> +

> +static const u16 JC_RUMBLE_DFLT_LOW_FREQ = 160;

> +static const u16 JC_RUMBLE_DFLT_HIGH_FREQ = 320;

> +static const u16 JC_RUMBLE_PERIOD_MS = 50;

>  

>  /* Each physical controller is associated with a joycon_ctlr struct */

>  struct joycon_ctlr {

> @@ -219,6 +341,18 @@ struct joycon_ctlr {

>  	u8 battery_capacity;

>  	bool battery_charging;

>  	bool host_powered;

> +

> +	/* rumble */

> +	u8 rumble_data[JC_RUMBLE_QUEUE_SIZE][JC_RUMBLE_DATA_SIZE];

> +	int rumble_queue_head;

> +	int rumble_queue_tail;

> +	struct workqueue_struct *rumble_queue;

> +	struct work_struct rumble_worker;

> +	unsigned int rumble_msecs;

> +	u16 rumble_ll_freq;

> +	u16 rumble_lh_freq;

> +	u16 rumble_rl_freq;

> +	u16 rumble_rh_freq;

>  };

>  

>  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)

> @@ -275,6 +409,12 @@ static int joycon_send_subcmd(struct joycon_ctlr *ctlr,

>  			      size_t data_len)

>  {

>  	int ret;

> +	unsigned long flags;

> +

> +	spin_lock_irqsave(&ctlr->lock, flags);

> +	memcpy(subcmd->rumble_data, ctlr->rumble_data[ctlr->rumble_queue_tail],

> +	       JC_RUMBLE_DATA_SIZE);

> +	spin_unlock_irqrestore(&ctlr->lock, flags);

>  

>  	subcmd->output_id = JC_OUTPUT_RUMBLE_AND_SUBCMD;

>  	subcmd->packet_num = ctlr->subcmd_num;

> @@ -427,6 +567,19 @@ static int joycon_set_report_mode(struct joycon_ctlr *ctlr)

>  	return joycon_send_subcmd(ctlr, req, 1);

>  }

>  

> +static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)


We only call this function with the "enable" parameter being "true". Maybe
it would be better to get rid of the parameter entirely?


Cheers,

Silvan