Linux Input Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH v10 00/12] HID:nintendo
@ 2019-12-30  1:27 Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 01/12] HID: nintendo: add nintendo switch controller driver Daniel J. Ogorchock
                   ` (11 more replies)
  0 siblings, 12 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

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 (12):
  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 IMU support
  HID: nintendo: add support for charging grip

 MAINTAINERS                |    6 +
 drivers/hid/Kconfig        |   24 +
 drivers/hid/Makefile       |    1 +
 drivers/hid/hid-ids.h      |    4 +
 drivers/hid/hid-nintendo.c | 1867 ++++++++++++++++++++++++++++++++++++
 5 files changed, 1902 insertions(+)
 create mode 100644 drivers/hid/hid-nintendo.c

-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 01/12] HID: nintendo: add nintendo switch controller driver
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 02/12] HID: nintendo: add player led support Daniel J. Ogorchock
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

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 | 822 +++++++++++++++++++++++++++++++++++++
 5 files changed, 843 insertions(+)
 create mode 100644 drivers/hid/hid-nintendo.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 9d3a5c54a41d..b1827a3c2fb2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11504,6 +11504,12 @@ S:	Maintained
 F:	Documentation/scsi/NinjaSCSI.txt
 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 <lftan@altera.com>
 L:	nios2-dev@lists.rocketboards.org (moderated for non-subscribers)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 1ecb5124421c..018fb7220d71 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -701,6 +701,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 0c03308cfb08..eee74c36617a 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -73,6 +73,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 447e8db21174..5e3e872feb29 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -868,6 +868,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..e5ba1153bcf5
--- /dev/null
+++ b/drivers/hid/hid-nintendo.c
@@ -0,0 +1,822 @@
+// 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>
+#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;
+
+/* 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 */
+} __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 */
+} __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);
+		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);
+		}
+		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);
+		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);
+	}
+	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_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
+	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 */
+};
+
+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 */
+	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);
+	}
+	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);
+	}
+
+	/* set up buttons */
+	if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONR) {
+		for (i = 0; joycon_button_inputs_l[i] > 0; i++)
+			input_set_capability(ctlr->input, EV_KEY,
+					     joycon_button_inputs_l[i]);
+	}
+	if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) {
+		for (i = 0; joycon_button_inputs_r[i] > 0; i++)
+			input_set_capability(ctlr->input, EV_KEY,
+					     joycon_button_inputs_r[i]);
+	}
+
+	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;
+}
+
+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;
+		}
+
+		if (match) {
+			memcpy(ctlr->input_buf, data,
+			       min(size, (int)JC_MAX_RESP_SIZE));
+			ctlr->msg_type = JOYCON_MSG_TYPE_NONE;
+			ctlr->received_resp = true;
+			wake_up(&ctlr->wait);
+
+			/* This message has been handled */
+			return 1;
+		}
+	}
+
+	if (ctlr->ctlr_state == JOYCON_CTLR_STATE_READ)
+		ret = joycon_ctlr_read_handler(ctlr, data, size);
+
+	return ret;
+}
+
+static int nintendo_hid_event(struct hid_device *hdev,
+			      struct hid_report *report, u8 *raw_data, int size)
+{
+	struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
+
+	if (size < 1)
+		return -EINVAL;
+
+	return joycon_ctlr_handle_event(ctlr, raw_data, size);
+}
+
+static int nintendo_hid_probe(struct hid_device *hdev,
+			    const struct hid_device_id *id)
+{
+	int ret;
+	struct joycon_ctlr *ctlr;
+
+	hid_dbg(hdev, "probe - start\n");
+
+	ctlr = devm_kzalloc(&hdev->dev, sizeof(*ctlr), GFP_KERNEL);
+	if (!ctlr) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	ctlr->hdev = hdev;
+	ctlr->ctlr_state = JOYCON_CTLR_STATE_INIT;
+	hid_set_drvdata(hdev, ctlr);
+	mutex_init(&ctlr->output_mutex);
+	init_waitqueue_head(&ctlr->wait);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "HID parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	if (ret) {
+		hid_err(hdev, "HW start failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_open(hdev);
+	if (ret) {
+		hid_err(hdev, "cannot start hardware I/O\n");
+		goto err_stop;
+	}
+
+	hid_device_io_start(hdev);
+
+	/* Initialize the controller */
+	mutex_lock(&ctlr->output_mutex);
+	/* if handshake command fails, assume ble pro controller */
+	if (hdev->product == USB_DEVICE_ID_NINTENDO_PROCON &&
+	    !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE)) {
+		hid_dbg(hdev, "detected USB controller\n");
+		/* set baudrate for improved latency */
+		ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M);
+		if (ret) {
+			hid_err(hdev, "Failed to set baudrate; ret=%d\n", ret);
+			goto err_mutex;
+		}
+		/* handshake */
+		ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE);
+		if (ret) {
+			hid_err(hdev, "Failed handshake; ret=%d\n", ret);
+			goto err_mutex;
+		}
+		/*
+		 * Set no timeout (to keep controller in USB mode).
+		 * This doesn't send a response, so ignore the timeout.
+		 */
+		joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT);
+	}
+
+	/* 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");
+	}
+
+	/* Set the reporting mode to 0x30, which is the full report mode */
+	ret = joycon_set_report_mode(ctlr);
+	if (ret) {
+		hid_err(hdev, "Failed to set report mode; ret=%d\n", ret);
+		goto err_mutex;
+	}
+
+	mutex_unlock(&ctlr->output_mutex);
+
+	ret = joycon_input_create(ctlr);
+	if (ret) {
+		hid_err(hdev, "Failed to create input device; ret=%d\n", ret);
+		goto err_close;
+	}
+
+	ctlr->ctlr_state = JOYCON_CTLR_STATE_READ;
+
+	hid_dbg(hdev, "probe - success\n");
+	return 0;
+
+err_mutex:
+	mutex_unlock(&ctlr->output_mutex);
+err_close:
+	hid_hw_close(hdev);
+err_stop:
+	hid_hw_stop(hdev);
+err:
+	hid_err(hdev, "probe - fail = %d\n", ret);
+	return ret;
+}
+
+static void nintendo_hid_remove(struct hid_device *hdev)
+{
+	struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
+
+	hid_dbg(hdev, "remove\n");
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id nintendo_hid_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+			 USB_DEVICE_ID_NINTENDO_PROCON) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+			 USB_DEVICE_ID_NINTENDO_PROCON) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+			 USB_DEVICE_ID_NINTENDO_JOYCONL) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+			 USB_DEVICE_ID_NINTENDO_JOYCONR) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
+
+static struct hid_driver nintendo_hid_driver = {
+	.name		= "nintendo",
+	.id_table	= nintendo_hid_devices,
+	.probe		= nintendo_hid_probe,
+	.remove		= nintendo_hid_remove,
+	.raw_event	= nintendo_hid_event,
+};
+module_hid_driver(nintendo_hid_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>");
+MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 02/12] HID: nintendo: add player led support
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 01/12] HID: nintendo: add nintendo switch controller driver Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 03/12] HID: nintendo: add power supply support Daniel J. Ogorchock
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

This patch adds led_classdev functionality to the switch controller
driver. It adds support for the 4 player LEDs. The Home Button LED still
needs to be supported on the pro controllers and right joy-con.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/Kconfig        |  2 +
 drivers/hid/hid-nintendo.c | 95 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 95 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 018fb7220d71..af5e68d350eb 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -704,6 +704,8 @@ config HID_MULTITOUCH
 config HID_NINTENDO
 	tristate "Nintendo Joy-Con and Pro Controller support"
 	depends on HID
+	depends on NEW_LEDS
+	depends on LEDS_CLASS
 	help
 	Adds support for the Nintendo Switch Joy-Cons and Pro Controller.
 	All controllers support bluetooth, and the Pro Controller also supports
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index e5ba1153bcf5..2ab3be27f24e 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -25,6 +25,7 @@
 #include <linux/device.h>
 #include <linux/hid.h>
 #include <linux/input.h>
+#include <linux/leds.h>
 #include <linux/module.h>
 #include <linux/spinlock.h>
 
@@ -178,11 +179,13 @@ struct joycon_input_report {
 } __packed;
 
 #define JC_MAX_RESP_SIZE	(sizeof(struct joycon_input_report) + 35)
+#define JC_NUM_LEDS		4
 
 /* Each physical controller is associated with a joycon_ctlr struct */
 struct joycon_ctlr {
 	struct hid_device *hdev;
 	struct input_dev *input;
+	struct led_classdev leds[JC_NUM_LEDS];
 	enum joycon_ctlr_state ctlr_state;
 
 	/* The following members are used for synchronous sends/receives */
@@ -521,11 +524,9 @@ static const unsigned int joycon_button_inputs_r[] = {
 	0 /* 0 signals end of array */
 };
 
-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;
@@ -592,6 +593,66 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 	if (ret)
 		return ret;
 
+	return 0;
+}
+
+static int joycon_player_led_brightness_set(struct led_classdev *led,
+					    enum led_brightness brightness)
+{
+	struct device *dev = led->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct joycon_ctlr *ctlr;
+	int val = 0;
+	int i;
+	int ret;
+	int num;
+
+	ctlr = hid_get_drvdata(hdev);
+	if (!ctlr) {
+		hid_err(hdev, "No controller data\n");
+		return -ENODEV;
+	}
+
+	/* determine which player led this is */
+	for (num = 0; num < JC_NUM_LEDS; num++) {
+		if (&ctlr->leds[num] == led)
+			break;
+	}
+	if (num >= JC_NUM_LEDS)
+		return -EINVAL;
+
+	mutex_lock(&ctlr->output_mutex);
+	for (i = 0; i < JC_NUM_LEDS; i++) {
+		if (i == num)
+			val |= brightness << i;
+		else
+			val |= ctlr->leds[i].brightness << i;
+	}
+	ret = joycon_set_player_leds(ctlr, 0, val);
+	mutex_unlock(&ctlr->output_mutex);
+
+	return ret;
+}
+
+static const char * const joycon_player_led_names[] = {
+	"player1",
+	"player2",
+	"player3",
+	"player4"
+};
+
+static DEFINE_MUTEX(joycon_input_num_mutex);
+static int joycon_player_leds_create(struct joycon_ctlr *ctlr)
+{
+	struct hid_device *hdev = ctlr->hdev;
+	struct device *dev = &hdev->dev;
+	const char *d_name = dev_name(dev);
+	struct led_classdev *led;
+	char *name;
+	int ret = 0;
+	int i;
+	static int input_num = 1;
+
 	/* Set the default controller player leds based on controller number */
 	mutex_lock(&joycon_input_num_mutex);
 	mutex_lock(&ctlr->output_mutex);
@@ -599,6 +660,29 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 	if (ret)
 		hid_warn(ctlr->hdev, "Failed to set leds; ret=%d\n", ret);
 	mutex_unlock(&ctlr->output_mutex);
+
+	/* configure the player LEDs */
+	for (i = 0; i < JC_NUM_LEDS; i++) {
+		name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", d_name,
+				      joycon_player_led_names[i]);
+		if (!name)
+			return -ENOMEM;
+
+		led = &ctlr->leds[i];
+		led->name = name;
+		led->brightness = ((i + 1) <= input_num) ? LED_ON : LED_OFF;
+		led->max_brightness = LED_ON;
+		led->brightness_set_blocking =
+					joycon_player_led_brightness_set;
+		led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE;
+
+		ret = devm_led_classdev_register(&hdev->dev, led);
+		if (ret) {
+			hid_err(hdev, "Failed registering %s LED\n", led->name);
+			break;
+		}
+	}
+
 	if (++input_num > 4)
 		input_num = 1;
 	mutex_unlock(&joycon_input_num_mutex);
@@ -764,6 +848,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 
 	mutex_unlock(&ctlr->output_mutex);
 
+	/* Initialize the leds */
+	ret = joycon_player_leds_create(ctlr);
+	if (ret) {
+		hid_err(hdev, "Failed to create leds; ret=%d\n", ret);
+		goto err_close;
+	}
+
 	ret = joycon_input_create(ctlr);
 	if (ret) {
 		hid_err(hdev, "Failed to create input device; ret=%d\n", ret);
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 03/12] HID: nintendo: add power supply support
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 01/12] HID: nintendo: add nintendo switch controller driver Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 02/12] HID: nintendo: add player led support Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 04/12] HID: nintendo: add home led support Daniel J. Ogorchock
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

This patch adds power_supply functionality to the switch controller
driver for its battery.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/Kconfig        |   1 +
 drivers/hid/hid-nintendo.c | 133 +++++++++++++++++++++++++++++++++++++
 2 files changed, 134 insertions(+)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index af5e68d350eb..9467202cbee2 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -706,6 +706,7 @@ config HID_NINTENDO
 	depends on HID
 	depends on NEW_LEDS
 	depends on LEDS_CLASS
+	select POWER_SUPPLY
 	help
 	Adds support for the Nintendo Switch Joy-Cons and Pro Controller.
 	All controllers support bluetooth, and the Pro Controller also supports
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 2ab3be27f24e..be494f421b02 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -11,6 +11,7 @@
  *   https://github.com/MTCKC/ProconXInput
  *   hid-wiimote kernel hid driver
  *   hid-logitech-hidpp driver
+ *   hid-sony driver
  *
  * This driver supports the Nintendo Switch Joy-Cons and Pro Controllers. The
  * Pro Controllers can either be used over USB or Bluetooth.
@@ -27,6 +28,7 @@
 #include <linux/input.h>
 #include <linux/leds.h>
 #include <linux/module.h>
+#include <linux/power_supply.h>
 #include <linux/spinlock.h>
 
 /*
@@ -187,6 +189,7 @@ struct joycon_ctlr {
 	struct input_dev *input;
 	struct led_classdev leds[JC_NUM_LEDS];
 	enum joycon_ctlr_state ctlr_state;
+	spinlock_t lock;
 
 	/* The following members are used for synchronous sends/receives */
 	enum joycon_msg_type msg_type;
@@ -204,6 +207,12 @@ struct joycon_ctlr {
 	struct joycon_stick_cal right_stick_cal_x;
 	struct joycon_stick_cal right_stick_cal_y;
 
+	/* power supply data */
+	struct power_supply *battery;
+	struct power_supply_desc battery_desc;
+	u8 battery_capacity;
+	bool battery_charging;
+	bool host_powered;
 };
 
 static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
@@ -434,9 +443,41 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 				struct joycon_input_report *rep)
 {
 	struct input_dev *dev = ctlr->input;
+	unsigned long flags;
+	u8 tmp;
 	u32 btns;
 	u32 id = ctlr->hdev->product;
 
+	/* Parse the battery status */
+	tmp = rep->bat_con;
+	spin_lock_irqsave(&ctlr->lock, flags);
+	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;
+		break;
+	case 1: /* low */
+		ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+		break;
+	case 2: /* medium */
+		ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+		break;
+	case 3: /* high */
+		ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+		break;
+	case 4: /* full */
+		ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+		break;
+	default:
+		ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+		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 (id != USB_DEVICE_ID_NINTENDO_JOYCONR) {
@@ -690,6 +731,90 @@ static int joycon_player_leds_create(struct joycon_ctlr *ctlr)
 	return 0;
 }
 
+static int joycon_battery_get_property(struct power_supply *supply,
+				       enum power_supply_property prop,
+				       union power_supply_propval *val)
+{
+	struct joycon_ctlr *ctlr = power_supply_get_drvdata(supply);
+	unsigned long flags;
+	int ret = 0;
+	u8 capacity;
+	bool charging;
+	bool powered;
+
+	spin_lock_irqsave(&ctlr->lock, flags);
+	capacity = ctlr->battery_capacity;
+	charging = ctlr->battery_charging;
+	powered = ctlr->host_powered;
+	spin_unlock_irqrestore(&ctlr->lock, flags);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+		val->intval = capacity;
+		break;
+	case POWER_SUPPLY_PROP_STATUS:
+		if (charging)
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		else if (capacity == POWER_SUPPLY_CAPACITY_LEVEL_FULL &&
+			 powered)
+			val->intval = POWER_SUPPLY_STATUS_FULL;
+		else
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static enum power_supply_property joycon_battery_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_STATUS,
+};
+
+static int joycon_power_supply_create(struct joycon_ctlr *ctlr)
+{
+	struct hid_device *hdev = ctlr->hdev;
+	struct power_supply_config supply_config = { .drv_data = ctlr, };
+	const char * const name_fmt = "nintendo_switch_controller_battery_%s";
+	int ret = 0;
+
+	/* Set initially to unknown before receiving first input report */
+	ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+
+	/* Configure the battery's description */
+	ctlr->battery_desc.properties = joycon_battery_props;
+	ctlr->battery_desc.num_properties =
+					ARRAY_SIZE(joycon_battery_props);
+	ctlr->battery_desc.get_property = joycon_battery_get_property;
+	ctlr->battery_desc.use_for_apm = 0;
+	ctlr->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+						 name_fmt,
+						 dev_name(&hdev->dev));
+	if (!ctlr->battery_desc.name)
+		return -ENOMEM;
+
+	ctlr->battery = devm_power_supply_register(&hdev->dev,
+						   &ctlr->battery_desc,
+						   &supply_config);
+	if (IS_ERR(ctlr->battery)) {
+		ret = PTR_ERR(ctlr->battery);
+		hid_err(hdev, "Failed to register battery; ret=%d\n", ret);
+		return ret;
+	}
+	power_supply_powers(ctlr->battery, &hdev->dev);
+	return 0;
+}
+
 /* Common handler for parsing inputs */
 static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data,
 							      int size)
@@ -783,6 +908,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 	hid_set_drvdata(hdev, ctlr);
 	mutex_init(&ctlr->output_mutex);
 	init_waitqueue_head(&ctlr->wait);
+	spin_lock_init(&ctlr->lock);
 
 	ret = hid_parse(hdev);
 	if (ret) {
@@ -855,6 +981,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		goto err_close;
 	}
 
+	/* Initialize the battery power supply */
+	ret = joycon_power_supply_create(ctlr);
+	if (ret) {
+		hid_err(hdev, "Failed to create power_supply; ret=%d\n", ret);
+		goto err_close;
+	}
+
 	ret = joycon_input_create(ctlr);
 	if (ret) {
 		hid_err(hdev, "Failed to create input device; ret=%d\n", ret);
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 04/12] HID: nintendo: add home led support
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (2 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 03/12] HID: nintendo: add power supply support Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 05/12] HID: nintendo: add rumble support Daniel J. Ogorchock
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

This patch adds the ability to set the intensity level of the home
button's LED.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/hid-nintendo.c | 69 +++++++++++++++++++++++++++++++++++---
 1 file changed, 65 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index be494f421b02..02178ab9619e 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -187,7 +187,8 @@ struct joycon_input_report {
 struct joycon_ctlr {
 	struct hid_device *hdev;
 	struct input_dev *input;
-	struct led_classdev leds[JC_NUM_LEDS];
+	struct led_classdev leds[JC_NUM_LEDS]; /* player leds */
+	struct led_classdev home_led;
 	enum joycon_ctlr_state ctlr_state;
 	spinlock_t lock;
 
@@ -675,6 +676,40 @@ static int joycon_player_led_brightness_set(struct led_classdev *led,
 	return ret;
 }
 
+static int joycon_home_led_brightness_set(struct led_classdev *led,
+					  enum led_brightness brightness)
+{
+	struct device *dev = led->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct joycon_ctlr *ctlr;
+	struct joycon_subcmd_request *req;
+	u8 buffer[sizeof(*req) + 5] = { 0 };
+	u8 *data;
+	int ret;
+
+	ctlr = hid_get_drvdata(hdev);
+	if (!ctlr) {
+		hid_err(hdev, "No controller data\n");
+		return -ENODEV;
+	}
+
+	req = (struct joycon_subcmd_request *)buffer;
+	req->subcmd_id = JC_SUBCMD_SET_HOME_LIGHT;
+	data = req->data;
+	data[0] = 0x01;
+	data[1] = brightness << 4;
+	data[2] = brightness | (brightness << 4);
+	data[3] = 0x11;
+	data[4] = 0x11;
+
+	hid_dbg(hdev, "setting home led brightness\n");
+	mutex_lock(&ctlr->output_mutex);
+	ret = joycon_send_subcmd(ctlr, req, 5);
+	mutex_unlock(&ctlr->output_mutex);
+
+	return ret;
+}
+
 static const char * const joycon_player_led_names[] = {
 	"player1",
 	"player2",
@@ -683,7 +718,7 @@ static const char * const joycon_player_led_names[] = {
 };
 
 static DEFINE_MUTEX(joycon_input_num_mutex);
-static int joycon_player_leds_create(struct joycon_ctlr *ctlr)
+static int joycon_leds_create(struct joycon_ctlr *ctlr)
 {
 	struct hid_device *hdev = ctlr->hdev;
 	struct device *dev = &hdev->dev;
@@ -720,7 +755,7 @@ static int joycon_player_leds_create(struct joycon_ctlr *ctlr)
 		ret = devm_led_classdev_register(&hdev->dev, led);
 		if (ret) {
 			hid_err(hdev, "Failed registering %s LED\n", led->name);
-			break;
+			return ret;
 		}
 	}
 
@@ -728,6 +763,32 @@ static int joycon_player_leds_create(struct joycon_ctlr *ctlr)
 		input_num = 1;
 	mutex_unlock(&joycon_input_num_mutex);
 
+	/* configure the home LED */
+	if (ctlr->hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) {
+		name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", d_name, "home");
+		if (!name)
+			return ret;
+
+		led = &ctlr->home_led;
+		led->name = name;
+		led->brightness = 0;
+		led->max_brightness = 0xF;
+		led->brightness_set_blocking = joycon_home_led_brightness_set;
+		led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE;
+		ret = devm_led_classdev_register(&hdev->dev, led);
+		if (ret) {
+			hid_err(hdev, "Failed registering home led\n");
+			return ret;
+		}
+		/* Set the home LED to 0 as default state */
+		ret = joycon_home_led_brightness_set(led, 0);
+		if (ret) {
+			hid_err(hdev, "Failed to set home LED dflt; ret=%d\n",
+									ret);
+			return ret;
+		}
+	}
+
 	return 0;
 }
 
@@ -975,7 +1036,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 	mutex_unlock(&ctlr->output_mutex);
 
 	/* Initialize the leds */
-	ret = joycon_player_leds_create(ctlr);
+	ret = joycon_leds_create(ctlr);
 	if (ret) {
 		hid_err(hdev, "Failed to create leds; ret=%d\n", ret);
 		goto err_close;
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 05/12] HID: nintendo: add rumble support
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (3 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 04/12] HID: nintendo: add home led support Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 06/12] HID: nintendo: improve subcommand reliability Daniel J. Ogorchock
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

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 | 347 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 354 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 9467202cbee2..70ab3e8d19d3 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -715,6 +715,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 02178ab9619e..7afb84234c6d 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>
@@ -105,6 +107,120 @@ static const u16 JC_MAX_STICK_MAG		= 32767;
 static const u16 JC_STICK_FUZZ			= 250;
 static const u16 JC_STICK_FLAT			= 500;
 
+/* 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,
@@ -182,6 +298,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 {
@@ -214,6 +336,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)
@@ -270,6 +404,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;
@@ -422,6 +562,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)
+{
+	struct joycon_subcmd_request *req;
+	u8 buffer[sizeof(*req) + 1] = { 0 };
+
+	req = (struct joycon_subcmd_request *)buffer;
+	req->subcmd_id = JC_SUBCMD_ENABLE_VIBRATION;
+	req->data[0] = enable ? 0x01 : 0x00;
+
+	hid_dbg(ctlr->hdev, "%s rumble\n", enable ? "enabling" : "disabling");
+	return joycon_send_subcmd(ctlr, req, 1);
+}
+
 static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
 {
 	s32 center = cal->center;
@@ -448,10 +601,15 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 	u8 tmp;
 	u32 btns;
 	u32 id = ctlr->hdev->product;
+	unsigned long msecs = jiffies_to_msecs(jiffies);
+
+	spin_lock_irqsave(&ctlr->lock, flags);
+	if (IS_ENABLED(CONFIG_NINTENDO_FF) && rep->vibrator_report &&
+	    (msecs - ctlr->rumble_msecs) >= JC_RUMBLE_PERIOD_MS)
+		queue_work(ctlr->rumble_queue, &ctlr->rumble_worker);
 
 	/* Parse the battery status */
 	tmp = rep->bat_con;
-	spin_lock_irqsave(&ctlr->lock, flags);
 	ctlr->host_powered = tmp & BIT(0);
 	ctlr->battery_charging = tmp & BIT(4);
 	tmp = tmp >> 5;
@@ -551,6 +709,161 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 	input_sync(dev);
 }
 
+static void joycon_rumble_worker(struct work_struct *work)
+{
+	struct joycon_ctlr *ctlr = container_of(work, struct joycon_ctlr,
+							rumble_worker);
+	unsigned long flags;
+	bool again = true;
+	int ret;
+
+	while (again) {
+		mutex_lock(&ctlr->output_mutex);
+		ret = joycon_enable_rumble(ctlr, true);
+		mutex_unlock(&ctlr->output_mutex);
+		if (ret < 0)
+			hid_warn(ctlr->hdev, "Failed to set rumble; e=%d", ret);
+
+		spin_lock_irqsave(&ctlr->lock, flags);
+		ctlr->rumble_msecs = jiffies_to_msecs(jiffies);
+		if (ctlr->rumble_queue_tail != ctlr->rumble_queue_head) {
+			if (++ctlr->rumble_queue_tail >= JC_RUMBLE_QUEUE_SIZE)
+				ctlr->rumble_queue_tail = 0;
+		} else {
+			again = false;
+		}
+		spin_unlock_irqrestore(&ctlr->lock, flags);
+	}
+}
+
+#if IS_ENABLED(CONFIG_NINTENDO_FF)
+static struct joycon_rumble_freq_data joycon_find_rumble_freq(u16 freq)
+{
+	const size_t length = ARRAY_SIZE(joycon_rumble_frequencies);
+	const struct joycon_rumble_freq_data *data = joycon_rumble_frequencies;
+	int i = 0;
+
+	if (freq > data[0].freq) {
+		for (i = 1; i < length - 1; i++) {
+			if (freq > data[i - 1].freq && freq <= data[i].freq)
+				break;
+		}
+	}
+
+	return data[i];
+}
+
+static struct joycon_rumble_amp_data joycon_find_rumble_amp(u16 amp)
+{
+	const size_t length = ARRAY_SIZE(joycon_rumble_amplitudes);
+	const struct joycon_rumble_amp_data *data = joycon_rumble_amplitudes;
+	int i = 0;
+
+	if (amp > data[0].amp) {
+		for (i = 1; i < length - 1; i++) {
+			if (amp > data[i - 1].amp && amp <= data[i].amp)
+				break;
+		}
+	}
+
+	return data[i];
+}
+
+static void joycon_encode_rumble(u8 *data, u16 freq_low, u16 freq_high, u16 amp)
+{
+	struct joycon_rumble_freq_data freq_data_low;
+	struct joycon_rumble_freq_data freq_data_high;
+	struct joycon_rumble_amp_data amp_data;
+
+	freq_data_low = joycon_find_rumble_freq(freq_low);
+	freq_data_high = joycon_find_rumble_freq(freq_high);
+	amp_data = joycon_find_rumble_amp(amp);
+
+	data[0] = (freq_data_high.high >> 8) & 0xFF;
+	data[1] = (freq_data_high.high & 0xFF) + amp_data.high;
+	data[2] = freq_data_low.low + ((amp_data.low >> 8) & 0xFF);
+	data[3] = amp_data.low & 0xFF;
+}
+
+static const u16 JOYCON_MAX_RUMBLE_HIGH_FREQ	= 1253;
+static const u16 JOYCON_MIN_RUMBLE_HIGH_FREQ	= 82;
+static const u16 JOYCON_MAX_RUMBLE_LOW_FREQ	= 626;
+static const u16 JOYCON_MIN_RUMBLE_LOW_FREQ	= 41;
+
+static void joycon_clamp_rumble_freqs(struct joycon_ctlr *ctlr)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ctlr->lock, flags);
+	ctlr->rumble_ll_freq = clamp(ctlr->rumble_ll_freq,
+				     JOYCON_MIN_RUMBLE_LOW_FREQ,
+				     JOYCON_MAX_RUMBLE_LOW_FREQ);
+	ctlr->rumble_lh_freq = clamp(ctlr->rumble_lh_freq,
+				     JOYCON_MIN_RUMBLE_HIGH_FREQ,
+				     JOYCON_MAX_RUMBLE_HIGH_FREQ);
+	ctlr->rumble_rl_freq = clamp(ctlr->rumble_rl_freq,
+				     JOYCON_MIN_RUMBLE_LOW_FREQ,
+				     JOYCON_MAX_RUMBLE_LOW_FREQ);
+	ctlr->rumble_rh_freq = clamp(ctlr->rumble_rh_freq,
+				     JOYCON_MIN_RUMBLE_HIGH_FREQ,
+				     JOYCON_MAX_RUMBLE_HIGH_FREQ);
+	spin_unlock_irqrestore(&ctlr->lock, flags);
+}
+
+static int joycon_set_rumble(struct joycon_ctlr *ctlr, u16 amp_r, u16 amp_l,
+			     bool schedule_now)
+{
+	u8 data[JC_RUMBLE_DATA_SIZE];
+	u16 amp;
+	u16 freq_r_low;
+	u16 freq_r_high;
+	u16 freq_l_low;
+	u16 freq_l_high;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ctlr->lock, flags);
+	freq_r_low = ctlr->rumble_rl_freq;
+	freq_r_high = ctlr->rumble_rh_freq;
+	freq_l_low = ctlr->rumble_ll_freq;
+	freq_l_high = ctlr->rumble_lh_freq;
+	spin_unlock_irqrestore(&ctlr->lock, flags);
+
+	/* right joy-con */
+	amp = amp_r * (u32)joycon_max_rumble_amp / 65535;
+	joycon_encode_rumble(data + 4, freq_r_low, freq_r_high, amp);
+
+	/* left joy-con */
+	amp = amp_l * (u32)joycon_max_rumble_amp / 65535;
+	joycon_encode_rumble(data, freq_l_low, freq_l_high, amp);
+
+	spin_lock_irqsave(&ctlr->lock, flags);
+	if (++ctlr->rumble_queue_head >= JC_RUMBLE_QUEUE_SIZE)
+		ctlr->rumble_queue_head = 0;
+	memcpy(ctlr->rumble_data[ctlr->rumble_queue_head], data,
+	       JC_RUMBLE_DATA_SIZE);
+	spin_unlock_irqrestore(&ctlr->lock, flags);
+
+	/* don't wait for the periodic send (reduces latency) */
+	if (schedule_now)
+		queue_work(ctlr->rumble_queue, &ctlr->rumble_worker);
+
+	return 0;
+}
+
+static int joycon_play_effect(struct input_dev *dev, void *data,
+						     struct ff_effect *effect)
+{
+	struct joycon_ctlr *ctlr = input_get_drvdata(dev);
+
+	if (effect->type != FF_RUMBLE)
+		return 0;
+
+	return joycon_set_rumble(ctlr,
+				 effect->u.rumble.weak_magnitude,
+				 effect->u.rumble.strong_magnitude,
+				 true);
+}
+#endif /* IS_ENABLED(CONFIG_NINTENDO_FF) */
 
 static const unsigned int joycon_button_inputs_l[] = {
 	BTN_SELECT, BTN_Z, BTN_THUMBL,
@@ -631,6 +944,19 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 					     joycon_button_inputs_r[i]);
 	}
 
+#if IS_ENABLED(CONFIG_NINTENDO_FF)
+	/* set up rumble */
+	input_set_capability(ctlr->input, EV_FF, FF_RUMBLE);
+	input_ff_create_memless(ctlr->input, NULL, joycon_play_effect);
+	ctlr->rumble_ll_freq = JC_RUMBLE_DFLT_LOW_FREQ;
+	ctlr->rumble_lh_freq = JC_RUMBLE_DFLT_HIGH_FREQ;
+	ctlr->rumble_rl_freq = JC_RUMBLE_DFLT_LOW_FREQ;
+	ctlr->rumble_rh_freq = JC_RUMBLE_DFLT_HIGH_FREQ;
+	joycon_clamp_rumble_freqs(ctlr);
+	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;
@@ -966,21 +1292,26 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 
 	ctlr->hdev = hdev;
 	ctlr->ctlr_state = JOYCON_CTLR_STATE_INIT;
+	ctlr->rumble_queue_head = JC_RUMBLE_QUEUE_SIZE - 1;
+	ctlr->rumble_queue_tail = 0;
 	hid_set_drvdata(hdev, ctlr);
 	mutex_init(&ctlr->output_mutex);
 	init_waitqueue_head(&ctlr->wait);
 	spin_lock_init(&ctlr->lock);
+	ctlr->rumble_queue = alloc_workqueue("hid-nintendo-rumble_wq",
+					     WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);
+	INIT_WORK(&ctlr->rumble_worker, joycon_rumble_worker);
 
 	ret = hid_parse(hdev);
 	if (ret) {
 		hid_err(hdev, "HID parse failed\n");
-		goto err;
+		goto err_wq;
 	}
 
 	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
 	if (ret) {
 		hid_err(hdev, "HW start failed\n");
-		goto err;
+		goto err_wq;
 	}
 
 	ret = hid_hw_open(hdev);
@@ -1033,6 +1364,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		goto err_mutex;
 	}
 
+	/* Enable rumble */
+	ret = joycon_enable_rumble(ctlr, true);
+	if (ret) {
+		hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
+		goto err_mutex;
+	}
+
 	mutex_unlock(&ctlr->output_mutex);
 
 	/* Initialize the leds */
@@ -1066,6 +1404,8 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 	hid_hw_close(hdev);
 err_stop:
 	hid_hw_stop(hdev);
+err_wq:
+	destroy_workqueue(ctlr->rumble_queue);
 err:
 	hid_err(hdev, "probe - fail = %d\n", ret);
 	return ret;
@@ -1076,6 +1416,7 @@ static void nintendo_hid_remove(struct hid_device *hdev)
 	struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
 
 	hid_dbg(hdev, "remove\n");
+	destroy_workqueue(ctlr->rumble_queue);
 	hid_hw_close(hdev);
 	hid_hw_stop(hdev);
 }
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 06/12] HID: nintendo: improve subcommand reliability
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (4 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 05/12] HID: nintendo: add rumble support Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 07/12] HID: nintendo: send subcommands after receiving input report Daniel J. Ogorchock
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

The controller occasionally doesn't respond to subcommands. It appears
that it's dropping them. To improve reliability, this patch attempts one
retry in the case of a synchronous send timeout. In testing, this has
resolved all timeout failures (most common for LED setting and rumble
setting subcommands).

The 1 second timeout is excessively long for rumble and LED subcommands,
so the timeout has been made a param for joycon_hid_send_sync. Most
subcommands continue to use the 1s timeout, since they can result in
long response times. Rumble and LED setting subcommands have been
reduced to 250ms, since response times for them are much quicker (and
this significantly reduces the observable impact in the case of a retry
being required).

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/hid-nintendo.c | 66 ++++++++++++++++++++++++--------------
 1 file changed, 42 insertions(+), 24 deletions(-)

diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 7afb84234c6d..05d4c0e9636e 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -365,27 +365,45 @@ static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
 	return ret;
 }
 
-static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len)
+static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
+				u32 timeout)
 {
 	int ret;
+	int tries = 2;
 
-	ret = __joycon_hid_send(ctlr->hdev, data, len);
-	if (ret < 0) {
-		memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE);
-		return ret;
-	}
+	/*
+	 * The controller occasionally seems to drop subcommands. In testing,
+	 * doing one retry after a timeout appears to always work.
+	 */
+	while (tries--) {
+		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;
+		ret = wait_event_timeout(ctlr->wait, ctlr->received_resp,
+					 timeout);
+		if (!ret) {
+			hid_dbg(ctlr->hdev,
+				"synchronous send/receive timed out\n");
+			if (tries) {
+				hid_dbg(ctlr->hdev,
+					"retrying sync send after timeout\n");
+			}
+			memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE);
+			ret = -ETIMEDOUT;
+		} else {
+			ret = 0;
+			break;
+		}
 	}
 
 	ctlr->received_resp = false;
-	return 0;
+	return ret;
 }
 
-static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 cmd)
+static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 cmd, u32 timeout)
 {
 	int ret;
 	u8 buf[2] = {JC_OUTPUT_USB_CMD};
@@ -393,7 +411,7 @@ static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 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));
+	ret = joycon_hid_send_sync(ctlr, buf, sizeof(buf), timeout);
 	if (ret)
 		hid_dbg(ctlr->hdev, "send usb command failed; ret=%d\n", ret);
 	return ret;
@@ -401,7 +419,7 @@ static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 cmd)
 
 static int joycon_send_subcmd(struct joycon_ctlr *ctlr,
 			      struct joycon_subcmd_request *subcmd,
-			      size_t data_len)
+			      size_t data_len, u32 timeout)
 {
 	int ret;
 	unsigned long flags;
@@ -419,7 +437,7 @@ static int joycon_send_subcmd(struct joycon_ctlr *ctlr,
 	ctlr->msg_type = JOYCON_MSG_TYPE_SUBCMD;
 
 	ret = joycon_hid_send_sync(ctlr, (u8 *)subcmd,
-				   sizeof(*subcmd) + data_len);
+				   sizeof(*subcmd) + data_len, timeout);
 	if (ret < 0)
 		hid_dbg(ctlr->hdev, "send subcommand failed; ret=%d\n", ret);
 	else
@@ -438,7 +456,7 @@ static int joycon_set_player_leds(struct joycon_ctlr *ctlr, u8 flash, u8 on)
 	req->data[0] = (flash << 4) | on;
 
 	hid_dbg(ctlr->hdev, "setting player leds\n");
-	return joycon_send_subcmd(ctlr, req, 1);
+	return joycon_send_subcmd(ctlr, req, 1, HZ/4);
 }
 
 static const u16 DFLT_STICK_CAL_CEN = 2000;
@@ -469,7 +487,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
 	data[4] = JC_CAL_DATA_SIZE;
 
 	hid_dbg(ctlr->hdev, "requesting cal data\n");
-	ret = joycon_send_subcmd(ctlr, req, 5);
+	ret = joycon_send_subcmd(ctlr, req, 5, HZ);
 	if (ret) {
 		hid_warn(ctlr->hdev,
 			 "Failed to read stick cal, using defaults; ret=%d\n",
@@ -559,7 +577,7 @@ static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
 	req->data[0] = 0x30; /* standard, full report mode */
 
 	hid_dbg(ctlr->hdev, "setting controller report mode\n");
-	return joycon_send_subcmd(ctlr, req, 1);
+	return joycon_send_subcmd(ctlr, req, 1, HZ);
 }
 
 static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
@@ -572,7 +590,7 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
 	req->data[0] = enable ? 0x01 : 0x00;
 
 	hid_dbg(ctlr->hdev, "%s rumble\n", enable ? "enabling" : "disabling");
-	return joycon_send_subcmd(ctlr, req, 1);
+	return joycon_send_subcmd(ctlr, req, 1, HZ/4);
 }
 
 static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
@@ -1030,7 +1048,7 @@ static int joycon_home_led_brightness_set(struct led_classdev *led,
 
 	hid_dbg(hdev, "setting home led brightness\n");
 	mutex_lock(&ctlr->output_mutex);
-	ret = joycon_send_subcmd(ctlr, req, 5);
+	ret = joycon_send_subcmd(ctlr, req, 5, HZ/4);
 	mutex_unlock(&ctlr->output_mutex);
 
 	return ret;
@@ -1326,16 +1344,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 	mutex_lock(&ctlr->output_mutex);
 	/* if handshake command fails, assume ble pro controller */
 	if (hdev->product == USB_DEVICE_ID_NINTENDO_PROCON &&
-	    !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE)) {
+	    !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);
+		ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M, HZ);
 		if (ret) {
 			hid_err(hdev, "Failed to set baudrate; ret=%d\n", ret);
 			goto err_mutex;
 		}
 		/* handshake */
-		ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE);
+		ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ);
 		if (ret) {
 			hid_err(hdev, "Failed handshake; ret=%d\n", ret);
 			goto err_mutex;
@@ -1344,7 +1362,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		 * Set no timeout (to keep controller in USB mode).
 		 * This doesn't send a response, so ignore the timeout.
 		 */
-		joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT);
+		joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT, HZ/10);
 	}
 
 	/* get controller calibration data, and parse it */
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 07/12] HID: nintendo: send subcommands after receiving input report
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (5 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 06/12] HID: nintendo: improve subcommand reliability Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 08/12] HID: nintendo: reduce device removal subcommand errors Daniel J. Ogorchock
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

Waiting to send subcommands until right after receiving an input report
drastically improves subcommand reliability. If the driver has finished
initial controller configuration, it now waits until receiving an input
report for all subcommands.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/hid-nintendo.c | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 05d4c0e9636e..152d7eb9c27d 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -323,6 +323,7 @@ struct joycon_ctlr {
 	bool received_resp;
 	u8 usb_ack_match;
 	u8 subcmd_ack_match;
+	bool received_input_report;
 
 	/* factory calibration data */
 	struct joycon_stick_cal left_stick_cal_x;
@@ -376,6 +377,26 @@ static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
 	 * doing one retry after a timeout appears to always work.
 	 */
 	while (tries--) {
+		/*
+		 * If we are in the proper reporting mode, wait for an input
+		 * report prior to sending the subcommand. This improves
+		 * reliability considerably.
+		 */
+		if (ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
+			unsigned long flags;
+
+			spin_lock_irqsave(&ctlr->lock, flags);
+			ctlr->received_input_report = false;
+			spin_unlock_irqrestore(&ctlr->lock, flags);
+			ret = wait_event_timeout(ctlr->wait,
+						 ctlr->received_input_report,
+						 HZ / 4);
+			/* We will still proceed, even with a timeout here */
+			if (!ret)
+				hid_warn(ctlr->hdev,
+					 "timeout waiting for input report\n");
+		}
+
 		ret = __joycon_hid_send(ctlr->hdev, data, len);
 		if (ret < 0) {
 			memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE);
@@ -725,6 +746,18 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 	}
 
 	input_sync(dev);
+
+	/*
+	 * Immediately after receiving a report is the most reliable time to
+	 * send a subcommand to the controller. Wake any subcommand senders
+	 * waiting for a report.
+	 */
+	if (unlikely(mutex_is_locked(&ctlr->output_mutex))) {
+		spin_lock_irqsave(&ctlr->lock, flags);
+		ctlr->received_input_report = true;
+		spin_unlock_irqrestore(&ctlr->lock, flags);
+		wake_up(&ctlr->wait);
+	}
 }
 
 static void joycon_rumble_worker(struct work_struct *work)
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 08/12] HID: nintendo: reduce device removal subcommand errors
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (6 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 07/12] HID: nintendo: send subcommands after receiving input report Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 09/12] HID: nintendo: patch hw version for userspace HID mappings Daniel J. Ogorchock
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

This patch fixes meaningless error output from trying to send
subcommands immediately after controller removal. It now disables
subcommands as soon as possible on removal.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/hid-nintendo.c | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 152d7eb9c27d..4653a0a10782 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -225,6 +225,7 @@ static const struct joycon_rumble_amp_data joycon_rumble_amplitudes[] = {
 enum joycon_ctlr_state {
 	JOYCON_CTLR_STATE_INIT,
 	JOYCON_CTLR_STATE_READ,
+	JOYCON_CTLR_STATE_REMOVED,
 };
 
 struct joycon_stick_cal {
@@ -391,10 +392,12 @@ static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
 			ret = wait_event_timeout(ctlr->wait,
 						 ctlr->received_input_report,
 						 HZ / 4);
+			spin_lock_irqsave(&ctlr->lock, flags);
 			/* We will still proceed, even with a timeout here */
 			if (!ret)
 				hid_warn(ctlr->hdev,
 					 "timeout waiting for input report\n");
+			spin_unlock_irqrestore(&ctlr->lock, flags);
 		}
 
 		ret = __joycon_hid_send(ctlr->hdev, data, len);
@@ -446,6 +449,14 @@ static int joycon_send_subcmd(struct joycon_ctlr *ctlr,
 	unsigned long flags;
 
 	spin_lock_irqsave(&ctlr->lock, flags);
+	/*
+	 * If the controller has been removed, just return ENODEV so the LED
+	 * subsystem doesn't print invalid errors on removal.
+	 */
+	if (ctlr->ctlr_state == JOYCON_CTLR_STATE_REMOVED) {
+		spin_unlock_irqrestore(&ctlr->lock, flags);
+		return -ENODEV;
+	}
 	memcpy(subcmd->rumble_data, ctlr->rumble_data[ctlr->rumble_queue_tail],
 	       JC_RUMBLE_DATA_SIZE);
 	spin_unlock_irqrestore(&ctlr->lock, flags);
@@ -772,10 +783,13 @@ static void joycon_rumble_worker(struct work_struct *work)
 		mutex_lock(&ctlr->output_mutex);
 		ret = joycon_enable_rumble(ctlr, true);
 		mutex_unlock(&ctlr->output_mutex);
-		if (ret < 0)
-			hid_warn(ctlr->hdev, "Failed to set rumble; e=%d", ret);
 
+		/* -ENODEV means the controller was just unplugged */
 		spin_lock_irqsave(&ctlr->lock, flags);
+		if (ret < 0 && ret != -ENODEV &&
+		    ctlr->ctlr_state != JOYCON_CTLR_STATE_REMOVED)
+			hid_warn(ctlr->hdev, "Failed to set rumble; e=%d", ret);
+
 		ctlr->rumble_msecs = jiffies_to_msecs(jiffies);
 		if (ctlr->rumble_queue_tail != ctlr->rumble_queue_head) {
 			if (++ctlr->rumble_queue_tail >= JC_RUMBLE_QUEUE_SIZE)
@@ -1465,9 +1479,17 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 static void nintendo_hid_remove(struct hid_device *hdev)
 {
 	struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
+	unsigned long flags;
 
 	hid_dbg(hdev, "remove\n");
+
+	/* Prevent further attempts at sending subcommands. */
+	spin_lock_irqsave(&ctlr->lock, flags);
+	ctlr->ctlr_state = JOYCON_CTLR_STATE_REMOVED;
+	spin_unlock_irqrestore(&ctlr->lock, flags);
+
 	destroy_workqueue(ctlr->rumble_queue);
+
 	hid_hw_close(hdev);
 	hid_hw_stop(hdev);
 }
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 09/12] HID: nintendo: patch hw version for userspace HID mappings
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (7 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 08/12] HID: nintendo: reduce device removal subcommand errors Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 10/12] HID: nintendo: set controller uniq to MAC Daniel J. Ogorchock
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

This patch sets the most significant bit of the hid hw version to allow
userspace to distinguish between this driver's input mappings vs. the
default hid mappings. This prevents breaking userspace applications that
use SDL2 for gamepad input, allowing them to distinguish the mappings
based on the version.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/hid-nintendo.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 4653a0a10782..b11c27a8e381 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -1373,6 +1373,15 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		goto err_wq;
 	}
 
+	/*
+	 * Patch the hw version of pro controller/joycons, so applications can
+	 * distinguish between the default HID mappings and the mappings defined
+	 * by the Linux game controller spec. This is important for the SDL2
+	 * library, which has a game controller database, which uses device ids
+	 * in combination with version as a key.
+	 */
+	hdev->version |= 0x8000;
+
 	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
 	if (ret) {
 		hid_err(hdev, "HW start failed\n");
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 10/12] HID: nintendo: set controller uniq to MAC
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (8 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 09/12] HID: nintendo: patch hw version for userspace HID mappings Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 11/12] HID: nintendo: add IMU support Daniel J. Ogorchock
  2019-12-30  1:27 ` [PATCH v10 12/12] HID: nintendo: add support for charging grip Daniel J. Ogorchock
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

