From patchwork Sat Sep 11 17:36:24 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 509306 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-20.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, MENTIONS_GIT_HOSTING, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3079FC433FE for ; Sat, 11 Sep 2021 17:37:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 11FB161026 for ; Sat, 11 Sep 2021 17:37:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230018AbhIKRiN (ORCPT ); Sat, 11 Sep 2021 13:38:13 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48506 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232635AbhIKRiG (ORCPT ); Sat, 11 Sep 2021 13:38:06 -0400 Received: from mail-qv1-xf2e.google.com (mail-qv1-xf2e.google.com [IPv6:2607:f8b0:4864:20::f2e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 85929C061756 for ; Sat, 11 Sep 2021 10:36:53 -0700 (PDT) Received: by mail-qv1-xf2e.google.com with SMTP id u4so3431029qvb.6 for ; Sat, 11 Sep 2021 10:36:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=wJHR7Jk8HhTt5SDXZPLtNJSiHehLBsQRpQpxgd0gO/Q=; b=DLw289/PtFdUqEuYFaRgc6wJ0OP7P09xj1oLiee+EdpOotgxPZugX6b59bA612p672 J3gL39pydmnyMRAXLzeR0hqwrIfzGQdXA02knSJblocL1STToemCpvGu9hTDYMPFuZ1V X3N6jo43PuAIexR0iQoIGTeAzG4Ay1AHqrc11G9yWrdOR8HFN8htMbHvnP5CDupmBS3H ltlQviy+VP1OmYCvDDCxX5D0J/Ssctdz3mx8SN/Hn0V2OIWJU5+hu+Zud7Dpl3fr13Rp X0AZj+x7Wlsg6bdiQuyzUsK7VIhsbkCE96AymQ4dBe0sKcf0tIAgV7o+D+FBIA2zvZw3 3kSA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=wJHR7Jk8HhTt5SDXZPLtNJSiHehLBsQRpQpxgd0gO/Q=; b=WPRCrTHpDh8tpOVkpRf1bXgyqamfGlbpKIg+NiEbUM6hiZS1M7/f+RmW4NTUO62DR+ dRPkjDDDl/WCpGiD1dSCV6mwUJxw5F3lCs1XuUtT+XLbjVPAunvI2FIW7KnSLh/RWzkn Bl+B+fmXSZ9bSD0ee1hXHwt5HGV99iY2XMesHTHfwQxkHnXMCcmNYuTVYzFUOk9XnVDS 3TQ2DGTMXIjw463naLi3rjaLYjTjaxIzxTrbyYsqWSF1CJq0/tTP+6FR8kYJAkvaY71f vbT2sjbQVf1OYxF+hRSH8k7oBG/bPZr9ODZY6tB5RW9sVlmT2dGNHjpoVaK3FF8dWvm6 3DWw== X-Gm-Message-State: AOAM532msFUeEKKdZSOp9OZu8WqtVQY+Jn5JZAg1LEHbTsaesOeBZuoG X6Xp4SB50j0fUnxzJbFZToJ2o40RXxan5g== X-Google-Smtp-Source: ABdhPJyTbvn9gu5l1GXB1PUiVREbqWxursTNOB6JuZACg8XlPaWMbYLK2yGJF8nRMsowneCKb8boSw== X-Received: by 2002:a0c:f58a:: with SMTP id k10mr3290999qvm.62.1631381812246; Sat, 11 Sep 2021 10:36:52 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-59-147.nycmny.fios.verizon.net. [173.68.59.147]) by smtp.gmail.com with ESMTPSA id m5sm1594286qkn.33.2021.09.11.10.36.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Sep 2021 10:36:51 -0700 (PDT) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: thunderbird2k@gmail.com, blaws05@gmail.com, benjamin.tissoires@redhat.com, jikos@kernel.org, Roderick.Colenbrander@sony.com, svv@google.com, s.jegen@gmail.com, carmueller@gmail.com, pgriffais@valvesoftware.com, hadess@hadess.net, pobrn@protonmail.com, lee.jones@linaro.org, "Daniel J. Ogorchock" Subject: [PATCH v16 01/16] HID: nintendo: add nintendo switch controller driver Date: Sat, 11 Sep 2021 13:36:24 -0400 Message-Id: <20210911173639.5688-2-djogorchock@gmail.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20210911173639.5688-1-djogorchock@gmail.com> References: <20210911173639.5688-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 --- MAINTAINERS | 6 + drivers/hid/Kconfig | 11 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-nintendo.c | 869 +++++++++++++++++++++++++++++++++++++ 5 files changed, 890 insertions(+) create mode 100644 drivers/hid/hid-nintendo.c diff --git a/MAINTAINERS b/MAINTAINERS index 0590fe14b3bcb..e07c54a680c40 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13254,6 +13254,12 @@ W: http://www.netlab.is.tsukuba.ac.jp/~yokota/izumi/ninja/ F: Documentation/scsi/NinjaSCSI.rst F: drivers/scsi/nsp32* +NINTENDO HID DRIVER +M: Daniel J. Ogorchock +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-nintendo* + NIOS2 ARCHITECTURE M: Ley Foon Tan S: Maintained diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 3c33bf572d6d3..60c4f555773d4 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -731,6 +731,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 e29efcb1c0402..7a71371e3adfa 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -78,6 +78,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 29564b370341e..6b698c64bf525 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -917,6 +917,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 0000000000000..b6c0e5e36d8b0 --- /dev/null +++ b/drivers/hid/hid-nintendo.c @@ -0,0 +1,869 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HID driver for Nintendo Switch Joy-Cons and Pro Controllers + * + * Copyright (c) 2019 Daniel J. Ogorchock + * + * 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 +#include +#include +#include +#include +#include + +/* + * Reference the url below for the following HID report defines: + * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + */ + +/* Output Reports */ +static const u8 JC_OUTPUT_RUMBLE_AND_SUBCMD = 0x01; +static const u8 JC_OUTPUT_FW_UPDATE_PKT = 0x03; +static const u8 JC_OUTPUT_RUMBLE_ONLY = 0x10; +static const u8 JC_OUTPUT_MCU_DATA = 0x11; +static const u8 JC_OUTPUT_USB_CMD = 0x80; + +/* Subcommand IDs */ +static const u8 JC_SUBCMD_STATE /*= 0x00*/; +static const u8 JC_SUBCMD_MANUAL_BT_PAIRING = 0x01; +static const u8 JC_SUBCMD_REQ_DEV_INFO = 0x02; +static const u8 JC_SUBCMD_SET_REPORT_MODE = 0x03; +static const u8 JC_SUBCMD_TRIGGERS_ELAPSED = 0x04; +static const u8 JC_SUBCMD_GET_PAGE_LIST_STATE = 0x05; +static const u8 JC_SUBCMD_SET_HCI_STATE = 0x06; +static const u8 JC_SUBCMD_RESET_PAIRING_INFO = 0x07; +static const u8 JC_SUBCMD_LOW_POWER_MODE = 0x08; +static const u8 JC_SUBCMD_SPI_FLASH_READ = 0x10; +static const u8 JC_SUBCMD_SPI_FLASH_WRITE = 0x11; +static const u8 JC_SUBCMD_RESET_MCU = 0x20; +static const u8 JC_SUBCMD_SET_MCU_CONFIG = 0x21; +static const u8 JC_SUBCMD_SET_MCU_STATE = 0x22; +static const u8 JC_SUBCMD_SET_PLAYER_LIGHTS = 0x30; +static const u8 JC_SUBCMD_GET_PLAYER_LIGHTS = 0x31; +static const u8 JC_SUBCMD_SET_HOME_LIGHT = 0x38; +static const u8 JC_SUBCMD_ENABLE_IMU = 0x40; +static const u8 JC_SUBCMD_SET_IMU_SENSITIVITY = 0x41; +static const u8 JC_SUBCMD_WRITE_IMU_REG = 0x42; +static const u8 JC_SUBCMD_READ_IMU_REG = 0x43; +static const u8 JC_SUBCMD_ENABLE_VIBRATION = 0x48; +static const u8 JC_SUBCMD_GET_REGULATED_VOLTAGE = 0x50; + +/* Input Reports */ +static const u8 JC_INPUT_BUTTON_EVENT = 0x3F; +static const u8 JC_INPUT_SUBCMD_REPLY = 0x21; +static const u8 JC_INPUT_IMU_DATA = 0x30; +static const u8 JC_INPUT_MCU_DATA = 0x31; +static const u8 JC_INPUT_USB_RESPONSE = 0x81; + +/* Feature Reports */ +static const u8 JC_FEATURE_LAST_SUBCMD = 0x02; +static const u8 JC_FEATURE_OTA_FW_UPGRADE = 0x70; +static const u8 JC_FEATURE_SETUP_MEM_READ = 0x71; +static const u8 JC_FEATURE_MEM_READ = 0x72; +static const u8 JC_FEATURE_ERASE_MEM_SECTOR = 0x73; +static const u8 JC_FEATURE_MEM_WRITE = 0x74; +static const u8 JC_FEATURE_LAUNCH = 0x75; + +/* USB Commands */ +static const u8 JC_USB_CMD_CONN_STATUS = 0x01; +static const u8 JC_USB_CMD_HANDSHAKE = 0x02; +static const u8 JC_USB_CMD_BAUDRATE_3M = 0x03; +static const u8 JC_USB_CMD_NO_TIMEOUT = 0x04; +static const u8 JC_USB_CMD_EN_TIMEOUT = 0x05; +static const u8 JC_USB_RESET = 0x06; +static const u8 JC_USB_PRE_HANDSHAKE = 0x91; +static const u8 JC_USB_SEND_UART = 0x92; + +/* SPI storage addresses of factory calibration data */ +static const u16 JC_CAL_DATA_START = 0x603d; +static const u16 JC_CAL_DATA_END = 0x604e; +#define JC_CAL_DATA_SIZE (JC_CAL_DATA_END - JC_CAL_DATA_START + 1) + + +/* The raw analog joystick values will be mapped in terms of this magnitude */ +static const u16 JC_MAX_STICK_MAG = 32767; +static const u16 JC_STICK_FUZZ = 250; +static const u16 JC_STICK_FLAT = 500; + +/* Hat values for pro controller's d-pad */ +static const u16 JC_MAX_DPAD_MAG = 1; +static const u16 JC_DPAD_FUZZ /*= 0*/; +static const u16 JC_DPAD_FLAT /*= 0*/; + +/* States for controller state machine */ +enum joycon_ctlr_state { + JOYCON_CTLR_STATE_INIT, + JOYCON_CTLR_STATE_READ, +}; + +struct joycon_stick_cal { + s32 max; + s32 min; + s32 center; +}; + +/* + * All the controller's button values are stored in a u32. + * They can be accessed with bitwise ANDs. + */ +static const u32 JC_BTN_Y = BIT(0); +static const u32 JC_BTN_X = BIT(1); +static const u32 JC_BTN_B = BIT(2); +static const u32 JC_BTN_A = BIT(3); +static const u32 JC_BTN_SR_R = BIT(4); +static const u32 JC_BTN_SL_R = BIT(5); +static const u32 JC_BTN_R = BIT(6); +static const u32 JC_BTN_ZR = BIT(7); +static const u32 JC_BTN_MINUS = BIT(8); +static const u32 JC_BTN_PLUS = BIT(9); +static const u32 JC_BTN_RSTICK = BIT(10); +static const u32 JC_BTN_LSTICK = BIT(11); +static const u32 JC_BTN_HOME = BIT(12); +static const u32 JC_BTN_CAP = BIT(13); /* capture button */ +static const u32 JC_BTN_DOWN = BIT(16); +static const u32 JC_BTN_UP = BIT(17); +static const u32 JC_BTN_RIGHT = BIT(18); +static const u32 JC_BTN_LEFT = BIT(19); +static const u32 JC_BTN_SR_L = BIT(20); +static const u32 JC_BTN_SL_L = BIT(21); +static const u32 JC_BTN_L = BIT(22); +static const u32 JC_BTN_ZL = BIT(23); + +enum joycon_msg_type { + JOYCON_MSG_TYPE_NONE, + JOYCON_MSG_TYPE_USB, + JOYCON_MSG_TYPE_SUBCMD, +}; + +struct joycon_subcmd_request { + u8 output_id; /* must be 0x01 for subcommand, 0x10 for rumble only */ + u8 packet_num; /* incremented every send */ + u8 rumble_data[8]; + u8 subcmd_id; + u8 data[]; /* 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[]; /* 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); + input_report_key(dev, BTN_SELECT, btns & JC_BTN_MINUS); + input_report_key(dev, BTN_THUMBL, btns & JC_BTN_LSTICK); + input_report_key(dev, BTN_Z, btns & JC_BTN_CAP); + + if (id != USB_DEVICE_ID_NINTENDO_PROCON) { + /* Report the S buttons as the non-existent triggers */ + input_report_key(dev, BTN_TR, btns & JC_BTN_SL_L); + input_report_key(dev, BTN_TR2, btns & JC_BTN_SR_L); + + /* Report d-pad as digital buttons for the joy-cons */ + input_report_key(dev, BTN_DPAD_DOWN, + btns & JC_BTN_DOWN); + input_report_key(dev, BTN_DPAD_UP, btns & JC_BTN_UP); + input_report_key(dev, BTN_DPAD_RIGHT, + btns & JC_BTN_RIGHT); + input_report_key(dev, BTN_DPAD_LEFT, + btns & JC_BTN_LEFT); + } else { + int hatx = 0; + int haty = 0; + + /* d-pad x */ + if (btns & JC_BTN_LEFT) + hatx = -1; + else if (btns & JC_BTN_RIGHT) + hatx = 1; + input_report_abs(dev, ABS_HAT0X, hatx); + + /* d-pad y */ + if (btns & JC_BTN_UP) + haty = -1; + else if (btns & JC_BTN_DOWN) + haty = 1; + input_report_abs(dev, ABS_HAT0Y, haty); + } + } + if (id != USB_DEVICE_ID_NINTENDO_JOYCONL) { + u16 raw_x; + u16 raw_y; + s32 x; + s32 y; + + /* get raw stick values */ + raw_x = hid_field_extract(ctlr->hdev, rep->right_stick, 0, 12); + raw_y = hid_field_extract(ctlr->hdev, + rep->right_stick + 1, 4, 12); + /* map stick values */ + x = joycon_map_stick_val(&ctlr->right_stick_cal_x, raw_x); + y = -joycon_map_stick_val(&ctlr->right_stick_cal_y, raw_y); + /* report sticks */ + input_report_abs(dev, ABS_RX, x); + input_report_abs(dev, ABS_RY, y); + + /* report buttons */ + input_report_key(dev, BTN_TR, btns & JC_BTN_R); + input_report_key(dev, BTN_TR2, btns & JC_BTN_ZR); + if (id != USB_DEVICE_ID_NINTENDO_PROCON) { + /* Report the S buttons as the non-existent triggers */ + input_report_key(dev, BTN_TL, btns & JC_BTN_SL_R); + input_report_key(dev, BTN_TL2, btns & JC_BTN_SR_R); + } + input_report_key(dev, BTN_START, btns & JC_BTN_PLUS); + input_report_key(dev, BTN_THUMBR, btns & JC_BTN_RSTICK); + input_report_key(dev, BTN_MODE, btns & JC_BTN_HOME); + input_report_key(dev, BTN_WEST, btns & JC_BTN_Y); + input_report_key(dev, BTN_NORTH, btns & JC_BTN_X); + input_report_key(dev, BTN_EAST, btns & JC_BTN_A); + input_report_key(dev, BTN_SOUTH, btns & JC_BTN_B); + } + + input_sync(dev); +} + + +static const unsigned int joycon_button_inputs_l[] = { + BTN_SELECT, BTN_Z, BTN_THUMBL, + BTN_TL, BTN_TL2, + 0 /* 0 signals end of array */ +}; + +static const unsigned int joycon_button_inputs_r[] = { + BTN_START, BTN_MODE, BTN_THUMBR, + BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST, + BTN_TR, BTN_TR2, + 0 /* 0 signals end of array */ +}; + +/* We report joy-con d-pad inputs as buttons and pro controller as a hat. */ +static const unsigned int joycon_dpad_inputs_jc[] = { + BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, +}; + +static DEFINE_MUTEX(joycon_input_num_mutex); +static int joycon_input_create(struct joycon_ctlr *ctlr) +{ + struct hid_device *hdev; + static int input_num = 1; + const char *name; + int ret; + int i; + + hdev = ctlr->hdev; + + switch (hdev->product) { + case USB_DEVICE_ID_NINTENDO_PROCON: + name = "Nintendo Switch Pro Controller"; + break; + case USB_DEVICE_ID_NINTENDO_JOYCONL: + name = "Nintendo Switch Left Joy-Con"; + break; + case USB_DEVICE_ID_NINTENDO_JOYCONR: + name = "Nintendo Switch Right Joy-Con"; + break; + default: /* Should be impossible */ + hid_err(hdev, "Invalid hid product\n"); + return -EINVAL; + } + + ctlr->input = devm_input_allocate_device(&hdev->dev); + if (!ctlr->input) + return -ENOMEM; + ctlr->input->id.bustype = hdev->bus; + ctlr->input->id.vendor = hdev->vendor; + ctlr->input->id.product = hdev->product; + ctlr->input->id.version = hdev->version; + ctlr->input->name = name; + input_set_drvdata(ctlr->input, ctlr); + + + /* set up sticks and buttons */ + if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONR) { + input_set_abs_params(ctlr->input, ABS_X, + -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, + JC_STICK_FUZZ, JC_STICK_FLAT); + input_set_abs_params(ctlr->input, ABS_Y, + -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, + JC_STICK_FUZZ, JC_STICK_FLAT); + + for (i = 0; joycon_button_inputs_l[i] > 0; i++) + input_set_capability(ctlr->input, EV_KEY, + joycon_button_inputs_l[i]); + + /* configure d-pad differently for joy-con vs pro controller */ + if (hdev->product != USB_DEVICE_ID_NINTENDO_PROCON) { + for (i = 0; joycon_dpad_inputs_jc[i] > 0; i++) + input_set_capability(ctlr->input, EV_KEY, + joycon_dpad_inputs_jc[i]); + } else { + input_set_abs_params(ctlr->input, ABS_HAT0X, + -JC_MAX_DPAD_MAG, JC_MAX_DPAD_MAG, + JC_DPAD_FUZZ, JC_DPAD_FLAT); + input_set_abs_params(ctlr->input, ABS_HAT0Y, + -JC_MAX_DPAD_MAG, JC_MAX_DPAD_MAG, + JC_DPAD_FUZZ, JC_DPAD_FLAT); + } + } + if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) { + input_set_abs_params(ctlr->input, ABS_RX, + -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, + JC_STICK_FUZZ, JC_STICK_FLAT); + input_set_abs_params(ctlr->input, ABS_RY, + -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, + JC_STICK_FUZZ, JC_STICK_FLAT); + + for (i = 0; joycon_button_inputs_r[i] > 0; i++) + input_set_capability(ctlr->input, EV_KEY, + joycon_button_inputs_r[i]); + } + + /* Let's report joy-con S triggers separately */ + if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL) { + input_set_capability(ctlr->input, EV_KEY, BTN_TR); + input_set_capability(ctlr->input, EV_KEY, BTN_TR2); + } else if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR) { + input_set_capability(ctlr->input, EV_KEY, BTN_TL); + input_set_capability(ctlr->input, EV_KEY, BTN_TL2); + } + + ret = input_register_device(ctlr->input); + if (ret) + return ret; + + /* Set the default controller player leds based on controller number */ + mutex_lock(&joycon_input_num_mutex); + mutex_lock(&ctlr->output_mutex); + ret = joycon_set_player_leds(ctlr, 0, 0xF >> (4 - input_num)); + if (ret) + hid_warn(ctlr->hdev, "Failed to set leds; ret=%d\n", ret); + mutex_unlock(&ctlr->output_mutex); + if (++input_num > 4) + input_num = 1; + mutex_unlock(&joycon_input_num_mutex); + + return 0; +} + +/* Common handler for parsing inputs */ +static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data, + int size) +{ + 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 0; +} + +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) +{ + 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 "); +MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers"); From patchwork Sat Sep 11 17:36:27 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 509305 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 838B8C43219 for ; Sat, 11 Sep 2021 17:37:03 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 7047C60FE3 for ; Sat, 11 Sep 2021 17:37:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232995AbhIKRiO (ORCPT ); Sat, 11 Sep 2021 13:38:14 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48522 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232985AbhIKRiK (ORCPT ); Sat, 11 Sep 2021 13:38:10 -0400 Received: from mail-qv1-xf36.google.com (mail-qv1-xf36.google.com [IPv6:2607:f8b0:4864:20::f36]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E07FCC061574 for ; Sat, 11 Sep 2021 10:36:56 -0700 (PDT) Received: by mail-qv1-xf36.google.com with SMTP id s16so3411541qvt.13 for ; Sat, 11 Sep 2021 10:36:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=SnhCrzdj+vTHiQFbrgxuFQfOJjT9PpV+x3kmv1TMZj8=; b=XRYSYprLA9dNQu1slWtyDAfdyk/S97sCBzhnHeEM0TfFo2AfW7tn9IRi7sYoWJ8D4s rbjqPGA2F/IWRVwHLi8sy6RkVNjq58Z8gpCbM9l1l8vrBdDekB1TFuUFLU5DhOqwZ7id Q+RHzehHeATOX2UJfBj51QiLCkUjm1MROKgJlObGJpM4K8zNq33UHQbHVf9v2bGsKMIq 39MfBF0KCFWNyvh5LRFeSA3Q3qAfsVCtWZkzN4pYy2F4Ry5pdmqCWsyzbJHkEt+aAZpI tRYyoHw7jCYG2w0pUpcPhr06/cE6x7uT/2gf6YNFPGp66TUJRZ4i9v49CV3ygOygtvRN qi2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=SnhCrzdj+vTHiQFbrgxuFQfOJjT9PpV+x3kmv1TMZj8=; b=Ut2VN5A/Ys7iYtboaoxiMhnVoZ24BXb3I+xcdW64eVLApdJQItpePKW5adpMTr56+m Otsxu7zlZ9dktdicm5CPr2RdeKhgPpuoxh2fLJbD+vHNATNRQy1gvi2Zjj+mvBh/DcSJ bSF4AsA9QSkRSayRInMT3VTWyM6VXlNcttA7HvUvUg0yHhTzXIU6Iqatm2Z14FC6quhh VyNZ8E/IMtZl/wNUaV5PmRRmSJvGxvx6hEylBmSpjQ2K7lHAU5n9iisFWJLvk8daShCw tbdRjd+D7AuPMe7skXgTos2CN5F2Vv9evGjVfcGVmngaPdwefcTTcLMGOszT+DCsx+LK TivQ== X-Gm-Message-State: AOAM5322lb2QYJ8Tfs+8ggqp4t9e6ELejQZBfzFrzX6IEw3cNxMKxZjj GPuBc84CxQ0fft/AWbdfixzX2LFKnxPc7A== X-Google-Smtp-Source: ABdhPJwo455t1IMBYeeGlh3eQ9BFWkMV+dMx442hFu5OTfPfmCLyqK+IXCqj3WBWxbD28SB9qQHvZw== X-Received: by 2002:a0c:f010:: with SMTP id z16mr3286135qvk.8.1631381815932; Sat, 11 Sep 2021 10:36:55 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-59-147.nycmny.fios.verizon.net. [173.68.59.147]) by smtp.gmail.com with ESMTPSA id m5sm1594286qkn.33.2021.09.11.10.36.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Sep 2021 10:36:55 -0700 (PDT) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: thunderbird2k@gmail.com, blaws05@gmail.com, benjamin.tissoires@redhat.com, jikos@kernel.org, Roderick.Colenbrander@sony.com, svv@google.com, s.jegen@gmail.com, carmueller@gmail.com, pgriffais@valvesoftware.com, hadess@hadess.net, pobrn@protonmail.com, lee.jones@linaro.org, "Daniel J. Ogorchock" Subject: [PATCH v16 04/16] HID: nintendo: add home led support Date: Sat, 11 Sep 2021 13:36:27 -0400 Message-Id: <20210911173639.5688-5-djogorchock@gmail.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20210911173639.5688-1-djogorchock@gmail.com> References: <20210911173639.5688-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org This patch adds the ability to set the intensity level of the home button's LED. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-nintendo.c | 72 +++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index c6c715a9ac7cd..59cdb7dfa59c3 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -199,7 +199,8 @@ static const char * const joycon_player_led_names[] = { 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; @@ -733,8 +734,42 @@ 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 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; @@ -773,7 +808,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; } } @@ -781,6 +816,35 @@ 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:%s", + d_name, + "blue", + LED_FUNCTION_PLAYER5); + if (!name) + return -ENOMEM; + + 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; } @@ -1027,7 +1091,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; From patchwork Sat Sep 11 17:36:30 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 509304 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 56E21C43217 for ; Sat, 11 Sep 2021 17:37:04 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 42AB9610C8 for ; Sat, 11 Sep 2021 17:37:04 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233130AbhIKRiQ (ORCPT ); Sat, 11 Sep 2021 13:38:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48540 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232635AbhIKRiN (ORCPT ); Sat, 11 Sep 2021 13:38:13 -0400 Received: from mail-qk1-x72a.google.com (mail-qk1-x72a.google.com [IPv6:2607:f8b0:4864:20::72a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 68345C061574 for ; Sat, 11 Sep 2021 10:37:00 -0700 (PDT) Received: by mail-qk1-x72a.google.com with SMTP id y144so5810656qkb.6 for ; Sat, 11 Sep 2021 10:37:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=5BwZyKNVET1XegG16gwOWtMxcmyh03ak3Bqt1yLzo+U=; b=E+nuuWnSOSGdqWvizOG4GvQWWeklib+P+X3zp/jgWjNU+QbGeXgUQm0v4u2MXZXFHN off/f3HjRHMrUo6B5fq4isGNFgCyIiAPn6R1bgsC24mS3UnzlwLKjebg9+xLI5qOpICP Xt7HoyJvoQQoKAiUbIHnlBWJkT2eLg+hpbtuO+dNI7ut2VbDganNPwBffzue+3A0Mdda 2uce5t4+J4BQhuxdz0ik3uCrI2AQUEinYg3U7qO9EupYe8e6Bq5TGLw5TIqixwa6b7kN Xr6elwW3248MuxRlvNponIcuTMIuzEm+69P3w9ZTqOJ/WkOUsaqxYkXUTgxX23Gge8it U3qg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=5BwZyKNVET1XegG16gwOWtMxcmyh03ak3Bqt1yLzo+U=; b=lcRwemVbm1i2LVW+B9l790g9VuiBK+XM2stUOBJahEODgAPt4zda8kIUOKx5XmIZNO SEo7To7MEgsJEEk7wmlEP/zQ5Dln933S9b6SEHg3J2EmuF0HSFDtabSuuphKsC1KUOej HD88RoN1aLHczJjZcfaFLTHjUcOJpUz/SdKLgAPkMB5brnUpwlg13Q6RiRKb/C/3JoQV xPlDveVGwUt2Xek0+6UeG4WLYaOVNnkgON0sLowiE1IA/RTFuXmkiJDyavN2j0u8hYVf lfrnTTZIRWEWpk25IyRub08Can0W9hzBhuZHtd6JcFDOCC8FiTlHzccEht6qLJmjf9A+ r4BQ== X-Gm-Message-State: AOAM5322ZpbUheMKUBo5nzg/9q8r66Zpvxg2V6CXTTTG88MHx1pQzir5 l8RldyAe5om8wpIulQS9JIzDVIkB3gqDKA== X-Google-Smtp-Source: ABdhPJwc+lIH9c0xosPsuoIkjAFEC4kDyYSwv3M8bV2yhcP/v+CfvF4UPYzbGQOJLz2n+QgZRcp7eg== X-Received: by 2002:a37:a64b:: with SMTP id p72mr2849888qke.447.1631381819475; Sat, 11 Sep 2021 10:36:59 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-59-147.nycmny.fios.verizon.net. [173.68.59.147]) by smtp.gmail.com with ESMTPSA id m5sm1594286qkn.33.2021.09.11.10.36.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Sep 2021 10:36:58 -0700 (PDT) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: thunderbird2k@gmail.com, blaws05@gmail.com, benjamin.tissoires@redhat.com, jikos@kernel.org, Roderick.Colenbrander@sony.com, svv@google.com, s.jegen@gmail.com, carmueller@gmail.com, pgriffais@valvesoftware.com, hadess@hadess.net, pobrn@protonmail.com, lee.jones@linaro.org, "Daniel J. Ogorchock" Subject: [PATCH v16 07/16] HID: nintendo: send subcommands after receiving input report Date: Sat, 11 Sep 2021 13:36:30 -0400 Message-Id: <20210911173639.5688-8-djogorchock@gmail.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20210911173639.5688-1-djogorchock@gmail.com> References: <20210911173639.5688-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 --- 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 a95943c2fc6c0..a000a287206e8 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -335,6 +335,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; @@ -388,6 +389,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); @@ -760,6 +781,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) From patchwork Sat Sep 11 17:36:32 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 509303 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 07D45C433FE for ; Sat, 11 Sep 2021 17:37:05 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E4FE961059 for ; Sat, 11 Sep 2021 17:37:04 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232985AbhIKRiQ (ORCPT ); Sat, 11 Sep 2021 13:38:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48552 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233074AbhIKRiP (ORCPT ); Sat, 11 Sep 2021 13:38:15 -0400 Received: from mail-qk1-x733.google.com (mail-qk1-x733.google.com [IPv6:2607:f8b0:4864:20::733]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 48FF0C061756 for ; Sat, 11 Sep 2021 10:37:02 -0700 (PDT) Received: by mail-qk1-x733.google.com with SMTP id bk29so5797471qkb.8 for ; Sat, 11 Sep 2021 10:37:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=czpR2U/8SvztuRjNgqTIyIRpRJzK2hatecEATnWUBi0=; b=oXHB5dUZElbZSaEcK5w6HDwpU537VEiJOejy1DrvYJOSJtpwrMuOKgx0Gh+ZXtfMXs X4gMra6Fc8vs7Ywqpi8TXiQduitfTBlP/04X9PVankmdpooecJazfUbXD2YTmuVRFdRY g4AFRSWnCJR8hYzxrLj3A4qjI/L9tG7izi0hJPWz3LgXgIsG0i1kqMLh+QfVEiZgJAay ejl1diYLPoAyHyjeOXpYrSD7CEe9yHqm9Xq1BREJDf2C3seKuP6QXg9NesVrjdN9532a j27xiBhrJqKkPkSlqGyeqek9/eT5slduXLNIPBxNACmGdbTXToqT+EWOpjjskEWTVwMB dJyQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=czpR2U/8SvztuRjNgqTIyIRpRJzK2hatecEATnWUBi0=; b=XZoMshtvjLsFe7Kuw2I7PEGf76kYoBGfZ+zr3/5jAtAD1bvQkUr2nSyJ4D6zjkjG8s Xm8sU+vipj2U2HIqvthzhZnOy0bGmu/EE4GK0TbBLAfujOhFnwNi0C8oQS6oU6R51Oib xUi2x8RhdNPfQAz34etdTortQrFdMXk2ZGW065L1mfI6rIcD9uIwyW1qUk3RR3BweqCZ 2q1PEJPoto9ylQiplZv8d36+sSO9ZNtla8g5WfX7asKMxx9wgzIHdGsCmigE8Ipd+DdX vRHEX9JoXB919oZIC4FqcQ4ZcPqwLSjW0nEZ0RtO1RtA5mbf/XqcCdoHV/LUI/Y7PcEM 9UxQ== X-Gm-Message-State: AOAM530oFXhBqDQZsj/9jbex5e9TLIjeg/ABgTYVHtO67PDpQ4UWWm7D 9XtFGgIAahtiaMeDZra+WEga6AlxGYDM/g== X-Google-Smtp-Source: ABdhPJzHKvMAY4NZ7/ljFr9tcjt5gCYKZ9tA/H0BsrQnzUGZPSErmh8rXs1GuIyAUSwQyJjt7NdRuQ== X-Received: by 2002:a37:c97:: with SMTP id 145mr2836001qkm.121.1631381821414; Sat, 11 Sep 2021 10:37:01 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-59-147.nycmny.fios.verizon.net. [173.68.59.147]) by smtp.gmail.com with ESMTPSA id m5sm1594286qkn.33.2021.09.11.10.37.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Sep 2021 10:37:01 -0700 (PDT) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: thunderbird2k@gmail.com, blaws05@gmail.com, benjamin.tissoires@redhat.com, jikos@kernel.org, Roderick.Colenbrander@sony.com, svv@google.com, s.jegen@gmail.com, carmueller@gmail.com, pgriffais@valvesoftware.com, hadess@hadess.net, pobrn@protonmail.com, lee.jones@linaro.org, "Daniel J. Ogorchock" Subject: [PATCH v16 09/16] HID: nintendo: patch hw version for userspace HID mappings Date: Sat, 11 Sep 2021 13:36:32 -0400 Message-Id: <20210911173639.5688-10-djogorchock@gmail.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20210911173639.5688-1-djogorchock@gmail.com> References: <20210911173639.5688-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 --- 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 cda0118ebd14b..ed9cf7b373067 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -1426,6 +1426,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"); From patchwork Sat Sep 11 17:36:33 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 509302 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6D56CC4332F for ; Sat, 11 Sep 2021 17:37:05 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 589C66109F for ; Sat, 11 Sep 2021 17:37:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233162AbhIKRiR (ORCPT ); Sat, 11 Sep 2021 13:38:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48556 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233121AbhIKRiQ (ORCPT ); Sat, 11 Sep 2021 13:38:16 -0400 Received: from mail-qk1-x729.google.com (mail-qk1-x729.google.com [IPv6:2607:f8b0:4864:20::729]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 312F2C061757 for ; Sat, 11 Sep 2021 10:37:03 -0700 (PDT) Received: by mail-qk1-x729.google.com with SMTP id 22so5822742qkg.2 for ; Sat, 11 Sep 2021 10:37:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=WgY2Lx7Q+Rn8EFfctbvGz9yFk4kqkwqPYRmIwqaquMc=; b=KkPg9yeOu1GYofU4FrP/1YH/Nvw58MrC48cHDv/p/i8lYzfY/vEtCX9b/IqET2v9Kl Vv9ekM6VQwupuswgZaYgf26Dmpm+GtQpzxHmYPuP8WenV/gcQjEOTSsGE+u6J31gT77u fAKmcsgeCh8/0GHaaOeuD2TTmtdkfmH4vRINP5nTnH78L77siJmZc8taTWzalO2/Gdkw WCZs9ztFPQrnGk1hUwIYBoujf5VJgluVcgaU4dAqr8ShT9zCAHv8S1Oggxghth7u/el4 TylRYlnZcG3+QY9wbqEbbF2HeYu2jFE4SPe43vwLwkI8yURozDwd+DNEroJK5lt9xvp9 2gLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=WgY2Lx7Q+Rn8EFfctbvGz9yFk4kqkwqPYRmIwqaquMc=; b=nQdvlAmMqK2CMdqDIcvG85wSzm/QoTEvJO50CdaEjiwQmB4pyftWOaaRsmP7LlqV/3 Hx1/FZ/LW6Mbbee+2KMg8I1T9NvstyiFkOceJ04fceNpg/TAqiP0UKLFL53BY55RFSXI WxKFm+kuIAjTonmial9RkIBbhLg0Lzrj/NvPiz3lOpi5ByC0Dj4enkPRLHWO2FCL0dTj 1f+z9FdRtqPLuRs8wcl2gnXrvHst6eHmiboEBM7EPWTLUe3GEqoy8a6kFT3vwhLLa1hj xFq6N+eeBkwSsMFveN5DY23l1AVOjZtAU+SoQEUM80T5XIJyhnNSADPxn2lg6iN1+Oec ldTQ== X-Gm-Message-State: AOAM5317z7dgnYiT3bk77tygIBVLs0AO9J2K/QlXBXPBS3TBbu0TTA7n jV3qwF27Yo+3+h038pHNb8XEhp3GRqbqCA== X-Google-Smtp-Source: ABdhPJyugoagFgGAN2RglZ+opIw/23lH77aAoIZYPu7g4R4OdpfqXSHY8nQosh+dFQSyKoFhmIZCGw== X-Received: by 2002:a05:620a:4094:: with SMTP id f20mr2860245qko.488.1631381822322; Sat, 11 Sep 2021 10:37:02 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-59-147.nycmny.fios.verizon.net. [173.68.59.147]) by smtp.gmail.com with ESMTPSA id m5sm1594286qkn.33.2021.09.11.10.37.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Sep 2021 10:37:01 -0700 (PDT) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: thunderbird2k@gmail.com, blaws05@gmail.com, benjamin.tissoires@redhat.com, jikos@kernel.org, Roderick.Colenbrander@sony.com, svv@google.com, s.jegen@gmail.com, carmueller@gmail.com, pgriffais@valvesoftware.com, hadess@hadess.net, pobrn@protonmail.com, lee.jones@linaro.org, "Daniel J. Ogorchock" Subject: [PATCH v16 10/16] HID: nintendo: set controller uniq to MAC Date: Sat, 11 Sep 2021 13:36:33 -0400 Message-Id: <20210911173639.5688-11-djogorchock@gmail.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20210911173639.5688-1-djogorchock@gmail.com> References: <20210911173639.5688-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 --- 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 ed9cf7b373067..ac171ee5066ad 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -326,6 +326,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; @@ -1012,6 +1014,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); @@ -1322,6 +1325,41 @@ static int joycon_power_supply_create(struct joycon_ctlr *ctlr) return power_supply_powers(ctlr->battery, &hdev->dev); } +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) @@ -1498,6 +1536,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_mutex; + } + mutex_unlock(&ctlr->output_mutex); /* Initialize the leds */ From patchwork Sat Sep 11 17:36:35 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 509301 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9950FC43217 for ; Sat, 11 Sep 2021 17:37:06 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 7E041610A3 for ; Sat, 11 Sep 2021 17:37:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233167AbhIKRiS (ORCPT ); Sat, 11 Sep 2021 13:38:18 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48570 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233074AbhIKRiS (ORCPT ); Sat, 11 Sep 2021 13:38:18 -0400 Received: from mail-qk1-x731.google.com (mail-qk1-x731.google.com [IPv6:2607:f8b0:4864:20::731]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3A5A3C061574 for ; Sat, 11 Sep 2021 10:37:05 -0700 (PDT) Received: by mail-qk1-x731.google.com with SMTP id f22so5810386qkm.5 for ; Sat, 11 Sep 2021 10:37:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=z3Y77pjwJWrZVAZXDFvDASTJ1Db7OmgDH1J0EQCaCiw=; b=CYEe9tIBDMMoZGJWFQHVXUP8uI/Hex1YRhA3Lh3X6rWR5PQnZIavZtOuZHEqe43yCC hsa2wMzx98kuzhVHRRFNqYA+BQuKC1TGEVnkVCHeT93ZWD7qNe4Q18onFg39iHdpT5PA ggN7//JSK6S1/mxcN2QhnQGNosYDlq3s6whtT7PfW5v1vd920ZbcNxfM9RBpFwcTOGHH bSqNmhwZrZxLRwOnPWk//eWdCqD/znVUifC0AgPg1XZqoXiwrG6uQF5B4tqFlbqCuyPy y4gMRviTpeSmyltjao1RwPKVURD44EOXLuv7v0JEnaZIZ509jkO8gM0BNRBfhxiHgQ90 kTNQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=z3Y77pjwJWrZVAZXDFvDASTJ1Db7OmgDH1J0EQCaCiw=; b=1dvq5KYiGr1dus4b9GRcJqJivRwWkP5DcczRcu6OPJjHpIL2TWkYDX7jmG2FQcYTnk AzvBrw+6CdIxaPOUiHKcq6FmLeVV10dQo8NTCBua8b0LdwVX6xWGliLRio8NN00HiV6q EMEE+zfiMBRLQxHyvGEzvhKzI/xMGJ3Q+d3l77d4cxeFJLFzqRBvE3j3BgeVDDlCpVQC D1GAUu3QV6FZxvSvgvjabr05p6UdjLhZUlZbGtjpXhMjX/Fv6+mXseGulskPDmJCkH4e FIUB0icB1g0k7++Rtkqm0iIpqtZQXlWbdSoz83SS98rnjtLnzrvy/koSWnnvxR34ZtNA Zgjw== X-Gm-Message-State: AOAM530NA/mrWrVjgJUAq5j98lE87ivbBRbiURXYUyAzj0Uauz/oytLe kcpv+CT+DI/2NbZ4tJPY5Y56lDIeYLzbHA== X-Google-Smtp-Source: ABdhPJxxTpcEag/ElYVYhMw8JI2/ThKRx5L5yMF6zDqawql+sOY0cuj5lUBpcIOguoCKAo3ATYnzUg== X-Received: by 2002:a37:9f15:: with SMTP id i21mr2926173qke.16.1631381824240; Sat, 11 Sep 2021 10:37:04 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-59-147.nycmny.fios.verizon.net. [173.68.59.147]) by smtp.gmail.com with ESMTPSA id m5sm1594286qkn.33.2021.09.11.10.37.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Sep 2021 10:37:03 -0700 (PDT) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: thunderbird2k@gmail.com, blaws05@gmail.com, benjamin.tissoires@redhat.com, jikos@kernel.org, Roderick.Colenbrander@sony.com, svv@google.com, s.jegen@gmail.com, carmueller@gmail.com, pgriffais@valvesoftware.com, hadess@hadess.net, pobrn@protonmail.com, lee.jones@linaro.org, "Daniel J. Ogorchock" Subject: [PATCH v16 12/16] HID: nintendo: add support for reading user calibration Date: Sat, 11 Sep 2021 13:36:35 -0400 Message-Id: <20210911173639.5688-13-djogorchock@gmail.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20210911173639.5688-1-djogorchock@gmail.com> References: <20210911173639.5688-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org If the controller's SPI flash contains user stick calibration(s), they should be prioritized over the factory calibrations. The user calibrations have 2 magic bytes preceding them. If the bytes are the correct magic values, the user calibration is used. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-nintendo.c | 206 ++++++++++++++++++++++++++----------- 1 file changed, 148 insertions(+), 58 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index ad054ca9080c1..b914e262e6f64 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -23,6 +23,7 @@ */ #include "hid-ids.h" +#include #include #include #include @@ -96,11 +97,23 @@ 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) +/* Magic value denoting presence of user calibration */ +static const u16 JC_CAL_USR_MAGIC_0 = 0xB2; +static const u16 JC_CAL_USR_MAGIC_1 = 0xA1; +static const u8 JC_CAL_USR_MAGIC_SIZE = 2; + +/* SPI storage addresses of user calibration data */ +static const u16 JC_CAL_USR_LEFT_MAGIC_ADDR = 0x8010; +static const u16 JC_CAL_USR_LEFT_DATA_ADDR = 0x8012; +static const u16 JC_CAL_USR_LEFT_DATA_END = 0x801A; +static const u16 JC_CAL_USR_RIGHT_MAGIC_ADDR = 0x801B; +static const u16 JC_CAL_USR_RIGHT_DATA_ADDR = 0x801D; +#define JC_CAL_STICK_DATA_SIZE \ + (JC_CAL_USR_LEFT_DATA_END - JC_CAL_USR_LEFT_DATA_ADDR + 1) +/* SPI storage addresses of factory calibration data */ +static const u16 JC_CAL_FCT_DATA_LEFT_ADDR = 0x603d; +static const u16 JC_CAL_FCT_DATA_RIGHT_ADDR = 0x6046; /* The raw analog joystick values will be mapped in terms of this magnitude */ static const u16 JC_MAX_STICK_MAG = 32767; @@ -531,38 +544,140 @@ static int joycon_set_player_leds(struct joycon_ctlr *ctlr, u8 flash, u8 on) return joycon_send_subcmd(ctlr, req, 1, HZ/4); } -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) +static int joycon_request_spi_flash_read(struct joycon_ctlr *ctlr, + u32 start_addr, u8 size, u8 **reply) { 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; + u8 buffer[sizeof(*req) + 5] = { 0 }; + u8 *data; + int ret; + + if (!reply) + return -EINVAL; + + req = (struct joycon_subcmd_request *)buffer; + req->subcmd_id = JC_SUBCMD_SPI_FLASH_READ; + data = req->data; + put_unaligned_le32(start_addr, data); + data[4] = size; + + hid_dbg(ctlr->hdev, "requesting SPI flash data\n"); + ret = joycon_send_subcmd(ctlr, req, 5, HZ); + if (ret) { + hid_err(ctlr->hdev, "failed reading SPI flash; ret=%d\n", ret); + } else { + report = (struct joycon_input_report *)ctlr->input_buf; + /* The read data starts at the 6th byte */ + *reply = &report->reply.data[5]; + } + return ret; +} + +/* + * User calibration's presence is denoted with a magic byte preceding it. + * returns 0 if magic val is present, 1 if not present, < 0 on error + */ +static int joycon_check_for_cal_magic(struct joycon_ctlr *ctlr, u32 flash_addr) +{ + int ret; + u8 *reply; + + ret = joycon_request_spi_flash_read(ctlr, flash_addr, + JC_CAL_USR_MAGIC_SIZE, &reply); + if (ret) + return ret; + + return reply[0] != JC_CAL_USR_MAGIC_0 || reply[1] != JC_CAL_USR_MAGIC_1; +} + +static int joycon_read_stick_calibration(struct joycon_ctlr *ctlr, u16 cal_addr, + struct joycon_stick_cal *cal_x, + struct joycon_stick_cal *cal_y, + bool left_stick) +{ 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; + ret = joycon_request_spi_flash_read(ctlr, cal_addr, + JC_CAL_STICK_DATA_SIZE, &raw_cal); + if (ret) + return ret; + + /* stick calibration parsing: note the order differs based on stick */ + if (left_stick) { + 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); + } else { + 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; + + return 0; +} + +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) +{ + u16 left_stick_addr = JC_CAL_FCT_DATA_LEFT_ADDR; + u16 right_stick_addr = JC_CAL_FCT_DATA_RIGHT_ADDR; + int ret; hid_dbg(ctlr->hdev, "requesting cal data\n"); - ret = joycon_send_subcmd(ctlr, req, 5, HZ); + + /* check if user stick calibrations are present */ + if (!joycon_check_for_cal_magic(ctlr, JC_CAL_USR_LEFT_MAGIC_ADDR)) { + left_stick_addr = JC_CAL_USR_LEFT_DATA_ADDR; + hid_info(ctlr->hdev, "using user cal for left stick\n"); + } else { + hid_info(ctlr->hdev, "using factory cal for left stick\n"); + } + if (!joycon_check_for_cal_magic(ctlr, JC_CAL_USR_RIGHT_MAGIC_ADDR)) { + right_stick_addr = JC_CAL_USR_RIGHT_DATA_ADDR; + hid_info(ctlr->hdev, "using user cal for right stick\n"); + } else { + hid_info(ctlr->hdev, "using factory cal for right stick\n"); + } + + /* read the left stick calibration data */ + ret = joycon_read_stick_calibration(ctlr, left_stick_addr, + &ctlr->left_stick_cal_x, + &ctlr->left_stick_cal_y, + true); if (ret) { hid_warn(ctlr->hdev, - "Failed to read stick cal, using defaults; ret=%d\n", + "Failed to read left stick cal, using dflts; e=%d\n", ret); ctlr->left_stick_cal_x.center = DFLT_STICK_CAL_CEN; @@ -572,6 +687,17 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr) 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; + } + + /* read the right stick calibration data */ + ret = joycon_read_stick_calibration(ctlr, right_stick_addr, + &ctlr->right_stick_cal_x, + &ctlr->right_stick_cal_y, + false); + if (ret) { + hid_warn(ctlr->hdev, + "Failed to read right stick cal, using dflts; e=%d\n", + ret); ctlr->right_stick_cal_x.center = DFLT_STICK_CAL_CEN; ctlr->right_stick_cal_x.max = DFLT_STICK_CAL_MAX; @@ -580,44 +706,8 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr) 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" From patchwork Sat Sep 11 17:36:36 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 509300 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-20.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, MENTIONS_GIT_HOSTING, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7CE4FC433EF for ; Sat, 11 Sep 2021 17:37:08 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6084861051 for ; Sat, 11 Sep 2021 17:37:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233007AbhIKRiU (ORCPT ); Sat, 11 Sep 2021 13:38:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48578 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233074AbhIKRiT (ORCPT ); Sat, 11 Sep 2021 13:38:19 -0400 Received: from mail-qt1-x82c.google.com (mail-qt1-x82c.google.com [IPv6:2607:f8b0:4864:20::82c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C715CC061574 for ; Sat, 11 Sep 2021 10:37:06 -0700 (PDT) Received: by mail-qt1-x82c.google.com with SMTP id b14so4615536qtb.0 for ; Sat, 11 Sep 2021 10:37:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=5FRX3nsiuKVvMNglVFGONJgNGQTlW2wG138izPHK7nA=; b=JLpffS7saL3pQgyTmQF3dAc7SOYXXtP+etJ8Rb4v6aH4/rn/Okw1eGvwVgw29mwrjj C/E3U5w87btPS7oKmNvzKfF2TAjemN8pAumKvlcsOSVwp9lItAuOWlRZtSxy2oQBlqL9 TTs3L7r9+2Otomr2VjCY8yIMd8bvlr5Dd0V9JJNIEBxmRyrIApNJAct7oDw1qETSdtgy 0kgUXluX76+WLXX/7k144nkFTxZqBtGrN5uWzQc7/gtD0tSWj3Sxcegb0HVYmW1koKhr tMJRdyty5lj5Pzs2/p7jCr5Jn0JCyY+fbo+Hhup+OHtMvvVltDXVk8vU4oNbf+ldHReu fodw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=5FRX3nsiuKVvMNglVFGONJgNGQTlW2wG138izPHK7nA=; b=4hK7O9RpmWt20wujqaDPSZLmkJDK09OKBVZibXRSPqz89/Kj8NjthjXR3nTPewmhsc gl9y3Y0C4BGoqu7a1H6/MdOOGzfLnbbwb6jnwpefUnyRmk8PWHojSuuY7AllSBNCZgvx TAcIYL2NK1sLJID2LjiBjtfX7GLiu8Vb4Ng/yXhmYexZ6hlDUp+swjGMu192YSQcLrE0 GUWLAH4KpxqTSRsouMjTy/UsYEV/tMbN7yrw30dAA5/IoOP59oSCocyWmg2Aht2CnTXB uRzvbENe1aSpTFC//yraxJcvtk9LJYdVPyeY8gimjqx3BQ3sJN8Tsg55RNaOyep4WMoy 46hg== X-Gm-Message-State: AOAM531q2r+MjM4pYD6uXNV5T7s54K6t+whb6JFuV3J189KDwKFeVttk hwsqYHTKjC6GPFRLg8mLiaYazG0zejlqHQ== X-Google-Smtp-Source: ABdhPJy1NGFGo7ajnNt+ofTm2ve14lYaqmMSiw2IDuUmUMmb4mc+c7hrzGSGpGf6Oon9mdk2eoFPTw== X-Received: by 2002:ac8:6697:: with SMTP id d23mr3061177qtp.34.1631381825217; Sat, 11 Sep 2021 10:37:05 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-59-147.nycmny.fios.verizon.net. [173.68.59.147]) by smtp.gmail.com with ESMTPSA id m5sm1594286qkn.33.2021.09.11.10.37.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Sep 2021 10:37:04 -0700 (PDT) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: thunderbird2k@gmail.com, blaws05@gmail.com, benjamin.tissoires@redhat.com, jikos@kernel.org, Roderick.Colenbrander@sony.com, svv@google.com, s.jegen@gmail.com, carmueller@gmail.com, pgriffais@valvesoftware.com, hadess@hadess.net, pobrn@protonmail.com, lee.jones@linaro.org, "Daniel J. Ogorchock" Subject: [PATCH v16 13/16] HID: nintendo: add IMU support Date: Sat, 11 Sep 2021 13:36:36 -0400 Message-Id: <20210911173639.5688-14-djogorchock@gmail.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20210911173639.5688-1-djogorchock@gmail.com> References: <20210911173639.5688-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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). A large part of this patch's functionality was provided by Carl Mueller. The IMU device is blacklisted from the joydev input handler. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-nintendo.c | 503 ++++++++++++++++++++++++++++++++++++- drivers/input/joydev.c | 10 + 2 files changed, 501 insertions(+), 12 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index b914e262e6f64..1e8dc34f39802 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -2,7 +2,7 @@ /* * HID driver for Nintendo Switch Joy-Cons and Pro Controllers * - * Copyright (c) 2019 Daniel J. Ogorchock + * Copyright (c) 2019-2020 Daniel J. Ogorchock * * The following resources/projects were referenced for this driver: * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -115,6 +116,15 @@ static const u16 JC_CAL_USR_RIGHT_DATA_ADDR = 0x801D; static const u16 JC_CAL_FCT_DATA_LEFT_ADDR = 0x603d; static const u16 JC_CAL_FCT_DATA_RIGHT_ADDR = 0x6046; +/* SPI storage addresses of IMU factory calibration data */ +static const u16 JC_IMU_CAL_FCT_DATA_ADDR = 0x6020; +static const u16 JC_IMU_CAL_FCT_DATA_END = 0x6037; +#define JC_IMU_CAL_DATA_SIZE \ + (JC_IMU_CAL_FCT_DATA_END - JC_IMU_CAL_FCT_DATA_ADDR + 1) +/* SPI storage addresses of IMU user calibration data */ +static const u16 JC_IMU_CAL_USR_MAGIC_ADDR = 0x8026; +static const u16 JC_IMU_CAL_USR_DATA_ADDR = 0x8028; + /* 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; @@ -125,6 +135,47 @@ static const u16 JC_MAX_DPAD_MAG = 1; static const u16 JC_DPAD_FUZZ /*= 0*/; static const u16 JC_DPAD_FLAT /*= 0*/; +/* Under most circumstances IMU reports are pushed every 15ms; use as default */ +static const u16 JC_IMU_DFLT_AVG_DELTA_MS = 15; +/* How many samples to sum before calculating average IMU report delta */ +static const u16 JC_IMU_SAMPLES_PER_DELTA_AVG = 300; +/* Controls how many dropped IMU packets at once trigger a warning message */ +static const u16 JC_IMU_DROPPED_PKT_WARNING = 3; + +/* + * The controller's accelerometer has a sensor resolution of 16bits and is + * configured with a range of +-8000 milliGs. Therefore, the resolution can be + * calculated thus: (2^16-1)/(8000 * 2) = 4.096 digits per milliG + * Resolution per G (rather than per millliG): 4.096 * 1000 = 4096 digits per G + * Alternatively: 1/4096 = .0002441 Gs per digit + */ +static const s32 JC_IMU_MAX_ACCEL_MAG = 32767; +static const u16 JC_IMU_ACCEL_RES_PER_G = 4096; +static const u16 JC_IMU_ACCEL_FUZZ = 10; +static const u16 JC_IMU_ACCEL_FLAT /*= 0*/; + +/* + * The controller's gyroscope has a sensor resolution of 16bits and is + * configured with a range of +-2000 degrees/second. + * Digits per dps: (2^16 -1)/(2000*2) = 16.38375 + * dps per digit: 16.38375E-1 = .0610 + * + * STMicro recommends in the datasheet to add 15% to the dps/digit. This allows + * the full sensitivity range to be saturated without clipping. This yields more + * accurate results, so it's the technique this driver uses. + * dps per digit (corrected): .0610 * 1.15 = .0702 + * digits per dps (corrected): .0702E-1 = 14.247 + * + * Now, 14.247 truncating to 14 loses a lot of precision, so we rescale the + * min/max range by 1000. + */ +static const s32 JC_IMU_PREC_RANGE_SCALE = 1000; +/* Note: change mag and res_per_dps if prec_range_scale is ever altered */ +static const s32 JC_IMU_MAX_GYRO_MAG = 32767000; /* (2^16-1)*1000 */ +static const u16 JC_IMU_GYRO_RES_PER_DPS = 14247; /* (14.247*1000) */ +static const u16 JC_IMU_GYRO_FUZZ = 10; +static const u16 JC_IMU_GYRO_FLAT /*= 0*/; + /* frequency/amplitude tables for rumble */ struct joycon_rumble_freq_data { u16 high; @@ -259,6 +310,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. @@ -306,6 +362,15 @@ struct joycon_subcmd_reply { u8 data[]; /* 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_input_report { u8 id; u8 timer; @@ -315,11 +380,11 @@ 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; + /* IMU input reports contain 3 samples */ + u8 imu_raw_bytes[sizeof(struct joycon_imu_data) * 3]; + }; } __packed; #define JC_MAX_RESP_SIZE (sizeof(struct joycon_input_report) + 35) @@ -367,6 +432,13 @@ 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; + + /* prevents needlessly recalculating these divisors every sample */ + s32 imu_cal_accel_divisor[3]; + s32 imu_cal_gyro_divisor[3]; + /* power supply data */ struct power_supply *battery; struct power_supply_desc battery_desc; @@ -385,6 +457,16 @@ struct joycon_ctlr { u16 rumble_lh_freq; u16 rumble_rl_freq; u16 rumble_rh_freq; + + /* imu */ + struct input_dev *imu_input; + bool imu_first_packet_received; /* helps in initiating timestamp */ + unsigned int imu_timestamp_us; /* timestamp we report to userspace */ + unsigned int imu_last_pkt_ms; /* used to calc imu report delta */ + /* the following are used to track the average imu report time delta */ + unsigned int imu_delta_samples_count; + unsigned int imu_delta_samples_sum; + unsigned int imu_avg_delta_ms; }; /* Helper macros for checking controller type */ @@ -569,7 +651,7 @@ static int joycon_request_spi_flash_read(struct joycon_ctlr *ctlr, } else { report = (struct joycon_input_report *)ctlr->input_buf; /* The read data starts at the 6th byte */ - *reply = &report->reply.data[5]; + *reply = &report->subcmd_reply.data[5]; } return ret; } @@ -729,6 +811,94 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr) return 0; } +/* + * These divisors are calculated once rather than for each sample. They are only + * dependent on the IMU calibration values. They are used when processing the + * IMU input reports. + */ +static void joycon_calc_imu_cal_divisors(struct joycon_ctlr *ctlr) +{ + int i; + + for (i = 0; i < 3; i++) { + ctlr->imu_cal_accel_divisor[i] = ctlr->accel_cal.scale[i] - + ctlr->accel_cal.offset[i]; + ctlr->imu_cal_gyro_divisor[i] = ctlr->gyro_cal.scale[i] - + ctlr->gyro_cal.offset[i]; + } +} + +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) +{ + u16 imu_cal_addr = JC_IMU_CAL_FCT_DATA_ADDR; + u8 *raw_cal; + int ret; + int i; + + /* check if user calibration exists */ + if (!joycon_check_for_cal_magic(ctlr, JC_IMU_CAL_USR_MAGIC_ADDR)) { + imu_cal_addr = JC_IMU_CAL_USR_DATA_ADDR; + hid_info(ctlr->hdev, "using user cal for IMU\n"); + } else { + hid_info(ctlr->hdev, "using factory cal for IMU\n"); + } + + /* request IMU calibration data */ + hid_dbg(ctlr->hdev, "requesting IMU cal data\n"); + ret = joycon_request_spi_flash_read(ctlr, imu_cal_addr, + JC_IMU_CAL_DATA_SIZE, &raw_cal); + 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; + } + joycon_calc_imu_cal_divisors(ctlr); + return ret; + } + + /* IMU calibration parsing */ + for (i = 0; i < 3; i++) { + int j = i * 2; + + ctlr->accel_cal.offset[i] = get_unaligned_le16(raw_cal + j); + ctlr->accel_cal.scale[i] = get_unaligned_le16(raw_cal + j + 6); + ctlr->gyro_cal.offset[i] = get_unaligned_le16(raw_cal + j + 12); + ctlr->gyro_cal.scale[i] = get_unaligned_le16(raw_cal + j + 18); + } + + joycon_calc_imu_cal_divisors(ctlr); + + 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; @@ -755,6 +925,19 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr) return joycon_send_subcmd(ctlr, req, 1, HZ/4); } +static int joycon_enable_imu(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_ENABLE_IMU; + req->data[0] = 0x01; /* note: 0x00 would disable */ + + hid_dbg(ctlr->hdev, "enabling IMU\n"); + 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; @@ -773,6 +956,224 @@ static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val) return new_val; } +static void joycon_input_report_parse_imu_data(struct joycon_ctlr *ctlr, + struct joycon_input_report *rep, + struct joycon_imu_data *imu_data) +{ + u8 *raw = rep->imu_raw_bytes; + int i; + + for (i = 0; i < 3; i++) { + struct joycon_imu_data *data = &imu_data[i]; + + data->accel_x = get_unaligned_le16(raw + 0); + data->accel_y = get_unaligned_le16(raw + 2); + data->accel_z = get_unaligned_le16(raw + 4); + data->gyro_x = get_unaligned_le16(raw + 6); + data->gyro_y = get_unaligned_le16(raw + 8); + data->gyro_z = get_unaligned_le16(raw + 10); + /* point to next imu sample */ + raw += sizeof(struct joycon_imu_data); + } +} + +static void joycon_parse_imu_report(struct joycon_ctlr *ctlr, + struct joycon_input_report *rep) +{ + struct joycon_imu_data imu_data[3] = {0}; /* 3 reports per packet */ + struct input_dev *idev = ctlr->imu_input; + unsigned int msecs = jiffies_to_msecs(jiffies); + unsigned int last_msecs = ctlr->imu_last_pkt_ms; + int i; + int value[6]; + + joycon_input_report_parse_imu_data(ctlr, rep, imu_data); + + /* + * There are complexities surrounding how we determine the timestamps we + * associate with the samples we pass to userspace. The IMU input + * reports do not provide us with a good timestamp. There's a quickly + * incrementing 8-bit counter per input report, but it is not very + * useful for this purpose (it is not entirely clear what rate it + * increments at or if it varies based on packet push rate - more on + * the push rate below...). + * + * The reverse engineering work done on the joy-cons and pro controllers + * by the community seems to indicate the following: + * - The controller samples the IMU every 1.35ms. It then does some of + * its own processing, probably averaging the samples out. + * - Each imu input report contains 3 IMU samples, (usually 5ms apart). + * - In the standard reporting mode (which this driver uses exclusively) + * input reports are pushed from the controller as follows: + * * joy-con (bluetooth): every 15 ms + * * joy-cons (in charging grip via USB): every 15 ms + * * pro controller (USB): every 15 ms + * * pro controller (bluetooth): every 8 ms (this is the wildcard) + * + * Further complicating matters is that some bluetooth stacks are known + * to alter the controller's packet rate by hardcoding the bluetooth + * SSR for the switch controllers (android's stack currently sets the + * SSR to 11ms for both the joy-cons and pro controllers). + * + * In my own testing, I've discovered that my pro controller either + * reports IMU sample batches every 11ms or every 15ms. This rate is + * stable after connecting. It isn't 100% clear what determines this + * rate. Importantly, even when sending every 11ms, none of the samples + * are duplicates. This seems to indicate that the time deltas between + * reported samples can vary based on the input report rate. + * + * The solution employed in this driver is to keep track of the average + * time delta between IMU input reports. In testing, this value has + * proven to be stable, staying at 15ms or 11ms, though other hardware + * configurations and bluetooth stacks could potentially see other rates + * (hopefully this will become more clear as more people use the + * driver). + * + * Keeping track of the average report delta allows us to submit our + * timestamps to userspace based on that. Each report contains 3 + * samples, so the IMU sampling rate should be avg_time_delta/3. We can + * also use this average to detect events where we have dropped a + * packet. The userspace timestamp for the samples will be adjusted + * accordingly to prevent unwanted behvaior. + */ + if (!ctlr->imu_first_packet_received) { + ctlr->imu_timestamp_us = 0; + ctlr->imu_delta_samples_count = 0; + ctlr->imu_delta_samples_sum = 0; + ctlr->imu_avg_delta_ms = JC_IMU_DFLT_AVG_DELTA_MS; + ctlr->imu_first_packet_received = true; + } else { + unsigned int delta = msecs - last_msecs; + unsigned int dropped_pkts; + unsigned int dropped_threshold; + + /* avg imu report delta housekeeping */ + ctlr->imu_delta_samples_sum += delta; + ctlr->imu_delta_samples_count++; + if (ctlr->imu_delta_samples_count >= + JC_IMU_SAMPLES_PER_DELTA_AVG) { + ctlr->imu_avg_delta_ms = ctlr->imu_delta_samples_sum / + ctlr->imu_delta_samples_count; + /* don't ever want divide by zero shenanigans */ + if (ctlr->imu_avg_delta_ms == 0) { + ctlr->imu_avg_delta_ms = 1; + hid_warn(ctlr->hdev, + "calculated avg imu delta of 0\n"); + } + ctlr->imu_delta_samples_count = 0; + ctlr->imu_delta_samples_sum = 0; + } + + /* useful for debugging IMU sample rate */ + hid_dbg(ctlr->hdev, + "imu_report: ms=%u last_ms=%u delta=%u avg_delta=%u\n", + msecs, last_msecs, delta, ctlr->imu_avg_delta_ms); + + /* check if any packets have been dropped */ + dropped_threshold = ctlr->imu_avg_delta_ms * 3 / 2; + dropped_pkts = (delta - min(delta, dropped_threshold)) / + ctlr->imu_avg_delta_ms; + ctlr->imu_timestamp_us += 1000 * ctlr->imu_avg_delta_ms; + if (dropped_pkts > JC_IMU_DROPPED_PKT_WARNING) { + hid_warn(ctlr->hdev, + "compensating for %u dropped IMU reports\n", + dropped_pkts); + hid_warn(ctlr->hdev, + "delta=%u avg_delta=%u\n", + delta, ctlr->imu_avg_delta_ms); + } + } + ctlr->imu_last_pkt_ms = msecs; + + /* Each IMU input report contains three samples */ + for (i = 0; i < 3; i++) { + input_event(idev, EV_MSC, MSC_TIMESTAMP, + ctlr->imu_timestamp_us); + + /* + * These calculations (which use the controller's calibration + * settings to improve the final values) are based on those + * found in the community's reverse-engineering repo (linked at + * top of driver). For hid-nintendo, we make sure that the final + * value given to userspace is always in terms of the axis + * resolution we provided. + * + * Currently only the gyro calculations subtract the calibration + * offsets from the raw value itself. In testing, doing the same + * for the accelerometer raw values decreased accuracy. + * + * Note that the gyro values are multiplied by the + * precision-saving scaling factor to prevent large inaccuracies + * due to truncation of the resolution value which would + * otherwise occur. To prevent overflow (without resorting to 64 + * bit integer math), the mult_frac macro is used. + */ + value[0] = mult_frac((JC_IMU_PREC_RANGE_SCALE * + (imu_data[i].gyro_x - + ctlr->gyro_cal.offset[0])), + ctlr->gyro_cal.scale[0], + ctlr->imu_cal_gyro_divisor[0]); + value[1] = mult_frac((JC_IMU_PREC_RANGE_SCALE * + (imu_data[i].gyro_y - + ctlr->gyro_cal.offset[1])), + ctlr->gyro_cal.scale[1], + ctlr->imu_cal_gyro_divisor[1]); + value[2] = mult_frac((JC_IMU_PREC_RANGE_SCALE * + (imu_data[i].gyro_z - + ctlr->gyro_cal.offset[2])), + ctlr->gyro_cal.scale[2], + ctlr->imu_cal_gyro_divisor[2]); + + value[3] = ((s32)imu_data[i].accel_x * + ctlr->accel_cal.scale[0]) / + ctlr->imu_cal_accel_divisor[0]; + value[4] = ((s32)imu_data[i].accel_y * + ctlr->accel_cal.scale[1]) / + ctlr->imu_cal_accel_divisor[1]; + value[5] = ((s32)imu_data[i].accel_z * + ctlr->accel_cal.scale[2]) / + ctlr->imu_cal_accel_divisor[2]; + + hid_dbg(ctlr->hdev, "raw_gyro: g_x=%d g_y=%d g_z=%d\n", + imu_data[i].gyro_x, imu_data[i].gyro_y, + imu_data[i].gyro_z); + hid_dbg(ctlr->hdev, "raw_accel: a_x=%d a_y=%d a_z=%d\n", + imu_data[i].accel_x, imu_data[i].accel_y, + imu_data[i].accel_z); + + /* + * The right joy-con has 2 axes negated, Y and Z. This is due to + * the orientation of the IMU in the controller. We negate those + * axes' values in order to be consistent with the left joy-con + * and the pro controller: + * X: positive is pointing toward the triggers + * Y: positive is pointing to the left + * Z: positive is pointing up (out of the buttons/sticks) + * The axes follow the right-hand rule. + */ + if (jc_type_is_joycon(ctlr) && jc_type_has_right(ctlr)) { + int j; + + /* negate all but x axis */ + for (j = 1; j < 6; ++j) { + if (j == 3) + continue; + value[j] *= -1; + } + } + + 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); + /* convert to micros and divide by 3 (3 samples per report). */ + ctlr->imu_timestamp_us += ctlr->imu_avg_delta_ms * 1000 / 3; + } +} + static void joycon_parse_report(struct joycon_ctlr *ctlr, struct joycon_input_report *rep) { @@ -921,6 +1322,10 @@ 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) + joycon_parse_imu_report(ctlr, rep); } static void joycon_rumble_worker(struct work_struct *work) @@ -1104,6 +1509,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; @@ -1112,18 +1518,24 @@ 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_CHRGGRIP: - if (jc_type_has_left(ctlr)) + if (jc_type_has_left(ctlr)) { name = "Nintendo Switch Left Joy-Con (Grip)"; - else + imu_name = "Nintendo Switch Left Joy-Con IMU (Grip)"; + } else { name = "Nintendo Switch Right Joy-Con (Grip)"; + imu_name = "Nintendo Switch Right Joy-Con IMU (Grip)"; + } break; case USB_DEVICE_ID_NINTENDO_JOYCONL: name = "Nintendo Switch Left Joy-Con"; + imu_name = "Nintendo Switch Left Joy-Con IMU"; break; case USB_DEVICE_ID_NINTENDO_JOYCONR: name = "Nintendo Switch Right Joy-Con"; + imu_name = "Nintendo Switch Right Joy-Con IMU"; break; default: /* Should be impossible */ hid_err(hdev, "Invalid hid product\n"); @@ -1207,6 +1619,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_IMU_MAX_ACCEL_MAG, JC_IMU_MAX_ACCEL_MAG, + JC_IMU_ACCEL_FUZZ, JC_IMU_ACCEL_FLAT); + input_set_abs_params(ctlr->imu_input, ABS_Y, + -JC_IMU_MAX_ACCEL_MAG, JC_IMU_MAX_ACCEL_MAG, + JC_IMU_ACCEL_FUZZ, JC_IMU_ACCEL_FLAT); + input_set_abs_params(ctlr->imu_input, ABS_Z, + -JC_IMU_MAX_ACCEL_MAG, JC_IMU_MAX_ACCEL_MAG, + JC_IMU_ACCEL_FUZZ, JC_IMU_ACCEL_FLAT); + input_abs_set_res(ctlr->imu_input, ABS_X, JC_IMU_ACCEL_RES_PER_G); + input_abs_set_res(ctlr->imu_input, ABS_Y, JC_IMU_ACCEL_RES_PER_G); + input_abs_set_res(ctlr->imu_input, ABS_Z, JC_IMU_ACCEL_RES_PER_G); + + input_set_abs_params(ctlr->imu_input, ABS_RX, + -JC_IMU_MAX_GYRO_MAG, JC_IMU_MAX_GYRO_MAG, + JC_IMU_GYRO_FUZZ, JC_IMU_GYRO_FLAT); + input_set_abs_params(ctlr->imu_input, ABS_RY, + -JC_IMU_MAX_GYRO_MAG, JC_IMU_MAX_GYRO_MAG, + JC_IMU_GYRO_FUZZ, JC_IMU_GYRO_FLAT); + input_set_abs_params(ctlr->imu_input, ABS_RZ, + -JC_IMU_MAX_GYRO_MAG, JC_IMU_MAX_GYRO_MAG, + JC_IMU_GYRO_FUZZ, JC_IMU_GYRO_FLAT); + + input_abs_set_res(ctlr->imu_input, ABS_RX, JC_IMU_GYRO_RES_PER_DPS); + input_abs_set_res(ctlr->imu_input, ABS_RY, JC_IMU_GYRO_RES_PER_DPS); + input_abs_set_res(ctlr->imu_input, ABS_RZ, JC_IMU_GYRO_RES_PER_DPS); + + __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; } @@ -1465,7 +1926,7 @@ static int joycon_read_info(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", @@ -1480,7 +1941,7 @@ static int joycon_read_info(struct joycon_ctlr *ctlr) 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->reply.data[2]; + ctlr->ctlr_type = report->subcmd_reply.data[2]; return 0; } @@ -1521,7 +1982,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: @@ -1651,6 +2112,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) { @@ -1665,6 +2136,13 @@ static int nintendo_hid_probe(struct hid_device *hdev, goto err_mutex; } + /* Enable the IMU */ + ret = joycon_enable_imu(ctlr); + if (ret) { + hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret); + goto err_mutex; + } + ret = joycon_read_info(ctlr); if (ret) { hid_err(hdev, "Failed to retrieve controller info; ret=%d\n", @@ -1757,3 +2235,4 @@ module_hid_driver(nintendo_hid_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Daniel J. Ogorchock "); MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers"); + diff --git a/drivers/input/joydev.c b/drivers/input/joydev.c index 947d440a3be63..b45ddb4570028 100644 --- a/drivers/input/joydev.c +++ b/drivers/input/joydev.c @@ -758,6 +758,12 @@ static void joydev_cleanup(struct joydev *joydev) #define USB_VENDOR_ID_THQ 0x20d6 #define USB_DEVICE_ID_THQ_PS3_UDRAW 0xcb17 +#define USB_VENDOR_ID_NINTENDO 0x057e +#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 ACCEL_DEV(vnd, prd) \ { \ .flags = INPUT_DEVICE_ID_MATCH_VENDOR | \ @@ -789,6 +795,10 @@ static const struct input_device_id joydev_blacklist[] = { ACCEL_DEV(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2), ACCEL_DEV(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE), ACCEL_DEV(USB_VENDOR_ID_THQ, USB_DEVICE_ID_THQ_PS3_UDRAW), + ACCEL_DEV(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON), + ACCEL_DEV(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_CHRGGRIP), + ACCEL_DEV(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_JOYCONL), + ACCEL_DEV(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_JOYCONR), { /* sentinel */ } }; From patchwork Sat Sep 11 17:36:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 509299 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-20.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, MENTIONS_GIT_HOSTING, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5A74AC433F5 for ; Sat, 11 Sep 2021 17:37:09 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3EFD061051 for ; Sat, 11 Sep 2021 17:37:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233089AbhIKRiV (ORCPT ); Sat, 11 Sep 2021 13:38:21 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48582 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233181AbhIKRiU (ORCPT ); Sat, 11 Sep 2021 13:38:20 -0400 Received: from mail-qk1-x72b.google.com (mail-qk1-x72b.google.com [IPv6:2607:f8b0:4864:20::72b]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 167F4C061574 for ; Sat, 11 Sep 2021 10:37:08 -0700 (PDT) Received: by mail-qk1-x72b.google.com with SMTP id y144so5810957qkb.6 for ; Sat, 11 Sep 2021 10:37:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=8yKC4KVJDFdQGz6EqFNaGHcVw2fwaznIXaB+lSathQE=; b=CHHVtKwWNLPXz+A19bcHZDkYijB0scpVJkjIvNUiSUBJWXj1HTvCRrAl8nLP/H8Y/k 6u3EOpjzuhhdZXh4ogvgonIuoYSygioX3IsnTdiiqP0b6vjVnPmqZN0XAVMKD1t9DDhg 8UQ+Udt6Z6+ouNUwPLy0X/KtiP7VoydHqEPiT1uNfFjuaD+jeJMYnmfx+JeiW53Ygtr2 vLGQngvv9ailAqc1wrSNQmfRyslBZqYVaKSObD5RtNVSjL+gnqkwz73ypht3VBAK/0Q2 6vz5NG0maZgLsfliQu47OKqCtm/YIMyKiu7nK9J0QaYmu429X1SktCSmNA3eka4Wk4aQ ISTw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=8yKC4KVJDFdQGz6EqFNaGHcVw2fwaznIXaB+lSathQE=; b=b7D5DTwSTXzmjyI6Y2NuCrMVPVYxvCRMiDg87O/45NDKQtP37DboxrZ4Sy78rQoz5a NxC75mUMofVnPLgT+LoTlsaDAIXKijRGyaQ/gyLXIDePZniqug1qtDdmM8d0nCbs1CTf K3bogg/ty7c8E4hehqCtN9fuvP3xVCpUNGORUXaO8QTzt1DPbKeTkbiOt0W1rfgnBDXy CcIAxWT8Hwpb/rou7Jxh9+IZaM15+v3rxxdvlKK9HWLPMngDbu8AB9yEfQsoIjd2AvFJ B1fPay75tJEnHfBSExT4NU+yLNr/CUe5vrX7RFXAjYbtOjT5aUmlIjXb9iWGr3ISQZb5 rSWw== X-Gm-Message-State: AOAM533S4i8SOFX6bs3X/7Y0SEQh20wrO+lp66Xsfh5P24dLVADIcX2F X4Rdz5t8aMrup5mY+LVaiiAgD9iN3EjGAA== X-Google-Smtp-Source: ABdhPJyjs/aAS6wHyw5pL3ZeZYHJygsV55HrMbZCYMhiVs2rru7kuAxpW2TJDkSSmXu+lS7AZroWag== X-Received: by 2002:a37:9e8c:: with SMTP id h134mr2947146qke.366.1631381827079; Sat, 11 Sep 2021 10:37:07 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-59-147.nycmny.fios.verizon.net. [173.68.59.147]) by smtp.gmail.com with ESMTPSA id m5sm1594286qkn.33.2021.09.11.10.37.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Sep 2021 10:37:06 -0700 (PDT) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: thunderbird2k@gmail.com, blaws05@gmail.com, benjamin.tissoires@redhat.com, jikos@kernel.org, Roderick.Colenbrander@sony.com, svv@google.com, s.jegen@gmail.com, carmueller@gmail.com, pgriffais@valvesoftware.com, hadess@hadess.net, pobrn@protonmail.com, lee.jones@linaro.org, "Daniel J. Ogorchock" Subject: [PATCH v16 15/16] HID: nintendo: ratelimit subcommands and rumble Date: Sat, 11 Sep 2021 13:36:38 -0400 Message-Id: <20210911173639.5688-16-djogorchock@gmail.com> X-Mailer: git-send-email 2.33.0 In-Reply-To: <20210911173639.5688-1-djogorchock@gmail.com> References: <20210911173639.5688-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org It has been found that sending subcommands and rumble data packets at too great a rate can result in controller disconnects. This patch limits the rate of subcommands/rumble to once every 25 milliseconds. Similar to sending subcommands, it is more reliable to send the rumble data packets immediately after we've received an input report from the controller. This results in far fewer bluetooth disconnects for the controller. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-nintendo.c | 69 +++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 2351e1d8c8e07..ae3cd8ca9fa78 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -2,7 +2,7 @@ /* * HID driver for Nintendo Switch Joy-Cons and Pro Controllers * - * Copyright (c) 2019-2020 Daniel J. Ogorchock + * Copyright (c) 2019-2021 Daniel J. Ogorchock * * The following resources/projects were referenced for this driver: * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering @@ -431,6 +431,7 @@ struct joycon_ctlr { u8 usb_ack_match; u8 subcmd_ack_match; bool received_input_report; + unsigned int last_subcmd_sent_msecs; /* factory calibration data */ struct joycon_stick_cal left_stick_cal_x; @@ -510,6 +511,50 @@ static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len) return ret; } +static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr) +{ + int ret; + + /* + * 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"); + } +} + +/* + * Sending subcommands and/or rumble data at too high a rate can cause bluetooth + * controller disconnections. + */ +static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr) +{ + static const unsigned int max_subcmd_rate_ms = 25; + unsigned int current_ms = jiffies_to_msecs(jiffies); + unsigned int delta_ms = current_ms - ctlr->last_subcmd_sent_msecs; + + while (delta_ms < max_subcmd_rate_ms && + ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) { + joycon_wait_for_input_report(ctlr); + current_ms = jiffies_to_msecs(jiffies); + delta_ms = current_ms - ctlr->last_subcmd_sent_msecs; + } + ctlr->last_subcmd_sent_msecs = current_ms; +} + static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len, u32 timeout) { @@ -521,25 +566,7 @@ 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"); - } + joycon_enforce_subcmd_rate(ctlr); ret = __joycon_hid_send(ctlr->hdev, data, len); if (ret < 0) { @@ -1359,6 +1386,8 @@ static int joycon_send_rumble_data(struct joycon_ctlr *ctlr) if (++ctlr->subcmd_num > 0xF) ctlr->subcmd_num = 0; + joycon_enforce_subcmd_rate(ctlr); + ret = __joycon_hid_send(ctlr->hdev, (u8 *)&rumble_output, sizeof(rumble_output)); return ret;