This patch sets the input device's uniq identifier to the controller's
MAC address. This is useful for future association between an IMU input
device with the normal input device as well as associating the
controller with any serial joy-con driver.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/hid-nintendo.c | 45 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index b11c27a8e381..7b7a0cf3fe0b 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -314,6 +314,8 @@ struct joycon_ctlr {
 	struct led_classdev home_led;
 	enum joycon_ctlr_state ctlr_state;
 	spinlock_t lock;
+	u8 mac_addr[6];
+	char *mac_addr_str;
 
 	/* The following members are used for synchronous sends/receives */
 	enum joycon_msg_type msg_type;
@@ -975,6 +977,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 	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;
 	input_set_drvdata(ctlr->input, ctlr);
 
@@ -1267,6 +1270,41 @@ static int joycon_power_supply_create(struct joycon_ctlr *ctlr)
 	return 0;
 }
 
+static int joycon_read_mac(struct joycon_ctlr *ctlr)
+{
+	int ret;
+	int i;
+	int j;
+	struct joycon_subcmd_request req = { 0 };
+	struct joycon_input_report *report;
+
+	req.subcmd_id = JC_SUBCMD_REQ_DEV_INFO;
+	ret = joycon_send_subcmd(ctlr, &req, 0, HZ);
+	if (ret) {
+		hid_err(ctlr->hdev, "Failed to get joycon info; ret=%d\n", ret);
+		return ret;
+	}
+
+	report = (struct joycon_input_report *)ctlr->input_buf;
+
+	for (i = 4, j = 0; j < 6; i++, j++)
+		ctlr->mac_addr[j] = report->reply.data[i];
+
+	ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
+					    "%02X:%02X:%02X:%02X:%02X:%02X",
+					    ctlr->mac_addr[0],
+					    ctlr->mac_addr[1],
+					    ctlr->mac_addr[2],
+					    ctlr->mac_addr[3],
+					    ctlr->mac_addr[4],
+					    ctlr->mac_addr[5]);
+	if (!ctlr->mac_addr_str)
+		return -ENOMEM;
+	hid_info(ctlr->hdev, "controller MAC = %s\n", ctlr->mac_addr_str);
+
+	return 0;
+}
+
 /* Common handler for parsing inputs */
 static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data,
 							      int size)
@@ -1445,6 +1483,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		goto err_mutex;
 	}
 
+	ret = joycon_read_mac(ctlr);
+	if (ret) {
+		hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
+			ret);
+		goto err_close;
+	}
+
 	mutex_unlock(&ctlr->output_mutex);
 
 	/* Initialize the leds */
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 11/12] HID: nintendo: add IMU support
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (9 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 10/12] HID: nintendo: set controller uniq to MAC Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  2019-12-31  6:29   ` Roderick Colenbrander
  2019-12-30  1:27 ` [PATCH v10 12/12] HID: nintendo: add support for charging grip Daniel J. Ogorchock
  11 siblings, 1 reply; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

This patch adds support for the controller's IMU. The accelerometer and
gyro data are both provided to userspace using a second input device.
The devices can be associated using their uniq value (set to the
controller's MAC address).

The majority of this patch's functinoality was provided by Carl Mueller.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
 1 file changed, 259 insertions(+), 8 deletions(-)

diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 7b7a0cf3fe0b..edf2ef140cb3 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -101,12 +101,29 @@ 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)
 
+/* SPI storage addresses of IMU factory calibration data */
+static const u16 JC_IMU_CAL_DATA_START		= 0x6020;
+static const u16 JC_IMU_CAL_DATA_END		= 0x6037;
+#define JC_IMU_CAL_DATA_SIZE \
+			(JC_IMU_CAL_DATA_END - JC_IMU_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;
 
+/* The accel axes will be mapped in terms of this magnitude */
+static const u16 JC_MAX_ACCEL_MAG		= 32767;
+static const u16 JC_ACCEL_RES			= 4096;
+static const u16 JC_ACCEL_FUZZ			= 10;
+static const u16 JC_ACCEL_FLAT			/*= 0*/;
+
+/* The gyro axes will be mapped in terms of this magnitude */
+static const u16 JC_MAX_GYRO_MAG		= 32767;
+static const u16 JC_GYRO_RES			= 13371 / 936; /* 14 (14.285) */
+static const u16 JC_GYRO_FUZZ			= 10;
+static const u16 JC_GYRO_FLAT			/*= 0*/;
+
 /* frequency/amplitude tables for rumble */
 struct joycon_rumble_freq_data {
 	u16 high;
@@ -234,6 +251,11 @@ struct joycon_stick_cal {
 	s32 center;
 };
 
+struct joycon_imu_cal {
+	s16 offset[3];
+	s16 scale[3];
+};
+
 /*
  * All the controller's button values are stored in a u32.
  * They can be accessed with bitwise ANDs.
@@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
 	u8 data[0]; /* will be at most 35 bytes */
 } __packed;
 
+struct joycon_imu_data {
+	s16 accel_x;
+	s16 accel_y;
+	s16 accel_z;
+	s16 gyro_x;
+	s16 gyro_y;
+	s16 gyro_z;
+} __packed;
+
+struct joycon_imu_report {
+	struct joycon_imu_data data[3];
+} __packed;
+
 struct joycon_input_report {
 	u8 id;
 	u8 timer;
@@ -290,11 +325,10 @@ struct joycon_input_report {
 	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;
+	union {
+		struct joycon_subcmd_reply subcmd_reply;
+		struct joycon_imu_report imu_report;
+	};
 } __packed;
 
 #define JC_MAX_RESP_SIZE	(sizeof(struct joycon_input_report) + 35)
@@ -334,6 +368,9 @@ struct joycon_ctlr {
 	struct joycon_stick_cal right_stick_cal_x;
 	struct joycon_stick_cal right_stick_cal_y;
 
+	struct joycon_imu_cal accel_cal;
+	struct joycon_imu_cal gyro_cal;
+
 	/* power supply data */
 	struct power_supply *battery;
 	struct power_supply_desc battery_desc;
@@ -352,6 +389,10 @@ struct joycon_ctlr {
 	u16 rumble_lh_freq;
 	u16 rumble_rl_freq;
 	u16 rumble_rh_freq;
+
+	/* imu */
+	struct input_dev *imu_input;
+	int timestamp;
 };
 
 static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
@@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
 	}
 
 	report = (struct joycon_input_report *)ctlr->input_buf;
-	raw_cal = &report->reply.data[5];
+	raw_cal = &report->subcmd_reply.data[5];
 
 	/* left stick calibration parsing */
 	cal_x = &ctlr->left_stick_cal_x;
@@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
 	return 0;
 }
 
+static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
+static const s16 DFLT_ACCEL_SCALE = 16384;
+static const s16 DFLT_GYRO_OFFSET /*= 0*/;
+static const s16 DFLT_GYRO_SCALE  = 13371;
+static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
+{
+	struct joycon_subcmd_request *req;
+	u8 buffer[sizeof(*req) + 5] = { 0 };
+	struct joycon_input_report *report;
+	u8 *data;
+	u8 *raw_cal;
+	int ret;
+	int i;
+
+	/* request IMU calibration data */
+	req = (struct joycon_subcmd_request *)buffer;
+	req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
+	data = req->data;
+	data[0] = 0xFF & JC_IMU_CAL_DATA_START;
+	data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
+	data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
+	data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
+	data[4] = JC_IMU_CAL_DATA_SIZE;
+
+	hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
+	ret = joycon_send_subcmd(ctlr, req, 5, HZ);
+
+	if (ret) {
+		hid_warn(ctlr->hdev,
+			 "Failed to read IMU cal, using defaults; ret=%d\n",
+			 ret);
+
+		for (i = 0; i < 3; i++) {
+			ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
+			ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
+			ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
+			ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
+		}
+		return ret;
+	}
+
+	report = (struct joycon_input_report *)ctlr->input_buf;
+	raw_cal = &report->subcmd_reply.data[5];
+
+	/* IMU calibration parsing */
+	for (i = 0; i < 3; i++) {
+		int j = i * 2;
+
+		ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
+						((s16)raw_cal[j + 1] << 8);
+		ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
+						((s16)raw_cal[j + 7] << 8);
+		ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
+						((s16)raw_cal[j + 13] << 8);
+		ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
+						((s16)raw_cal[j + 19] << 8);
+	}
+
+	hid_dbg(ctlr->hdev, "IMU calibration:\n"
+			    "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
+			    "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
+			    "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
+			    "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
+			    ctlr->accel_cal.offset[0],
+			    ctlr->accel_cal.offset[1],
+			    ctlr->accel_cal.offset[2],
+			    ctlr->accel_cal.scale[0],
+			    ctlr->accel_cal.scale[1],
+			    ctlr->accel_cal.scale[2],
+			    ctlr->gyro_cal.offset[0],
+			    ctlr->gyro_cal.offset[1],
+			    ctlr->gyro_cal.offset[2],
+			    ctlr->gyro_cal.scale[0],
+			    ctlr->gyro_cal.scale[1],
+			    ctlr->gyro_cal.scale[2]);
+
+	return 0;
+}
+
 static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
 {
 	struct joycon_subcmd_request *req;
@@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
 	return joycon_send_subcmd(ctlr, req, 1, HZ/4);
 }
 
+static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
+{
+	struct joycon_subcmd_request *req;
+	u8 buffer[sizeof(*req) + 1] = { 0 };
+
+	req = (struct joycon_subcmd_request *)buffer;
+	req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
+	req->data[0] = enable ? 0x01 : 0x00;
+
+	hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
+	return joycon_send_subcmd(ctlr, req, 1, HZ);
+}
+
 static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
 {
 	s32 center = cal->center;
@@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 		spin_unlock_irqrestore(&ctlr->lock, flags);
 		wake_up(&ctlr->wait);
 	}
+
+	/* parse IMU data if present */
+	if (rep->id == JC_INPUT_IMU_DATA) {
+		struct joycon_imu_data *imu_data = rep->imu_report.data;
+		struct input_dev *idev = ctlr->imu_input;
+		int i;
+		int value[6];
+
+		for (i = 0; i < 3; i++) { /* there are 3 reports */
+			ctlr->timestamp += 5000; /* each is 5 ms apart */
+			input_event(idev, EV_MSC, MSC_TIMESTAMP,
+				    ctlr->timestamp);
+
+			/*
+			 * Rather than convert to floats, we adjust by
+			 * calibration factors and then multiply by the default
+			 * scaling values. This way, the consumer only needs to
+			 * divide by the default scale values.
+			 */
+			value[0] = (imu_data[i].gyro_x -
+				    ctlr->gyro_cal.offset[0]) *
+				    DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
+			value[1] = (imu_data[i].gyro_y -
+				    ctlr->gyro_cal.offset[1]) *
+				    DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
+			value[2] = (imu_data[i].gyro_z -
+				    ctlr->gyro_cal.offset[2]) *
+				    DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
+
+			value[3] = (imu_data[i].accel_x -
+				    ctlr->accel_cal.offset[0]) *
+				    DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
+			value[4] = (imu_data[i].accel_y -
+				    ctlr->accel_cal.offset[1]) *
+				    DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
+			value[5] = (imu_data[i].accel_z -
+				    ctlr->accel_cal.offset[2]) *
+				    DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
+
+			input_report_abs(idev, ABS_RX, value[0]);
+			input_report_abs(idev, ABS_RY, value[1]);
+			input_report_abs(idev, ABS_RZ, value[2]);
+			input_report_abs(idev, ABS_X, value[3]);
+			input_report_abs(idev, ABS_Y, value[4]);
+			input_report_abs(idev, ABS_Z, value[5]);
+			input_sync(idev);
+		}
+	}
 }
 
 static void joycon_rumble_worker(struct work_struct *work)
@@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 {
 	struct hid_device *hdev;
 	const char *name;
+	const char *imu_name;
 	int ret;
 	int i;
 
@@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 	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_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");
@@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 	if (ret)
 		return ret;
 
+	/* configure the imu input device */
+	ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
+	if (!ctlr->imu_input)
+		return -ENOMEM;
+
+	ctlr->imu_input->id.bustype = hdev->bus;
+	ctlr->imu_input->id.vendor = hdev->vendor;
+	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;
+	input_set_drvdata(ctlr->imu_input, ctlr);
+
+	/* configure imu axes */
+	input_set_abs_params(ctlr->imu_input, ABS_X,
+			     -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
+			     JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
+	input_set_abs_params(ctlr->imu_input, ABS_Y,
+			     -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
+			     JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
+	input_set_abs_params(ctlr->imu_input, ABS_Z,
+			     -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
+			     JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
+	input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
+	input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
+	input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
+
+	input_set_abs_params(ctlr->imu_input, ABS_RX,
+			     -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
+			     JC_GYRO_FUZZ, JC_GYRO_FLAT);
+	input_set_abs_params(ctlr->imu_input, ABS_RY,
+			     -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
+			     JC_GYRO_FUZZ, JC_GYRO_FLAT);
+	input_set_abs_params(ctlr->imu_input, ABS_RZ,
+			     -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
+			     JC_GYRO_FUZZ, JC_GYRO_FLAT);
+
+	input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
+	input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
+	input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
+
+	__set_bit(EV_MSC, ctlr->imu_input->evbit);
+	__set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
+	__set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
+
+	ret = input_register_device(ctlr->imu_input);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
@@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
 	report = (struct joycon_input_report *)ctlr->input_buf;
 
 	for (i = 4, j = 0; j < 6; i++, j++)
-		ctlr->mac_addr[j] = report->reply.data[i];
+		ctlr->mac_addr[j] = report->subcmd_reply.data[i];
 
 	ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
 					    "%02X:%02X:%02X:%02X:%02X:%02X",
@@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
 			    data[0] != JC_INPUT_SUBCMD_REPLY)
 				break;
 			report = (struct joycon_input_report *)data;
-			if (report->reply.id == ctlr->subcmd_ack_match)
+			if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
 				match = true;
 			break;
 		default:
@@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		hid_warn(hdev, "Analog stick positions may be inaccurate\n");
 	}
 
+	/* get IMU calibration data, and parse it */
+	ret = joycon_request_imu_calibration(ctlr);
+	if (ret) {
+		/*
+		 * We can function with default calibration, but it may be
+		 * inaccurate. Provide a warning, and continue on.
+		 */
+		hid_warn(hdev, "Unable to read IMU calibration data\n");
+	}
+
 	/* Set the reporting mode to 0x30, which is the full report mode */
 	ret = joycon_set_report_mode(ctlr);
 	if (ret) {
@@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		goto err_mutex;
 	}
 
+	/* Enable the IMU */
+	ret = joycon_enable_imu(ctlr, true);
+	if (ret) {
+		hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
+		goto err_mutex;
+	}
+
 	ret = joycon_read_mac(ctlr);
 	if (ret) {
 		hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v10 12/12] HID: nintendo: add support for charging grip
  2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
                   ` (10 preceding siblings ...)
  2019-12-30  1:27 ` [PATCH v10 11/12] HID: nintendo: add IMU support Daniel J. Ogorchock
@ 2019-12-30  1:27 ` Daniel J. Ogorchock
  11 siblings, 0 replies; 25+ messages in thread
From: Daniel J. Ogorchock @ 2019-12-30  1:27 UTC (permalink / raw)
  To: linux-input
  Cc: thunderbird2k, blaws05, benjamin.tissoires, jikos,
	Roderick.Colenbrander, svv, s.jegen, carmueller,
	Daniel J. Ogorchock

This patch adds support for the joy-con charging grip. The peripheral
essentially behaves the same as a pro controller, but with two joy-cons
attached to the grip. However the grip exposes the two joy-cons as
separate hid devices, so extra handling is required. The joy-con is
queried to check if it is a right or left joy-con (since the product ID
is identical between left/right when using the grip).

Since controller model detection is now more complicated, the various
checks for hid product values have been replaced with helper macros to
reduce code duplication.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
---
 drivers/hid/hid-ids.h      |  1 +
 drivers/hid/hid-nintendo.c | 87 ++++++++++++++++++++++++++++----------
 2 files changed, 65 insertions(+), 23 deletions(-)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 5e3e872feb29..89807f826e5f 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -871,6 +871,7 @@
 #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_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
index edf2ef140cb3..b86588a2ccc3 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -245,6 +245,13 @@ enum joycon_ctlr_state {
 	JOYCON_CTLR_STATE_REMOVED,
 };
 
+/* 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,
+};
+
 struct joycon_stick_cal {
 	s32 max;
 	s32 min;
@@ -350,6 +357,7 @@ struct joycon_ctlr {
 	spinlock_t lock;
 	u8 mac_addr[6];
 	char *mac_addr_str;
+	enum joycon_ctlr_type ctlr_type;
 
 	/* The following members are used for synchronous sends/receives */
 	enum joycon_msg_type msg_type;
@@ -395,6 +403,26 @@ struct joycon_ctlr {
 	int timestamp;
 };
 
+/* Helper macros for checking controller type */
+#define jc_type_is_joycon(ctlr) \
+	(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL || \
+	 ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR || \
+	 ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP)
+#define jc_type_is_procon(ctlr) \
+	(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_PROCON)
+#define jc_type_is_chrggrip(ctlr) \
+	(ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP)
+
+/* 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)
+
+/* 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)
+
 static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
 {
 	u8 *buf;
@@ -785,7 +813,6 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 	unsigned long flags;
 	u8 tmp;
 	u32 btns;
-	u32 id = ctlr->hdev->product;
 	unsigned long msecs = jiffies_to_msecs(jiffies);
 
 	spin_lock_irqsave(&ctlr->lock, flags);
@@ -824,7 +851,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 	/* Parse the buttons and sticks */
 	btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24);
 
-	if (id != USB_DEVICE_ID_NINTENDO_JOYCONR) {
+	if (jc_type_has_left(ctlr)) {
 		u16 raw_x;
 		u16 raw_y;
 		s32 x;
@@ -844,7 +871,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 		/* report buttons */
 		input_report_key(dev, BTN_TL, btns & JC_BTN_L);
 		input_report_key(dev, BTN_TL2, btns & JC_BTN_ZL);
-		if (id != USB_DEVICE_ID_NINTENDO_PROCON) {
+		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);
@@ -857,7 +884,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 		input_report_key(dev, BTN_DPAD_RIGHT, btns & JC_BTN_RIGHT);
 		input_report_key(dev, BTN_DPAD_LEFT, btns & JC_BTN_LEFT);
 	}
-	if (id != USB_DEVICE_ID_NINTENDO_JOYCONL) {
+	if (jc_type_has_right(ctlr)) {
 		u16 raw_x;
 		u16 raw_y;
 		s32 x;
@@ -877,7 +904,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
 		/* 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) {
+		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);
@@ -1142,6 +1169,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 		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";
@@ -1166,32 +1202,28 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
 	ctlr->input->name = name;
 	input_set_drvdata(ctlr->input, ctlr);
 
-
-	/* set up sticks */
-	if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONR) {
+	if (jc_type_has_left(ctlr)) {
+		/* set up sticks */
 		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);
+		/* set up buttons */
+		for (i = 0; joycon_button_inputs_l[i] > 0; i++)
+			input_set_capability(ctlr->input, EV_KEY,
+					     joycon_button_inputs_l[i]);
 	}
-	if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) {
+	if (jc_type_has_right(ctlr)) {
+		/* set up sticks */
 		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);
-	}
-
-	/* set up buttons */
-	if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONR) {
-		for (i = 0; joycon_button_inputs_l[i] > 0; i++)
-			input_set_capability(ctlr->input, EV_KEY,
-					     joycon_button_inputs_l[i]);
-	}
-	if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) {
+		/* set up buttons */
 		for (i = 0; joycon_button_inputs_r[i] > 0; i++)
 			input_set_capability(ctlr->input, EV_KEY,
 					     joycon_button_inputs_r[i]);
@@ -1392,7 +1424,7 @@ static int joycon_leds_create(struct joycon_ctlr *ctlr)
 	mutex_unlock(&joycon_input_num_mutex);
 
 	/* configure the home LED */
-	if (ctlr->hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) {
+	if (jc_type_has_right(ctlr)) {
 		name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", d_name, "home");
 		if (!name)
 			return ret;
@@ -1504,7 +1536,7 @@ static int joycon_power_supply_create(struct joycon_ctlr *ctlr)
 	return 0;
 }
 
-static int joycon_read_mac(struct joycon_ctlr *ctlr)
+static int joycon_read_info(struct joycon_ctlr *ctlr)
 {
 	int ret;
 	int i;
@@ -1536,6 +1568,9 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
 		return -ENOMEM;
 	hid_info(ctlr->hdev, "controller MAC = %s\n", ctlr->mac_addr_str);
 
+	/* Retrieve the type so we can distinguish for charging grip */
+	ctlr->ctlr_type = report->subcmd_reply.data[2];
+
 	return 0;
 }
 
@@ -1671,7 +1706,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 	/* Initialize the controller */
 	mutex_lock(&ctlr->output_mutex);
 	/* if handshake command fails, assume ble pro controller */
-	if (hdev->product == USB_DEVICE_ID_NINTENDO_PROCON &&
+	if ((jc_type_is_procon(ctlr) || jc_type_is_chrggrip(ctlr)) &&
 	    !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) {
 		hid_dbg(hdev, "detected USB controller\n");
 		/* set baudrate for improved latency */
@@ -1691,6 +1726,10 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		 * This doesn't send a response, so ignore the timeout.
 		 */
 		joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT, HZ/10);
+	} else if (jc_type_is_chrggrip(ctlr)) {
+		hid_err(hdev, "Failed charging grip handshake\n");
+		ret = -ETIMEDOUT;
+		goto err_mutex;
 	}
 
 	/* get controller calibration data, and parse it */
@@ -1734,9 +1773,9 @@ static int nintendo_hid_probe(struct hid_device *hdev,
 		goto err_mutex;
 	}
 
-	ret = joycon_read_mac(ctlr);
+	ret = joycon_read_info(ctlr);
 	if (ret) {
-		hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
+		hid_err(hdev, "Failed to retrieve controller info; ret=%d\n",
 			ret);
 		goto err_close;
 	}
@@ -1804,6 +1843,8 @@ static const struct hid_device_id nintendo_hid_devices[] = {
 			 USB_DEVICE_ID_NINTENDO_PROCON) },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
 			 USB_DEVICE_ID_NINTENDO_PROCON) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+			 USB_DEVICE_ID_NINTENDO_CHRGGRIP) },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
 			 USB_DEVICE_ID_NINTENDO_JOYCONL) },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
-- 
2.24.1


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2019-12-30  1:27 ` [PATCH v10 11/12] HID: nintendo: add IMU support Daniel J. Ogorchock
@ 2019-12-31  6:29   ` Roderick Colenbrander
  2020-01-09  3:26     ` Daniel Ogorchock
  0 siblings, 1 reply; 25+ messages in thread
From: Roderick Colenbrander @ 2019-12-31  6:29 UTC (permalink / raw)
  To: Daniel J. Ogorchock
  Cc: linux-input, Billy Laws, Benjamin Tissoires, Jiri Kosina,
	Colenbrander, Roelof, Siarhei Vishniakou, s.jegen, carmueller

Hi Daniel,

I had some time to review the motion sensors patch. I have added some
feedback inline with your patch below.

Aside from standard feedback on the code, I wanted to have a close
look at the accelerometer / gyro data. My team has been doing a lot of
work on this (and still is) and I want to make sure any work we do on
"user space" software (e.g. Android) automatically works for Joycon as
well. The accuracy of the data is very important else applications
will make bad decisions. Userspace applications often combine the data
of both sensors to calculate a position for position tracking.
Inaccurate axes ranges or wrong precision can cause major issues. I
may be a bit strict below, but it will just be to prevent headaches
for others later on. I use the DS4 as a reference point as it was the
first device (aside from Wii early on) where we properly report
acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
be consistent with it here else applications could be confused, so we
should use similar orientation of axes and resolutions.

Thanks,
Roderick

On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
<djogorchock@gmail.com> wrote:
>
> This patch adds support for the controller's IMU. The accelerometer and
> gyro data are both provided to userspace using a second input device.
> The devices can be associated using their uniq value (set to the
> controller's MAC address).
>
> The majority of this patch's functinoality was provided by Carl Mueller.
>
> Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> ---
>  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
>  1 file changed, 259 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index 7b7a0cf3fe0b..edf2ef140cb3 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
> @@ -101,12 +101,29 @@ 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)
>
> +/* SPI storage addresses of IMU factory calibration data */
> +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> +#define JC_IMU_CAL_DATA_SIZE \
> +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
>
> +/* The accel axes will be mapped in terms of this magnitude */
> +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> +static const u16 JC_ACCEL_RES                  = 4096;

What does the acceleration resolution equate to? For DS4 we use
multiples of 'g'. (We don't know the position on earth, so can't
report in m/s^2).

> +static const u16 JC_ACCEL_FUZZ                 = 10;
> +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> +
> +/* The gyro axes will be mapped in terms of this magnitude */
> +static const u16 JC_MAX_GYRO_MAG               = 32767;
> +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */

What does the gyro resolution equate to? For DS4 we report "degrees
per second". We should do the same for the joycons, but I don't know
how you guys figured out the exact resolution. As I mentioned if it is
not accurate, applications will make wrong calculations.

> +static const u16 JC_GYRO_FUZZ                  = 10;
> +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> +
>  /* frequency/amplitude tables for rumble */
>  struct joycon_rumble_freq_data {
>         u16 high;
> @@ -234,6 +251,11 @@ struct joycon_stick_cal {
>         s32 center;
>  };
>
> +struct joycon_imu_cal {
> +       s16 offset[3];
> +       s16 scale[3];
> +};
> +
>  /*
>   * All the controller's button values are stored in a u32.
>   * They can be accessed with bitwise ANDs.
> @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
>         u8 data[0]; /* will be at most 35 bytes */
>  } __packed;
>
> +struct joycon_imu_data {
> +       s16 accel_x;
> +       s16 accel_y;
> +       s16 accel_z;
> +       s16 gyro_x;
> +       s16 gyro_y;
> +       s16 gyro_z;
> +} __packed;
> +
> +struct joycon_imu_report {
> +       struct joycon_imu_data data[3];
> +} __packed;

See comments later on about imu_data, but you can't directly cast your
data buffer to this data type due to endian issues. It may not really
make sense to keep the data structure as you need to do manual data
fetching.

> +
>  struct joycon_input_report {
>         u8 id;
>         u8 timer;
> @@ -290,11 +325,10 @@ struct joycon_input_report {
>         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;
> +       union {
> +               struct joycon_subcmd_reply subcmd_reply;
> +               struct joycon_imu_report imu_report;

maybe add a raw byte array to this union. Could help for parsing the imu data.
> +       };
>  } __packed;
>
>  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> @@ -334,6 +368,9 @@ struct joycon_ctlr {
>         struct joycon_stick_cal right_stick_cal_x;
>         struct joycon_stick_cal right_stick_cal_y;
>
> +       struct joycon_imu_cal accel_cal;
> +       struct joycon_imu_cal gyro_cal;
> +
>         /* power supply data */
>         struct power_supply *battery;
>         struct power_supply_desc battery_desc;
> @@ -352,6 +389,10 @@ struct joycon_ctlr {
>         u16 rumble_lh_freq;
>         u16 rumble_rl_freq;
>         u16 rumble_rh_freq;
> +
> +       /* imu */
> +       struct input_dev *imu_input;
> +       int timestamp;
>  };
>
>  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
>         }
>
>         report = (struct joycon_input_report *)ctlr->input_buf;
> -       raw_cal = &report->reply.data[5];
> +       raw_cal = &report->subcmd_reply.data[5];
>
>         /* left stick calibration parsing */
>         cal_x = &ctlr->left_stick_cal_x;
> @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
>         return 0;
>  }
>
> +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> +static const s16 DFLT_ACCEL_SCALE = 16384;
> +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> +static const s16 DFLT_GYRO_SCALE  = 13371;
> +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> +{
> +       struct joycon_subcmd_request *req;
> +       u8 buffer[sizeof(*req) + 5] = { 0 };
> +       struct joycon_input_report *report;
> +       u8 *data;
> +       u8 *raw_cal;
> +       int ret;
> +       int i;
> +
> +       /* request IMU calibration data */
> +       req = (struct joycon_subcmd_request *)buffer;
> +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> +       data = req->data;
> +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);

Maybe use put_unaligned_le32? I think it allows you to avoid doing all
these calculations yourself:
put_unaligned_le32(JC_IMU_CAL_DATA_START, data);

> +       data[4] = JC_IMU_CAL_DATA_SIZE;
> +
> +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> +
> +       if (ret) {
> +               hid_warn(ctlr->hdev,
> +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> +                        ret);
> +
> +               for (i = 0; i < 3; i++) {
> +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> +               }
> +               return ret;
> +       }
> +
> +       report = (struct joycon_input_report *)ctlr->input_buf;
> +       raw_cal = &report->subcmd_reply.data[5];
> +
> +       /* IMU calibration parsing */
> +       for (i = 0; i < 3; i++) {
> +               int j = i * 2;
> +
> +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> +                                               ((s16)raw_cal[j + 1] << 8);
> +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> +                                               ((s16)raw_cal[j + 7] << 8);
> +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> +                                               ((s16)raw_cal[j + 13] << 8);
> +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> +                                               ((s16)raw_cal[j + 19] << 8);

get_unaligned_le16 instead of doing your own bitshifts?
> +       }
> +
> +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> +                           ctlr->accel_cal.offset[0],
> +                           ctlr->accel_cal.offset[1],
> +                           ctlr->accel_cal.offset[2],
> +                           ctlr->accel_cal.scale[0],
> +                           ctlr->accel_cal.scale[1],
> +                           ctlr->accel_cal.scale[2],
> +                           ctlr->gyro_cal.offset[0],
> +                           ctlr->gyro_cal.offset[1],
> +                           ctlr->gyro_cal.offset[2],
> +                           ctlr->gyro_cal.scale[0],
> +                           ctlr->gyro_cal.scale[1],
> +                           ctlr->gyro_cal.scale[2]);
> +
> +       return 0;
> +}
> +
>  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
>  {
>         struct joycon_subcmd_request *req;
> @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
>         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
>  }
>
> +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> +{
> +       struct joycon_subcmd_request *req;
> +       u8 buffer[sizeof(*req) + 1] = { 0 };
> +
> +       req = (struct joycon_subcmd_request *)buffer;
> +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> +       req->data[0] = enable ? 0x01 : 0x00;
> +
> +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> +}
> +
>  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
>  {
>         s32 center = cal->center;
> @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
>                 spin_unlock_irqrestore(&ctlr->lock, flags);
>                 wake_up(&ctlr->wait);
>         }
> +
> +       /* parse IMU data if present */
> +       if (rep->id == JC_INPUT_IMU_DATA) {
> +               struct joycon_imu_data *imu_data = rep->imu_report.data;

I don't think you can directly covert your input report data to
imu_data. Until now you have been lucky enough (based on a quick
glance of the the other patches) that your data are single bytes.
However, this data seems to be a bunch of s16's. In other words you
have to deal with endianess issues. You need to use get_unaligned_le16
to retrieve data from your raw byte buffer. See other HID drivers for
reference.

Since you will have to use get_unaligned_le16, it probably won't make
much sense to really have a joycon_imu_data structure. Maybe extend
your input_buffer union with raw bytes and in this case just use raw
bytes. Each of your loop iterations below just grab the values from
the buffer and store them in a variable.

> +               struct input_dev *idev = ctlr->imu_input;
> +               int i;
> +               int value[6];
> +
> +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> +                                   ctlr->timestamp);

How sure are you that this is always 5ms? Is there a hardware
timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
(Bluetooth is lossy) and not all packets even make it.

> +
> +                       /*
> +                        * Rather than convert to floats, we adjust by
> +                        * calibration factors and then multiply by the default
> +                        * scaling values. This way, the consumer only needs to
> +                        * divide by the default scale values.
> +                        */
> +                       value[0] = (imu_data[i].gyro_x -
> +                                   ctlr->gyro_cal.offset[0]) *
> +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> +                       value[1] = (imu_data[i].gyro_y -
> +                                   ctlr->gyro_cal.offset[1]) *
> +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> +                       value[2] = (imu_data[i].gyro_z -
> +                                   ctlr->gyro_cal.offset[2]) *
> +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> +
> +                       value[3] = (imu_data[i].accel_x -
> +                                   ctlr->accel_cal.offset[0]) *
> +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> +                       value[4] = (imu_data[i].accel_y -
> +                                   ctlr->accel_cal.offset[1]) *
> +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> +                       value[5] = (imu_data[i].accel_z -
> +                                   ctlr->accel_cal.offset[2]) *
> +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];

Just in case I would double check the precision of your calculations.
For DS4 we had similar calculations, but we had a small loss of
precision, which ultimately caused major calculation errors.
(Applications often use accelerometer + gyro data to calculate an
absolute position. This involves  integration.. and small errors
become big). We had to use the "mult_frac" macro to restore some of
the precision during the calculations. It might be something to double
check.

In addition what orientation are you using for the axes? I need to
double check the DS4 datasheets, but I think we were using a "right
handed" coordinate system. (So if you make a fist of your right hand
with thumb up, the gyro curls around it counter clockwise along the
direction of your fingers).


> +
> +                       input_report_abs(idev, ABS_RX, value[0]);
> +                       input_report_abs(idev, ABS_RY, value[1]);
> +                       input_report_abs(idev, ABS_RZ, value[2]);
> +                       input_report_abs(idev, ABS_X, value[3]);
> +                       input_report_abs(idev, ABS_Y, value[4]);
> +                       input_report_abs(idev, ABS_Z, value[5]);
> +                       input_sync(idev);
> +               }
> +       }
>  }
>
>  static void joycon_rumble_worker(struct work_struct *work)
> @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
>  {
>         struct hid_device *hdev;
>         const char *name;
> +       const char *imu_name;
>         int ret;
>         int i;
>
> @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
>         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_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");
> @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
>         if (ret)
>                 return ret;
>
> +       /* configure the imu input device */
> +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> +       if (!ctlr->imu_input)
> +               return -ENOMEM;
> +
> +       ctlr->imu_input->id.bustype = hdev->bus;
> +       ctlr->imu_input->id.vendor = hdev->vendor;
> +       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;
> +       input_set_drvdata(ctlr->imu_input, ctlr);
> +
> +       /* configure imu axes */
> +       input_set_abs_params(ctlr->imu_input, ABS_X,
> +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> +
> +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> +
> +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> +
> +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> +
> +       ret = input_register_device(ctlr->imu_input);
> +       if (ret)
> +               return ret;
> +
>         return 0;
>  }
>
> @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
>         report = (struct joycon_input_report *)ctlr->input_buf;
>
>         for (i = 4, j = 0; j < 6; i++, j++)
> -               ctlr->mac_addr[j] = report->reply.data[i];
> +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
>
>         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
>                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
>                             data[0] != JC_INPUT_SUBCMD_REPLY)
>                                 break;
>                         report = (struct joycon_input_report *)data;
> -                       if (report->reply.id == ctlr->subcmd_ack_match)
> +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
>                                 match = true;
>                         break;
>                 default:
> @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
>                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
>         }
>
> +       /* get IMU calibration data, and parse it */
> +       ret = joycon_request_imu_calibration(ctlr);
> +       if (ret) {
> +               /*
> +                * We can function with default calibration, but it may be
> +                * inaccurate. Provide a warning, and continue on.
> +                */
> +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> +       }
> +
>         /* Set the reporting mode to 0x30, which is the full report mode */
>         ret = joycon_set_report_mode(ctlr);
>         if (ret) {
> @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
>                 goto err_mutex;
>         }
>
> +       /* Enable the IMU */
> +       ret = joycon_enable_imu(ctlr, true);
> +       if (ret) {
> +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> +               goto err_mutex;
> +       }
> +
>         ret = joycon_read_mac(ctlr);
>         if (ret) {
>                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> --
> 2.24.1
>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2019-12-31  6:29   ` Roderick Colenbrander
@ 2020-01-09  3:26     ` Daniel Ogorchock
  2020-01-09  5:22       ` Roderick Colenbrander
  0 siblings, 1 reply; 25+ messages in thread
From: Daniel Ogorchock @ 2020-01-09  3:26 UTC (permalink / raw)
  To: Roderick Colenbrander
  Cc: linux-input, Billy Laws, Benjamin Tissoires, Jiri Kosina,
	Colenbrander, Roelof, Siarhei Vishniakou, s.jegen, Carl Mueller

Hi Roderick,

Thanks for the feedback. In addition to the inline comments below,
do you have any recommendations for test programs that you
know work well with hid-sony's gyro implementation? Up to this
point I've just been using evtest, which obviously isn't too useful
for testing actual functionality of the gyro in an intuitive way.

On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
<thunderbird2k@gmail.com> wrote:
>
> Hi Daniel,
>
> I had some time to review the motion sensors patch. I have added some
> feedback inline with your patch below.
>
> Aside from standard feedback on the code, I wanted to have a close
> look at the accelerometer / gyro data. My team has been doing a lot of
> work on this (and still is) and I want to make sure any work we do on
> "user space" software (e.g. Android) automatically works for Joycon as
> well. The accuracy of the data is very important else applications
> will make bad decisions. Userspace applications often combine the data
> of both sensors to calculate a position for position tracking.
> Inaccurate axes ranges or wrong precision can cause major issues. I
> may be a bit strict below, but it will just be to prevent headaches
> for others later on. I use the DS4 as a reference point as it was the
> first device (aside from Wii early on) where we properly report
> acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> be consistent with it here else applications could be confused, so we
> should use similar orientation of axes and resolutions.
>
> Thanks,
> Roderick
>
> On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> <djogorchock@gmail.com> wrote:
> >
> > This patch adds support for the controller's IMU. The accelerometer and
> > gyro data are both provided to userspace using a second input device.
> > The devices can be associated using their uniq value (set to the
> > controller's MAC address).
> >
> > The majority of this patch's functinoality was provided by Carl Mueller.
> >
> > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > ---
> >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> >  1 file changed, 259 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > --- a/drivers/hid/hid-nintendo.c
> > +++ b/drivers/hid/hid-nintendo.c
> > @@ -101,12 +101,29 @@ 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)
> >
> > +/* SPI storage addresses of IMU factory calibration data */
> > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > +#define JC_IMU_CAL_DATA_SIZE \
> > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> >
> > +/* The accel axes will be mapped in terms of this magnitude */
> > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > +static const u16 JC_ACCEL_RES                  = 4096;
>
> What does the acceleration resolution equate to? For DS4 we use
> multiples of 'g'. (We don't know the position on earth, so can't
> report in m/s^2).
>
> > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > +
> > +/* The gyro axes will be mapped in terms of this magnitude */
> > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
>
> What does the gyro resolution equate to? For DS4 we report "degrees
> per second". We should do the same for the joycons, but I don't know
> how you guys figured out the exact resolution. As I mentioned if it is
> not accurate, applications will make wrong calculations.
>
> > +static const u16 JC_GYRO_FUZZ                  = 10;
> > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > +
> >  /* frequency/amplitude tables for rumble */
> >  struct joycon_rumble_freq_data {
> >         u16 high;
> > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> >         s32 center;
> >  };
> >
> > +struct joycon_imu_cal {
> > +       s16 offset[3];
> > +       s16 scale[3];
> > +};
> > +
> >  /*
> >   * All the controller's button values are stored in a u32.
> >   * They can be accessed with bitwise ANDs.
> > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> >         u8 data[0]; /* will be at most 35 bytes */
> >  } __packed;
> >
> > +struct joycon_imu_data {
> > +       s16 accel_x;
> > +       s16 accel_y;
> > +       s16 accel_z;
> > +       s16 gyro_x;
> > +       s16 gyro_y;
> > +       s16 gyro_z;
> > +} __packed;
> > +
> > +struct joycon_imu_report {
> > +       struct joycon_imu_data data[3];
> > +} __packed;
>
> See comments later on about imu_data, but you can't directly cast your
> data buffer to this data type due to endian issues. It may not really
> make sense to keep the data structure as you need to do manual data
> fetching.
>
> > +
> >  struct joycon_input_report {
> >         u8 id;
> >         u8 timer;
> > @@ -290,11 +325,10 @@ struct joycon_input_report {
> >         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;
> > +       union {
> > +               struct joycon_subcmd_reply subcmd_reply;
> > +               struct joycon_imu_report imu_report;
>
> maybe add a raw byte array to this union. Could help for parsing the imu data.
> > +       };
> >  } __packed;
> >
> >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> >         struct joycon_stick_cal right_stick_cal_x;
> >         struct joycon_stick_cal right_stick_cal_y;
> >
> > +       struct joycon_imu_cal accel_cal;
> > +       struct joycon_imu_cal gyro_cal;
> > +
> >         /* power supply data */
> >         struct power_supply *battery;
> >         struct power_supply_desc battery_desc;
> > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> >         u16 rumble_lh_freq;
> >         u16 rumble_rl_freq;
> >         u16 rumble_rh_freq;
> > +
> > +       /* imu */
> > +       struct input_dev *imu_input;
> > +       int timestamp;
> >  };
> >
> >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> >         }
> >
> >         report = (struct joycon_input_report *)ctlr->input_buf;
> > -       raw_cal = &report->reply.data[5];
> > +       raw_cal = &report->subcmd_reply.data[5];
> >
> >         /* left stick calibration parsing */
> >         cal_x = &ctlr->left_stick_cal_x;
> > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> >         return 0;
> >  }
> >
> > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > +{
> > +       struct joycon_subcmd_request *req;
> > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > +       struct joycon_input_report *report;
> > +       u8 *data;
> > +       u8 *raw_cal;
> > +       int ret;
> > +       int i;
> > +
> > +       /* request IMU calibration data */
> > +       req = (struct joycon_subcmd_request *)buffer;
> > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > +       data = req->data;
> > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
>
> Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> these calculations yourself:
> put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
>
> > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > +
> > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > +
> > +       if (ret) {
> > +               hid_warn(ctlr->hdev,
> > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > +                        ret);
> > +
> > +               for (i = 0; i < 3; i++) {
> > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > +               }
> > +               return ret;
> > +       }
> > +
> > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > +       raw_cal = &report->subcmd_reply.data[5];
> > +
> > +       /* IMU calibration parsing */
> > +       for (i = 0; i < 3; i++) {
> > +               int j = i * 2;
> > +
> > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > +                                               ((s16)raw_cal[j + 1] << 8);
> > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > +                                               ((s16)raw_cal[j + 7] << 8);
> > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > +                                               ((s16)raw_cal[j + 13] << 8);
> > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > +                                               ((s16)raw_cal[j + 19] << 8);
>
> get_unaligned_le16 instead of doing your own bitshifts?
> > +       }
> > +
> > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > +                           ctlr->accel_cal.offset[0],
> > +                           ctlr->accel_cal.offset[1],
> > +                           ctlr->accel_cal.offset[2],
> > +                           ctlr->accel_cal.scale[0],
> > +                           ctlr->accel_cal.scale[1],
> > +                           ctlr->accel_cal.scale[2],
> > +                           ctlr->gyro_cal.offset[0],
> > +                           ctlr->gyro_cal.offset[1],
> > +                           ctlr->gyro_cal.offset[2],
> > +                           ctlr->gyro_cal.scale[0],
> > +                           ctlr->gyro_cal.scale[1],
> > +                           ctlr->gyro_cal.scale[2]);
> > +
> > +       return 0;
> > +}
> > +
> >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> >  {
> >         struct joycon_subcmd_request *req;
> > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> >  }
> >
> > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > +{
> > +       struct joycon_subcmd_request *req;
> > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > +
> > +       req = (struct joycon_subcmd_request *)buffer;
> > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > +       req->data[0] = enable ? 0x01 : 0x00;
> > +
> > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > +}
> > +
> >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> >  {
> >         s32 center = cal->center;
> > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> >                 wake_up(&ctlr->wait);
> >         }
> > +
> > +       /* parse IMU data if present */
> > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
>
> I don't think you can directly covert your input report data to
> imu_data. Until now you have been lucky enough (based on a quick
> glance of the the other patches) that your data are single bytes.
> However, this data seems to be a bunch of s16's. In other words you
> have to deal with endianess issues. You need to use get_unaligned_le16
> to retrieve data from your raw byte buffer. See other HID drivers for
> reference.

Ah, good point. I'd overlooked that entirely.

>
> Since you will have to use get_unaligned_le16, it probably won't make
> much sense to really have a joycon_imu_data structure. Maybe extend
> your input_buffer union with raw bytes and in this case just use raw
> bytes. Each of your loop iterations below just grab the values from
> the buffer and store them in a variable.
>
> > +               struct input_dev *idev = ctlr->imu_input;
> > +               int i;
> > +               int value[6];
> > +
> > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > +                                   ctlr->timestamp);
>
> How sure are you that this is always 5ms? Is there a hardware
> timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> (Bluetooth is lossy) and not all packets even make it.
>

It appears that the closest thing to a hardware timestamp available is a
quickly incrementing 1-byte timer sent with every report. It's only really
useful for latency estimation. Each input report includes 3 IMU samples
which are 5ms apart for the joy-cons. It's recently come to my attention
that the samples may be spaced differently for the pro controller, so I'm
going to need to dive into this more anyway. I'm not sure what a great
way would be to handle lost reports.

> > +
> > +                       /*
> > +                        * Rather than convert to floats, we adjust by
> > +                        * calibration factors and then multiply by the default
> > +                        * scaling values. This way, the consumer only needs to
> > +                        * divide by the default scale values.
> > +                        */
> > +                       value[0] = (imu_data[i].gyro_x -
> > +                                   ctlr->gyro_cal.offset[0]) *
> > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > +                       value[1] = (imu_data[i].gyro_y -
> > +                                   ctlr->gyro_cal.offset[1]) *
> > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > +                       value[2] = (imu_data[i].gyro_z -
> > +                                   ctlr->gyro_cal.offset[2]) *
> > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > +
> > +                       value[3] = (imu_data[i].accel_x -
> > +                                   ctlr->accel_cal.offset[0]) *
> > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > +                       value[4] = (imu_data[i].accel_y -
> > +                                   ctlr->accel_cal.offset[1]) *
> > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > +                       value[5] = (imu_data[i].accel_z -
> > +                                   ctlr->accel_cal.offset[2]) *
> > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
>
> Just in case I would double check the precision of your calculations.
> For DS4 we had similar calculations, but we had a small loss of
> precision, which ultimately caused major calculation errors.
> (Applications often use accelerometer + gyro data to calculate an
> absolute position. This involves  integration.. and small errors
> become big). We had to use the "mult_frac" macro to restore some of
> the precision during the calculations. It might be something to double
> check.
>

That's good to know. I'll look more closely at how it's implemented in
hid-sony.

> In addition what orientation are you using for the axes? I need to
> double check the DS4 datasheets, but I think we were using a "right
> handed" coordinate system. (So if you make a fist of your right hand
> with thumb up, the gyro curls around it counter clockwise along the
> direction of your fingers).
>

The orientation of the axes (and much more) are documented here:
https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
Since the the right vs. left joy-cons have different axes orientations, I
assume it's preferable to standardize it all in software to match the
orientation of the DS4.

>
> > +
> > +                       input_report_abs(idev, ABS_RX, value[0]);
> > +                       input_report_abs(idev, ABS_RY, value[1]);
> > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > +                       input_report_abs(idev, ABS_X, value[3]);
> > +                       input_report_abs(idev, ABS_Y, value[4]);
> > +                       input_report_abs(idev, ABS_Z, value[5]);
> > +                       input_sync(idev);
> > +               }
> > +       }
> >  }
> >
> >  static void joycon_rumble_worker(struct work_struct *work)
> > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> >  {
> >         struct hid_device *hdev;
> >         const char *name;
> > +       const char *imu_name;
> >         int ret;
> >         int i;
> >
> > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> >         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_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");
> > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> >         if (ret)
> >                 return ret;
> >
> > +       /* configure the imu input device */
> > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > +       if (!ctlr->imu_input)
> > +               return -ENOMEM;
> > +
> > +       ctlr->imu_input->id.bustype = hdev->bus;
> > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > +       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;
> > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > +
> > +       /* configure imu axes */
> > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > +
> > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > +
> > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > +
> > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > +
> > +       ret = input_register_device(ctlr->imu_input);
> > +       if (ret)
> > +               return ret;
> > +
> >         return 0;
> >  }
> >
> > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> >         report = (struct joycon_input_report *)ctlr->input_buf;
> >
> >         for (i = 4, j = 0; j < 6; i++, j++)
> > -               ctlr->mac_addr[j] = report->reply.data[i];
> > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> >
> >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> >                                 break;
> >                         report = (struct joycon_input_report *)data;
> > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> >                                 match = true;
> >                         break;
> >                 default:
> > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> >         }
> >
> > +       /* get IMU calibration data, and parse it */
> > +       ret = joycon_request_imu_calibration(ctlr);
> > +       if (ret) {
> > +               /*
> > +                * We can function with default calibration, but it may be
> > +                * inaccurate. Provide a warning, and continue on.
> > +                */
> > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > +       }
> > +
> >         /* Set the reporting mode to 0x30, which is the full report mode */
> >         ret = joycon_set_report_mode(ctlr);
> >         if (ret) {
> > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> >                 goto err_mutex;
> >         }
> >
> > +       /* Enable the IMU */
> > +       ret = joycon_enable_imu(ctlr, true);
> > +       if (ret) {
> > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > +               goto err_mutex;
> > +       }
> > +
> >         ret = joycon_read_mac(ctlr);
> >         if (ret) {
> >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > --
> > 2.24.1
> >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-09  3:26     ` Daniel Ogorchock
@ 2020-01-09  5:22       ` Roderick Colenbrander
  2020-01-09  6:23         ` Carl Mueller
  2020-01-09  8:55         ` Daniel Ogorchock
  0 siblings, 2 replies; 25+ messages in thread
From: Roderick Colenbrander @ 2020-01-09  5:22 UTC (permalink / raw)
  To: Daniel Ogorchock
  Cc: linux-input, Billy Laws, Benjamin Tissoires, Jiri Kosina,
	Colenbrander, Roelof, Siarhei Vishniakou, s.jegen, Carl Mueller

Hi Daniel,

Unfortunately there is no public test application at the moment to
illustrate these features. I agree something is definitely needed.

I need to see if we can come up with something. One of the test apps
we have internally is a 3D cube, which is controllable by controller
motion. It of course shows the gyro / accelerometer values, but the
position of the cube is tied to the calculated orientation. If
something is off you will see weird movements in the cube or drift
building up over time etcetera.

Though it would take some time to prepare something. The rest of the
patch series looked fine I think, so the IMU parts may need to wait
for a next kernel cycle, but all the other plumbing could go in (if
others are okay of course).

Thanks,
Roderick

On Wed, Jan 8, 2020 at 7:26 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
>
> Hi Roderick,
>
> Thanks for the feedback. In addition to the inline comments below,
> do you have any recommendations for test programs that you
> know work well with hid-sony's gyro implementation? Up to this
> point I've just been using evtest, which obviously isn't too useful
> for testing actual functionality of the gyro in an intuitive way.
>
> On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
> <thunderbird2k@gmail.com> wrote:
> >
> > Hi Daniel,
> >
> > I had some time to review the motion sensors patch. I have added some
> > feedback inline with your patch below.
> >
> > Aside from standard feedback on the code, I wanted to have a close
> > look at the accelerometer / gyro data. My team has been doing a lot of
> > work on this (and still is) and I want to make sure any work we do on
> > "user space" software (e.g. Android) automatically works for Joycon as
> > well. The accuracy of the data is very important else applications
> > will make bad decisions. Userspace applications often combine the data
> > of both sensors to calculate a position for position tracking.
> > Inaccurate axes ranges or wrong precision can cause major issues. I
> > may be a bit strict below, but it will just be to prevent headaches
> > for others later on. I use the DS4 as a reference point as it was the
> > first device (aside from Wii early on) where we properly report
> > acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> > be consistent with it here else applications could be confused, so we
> > should use similar orientation of axes and resolutions.
> >
> > Thanks,
> > Roderick
> >
> > On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> > <djogorchock@gmail.com> wrote:
> > >
> > > This patch adds support for the controller's IMU. The accelerometer and
> > > gyro data are both provided to userspace using a second input device.
> > > The devices can be associated using their uniq value (set to the
> > > controller's MAC address).
> > >
> > > The majority of this patch's functinoality was provided by Carl Mueller.
> > >
> > > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > > ---
> > >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> > >  1 file changed, 259 insertions(+), 8 deletions(-)
> > >
> > > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > > --- a/drivers/hid/hid-nintendo.c
> > > +++ b/drivers/hid/hid-nintendo.c
> > > @@ -101,12 +101,29 @@ 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)
> > >
> > > +/* SPI storage addresses of IMU factory calibration data */
> > > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > > +#define JC_IMU_CAL_DATA_SIZE \
> > > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> > >
> > > +/* The accel axes will be mapped in terms of this magnitude */
> > > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > > +static const u16 JC_ACCEL_RES                  = 4096;
> >
> > What does the acceleration resolution equate to? For DS4 we use
> > multiples of 'g'. (We don't know the position on earth, so can't
> > report in m/s^2).
> >
> > > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > > +
> > > +/* The gyro axes will be mapped in terms of this magnitude */
> > > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
> >
> > What does the gyro resolution equate to? For DS4 we report "degrees
> > per second". We should do the same for the joycons, but I don't know
> > how you guys figured out the exact resolution. As I mentioned if it is
> > not accurate, applications will make wrong calculations.
> >
> > > +static const u16 JC_GYRO_FUZZ                  = 10;
> > > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > > +
> > >  /* frequency/amplitude tables for rumble */
> > >  struct joycon_rumble_freq_data {
> > >         u16 high;
> > > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> > >         s32 center;
> > >  };
> > >
> > > +struct joycon_imu_cal {
> > > +       s16 offset[3];
> > > +       s16 scale[3];
> > > +};
> > > +
> > >  /*
> > >   * All the controller's button values are stored in a u32.
> > >   * They can be accessed with bitwise ANDs.
> > > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> > >         u8 data[0]; /* will be at most 35 bytes */
> > >  } __packed;
> > >
> > > +struct joycon_imu_data {
> > > +       s16 accel_x;
> > > +       s16 accel_y;
> > > +       s16 accel_z;
> > > +       s16 gyro_x;
> > > +       s16 gyro_y;
> > > +       s16 gyro_z;
> > > +} __packed;
> > > +
> > > +struct joycon_imu_report {
> > > +       struct joycon_imu_data data[3];
> > > +} __packed;
> >
> > See comments later on about imu_data, but you can't directly cast your
> > data buffer to this data type due to endian issues. It may not really
> > make sense to keep the data structure as you need to do manual data
> > fetching.
> >
> > > +
> > >  struct joycon_input_report {
> > >         u8 id;
> > >         u8 timer;
> > > @@ -290,11 +325,10 @@ struct joycon_input_report {
> > >         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;
> > > +       union {
> > > +               struct joycon_subcmd_reply subcmd_reply;
> > > +               struct joycon_imu_report imu_report;
> >
> > maybe add a raw byte array to this union. Could help for parsing the imu data.
> > > +       };
> > >  } __packed;
> > >
> > >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> > >         struct joycon_stick_cal right_stick_cal_x;
> > >         struct joycon_stick_cal right_stick_cal_y;
> > >
> > > +       struct joycon_imu_cal accel_cal;
> > > +       struct joycon_imu_cal gyro_cal;
> > > +
> > >         /* power supply data */
> > >         struct power_supply *battery;
> > >         struct power_supply_desc battery_desc;
> > > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> > >         u16 rumble_lh_freq;
> > >         u16 rumble_rl_freq;
> > >         u16 rumble_rh_freq;
> > > +
> > > +       /* imu */
> > > +       struct input_dev *imu_input;
> > > +       int timestamp;
> > >  };
> > >
> > >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > >         }
> > >
> > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > -       raw_cal = &report->reply.data[5];
> > > +       raw_cal = &report->subcmd_reply.data[5];
> > >
> > >         /* left stick calibration parsing */
> > >         cal_x = &ctlr->left_stick_cal_x;
> > > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > >         return 0;
> > >  }
> > >
> > > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > > +{
> > > +       struct joycon_subcmd_request *req;
> > > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > > +       struct joycon_input_report *report;
> > > +       u8 *data;
> > > +       u8 *raw_cal;
> > > +       int ret;
> > > +       int i;
> > > +
> > > +       /* request IMU calibration data */
> > > +       req = (struct joycon_subcmd_request *)buffer;
> > > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > > +       data = req->data;
> > > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
> >
> > Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> > these calculations yourself:
> > put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
> >
> > > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > > +
> > > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > > +
> > > +       if (ret) {
> > > +               hid_warn(ctlr->hdev,
> > > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > > +                        ret);
> > > +
> > > +               for (i = 0; i < 3; i++) {
> > > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > > +               }
> > > +               return ret;
> > > +       }
> > > +
> > > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > > +       raw_cal = &report->subcmd_reply.data[5];
> > > +
> > > +       /* IMU calibration parsing */
> > > +       for (i = 0; i < 3; i++) {
> > > +               int j = i * 2;
> > > +
> > > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > > +                                               ((s16)raw_cal[j + 1] << 8);
> > > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > > +                                               ((s16)raw_cal[j + 7] << 8);
> > > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > > +                                               ((s16)raw_cal[j + 13] << 8);
> > > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > > +                                               ((s16)raw_cal[j + 19] << 8);
> >
> > get_unaligned_le16 instead of doing your own bitshifts?
> > > +       }
> > > +
> > > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > > +                           ctlr->accel_cal.offset[0],
> > > +                           ctlr->accel_cal.offset[1],
> > > +                           ctlr->accel_cal.offset[2],
> > > +                           ctlr->accel_cal.scale[0],
> > > +                           ctlr->accel_cal.scale[1],
> > > +                           ctlr->accel_cal.scale[2],
> > > +                           ctlr->gyro_cal.offset[0],
> > > +                           ctlr->gyro_cal.offset[1],
> > > +                           ctlr->gyro_cal.offset[2],
> > > +                           ctlr->gyro_cal.scale[0],
> > > +                           ctlr->gyro_cal.scale[1],
> > > +                           ctlr->gyro_cal.scale[2]);
> > > +
> > > +       return 0;
> > > +}
> > > +
> > >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> > >  {
> > >         struct joycon_subcmd_request *req;
> > > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> > >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> > >  }
> > >
> > > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > > +{
> > > +       struct joycon_subcmd_request *req;
> > > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > > +
> > > +       req = (struct joycon_subcmd_request *)buffer;
> > > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > > +       req->data[0] = enable ? 0x01 : 0x00;
> > > +
> > > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > > +}
> > > +
> > >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> > >  {
> > >         s32 center = cal->center;
> > > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> > >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> > >                 wake_up(&ctlr->wait);
> > >         }
> > > +
> > > +       /* parse IMU data if present */
> > > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
> >
> > I don't think you can directly covert your input report data to
> > imu_data. Until now you have been lucky enough (based on a quick
> > glance of the the other patches) that your data are single bytes.
> > However, this data seems to be a bunch of s16's. In other words you
> > have to deal with endianess issues. You need to use get_unaligned_le16
> > to retrieve data from your raw byte buffer. See other HID drivers for
> > reference.
>
> Ah, good point. I'd overlooked that entirely.
>
> >
> > Since you will have to use get_unaligned_le16, it probably won't make
> > much sense to really have a joycon_imu_data structure. Maybe extend
> > your input_buffer union with raw bytes and in this case just use raw
> > bytes. Each of your loop iterations below just grab the values from
> > the buffer and store them in a variable.
> >
> > > +               struct input_dev *idev = ctlr->imu_input;
> > > +               int i;
> > > +               int value[6];
> > > +
> > > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > > +                                   ctlr->timestamp);
> >
> > How sure are you that this is always 5ms? Is there a hardware
> > timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> > (Bluetooth is lossy) and not all packets even make it.
> >
>
> It appears that the closest thing to a hardware timestamp available is a
> quickly incrementing 1-byte timer sent with every report. It's only really
> useful for latency estimation. Each input report includes 3 IMU samples
> which are 5ms apart for the joy-cons. It's recently come to my attention
> that the samples may be spaced differently for the pro controller, so I'm
> going to need to dive into this more anyway. I'm not sure what a great
> way would be to handle lost reports.
>
> > > +
> > > +                       /*
> > > +                        * Rather than convert to floats, we adjust by
> > > +                        * calibration factors and then multiply by the default
> > > +                        * scaling values. This way, the consumer only needs to
> > > +                        * divide by the default scale values.
> > > +                        */
> > > +                       value[0] = (imu_data[i].gyro_x -
> > > +                                   ctlr->gyro_cal.offset[0]) *
> > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > > +                       value[1] = (imu_data[i].gyro_y -
> > > +                                   ctlr->gyro_cal.offset[1]) *
> > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > > +                       value[2] = (imu_data[i].gyro_z -
> > > +                                   ctlr->gyro_cal.offset[2]) *
> > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > > +
> > > +                       value[3] = (imu_data[i].accel_x -
> > > +                                   ctlr->accel_cal.offset[0]) *
> > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > > +                       value[4] = (imu_data[i].accel_y -
> > > +                                   ctlr->accel_cal.offset[1]) *
> > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > > +                       value[5] = (imu_data[i].accel_z -
> > > +                                   ctlr->accel_cal.offset[2]) *
> > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
> >
> > Just in case I would double check the precision of your calculations.
> > For DS4 we had similar calculations, but we had a small loss of
> > precision, which ultimately caused major calculation errors.
> > (Applications often use accelerometer + gyro data to calculate an
> > absolute position. This involves  integration.. and small errors
> > become big). We had to use the "mult_frac" macro to restore some of
> > the precision during the calculations. It might be something to double
> > check.
> >
>
> That's good to know. I'll look more closely at how it's implemented in
> hid-sony.
>
> > In addition what orientation are you using for the axes? I need to
> > double check the DS4 datasheets, but I think we were using a "right
> > handed" coordinate system. (So if you make a fist of your right hand
> > with thumb up, the gyro curls around it counter clockwise along the
> > direction of your fingers).
> >
>
> The orientation of the axes (and much more) are documented here:
> https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
> Since the the right vs. left joy-cons have different axes orientations, I
> assume it's preferable to standardize it all in software to match the
> orientation of the DS4.
>
> >
> > > +
> > > +                       input_report_abs(idev, ABS_RX, value[0]);
> > > +                       input_report_abs(idev, ABS_RY, value[1]);
> > > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > > +                       input_report_abs(idev, ABS_X, value[3]);
> > > +                       input_report_abs(idev, ABS_Y, value[4]);
> > > +                       input_report_abs(idev, ABS_Z, value[5]);
> > > +                       input_sync(idev);
> > > +               }
> > > +       }
> > >  }
> > >
> > >  static void joycon_rumble_worker(struct work_struct *work)
> > > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > >  {
> > >         struct hid_device *hdev;
> > >         const char *name;
> > > +       const char *imu_name;
> > >         int ret;
> > >         int i;
> > >
> > > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > >         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_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");
> > > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > >         if (ret)
> > >                 return ret;
> > >
> > > +       /* configure the imu input device */
> > > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > > +       if (!ctlr->imu_input)
> > > +               return -ENOMEM;
> > > +
> > > +       ctlr->imu_input->id.bustype = hdev->bus;
> > > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > > +       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;
> > > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > > +
> > > +       /* configure imu axes */
> > > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > > +
> > > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > +
> > > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > > +
> > > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > > +
> > > +       ret = input_register_device(ctlr->imu_input);
> > > +       if (ret)
> > > +               return ret;
> > > +
> > >         return 0;
> > >  }
> > >
> > > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > >
> > >         for (i = 4, j = 0; j < 6; i++, j++)
> > > -               ctlr->mac_addr[j] = report->reply.data[i];
> > > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> > >
> > >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> > >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> > >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> > >                                 break;
> > >                         report = (struct joycon_input_report *)data;
> > > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> > >                                 match = true;
> > >                         break;
> > >                 default:
> > > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> > >         }
> > >
> > > +       /* get IMU calibration data, and parse it */
> > > +       ret = joycon_request_imu_calibration(ctlr);
> > > +       if (ret) {
> > > +               /*
> > > +                * We can function with default calibration, but it may be
> > > +                * inaccurate. Provide a warning, and continue on.
> > > +                */
> > > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > > +       }
> > > +
> > >         /* Set the reporting mode to 0x30, which is the full report mode */
> > >         ret = joycon_set_report_mode(ctlr);
> > >         if (ret) {
> > > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > >                 goto err_mutex;
> > >         }
> > >
> > > +       /* Enable the IMU */
> > > +       ret = joycon_enable_imu(ctlr, true);
> > > +       if (ret) {
> > > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > > +               goto err_mutex;
> > > +       }
> > > +
> > >         ret = joycon_read_mac(ctlr);
> > >         if (ret) {
> > >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > > --
> > > 2.24.1
> > >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-09  5:22       ` Roderick Colenbrander
@ 2020-01-09  6:23         ` Carl Mueller
  2020-01-09  8:53           ` Daniel Ogorchock
  2020-01-09  8:55         ` Daniel Ogorchock
  1 sibling, 1 reply; 25+ messages in thread
From: Carl Mueller @ 2020-01-09  6:23 UTC (permalink / raw)
  To: Roderick Colenbrander
  Cc: Daniel Ogorchock, linux-input, Billy Laws, Benjamin Tissoires,
	Jiri Kosina, Colenbrander, Roelof, Siarhei Vishniakou, s.jegen

(3rd/final attempt at reply due to list server rejecting message with
HTML subpart; sorry about the spam!)

Hi Everyone,

In trying out this driver to use with the Switch Pro Controller,
the biggest difficulty I had was that the D-pad is reported as buttons
instead of as a Hat.  All other controllers that are similar in structure
(such as the PS3 and PS4 controllers, the Xbox controllers, and many
others) report the D-pad as a Hat.  This kind of consistency is needed
to avoid lots of software compatibility issues.

I mentioned this to Daniel, and he indicated that he wanted the Switch
Pro Controller to behave like the JoyCons, which have buttons instead
of a D-pad.  Having the JoyCons report the buttons as buttons makes
sense, since it's possible to push opposite directions at the same time.
However, I don't think that the argument carries over to the Switch Pro
Controller, since it has a physically different control.

Again, the consistency with the other controllers that have all the same
physical structure and the same types of controls seems more important
to me than consistency with a controller that is physically much different.
Additionally, many games are already written for use with controllers
that look like the Switch Pro Controller, whereas fewer are written for use
with Nintendo JoyCons.  If we have to cause trouble for one side or the
other, let's not cause trouble with the more established side.

On Thu, Jan 9, 2020 at 12:22 AM Roderick Colenbrander
<thunderbird2k@gmail.com> wrote:
>
> Hi Daniel,
>
> Unfortunately there is no public test application at the moment to
> illustrate these features. I agree something is definitely needed.
>
> I need to see if we can come up with something. One of the test apps
> we have internally is a 3D cube, which is controllable by controller
> motion. It of course shows the gyro / accelerometer values, but the
> position of the cube is tied to the calculated orientation. If
> something is off you will see weird movements in the cube or drift
> building up over time etcetera.
>
> Though it would take some time to prepare something. The rest of the
> patch series looked fine I think, so the IMU parts may need to wait
> for a next kernel cycle, but all the other plumbing could go in (if
> others are okay of course).
>
> Thanks,
> Roderick
>
> On Wed, Jan 8, 2020 at 7:26 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> >
> > Hi Roderick,
> >
> > Thanks for the feedback. In addition to the inline comments below,
> > do you have any recommendations for test programs that you
> > know work well with hid-sony's gyro implementation? Up to this
> > point I've just been using evtest, which obviously isn't too useful
> > for testing actual functionality of the gyro in an intuitive way.
> >
> > On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
> > <thunderbird2k@gmail.com> wrote:
> > >
> > > Hi Daniel,
> > >
> > > I had some time to review the motion sensors patch. I have added some
> > > feedback inline with your patch below.
> > >
> > > Aside from standard feedback on the code, I wanted to have a close
> > > look at the accelerometer / gyro data. My team has been doing a lot of
> > > work on this (and still is) and I want to make sure any work we do on
> > > "user space" software (e.g. Android) automatically works for Joycon as
> > > well. The accuracy of the data is very important else applications
> > > will make bad decisions. Userspace applications often combine the data
> > > of both sensors to calculate a position for position tracking.
> > > Inaccurate axes ranges or wrong precision can cause major issues. I
> > > may be a bit strict below, but it will just be to prevent headaches
> > > for others later on. I use the DS4 as a reference point as it was the
> > > first device (aside from Wii early on) where we properly report
> > > acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> > > be consistent with it here else applications could be confused, so we
> > > should use similar orientation of axes and resolutions.
> > >
> > > Thanks,
> > > Roderick
> > >
> > > On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> > > <djogorchock@gmail.com> wrote:
> > > >
> > > > This patch adds support for the controller's IMU. The accelerometer and
> > > > gyro data are both provided to userspace using a second input device.
> > > > The devices can be associated using their uniq value (set to the
> > > > controller's MAC address).
> > > >
> > > > The majority of this patch's functinoality was provided by Carl Mueller.
> > > >
> > > > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > > > ---
> > > >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> > > >  1 file changed, 259 insertions(+), 8 deletions(-)
> > > >
> > > > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > > > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > > > --- a/drivers/hid/hid-nintendo.c
> > > > +++ b/drivers/hid/hid-nintendo.c
> > > > @@ -101,12 +101,29 @@ 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)
> > > >
> > > > +/* SPI storage addresses of IMU factory calibration data */
> > > > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > > > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > > > +#define JC_IMU_CAL_DATA_SIZE \
> > > > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> > > >
> > > > +/* The accel axes will be mapped in terms of this magnitude */
> > > > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > > > +static const u16 JC_ACCEL_RES                  = 4096;
> > >
> > > What does the acceleration resolution equate to? For DS4 we use
> > > multiples of 'g'. (We don't know the position on earth, so can't
> > > report in m/s^2).
> > >
> > > > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > > > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > > > +
> > > > +/* The gyro axes will be mapped in terms of this magnitude */
> > > > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > > > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
> > >
> > > What does the gyro resolution equate to? For DS4 we report "degrees
> > > per second". We should do the same for the joycons, but I don't know
> > > how you guys figured out the exact resolution. As I mentioned if it is
> > > not accurate, applications will make wrong calculations.
> > >
> > > > +static const u16 JC_GYRO_FUZZ                  = 10;
> > > > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > > > +
> > > >  /* frequency/amplitude tables for rumble */
> > > >  struct joycon_rumble_freq_data {
> > > >         u16 high;
> > > > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> > > >         s32 center;
> > > >  };
> > > >
> > > > +struct joycon_imu_cal {
> > > > +       s16 offset[3];
> > > > +       s16 scale[3];
> > > > +};
> > > > +
> > > >  /*
> > > >   * All the controller's button values are stored in a u32.
> > > >   * They can be accessed with bitwise ANDs.
> > > > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> > > >         u8 data[0]; /* will be at most 35 bytes */
> > > >  } __packed;
> > > >
> > > > +struct joycon_imu_data {
> > > > +       s16 accel_x;
> > > > +       s16 accel_y;
> > > > +       s16 accel_z;
> > > > +       s16 gyro_x;
> > > > +       s16 gyro_y;
> > > > +       s16 gyro_z;
> > > > +} __packed;
> > > > +
> > > > +struct joycon_imu_report {
> > > > +       struct joycon_imu_data data[3];
> > > > +} __packed;
> > >
> > > See comments later on about imu_data, but you can't directly cast your
> > > data buffer to this data type due to endian issues. It may not really
> > > make sense to keep the data structure as you need to do manual data
> > > fetching.
> > >
> > > > +
> > > >  struct joycon_input_report {
> > > >         u8 id;
> > > >         u8 timer;
> > > > @@ -290,11 +325,10 @@ struct joycon_input_report {
> > > >         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;
> > > > +       union {
> > > > +               struct joycon_subcmd_reply subcmd_reply;
> > > > +               struct joycon_imu_report imu_report;
> > >
> > > maybe add a raw byte array to this union. Could help for parsing the imu data.
> > > > +       };
> > > >  } __packed;
> > > >
> > > >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > > > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> > > >         struct joycon_stick_cal right_stick_cal_x;
> > > >         struct joycon_stick_cal right_stick_cal_y;
> > > >
> > > > +       struct joycon_imu_cal accel_cal;
> > > > +       struct joycon_imu_cal gyro_cal;
> > > > +
> > > >         /* power supply data */
> > > >         struct power_supply *battery;
> > > >         struct power_supply_desc battery_desc;
> > > > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> > > >         u16 rumble_lh_freq;
> > > >         u16 rumble_rl_freq;
> > > >         u16 rumble_rh_freq;
> > > > +
> > > > +       /* imu */
> > > > +       struct input_dev *imu_input;
> > > > +       int timestamp;
> > > >  };
> > > >
> > > >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > > > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > >         }
> > > >
> > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > -       raw_cal = &report->reply.data[5];
> > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > >
> > > >         /* left stick calibration parsing */
> > > >         cal_x = &ctlr->left_stick_cal_x;
> > > > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > >         return 0;
> > > >  }
> > > >
> > > > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > > > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > > > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > > > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > > > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > > > +{
> > > > +       struct joycon_subcmd_request *req;
> > > > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > > > +       struct joycon_input_report *report;
> > > > +       u8 *data;
> > > > +       u8 *raw_cal;
> > > > +       int ret;
> > > > +       int i;
> > > > +
> > > > +       /* request IMU calibration data */
> > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > > > +       data = req->data;
> > > > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > > > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > > > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > > > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
> > >
> > > Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> > > these calculations yourself:
> > > put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
> > >
> > > > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > > > +
> > > > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > > > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > > > +
> > > > +       if (ret) {
> > > > +               hid_warn(ctlr->hdev,
> > > > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > > > +                        ret);
> > > > +
> > > > +               for (i = 0; i < 3; i++) {
> > > > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > > > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > > > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > > > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > > > +               }
> > > > +               return ret;
> > > > +       }
> > > > +
> > > > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > +
> > > > +       /* IMU calibration parsing */
> > > > +       for (i = 0; i < 3; i++) {
> > > > +               int j = i * 2;
> > > > +
> > > > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > > > +                                               ((s16)raw_cal[j + 1] << 8);
> > > > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > > > +                                               ((s16)raw_cal[j + 7] << 8);
> > > > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > > > +                                               ((s16)raw_cal[j + 13] << 8);
> > > > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > > > +                                               ((s16)raw_cal[j + 19] << 8);
> > >
> > > get_unaligned_le16 instead of doing your own bitshifts?
> > > > +       }
> > > > +
> > > > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > > > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > > > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > > > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > > > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > > > +                           ctlr->accel_cal.offset[0],
> > > > +                           ctlr->accel_cal.offset[1],
> > > > +                           ctlr->accel_cal.offset[2],
> > > > +                           ctlr->accel_cal.scale[0],
> > > > +                           ctlr->accel_cal.scale[1],
> > > > +                           ctlr->accel_cal.scale[2],
> > > > +                           ctlr->gyro_cal.offset[0],
> > > > +                           ctlr->gyro_cal.offset[1],
> > > > +                           ctlr->gyro_cal.offset[2],
> > > > +                           ctlr->gyro_cal.scale[0],
> > > > +                           ctlr->gyro_cal.scale[1],
> > > > +                           ctlr->gyro_cal.scale[2]);
> > > > +
> > > > +       return 0;
> > > > +}
> > > > +
> > > >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> > > >  {
> > > >         struct joycon_subcmd_request *req;
> > > > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> > > >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> > > >  }
> > > >
> > > > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > > > +{
> > > > +       struct joycon_subcmd_request *req;
> > > > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > > > +
> > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > > > +       req->data[0] = enable ? 0x01 : 0x00;
> > > > +
> > > > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > > > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > > > +}
> > > > +
> > > >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> > > >  {
> > > >         s32 center = cal->center;
> > > > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> > > >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> > > >                 wake_up(&ctlr->wait);
> > > >         }
> > > > +
> > > > +       /* parse IMU data if present */
> > > > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > > > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
> > >
> > > I don't think you can directly covert your input report data to
> > > imu_data. Until now you have been lucky enough (based on a quick
> > > glance of the the other patches) that your data are single bytes.
> > > However, this data seems to be a bunch of s16's. In other words you
> > > have to deal with endianess issues. You need to use get_unaligned_le16
> > > to retrieve data from your raw byte buffer. See other HID drivers for
> > > reference.
> >
> > Ah, good point. I'd overlooked that entirely.
> >
> > >
> > > Since you will have to use get_unaligned_le16, it probably won't make
> > > much sense to really have a joycon_imu_data structure. Maybe extend
> > > your input_buffer union with raw bytes and in this case just use raw
> > > bytes. Each of your loop iterations below just grab the values from
> > > the buffer and store them in a variable.
> > >
> > > > +               struct input_dev *idev = ctlr->imu_input;
> > > > +               int i;
> > > > +               int value[6];
> > > > +
> > > > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > > > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > > > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > > > +                                   ctlr->timestamp);
> > >
> > > How sure are you that this is always 5ms? Is there a hardware
> > > timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> > > (Bluetooth is lossy) and not all packets even make it.
> > >
> >
> > It appears that the closest thing to a hardware timestamp available is a
> > quickly incrementing 1-byte timer sent with every report. It's only really
> > useful for latency estimation. Each input report includes 3 IMU samples
> > which are 5ms apart for the joy-cons. It's recently come to my attention
> > that the samples may be spaced differently for the pro controller, so I'm
> > going to need to dive into this more anyway. I'm not sure what a great
> > way would be to handle lost reports.
> >
> > > > +
> > > > +                       /*
> > > > +                        * Rather than convert to floats, we adjust by
> > > > +                        * calibration factors and then multiply by the default
> > > > +                        * scaling values. This way, the consumer only needs to
> > > > +                        * divide by the default scale values.
> > > > +                        */
> > > > +                       value[0] = (imu_data[i].gyro_x -
> > > > +                                   ctlr->gyro_cal.offset[0]) *
> > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > > > +                       value[1] = (imu_data[i].gyro_y -
> > > > +                                   ctlr->gyro_cal.offset[1]) *
> > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > > > +                       value[2] = (imu_data[i].gyro_z -
> > > > +                                   ctlr->gyro_cal.offset[2]) *
> > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > > > +
> > > > +                       value[3] = (imu_data[i].accel_x -
> > > > +                                   ctlr->accel_cal.offset[0]) *
> > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > > > +                       value[4] = (imu_data[i].accel_y -
> > > > +                                   ctlr->accel_cal.offset[1]) *
> > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > > > +                       value[5] = (imu_data[i].accel_z -
> > > > +                                   ctlr->accel_cal.offset[2]) *
> > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
> > >
> > > Just in case I would double check the precision of your calculations.
> > > For DS4 we had similar calculations, but we had a small loss of
> > > precision, which ultimately caused major calculation errors.
> > > (Applications often use accelerometer + gyro data to calculate an
> > > absolute position. This involves  integration.. and small errors
> > > become big). We had to use the "mult_frac" macro to restore some of
> > > the precision during the calculations. It might be something to double
> > > check.
> > >
> >
> > That's good to know. I'll look more closely at how it's implemented in
> > hid-sony.
> >
> > > In addition what orientation are you using for the axes? I need to
> > > double check the DS4 datasheets, but I think we were using a "right
> > > handed" coordinate system. (So if you make a fist of your right hand
> > > with thumb up, the gyro curls around it counter clockwise along the
> > > direction of your fingers).
> > >
> >
> > The orientation of the axes (and much more) are documented here:
> > https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
> > Since the the right vs. left joy-cons have different axes orientations, I
> > assume it's preferable to standardize it all in software to match the
> > orientation of the DS4.
> >
> > >
> > > > +
> > > > +                       input_report_abs(idev, ABS_RX, value[0]);
> > > > +                       input_report_abs(idev, ABS_RY, value[1]);
> > > > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > > > +                       input_report_abs(idev, ABS_X, value[3]);
> > > > +                       input_report_abs(idev, ABS_Y, value[4]);
> > > > +                       input_report_abs(idev, ABS_Z, value[5]);
> > > > +                       input_sync(idev);
> > > > +               }
> > > > +       }
> > > >  }
> > > >
> > > >  static void joycon_rumble_worker(struct work_struct *work)
> > > > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > >  {
> > > >         struct hid_device *hdev;
> > > >         const char *name;
> > > > +       const char *imu_name;
> > > >         int ret;
> > > >         int i;
> > > >
> > > > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > >         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_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");
> > > > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > >         if (ret)
> > > >                 return ret;
> > > >
> > > > +       /* configure the imu input device */
> > > > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > > > +       if (!ctlr->imu_input)
> > > > +               return -ENOMEM;
> > > > +
> > > > +       ctlr->imu_input->id.bustype = hdev->bus;
> > > > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > > > +       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;
> > > > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > > > +
> > > > +       /* configure imu axes */
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > > > +
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > +
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > > > +
> > > > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > > > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > > > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > > > +
> > > > +       ret = input_register_device(ctlr->imu_input);
> > > > +       if (ret)
> > > > +               return ret;
> > > > +
> > > >         return 0;
> > > >  }
> > > >
> > > > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > >
> > > >         for (i = 4, j = 0; j < 6; i++, j++)
> > > > -               ctlr->mac_addr[j] = report->reply.data[i];
> > > > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> > > >
> > > >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> > > >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > > > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> > > >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> > > >                                 break;
> > > >                         report = (struct joycon_input_report *)data;
> > > > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > > > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> > > >                                 match = true;
> > > >                         break;
> > > >                 default:
> > > > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> > > >         }
> > > >
> > > > +       /* get IMU calibration data, and parse it */
> > > > +       ret = joycon_request_imu_calibration(ctlr);
> > > > +       if (ret) {
> > > > +               /*
> > > > +                * We can function with default calibration, but it may be
> > > > +                * inaccurate. Provide a warning, and continue on.
> > > > +                */
> > > > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > > > +       }
> > > > +
> > > >         /* Set the reporting mode to 0x30, which is the full report mode */
> > > >         ret = joycon_set_report_mode(ctlr);
> > > >         if (ret) {
> > > > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > >                 goto err_mutex;
> > > >         }
> > > >
> > > > +       /* Enable the IMU */
> > > > +       ret = joycon_enable_imu(ctlr, true);
> > > > +       if (ret) {
> > > > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > > > +               goto err_mutex;
> > > > +       }
> > > > +
> > > >         ret = joycon_read_mac(ctlr);
> > > >         if (ret) {
> > > >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > > > --
> > > > 2.24.1
> > > >

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-09  6:23         ` Carl Mueller
@ 2020-01-09  8:53           ` Daniel Ogorchock
  2020-01-09 16:37             ` Roderick Colenbrander
  0 siblings, 1 reply; 25+ messages in thread
From: Daniel Ogorchock @ 2020-01-09  8:53 UTC (permalink / raw)
  To: Carl Mueller
  Cc: Roderick Colenbrander, linux-input, Billy Laws,
	Benjamin Tissoires, Jiri Kosina, Colenbrander, Roelof,
	Siarhei Vishniakou, s.jegen

Hi Carl,

When I was initially implementing the button mappings, I referred to the
standard described here:
    https://www.kernel.org/doc/html/latest/input/gamepad.html

It mentions under the d-pad section that digital inputs should be
reported using the BTN_DPAD_* variant. Do the other controllers
mentioned report their d-pads as analog values, or has using the
ABS_HAT* values become the de facto format for digital inputs
as well?

If the latter, I guess it would make sense to go with the crowd for the pro.
It just seems a bit odd to report digital inputs as absolute axes, but I'm
open to changing it if that's the consensus.

Thanks,
Daniel

On Thu, Jan 9, 2020 at 12:23 AM Carl Mueller <carmueller@gmail.com> wrote:
>
> (3rd/final attempt at reply due to list server rejecting message with
> HTML subpart; sorry about the spam!)
>
> Hi Everyone,
>
> In trying out this driver to use with the Switch Pro Controller,
> the biggest difficulty I had was that the D-pad is reported as buttons
> instead of as a Hat.  All other controllers that are similar in structure
> (such as the PS3 and PS4 controllers, the Xbox controllers, and many
> others) report the D-pad as a Hat.  This kind of consistency is needed
> to avoid lots of software compatibility issues.
>
> I mentioned this to Daniel, and he indicated that he wanted the Switch
> Pro Controller to behave like the JoyCons, which have buttons instead
> of a D-pad.  Having the JoyCons report the buttons as buttons makes
> sense, since it's possible to push opposite directions at the same time.
> However, I don't think that the argument carries over to the Switch Pro
> Controller, since it has a physically different control.
>
> Again, the consistency with the other controllers that have all the same
> physical structure and the same types of controls seems more important
> to me than consistency with a controller that is physically much different.
> Additionally, many games are already written for use with controllers
> that look like the Switch Pro Controller, whereas fewer are written for use
> with Nintendo JoyCons.  If we have to cause trouble for one side or the
> other, let's not cause trouble with the more established side.
>
> On Thu, Jan 9, 2020 at 12:22 AM Roderick Colenbrander
> <thunderbird2k@gmail.com> wrote:
> >
> > Hi Daniel,
> >
> > Unfortunately there is no public test application at the moment to
> > illustrate these features. I agree something is definitely needed.
> >
> > I need to see if we can come up with something. One of the test apps
> > we have internally is a 3D cube, which is controllable by controller
> > motion. It of course shows the gyro / accelerometer values, but the
> > position of the cube is tied to the calculated orientation. If
> > something is off you will see weird movements in the cube or drift
> > building up over time etcetera.
> >
> > Though it would take some time to prepare something. The rest of the
> > patch series looked fine I think, so the IMU parts may need to wait
> > for a next kernel cycle, but all the other plumbing could go in (if
> > others are okay of course).
> >
> > Thanks,
> > Roderick
> >
> > On Wed, Jan 8, 2020 at 7:26 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> > >
> > > Hi Roderick,
> > >
> > > Thanks for the feedback. In addition to the inline comments below,
> > > do you have any recommendations for test programs that you
> > > know work well with hid-sony's gyro implementation? Up to this
> > > point I've just been using evtest, which obviously isn't too useful
> > > for testing actual functionality of the gyro in an intuitive way.
> > >
> > > On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
> > > <thunderbird2k@gmail.com> wrote:
> > > >
> > > > Hi Daniel,
> > > >
> > > > I had some time to review the motion sensors patch. I have added some
> > > > feedback inline with your patch below.
> > > >
> > > > Aside from standard feedback on the code, I wanted to have a close
> > > > look at the accelerometer / gyro data. My team has been doing a lot of
> > > > work on this (and still is) and I want to make sure any work we do on
> > > > "user space" software (e.g. Android) automatically works for Joycon as
> > > > well. The accuracy of the data is very important else applications
> > > > will make bad decisions. Userspace applications often combine the data
> > > > of both sensors to calculate a position for position tracking.
> > > > Inaccurate axes ranges or wrong precision can cause major issues. I
> > > > may be a bit strict below, but it will just be to prevent headaches
> > > > for others later on. I use the DS4 as a reference point as it was the
> > > > first device (aside from Wii early on) where we properly report
> > > > acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> > > > be consistent with it here else applications could be confused, so we
> > > > should use similar orientation of axes and resolutions.
> > > >
> > > > Thanks,
> > > > Roderick
> > > >
> > > > On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> > > > <djogorchock@gmail.com> wrote:
> > > > >
> > > > > This patch adds support for the controller's IMU. The accelerometer and
> > > > > gyro data are both provided to userspace using a second input device.
> > > > > The devices can be associated using their uniq value (set to the
> > > > > controller's MAC address).
> > > > >
> > > > > The majority of this patch's functinoality was provided by Carl Mueller.
> > > > >
> > > > > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > > > > ---
> > > > >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> > > > >  1 file changed, 259 insertions(+), 8 deletions(-)
> > > > >
> > > > > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > > > > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > > > > --- a/drivers/hid/hid-nintendo.c
> > > > > +++ b/drivers/hid/hid-nintendo.c
> > > > > @@ -101,12 +101,29 @@ 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)
> > > > >
> > > > > +/* SPI storage addresses of IMU factory calibration data */
> > > > > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > > > > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > > > > +#define JC_IMU_CAL_DATA_SIZE \
> > > > > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> > > > >
> > > > > +/* The accel axes will be mapped in terms of this magnitude */
> > > > > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > > > > +static const u16 JC_ACCEL_RES                  = 4096;
> > > >
> > > > What does the acceleration resolution equate to? For DS4 we use
> > > > multiples of 'g'. (We don't know the position on earth, so can't
> > > > report in m/s^2).
> > > >
> > > > > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > > > > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > > > > +
> > > > > +/* The gyro axes will be mapped in terms of this magnitude */
> > > > > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > > > > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
> > > >
> > > > What does the gyro resolution equate to? For DS4 we report "degrees
> > > > per second". We should do the same for the joycons, but I don't know
> > > > how you guys figured out the exact resolution. As I mentioned if it is
> > > > not accurate, applications will make wrong calculations.
> > > >
> > > > > +static const u16 JC_GYRO_FUZZ                  = 10;
> > > > > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > > > > +
> > > > >  /* frequency/amplitude tables for rumble */
> > > > >  struct joycon_rumble_freq_data {
> > > > >         u16 high;
> > > > > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> > > > >         s32 center;
> > > > >  };
> > > > >
> > > > > +struct joycon_imu_cal {
> > > > > +       s16 offset[3];
> > > > > +       s16 scale[3];
> > > > > +};
> > > > > +
> > > > >  /*
> > > > >   * All the controller's button values are stored in a u32.
> > > > >   * They can be accessed with bitwise ANDs.
> > > > > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> > > > >         u8 data[0]; /* will be at most 35 bytes */
> > > > >  } __packed;
> > > > >
> > > > > +struct joycon_imu_data {
> > > > > +       s16 accel_x;
> > > > > +       s16 accel_y;
> > > > > +       s16 accel_z;
> > > > > +       s16 gyro_x;
> > > > > +       s16 gyro_y;
> > > > > +       s16 gyro_z;
> > > > > +} __packed;
> > > > > +
> > > > > +struct joycon_imu_report {
> > > > > +       struct joycon_imu_data data[3];
> > > > > +} __packed;
> > > >
> > > > See comments later on about imu_data, but you can't directly cast your
> > > > data buffer to this data type due to endian issues. It may not really
> > > > make sense to keep the data structure as you need to do manual data
> > > > fetching.
> > > >
> > > > > +
> > > > >  struct joycon_input_report {
> > > > >         u8 id;
> > > > >         u8 timer;
> > > > > @@ -290,11 +325,10 @@ struct joycon_input_report {
> > > > >         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;
> > > > > +       union {
> > > > > +               struct joycon_subcmd_reply subcmd_reply;
> > > > > +               struct joycon_imu_report imu_report;
> > > >
> > > > maybe add a raw byte array to this union. Could help for parsing the imu data.
> > > > > +       };
> > > > >  } __packed;
> > > > >
> > > > >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > > > > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> > > > >         struct joycon_stick_cal right_stick_cal_x;
> > > > >         struct joycon_stick_cal right_stick_cal_y;
> > > > >
> > > > > +       struct joycon_imu_cal accel_cal;
> > > > > +       struct joycon_imu_cal gyro_cal;
> > > > > +
> > > > >         /* power supply data */
> > > > >         struct power_supply *battery;
> > > > >         struct power_supply_desc battery_desc;
> > > > > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> > > > >         u16 rumble_lh_freq;
> > > > >         u16 rumble_rl_freq;
> > > > >         u16 rumble_rh_freq;
> > > > > +
> > > > > +       /* imu */
> > > > > +       struct input_dev *imu_input;
> > > > > +       int timestamp;
> > > > >  };
> > > > >
> > > > >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > > > > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > >         }
> > > > >
> > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > -       raw_cal = &report->reply.data[5];
> > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > >
> > > > >         /* left stick calibration parsing */
> > > > >         cal_x = &ctlr->left_stick_cal_x;
> > > > > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > >         return 0;
> > > > >  }
> > > > >
> > > > > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > > > > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > > > > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > > > > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > > > > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > > > > +{
> > > > > +       struct joycon_subcmd_request *req;
> > > > > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > > > > +       struct joycon_input_report *report;
> > > > > +       u8 *data;
> > > > > +       u8 *raw_cal;
> > > > > +       int ret;
> > > > > +       int i;
> > > > > +
> > > > > +       /* request IMU calibration data */
> > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > > > > +       data = req->data;
> > > > > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > > > > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > > > > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > > > > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
> > > >
> > > > Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> > > > these calculations yourself:
> > > > put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
> > > >
> > > > > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > > > > +
> > > > > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > > > > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > > > > +
> > > > > +       if (ret) {
> > > > > +               hid_warn(ctlr->hdev,
> > > > > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > > > > +                        ret);
> > > > > +
> > > > > +               for (i = 0; i < 3; i++) {
> > > > > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > > > > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > > > > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > > > > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > > > > +               }
> > > > > +               return ret;
> > > > > +       }
> > > > > +
> > > > > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > +
> > > > > +       /* IMU calibration parsing */
> > > > > +       for (i = 0; i < 3; i++) {
> > > > > +               int j = i * 2;
> > > > > +
> > > > > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > > > > +                                               ((s16)raw_cal[j + 1] << 8);
> > > > > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > > > > +                                               ((s16)raw_cal[j + 7] << 8);
> > > > > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > > > > +                                               ((s16)raw_cal[j + 13] << 8);
> > > > > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > > > > +                                               ((s16)raw_cal[j + 19] << 8);
> > > >
> > > > get_unaligned_le16 instead of doing your own bitshifts?
> > > > > +       }
> > > > > +
> > > > > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > > > > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > > > > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > > > > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > > > > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > > > > +                           ctlr->accel_cal.offset[0],
> > > > > +                           ctlr->accel_cal.offset[1],
> > > > > +                           ctlr->accel_cal.offset[2],
> > > > > +                           ctlr->accel_cal.scale[0],
> > > > > +                           ctlr->accel_cal.scale[1],
> > > > > +                           ctlr->accel_cal.scale[2],
> > > > > +                           ctlr->gyro_cal.offset[0],
> > > > > +                           ctlr->gyro_cal.offset[1],
> > > > > +                           ctlr->gyro_cal.offset[2],
> > > > > +                           ctlr->gyro_cal.scale[0],
> > > > > +                           ctlr->gyro_cal.scale[1],
> > > > > +                           ctlr->gyro_cal.scale[2]);
> > > > > +
> > > > > +       return 0;
> > > > > +}
> > > > > +
> > > > >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> > > > >  {
> > > > >         struct joycon_subcmd_request *req;
> > > > > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> > > > >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> > > > >  }
> > > > >
> > > > > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > > > > +{
> > > > > +       struct joycon_subcmd_request *req;
> > > > > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > > > > +
> > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > > > > +       req->data[0] = enable ? 0x01 : 0x00;
> > > > > +
> > > > > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > > > > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > > > > +}
> > > > > +
> > > > >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> > > > >  {
> > > > >         s32 center = cal->center;
> > > > > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> > > > >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> > > > >                 wake_up(&ctlr->wait);
> > > > >         }
> > > > > +
> > > > > +       /* parse IMU data if present */
> > > > > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > > > > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
> > > >
> > > > I don't think you can directly covert your input report data to
> > > > imu_data. Until now you have been lucky enough (based on a quick
> > > > glance of the the other patches) that your data are single bytes.
> > > > However, this data seems to be a bunch of s16's. In other words you
> > > > have to deal with endianess issues. You need to use get_unaligned_le16
> > > > to retrieve data from your raw byte buffer. See other HID drivers for
> > > > reference.
> > >
> > > Ah, good point. I'd overlooked that entirely.
> > >
> > > >
> > > > Since you will have to use get_unaligned_le16, it probably won't make
> > > > much sense to really have a joycon_imu_data structure. Maybe extend
> > > > your input_buffer union with raw bytes and in this case just use raw
> > > > bytes. Each of your loop iterations below just grab the values from
> > > > the buffer and store them in a variable.
> > > >
> > > > > +               struct input_dev *idev = ctlr->imu_input;
> > > > > +               int i;
> > > > > +               int value[6];
> > > > > +
> > > > > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > > > > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > > > > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > > > > +                                   ctlr->timestamp);
> > > >
> > > > How sure are you that this is always 5ms? Is there a hardware
> > > > timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> > > > (Bluetooth is lossy) and not all packets even make it.
> > > >
> > >
> > > It appears that the closest thing to a hardware timestamp available is a
> > > quickly incrementing 1-byte timer sent with every report. It's only really
> > > useful for latency estimation. Each input report includes 3 IMU samples
> > > which are 5ms apart for the joy-cons. It's recently come to my attention
> > > that the samples may be spaced differently for the pro controller, so I'm
> > > going to need to dive into this more anyway. I'm not sure what a great
> > > way would be to handle lost reports.
> > >
> > > > > +
> > > > > +                       /*
> > > > > +                        * Rather than convert to floats, we adjust by
> > > > > +                        * calibration factors and then multiply by the default
> > > > > +                        * scaling values. This way, the consumer only needs to
> > > > > +                        * divide by the default scale values.
> > > > > +                        */
> > > > > +                       value[0] = (imu_data[i].gyro_x -
> > > > > +                                   ctlr->gyro_cal.offset[0]) *
> > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > > > > +                       value[1] = (imu_data[i].gyro_y -
> > > > > +                                   ctlr->gyro_cal.offset[1]) *
> > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > > > > +                       value[2] = (imu_data[i].gyro_z -
> > > > > +                                   ctlr->gyro_cal.offset[2]) *
> > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > > > > +
> > > > > +                       value[3] = (imu_data[i].accel_x -
> > > > > +                                   ctlr->accel_cal.offset[0]) *
> > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > > > > +                       value[4] = (imu_data[i].accel_y -
> > > > > +                                   ctlr->accel_cal.offset[1]) *
> > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > > > > +                       value[5] = (imu_data[i].accel_z -
> > > > > +                                   ctlr->accel_cal.offset[2]) *
> > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
> > > >
> > > > Just in case I would double check the precision of your calculations.
> > > > For DS4 we had similar calculations, but we had a small loss of
> > > > precision, which ultimately caused major calculation errors.
> > > > (Applications often use accelerometer + gyro data to calculate an
> > > > absolute position. This involves  integration.. and small errors
> > > > become big). We had to use the "mult_frac" macro to restore some of
> > > > the precision during the calculations. It might be something to double
> > > > check.
> > > >
> > >
> > > That's good to know. I'll look more closely at how it's implemented in
> > > hid-sony.
> > >
> > > > In addition what orientation are you using for the axes? I need to
> > > > double check the DS4 datasheets, but I think we were using a "right
> > > > handed" coordinate system. (So if you make a fist of your right hand
> > > > with thumb up, the gyro curls around it counter clockwise along the
> > > > direction of your fingers).
> > > >
> > >
> > > The orientation of the axes (and much more) are documented here:
> > > https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
> > > Since the the right vs. left joy-cons have different axes orientations, I
> > > assume it's preferable to standardize it all in software to match the
> > > orientation of the DS4.
> > >
> > > >
> > > > > +
> > > > > +                       input_report_abs(idev, ABS_RX, value[0]);
> > > > > +                       input_report_abs(idev, ABS_RY, value[1]);
> > > > > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > > > > +                       input_report_abs(idev, ABS_X, value[3]);
> > > > > +                       input_report_abs(idev, ABS_Y, value[4]);
> > > > > +                       input_report_abs(idev, ABS_Z, value[5]);
> > > > > +                       input_sync(idev);
> > > > > +               }
> > > > > +       }
> > > > >  }
> > > > >
> > > > >  static void joycon_rumble_worker(struct work_struct *work)
> > > > > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > >  {
> > > > >         struct hid_device *hdev;
> > > > >         const char *name;
> > > > > +       const char *imu_name;
> > > > >         int ret;
> > > > >         int i;
> > > > >
> > > > > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > >         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_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");
> > > > > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > >         if (ret)
> > > > >                 return ret;
> > > > >
> > > > > +       /* configure the imu input device */
> > > > > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > > > > +       if (!ctlr->imu_input)
> > > > > +               return -ENOMEM;
> > > > > +
> > > > > +       ctlr->imu_input->id.bustype = hdev->bus;
> > > > > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > > > > +       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;
> > > > > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > > > > +
> > > > > +       /* configure imu axes */
> > > > > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > > > > +
> > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > +
> > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > > > > +
> > > > > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > > > > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > > > > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > > > > +
> > > > > +       ret = input_register_device(ctlr->imu_input);
> > > > > +       if (ret)
> > > > > +               return ret;
> > > > > +
> > > > >         return 0;
> > > > >  }
> > > > >
> > > > > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > >
> > > > >         for (i = 4, j = 0; j < 6; i++, j++)
> > > > > -               ctlr->mac_addr[j] = report->reply.data[i];
> > > > > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> > > > >
> > > > >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> > > > >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > > > > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> > > > >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> > > > >                                 break;
> > > > >                         report = (struct joycon_input_report *)data;
> > > > > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > > > > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> > > > >                                 match = true;
> > > > >                         break;
> > > > >                 default:
> > > > > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> > > > >         }
> > > > >
> > > > > +       /* get IMU calibration data, and parse it */
> > > > > +       ret = joycon_request_imu_calibration(ctlr);
> > > > > +       if (ret) {
> > > > > +               /*
> > > > > +                * We can function with default calibration, but it may be
> > > > > +                * inaccurate. Provide a warning, and continue on.
> > > > > +                */
> > > > > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > > > > +       }
> > > > > +
> > > > >         /* Set the reporting mode to 0x30, which is the full report mode */
> > > > >         ret = joycon_set_report_mode(ctlr);
> > > > >         if (ret) {
> > > > > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > >                 goto err_mutex;
> > > > >         }
> > > > >
> > > > > +       /* Enable the IMU */
> > > > > +       ret = joycon_enable_imu(ctlr, true);
> > > > > +       if (ret) {
> > > > > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > > > > +               goto err_mutex;
> > > > > +       }
> > > > > +
> > > > >         ret = joycon_read_mac(ctlr);
> > > > >         if (ret) {
> > > > >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > > > > --
> > > > > 2.24.1
> > > > >



-- 
Daniel Ogorchock

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-09  5:22       ` Roderick Colenbrander
  2020-01-09  6:23         ` Carl Mueller
@ 2020-01-09  8:55         ` Daniel Ogorchock
  1 sibling, 0 replies; 25+ messages in thread
From: Daniel Ogorchock @ 2020-01-09  8:55 UTC (permalink / raw)
  To: Roderick Colenbrander
  Cc: linux-input, Billy Laws, Benjamin Tissoires, Jiri Kosina,
	Colenbrander, Roelof, Siarhei Vishniakou, s.jegen, Carl Mueller

Hi Roderick,

On Wed, Jan 8, 2020 at 11:22 PM Roderick Colenbrander
<thunderbird2k@gmail.com> wrote:
>
> Hi Daniel,
>
> Unfortunately there is no public test application at the moment to
> illustrate these features. I agree something is definitely needed.
>
> I need to see if we can come up with something. One of the test apps
> we have internally is a 3D cube, which is controllable by controller
> motion. It of course shows the gyro / accelerometer values, but the
> position of the cube is tied to the calculated orientation. If
> something is off you will see weird movements in the cube or drift
> building up over time etcetera.
>
> Though it would take some time to prepare something. The rest of the
> patch series looked fine I think, so the IMU parts may need to wait
> for a next kernel cycle, but all the other plumbing could go in (if
> others are okay of course).

I agree with postponing the IMU patch for later. I'll probably rebase things
so support for the charging grip can be added independent of IMU support.

Thanks,
Daniel

>
> Thanks,
> Roderick
>
> On Wed, Jan 8, 2020 at 7:26 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> >
> > Hi Roderick,
> >
> > Thanks for the feedback. In addition to the inline comments below,
> > do you have any recommendations for test programs that you
> > know work well with hid-sony's gyro implementation? Up to this
> > point I've just been using evtest, which obviously isn't too useful
> > for testing actual functionality of the gyro in an intuitive way.
> >
> > On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
> > <thunderbird2k@gmail.com> wrote:
> > >
> > > Hi Daniel,
> > >
> > > I had some time to review the motion sensors patch. I have added some
> > > feedback inline with your patch below.
> > >
> > > Aside from standard feedback on the code, I wanted to have a close
> > > look at the accelerometer / gyro data. My team has been doing a lot of
> > > work on this (and still is) and I want to make sure any work we do on
> > > "user space" software (e.g. Android) automatically works for Joycon as
> > > well. The accuracy of the data is very important else applications
> > > will make bad decisions. Userspace applications often combine the data
> > > of both sensors to calculate a position for position tracking.
> > > Inaccurate axes ranges or wrong precision can cause major issues. I
> > > may be a bit strict below, but it will just be to prevent headaches
> > > for others later on. I use the DS4 as a reference point as it was the
> > > first device (aside from Wii early on) where we properly report
> > > acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> > > be consistent with it here else applications could be confused, so we
> > > should use similar orientation of axes and resolutions.
> > >
> > > Thanks,
> > > Roderick
> > >
> > > On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> > > <djogorchock@gmail.com> wrote:
> > > >
> > > > This patch adds support for the controller's IMU. The accelerometer and
> > > > gyro data are both provided to userspace using a second input device.
> > > > The devices can be associated using their uniq value (set to the
> > > > controller's MAC address).
> > > >
> > > > The majority of this patch's functinoality was provided by Carl Mueller.
> > > >
> > > > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > > > ---
> > > >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> > > >  1 file changed, 259 insertions(+), 8 deletions(-)
> > > >
> > > > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > > > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > > > --- a/drivers/hid/hid-nintendo.c
> > > > +++ b/drivers/hid/hid-nintendo.c
> > > > @@ -101,12 +101,29 @@ 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)
> > > >
> > > > +/* SPI storage addresses of IMU factory calibration data */
> > > > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > > > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > > > +#define JC_IMU_CAL_DATA_SIZE \
> > > > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> > > >
> > > > +/* The accel axes will be mapped in terms of this magnitude */
> > > > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > > > +static const u16 JC_ACCEL_RES                  = 4096;
> > >
> > > What does the acceleration resolution equate to? For DS4 we use
> > > multiples of 'g'. (We don't know the position on earth, so can't
> > > report in m/s^2).
> > >
> > > > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > > > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > > > +
> > > > +/* The gyro axes will be mapped in terms of this magnitude */
> > > > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > > > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
> > >
> > > What does the gyro resolution equate to? For DS4 we report "degrees
> > > per second". We should do the same for the joycons, but I don't know
> > > how you guys figured out the exact resolution. As I mentioned if it is
> > > not accurate, applications will make wrong calculations.
> > >
> > > > +static const u16 JC_GYRO_FUZZ                  = 10;
> > > > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > > > +
> > > >  /* frequency/amplitude tables for rumble */
> > > >  struct joycon_rumble_freq_data {
> > > >         u16 high;
> > > > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> > > >         s32 center;
> > > >  };
> > > >
> > > > +struct joycon_imu_cal {
> > > > +       s16 offset[3];
> > > > +       s16 scale[3];
> > > > +};
> > > > +
> > > >  /*
> > > >   * All the controller's button values are stored in a u32.
> > > >   * They can be accessed with bitwise ANDs.
> > > > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> > > >         u8 data[0]; /* will be at most 35 bytes */
> > > >  } __packed;
> > > >
> > > > +struct joycon_imu_data {
> > > > +       s16 accel_x;
> > > > +       s16 accel_y;
> > > > +       s16 accel_z;
> > > > +       s16 gyro_x;
> > > > +       s16 gyro_y;
> > > > +       s16 gyro_z;
> > > > +} __packed;
> > > > +
> > > > +struct joycon_imu_report {
> > > > +       struct joycon_imu_data data[3];
> > > > +} __packed;
> > >
> > > See comments later on about imu_data, but you can't directly cast your
> > > data buffer to this data type due to endian issues. It may not really
> > > make sense to keep the data structure as you need to do manual data
> > > fetching.
> > >
> > > > +
> > > >  struct joycon_input_report {
> > > >         u8 id;
> > > >         u8 timer;
> > > > @@ -290,11 +325,10 @@ struct joycon_input_report {
> > > >         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;
> > > > +       union {
> > > > +               struct joycon_subcmd_reply subcmd_reply;
> > > > +               struct joycon_imu_report imu_report;
> > >
> > > maybe add a raw byte array to this union. Could help for parsing the imu data.
> > > > +       };
> > > >  } __packed;
> > > >
> > > >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > > > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> > > >         struct joycon_stick_cal right_stick_cal_x;
> > > >         struct joycon_stick_cal right_stick_cal_y;
> > > >
> > > > +       struct joycon_imu_cal accel_cal;
> > > > +       struct joycon_imu_cal gyro_cal;
> > > > +
> > > >         /* power supply data */
> > > >         struct power_supply *battery;
> > > >         struct power_supply_desc battery_desc;
> > > > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> > > >         u16 rumble_lh_freq;
> > > >         u16 rumble_rl_freq;
> > > >         u16 rumble_rh_freq;
> > > > +
> > > > +       /* imu */
> > > > +       struct input_dev *imu_input;
> > > > +       int timestamp;
> > > >  };
> > > >
> > > >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > > > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > >         }
> > > >
> > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > -       raw_cal = &report->reply.data[5];
> > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > >
> > > >         /* left stick calibration parsing */
> > > >         cal_x = &ctlr->left_stick_cal_x;
> > > > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > >         return 0;
> > > >  }
> > > >
> > > > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > > > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > > > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > > > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > > > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > > > +{
> > > > +       struct joycon_subcmd_request *req;
> > > > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > > > +       struct joycon_input_report *report;
> > > > +       u8 *data;
> > > > +       u8 *raw_cal;
> > > > +       int ret;
> > > > +       int i;
> > > > +
> > > > +       /* request IMU calibration data */
> > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > > > +       data = req->data;
> > > > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > > > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > > > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > > > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
> > >
> > > Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> > > these calculations yourself:
> > > put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
> > >
> > > > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > > > +
> > > > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > > > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > > > +
> > > > +       if (ret) {
> > > > +               hid_warn(ctlr->hdev,
> > > > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > > > +                        ret);
> > > > +
> > > > +               for (i = 0; i < 3; i++) {
> > > > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > > > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > > > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > > > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > > > +               }
> > > > +               return ret;
> > > > +       }
> > > > +
> > > > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > +
> > > > +       /* IMU calibration parsing */
> > > > +       for (i = 0; i < 3; i++) {
> > > > +               int j = i * 2;
> > > > +
> > > > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > > > +                                               ((s16)raw_cal[j + 1] << 8);
> > > > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > > > +                                               ((s16)raw_cal[j + 7] << 8);
> > > > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > > > +                                               ((s16)raw_cal[j + 13] << 8);
> > > > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > > > +                                               ((s16)raw_cal[j + 19] << 8);
> > >
> > > get_unaligned_le16 instead of doing your own bitshifts?
> > > > +       }
> > > > +
> > > > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > > > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > > > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > > > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > > > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > > > +                           ctlr->accel_cal.offset[0],
> > > > +                           ctlr->accel_cal.offset[1],
> > > > +                           ctlr->accel_cal.offset[2],
> > > > +                           ctlr->accel_cal.scale[0],
> > > > +                           ctlr->accel_cal.scale[1],
> > > > +                           ctlr->accel_cal.scale[2],
> > > > +                           ctlr->gyro_cal.offset[0],
> > > > +                           ctlr->gyro_cal.offset[1],
> > > > +                           ctlr->gyro_cal.offset[2],
> > > > +                           ctlr->gyro_cal.scale[0],
> > > > +                           ctlr->gyro_cal.scale[1],
> > > > +                           ctlr->gyro_cal.scale[2]);
> > > > +
> > > > +       return 0;
> > > > +}
> > > > +
> > > >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> > > >  {
> > > >         struct joycon_subcmd_request *req;
> > > > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> > > >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> > > >  }
> > > >
> > > > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > > > +{
> > > > +       struct joycon_subcmd_request *req;
> > > > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > > > +
> > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > > > +       req->data[0] = enable ? 0x01 : 0x00;
> > > > +
> > > > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > > > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > > > +}
> > > > +
> > > >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> > > >  {
> > > >         s32 center = cal->center;
> > > > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> > > >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> > > >                 wake_up(&ctlr->wait);
> > > >         }
> > > > +
> > > > +       /* parse IMU data if present */
> > > > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > > > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
> > >
> > > I don't think you can directly covert your input report data to
> > > imu_data. Until now you have been lucky enough (based on a quick
> > > glance of the the other patches) that your data are single bytes.
> > > However, this data seems to be a bunch of s16's. In other words you
> > > have to deal with endianess issues. You need to use get_unaligned_le16
> > > to retrieve data from your raw byte buffer. See other HID drivers for
> > > reference.
> >
> > Ah, good point. I'd overlooked that entirely.
> >
> > >
> > > Since you will have to use get_unaligned_le16, it probably won't make
> > > much sense to really have a joycon_imu_data structure. Maybe extend
> > > your input_buffer union with raw bytes and in this case just use raw
> > > bytes. Each of your loop iterations below just grab the values from
> > > the buffer and store them in a variable.
> > >
> > > > +               struct input_dev *idev = ctlr->imu_input;
> > > > +               int i;
> > > > +               int value[6];
> > > > +
> > > > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > > > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > > > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > > > +                                   ctlr->timestamp);
> > >
> > > How sure are you that this is always 5ms? Is there a hardware
> > > timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> > > (Bluetooth is lossy) and not all packets even make it.
> > >
> >
> > It appears that the closest thing to a hardware timestamp available is a
> > quickly incrementing 1-byte timer sent with every report. It's only really
> > useful for latency estimation. Each input report includes 3 IMU samples
> > which are 5ms apart for the joy-cons. It's recently come to my attention
> > that the samples may be spaced differently for the pro controller, so I'm
> > going to need to dive into this more anyway. I'm not sure what a great
> > way would be to handle lost reports.
> >
> > > > +
> > > > +                       /*
> > > > +                        * Rather than convert to floats, we adjust by
> > > > +                        * calibration factors and then multiply by the default
> > > > +                        * scaling values. This way, the consumer only needs to
> > > > +                        * divide by the default scale values.
> > > > +                        */
> > > > +                       value[0] = (imu_data[i].gyro_x -
> > > > +                                   ctlr->gyro_cal.offset[0]) *
> > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > > > +                       value[1] = (imu_data[i].gyro_y -
> > > > +                                   ctlr->gyro_cal.offset[1]) *
> > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > > > +                       value[2] = (imu_data[i].gyro_z -
> > > > +                                   ctlr->gyro_cal.offset[2]) *
> > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > > > +
> > > > +                       value[3] = (imu_data[i].accel_x -
> > > > +                                   ctlr->accel_cal.offset[0]) *
> > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > > > +                       value[4] = (imu_data[i].accel_y -
> > > > +                                   ctlr->accel_cal.offset[1]) *
> > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > > > +                       value[5] = (imu_data[i].accel_z -
> > > > +                                   ctlr->accel_cal.offset[2]) *
> > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
> > >
> > > Just in case I would double check the precision of your calculations.
> > > For DS4 we had similar calculations, but we had a small loss of
> > > precision, which ultimately caused major calculation errors.
> > > (Applications often use accelerometer + gyro data to calculate an
> > > absolute position. This involves  integration.. and small errors
> > > become big). We had to use the "mult_frac" macro to restore some of
> > > the precision during the calculations. It might be something to double
> > > check.
> > >
> >
> > That's good to know. I'll look more closely at how it's implemented in
> > hid-sony.
> >
> > > In addition what orientation are you using for the axes? I need to
> > > double check the DS4 datasheets, but I think we were using a "right
> > > handed" coordinate system. (So if you make a fist of your right hand
> > > with thumb up, the gyro curls around it counter clockwise along the
> > > direction of your fingers).
> > >
> >
> > The orientation of the axes (and much more) are documented here:
> > https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
> > Since the the right vs. left joy-cons have different axes orientations, I
> > assume it's preferable to standardize it all in software to match the
> > orientation of the DS4.
> >
> > >
> > > > +
> > > > +                       input_report_abs(idev, ABS_RX, value[0]);
> > > > +                       input_report_abs(idev, ABS_RY, value[1]);
> > > > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > > > +                       input_report_abs(idev, ABS_X, value[3]);
> > > > +                       input_report_abs(idev, ABS_Y, value[4]);
> > > > +                       input_report_abs(idev, ABS_Z, value[5]);
> > > > +                       input_sync(idev);
> > > > +               }
> > > > +       }
> > > >  }
> > > >
> > > >  static void joycon_rumble_worker(struct work_struct *work)
> > > > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > >  {
> > > >         struct hid_device *hdev;
> > > >         const char *name;
> > > > +       const char *imu_name;
> > > >         int ret;
> > > >         int i;
> > > >
> > > > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > >         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_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");
> > > > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > >         if (ret)
> > > >                 return ret;
> > > >
> > > > +       /* configure the imu input device */
> > > > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > > > +       if (!ctlr->imu_input)
> > > > +               return -ENOMEM;
> > > > +
> > > > +       ctlr->imu_input->id.bustype = hdev->bus;
> > > > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > > > +       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;
> > > > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > > > +
> > > > +       /* configure imu axes */
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > > > +
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > +
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > > > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > > > +
> > > > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > > > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > > > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > > > +
> > > > +       ret = input_register_device(ctlr->imu_input);
> > > > +       if (ret)
> > > > +               return ret;
> > > > +
> > > >         return 0;
> > > >  }
> > > >
> > > > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > >
> > > >         for (i = 4, j = 0; j < 6; i++, j++)
> > > > -               ctlr->mac_addr[j] = report->reply.data[i];
> > > > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> > > >
> > > >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> > > >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > > > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> > > >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> > > >                                 break;
> > > >                         report = (struct joycon_input_report *)data;
> > > > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > > > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> > > >                                 match = true;
> > > >                         break;
> > > >                 default:
> > > > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> > > >         }
> > > >
> > > > +       /* get IMU calibration data, and parse it */
> > > > +       ret = joycon_request_imu_calibration(ctlr);
> > > > +       if (ret) {
> > > > +               /*
> > > > +                * We can function with default calibration, but it may be
> > > > +                * inaccurate. Provide a warning, and continue on.
> > > > +                */
> > > > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > > > +       }
> > > > +
> > > >         /* Set the reporting mode to 0x30, which is the full report mode */
> > > >         ret = joycon_set_report_mode(ctlr);
> > > >         if (ret) {
> > > > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > >                 goto err_mutex;
> > > >         }
> > > >
> > > > +       /* Enable the IMU */
> > > > +       ret = joycon_enable_imu(ctlr, true);
> > > > +       if (ret) {
> > > > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > > > +               goto err_mutex;
> > > > +       }
> > > > +
> > > >         ret = joycon_read_mac(ctlr);
> > > >         if (ret) {
> > > >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > > > --
> > > > 2.24.1
> > > >



-- 
Daniel Ogorchock

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-09  8:53           ` Daniel Ogorchock
@ 2020-01-09 16:37             ` Roderick Colenbrander
  2020-01-09 20:38               ` Carl Mueller
  0 siblings, 1 reply; 25+ messages in thread
From: Roderick Colenbrander @ 2020-01-09 16:37 UTC (permalink / raw)
  To: Daniel Ogorchock
  Cc: Carl Mueller, linux-input, Billy Laws, Benjamin Tissoires,
	Jiri Kosina, Colenbrander, Roelof, Siarhei Vishniakou, s.jegen

Hi Daniel,

Whether to report as HAT or BTN_DPAD depends on how the hardware works
internally. You see the HAT vs buton at the HID report layer (it is
quite different), but if you just touch the controller sticks you will
also notice. A HAT axis is really like a joystick e.g. with a range -1
to +1. If you hold down e.g. "left" and "right" on a d-pad, you would
get "0" back as you are unable to press both left and right at the
same time. With d-pad buttons you could press left and right at the
same time (depending on design).

When I just tried my Switch Pro controller it really felt like the
d-pad were buttons. Various other controllers using BTN_DPAD as well
for example DualShock 3. Most applications these days should use SDL2,
which will hide that.

Thanks,
Roderick

On Thu, Jan 9, 2020 at 12:53 AM Daniel Ogorchock <djogorchock@gmail.com> wrote:
>
> Hi Carl,
>
> When I was initially implementing the button mappings, I referred to the
> standard described here:
>     https://www.kernel.org/doc/html/latest/input/gamepad.html
>
> It mentions under the d-pad section that digital inputs should be
> reported using the BTN_DPAD_* variant. Do the other controllers
> mentioned report their d-pads as analog values, or has using the
> ABS_HAT* values become the de facto format for digital inputs
> as well?
>
> If the latter, I guess it would make sense to go with the crowd for the pro.
> It just seems a bit odd to report digital inputs as absolute axes, but I'm
> open to changing it if that's the consensus.
>
> Thanks,
> Daniel
>
> On Thu, Jan 9, 2020 at 12:23 AM Carl Mueller <carmueller@gmail.com> wrote:
> >
> > (3rd/final attempt at reply due to list server rejecting message with
> > HTML subpart; sorry about the spam!)
> >
> > Hi Everyone,
> >
> > In trying out this driver to use with the Switch Pro Controller,
> > the biggest difficulty I had was that the D-pad is reported as buttons
> > instead of as a Hat.  All other controllers that are similar in structure
> > (such as the PS3 and PS4 controllers, the Xbox controllers, and many
> > others) report the D-pad as a Hat.  This kind of consistency is needed
> > to avoid lots of software compatibility issues.
> >
> > I mentioned this to Daniel, and he indicated that he wanted the Switch
> > Pro Controller to behave like the JoyCons, which have buttons instead
> > of a D-pad.  Having the JoyCons report the buttons as buttons makes
> > sense, since it's possible to push opposite directions at the same time.
> > However, I don't think that the argument carries over to the Switch Pro
> > Controller, since it has a physically different control.
> >
> > Again, the consistency with the other controllers that have all the same
> > physical structure and the same types of controls seems more important
> > to me than consistency with a controller that is physically much different.
> > Additionally, many games are already written for use with controllers
> > that look like the Switch Pro Controller, whereas fewer are written for use
> > with Nintendo JoyCons.  If we have to cause trouble for one side or the
> > other, let's not cause trouble with the more established side.
> >
> > On Thu, Jan 9, 2020 at 12:22 AM Roderick Colenbrander
> > <thunderbird2k@gmail.com> wrote:
> > >
> > > Hi Daniel,
> > >
> > > Unfortunately there is no public test application at the moment to
> > > illustrate these features. I agree something is definitely needed.
> > >
> > > I need to see if we can come up with something. One of the test apps
> > > we have internally is a 3D cube, which is controllable by controller
> > > motion. It of course shows the gyro / accelerometer values, but the
> > > position of the cube is tied to the calculated orientation. If
> > > something is off you will see weird movements in the cube or drift
> > > building up over time etcetera.
> > >
> > > Though it would take some time to prepare something. The rest of the
> > > patch series looked fine I think, so the IMU parts may need to wait
> > > for a next kernel cycle, but all the other plumbing could go in (if
> > > others are okay of course).
> > >
> > > Thanks,
> > > Roderick
> > >
> > > On Wed, Jan 8, 2020 at 7:26 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> > > >
> > > > Hi Roderick,
> > > >
> > > > Thanks for the feedback. In addition to the inline comments below,
> > > > do you have any recommendations for test programs that you
> > > > know work well with hid-sony's gyro implementation? Up to this
> > > > point I've just been using evtest, which obviously isn't too useful
> > > > for testing actual functionality of the gyro in an intuitive way.
> > > >
> > > > On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
> > > > <thunderbird2k@gmail.com> wrote:
> > > > >
> > > > > Hi Daniel,
> > > > >
> > > > > I had some time to review the motion sensors patch. I have added some
> > > > > feedback inline with your patch below.
> > > > >
> > > > > Aside from standard feedback on the code, I wanted to have a close
> > > > > look at the accelerometer / gyro data. My team has been doing a lot of
> > > > > work on this (and still is) and I want to make sure any work we do on
> > > > > "user space" software (e.g. Android) automatically works for Joycon as
> > > > > well. The accuracy of the data is very important else applications
> > > > > will make bad decisions. Userspace applications often combine the data
> > > > > of both sensors to calculate a position for position tracking.
> > > > > Inaccurate axes ranges or wrong precision can cause major issues. I
> > > > > may be a bit strict below, but it will just be to prevent headaches
> > > > > for others later on. I use the DS4 as a reference point as it was the
> > > > > first device (aside from Wii early on) where we properly report
> > > > > acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> > > > > be consistent with it here else applications could be confused, so we
> > > > > should use similar orientation of axes and resolutions.
> > > > >
> > > > > Thanks,
> > > > > Roderick
> > > > >
> > > > > On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> > > > > <djogorchock@gmail.com> wrote:
> > > > > >
> > > > > > This patch adds support for the controller's IMU. The accelerometer and
> > > > > > gyro data are both provided to userspace using a second input device.
> > > > > > The devices can be associated using their uniq value (set to the
> > > > > > controller's MAC address).
> > > > > >
> > > > > > The majority of this patch's functinoality was provided by Carl Mueller.
> > > > > >
> > > > > > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > > > > > ---
> > > > > >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> > > > > >  1 file changed, 259 insertions(+), 8 deletions(-)
> > > > > >
> > > > > > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > > > > > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > > > > > --- a/drivers/hid/hid-nintendo.c
> > > > > > +++ b/drivers/hid/hid-nintendo.c
> > > > > > @@ -101,12 +101,29 @@ 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)
> > > > > >
> > > > > > +/* SPI storage addresses of IMU factory calibration data */
> > > > > > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > > > > > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > > > > > +#define JC_IMU_CAL_DATA_SIZE \
> > > > > > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> > > > > >
> > > > > > +/* The accel axes will be mapped in terms of this magnitude */
> > > > > > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > > > > > +static const u16 JC_ACCEL_RES                  = 4096;
> > > > >
> > > > > What does the acceleration resolution equate to? For DS4 we use
> > > > > multiples of 'g'. (We don't know the position on earth, so can't
> > > > > report in m/s^2).
> > > > >
> > > > > > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > > > > > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > > > > > +
> > > > > > +/* The gyro axes will be mapped in terms of this magnitude */
> > > > > > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > > > > > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
> > > > >
> > > > > What does the gyro resolution equate to? For DS4 we report "degrees
> > > > > per second". We should do the same for the joycons, but I don't know
> > > > > how you guys figured out the exact resolution. As I mentioned if it is
> > > > > not accurate, applications will make wrong calculations.
> > > > >
> > > > > > +static const u16 JC_GYRO_FUZZ                  = 10;
> > > > > > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > > > > > +
> > > > > >  /* frequency/amplitude tables for rumble */
> > > > > >  struct joycon_rumble_freq_data {
> > > > > >         u16 high;
> > > > > > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> > > > > >         s32 center;
> > > > > >  };
> > > > > >
> > > > > > +struct joycon_imu_cal {
> > > > > > +       s16 offset[3];
> > > > > > +       s16 scale[3];
> > > > > > +};
> > > > > > +
> > > > > >  /*
> > > > > >   * All the controller's button values are stored in a u32.
> > > > > >   * They can be accessed with bitwise ANDs.
> > > > > > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> > > > > >         u8 data[0]; /* will be at most 35 bytes */
> > > > > >  } __packed;
> > > > > >
> > > > > > +struct joycon_imu_data {
> > > > > > +       s16 accel_x;
> > > > > > +       s16 accel_y;
> > > > > > +       s16 accel_z;
> > > > > > +       s16 gyro_x;
> > > > > > +       s16 gyro_y;
> > > > > > +       s16 gyro_z;
> > > > > > +} __packed;
> > > > > > +
> > > > > > +struct joycon_imu_report {
> > > > > > +       struct joycon_imu_data data[3];
> > > > > > +} __packed;
> > > > >
> > > > > See comments later on about imu_data, but you can't directly cast your
> > > > > data buffer to this data type due to endian issues. It may not really
> > > > > make sense to keep the data structure as you need to do manual data
> > > > > fetching.
> > > > >
> > > > > > +
> > > > > >  struct joycon_input_report {
> > > > > >         u8 id;
> > > > > >         u8 timer;
> > > > > > @@ -290,11 +325,10 @@ struct joycon_input_report {
> > > > > >         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;
> > > > > > +       union {
> > > > > > +               struct joycon_subcmd_reply subcmd_reply;
> > > > > > +               struct joycon_imu_report imu_report;
> > > > >
> > > > > maybe add a raw byte array to this union. Could help for parsing the imu data.
> > > > > > +       };
> > > > > >  } __packed;
> > > > > >
> > > > > >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > > > > > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> > > > > >         struct joycon_stick_cal right_stick_cal_x;
> > > > > >         struct joycon_stick_cal right_stick_cal_y;
> > > > > >
> > > > > > +       struct joycon_imu_cal accel_cal;
> > > > > > +       struct joycon_imu_cal gyro_cal;
> > > > > > +
> > > > > >         /* power supply data */
> > > > > >         struct power_supply *battery;
> > > > > >         struct power_supply_desc battery_desc;
> > > > > > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> > > > > >         u16 rumble_lh_freq;
> > > > > >         u16 rumble_rl_freq;
> > > > > >         u16 rumble_rh_freq;
> > > > > > +
> > > > > > +       /* imu */
> > > > > > +       struct input_dev *imu_input;
> > > > > > +       int timestamp;
> > > > > >  };
> > > > > >
> > > > > >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > > > > > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > > >         }
> > > > > >
> > > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > -       raw_cal = &report->reply.data[5];
> > > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > >
> > > > > >         /* left stick calibration parsing */
> > > > > >         cal_x = &ctlr->left_stick_cal_x;
> > > > > > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > > >         return 0;
> > > > > >  }
> > > > > >
> > > > > > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > > > > > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > > > > > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > > > > > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > > > > > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > > > > > +{
> > > > > > +       struct joycon_subcmd_request *req;
> > > > > > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > > > > > +       struct joycon_input_report *report;
> > > > > > +       u8 *data;
> > > > > > +       u8 *raw_cal;
> > > > > > +       int ret;
> > > > > > +       int i;
> > > > > > +
> > > > > > +       /* request IMU calibration data */
> > > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > > > > > +       data = req->data;
> > > > > > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > > > > > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > > > > > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > > > > > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
> > > > >
> > > > > Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> > > > > these calculations yourself:
> > > > > put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
> > > > >
> > > > > > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > > > > > +
> > > > > > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > > > > > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > > > > > +
> > > > > > +       if (ret) {
> > > > > > +               hid_warn(ctlr->hdev,
> > > > > > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > > > > > +                        ret);
> > > > > > +
> > > > > > +               for (i = 0; i < 3; i++) {
> > > > > > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > > > > > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > > > > > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > > > > > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > > > > > +               }
> > > > > > +               return ret;
> > > > > > +       }
> > > > > > +
> > > > > > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > > +
> > > > > > +       /* IMU calibration parsing */
> > > > > > +       for (i = 0; i < 3; i++) {
> > > > > > +               int j = i * 2;
> > > > > > +
> > > > > > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > > > > > +                                               ((s16)raw_cal[j + 1] << 8);
> > > > > > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > > > > > +                                               ((s16)raw_cal[j + 7] << 8);
> > > > > > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > > > > > +                                               ((s16)raw_cal[j + 13] << 8);
> > > > > > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > > > > > +                                               ((s16)raw_cal[j + 19] << 8);
> > > > >
> > > > > get_unaligned_le16 instead of doing your own bitshifts?
> > > > > > +       }
> > > > > > +
> > > > > > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > > > > > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > > > > > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > > > > > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > > > > > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > > > > > +                           ctlr->accel_cal.offset[0],
> > > > > > +                           ctlr->accel_cal.offset[1],
> > > > > > +                           ctlr->accel_cal.offset[2],
> > > > > > +                           ctlr->accel_cal.scale[0],
> > > > > > +                           ctlr->accel_cal.scale[1],
> > > > > > +                           ctlr->accel_cal.scale[2],
> > > > > > +                           ctlr->gyro_cal.offset[0],
> > > > > > +                           ctlr->gyro_cal.offset[1],
> > > > > > +                           ctlr->gyro_cal.offset[2],
> > > > > > +                           ctlr->gyro_cal.scale[0],
> > > > > > +                           ctlr->gyro_cal.scale[1],
> > > > > > +                           ctlr->gyro_cal.scale[2]);
> > > > > > +
> > > > > > +       return 0;
> > > > > > +}
> > > > > > +
> > > > > >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> > > > > >  {
> > > > > >         struct joycon_subcmd_request *req;
> > > > > > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> > > > > >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> > > > > >  }
> > > > > >
> > > > > > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > > > > > +{
> > > > > > +       struct joycon_subcmd_request *req;
> > > > > > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > > > > > +
> > > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > > > > > +       req->data[0] = enable ? 0x01 : 0x00;
> > > > > > +
> > > > > > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > > > > > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > > > > > +}
> > > > > > +
> > > > > >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> > > > > >  {
> > > > > >         s32 center = cal->center;
> > > > > > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> > > > > >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> > > > > >                 wake_up(&ctlr->wait);
> > > > > >         }
> > > > > > +
> > > > > > +       /* parse IMU data if present */
> > > > > > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > > > > > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
> > > > >
> > > > > I don't think you can directly covert your input report data to
> > > > > imu_data. Until now you have been lucky enough (based on a quick
> > > > > glance of the the other patches) that your data are single bytes.
> > > > > However, this data seems to be a bunch of s16's. In other words you
> > > > > have to deal with endianess issues. You need to use get_unaligned_le16
> > > > > to retrieve data from your raw byte buffer. See other HID drivers for
> > > > > reference.
> > > >
> > > > Ah, good point. I'd overlooked that entirely.
> > > >
> > > > >
> > > > > Since you will have to use get_unaligned_le16, it probably won't make
> > > > > much sense to really have a joycon_imu_data structure. Maybe extend
> > > > > your input_buffer union with raw bytes and in this case just use raw
> > > > > bytes. Each of your loop iterations below just grab the values from
> > > > > the buffer and store them in a variable.
> > > > >
> > > > > > +               struct input_dev *idev = ctlr->imu_input;
> > > > > > +               int i;
> > > > > > +               int value[6];
> > > > > > +
> > > > > > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > > > > > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > > > > > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > > > > > +                                   ctlr->timestamp);
> > > > >
> > > > > How sure are you that this is always 5ms? Is there a hardware
> > > > > timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> > > > > (Bluetooth is lossy) and not all packets even make it.
> > > > >
> > > >
> > > > It appears that the closest thing to a hardware timestamp available is a
> > > > quickly incrementing 1-byte timer sent with every report. It's only really
> > > > useful for latency estimation. Each input report includes 3 IMU samples
> > > > which are 5ms apart for the joy-cons. It's recently come to my attention
> > > > that the samples may be spaced differently for the pro controller, so I'm
> > > > going to need to dive into this more anyway. I'm not sure what a great
> > > > way would be to handle lost reports.
> > > >
> > > > > > +
> > > > > > +                       /*
> > > > > > +                        * Rather than convert to floats, we adjust by
> > > > > > +                        * calibration factors and then multiply by the default
> > > > > > +                        * scaling values. This way, the consumer only needs to
> > > > > > +                        * divide by the default scale values.
> > > > > > +                        */
> > > > > > +                       value[0] = (imu_data[i].gyro_x -
> > > > > > +                                   ctlr->gyro_cal.offset[0]) *
> > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > > > > > +                       value[1] = (imu_data[i].gyro_y -
> > > > > > +                                   ctlr->gyro_cal.offset[1]) *
> > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > > > > > +                       value[2] = (imu_data[i].gyro_z -
> > > > > > +                                   ctlr->gyro_cal.offset[2]) *
> > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > > > > > +
> > > > > > +                       value[3] = (imu_data[i].accel_x -
> > > > > > +                                   ctlr->accel_cal.offset[0]) *
> > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > > > > > +                       value[4] = (imu_data[i].accel_y -
> > > > > > +                                   ctlr->accel_cal.offset[1]) *
> > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > > > > > +                       value[5] = (imu_data[i].accel_z -
> > > > > > +                                   ctlr->accel_cal.offset[2]) *
> > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
> > > > >
> > > > > Just in case I would double check the precision of your calculations.
> > > > > For DS4 we had similar calculations, but we had a small loss of
> > > > > precision, which ultimately caused major calculation errors.
> > > > > (Applications often use accelerometer + gyro data to calculate an
> > > > > absolute position. This involves  integration.. and small errors
> > > > > become big). We had to use the "mult_frac" macro to restore some of
> > > > > the precision during the calculations. It might be something to double
> > > > > check.
> > > > >
> > > >
> > > > That's good to know. I'll look more closely at how it's implemented in
> > > > hid-sony.
> > > >
> > > > > In addition what orientation are you using for the axes? I need to
> > > > > double check the DS4 datasheets, but I think we were using a "right
> > > > > handed" coordinate system. (So if you make a fist of your right hand
> > > > > with thumb up, the gyro curls around it counter clockwise along the
> > > > > direction of your fingers).
> > > > >
> > > >
> > > > The orientation of the axes (and much more) are documented here:
> > > > https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
> > > > Since the the right vs. left joy-cons have different axes orientations, I
> > > > assume it's preferable to standardize it all in software to match the
> > > > orientation of the DS4.
> > > >
> > > > >
> > > > > > +
> > > > > > +                       input_report_abs(idev, ABS_RX, value[0]);
> > > > > > +                       input_report_abs(idev, ABS_RY, value[1]);
> > > > > > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > > > > > +                       input_report_abs(idev, ABS_X, value[3]);
> > > > > > +                       input_report_abs(idev, ABS_Y, value[4]);
> > > > > > +                       input_report_abs(idev, ABS_Z, value[5]);
> > > > > > +                       input_sync(idev);
> > > > > > +               }
> > > > > > +       }
> > > > > >  }
> > > > > >
> > > > > >  static void joycon_rumble_worker(struct work_struct *work)
> > > > > > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > >  {
> > > > > >         struct hid_device *hdev;
> > > > > >         const char *name;
> > > > > > +       const char *imu_name;
> > > > > >         int ret;
> > > > > >         int i;
> > > > > >
> > > > > > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > >         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_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");
> > > > > > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > >         if (ret)
> > > > > >                 return ret;
> > > > > >
> > > > > > +       /* configure the imu input device */
> > > > > > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > > > > > +       if (!ctlr->imu_input)
> > > > > > +               return -ENOMEM;
> > > > > > +
> > > > > > +       ctlr->imu_input->id.bustype = hdev->bus;
> > > > > > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > > > > > +       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;
> > > > > > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > > > > > +
> > > > > > +       /* configure imu axes */
> > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > > > > > +
> > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > +
> > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > > > > > +
> > > > > > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > > > > > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > > > > > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > > > > > +
> > > > > > +       ret = input_register_device(ctlr->imu_input);
> > > > > > +       if (ret)
> > > > > > +               return ret;
> > > > > > +
> > > > > >         return 0;
> > > > > >  }
> > > > > >
> > > > > > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> > > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > >
> > > > > >         for (i = 4, j = 0; j < 6; i++, j++)
> > > > > > -               ctlr->mac_addr[j] = report->reply.data[i];
> > > > > > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> > > > > >
> > > > > >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> > > > > >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > > > > > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> > > > > >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> > > > > >                                 break;
> > > > > >                         report = (struct joycon_input_report *)data;
> > > > > > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > > > > > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> > > > > >                                 match = true;
> > > > > >                         break;
> > > > > >                 default:
> > > > > > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > > >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> > > > > >         }
> > > > > >
> > > > > > +       /* get IMU calibration data, and parse it */
> > > > > > +       ret = joycon_request_imu_calibration(ctlr);
> > > > > > +       if (ret) {
> > > > > > +               /*
> > > > > > +                * We can function with default calibration, but it may be
> > > > > > +                * inaccurate. Provide a warning, and continue on.
> > > > > > +                */
> > > > > > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > > > > > +       }
> > > > > > +
> > > > > >         /* Set the reporting mode to 0x30, which is the full report mode */
> > > > > >         ret = joycon_set_report_mode(ctlr);
> > > > > >         if (ret) {
> > > > > > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > > >                 goto err_mutex;
> > > > > >         }
> > > > > >
> > > > > > +       /* Enable the IMU */
> > > > > > +       ret = joycon_enable_imu(ctlr, true);
> > > > > > +       if (ret) {
> > > > > > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > > > > > +               goto err_mutex;
> > > > > > +       }
> > > > > > +
> > > > > >         ret = joycon_read_mac(ctlr);
> > > > > >         if (ret) {
> > > > > >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > > > > > --
> > > > > > 2.24.1
> > > > > >
>
>
>
> --
> Daniel Ogorchock

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-09 16:37             ` Roderick Colenbrander
@ 2020-01-09 20:38               ` Carl Mueller
       [not found]                 ` <CAKF84v26=X8OLPavdE52tprm=WOynUXRz2aDjz5Bvqw6rdTZQg@mail.gmail.com>
  2020-01-21  2:49                 ` Daniel Ogorchock
  0 siblings, 2 replies; 25+ messages in thread
From: Carl Mueller @ 2020-01-09 20:38 UTC (permalink / raw)
  To: Roderick Colenbrander
  Cc: Daniel Ogorchock, linux-input, Billy Laws, Benjamin Tissoires,
	Jiri Kosina, Colenbrander, Roelof, Siarhei Vishniakou, s.jegen

Hi Roderick,

The Switch Pro Controller D-pad functions as a D-pad.  You cannot push
left and right at the same time.

Also, the Switch Pro Controller already functions as a controller when
connected with Bluetooth and
using the standard Linux device driver.  In this case, the D-pad is
registered as a Hat.
I'd prefer if we didn't change this.


On Thu, Jan 9, 2020 at 11:38 AM Roderick Colenbrander
<thunderbird2k@gmail.com> wrote:
>
> Hi Daniel,
>
> Whether to report as HAT or BTN_DPAD depends on how the hardware works
> internally. You see the HAT vs buton at the HID report layer (it is
> quite different), but if you just touch the controller sticks you will
> also notice. A HAT axis is really like a joystick e.g. with a range -1
> to +1. If you hold down e.g. "left" and "right" on a d-pad, you would
> get "0" back as you are unable to press both left and right at the
> same time. With d-pad buttons you could press left and right at the
> same time (depending on design).
>
> When I just tried my Switch Pro controller it really felt like the
> d-pad were buttons. Various other controllers using BTN_DPAD as well
> for example DualShock 3. Most applications these days should use SDL2,
> which will hide that.
>
> Thanks,
> Roderick
>
> On Thu, Jan 9, 2020 at 12:53 AM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> >
> > Hi Carl,
> >
> > When I was initially implementing the button mappings, I referred to the
> > standard described here:
> >     https://www.kernel.org/doc/html/latest/input/gamepad.html
> >
> > It mentions under the d-pad section that digital inputs should be
> > reported using the BTN_DPAD_* variant. Do the other controllers
> > mentioned report their d-pads as analog values, or has using the
> > ABS_HAT* values become the de facto format for digital inputs
> > as well?
> >
> > If the latter, I guess it would make sense to go with the crowd for the pro.
> > It just seems a bit odd to report digital inputs as absolute axes, but I'm
> > open to changing it if that's the consensus.
> >
> > Thanks,
> > Daniel
> >
> > On Thu, Jan 9, 2020 at 12:23 AM Carl Mueller <carmueller@gmail.com> wrote:
> > >
> > > (3rd/final attempt at reply due to list server rejecting message with
> > > HTML subpart; sorry about the spam!)
> > >
> > > Hi Everyone,
> > >
> > > In trying out this driver to use with the Switch Pro Controller,
> > > the biggest difficulty I had was that the D-pad is reported as buttons
> > > instead of as a Hat.  All other controllers that are similar in structure
> > > (such as the PS3 and PS4 controllers, the Xbox controllers, and many
> > > others) report the D-pad as a Hat.  This kind of consistency is needed
> > > to avoid lots of software compatibility issues.
> > >
> > > I mentioned this to Daniel, and he indicated that he wanted the Switch
> > > Pro Controller to behave like the JoyCons, which have buttons instead
> > > of a D-pad.  Having the JoyCons report the buttons as buttons makes
> > > sense, since it's possible to push opposite directions at the same time.
> > > However, I don't think that the argument carries over to the Switch Pro
> > > Controller, since it has a physically different control.
> > >
> > > Again, the consistency with the other controllers that have all the same
> > > physical structure and the same types of controls seems more important
> > > to me than consistency with a controller that is physically much different.
> > > Additionally, many games are already written for use with controllers
> > > that look like the Switch Pro Controller, whereas fewer are written for use
> > > with Nintendo JoyCons.  If we have to cause trouble for one side or the
> > > other, let's not cause trouble with the more established side.
> > >
> > > On Thu, Jan 9, 2020 at 12:22 AM Roderick Colenbrander
> > > <thunderbird2k@gmail.com> wrote:
> > > >
> > > > Hi Daniel,
> > > >
> > > > Unfortunately there is no public test application at the moment to
> > > > illustrate these features. I agree something is definitely needed.
> > > >
> > > > I need to see if we can come up with something. One of the test apps
> > > > we have internally is a 3D cube, which is controllable by controller
> > > > motion. It of course shows the gyro / accelerometer values, but the
> > > > position of the cube is tied to the calculated orientation. If
> > > > something is off you will see weird movements in the cube or drift
> > > > building up over time etcetera.
> > > >
> > > > Though it would take some time to prepare something. The rest of the
> > > > patch series looked fine I think, so the IMU parts may need to wait
> > > > for a next kernel cycle, but all the other plumbing could go in (if
> > > > others are okay of course).
> > > >
> > > > Thanks,
> > > > Roderick
> > > >
> > > > On Wed, Jan 8, 2020 at 7:26 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> > > > >
> > > > > Hi Roderick,
> > > > >
> > > > > Thanks for the feedback. In addition to the inline comments below,
> > > > > do you have any recommendations for test programs that you
> > > > > know work well with hid-sony's gyro implementation? Up to this
> > > > > point I've just been using evtest, which obviously isn't too useful
> > > > > for testing actual functionality of the gyro in an intuitive way.
> > > > >
> > > > > On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
> > > > > <thunderbird2k@gmail.com> wrote:
> > > > > >
> > > > > > Hi Daniel,
> > > > > >
> > > > > > I had some time to review the motion sensors patch. I have added some
> > > > > > feedback inline with your patch below.
> > > > > >
> > > > > > Aside from standard feedback on the code, I wanted to have a close
> > > > > > look at the accelerometer / gyro data. My team has been doing a lot of
> > > > > > work on this (and still is) and I want to make sure any work we do on
> > > > > > "user space" software (e.g. Android) automatically works for Joycon as
> > > > > > well. The accuracy of the data is very important else applications
> > > > > > will make bad decisions. Userspace applications often combine the data
> > > > > > of both sensors to calculate a position for position tracking.
> > > > > > Inaccurate axes ranges or wrong precision can cause major issues. I
> > > > > > may be a bit strict below, but it will just be to prevent headaches
> > > > > > for others later on. I use the DS4 as a reference point as it was the
> > > > > > first device (aside from Wii early on) where we properly report
> > > > > > acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> > > > > > be consistent with it here else applications could be confused, so we
> > > > > > should use similar orientation of axes and resolutions.
> > > > > >
> > > > > > Thanks,
> > > > > > Roderick
> > > > > >
> > > > > > On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> > > > > > <djogorchock@gmail.com> wrote:
> > > > > > >
> > > > > > > This patch adds support for the controller's IMU. The accelerometer and
> > > > > > > gyro data are both provided to userspace using a second input device.
> > > > > > > The devices can be associated using their uniq value (set to the
> > > > > > > controller's MAC address).
> > > > > > >
> > > > > > > The majority of this patch's functinoality was provided by Carl Mueller.
> > > > > > >
> > > > > > > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > > > > > > ---
> > > > > > >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> > > > > > >  1 file changed, 259 insertions(+), 8 deletions(-)
> > > > > > >
> > > > > > > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > > > > > > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > > > > > > --- a/drivers/hid/hid-nintendo.c
> > > > > > > +++ b/drivers/hid/hid-nintendo.c
> > > > > > > @@ -101,12 +101,29 @@ 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)
> > > > > > >
> > > > > > > +/* SPI storage addresses of IMU factory calibration data */
> > > > > > > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > > > > > > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > > > > > > +#define JC_IMU_CAL_DATA_SIZE \
> > > > > > > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> > > > > > >
> > > > > > > +/* The accel axes will be mapped in terms of this magnitude */
> > > > > > > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > > > > > > +static const u16 JC_ACCEL_RES                  = 4096;
> > > > > >
> > > > > > What does the acceleration resolution equate to? For DS4 we use
> > > > > > multiples of 'g'. (We don't know the position on earth, so can't
> > > > > > report in m/s^2).
> > > > > >
> > > > > > > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > > > > > > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > > > > > > +
> > > > > > > +/* The gyro axes will be mapped in terms of this magnitude */
> > > > > > > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > > > > > > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
> > > > > >
> > > > > > What does the gyro resolution equate to? For DS4 we report "degrees
> > > > > > per second". We should do the same for the joycons, but I don't know
> > > > > > how you guys figured out the exact resolution. As I mentioned if it is
> > > > > > not accurate, applications will make wrong calculations.
> > > > > >
> > > > > > > +static const u16 JC_GYRO_FUZZ                  = 10;
> > > > > > > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > > > > > > +
> > > > > > >  /* frequency/amplitude tables for rumble */
> > > > > > >  struct joycon_rumble_freq_data {
> > > > > > >         u16 high;
> > > > > > > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> > > > > > >         s32 center;
> > > > > > >  };
> > > > > > >
> > > > > > > +struct joycon_imu_cal {
> > > > > > > +       s16 offset[3];
> > > > > > > +       s16 scale[3];
> > > > > > > +};
> > > > > > > +
> > > > > > >  /*
> > > > > > >   * All the controller's button values are stored in a u32.
> > > > > > >   * They can be accessed with bitwise ANDs.
> > > > > > > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> > > > > > >         u8 data[0]; /* will be at most 35 bytes */
> > > > > > >  } __packed;
> > > > > > >
> > > > > > > +struct joycon_imu_data {
> > > > > > > +       s16 accel_x;
> > > > > > > +       s16 accel_y;
> > > > > > > +       s16 accel_z;
> > > > > > > +       s16 gyro_x;
> > > > > > > +       s16 gyro_y;
> > > > > > > +       s16 gyro_z;
> > > > > > > +} __packed;
> > > > > > > +
> > > > > > > +struct joycon_imu_report {
> > > > > > > +       struct joycon_imu_data data[3];
> > > > > > > +} __packed;
> > > > > >
> > > > > > See comments later on about imu_data, but you can't directly cast your
> > > > > > data buffer to this data type due to endian issues. It may not really
> > > > > > make sense to keep the data structure as you need to do manual data
> > > > > > fetching.
> > > > > >
> > > > > > > +
> > > > > > >  struct joycon_input_report {
> > > > > > >         u8 id;
> > > > > > >         u8 timer;
> > > > > > > @@ -290,11 +325,10 @@ struct joycon_input_report {
> > > > > > >         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;
> > > > > > > +       union {
> > > > > > > +               struct joycon_subcmd_reply subcmd_reply;
> > > > > > > +               struct joycon_imu_report imu_report;
> > > > > >
> > > > > > maybe add a raw byte array to this union. Could help for parsing the imu data.
> > > > > > > +       };
> > > > > > >  } __packed;
> > > > > > >
> > > > > > >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > > > > > > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> > > > > > >         struct joycon_stick_cal right_stick_cal_x;
> > > > > > >         struct joycon_stick_cal right_stick_cal_y;
> > > > > > >
> > > > > > > +       struct joycon_imu_cal accel_cal;
> > > > > > > +       struct joycon_imu_cal gyro_cal;
> > > > > > > +
> > > > > > >         /* power supply data */
> > > > > > >         struct power_supply *battery;
> > > > > > >         struct power_supply_desc battery_desc;
> > > > > > > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> > > > > > >         u16 rumble_lh_freq;
> > > > > > >         u16 rumble_rl_freq;
> > > > > > >         u16 rumble_rh_freq;
> > > > > > > +
> > > > > > > +       /* imu */
> > > > > > > +       struct input_dev *imu_input;
> > > > > > > +       int timestamp;
> > > > > > >  };
> > > > > > >
> > > > > > >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > > > > > > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > > > >         }
> > > > > > >
> > > > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > > -       raw_cal = &report->reply.data[5];
> > > > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > > >
> > > > > > >         /* left stick calibration parsing */
> > > > > > >         cal_x = &ctlr->left_stick_cal_x;
> > > > > > > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > > > >         return 0;
> > > > > > >  }
> > > > > > >
> > > > > > > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > > > > > > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > > > > > > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > > > > > > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > > > > > > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > > > > > > +{
> > > > > > > +       struct joycon_subcmd_request *req;
> > > > > > > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > > > > > > +       struct joycon_input_report *report;
> > > > > > > +       u8 *data;
> > > > > > > +       u8 *raw_cal;
> > > > > > > +       int ret;
> > > > > > > +       int i;
> > > > > > > +
> > > > > > > +       /* request IMU calibration data */
> > > > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > > > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > > > > > > +       data = req->data;
> > > > > > > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > > > > > > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > > > > > > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > > > > > > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
> > > > > >
> > > > > > Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> > > > > > these calculations yourself:
> > > > > > put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
> > > > > >
> > > > > > > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > > > > > > +
> > > > > > > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > > > > > > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > > > > > > +
> > > > > > > +       if (ret) {
> > > > > > > +               hid_warn(ctlr->hdev,
> > > > > > > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > > > > > > +                        ret);
> > > > > > > +
> > > > > > > +               for (i = 0; i < 3; i++) {
> > > > > > > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > > > > > > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > > > > > > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > > > > > > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > > > > > > +               }
> > > > > > > +               return ret;
> > > > > > > +       }
> > > > > > > +
> > > > > > > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > > > +
> > > > > > > +       /* IMU calibration parsing */
> > > > > > > +       for (i = 0; i < 3; i++) {
> > > > > > > +               int j = i * 2;
> > > > > > > +
> > > > > > > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > > > > > > +                                               ((s16)raw_cal[j + 1] << 8);
> > > > > > > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > > > > > > +                                               ((s16)raw_cal[j + 7] << 8);
> > > > > > > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > > > > > > +                                               ((s16)raw_cal[j + 13] << 8);
> > > > > > > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > > > > > > +                                               ((s16)raw_cal[j + 19] << 8);
> > > > > >
> > > > > > get_unaligned_le16 instead of doing your own bitshifts?
> > > > > > > +       }
> > > > > > > +
> > > > > > > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > > > > > > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > > > > > > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > > > > > > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > > > > > > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > > > > > > +                           ctlr->accel_cal.offset[0],
> > > > > > > +                           ctlr->accel_cal.offset[1],
> > > > > > > +                           ctlr->accel_cal.offset[2],
> > > > > > > +                           ctlr->accel_cal.scale[0],
> > > > > > > +                           ctlr->accel_cal.scale[1],
> > > > > > > +                           ctlr->accel_cal.scale[2],
> > > > > > > +                           ctlr->gyro_cal.offset[0],
> > > > > > > +                           ctlr->gyro_cal.offset[1],
> > > > > > > +                           ctlr->gyro_cal.offset[2],
> > > > > > > +                           ctlr->gyro_cal.scale[0],
> > > > > > > +                           ctlr->gyro_cal.scale[1],
> > > > > > > +                           ctlr->gyro_cal.scale[2]);
> > > > > > > +
> > > > > > > +       return 0;
> > > > > > > +}
> > > > > > > +
> > > > > > >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> > > > > > >  {
> > > > > > >         struct joycon_subcmd_request *req;
> > > > > > > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> > > > > > >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> > > > > > >  }
> > > > > > >
> > > > > > > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > > > > > > +{
> > > > > > > +       struct joycon_subcmd_request *req;
> > > > > > > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > > > > > > +
> > > > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > > > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > > > > > > +       req->data[0] = enable ? 0x01 : 0x00;
> > > > > > > +
> > > > > > > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > > > > > > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > > > > > > +}
> > > > > > > +
> > > > > > >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> > > > > > >  {
> > > > > > >         s32 center = cal->center;
> > > > > > > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> > > > > > >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> > > > > > >                 wake_up(&ctlr->wait);
> > > > > > >         }
> > > > > > > +
> > > > > > > +       /* parse IMU data if present */
> > > > > > > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > > > > > > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
> > > > > >
> > > > > > I don't think you can directly covert your input report data to
> > > > > > imu_data. Until now you have been lucky enough (based on a quick
> > > > > > glance of the the other patches) that your data are single bytes.
> > > > > > However, this data seems to be a bunch of s16's. In other words you
> > > > > > have to deal with endianess issues. You need to use get_unaligned_le16
> > > > > > to retrieve data from your raw byte buffer. See other HID drivers for
> > > > > > reference.
> > > > >
> > > > > Ah, good point. I'd overlooked that entirely.
> > > > >
> > > > > >
> > > > > > Since you will have to use get_unaligned_le16, it probably won't make
> > > > > > much sense to really have a joycon_imu_data structure. Maybe extend
> > > > > > your input_buffer union with raw bytes and in this case just use raw
> > > > > > bytes. Each of your loop iterations below just grab the values from
> > > > > > the buffer and store them in a variable.
> > > > > >
> > > > > > > +               struct input_dev *idev = ctlr->imu_input;
> > > > > > > +               int i;
> > > > > > > +               int value[6];
> > > > > > > +
> > > > > > > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > > > > > > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > > > > > > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > > > > > > +                                   ctlr->timestamp);
> > > > > >
> > > > > > How sure are you that this is always 5ms? Is there a hardware
> > > > > > timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> > > > > > (Bluetooth is lossy) and not all packets even make it.
> > > > > >
> > > > >
> > > > > It appears that the closest thing to a hardware timestamp available is a
> > > > > quickly incrementing 1-byte timer sent with every report. It's only really
> > > > > useful for latency estimation. Each input report includes 3 IMU samples
> > > > > which are 5ms apart for the joy-cons. It's recently come to my attention
> > > > > that the samples may be spaced differently for the pro controller, so I'm
> > > > > going to need to dive into this more anyway. I'm not sure what a great
> > > > > way would be to handle lost reports.
> > > > >
> > > > > > > +
> > > > > > > +                       /*
> > > > > > > +                        * Rather than convert to floats, we adjust by
> > > > > > > +                        * calibration factors and then multiply by the default
> > > > > > > +                        * scaling values. This way, the consumer only needs to
> > > > > > > +                        * divide by the default scale values.
> > > > > > > +                        */
> > > > > > > +                       value[0] = (imu_data[i].gyro_x -
> > > > > > > +                                   ctlr->gyro_cal.offset[0]) *
> > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > > > > > > +                       value[1] = (imu_data[i].gyro_y -
> > > > > > > +                                   ctlr->gyro_cal.offset[1]) *
> > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > > > > > > +                       value[2] = (imu_data[i].gyro_z -
> > > > > > > +                                   ctlr->gyro_cal.offset[2]) *
> > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > > > > > > +
> > > > > > > +                       value[3] = (imu_data[i].accel_x -
> > > > > > > +                                   ctlr->accel_cal.offset[0]) *
> > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > > > > > > +                       value[4] = (imu_data[i].accel_y -
> > > > > > > +                                   ctlr->accel_cal.offset[1]) *
> > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > > > > > > +                       value[5] = (imu_data[i].accel_z -
> > > > > > > +                                   ctlr->accel_cal.offset[2]) *
> > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
> > > > > >
> > > > > > Just in case I would double check the precision of your calculations.
> > > > > > For DS4 we had similar calculations, but we had a small loss of
> > > > > > precision, which ultimately caused major calculation errors.
> > > > > > (Applications often use accelerometer + gyro data to calculate an
> > > > > > absolute position. This involves  integration.. and small errors
> > > > > > become big). We had to use the "mult_frac" macro to restore some of
> > > > > > the precision during the calculations. It might be something to double
> > > > > > check.
> > > > > >
> > > > >
> > > > > That's good to know. I'll look more closely at how it's implemented in
> > > > > hid-sony.
> > > > >
> > > > > > In addition what orientation are you using for the axes? I need to
> > > > > > double check the DS4 datasheets, but I think we were using a "right
> > > > > > handed" coordinate system. (So if you make a fist of your right hand
> > > > > > with thumb up, the gyro curls around it counter clockwise along the
> > > > > > direction of your fingers).
> > > > > >
> > > > >
> > > > > The orientation of the axes (and much more) are documented here:
> > > > > https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
> > > > > Since the the right vs. left joy-cons have different axes orientations, I
> > > > > assume it's preferable to standardize it all in software to match the
> > > > > orientation of the DS4.
> > > > >
> > > > > >
> > > > > > > +
> > > > > > > +                       input_report_abs(idev, ABS_RX, value[0]);
> > > > > > > +                       input_report_abs(idev, ABS_RY, value[1]);
> > > > > > > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > > > > > > +                       input_report_abs(idev, ABS_X, value[3]);
> > > > > > > +                       input_report_abs(idev, ABS_Y, value[4]);
> > > > > > > +                       input_report_abs(idev, ABS_Z, value[5]);
> > > > > > > +                       input_sync(idev);
> > > > > > > +               }
> > > > > > > +       }
> > > > > > >  }
> > > > > > >
> > > > > > >  static void joycon_rumble_worker(struct work_struct *work)
> > > > > > > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > >  {
> > > > > > >         struct hid_device *hdev;
> > > > > > >         const char *name;
> > > > > > > +       const char *imu_name;
> > > > > > >         int ret;
> > > > > > >         int i;
> > > > > > >
> > > > > > > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > >         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_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");
> > > > > > > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > >         if (ret)
> > > > > > >                 return ret;
> > > > > > >
> > > > > > > +       /* configure the imu input device */
> > > > > > > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > > > > > > +       if (!ctlr->imu_input)
> > > > > > > +               return -ENOMEM;
> > > > > > > +
> > > > > > > +       ctlr->imu_input->id.bustype = hdev->bus;
> > > > > > > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > > > > > > +       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;
> > > > > > > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > > > > > > +
> > > > > > > +       /* configure imu axes */
> > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > > > > > > +
> > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > +
> > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > > > > > > +
> > > > > > > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > > > > > > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > > > > > > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > > > > > > +
> > > > > > > +       ret = input_register_device(ctlr->imu_input);
> > > > > > > +       if (ret)
> > > > > > > +               return ret;
> > > > > > > +
> > > > > > >         return 0;
> > > > > > >  }
> > > > > > >
> > > > > > > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> > > > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > >
> > > > > > >         for (i = 4, j = 0; j < 6; i++, j++)
> > > > > > > -               ctlr->mac_addr[j] = report->reply.data[i];
> > > > > > > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> > > > > > >
> > > > > > >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> > > > > > >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > > > > > > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> > > > > > >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> > > > > > >                                 break;
> > > > > > >                         report = (struct joycon_input_report *)data;
> > > > > > > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > > > > > > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> > > > > > >                                 match = true;
> > > > > > >                         break;
> > > > > > >                 default:
> > > > > > > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > > > >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> > > > > > >         }
> > > > > > >
> > > > > > > +       /* get IMU calibration data, and parse it */
> > > > > > > +       ret = joycon_request_imu_calibration(ctlr);
> > > > > > > +       if (ret) {
> > > > > > > +               /*
> > > > > > > +                * We can function with default calibration, but it may be
> > > > > > > +                * inaccurate. Provide a warning, and continue on.
> > > > > > > +                */
> > > > > > > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > > > > > > +       }
> > > > > > > +
> > > > > > >         /* Set the reporting mode to 0x30, which is the full report mode */
> > > > > > >         ret = joycon_set_report_mode(ctlr);
> > > > > > >         if (ret) {
> > > > > > > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > > > >                 goto err_mutex;
> > > > > > >         }
> > > > > > >
> > > > > > > +       /* Enable the IMU */
> > > > > > > +       ret = joycon_enable_imu(ctlr, true);
> > > > > > > +       if (ret) {
> > > > > > > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > > > > > > +               goto err_mutex;
> > > > > > > +       }
> > > > > > > +
> > > > > > >         ret = joycon_read_mac(ctlr);
> > > > > > >         if (ret) {
> > > > > > >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > > > > > > --
> > > > > > > 2.24.1
> > > > > > >
> >
> >
> >
> > --
> > Daniel Ogorchock

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
       [not found]                 ` <CAKF84v26=X8OLPavdE52tprm=WOynUXRz2aDjz5Bvqw6rdTZQg@mail.gmail.com>
@ 2020-01-18  3:49                   ` Daniel Ogorchock
  2020-01-21 18:19                     ` Siarhei Vishniakou
  0 siblings, 1 reply; 25+ messages in thread
From: Daniel Ogorchock @ 2020-01-18  3:49 UTC (permalink / raw)
  To: Siarhei Vishniakou
  Cc: Carl Mueller, Roderick Colenbrander, linux-input, Billy Laws,
	Benjamin Tissoires, Jiri Kosina, Colenbrander, Roelof, s.jegen

Hi Siarhei,

Thanks for catching that. I'll fix it for the next rev. I'll also do a
sanity check compilation for each patch in the series.

-Daniel

On Thu, Jan 9, 2020 at 2:55 PM Siarhei Vishniakou <svv@google.com> wrote:
>
> Hi Daniel,
>
> I just tried to build the first patch in isolation, and I'm hitting a build error: in the function nintendo_hid_remove, the variable "ctrl" is unused. It looks like it's first used in the rumble patch, 'add rumble support'. Could you please move that variable declaration there?
>
> Also, in the same rumble patch, I'm seeing that the function 'joycon_enable_rumble' is always called with the second parameter bool enable = true. Maybe that parameter can be removed from the function definition?

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-09 20:38               ` Carl Mueller
       [not found]                 ` <CAKF84v26=X8OLPavdE52tprm=WOynUXRz2aDjz5Bvqw6rdTZQg@mail.gmail.com>
@ 2020-01-21  2:49                 ` Daniel Ogorchock
  2020-01-22  5:19                   ` Roderick Colenbrander
  1 sibling, 1 reply; 25+ messages in thread
From: Daniel Ogorchock @ 2020-01-21  2:49 UTC (permalink / raw)
  To: Carl Mueller
  Cc: Roderick Colenbrander, linux-input, Billy Laws,
	Benjamin Tissoires, Jiri Kosina, Colenbrander, Roelof,
	Siarhei Vishniakou, s.jegen

Does anyone else have an opinion on this? I agree with Carl that the
D-pad cannot register opposite direction inputs simultaneously.

Thanks,
Daniel

On Thu, Jan 9, 2020 at 2:39 PM Carl Mueller <carmueller@gmail.com> wrote:
>
> Hi Roderick,
>
> The Switch Pro Controller D-pad functions as a D-pad.  You cannot push
> left and right at the same time.
>
> Also, the Switch Pro Controller already functions as a controller when
> connected with Bluetooth and
> using the standard Linux device driver.  In this case, the D-pad is
> registered as a Hat.
> I'd prefer if we didn't change this.
>
>
> On Thu, Jan 9, 2020 at 11:38 AM Roderick Colenbrander
> <thunderbird2k@gmail.com> wrote:
> >
> > Hi Daniel,
> >
> > Whether to report as HAT or BTN_DPAD depends on how the hardware works
> > internally. You see the HAT vs buton at the HID report layer (it is
> > quite different), but if you just touch the controller sticks you will
> > also notice. A HAT axis is really like a joystick e.g. with a range -1
> > to +1. If you hold down e.g. "left" and "right" on a d-pad, you would
> > get "0" back as you are unable to press both left and right at the
> > same time. With d-pad buttons you could press left and right at the
> > same time (depending on design).
> >
> > When I just tried my Switch Pro controller it really felt like the
> > d-pad were buttons. Various other controllers using BTN_DPAD as well
> > for example DualShock 3. Most applications these days should use SDL2,
> > which will hide that.
> >
> > Thanks,
> > Roderick
> >
> > On Thu, Jan 9, 2020 at 12:53 AM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> > >
> > > Hi Carl,
> > >
> > > When I was initially implementing the button mappings, I referred to the
> > > standard described here:
> > >     https://www.kernel.org/doc/html/latest/input/gamepad.html
> > >
> > > It mentions under the d-pad section that digital inputs should be
> > > reported using the BTN_DPAD_* variant. Do the other controllers
> > > mentioned report their d-pads as analog values, or has using the
> > > ABS_HAT* values become the de facto format for digital inputs
> > > as well?
> > >
> > > If the latter, I guess it would make sense to go with the crowd for the pro.
> > > It just seems a bit odd to report digital inputs as absolute axes, but I'm
> > > open to changing it if that's the consensus.
> > >
> > > Thanks,
> > > Daniel
> > >
> > > On Thu, Jan 9, 2020 at 12:23 AM Carl Mueller <carmueller@gmail.com> wrote:
> > > >
> > > > (3rd/final attempt at reply due to list server rejecting message with
> > > > HTML subpart; sorry about the spam!)
> > > >
> > > > Hi Everyone,
> > > >
> > > > In trying out this driver to use with the Switch Pro Controller,
> > > > the biggest difficulty I had was that the D-pad is reported as buttons
> > > > instead of as a Hat.  All other controllers that are similar in structure
> > > > (such as the PS3 and PS4 controllers, the Xbox controllers, and many
> > > > others) report the D-pad as a Hat.  This kind of consistency is needed
> > > > to avoid lots of software compatibility issues.
> > > >
> > > > I mentioned this to Daniel, and he indicated that he wanted the Switch
> > > > Pro Controller to behave like the JoyCons, which have buttons instead
> > > > of a D-pad.  Having the JoyCons report the buttons as buttons makes
> > > > sense, since it's possible to push opposite directions at the same time.
> > > > However, I don't think that the argument carries over to the Switch Pro
> > > > Controller, since it has a physically different control.
> > > >
> > > > Again, the consistency with the other controllers that have all the same
> > > > physical structure and the same types of controls seems more important
> > > > to me than consistency with a controller that is physically much different.
> > > > Additionally, many games are already written for use with controllers
> > > > that look like the Switch Pro Controller, whereas fewer are written for use
> > > > with Nintendo JoyCons.  If we have to cause trouble for one side or the
> > > > other, let's not cause trouble with the more established side.
> > > >
> > > > On Thu, Jan 9, 2020 at 12:22 AM Roderick Colenbrander
> > > > <thunderbird2k@gmail.com> wrote:
> > > > >
> > > > > Hi Daniel,
> > > > >
> > > > > Unfortunately there is no public test application at the moment to
> > > > > illustrate these features. I agree something is definitely needed.
> > > > >
> > > > > I need to see if we can come up with something. One of the test apps
> > > > > we have internally is a 3D cube, which is controllable by controller
> > > > > motion. It of course shows the gyro / accelerometer values, but the
> > > > > position of the cube is tied to the calculated orientation. If
> > > > > something is off you will see weird movements in the cube or drift
> > > > > building up over time etcetera.
> > > > >
> > > > > Though it would take some time to prepare something. The rest of the
> > > > > patch series looked fine I think, so the IMU parts may need to wait
> > > > > for a next kernel cycle, but all the other plumbing could go in (if
> > > > > others are okay of course).
> > > > >
> > > > > Thanks,
> > > > > Roderick
> > > > >
> > > > > On Wed, Jan 8, 2020 at 7:26 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> > > > > >
> > > > > > Hi Roderick,
> > > > > >
> > > > > > Thanks for the feedback. In addition to the inline comments below,
> > > > > > do you have any recommendations for test programs that you
> > > > > > know work well with hid-sony's gyro implementation? Up to this
> > > > > > point I've just been using evtest, which obviously isn't too useful
> > > > > > for testing actual functionality of the gyro in an intuitive way.
> > > > > >
> > > > > > On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
> > > > > > <thunderbird2k@gmail.com> wrote:
> > > > > > >
> > > > > > > Hi Daniel,
> > > > > > >
> > > > > > > I had some time to review the motion sensors patch. I have added some
> > > > > > > feedback inline with your patch below.
> > > > > > >
> > > > > > > Aside from standard feedback on the code, I wanted to have a close
> > > > > > > look at the accelerometer / gyro data. My team has been doing a lot of
> > > > > > > work on this (and still is) and I want to make sure any work we do on
> > > > > > > "user space" software (e.g. Android) automatically works for Joycon as
> > > > > > > well. The accuracy of the data is very important else applications
> > > > > > > will make bad decisions. Userspace applications often combine the data
> > > > > > > of both sensors to calculate a position for position tracking.
> > > > > > > Inaccurate axes ranges or wrong precision can cause major issues. I
> > > > > > > may be a bit strict below, but it will just be to prevent headaches
> > > > > > > for others later on. I use the DS4 as a reference point as it was the
> > > > > > > first device (aside from Wii early on) where we properly report
> > > > > > > acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> > > > > > > be consistent with it here else applications could be confused, so we
> > > > > > > should use similar orientation of axes and resolutions.
> > > > > > >
> > > > > > > Thanks,
> > > > > > > Roderick
> > > > > > >
> > > > > > > On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> > > > > > > <djogorchock@gmail.com> wrote:
> > > > > > > >
> > > > > > > > This patch adds support for the controller's IMU. The accelerometer and
> > > > > > > > gyro data are both provided to userspace using a second input device.
> > > > > > > > The devices can be associated using their uniq value (set to the
> > > > > > > > controller's MAC address).
> > > > > > > >
> > > > > > > > The majority of this patch's functinoality was provided by Carl Mueller.
> > > > > > > >
> > > > > > > > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > > > > > > > ---
> > > > > > > >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> > > > > > > >  1 file changed, 259 insertions(+), 8 deletions(-)
> > > > > > > >
> > > > > > > > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > > > > > > > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > > > > > > > --- a/drivers/hid/hid-nintendo.c
> > > > > > > > +++ b/drivers/hid/hid-nintendo.c
> > > > > > > > @@ -101,12 +101,29 @@ 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)
> > > > > > > >
> > > > > > > > +/* SPI storage addresses of IMU factory calibration data */
> > > > > > > > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > > > > > > > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > > > > > > > +#define JC_IMU_CAL_DATA_SIZE \
> > > > > > > > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> > > > > > > >
> > > > > > > > +/* The accel axes will be mapped in terms of this magnitude */
> > > > > > > > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > > > > > > > +static const u16 JC_ACCEL_RES                  = 4096;
> > > > > > >
> > > > > > > What does the acceleration resolution equate to? For DS4 we use
> > > > > > > multiples of 'g'. (We don't know the position on earth, so can't
> > > > > > > report in m/s^2).
> > > > > > >
> > > > > > > > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > > > > > > > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > > > > > > > +
> > > > > > > > +/* The gyro axes will be mapped in terms of this magnitude */
> > > > > > > > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > > > > > > > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
> > > > > > >
> > > > > > > What does the gyro resolution equate to? For DS4 we report "degrees
> > > > > > > per second". We should do the same for the joycons, but I don't know
> > > > > > > how you guys figured out the exact resolution. As I mentioned if it is
> > > > > > > not accurate, applications will make wrong calculations.
> > > > > > >
> > > > > > > > +static const u16 JC_GYRO_FUZZ                  = 10;
> > > > > > > > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > > > > > > > +
> > > > > > > >  /* frequency/amplitude tables for rumble */
> > > > > > > >  struct joycon_rumble_freq_data {
> > > > > > > >         u16 high;
> > > > > > > > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> > > > > > > >         s32 center;
> > > > > > > >  };
> > > > > > > >
> > > > > > > > +struct joycon_imu_cal {
> > > > > > > > +       s16 offset[3];
> > > > > > > > +       s16 scale[3];
> > > > > > > > +};
> > > > > > > > +
> > > > > > > >  /*
> > > > > > > >   * All the controller's button values are stored in a u32.
> > > > > > > >   * They can be accessed with bitwise ANDs.
> > > > > > > > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> > > > > > > >         u8 data[0]; /* will be at most 35 bytes */
> > > > > > > >  } __packed;
> > > > > > > >
> > > > > > > > +struct joycon_imu_data {
> > > > > > > > +       s16 accel_x;
> > > > > > > > +       s16 accel_y;
> > > > > > > > +       s16 accel_z;
> > > > > > > > +       s16 gyro_x;
> > > > > > > > +       s16 gyro_y;
> > > > > > > > +       s16 gyro_z;
> > > > > > > > +} __packed;
> > > > > > > > +
> > > > > > > > +struct joycon_imu_report {
> > > > > > > > +       struct joycon_imu_data data[3];
> > > > > > > > +} __packed;
> > > > > > >
> > > > > > > See comments later on about imu_data, but you can't directly cast your
> > > > > > > data buffer to this data type due to endian issues. It may not really
> > > > > > > make sense to keep the data structure as you need to do manual data
> > > > > > > fetching.
> > > > > > >
> > > > > > > > +
> > > > > > > >  struct joycon_input_report {
> > > > > > > >         u8 id;
> > > > > > > >         u8 timer;
> > > > > > > > @@ -290,11 +325,10 @@ struct joycon_input_report {
> > > > > > > >         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;
> > > > > > > > +       union {
> > > > > > > > +               struct joycon_subcmd_reply subcmd_reply;
> > > > > > > > +               struct joycon_imu_report imu_report;
> > > > > > >
> > > > > > > maybe add a raw byte array to this union. Could help for parsing the imu data.
> > > > > > > > +       };
> > > > > > > >  } __packed;
> > > > > > > >
> > > > > > > >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > > > > > > > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> > > > > > > >         struct joycon_stick_cal right_stick_cal_x;
> > > > > > > >         struct joycon_stick_cal right_stick_cal_y;
> > > > > > > >
> > > > > > > > +       struct joycon_imu_cal accel_cal;
> > > > > > > > +       struct joycon_imu_cal gyro_cal;
> > > > > > > > +
> > > > > > > >         /* power supply data */
> > > > > > > >         struct power_supply *battery;
> > > > > > > >         struct power_supply_desc battery_desc;
> > > > > > > > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> > > > > > > >         u16 rumble_lh_freq;
> > > > > > > >         u16 rumble_rl_freq;
> > > > > > > >         u16 rumble_rh_freq;
> > > > > > > > +
> > > > > > > > +       /* imu */
> > > > > > > > +       struct input_dev *imu_input;
> > > > > > > > +       int timestamp;
> > > > > > > >  };
> > > > > > > >
> > > > > > > >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > > > > > > > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > > > > >         }
> > > > > > > >
> > > > > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > > > -       raw_cal = &report->reply.data[5];
> > > > > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > > > >
> > > > > > > >         /* left stick calibration parsing */
> > > > > > > >         cal_x = &ctlr->left_stick_cal_x;
> > > > > > > > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > > > > >         return 0;
> > > > > > > >  }
> > > > > > > >
> > > > > > > > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > > > > > > > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > > > > > > > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > > > > > > > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > > > > > > > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > > > > > > > +{
> > > > > > > > +       struct joycon_subcmd_request *req;
> > > > > > > > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > > > > > > > +       struct joycon_input_report *report;
> > > > > > > > +       u8 *data;
> > > > > > > > +       u8 *raw_cal;
> > > > > > > > +       int ret;
> > > > > > > > +       int i;
> > > > > > > > +
> > > > > > > > +       /* request IMU calibration data */
> > > > > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > > > > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > > > > > > > +       data = req->data;
> > > > > > > > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > > > > > > > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > > > > > > > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > > > > > > > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
> > > > > > >
> > > > > > > Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> > > > > > > these calculations yourself:
> > > > > > > put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
> > > > > > >
> > > > > > > > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > > > > > > > +
> > > > > > > > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > > > > > > > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > > > > > > > +
> > > > > > > > +       if (ret) {
> > > > > > > > +               hid_warn(ctlr->hdev,
> > > > > > > > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > > > > > > > +                        ret);
> > > > > > > > +
> > > > > > > > +               for (i = 0; i < 3; i++) {
> > > > > > > > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > > > > > > > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > > > > > > > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > > > > > > > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > > > > > > > +               }
> > > > > > > > +               return ret;
> > > > > > > > +       }
> > > > > > > > +
> > > > > > > > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > > > > +
> > > > > > > > +       /* IMU calibration parsing */
> > > > > > > > +       for (i = 0; i < 3; i++) {
> > > > > > > > +               int j = i * 2;
> > > > > > > > +
> > > > > > > > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > > > > > > > +                                               ((s16)raw_cal[j + 1] << 8);
> > > > > > > > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > > > > > > > +                                               ((s16)raw_cal[j + 7] << 8);
> > > > > > > > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > > > > > > > +                                               ((s16)raw_cal[j + 13] << 8);
> > > > > > > > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > > > > > > > +                                               ((s16)raw_cal[j + 19] << 8);
> > > > > > >
> > > > > > > get_unaligned_le16 instead of doing your own bitshifts?
> > > > > > > > +       }
> > > > > > > > +
> > > > > > > > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > > > > > > > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > > > > > > > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > > > > > > > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > > > > > > > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > > > > > > > +                           ctlr->accel_cal.offset[0],
> > > > > > > > +                           ctlr->accel_cal.offset[1],
> > > > > > > > +                           ctlr->accel_cal.offset[2],
> > > > > > > > +                           ctlr->accel_cal.scale[0],
> > > > > > > > +                           ctlr->accel_cal.scale[1],
> > > > > > > > +                           ctlr->accel_cal.scale[2],
> > > > > > > > +                           ctlr->gyro_cal.offset[0],
> > > > > > > > +                           ctlr->gyro_cal.offset[1],
> > > > > > > > +                           ctlr->gyro_cal.offset[2],
> > > > > > > > +                           ctlr->gyro_cal.scale[0],
> > > > > > > > +                           ctlr->gyro_cal.scale[1],
> > > > > > > > +                           ctlr->gyro_cal.scale[2]);
> > > > > > > > +
> > > > > > > > +       return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> > > > > > > >  {
> > > > > > > >         struct joycon_subcmd_request *req;
> > > > > > > > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> > > > > > > >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> > > > > > > >  }
> > > > > > > >
> > > > > > > > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > > > > > > > +{
> > > > > > > > +       struct joycon_subcmd_request *req;
> > > > > > > > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > > > > > > > +
> > > > > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > > > > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > > > > > > > +       req->data[0] = enable ? 0x01 : 0x00;
> > > > > > > > +
> > > > > > > > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > > > > > > > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> > > > > > > >  {
> > > > > > > >         s32 center = cal->center;
> > > > > > > > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> > > > > > > >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> > > > > > > >                 wake_up(&ctlr->wait);
> > > > > > > >         }
> > > > > > > > +
> > > > > > > > +       /* parse IMU data if present */
> > > > > > > > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > > > > > > > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
> > > > > > >
> > > > > > > I don't think you can directly covert your input report data to
> > > > > > > imu_data. Until now you have been lucky enough (based on a quick
> > > > > > > glance of the the other patches) that your data are single bytes.
> > > > > > > However, this data seems to be a bunch of s16's. In other words you
> > > > > > > have to deal with endianess issues. You need to use get_unaligned_le16
> > > > > > > to retrieve data from your raw byte buffer. See other HID drivers for
> > > > > > > reference.
> > > > > >
> > > > > > Ah, good point. I'd overlooked that entirely.
> > > > > >
> > > > > > >
> > > > > > > Since you will have to use get_unaligned_le16, it probably won't make
> > > > > > > much sense to really have a joycon_imu_data structure. Maybe extend
> > > > > > > your input_buffer union with raw bytes and in this case just use raw
> > > > > > > bytes. Each of your loop iterations below just grab the values from
> > > > > > > the buffer and store them in a variable.
> > > > > > >
> > > > > > > > +               struct input_dev *idev = ctlr->imu_input;
> > > > > > > > +               int i;
> > > > > > > > +               int value[6];
> > > > > > > > +
> > > > > > > > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > > > > > > > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > > > > > > > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > > > > > > > +                                   ctlr->timestamp);
> > > > > > >
> > > > > > > How sure are you that this is always 5ms? Is there a hardware
> > > > > > > timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> > > > > > > (Bluetooth is lossy) and not all packets even make it.
> > > > > > >
> > > > > >
> > > > > > It appears that the closest thing to a hardware timestamp available is a
> > > > > > quickly incrementing 1-byte timer sent with every report. It's only really
> > > > > > useful for latency estimation. Each input report includes 3 IMU samples
> > > > > > which are 5ms apart for the joy-cons. It's recently come to my attention
> > > > > > that the samples may be spaced differently for the pro controller, so I'm
> > > > > > going to need to dive into this more anyway. I'm not sure what a great
> > > > > > way would be to handle lost reports.
> > > > > >
> > > > > > > > +
> > > > > > > > +                       /*
> > > > > > > > +                        * Rather than convert to floats, we adjust by
> > > > > > > > +                        * calibration factors and then multiply by the default
> > > > > > > > +                        * scaling values. This way, the consumer only needs to
> > > > > > > > +                        * divide by the default scale values.
> > > > > > > > +                        */
> > > > > > > > +                       value[0] = (imu_data[i].gyro_x -
> > > > > > > > +                                   ctlr->gyro_cal.offset[0]) *
> > > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > > > > > > > +                       value[1] = (imu_data[i].gyro_y -
> > > > > > > > +                                   ctlr->gyro_cal.offset[1]) *
> > > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > > > > > > > +                       value[2] = (imu_data[i].gyro_z -
> > > > > > > > +                                   ctlr->gyro_cal.offset[2]) *
> > > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > > > > > > > +
> > > > > > > > +                       value[3] = (imu_data[i].accel_x -
> > > > > > > > +                                   ctlr->accel_cal.offset[0]) *
> > > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > > > > > > > +                       value[4] = (imu_data[i].accel_y -
> > > > > > > > +                                   ctlr->accel_cal.offset[1]) *
> > > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > > > > > > > +                       value[5] = (imu_data[i].accel_z -
> > > > > > > > +                                   ctlr->accel_cal.offset[2]) *
> > > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
> > > > > > >
> > > > > > > Just in case I would double check the precision of your calculations.
> > > > > > > For DS4 we had similar calculations, but we had a small loss of
> > > > > > > precision, which ultimately caused major calculation errors.
> > > > > > > (Applications often use accelerometer + gyro data to calculate an
> > > > > > > absolute position. This involves  integration.. and small errors
> > > > > > > become big). We had to use the "mult_frac" macro to restore some of
> > > > > > > the precision during the calculations. It might be something to double
> > > > > > > check.
> > > > > > >
> > > > > >
> > > > > > That's good to know. I'll look more closely at how it's implemented in
> > > > > > hid-sony.
> > > > > >
> > > > > > > In addition what orientation are you using for the axes? I need to
> > > > > > > double check the DS4 datasheets, but I think we were using a "right
> > > > > > > handed" coordinate system. (So if you make a fist of your right hand
> > > > > > > with thumb up, the gyro curls around it counter clockwise along the
> > > > > > > direction of your fingers).
> > > > > > >
> > > > > >
> > > > > > The orientation of the axes (and much more) are documented here:
> > > > > > https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
> > > > > > Since the the right vs. left joy-cons have different axes orientations, I
> > > > > > assume it's preferable to standardize it all in software to match the
> > > > > > orientation of the DS4.
> > > > > >
> > > > > > >
> > > > > > > > +
> > > > > > > > +                       input_report_abs(idev, ABS_RX, value[0]);
> > > > > > > > +                       input_report_abs(idev, ABS_RY, value[1]);
> > > > > > > > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > > > > > > > +                       input_report_abs(idev, ABS_X, value[3]);
> > > > > > > > +                       input_report_abs(idev, ABS_Y, value[4]);
> > > > > > > > +                       input_report_abs(idev, ABS_Z, value[5]);
> > > > > > > > +                       input_sync(idev);
> > > > > > > > +               }
> > > > > > > > +       }
> > > > > > > >  }
> > > > > > > >
> > > > > > > >  static void joycon_rumble_worker(struct work_struct *work)
> > > > > > > > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > > >  {
> > > > > > > >         struct hid_device *hdev;
> > > > > > > >         const char *name;
> > > > > > > > +       const char *imu_name;
> > > > > > > >         int ret;
> > > > > > > >         int i;
> > > > > > > >
> > > > > > > > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > > >         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_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");
> > > > > > > > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > > >         if (ret)
> > > > > > > >                 return ret;
> > > > > > > >
> > > > > > > > +       /* configure the imu input device */
> > > > > > > > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > > > > > > > +       if (!ctlr->imu_input)
> > > > > > > > +               return -ENOMEM;
> > > > > > > > +
> > > > > > > > +       ctlr->imu_input->id.bustype = hdev->bus;
> > > > > > > > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > > > > > > > +       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;
> > > > > > > > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > > > > > > > +
> > > > > > > > +       /* configure imu axes */
> > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > > > > > > > +
> > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > > +
> > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > > > > > > > +
> > > > > > > > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > > > > > > > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > > > > > > > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > > > > > > > +
> > > > > > > > +       ret = input_register_device(ctlr->imu_input);
> > > > > > > > +       if (ret)
> > > > > > > > +               return ret;
> > > > > > > > +
> > > > > > > >         return 0;
> > > > > > > >  }
> > > > > > > >
> > > > > > > > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> > > > > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > > >
> > > > > > > >         for (i = 4, j = 0; j < 6; i++, j++)
> > > > > > > > -               ctlr->mac_addr[j] = report->reply.data[i];
> > > > > > > > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> > > > > > > >
> > > > > > > >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> > > > > > > >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > > > > > > > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> > > > > > > >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> > > > > > > >                                 break;
> > > > > > > >                         report = (struct joycon_input_report *)data;
> > > > > > > > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > > > > > > > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> > > > > > > >                                 match = true;
> > > > > > > >                         break;
> > > > > > > >                 default:
> > > > > > > > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > > > > >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> > > > > > > >         }
> > > > > > > >
> > > > > > > > +       /* get IMU calibration data, and parse it */
> > > > > > > > +       ret = joycon_request_imu_calibration(ctlr);
> > > > > > > > +       if (ret) {
> > > > > > > > +               /*
> > > > > > > > +                * We can function with default calibration, but it may be
> > > > > > > > +                * inaccurate. Provide a warning, and continue on.
> > > > > > > > +                */
> > > > > > > > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > > > > > > > +       }
> > > > > > > > +
> > > > > > > >         /* Set the reporting mode to 0x30, which is the full report mode */
> > > > > > > >         ret = joycon_set_report_mode(ctlr);
> > > > > > > >         if (ret) {
> > > > > > > > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > > > > >                 goto err_mutex;
> > > > > > > >         }
> > > > > > > >
> > > > > > > > +       /* Enable the IMU */
> > > > > > > > +       ret = joycon_enable_imu(ctlr, true);
> > > > > > > > +       if (ret) {
> > > > > > > > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > > > > > > > +               goto err_mutex;
> > > > > > > > +       }
> > > > > > > > +
> > > > > > > >         ret = joycon_read_mac(ctlr);
> > > > > > > >         if (ret) {
> > > > > > > >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > > > > > > > --
> > > > > > > > 2.24.1
> > > > > > > >
> > >
> > >
> > >
> > > --
> > > Daniel Ogorchock



-- 
Daniel Ogorchock

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-18  3:49                   ` Daniel Ogorchock
@ 2020-01-21 18:19                     ` Siarhei Vishniakou
  0 siblings, 0 replies; 25+ messages in thread
From: Siarhei Vishniakou @ 2020-01-21 18:19 UTC (permalink / raw)
  To: Daniel Ogorchock
  Cc: Carl Mueller, Roderick Colenbrander, linux-input, Billy Laws,
	Benjamin Tissoires, Jiri Kosina, Colenbrander, Roelof, s.jegen

Hi Daniel,

I wonder if it would make sense to fold some of the patches into the
first patch?

As an example, the first patch essentially adds the basic joystick
functionality, and at the same time it uses subcommands to communicate
with the device, for things like retrieving the calibration data.
Somewhere later in the patch series, there is a patch that improves
subcommand reliability. Maybe the 'improve subcommands' patch can be
combined with the first patch?

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v10 11/12] HID: nintendo: add IMU support
  2020-01-21  2:49                 ` Daniel Ogorchock
@ 2020-01-22  5:19                   ` Roderick Colenbrander
  0 siblings, 0 replies; 25+ messages in thread
From: Roderick Colenbrander @ 2020-01-22  5:19 UTC (permalink / raw)
  To: Daniel Ogorchock
  Cc: Carl Mueller, linux-input, Billy Laws, Benjamin Tissoires,
	Jiri Kosina, Colenbrander, Roelof, Siarhei Vishniakou, s.jegen

Hi Daniel,

I tried a Switch Pro controller as well. It indeed is a real HAT, so I
think it should be HAT axes. The joycons are really buttons, so use
the button d-pad codes there.

Thanks,
Roderick

On Mon, Jan 20, 2020 at 6:49 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
>
> Does anyone else have an opinion on this? I agree with Carl that the
> D-pad cannot register opposite direction inputs simultaneously.
>
> Thanks,
> Daniel
>
> On Thu, Jan 9, 2020 at 2:39 PM Carl Mueller <carmueller@gmail.com> wrote:
> >
> > Hi Roderick,
> >
> > The Switch Pro Controller D-pad functions as a D-pad.  You cannot push
> > left and right at the same time.
> >
> > Also, the Switch Pro Controller already functions as a controller when
> > connected with Bluetooth and
> > using the standard Linux device driver.  In this case, the D-pad is
> > registered as a Hat.
> > I'd prefer if we didn't change this.
> >
> >
> > On Thu, Jan 9, 2020 at 11:38 AM Roderick Colenbrander
> > <thunderbird2k@gmail.com> wrote:
> > >
> > > Hi Daniel,
> > >
> > > Whether to report as HAT or BTN_DPAD depends on how the hardware works
> > > internally. You see the HAT vs buton at the HID report layer (it is
> > > quite different), but if you just touch the controller sticks you will
> > > also notice. A HAT axis is really like a joystick e.g. with a range -1
> > > to +1. If you hold down e.g. "left" and "right" on a d-pad, you would
> > > get "0" back as you are unable to press both left and right at the
> > > same time. With d-pad buttons you could press left and right at the
> > > same time (depending on design).
> > >
> > > When I just tried my Switch Pro controller it really felt like the
> > > d-pad were buttons. Various other controllers using BTN_DPAD as well
> > > for example DualShock 3. Most applications these days should use SDL2,
> > > which will hide that.
> > >
> > > Thanks,
> > > Roderick
> > >
> > > On Thu, Jan 9, 2020 at 12:53 AM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> > > >
> > > > Hi Carl,
> > > >
> > > > When I was initially implementing the button mappings, I referred to the
> > > > standard described here:
> > > >     https://www.kernel.org/doc/html/latest/input/gamepad.html
> > > >
> > > > It mentions under the d-pad section that digital inputs should be
> > > > reported using the BTN_DPAD_* variant. Do the other controllers
> > > > mentioned report their d-pads as analog values, or has using the
> > > > ABS_HAT* values become the de facto format for digital inputs
> > > > as well?
> > > >
> > > > If the latter, I guess it would make sense to go with the crowd for the pro.
> > > > It just seems a bit odd to report digital inputs as absolute axes, but I'm
> > > > open to changing it if that's the consensus.
> > > >
> > > > Thanks,
> > > > Daniel
> > > >
> > > > On Thu, Jan 9, 2020 at 12:23 AM Carl Mueller <carmueller@gmail.com> wrote:
> > > > >
> > > > > (3rd/final attempt at reply due to list server rejecting message with
> > > > > HTML subpart; sorry about the spam!)
> > > > >
> > > > > Hi Everyone,
> > > > >
> > > > > In trying out this driver to use with the Switch Pro Controller,
> > > > > the biggest difficulty I had was that the D-pad is reported as buttons
> > > > > instead of as a Hat.  All other controllers that are similar in structure
> > > > > (such as the PS3 and PS4 controllers, the Xbox controllers, and many
> > > > > others) report the D-pad as a Hat.  This kind of consistency is needed
> > > > > to avoid lots of software compatibility issues.
> > > > >
> > > > > I mentioned this to Daniel, and he indicated that he wanted the Switch
> > > > > Pro Controller to behave like the JoyCons, which have buttons instead
> > > > > of a D-pad.  Having the JoyCons report the buttons as buttons makes
> > > > > sense, since it's possible to push opposite directions at the same time.
> > > > > However, I don't think that the argument carries over to the Switch Pro
> > > > > Controller, since it has a physically different control.
> > > > >
> > > > > Again, the consistency with the other controllers that have all the same
> > > > > physical structure and the same types of controls seems more important
> > > > > to me than consistency with a controller that is physically much different.
> > > > > Additionally, many games are already written for use with controllers
> > > > > that look like the Switch Pro Controller, whereas fewer are written for use
> > > > > with Nintendo JoyCons.  If we have to cause trouble for one side or the
> > > > > other, let's not cause trouble with the more established side.
> > > > >
> > > > > On Thu, Jan 9, 2020 at 12:22 AM Roderick Colenbrander
> > > > > <thunderbird2k@gmail.com> wrote:
> > > > > >
> > > > > > Hi Daniel,
> > > > > >
> > > > > > Unfortunately there is no public test application at the moment to
> > > > > > illustrate these features. I agree something is definitely needed.
> > > > > >
> > > > > > I need to see if we can come up with something. One of the test apps
> > > > > > we have internally is a 3D cube, which is controllable by controller
> > > > > > motion. It of course shows the gyro / accelerometer values, but the
> > > > > > position of the cube is tied to the calculated orientation. If
> > > > > > something is off you will see weird movements in the cube or drift
> > > > > > building up over time etcetera.
> > > > > >
> > > > > > Though it would take some time to prepare something. The rest of the
> > > > > > patch series looked fine I think, so the IMU parts may need to wait
> > > > > > for a next kernel cycle, but all the other plumbing could go in (if
> > > > > > others are okay of course).
> > > > > >
> > > > > > Thanks,
> > > > > > Roderick
> > > > > >
> > > > > > On Wed, Jan 8, 2020 at 7:26 PM Daniel Ogorchock <djogorchock@gmail.com> wrote:
> > > > > > >
> > > > > > > Hi Roderick,
> > > > > > >
> > > > > > > Thanks for the feedback. In addition to the inline comments below,
> > > > > > > do you have any recommendations for test programs that you
> > > > > > > know work well with hid-sony's gyro implementation? Up to this
> > > > > > > point I've just been using evtest, which obviously isn't too useful
> > > > > > > for testing actual functionality of the gyro in an intuitive way.
> > > > > > >
> > > > > > > On Tue, Dec 31, 2019 at 12:29 AM Roderick Colenbrander
> > > > > > > <thunderbird2k@gmail.com> wrote:
> > > > > > > >
> > > > > > > > Hi Daniel,
> > > > > > > >
> > > > > > > > I had some time to review the motion sensors patch. I have added some
> > > > > > > > feedback inline with your patch below.
> > > > > > > >
> > > > > > > > Aside from standard feedback on the code, I wanted to have a close
> > > > > > > > look at the accelerometer / gyro data. My team has been doing a lot of
> > > > > > > > work on this (and still is) and I want to make sure any work we do on
> > > > > > > > "user space" software (e.g. Android) automatically works for Joycon as
> > > > > > > > well. The accuracy of the data is very important else applications
> > > > > > > > will make bad decisions. Userspace applications often combine the data
> > > > > > > > of both sensors to calculate a position for position tracking.
> > > > > > > > Inaccurate axes ranges or wrong precision can cause major issues. I
> > > > > > > > may be a bit strict below, but it will just be to prevent headaches
> > > > > > > > for others later on. I use the DS4 as a reference point as it was the
> > > > > > > > first device (aside from Wii early on) where we properly report
> > > > > > > > acceleration and gyro axes with INPUT_PROP_ACCELEROMETER and we should
> > > > > > > > be consistent with it here else applications could be confused, so we
> > > > > > > > should use similar orientation of axes and resolutions.
> > > > > > > >
> > > > > > > > Thanks,
> > > > > > > > Roderick
> > > > > > > >
> > > > > > > > On Sun, Dec 29, 2019 at 5:27 PM Daniel J. Ogorchock
> > > > > > > > <djogorchock@gmail.com> wrote:
> > > > > > > > >
> > > > > > > > > This patch adds support for the controller's IMU. The accelerometer and
> > > > > > > > > gyro data are both provided to userspace using a second input device.
> > > > > > > > > The devices can be associated using their uniq value (set to the
> > > > > > > > > controller's MAC address).
> > > > > > > > >
> > > > > > > > > The majority of this patch's functinoality was provided by Carl Mueller.
> > > > > > > > >
> > > > > > > > > Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
> > > > > > > > > ---
> > > > > > > > >  drivers/hid/hid-nintendo.c | 267 +++++++++++++++++++++++++++++++++++--
> > > > > > > > >  1 file changed, 259 insertions(+), 8 deletions(-)
> > > > > > > > >
> > > > > > > > > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > > > > > > > > index 7b7a0cf3fe0b..edf2ef140cb3 100644
> > > > > > > > > --- a/drivers/hid/hid-nintendo.c
> > > > > > > > > +++ b/drivers/hid/hid-nintendo.c
> > > > > > > > > @@ -101,12 +101,29 @@ 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)
> > > > > > > > >
> > > > > > > > > +/* SPI storage addresses of IMU factory calibration data */
> > > > > > > > > +static const u16 JC_IMU_CAL_DATA_START         = 0x6020;
> > > > > > > > > +static const u16 JC_IMU_CAL_DATA_END           = 0x6037;
> > > > > > > > > +#define JC_IMU_CAL_DATA_SIZE \
> > > > > > > > > +                       (JC_IMU_CAL_DATA_END - JC_IMU_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;
> > > > > > > > >
> > > > > > > > > +/* The accel axes will be mapped in terms of this magnitude */
> > > > > > > > > +static const u16 JC_MAX_ACCEL_MAG              = 32767;
> > > > > > > > > +static const u16 JC_ACCEL_RES                  = 4096;
> > > > > > > >
> > > > > > > > What does the acceleration resolution equate to? For DS4 we use
> > > > > > > > multiples of 'g'. (We don't know the position on earth, so can't
> > > > > > > > report in m/s^2).
> > > > > > > >
> > > > > > > > > +static const u16 JC_ACCEL_FUZZ                 = 10;
> > > > > > > > > +static const u16 JC_ACCEL_FLAT                 /*= 0*/;
> > > > > > > > > +
> > > > > > > > > +/* The gyro axes will be mapped in terms of this magnitude */
> > > > > > > > > +static const u16 JC_MAX_GYRO_MAG               = 32767;
> > > > > > > > > +static const u16 JC_GYRO_RES                   = 13371 / 936; /* 14 (14.285) */
> > > > > > > >
> > > > > > > > What does the gyro resolution equate to? For DS4 we report "degrees
> > > > > > > > per second". We should do the same for the joycons, but I don't know
> > > > > > > > how you guys figured out the exact resolution. As I mentioned if it is
> > > > > > > > not accurate, applications will make wrong calculations.
> > > > > > > >
> > > > > > > > > +static const u16 JC_GYRO_FUZZ                  = 10;
> > > > > > > > > +static const u16 JC_GYRO_FLAT                  /*= 0*/;
> > > > > > > > > +
> > > > > > > > >  /* frequency/amplitude tables for rumble */
> > > > > > > > >  struct joycon_rumble_freq_data {
> > > > > > > > >         u16 high;
> > > > > > > > > @@ -234,6 +251,11 @@ struct joycon_stick_cal {
> > > > > > > > >         s32 center;
> > > > > > > > >  };
> > > > > > > > >
> > > > > > > > > +struct joycon_imu_cal {
> > > > > > > > > +       s16 offset[3];
> > > > > > > > > +       s16 scale[3];
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > > >  /*
> > > > > > > > >   * All the controller's button values are stored in a u32.
> > > > > > > > >   * They can be accessed with bitwise ANDs.
> > > > > > > > > @@ -281,6 +303,19 @@ struct joycon_subcmd_reply {
> > > > > > > > >         u8 data[0]; /* will be at most 35 bytes */
> > > > > > > > >  } __packed;
> > > > > > > > >
> > > > > > > > > +struct joycon_imu_data {
> > > > > > > > > +       s16 accel_x;
> > > > > > > > > +       s16 accel_y;
> > > > > > > > > +       s16 accel_z;
> > > > > > > > > +       s16 gyro_x;
> > > > > > > > > +       s16 gyro_y;
> > > > > > > > > +       s16 gyro_z;
> > > > > > > > > +} __packed;
> > > > > > > > > +
> > > > > > > > > +struct joycon_imu_report {
> > > > > > > > > +       struct joycon_imu_data data[3];
> > > > > > > > > +} __packed;
> > > > > > > >
> > > > > > > > See comments later on about imu_data, but you can't directly cast your
> > > > > > > > data buffer to this data type due to endian issues. It may not really
> > > > > > > > make sense to keep the data structure as you need to do manual data
> > > > > > > > fetching.
> > > > > > > >
> > > > > > > > > +
> > > > > > > > >  struct joycon_input_report {
> > > > > > > > >         u8 id;
> > > > > > > > >         u8 timer;
> > > > > > > > > @@ -290,11 +325,10 @@ struct joycon_input_report {
> > > > > > > > >         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;
> > > > > > > > > +       union {
> > > > > > > > > +               struct joycon_subcmd_reply subcmd_reply;
> > > > > > > > > +               struct joycon_imu_report imu_report;
> > > > > > > >
> > > > > > > > maybe add a raw byte array to this union. Could help for parsing the imu data.
> > > > > > > > > +       };
> > > > > > > > >  } __packed;
> > > > > > > > >
> > > > > > > > >  #define JC_MAX_RESP_SIZE       (sizeof(struct joycon_input_report) + 35)
> > > > > > > > > @@ -334,6 +368,9 @@ struct joycon_ctlr {
> > > > > > > > >         struct joycon_stick_cal right_stick_cal_x;
> > > > > > > > >         struct joycon_stick_cal right_stick_cal_y;
> > > > > > > > >
> > > > > > > > > +       struct joycon_imu_cal accel_cal;
> > > > > > > > > +       struct joycon_imu_cal gyro_cal;
> > > > > > > > > +
> > > > > > > > >         /* power supply data */
> > > > > > > > >         struct power_supply *battery;
> > > > > > > > >         struct power_supply_desc battery_desc;
> > > > > > > > > @@ -352,6 +389,10 @@ struct joycon_ctlr {
> > > > > > > > >         u16 rumble_lh_freq;
> > > > > > > > >         u16 rumble_rl_freq;
> > > > > > > > >         u16 rumble_rh_freq;
> > > > > > > > > +
> > > > > > > > > +       /* imu */
> > > > > > > > > +       struct input_dev *imu_input;
> > > > > > > > > +       int timestamp;
> > > > > > > > >  };
> > > > > > > > >
> > > > > > > > >  static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
> > > > > > > > > @@ -547,7 +588,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > > > > > >         }
> > > > > > > > >
> > > > > > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > > > > -       raw_cal = &report->reply.data[5];
> > > > > > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > > > > >
> > > > > > > > >         /* left stick calibration parsing */
> > > > > > > > >         cal_x = &ctlr->left_stick_cal_x;
> > > > > > > > > @@ -601,6 +642,85 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
> > > > > > > > >         return 0;
> > > > > > > > >  }
> > > > > > > > >
> > > > > > > > > +static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
> > > > > > > > > +static const s16 DFLT_ACCEL_SCALE = 16384;
> > > > > > > > > +static const s16 DFLT_GYRO_OFFSET /*= 0*/;
> > > > > > > > > +static const s16 DFLT_GYRO_SCALE  = 13371;
> > > > > > > > > +static int joycon_request_imu_calibration(struct joycon_ctlr *ctlr)
> > > > > > > > > +{
> > > > > > > > > +       struct joycon_subcmd_request *req;
> > > > > > > > > +       u8 buffer[sizeof(*req) + 5] = { 0 };
> > > > > > > > > +       struct joycon_input_report *report;
> > > > > > > > > +       u8 *data;
> > > > > > > > > +       u8 *raw_cal;
> > > > > > > > > +       int ret;
> > > > > > > > > +       int i;
> > > > > > > > > +
> > > > > > > > > +       /* request IMU calibration data */
> > > > > > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > > > > > +       req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ;
> > > > > > > > > +       data = req->data;
> > > > > > > > > +       data[0] = 0xFF & JC_IMU_CAL_DATA_START;
> > > > > > > > > +       data[1] = 0xFF & (JC_IMU_CAL_DATA_START >> 8);
> > > > > > > > > +       data[2] = 0xFF & (JC_IMU_CAL_DATA_START >> 16);
> > > > > > > > > +       data[3] = 0xFF & (JC_IMU_CAL_DATA_START >> 24);
> > > > > > > >
> > > > > > > > Maybe use put_unaligned_le32? I think it allows you to avoid doing all
> > > > > > > > these calculations yourself:
> > > > > > > > put_unaligned_le32(JC_IMU_CAL_DATA_START, data);
> > > > > > > >
> > > > > > > > > +       data[4] = JC_IMU_CAL_DATA_SIZE;
> > > > > > > > > +
> > > > > > > > > +       hid_dbg(ctlr->hdev, "requesting IMU cal data\n");
> > > > > > > > > +       ret = joycon_send_subcmd(ctlr, req, 5, HZ);
> > > > > > > > > +
> > > > > > > > > +       if (ret) {
> > > > > > > > > +               hid_warn(ctlr->hdev,
> > > > > > > > > +                        "Failed to read IMU cal, using defaults; ret=%d\n",
> > > > > > > > > +                        ret);
> > > > > > > > > +
> > > > > > > > > +               for (i = 0; i < 3; i++) {
> > > > > > > > > +                       ctlr->accel_cal.offset[i] = DFLT_ACCEL_OFFSET;
> > > > > > > > > +                       ctlr->accel_cal.scale[i] = DFLT_ACCEL_SCALE;
> > > > > > > > > +                       ctlr->gyro_cal.offset[i] = DFLT_GYRO_OFFSET;
> > > > > > > > > +                       ctlr->gyro_cal.scale[i] = DFLT_GYRO_SCALE;
> > > > > > > > > +               }
> > > > > > > > > +               return ret;
> > > > > > > > > +       }
> > > > > > > > > +
> > > > > > > > > +       report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > > > > +       raw_cal = &report->subcmd_reply.data[5];
> > > > > > > > > +
> > > > > > > > > +       /* IMU calibration parsing */
> > > > > > > > > +       for (i = 0; i < 3; i++) {
> > > > > > > > > +               int j = i * 2;
> > > > > > > > > +
> > > > > > > > > +               ctlr->accel_cal.offset[i] = raw_cal[j + 0] |
> > > > > > > > > +                                               ((s16)raw_cal[j + 1] << 8);
> > > > > > > > > +               ctlr->accel_cal.scale[i] = raw_cal[j + 6] |
> > > > > > > > > +                                               ((s16)raw_cal[j + 7] << 8);
> > > > > > > > > +               ctlr->gyro_cal.offset[i] = raw_cal[j + 12] |
> > > > > > > > > +                                               ((s16)raw_cal[j + 13] << 8);
> > > > > > > > > +               ctlr->gyro_cal.scale[i] = raw_cal[j + 18] |
> > > > > > > > > +                                               ((s16)raw_cal[j + 19] << 8);
> > > > > > > >
> > > > > > > > get_unaligned_le16 instead of doing your own bitshifts?
> > > > > > > > > +       }
> > > > > > > > > +
> > > > > > > > > +       hid_dbg(ctlr->hdev, "IMU calibration:\n"
> > > > > > > > > +                           "a_o[0]=%d a_o[1]=%d a_o[2]=%d\n"
> > > > > > > > > +                           "a_s[0]=%d a_s[1]=%d a_s[2]=%d\n"
> > > > > > > > > +                           "g_o[0]=%d g_o[1]=%d g_o[2]=%d\n"
> > > > > > > > > +                           "g_s[0]=%d g_s[1]=%d g_s[2]=%d\n",
> > > > > > > > > +                           ctlr->accel_cal.offset[0],
> > > > > > > > > +                           ctlr->accel_cal.offset[1],
> > > > > > > > > +                           ctlr->accel_cal.offset[2],
> > > > > > > > > +                           ctlr->accel_cal.scale[0],
> > > > > > > > > +                           ctlr->accel_cal.scale[1],
> > > > > > > > > +                           ctlr->accel_cal.scale[2],
> > > > > > > > > +                           ctlr->gyro_cal.offset[0],
> > > > > > > > > +                           ctlr->gyro_cal.offset[1],
> > > > > > > > > +                           ctlr->gyro_cal.offset[2],
> > > > > > > > > +                           ctlr->gyro_cal.scale[0],
> > > > > > > > > +                           ctlr->gyro_cal.scale[1],
> > > > > > > > > +                           ctlr->gyro_cal.scale[2]);
> > > > > > > > > +
> > > > > > > > > +       return 0;
> > > > > > > > > +}
> > > > > > > > > +
> > > > > > > > >  static int joycon_set_report_mode(struct joycon_ctlr *ctlr)
> > > > > > > > >  {
> > > > > > > > >         struct joycon_subcmd_request *req;
> > > > > > > > > @@ -627,6 +747,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable)
> > > > > > > > >         return joycon_send_subcmd(ctlr, req, 1, HZ/4);
> > > > > > > > >  }
> > > > > > > > >
> > > > > > > > > +static int joycon_enable_imu(struct joycon_ctlr *ctlr, bool enable)
> > > > > > > > > +{
> > > > > > > > > +       struct joycon_subcmd_request *req;
> > > > > > > > > +       u8 buffer[sizeof(*req) + 1] = { 0 };
> > > > > > > > > +
> > > > > > > > > +       req = (struct joycon_subcmd_request *)buffer;
> > > > > > > > > +       req->subcmd_id = JC_SUBCMD_ENABLE_IMU;
> > > > > > > > > +       req->data[0] = enable ? 0x01 : 0x00;
> > > > > > > > > +
> > > > > > > > > +       hid_dbg(ctlr->hdev, "%s IMU\n", enable ? "enabling" : "disabling");
> > > > > > > > > +       return joycon_send_subcmd(ctlr, req, 1, HZ);
> > > > > > > > > +}
> > > > > > > > > +
> > > > > > > > >  static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val)
> > > > > > > > >  {
> > > > > > > > >         s32 center = cal->center;
> > > > > > > > > @@ -771,6 +904,54 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> > > > > > > > >                 spin_unlock_irqrestore(&ctlr->lock, flags);
> > > > > > > > >                 wake_up(&ctlr->wait);
> > > > > > > > >         }
> > > > > > > > > +
> > > > > > > > > +       /* parse IMU data if present */
> > > > > > > > > +       if (rep->id == JC_INPUT_IMU_DATA) {
> > > > > > > > > +               struct joycon_imu_data *imu_data = rep->imu_report.data;
> > > > > > > >
> > > > > > > > I don't think you can directly covert your input report data to
> > > > > > > > imu_data. Until now you have been lucky enough (based on a quick
> > > > > > > > glance of the the other patches) that your data are single bytes.
> > > > > > > > However, this data seems to be a bunch of s16's. In other words you
> > > > > > > > have to deal with endianess issues. You need to use get_unaligned_le16
> > > > > > > > to retrieve data from your raw byte buffer. See other HID drivers for
> > > > > > > > reference.
> > > > > > >
> > > > > > > Ah, good point. I'd overlooked that entirely.
> > > > > > >
> > > > > > > >
> > > > > > > > Since you will have to use get_unaligned_le16, it probably won't make
> > > > > > > > much sense to really have a joycon_imu_data structure. Maybe extend
> > > > > > > > your input_buffer union with raw bytes and in this case just use raw
> > > > > > > > bytes. Each of your loop iterations below just grab the values from
> > > > > > > > the buffer and store them in a variable.
> > > > > > > >
> > > > > > > > > +               struct input_dev *idev = ctlr->imu_input;
> > > > > > > > > +               int i;
> > > > > > > > > +               int value[6];
> > > > > > > > > +
> > > > > > > > > +               for (i = 0; i < 3; i++) { /* there are 3 reports */
> > > > > > > > > +                       ctlr->timestamp += 5000; /* each is 5 ms apart */
> > > > > > > > > +                       input_event(idev, EV_MSC, MSC_TIMESTAMP,
> > > > > > > > > +                                   ctlr->timestamp);
> > > > > > > >
> > > > > > > > How sure are you that this is always 5ms? Is there a hardware
> > > > > > > > timestamp somewhere? If I look at our DS4 the timing isn't guaranteed
> > > > > > > > (Bluetooth is lossy) and not all packets even make it.
> > > > > > > >
> > > > > > >
> > > > > > > It appears that the closest thing to a hardware timestamp available is a
> > > > > > > quickly incrementing 1-byte timer sent with every report. It's only really
> > > > > > > useful for latency estimation. Each input report includes 3 IMU samples
> > > > > > > which are 5ms apart for the joy-cons. It's recently come to my attention
> > > > > > > that the samples may be spaced differently for the pro controller, so I'm
> > > > > > > going to need to dive into this more anyway. I'm not sure what a great
> > > > > > > way would be to handle lost reports.
> > > > > > >
> > > > > > > > > +
> > > > > > > > > +                       /*
> > > > > > > > > +                        * Rather than convert to floats, we adjust by
> > > > > > > > > +                        * calibration factors and then multiply by the default
> > > > > > > > > +                        * scaling values. This way, the consumer only needs to
> > > > > > > > > +                        * divide by the default scale values.
> > > > > > > > > +                        */
> > > > > > > > > +                       value[0] = (imu_data[i].gyro_x -
> > > > > > > > > +                                   ctlr->gyro_cal.offset[0]) *
> > > > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[0];
> > > > > > > > > +                       value[1] = (imu_data[i].gyro_y -
> > > > > > > > > +                                   ctlr->gyro_cal.offset[1]) *
> > > > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[1];
> > > > > > > > > +                       value[2] = (imu_data[i].gyro_z -
> > > > > > > > > +                                   ctlr->gyro_cal.offset[2]) *
> > > > > > > > > +                                   DFLT_GYRO_SCALE / ctlr->gyro_cal.scale[2];
> > > > > > > > > +
> > > > > > > > > +                       value[3] = (imu_data[i].accel_x -
> > > > > > > > > +                                   ctlr->accel_cal.offset[0]) *
> > > > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[0];
> > > > > > > > > +                       value[4] = (imu_data[i].accel_y -
> > > > > > > > > +                                   ctlr->accel_cal.offset[1]) *
> > > > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[1];
> > > > > > > > > +                       value[5] = (imu_data[i].accel_z -
> > > > > > > > > +                                   ctlr->accel_cal.offset[2]) *
> > > > > > > > > +                                   DFLT_ACCEL_SCALE / ctlr->accel_cal.scale[2];
> > > > > > > >
> > > > > > > > Just in case I would double check the precision of your calculations.
> > > > > > > > For DS4 we had similar calculations, but we had a small loss of
> > > > > > > > precision, which ultimately caused major calculation errors.
> > > > > > > > (Applications often use accelerometer + gyro data to calculate an
> > > > > > > > absolute position. This involves  integration.. and small errors
> > > > > > > > become big). We had to use the "mult_frac" macro to restore some of
> > > > > > > > the precision during the calculations. It might be something to double
> > > > > > > > check.
> > > > > > > >
> > > > > > >
> > > > > > > That's good to know. I'll look more closely at how it's implemented in
> > > > > > > hid-sony.
> > > > > > >
> > > > > > > > In addition what orientation are you using for the axes? I need to
> > > > > > > > double check the DS4 datasheets, but I think we were using a "right
> > > > > > > > handed" coordinate system. (So if you make a fist of your right hand
> > > > > > > > with thumb up, the gyro curls around it counter clockwise along the
> > > > > > > > direction of your fingers).
> > > > > > > >
> > > > > > >
> > > > > > > The orientation of the axes (and much more) are documented here:
> > > > > > > https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
> > > > > > > Since the the right vs. left joy-cons have different axes orientations, I
> > > > > > > assume it's preferable to standardize it all in software to match the
> > > > > > > orientation of the DS4.
> > > > > > >
> > > > > > > >
> > > > > > > > > +
> > > > > > > > > +                       input_report_abs(idev, ABS_RX, value[0]);
> > > > > > > > > +                       input_report_abs(idev, ABS_RY, value[1]);
> > > > > > > > > +                       input_report_abs(idev, ABS_RZ, value[2]);
> > > > > > > > > +                       input_report_abs(idev, ABS_X, value[3]);
> > > > > > > > > +                       input_report_abs(idev, ABS_Y, value[4]);
> > > > > > > > > +                       input_report_abs(idev, ABS_Z, value[5]);
> > > > > > > > > +                       input_sync(idev);
> > > > > > > > > +               }
> > > > > > > > > +       }
> > > > > > > > >  }
> > > > > > > > >
> > > > > > > > >  static void joycon_rumble_worker(struct work_struct *work)
> > > > > > > > > @@ -950,6 +1131,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > > > >  {
> > > > > > > > >         struct hid_device *hdev;
> > > > > > > > >         const char *name;
> > > > > > > > > +       const char *imu_name;
> > > > > > > > >         int ret;
> > > > > > > > >         int i;
> > > > > > > > >
> > > > > > > > > @@ -958,12 +1140,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > > > >         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_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");
> > > > > > > > > @@ -1029,6 +1214,55 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
> > > > > > > > >         if (ret)
> > > > > > > > >                 return ret;
> > > > > > > > >
> > > > > > > > > +       /* configure the imu input device */
> > > > > > > > > +       ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
> > > > > > > > > +       if (!ctlr->imu_input)
> > > > > > > > > +               return -ENOMEM;
> > > > > > > > > +
> > > > > > > > > +       ctlr->imu_input->id.bustype = hdev->bus;
> > > > > > > > > +       ctlr->imu_input->id.vendor = hdev->vendor;
> > > > > > > > > +       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;
> > > > > > > > > +       input_set_drvdata(ctlr->imu_input, ctlr);
> > > > > > > > > +
> > > > > > > > > +       /* configure imu axes */
> > > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_X,
> > > > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Y,
> > > > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_Z,
> > > > > > > > > +                            -JC_MAX_ACCEL_MAG, JC_MAX_ACCEL_MAG,
> > > > > > > > > +                            JC_ACCEL_FUZZ, JC_ACCEL_FLAT);
> > > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_X, JC_ACCEL_RES);
> > > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Y, JC_ACCEL_RES);
> > > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_Z, JC_ACCEL_RES);
> > > > > > > > > +
> > > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RX,
> > > > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RY,
> > > > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > > > +       input_set_abs_params(ctlr->imu_input, ABS_RZ,
> > > > > > > > > +                            -JC_MAX_GYRO_MAG, JC_MAX_GYRO_MAG,
> > > > > > > > > +                            JC_GYRO_FUZZ, JC_GYRO_FLAT);
> > > > > > > > > +
> > > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RX, JC_GYRO_RES);
> > > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RY, JC_GYRO_RES);
> > > > > > > > > +       input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_GYRO_RES);
> > > > > > > > > +
> > > > > > > > > +       __set_bit(EV_MSC, ctlr->imu_input->evbit);
> > > > > > > > > +       __set_bit(MSC_TIMESTAMP, ctlr->imu_input->mscbit);
> > > > > > > > > +       __set_bit(INPUT_PROP_ACCELEROMETER, ctlr->imu_input->propbit);
> > > > > > > > > +
> > > > > > > > > +       ret = input_register_device(ctlr->imu_input);
> > > > > > > > > +       if (ret)
> > > > > > > > > +               return ret;
> > > > > > > > > +
> > > > > > > > >         return 0;
> > > > > > > > >  }
> > > > > > > > >
> > > > > > > > > @@ -1288,7 +1522,7 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr)
> > > > > > > > >         report = (struct joycon_input_report *)ctlr->input_buf;
> > > > > > > > >
> > > > > > > > >         for (i = 4, j = 0; j < 6; i++, j++)
> > > > > > > > > -               ctlr->mac_addr[j] = report->reply.data[i];
> > > > > > > > > +               ctlr->mac_addr[j] = report->subcmd_reply.data[i];
> > > > > > > > >
> > > > > > > > >         ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL,
> > > > > > > > >                                             "%02X:%02X:%02X:%02X:%02X:%02X",
> > > > > > > > > @@ -1343,7 +1577,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> > > > > > > > >                             data[0] != JC_INPUT_SUBCMD_REPLY)
> > > > > > > > >                                 break;
> > > > > > > > >                         report = (struct joycon_input_report *)data;
> > > > > > > > > -                       if (report->reply.id == ctlr->subcmd_ack_match)
> > > > > > > > > +                       if (report->subcmd_reply.id == ctlr->subcmd_ack_match)
> > > > > > > > >                                 match = true;
> > > > > > > > >                         break;
> > > > > > > > >                 default:
> > > > > > > > > @@ -1469,6 +1703,16 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > > > > > >                 hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> > > > > > > > >         }
> > > > > > > > >
> > > > > > > > > +       /* get IMU calibration data, and parse it */
> > > > > > > > > +       ret = joycon_request_imu_calibration(ctlr);
> > > > > > > > > +       if (ret) {
> > > > > > > > > +               /*
> > > > > > > > > +                * We can function with default calibration, but it may be
> > > > > > > > > +                * inaccurate. Provide a warning, and continue on.
> > > > > > > > > +                */
> > > > > > > > > +               hid_warn(hdev, "Unable to read IMU calibration data\n");
> > > > > > > > > +       }
> > > > > > > > > +
> > > > > > > > >         /* Set the reporting mode to 0x30, which is the full report mode */
> > > > > > > > >         ret = joycon_set_report_mode(ctlr);
> > > > > > > > >         if (ret) {
> > > > > > > > > @@ -1483,6 +1727,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> > > > > > > > >                 goto err_mutex;
> > > > > > > > >         }
> > > > > > > > >
> > > > > > > > > +       /* Enable the IMU */
> > > > > > > > > +       ret = joycon_enable_imu(ctlr, true);
> > > > > > > > > +       if (ret) {
> > > > > > > > > +               hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> > > > > > > > > +               goto err_mutex;
> > > > > > > > > +       }
> > > > > > > > > +
> > > > > > > > >         ret = joycon_read_mac(ctlr);
> > > > > > > > >         if (ret) {
> > > > > > > > >                 hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n",
> > > > > > > > > --
> > > > > > > > > 2.24.1
> > > > > > > > >
> > > >
> > > >
> > > >
> > > > --
> > > > Daniel Ogorchock
>
>
>
> --
> Daniel Ogorchock

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, back to index

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-30  1:27 [PATCH v10 00/12] HID:nintendo Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 01/12] HID: nintendo: add nintendo switch controller driver Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 02/12] HID: nintendo: add player led support Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 03/12] HID: nintendo: add power supply support Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 04/12] HID: nintendo: add home led support Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 05/12] HID: nintendo: add rumble support Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 06/12] HID: nintendo: improve subcommand reliability Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 07/12] HID: nintendo: send subcommands after receiving input report Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 08/12] HID: nintendo: reduce device removal subcommand errors Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 09/12] HID: nintendo: patch hw version for userspace HID mappings Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 10/12] HID: nintendo: set controller uniq to MAC Daniel J. Ogorchock
2019-12-30  1:27 ` [PATCH v10 11/12] HID: nintendo: add IMU support Daniel J. Ogorchock
2019-12-31  6:29   ` Roderick Colenbrander
2020-01-09  3:26     ` Daniel Ogorchock
2020-01-09  5:22       ` Roderick Colenbrander
2020-01-09  6:23         ` Carl Mueller
2020-01-09  8:53           ` Daniel Ogorchock
2020-01-09 16:37             ` Roderick Colenbrander
2020-01-09 20:38               ` Carl Mueller
     [not found]                 ` <CAKF84v26=X8OLPavdE52tprm=WOynUXRz2aDjz5Bvqw6rdTZQg@mail.gmail.com>
2020-01-18  3:49                   ` Daniel Ogorchock
2020-01-21 18:19                     ` Siarhei Vishniakou
2020-01-21  2:49                 ` Daniel Ogorchock
2020-01-22  5:19                   ` Roderick Colenbrander
2020-01-09  8:55         ` Daniel Ogorchock
2019-12-30  1:27 ` [PATCH v10 12/12] HID: nintendo: add support for charging grip Daniel J. Ogorchock

Linux Input Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-input/0 linux-input/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-input linux-input/ https://lore.kernel.org/linux-input \
		linux-input@vger.kernel.org
	public-inbox-index linux-input

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-input


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git