From patchwork Thu May 20 22:47:01 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271463 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 85D6FC433B4 for ; Thu, 20 May 2021 22:47:33 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5FDF6610CB for ; Thu, 20 May 2021 22:47:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231862AbhETWsy (ORCPT ); Thu, 20 May 2021 18:48:54 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55754 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230239AbhETWsx (ORCPT ); Thu, 20 May 2021 18:48:53 -0400 Received: from mail-qk1-x732.google.com (mail-qk1-x732.google.com [IPv6:2607:f8b0:4864:20::732]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 96B38C061761 for ; Thu, 20 May 2021 15:47:30 -0700 (PDT) Received: by mail-qk1-x732.google.com with SMTP id x8so17975277qkl.2 for ; Thu, 20 May 2021 15:47:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=9E7tAeQ+x35a/xuYJQMCYhrNCANna5B0oucTBRTWMgM=; b=hlARSUrAdH/esFye3TJNrnAyLfpqRPmboVWK8+07NxspwHHHsbGb3U0ROom1R/GTAK MLbiu8lBV0hfebVrKigN8e7GAGm/WenE6V6KHI2pKRx5rIrfJ/ynB3UBV/lPB2PuY0my 00pn2DcUwaU8B/VL8npFVacxzjS/y7V8DCgEoTDPg/iFiHJ2g1jMNlVCRF2DBKoCO8cP XlRMvTiKB+ozulWtZsmeWfOiS9S5Uqivg0Ld6Zz96mvsxABwepstakzEqVHJ8LrlgAyJ IhmrD+VyoKUzMPUZKm2dVbQ7NfnkUPgfAhG5rKXBLLF+2WlZjAc0RPmYMzAlYLW09qPA mYsg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=9E7tAeQ+x35a/xuYJQMCYhrNCANna5B0oucTBRTWMgM=; b=IIYYsccM2NUXrDWGZReemwdRxc0GxkVWWAUECMSEUVW8AVqjeZfvkEab4hdR4tXUl4 M4BOzJ9935JEQD8uFBXERjpwLkTMsr+2bByNOJHksDg2VE/bxoibN0namfkD+Tor6ZY2 BN4gd4IV4glOD8XKvKyn1/zK+g49qqSGo5vZbuuGgdt+P+Z7fgEJHKrqHjOZ7bSgmX7W ZEbK7R7G5wpbWd/KddtsE/FHPik6cj9ERWnPrEvpwROS58RZGZectZN8q7zAl/D+XP9K Vw/erEJG9rvORQrI7F3wDFYTbx0foFieuXeQ1XTRT8vmfykTHTeV90Gnng4yL5mOHlOK V3fA== X-Gm-Message-State: AOAM531uoKiEtK8wVPHUG5n/X2WO963W/oGiV7mJkpjwzqglATUF2TY+ eELLgL+GZWfSzyrY4N7QUtAepjK3p2q3BQ== X-Google-Smtp-Source: ABdhPJw+PCmqfEs79YcKFs4Vee9WKC2fHAJu2UG/+5aH7sbzg5pzxysIJdVL8tBnEKMAkueuyz2dEQ== X-Received: by 2002:a05:620a:130f:: with SMTP id o15mr7890580qkj.252.1621550849217; Thu, 20 May 2021 15:47:29 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:28 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 01/15] HID: nintendo: add nintendo switch controller driver Date: Thu, 20 May 2021 18:47:01 -0400 Message-Id: <20210520224715.680919-2-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 9450e052f1b13..24248ca0c627d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12671,6 +12671,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 786b71ef7738a..1f23e84f8bdf3 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -719,6 +719,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 c4f6d5c613dc4..d12c4bf077944 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -77,6 +77,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 ba338973e968d..5f353655a47bf 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -907,6 +907,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 Thu May 20 22:47:02 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271461 X-Patchwork-Delegate: jikos@jikos.cz 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 B9334C43460 for ; Thu, 20 May 2021 22:47:33 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9B737613AF for ; Thu, 20 May 2021 22:47:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230239AbhETWsy (ORCPT ); Thu, 20 May 2021 18:48:54 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55762 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231861AbhETWsy (ORCPT ); Thu, 20 May 2021 18:48:54 -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 9C97FC061574 for ; Thu, 20 May 2021 15:47:31 -0700 (PDT) Received: by mail-qk1-x72b.google.com with SMTP id c20so17973508qkm.3 for ; Thu, 20 May 2021 15:47:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=qhSQeJB763cMwz58WJbI0C+i53NDYrc5yQr8W5tYYBw=; b=h16vjXKdKOCR+FeT2Hdm2c/0p/AOPt4rGr8DIzGyYGEFCrcIhjaKe6JTSREWe5D6vD beEkF1ksBMDNVQf3ZruaiPtYOmI9ctcyqyPAeyWPjtAcXTvHDG1zhoaOw2/6w4FacMJg mVwyOuFt29s+GbIIAnQ5HD1vBjnHOUH1Ewa+aje4njn/Fg4nB9ZQs0/eO/nesCtXpVYj CmPVEJK3za0fI0H9gCw9OGY9kin2xVu8+myob0Ggy2V6AYetgybBbFe1DZo1KRckcTB/ vntrq70cqgl4Y65lhsaUjLobZjuyy2s3/x53FSvu2jJ3LVlSOl82D5PkIoLbKw8fcQDg zmNQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=qhSQeJB763cMwz58WJbI0C+i53NDYrc5yQr8W5tYYBw=; b=NMAhAv2P5h/Cpxyl0PRuQ0reomSKkNNKjaARp1OECiRfqN74e9urabVS0Am7jUv4CB ocOZfUTCgZbpff+N6Z4o6rwLlFThJKNL3BerRQg3AwMpzp8fq0wKpYcmCvBjd0/BpLzi Ehgr7xF/Gq4rH28/+Dncl3cAbnqvHAd5OXkLJlvYSCnZpVFGcRQsC08fEpBPGLfCoqEB l60JpoQE8f4K6OUWnPUSwiJ4sHpKfsDTCPAIUrD8VBPAQK0aESCiXEtXkY4Z+nZwjizg wzx8VpSqyHhoGfDkZYKaIO/ApdsNaG7Ru3QS7kQ4BJOJg0UeFOS6gCeiu/acViuGaSbd xoNA== X-Gm-Message-State: AOAM5307z2WDUhdnpNXvXNFLS671OlB8m7PuMVstWRTEtLYWbYo4tVvN nxrFw80CvyMwz7hqSzK+57HD5F3UexkQDA== X-Google-Smtp-Source: ABdhPJzMgWg2wh+LC6Eeis87c9V0JHyRKUaispwQI2HYchRoy6abKg/dCohp42jWnHfy6IlZckzN1g== X-Received: by 2002:a37:9c17:: with SMTP id f23mr6657340qke.125.1621550850510; Thu, 20 May 2021 15:47:30 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:29 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 02/15] HID: nintendo: add player led support Date: Thu, 20 May 2021 18:47:02 -0400 Message-Id: <20210520224715.680919-3-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org This patch adds led_classdev functionality to the switch controller driver. It adds support for the 4 player LEDs. The Home Button LED still needs to be supported on the pro controllers and right joy-con. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/Kconfig | 2 + drivers/hid/hid-nintendo.c | 95 +++++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1f23e84f8bdf3..86a6129d3d89f 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -722,6 +722,8 @@ config HID_MULTITOUCH config HID_NINTENDO tristate "Nintendo Joy-Con and Pro Controller support" depends on HID + depends on NEW_LEDS + depends on LEDS_CLASS help Adds support for the Nintendo Switch Joy-Cons and Pro Controller. All controllers support bluetooth, and the Pro Controller also supports diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index b6c0e5e36d8b0..c21682b4a2e62 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -183,11 +184,13 @@ struct joycon_input_report { } __packed; #define JC_MAX_RESP_SIZE (sizeof(struct joycon_input_report) + 35) +#define JC_NUM_LEDS 4 /* Each physical controller is associated with a joycon_ctlr struct */ struct joycon_ctlr { struct hid_device *hdev; struct input_dev *input; + struct led_classdev leds[JC_NUM_LEDS]; enum joycon_ctlr_state ctlr_state; /* The following members are used for synchronous sends/receives */ @@ -553,11 +556,9 @@ 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; @@ -643,6 +644,66 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) if (ret) return ret; + return 0; +} + +static int joycon_player_led_brightness_set(struct led_classdev *led, + enum led_brightness brightness) +{ + struct device *dev = led->dev->parent; + struct hid_device *hdev = to_hid_device(dev); + struct joycon_ctlr *ctlr; + int val = 0; + int i; + int ret; + int num; + + ctlr = hid_get_drvdata(hdev); + if (!ctlr) { + hid_err(hdev, "No controller data\n"); + return -ENODEV; + } + + /* determine which player led this is */ + for (num = 0; num < JC_NUM_LEDS; num++) { + if (&ctlr->leds[num] == led) + break; + } + if (num >= JC_NUM_LEDS) + return -EINVAL; + + mutex_lock(&ctlr->output_mutex); + for (i = 0; i < JC_NUM_LEDS; i++) { + if (i == num) + val |= brightness << i; + else + val |= ctlr->leds[i].brightness << i; + } + ret = joycon_set_player_leds(ctlr, 0, val); + mutex_unlock(&ctlr->output_mutex); + + return ret; +} + +static const char * const joycon_player_led_names[] = { + "player1", + "player2", + "player3", + "player4" +}; + +static DEFINE_MUTEX(joycon_input_num_mutex); +static int joycon_player_leds_create(struct joycon_ctlr *ctlr) +{ + struct hid_device *hdev = ctlr->hdev; + struct device *dev = &hdev->dev; + const char *d_name = dev_name(dev); + struct led_classdev *led; + char *name; + int ret = 0; + int i; + static int input_num = 1; + /* Set the default controller player leds based on controller number */ mutex_lock(&joycon_input_num_mutex); mutex_lock(&ctlr->output_mutex); @@ -650,6 +711,29 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) if (ret) hid_warn(ctlr->hdev, "Failed to set leds; ret=%d\n", ret); mutex_unlock(&ctlr->output_mutex); + + /* configure the player LEDs */ + for (i = 0; i < JC_NUM_LEDS; i++) { + name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", d_name, + joycon_player_led_names[i]); + if (!name) + return -ENOMEM; + + led = &ctlr->leds[i]; + led->name = name; + led->brightness = ((i + 1) <= input_num) ? LED_ON : LED_OFF; + led->max_brightness = LED_ON; + led->brightness_set_blocking = + joycon_player_led_brightness_set; + led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE; + + ret = devm_led_classdev_register(&hdev->dev, led); + if (ret) { + hid_err(hdev, "Failed registering %s LED\n", led->name); + break; + } + } + if (++input_num > 4) input_num = 1; mutex_unlock(&joycon_input_num_mutex); @@ -813,6 +897,13 @@ static int nintendo_hid_probe(struct hid_device *hdev, mutex_unlock(&ctlr->output_mutex); + /* Initialize the leds */ + ret = joycon_player_leds_create(ctlr); + if (ret) { + hid_err(hdev, "Failed to create leds; ret=%d\n", ret); + goto err_close; + } + ret = joycon_input_create(ctlr); if (ret) { hid_err(hdev, "Failed to create input device; ret=%d\n", ret); From patchwork Thu May 20 22:47:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271465 X-Patchwork-Delegate: jikos@jikos.cz 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 BED02C43461 for ; Thu, 20 May 2021 22:47:34 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9F807613AE for ; Thu, 20 May 2021 22:47:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231865AbhETWsz (ORCPT ); Thu, 20 May 2021 18:48:55 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55766 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231861AbhETWsz (ORCPT ); Thu, 20 May 2021 18:48:55 -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 8FC20C061763 for ; Thu, 20 May 2021 15:47:32 -0700 (PDT) Received: by mail-qk1-x729.google.com with SMTP id i67so17947969qkc.4 for ; Thu, 20 May 2021 15:47:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=cN00VnswZNywU4f8+y/j2s/4eNLee9eWyR/3WpqoxNI=; b=gkbwXylYEQOGd4a4mQG/FskPzcpUD2rVtTA/TVMDUs9aHrp9cLc0R7raoaDF480RQW 15sMF6YsFP8KwYlvRk0rGcpsZ7pFocIzYGbEh793th0IqvOTBbWiEOcX2hQFwNjRCoHu dgsw5hQPtAuWF41Egl6Ci0gQzRs5d4cQbkaW57TOuwS+BwTXPeDqzvRRxTXb2CtoN+HH lLD3H0fDFeFw8KaQ3HXUd/3czcSdIsrZbc6Z94OP+VBAPlo3qcCH1flFoj9NuqaPrQeV /R0ZjSQ1WKLMhtADHBnKZfEM6uesYlWNiwmEugukv4oAxqazVe8KWrWMVEpvk0QRs/fX HtVA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=cN00VnswZNywU4f8+y/j2s/4eNLee9eWyR/3WpqoxNI=; b=Xnp9P1zc6Fu+qaP8KKJbN4EPM/ZpasbERFM5vdrRVvy/DzrdkiDo1OwfNbL1v5Lt4/ remmTfRh/fBnF1XSCPiGTU2Z0z/3fadXaxoKFId58dWdKrOizJMUrfiQ3uPxcpgfj0nr 6uP6+fOtfB2i8lb89BZRklGxiXAxWXZqtrI+i88NO0HSRVjOp1YhIM8ObDOmzqR6tzDa 8nZvNIyuuQbs9TW5dLw/7W1CaWnjXcJu8WQUxZQ4oq9iNuPavTuvFqKxdKgbiygEmYTd gfb8m3kTUdc0RJBrU1QgRQ12i1MlgDutsVn/PzpNhmxTcZQUW5HahrkznLql6nN7Jatw 2P2w== X-Gm-Message-State: AOAM5336ey4WCQjQKpk5NnDbNRWZAKas9tPZY6WDB65BljzfD8jrlFIh yg4kSy9Rex84ai+rZNf+Ld2ibUeQ3XWumA== X-Google-Smtp-Source: ABdhPJzPOah2rrq7y9Z/FSWg64LHJ546HDSQIhJ7doFuTLQAwR6rpwbrBOjtfsVJr3Mc3iLrcUPJFw== X-Received: by 2002:a37:4047:: with SMTP id n68mr7713058qka.440.1621550851531; Thu, 20 May 2021 15:47:31 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:31 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 03/15] HID: nintendo: add power supply support Date: Thu, 20 May 2021 18:47:03 -0400 Message-Id: <20210520224715.680919-4-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org This patch adds power_supply functionality to the switch controller driver for its battery. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/Kconfig | 1 + drivers/hid/hid-nintendo.c | 134 +++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 86a6129d3d89f..6d680aaa2e30a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -724,6 +724,7 @@ config HID_NINTENDO depends on HID depends on NEW_LEDS depends on LEDS_CLASS + select POWER_SUPPLY help Adds support for the Nintendo Switch Joy-Cons and Pro Controller. All controllers support bluetooth, and the Pro Controller also supports diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index c21682b4a2e62..c3c7ea81baa66 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -11,6 +11,7 @@ * https://github.com/MTCKC/ProconXInput * hid-wiimote kernel hid driver * hid-logitech-hidpp driver + * hid-sony driver * * This driver supports the Nintendo Switch Joy-Cons and Pro Controllers. The * Pro Controllers can either be used over USB or Bluetooth. @@ -27,6 +28,7 @@ #include #include #include +#include #include /* @@ -192,6 +194,7 @@ struct joycon_ctlr { struct input_dev *input; struct led_classdev leds[JC_NUM_LEDS]; enum joycon_ctlr_state ctlr_state; + spinlock_t lock; /* The following members are used for synchronous sends/receives */ enum joycon_msg_type msg_type; @@ -209,6 +212,12 @@ struct joycon_ctlr { struct joycon_stick_cal right_stick_cal_x; struct joycon_stick_cal right_stick_cal_y; + /* power supply data */ + struct power_supply *battery; + struct power_supply_desc battery_desc; + u8 battery_capacity; + bool battery_charging; + bool host_powered; }; static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len) @@ -439,9 +448,41 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, struct joycon_input_report *rep) { struct input_dev *dev = ctlr->input; + unsigned long flags; + u8 tmp; u32 btns; u32 id = ctlr->hdev->product; + /* Parse the battery status */ + tmp = rep->bat_con; + spin_lock_irqsave(&ctlr->lock, flags); + ctlr->host_powered = tmp & BIT(0); + ctlr->battery_charging = tmp & BIT(4); + tmp = tmp >> 5; + switch (tmp) { + case 0: /* empty */ + ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + break; + case 1: /* low */ + ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + break; + case 2: /* medium */ + ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + break; + case 3: /* high */ + ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + break; + case 4: /* full */ + ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + break; + default: + ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + hid_warn(ctlr->hdev, "Invalid battery status\n"); + break; + } + spin_unlock_irqrestore(&ctlr->lock, flags); + + /* Parse the buttons and sticks */ btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24); if (id != USB_DEVICE_ID_NINTENDO_JOYCONR) { @@ -741,6 +782,91 @@ static int joycon_player_leds_create(struct joycon_ctlr *ctlr) return 0; } +static int joycon_battery_get_property(struct power_supply *supply, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct joycon_ctlr *ctlr = power_supply_get_drvdata(supply); + unsigned long flags; + int ret = 0; + u8 capacity; + bool charging; + bool powered; + + spin_lock_irqsave(&ctlr->lock, flags); + capacity = ctlr->battery_capacity; + charging = ctlr->battery_charging; + powered = ctlr->host_powered; + spin_unlock_irqrestore(&ctlr->lock, flags); + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = capacity; + break; + case POWER_SUPPLY_PROP_STATUS: + if (charging) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (capacity == POWER_SUPPLY_CAPACITY_LEVEL_FULL && + powered) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property joycon_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_STATUS, +}; + +static int joycon_power_supply_create(struct joycon_ctlr *ctlr) +{ + struct hid_device *hdev = ctlr->hdev; + struct power_supply_config supply_config = { .drv_data = ctlr, }; + const char * const name_fmt = "nintendo_switch_controller_battery_%s"; + int ret = 0; + + /* Set initially to unknown before receiving first input report */ + ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + + /* Configure the battery's description */ + ctlr->battery_desc.properties = joycon_battery_props; + ctlr->battery_desc.num_properties = + ARRAY_SIZE(joycon_battery_props); + ctlr->battery_desc.get_property = joycon_battery_get_property; + ctlr->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; + ctlr->battery_desc.use_for_apm = 0; + ctlr->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL, + name_fmt, + dev_name(&hdev->dev)); + if (!ctlr->battery_desc.name) + return -ENOMEM; + + ctlr->battery = devm_power_supply_register(&hdev->dev, + &ctlr->battery_desc, + &supply_config); + if (IS_ERR(ctlr->battery)) { + ret = PTR_ERR(ctlr->battery); + hid_err(hdev, "Failed to register battery; ret=%d\n", ret); + return ret; + } + + return power_supply_powers(ctlr->battery, &hdev->dev); +} + /* Common handler for parsing inputs */ static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data, int size) @@ -832,6 +958,7 @@ static int nintendo_hid_probe(struct hid_device *hdev, hid_set_drvdata(hdev, ctlr); mutex_init(&ctlr->output_mutex); init_waitqueue_head(&ctlr->wait); + spin_lock_init(&ctlr->lock); ret = hid_parse(hdev); if (ret) { @@ -904,6 +1031,13 @@ static int nintendo_hid_probe(struct hid_device *hdev, goto err_close; } + /* Initialize the battery power supply */ + ret = joycon_power_supply_create(ctlr); + if (ret) { + hid_err(hdev, "Failed to create power_supply; ret=%d\n", ret); + goto err_close; + } + ret = joycon_input_create(ctlr); if (ret) { hid_err(hdev, "Failed to create input device; ret=%d\n", ret); From patchwork Thu May 20 22:47:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271467 X-Patchwork-Delegate: jikos@jikos.cz 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 79910C433ED for ; Thu, 20 May 2021 22:47:35 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 59C3C613AE for ; Thu, 20 May 2021 22:47:35 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231868AbhETWs4 (ORCPT ); Thu, 20 May 2021 18:48:56 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55762 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231861AbhETWs4 (ORCPT ); Thu, 20 May 2021 18:48:56 -0400 Received: from mail-qk1-x736.google.com (mail-qk1-x736.google.com [IPv6:2607:f8b0:4864:20::736]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 70B29C061574 for ; Thu, 20 May 2021 15:47:33 -0700 (PDT) Received: by mail-qk1-x736.google.com with SMTP id k4so6852677qkd.0 for ; Thu, 20 May 2021 15:47:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=9G5vXVhaAjwuE/TnZ8zOM8M2+cjyaSIaMszQtzflk6E=; b=CJaLtmFyz6pkCgEoHzLLKO9/R/eAi6iCA2OdaQw4IMFcwQ0AewAzl/ogrSU6RTEttM o0XKnXi/9G8pWd5vwpu6ZVOtvRtFN9ZqzLF/1jc4h52izx4F4YQ8abUstmfhSQIMpti+ Xy4eE2VE96WyfbReW9Ry1oleHQnWS7eOsysY2aZrVbnCv4UluVYhC/AErq4C24XgdDZK 5jtfVcgUnL9LuJ3FBwu+5hF10lC5WUXGKh3OFr33i5s3Ub1F5sma6yMwfQBeq/4h0Wg4 nCelGu6rtofSNk3AUJ2mZ+kS366m2oXtHOQVwI5C6OqiQj9ISFfhnoePhxgMZQFjH4xz QOxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=9G5vXVhaAjwuE/TnZ8zOM8M2+cjyaSIaMszQtzflk6E=; b=mBW86FHE3ZUHz4arCHys5WaX1eL27yN3gbi7JcCBrBJy2hix2KiyJSDUacwA3XMfKq OSbGUlI6xWBS++j+1AaG4WRg375MajejyUPEr+H5ddj9ikNtLhSeaqlJDbNtzj/fYPo6 sDtnamIP2R1CFZ3IMCn3BdmtE72xJECraFuaJfmXOFAI2Nx24yGKI7VT4Yp8KfXAf4FJ WP3gDoJwGmp1/vf8U22h4RNnntAHSm8RsyMD+GFrv/a9eq9ohDXlhynCyOGOOMBZIxHc z3WjgAAMxICeaKYIVvXr0YcBVQwAn0ksHI8NrtMkaTSROCLFzwACHIkvmaBRFkOM8I05 13vg== X-Gm-Message-State: AOAM532k3NM6uYdWNHMKi1To4zkXPqeZX3qY0dgXabIR4fszp+oQgfv9 KmQO6SRZkQaqok4AGh14nfdbTZwGoQWOnQ== X-Google-Smtp-Source: ABdhPJxYsWURI6Y/FHy7UCLVUAR1wBcI9+0N97wowj2zjTkulWy7r4zyJ3R/L9ptM6sF2ptm0vWCbg== X-Received: by 2002:a05:620a:164f:: with SMTP id c15mr7884817qko.183.1621550852473; Thu, 20 May 2021 15:47:32 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:32 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 04/15] HID: nintendo: add home led support Date: Thu, 20 May 2021 18:47:04 -0400 Message-Id: <20210520224715.680919-5-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 | 69 +++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index c3c7ea81baa66..8e6c7bde91335 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -192,7 +192,8 @@ struct joycon_input_report { struct joycon_ctlr { struct hid_device *hdev; struct input_dev *input; - struct led_classdev leds[JC_NUM_LEDS]; + struct led_classdev leds[JC_NUM_LEDS]; /* player leds */ + struct led_classdev home_led; enum joycon_ctlr_state ctlr_state; spinlock_t lock; @@ -726,6 +727,40 @@ static int joycon_player_led_brightness_set(struct led_classdev *led, return ret; } +static int joycon_home_led_brightness_set(struct led_classdev *led, + enum led_brightness brightness) +{ + struct device *dev = led->dev->parent; + struct hid_device *hdev = to_hid_device(dev); + struct joycon_ctlr *ctlr; + struct joycon_subcmd_request *req; + u8 buffer[sizeof(*req) + 5] = { 0 }; + u8 *data; + int ret; + + ctlr = hid_get_drvdata(hdev); + if (!ctlr) { + hid_err(hdev, "No controller data\n"); + return -ENODEV; + } + + req = (struct joycon_subcmd_request *)buffer; + req->subcmd_id = JC_SUBCMD_SET_HOME_LIGHT; + data = req->data; + data[0] = 0x01; + data[1] = brightness << 4; + data[2] = brightness | (brightness << 4); + data[3] = 0x11; + data[4] = 0x11; + + hid_dbg(hdev, "setting home led brightness\n"); + mutex_lock(&ctlr->output_mutex); + ret = joycon_send_subcmd(ctlr, req, 5); + mutex_unlock(&ctlr->output_mutex); + + return ret; +} + static const char * const joycon_player_led_names[] = { "player1", "player2", @@ -734,7 +769,7 @@ static const char * const joycon_player_led_names[] = { }; static DEFINE_MUTEX(joycon_input_num_mutex); -static int joycon_player_leds_create(struct joycon_ctlr *ctlr) +static int joycon_leds_create(struct joycon_ctlr *ctlr) { struct hid_device *hdev = ctlr->hdev; struct device *dev = &hdev->dev; @@ -771,7 +806,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; } } @@ -779,6 +814,32 @@ static int joycon_player_leds_create(struct joycon_ctlr *ctlr) input_num = 1; mutex_unlock(&joycon_input_num_mutex); + /* configure the home LED */ + if (ctlr->hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) { + name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", d_name, "home"); + if (!name) + return -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; } @@ -1025,7 +1086,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 Thu May 20 22:47:05 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271471 X-Patchwork-Delegate: jikos@jikos.cz 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 133CFC43462 for ; Thu, 20 May 2021 22:47:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E7887613AC for ; Thu, 20 May 2021 22:47:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231861AbhETWs6 (ORCPT ); Thu, 20 May 2021 18:48:58 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55774 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231871AbhETWs5 (ORCPT ); Thu, 20 May 2021 18:48:57 -0400 Received: from mail-qk1-x72f.google.com (mail-qk1-x72f.google.com [IPv6:2607:f8b0:4864:20::72f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9DDA6C061574 for ; Thu, 20 May 2021 15:47:34 -0700 (PDT) Received: by mail-qk1-x72f.google.com with SMTP id c20so17973666qkm.3 for ; Thu, 20 May 2021 15:47:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=Vz+wioIL/cAWoxb637DkKzgNlvkbEECIFFP7ioB6xeE=; b=d3ytg/z1koO1y9JcrWvvs5HxMHPeUReBp9wxsF4bBvWvv8CFSaMRUlr7v9+OTkuPwm rr/jGMuFBrZ1RcXFDUghWMkpflSVwQRnCCqCxBjwbqXMQFy2ZUzKE0y9i/cCbIryEI97 GhsA8W92/TET+XjYs1CelVpzDWQsBUeDQkLQidruQXRpDfrnn20VgvuashkZPcNqeE3f 2muP7GNctkoZW6Gw78WacaSfIJPAL4rzZH9iHdeDsRGe8748KSy8nOMOd29+eT2494K6 U6mR0/8CzSpGQVaTwEcalRiOENj05ds9T/yn5hTdrrmt2S8MdjbChm2guY45UW+iWioh IaLQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Vz+wioIL/cAWoxb637DkKzgNlvkbEECIFFP7ioB6xeE=; b=si92sGyL2+tBTVufAWyzDJfM4kAKKmx6ARkArpDcjESlnGR21AInaSPI2Wu1b/QLga PUtuN9O3GoC2L8Hj1rrPiyCPFndEub/eyodwtZSSvp9GyiCR+3ALch/hItyWMckTmpE+ 7k3kLnL0E2zrJ1yCwlOaLZKhrupIt7r29LHwwdwWELt5PCAgaZ3ZXOo0Yw5jPvEUPkyn Ma7c/8bMPFA5FxWNJez1XGL9QZyFyI2yxNwGaE3VEvkvpDYUHVKv/OVfhdCPqYvPF0dP 3m8PTKtXmumGFPOIqhLvW3g/cHaYNYycs7aBlq3DQyfVqexSsRtUcJVX2WtTjGfGGw1f KjSw== X-Gm-Message-State: AOAM531jN19eNOHdBWYshgC9XU21AhyoFJXfFwThWW3sFKjcO4ySCbVU 9kFi3sgrnEPNTmTjooOQ7ldA/6P9HSDQdg== X-Google-Smtp-Source: ABdhPJyGERc8xROrPHM/ADDNDmRczkVNoDp/ZKLsuvwdoPks1UC5I4gMwHrqExs/bjViJVhzjXyG4g== X-Received: by 2002:a05:620a:4515:: with SMTP id t21mr7808457qkp.442.1621550853403; Thu, 20 May 2021 15:47:33 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:32 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 05/15] HID: nintendo: add rumble support Date: Thu, 20 May 2021 18:47:05 -0400 Message-Id: <20210520224715.680919-6-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 controller rumble. The ff_effect weak magnitude is associated with the pro controller's right motor (or with a right joy-con). The strong magnitude is associated with the pro's left motor (or a left joy-con). The rumble data is sent periodically (currently configured for every 50 milliseconds). If the controller receives no rumble data for too long a time period, it will stop vibrating. The data is also sent every time joycon_set_rumble is called to avoid latency of up to 50ms. Because the rumble subcommands are sent in a deferred workqueue (they can't be sent in the play_effect function due to the hid send sleeping), the effects are queued. This ensures that no rumble effect is missed due to them arriving in too quick of succession. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/Kconfig | 10 ++ drivers/hid/hid-nintendo.c | 349 ++++++++++++++++++++++++++++++++++++- 2 files changed, 356 insertions(+), 3 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 6d680aaa2e30a..6c75817497095 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -733,6 +733,16 @@ config HID_NINTENDO To compile this driver as a module, choose M here: the module will be called hid-nintendo. +config NINTENDO_FF + bool "Nintendo Switch controller force feedback support" + depends on HID_NINTENDO + select INPUT_FF_MEMLESS + help + Say Y here if you have a Nintendo Switch controller and want to enable + force feedback support for it. This works for both joy-cons and the pro + controller. For the pro controller, both rumble motors can be controlled + individually. + config HID_NTI tristate "NTI keyboard adapters" help diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 8e6c7bde91335..7236ec301de9e 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -9,6 +9,7 @@ * https://gitlab.com/pjranki/joycon-linux-kernel (Peter Rankin) * https://github.com/FrotBot/SwitchProConLinuxUSB * https://github.com/MTCKC/ProconXInput + * https://github.com/Davidobot/BetterJoyForCemu * hid-wiimote kernel hid driver * hid-logitech-hidpp driver * hid-sony driver @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +112,120 @@ static const u16 JC_MAX_DPAD_MAG = 1; static const u16 JC_DPAD_FUZZ /*= 0*/; static const u16 JC_DPAD_FLAT /*= 0*/; +/* frequency/amplitude tables for rumble */ +struct joycon_rumble_freq_data { + u16 high; + u8 low; + u16 freq; /* Hz*/ +}; + +struct joycon_rumble_amp_data { + u8 high; + u16 low; + u16 amp; +}; + +/* + * These tables are from + * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + */ +static const struct joycon_rumble_freq_data joycon_rumble_frequencies[] = { + /* high, low, freq */ + { 0x0000, 0x01, 41 }, { 0x0000, 0x02, 42 }, { 0x0000, 0x03, 43 }, + { 0x0000, 0x04, 44 }, { 0x0000, 0x05, 45 }, { 0x0000, 0x06, 46 }, + { 0x0000, 0x07, 47 }, { 0x0000, 0x08, 48 }, { 0x0000, 0x09, 49 }, + { 0x0000, 0x0A, 50 }, { 0x0000, 0x0B, 51 }, { 0x0000, 0x0C, 52 }, + { 0x0000, 0x0D, 53 }, { 0x0000, 0x0E, 54 }, { 0x0000, 0x0F, 55 }, + { 0x0000, 0x10, 57 }, { 0x0000, 0x11, 58 }, { 0x0000, 0x12, 59 }, + { 0x0000, 0x13, 60 }, { 0x0000, 0x14, 62 }, { 0x0000, 0x15, 63 }, + { 0x0000, 0x16, 64 }, { 0x0000, 0x17, 66 }, { 0x0000, 0x18, 67 }, + { 0x0000, 0x19, 69 }, { 0x0000, 0x1A, 70 }, { 0x0000, 0x1B, 72 }, + { 0x0000, 0x1C, 73 }, { 0x0000, 0x1D, 75 }, { 0x0000, 0x1e, 77 }, + { 0x0000, 0x1f, 78 }, { 0x0000, 0x20, 80 }, { 0x0400, 0x21, 82 }, + { 0x0800, 0x22, 84 }, { 0x0c00, 0x23, 85 }, { 0x1000, 0x24, 87 }, + { 0x1400, 0x25, 89 }, { 0x1800, 0x26, 91 }, { 0x1c00, 0x27, 93 }, + { 0x2000, 0x28, 95 }, { 0x2400, 0x29, 97 }, { 0x2800, 0x2a, 99 }, + { 0x2c00, 0x2b, 102 }, { 0x3000, 0x2c, 104 }, { 0x3400, 0x2d, 106 }, + { 0x3800, 0x2e, 108 }, { 0x3c00, 0x2f, 111 }, { 0x4000, 0x30, 113 }, + { 0x4400, 0x31, 116 }, { 0x4800, 0x32, 118 }, { 0x4c00, 0x33, 121 }, + { 0x5000, 0x34, 123 }, { 0x5400, 0x35, 126 }, { 0x5800, 0x36, 129 }, + { 0x5c00, 0x37, 132 }, { 0x6000, 0x38, 135 }, { 0x6400, 0x39, 137 }, + { 0x6800, 0x3a, 141 }, { 0x6c00, 0x3b, 144 }, { 0x7000, 0x3c, 147 }, + { 0x7400, 0x3d, 150 }, { 0x7800, 0x3e, 153 }, { 0x7c00, 0x3f, 157 }, + { 0x8000, 0x40, 160 }, { 0x8400, 0x41, 164 }, { 0x8800, 0x42, 167 }, + { 0x8c00, 0x43, 171 }, { 0x9000, 0x44, 174 }, { 0x9400, 0x45, 178 }, + { 0x9800, 0x46, 182 }, { 0x9c00, 0x47, 186 }, { 0xa000, 0x48, 190 }, + { 0xa400, 0x49, 194 }, { 0xa800, 0x4a, 199 }, { 0xac00, 0x4b, 203 }, + { 0xb000, 0x4c, 207 }, { 0xb400, 0x4d, 212 }, { 0xb800, 0x4e, 217 }, + { 0xbc00, 0x4f, 221 }, { 0xc000, 0x50, 226 }, { 0xc400, 0x51, 231 }, + { 0xc800, 0x52, 236 }, { 0xcc00, 0x53, 241 }, { 0xd000, 0x54, 247 }, + { 0xd400, 0x55, 252 }, { 0xd800, 0x56, 258 }, { 0xdc00, 0x57, 263 }, + { 0xe000, 0x58, 269 }, { 0xe400, 0x59, 275 }, { 0xe800, 0x5a, 281 }, + { 0xec00, 0x5b, 287 }, { 0xf000, 0x5c, 293 }, { 0xf400, 0x5d, 300 }, + { 0xf800, 0x5e, 306 }, { 0xfc00, 0x5f, 313 }, { 0x0001, 0x60, 320 }, + { 0x0401, 0x61, 327 }, { 0x0801, 0x62, 334 }, { 0x0c01, 0x63, 341 }, + { 0x1001, 0x64, 349 }, { 0x1401, 0x65, 357 }, { 0x1801, 0x66, 364 }, + { 0x1c01, 0x67, 372 }, { 0x2001, 0x68, 381 }, { 0x2401, 0x69, 389 }, + { 0x2801, 0x6a, 397 }, { 0x2c01, 0x6b, 406 }, { 0x3001, 0x6c, 415 }, + { 0x3401, 0x6d, 424 }, { 0x3801, 0x6e, 433 }, { 0x3c01, 0x6f, 443 }, + { 0x4001, 0x70, 453 }, { 0x4401, 0x71, 462 }, { 0x4801, 0x72, 473 }, + { 0x4c01, 0x73, 483 }, { 0x5001, 0x74, 494 }, { 0x5401, 0x75, 504 }, + { 0x5801, 0x76, 515 }, { 0x5c01, 0x77, 527 }, { 0x6001, 0x78, 538 }, + { 0x6401, 0x79, 550 }, { 0x6801, 0x7a, 562 }, { 0x6c01, 0x7b, 574 }, + { 0x7001, 0x7c, 587 }, { 0x7401, 0x7d, 600 }, { 0x7801, 0x7e, 613 }, + { 0x7c01, 0x7f, 626 }, { 0x8001, 0x00, 640 }, { 0x8401, 0x00, 654 }, + { 0x8801, 0x00, 668 }, { 0x8c01, 0x00, 683 }, { 0x9001, 0x00, 698 }, + { 0x9401, 0x00, 713 }, { 0x9801, 0x00, 729 }, { 0x9c01, 0x00, 745 }, + { 0xa001, 0x00, 761 }, { 0xa401, 0x00, 778 }, { 0xa801, 0x00, 795 }, + { 0xac01, 0x00, 812 }, { 0xb001, 0x00, 830 }, { 0xb401, 0x00, 848 }, + { 0xb801, 0x00, 867 }, { 0xbc01, 0x00, 886 }, { 0xc001, 0x00, 905 }, + { 0xc401, 0x00, 925 }, { 0xc801, 0x00, 945 }, { 0xcc01, 0x00, 966 }, + { 0xd001, 0x00, 987 }, { 0xd401, 0x00, 1009 }, { 0xd801, 0x00, 1031 }, + { 0xdc01, 0x00, 1053 }, { 0xe001, 0x00, 1076 }, { 0xe401, 0x00, 1100 }, + { 0xe801, 0x00, 1124 }, { 0xec01, 0x00, 1149 }, { 0xf001, 0x00, 1174 }, + { 0xf401, 0x00, 1199 }, { 0xf801, 0x00, 1226 }, { 0xfc01, 0x00, 1253 } +}; + +#define joycon_max_rumble_amp (1003) +static const struct joycon_rumble_amp_data joycon_rumble_amplitudes[] = { + /* high, low, amp */ + { 0x00, 0x0040, 0 }, + { 0x02, 0x8040, 10 }, { 0x04, 0x0041, 12 }, { 0x06, 0x8041, 14 }, + { 0x08, 0x0042, 17 }, { 0x0a, 0x8042, 20 }, { 0x0c, 0x0043, 24 }, + { 0x0e, 0x8043, 28 }, { 0x10, 0x0044, 33 }, { 0x12, 0x8044, 40 }, + { 0x14, 0x0045, 47 }, { 0x16, 0x8045, 56 }, { 0x18, 0x0046, 67 }, + { 0x1a, 0x8046, 80 }, { 0x1c, 0x0047, 95 }, { 0x1e, 0x8047, 112 }, + { 0x20, 0x0048, 117 }, { 0x22, 0x8048, 123 }, { 0x24, 0x0049, 128 }, + { 0x26, 0x8049, 134 }, { 0x28, 0x004a, 140 }, { 0x2a, 0x804a, 146 }, + { 0x2c, 0x004b, 152 }, { 0x2e, 0x804b, 159 }, { 0x30, 0x004c, 166 }, + { 0x32, 0x804c, 173 }, { 0x34, 0x004d, 181 }, { 0x36, 0x804d, 189 }, + { 0x38, 0x004e, 198 }, { 0x3a, 0x804e, 206 }, { 0x3c, 0x004f, 215 }, + { 0x3e, 0x804f, 225 }, { 0x40, 0x0050, 230 }, { 0x42, 0x8050, 235 }, + { 0x44, 0x0051, 240 }, { 0x46, 0x8051, 245 }, { 0x48, 0x0052, 251 }, + { 0x4a, 0x8052, 256 }, { 0x4c, 0x0053, 262 }, { 0x4e, 0x8053, 268 }, + { 0x50, 0x0054, 273 }, { 0x52, 0x8054, 279 }, { 0x54, 0x0055, 286 }, + { 0x56, 0x8055, 292 }, { 0x58, 0x0056, 298 }, { 0x5a, 0x8056, 305 }, + { 0x5c, 0x0057, 311 }, { 0x5e, 0x8057, 318 }, { 0x60, 0x0058, 325 }, + { 0x62, 0x8058, 332 }, { 0x64, 0x0059, 340 }, { 0x66, 0x8059, 347 }, + { 0x68, 0x005a, 355 }, { 0x6a, 0x805a, 362 }, { 0x6c, 0x005b, 370 }, + { 0x6e, 0x805b, 378 }, { 0x70, 0x005c, 387 }, { 0x72, 0x805c, 395 }, + { 0x74, 0x005d, 404 }, { 0x76, 0x805d, 413 }, { 0x78, 0x005e, 422 }, + { 0x7a, 0x805e, 431 }, { 0x7c, 0x005f, 440 }, { 0x7e, 0x805f, 450 }, + { 0x80, 0x0060, 460 }, { 0x82, 0x8060, 470 }, { 0x84, 0x0061, 480 }, + { 0x86, 0x8061, 491 }, { 0x88, 0x0062, 501 }, { 0x8a, 0x8062, 512 }, + { 0x8c, 0x0063, 524 }, { 0x8e, 0x8063, 535 }, { 0x90, 0x0064, 547 }, + { 0x92, 0x8064, 559 }, { 0x94, 0x0065, 571 }, { 0x96, 0x8065, 584 }, + { 0x98, 0x0066, 596 }, { 0x9a, 0x8066, 609 }, { 0x9c, 0x0067, 623 }, + { 0x9e, 0x8067, 636 }, { 0xa0, 0x0068, 650 }, { 0xa2, 0x8068, 665 }, + { 0xa4, 0x0069, 679 }, { 0xa6, 0x8069, 694 }, { 0xa8, 0x006a, 709 }, + { 0xaa, 0x806a, 725 }, { 0xac, 0x006b, 741 }, { 0xae, 0x806b, 757 }, + { 0xb0, 0x006c, 773 }, { 0xb2, 0x806c, 790 }, { 0xb4, 0x006d, 808 }, + { 0xb6, 0x806d, 825 }, { 0xb8, 0x006e, 843 }, { 0xba, 0x806e, 862 }, + { 0xbc, 0x006f, 881 }, { 0xbe, 0x806f, 900 }, { 0xc0, 0x0070, 920 }, + { 0xc2, 0x8070, 940 }, { 0xc4, 0x0071, 960 }, { 0xc6, 0x8071, 981 }, + { 0xc8, 0x0072, joycon_max_rumble_amp } +}; + /* States for controller state machine */ enum joycon_ctlr_state { JOYCON_CTLR_STATE_INIT, @@ -187,6 +303,12 @@ struct joycon_input_report { #define JC_MAX_RESP_SIZE (sizeof(struct joycon_input_report) + 35) #define JC_NUM_LEDS 4 +#define JC_RUMBLE_DATA_SIZE 8 +#define JC_RUMBLE_QUEUE_SIZE 8 + +static const u16 JC_RUMBLE_DFLT_LOW_FREQ = 160; +static const u16 JC_RUMBLE_DFLT_HIGH_FREQ = 320; +static const u16 JC_RUMBLE_PERIOD_MS = 50; /* Each physical controller is associated with a joycon_ctlr struct */ struct joycon_ctlr { @@ -219,6 +341,18 @@ struct joycon_ctlr { u8 battery_capacity; bool battery_charging; bool host_powered; + + /* rumble */ + u8 rumble_data[JC_RUMBLE_QUEUE_SIZE][JC_RUMBLE_DATA_SIZE]; + int rumble_queue_head; + int rumble_queue_tail; + struct workqueue_struct *rumble_queue; + struct work_struct rumble_worker; + unsigned int rumble_msecs; + u16 rumble_ll_freq; + u16 rumble_lh_freq; + u16 rumble_rl_freq; + u16 rumble_rh_freq; }; static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len) @@ -275,6 +409,12 @@ static int joycon_send_subcmd(struct joycon_ctlr *ctlr, size_t data_len) { int ret; + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + memcpy(subcmd->rumble_data, ctlr->rumble_data[ctlr->rumble_queue_tail], + JC_RUMBLE_DATA_SIZE); + spin_unlock_irqrestore(&ctlr->lock, flags); subcmd->output_id = JC_OUTPUT_RUMBLE_AND_SUBCMD; subcmd->packet_num = ctlr->subcmd_num; @@ -427,6 +567,19 @@ static int joycon_set_report_mode(struct joycon_ctlr *ctlr) return joycon_send_subcmd(ctlr, req, 1); } +static int joycon_enable_rumble(struct joycon_ctlr *ctlr) +{ + struct joycon_subcmd_request *req; + u8 buffer[sizeof(*req) + 1] = { 0 }; + + req = (struct joycon_subcmd_request *)buffer; + req->subcmd_id = JC_SUBCMD_ENABLE_VIBRATION; + req->data[0] = 0x01; /* note: 0x00 would disable */ + + hid_dbg(ctlr->hdev, "enabling rumble\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; @@ -453,10 +606,15 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, u8 tmp; u32 btns; u32 id = ctlr->hdev->product; + unsigned long msecs = jiffies_to_msecs(jiffies); + + spin_lock_irqsave(&ctlr->lock, flags); + if (IS_ENABLED(CONFIG_NINTENDO_FF) && rep->vibrator_report && + (msecs - ctlr->rumble_msecs) >= JC_RUMBLE_PERIOD_MS) + queue_work(ctlr->rumble_queue, &ctlr->rumble_worker); /* Parse the battery status */ tmp = rep->bat_con; - spin_lock_irqsave(&ctlr->lock, flags); ctlr->host_powered = tmp & BIT(0); ctlr->battery_charging = tmp & BIT(4); tmp = tmp >> 5; @@ -579,6 +737,161 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, input_sync(dev); } +static void joycon_rumble_worker(struct work_struct *work) +{ + struct joycon_ctlr *ctlr = container_of(work, struct joycon_ctlr, + rumble_worker); + unsigned long flags; + bool again = true; + int ret; + + while (again) { + mutex_lock(&ctlr->output_mutex); + ret = joycon_enable_rumble(ctlr); + mutex_unlock(&ctlr->output_mutex); + if (ret < 0) + hid_warn(ctlr->hdev, "Failed to set rumble; e=%d", ret); + + spin_lock_irqsave(&ctlr->lock, flags); + ctlr->rumble_msecs = jiffies_to_msecs(jiffies); + if (ctlr->rumble_queue_tail != ctlr->rumble_queue_head) { + if (++ctlr->rumble_queue_tail >= JC_RUMBLE_QUEUE_SIZE) + ctlr->rumble_queue_tail = 0; + } else { + again = false; + } + spin_unlock_irqrestore(&ctlr->lock, flags); + } +} + +#if IS_ENABLED(CONFIG_NINTENDO_FF) +static struct joycon_rumble_freq_data joycon_find_rumble_freq(u16 freq) +{ + const size_t length = ARRAY_SIZE(joycon_rumble_frequencies); + const struct joycon_rumble_freq_data *data = joycon_rumble_frequencies; + int i = 0; + + if (freq > data[0].freq) { + for (i = 1; i < length - 1; i++) { + if (freq > data[i - 1].freq && freq <= data[i].freq) + break; + } + } + + return data[i]; +} + +static struct joycon_rumble_amp_data joycon_find_rumble_amp(u16 amp) +{ + const size_t length = ARRAY_SIZE(joycon_rumble_amplitudes); + const struct joycon_rumble_amp_data *data = joycon_rumble_amplitudes; + int i = 0; + + if (amp > data[0].amp) { + for (i = 1; i < length - 1; i++) { + if (amp > data[i - 1].amp && amp <= data[i].amp) + break; + } + } + + return data[i]; +} + +static void joycon_encode_rumble(u8 *data, u16 freq_low, u16 freq_high, u16 amp) +{ + struct joycon_rumble_freq_data freq_data_low; + struct joycon_rumble_freq_data freq_data_high; + struct joycon_rumble_amp_data amp_data; + + freq_data_low = joycon_find_rumble_freq(freq_low); + freq_data_high = joycon_find_rumble_freq(freq_high); + amp_data = joycon_find_rumble_amp(amp); + + data[0] = (freq_data_high.high >> 8) & 0xFF; + data[1] = (freq_data_high.high & 0xFF) + amp_data.high; + data[2] = freq_data_low.low + ((amp_data.low >> 8) & 0xFF); + data[3] = amp_data.low & 0xFF; +} + +static const u16 JOYCON_MAX_RUMBLE_HIGH_FREQ = 1253; +static const u16 JOYCON_MIN_RUMBLE_HIGH_FREQ = 82; +static const u16 JOYCON_MAX_RUMBLE_LOW_FREQ = 626; +static const u16 JOYCON_MIN_RUMBLE_LOW_FREQ = 41; + +static void joycon_clamp_rumble_freqs(struct joycon_ctlr *ctlr) +{ + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + ctlr->rumble_ll_freq = clamp(ctlr->rumble_ll_freq, + JOYCON_MIN_RUMBLE_LOW_FREQ, + JOYCON_MAX_RUMBLE_LOW_FREQ); + ctlr->rumble_lh_freq = clamp(ctlr->rumble_lh_freq, + JOYCON_MIN_RUMBLE_HIGH_FREQ, + JOYCON_MAX_RUMBLE_HIGH_FREQ); + ctlr->rumble_rl_freq = clamp(ctlr->rumble_rl_freq, + JOYCON_MIN_RUMBLE_LOW_FREQ, + JOYCON_MAX_RUMBLE_LOW_FREQ); + ctlr->rumble_rh_freq = clamp(ctlr->rumble_rh_freq, + JOYCON_MIN_RUMBLE_HIGH_FREQ, + JOYCON_MAX_RUMBLE_HIGH_FREQ); + spin_unlock_irqrestore(&ctlr->lock, flags); +} + +static int joycon_set_rumble(struct joycon_ctlr *ctlr, u16 amp_r, u16 amp_l, + bool schedule_now) +{ + u8 data[JC_RUMBLE_DATA_SIZE]; + u16 amp; + u16 freq_r_low; + u16 freq_r_high; + u16 freq_l_low; + u16 freq_l_high; + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + freq_r_low = ctlr->rumble_rl_freq; + freq_r_high = ctlr->rumble_rh_freq; + freq_l_low = ctlr->rumble_ll_freq; + freq_l_high = ctlr->rumble_lh_freq; + spin_unlock_irqrestore(&ctlr->lock, flags); + + /* right joy-con */ + amp = amp_r * (u32)joycon_max_rumble_amp / 65535; + joycon_encode_rumble(data + 4, freq_r_low, freq_r_high, amp); + + /* left joy-con */ + amp = amp_l * (u32)joycon_max_rumble_amp / 65535; + joycon_encode_rumble(data, freq_l_low, freq_l_high, amp); + + spin_lock_irqsave(&ctlr->lock, flags); + if (++ctlr->rumble_queue_head >= JC_RUMBLE_QUEUE_SIZE) + ctlr->rumble_queue_head = 0; + memcpy(ctlr->rumble_data[ctlr->rumble_queue_head], data, + JC_RUMBLE_DATA_SIZE); + spin_unlock_irqrestore(&ctlr->lock, flags); + + /* don't wait for the periodic send (reduces latency) */ + if (schedule_now) + queue_work(ctlr->rumble_queue, &ctlr->rumble_worker); + + return 0; +} + +static int joycon_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct joycon_ctlr *ctlr = input_get_drvdata(dev); + + if (effect->type != FF_RUMBLE) + return 0; + + return joycon_set_rumble(ctlr, + effect->u.rumble.weak_magnitude, + effect->u.rumble.strong_magnitude, + true); +} +#endif /* IS_ENABLED(CONFIG_NINTENDO_FF) */ static const unsigned int joycon_button_inputs_l[] = { BTN_SELECT, BTN_Z, BTN_THUMBL, @@ -682,6 +995,19 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) input_set_capability(ctlr->input, EV_KEY, BTN_TL2); } +#if IS_ENABLED(CONFIG_NINTENDO_FF) + /* set up rumble */ + input_set_capability(ctlr->input, EV_FF, FF_RUMBLE); + input_ff_create_memless(ctlr->input, NULL, joycon_play_effect); + ctlr->rumble_ll_freq = JC_RUMBLE_DFLT_LOW_FREQ; + ctlr->rumble_lh_freq = JC_RUMBLE_DFLT_HIGH_FREQ; + ctlr->rumble_rl_freq = JC_RUMBLE_DFLT_LOW_FREQ; + ctlr->rumble_rh_freq = JC_RUMBLE_DFLT_HIGH_FREQ; + joycon_clamp_rumble_freqs(ctlr); + joycon_set_rumble(ctlr, 0, 0, false); + ctlr->rumble_msecs = jiffies_to_msecs(jiffies); +#endif + ret = input_register_device(ctlr->input); if (ret) return ret; @@ -1016,21 +1342,26 @@ static int nintendo_hid_probe(struct hid_device *hdev, ctlr->hdev = hdev; ctlr->ctlr_state = JOYCON_CTLR_STATE_INIT; + ctlr->rumble_queue_head = JC_RUMBLE_QUEUE_SIZE - 1; + ctlr->rumble_queue_tail = 0; hid_set_drvdata(hdev, ctlr); mutex_init(&ctlr->output_mutex); init_waitqueue_head(&ctlr->wait); spin_lock_init(&ctlr->lock); + ctlr->rumble_queue = alloc_workqueue("hid-nintendo-rumble_wq", + WQ_FREEZABLE | WQ_MEM_RECLAIM, 0); + INIT_WORK(&ctlr->rumble_worker, joycon_rumble_worker); ret = hid_parse(hdev); if (ret) { hid_err(hdev, "HID parse failed\n"); - goto err; + goto err_wq; } ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); if (ret) { hid_err(hdev, "HW start failed\n"); - goto err; + goto err_wq; } ret = hid_hw_open(hdev); @@ -1083,6 +1414,13 @@ static int nintendo_hid_probe(struct hid_device *hdev, goto err_mutex; } + /* Enable rumble */ + ret = joycon_enable_rumble(ctlr); + if (ret) { + hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret); + goto err_mutex; + } + mutex_unlock(&ctlr->output_mutex); /* Initialize the leds */ @@ -1116,6 +1454,8 @@ static int nintendo_hid_probe(struct hid_device *hdev, hid_hw_close(hdev); err_stop: hid_hw_stop(hdev); +err_wq: + destroy_workqueue(ctlr->rumble_queue); err: hid_err(hdev, "probe - fail = %d\n", ret); return ret; @@ -1123,7 +1463,10 @@ static int nintendo_hid_probe(struct hid_device *hdev, static void nintendo_hid_remove(struct hid_device *hdev) { + struct joycon_ctlr *ctlr = hid_get_drvdata(hdev); + hid_dbg(hdev, "remove\n"); + destroy_workqueue(ctlr->rumble_queue); hid_hw_close(hdev); hid_hw_stop(hdev); } From patchwork Thu May 20 22:47:06 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271469 X-Patchwork-Delegate: jikos@jikos.cz 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 C1338C43460 for ; Thu, 20 May 2021 22:47:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A29F760200 for ; Thu, 20 May 2021 22:47:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231873AbhETWs5 (ORCPT ); Thu, 20 May 2021 18:48:57 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55782 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231861AbhETWs5 (ORCPT ); Thu, 20 May 2021 18:48:57 -0400 Received: from mail-qk1-x72c.google.com (mail-qk1-x72c.google.com [IPv6:2607:f8b0:4864:20::72c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7ECF1C061761 for ; Thu, 20 May 2021 15:47:35 -0700 (PDT) Received: by mail-qk1-x72c.google.com with SMTP id k4so6852816qkd.0 for ; Thu, 20 May 2021 15:47:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=wi0b8e1Qbmaf9olg3RUDdmwTRlycPyRfgxs2W03I7Vw=; b=cUyB/AKhry3rimF8rfIDmDQwAQLw1pmQ1C8FeBT+zlCN2+VwR69610D3jR26HUppOb 0WmSbRuQhRJ2Qk7hUhxnHvLClPzHlxL/9OcoohLz4gkHOJMP9musl8+ctmWwuNZticKD HmKajSkZAdg/BeQIy0e5bEcm6PhbO1gc0vcFtej/HORFKc5K+4P+zV5YSJBLIDdBdju4 dm8k0HckDa/1EyhkQ8mCL+Ehwi2qtvQgFAzhreqx93DCtmgcuDL2DVvjVnTmT9fjQy8W Bz4oDwhs5u+HAZ+wByiKRlAvxZgMU2HQ+d9I3B2/zes/qVVnpeWRCLJ77U3z7FNSebDu vTKg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=wi0b8e1Qbmaf9olg3RUDdmwTRlycPyRfgxs2W03I7Vw=; b=tXPZ0SzoCroiDotWUnBlAIYbf84hoUdPvaBAYEivoQfDJsAsB4AmHIt+moxfIG8lV/ s8cwLVrf2N5Quyxup5ncpXKRvRgVlmk+eJ0ywmFGZcWxSyRPEsTC894ShHR3I9136vPk +PMI54aameqkP6bEEspC2vN+KIYxKvL04zXmOoCZIbL+LYRAvpuMVfd31ToAVR31NyHs eP0AR+1wx1kSnG3fAN4EjQq4yV3dfXty/bnSSy9oYND53BGpg9iMYvc/l5yiGcCY+Cv7 bkCkX5feSlJB2PS9CQJOgfPB9ARKoVO3Dfm065KSvo8urrzeZkEyJ0vgx+B33JVY9oiY KvvA== X-Gm-Message-State: AOAM531hGLX+xB+XfexVvVfqxuGmDHaN3UFxH0nQJqLsRG9e2hXPf/Mq 7Y1UMwtZwvXD+dReBYOH7rL/FBOMCLvIJA== X-Google-Smtp-Source: ABdhPJymAaGwWcLM83SJAtMXlxxUTQ4QqDbZsVntusEtKeOmwyu5Ad9R7adevTRUQ+TGW6bCMOIXfg== X-Received: by 2002:a05:620a:2149:: with SMTP id m9mr8500540qkm.284.1621550854506; Thu, 20 May 2021 15:47:34 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:33 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 06/15] HID: nintendo: improve subcommand reliability Date: Thu, 20 May 2021 18:47:06 -0400 Message-Id: <20210520224715.680919-7-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org The controller occasionally doesn't respond to subcommands. It appears that it's dropping them. To improve reliability, this patch attempts one retry in the case of a synchronous send timeout. In testing, this has resolved all timeout failures (most common for LED setting and rumble setting subcommands). The 1 second timeout is excessively long for rumble and LED subcommands, so the timeout has been made a param for joycon_hid_send_sync. Most subcommands continue to use the 1s timeout, since they can result in long response times. Rumble and LED setting subcommands have been reduced to 250ms, since response times for them are much quicker (and this significantly reduces the observable impact in the case of a retry being required). Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-nintendo.c | 66 ++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 7236ec301de9e..f475d7f3f63f0 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -370,27 +370,45 @@ static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len) return ret; } -static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len) +static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len, + u32 timeout) { int ret; + int tries = 2; - ret = __joycon_hid_send(ctlr->hdev, data, len); - if (ret < 0) { - memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE); - return ret; - } + /* + * The controller occasionally seems to drop subcommands. In testing, + * doing one retry after a timeout appears to always work. + */ + while (tries--) { + ret = __joycon_hid_send(ctlr->hdev, data, len); + if (ret < 0) { + memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE); + return ret; + } - if (!wait_event_timeout(ctlr->wait, ctlr->received_resp, HZ)) { - hid_dbg(ctlr->hdev, "synchronous send/receive timed out\n"); - memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE); - return -ETIMEDOUT; + ret = wait_event_timeout(ctlr->wait, ctlr->received_resp, + timeout); + if (!ret) { + hid_dbg(ctlr->hdev, + "synchronous send/receive timed out\n"); + if (tries) { + hid_dbg(ctlr->hdev, + "retrying sync send after timeout\n"); + } + memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE); + ret = -ETIMEDOUT; + } else { + ret = 0; + break; + } } ctlr->received_resp = false; - return 0; + return ret; } -static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 cmd) +static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 cmd, u32 timeout) { int ret; u8 buf[2] = {JC_OUTPUT_USB_CMD}; @@ -398,7 +416,7 @@ static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 cmd) buf[1] = cmd; ctlr->usb_ack_match = cmd; ctlr->msg_type = JOYCON_MSG_TYPE_USB; - ret = joycon_hid_send_sync(ctlr, buf, sizeof(buf)); + ret = joycon_hid_send_sync(ctlr, buf, sizeof(buf), timeout); if (ret) hid_dbg(ctlr->hdev, "send usb command failed; ret=%d\n", ret); return ret; @@ -406,7 +424,7 @@ static int joycon_send_usb(struct joycon_ctlr *ctlr, u8 cmd) static int joycon_send_subcmd(struct joycon_ctlr *ctlr, struct joycon_subcmd_request *subcmd, - size_t data_len) + size_t data_len, u32 timeout) { int ret; unsigned long flags; @@ -424,7 +442,7 @@ static int joycon_send_subcmd(struct joycon_ctlr *ctlr, ctlr->msg_type = JOYCON_MSG_TYPE_SUBCMD; ret = joycon_hid_send_sync(ctlr, (u8 *)subcmd, - sizeof(*subcmd) + data_len); + sizeof(*subcmd) + data_len, timeout); if (ret < 0) hid_dbg(ctlr->hdev, "send subcommand failed; ret=%d\n", ret); else @@ -443,7 +461,7 @@ static int joycon_set_player_leds(struct joycon_ctlr *ctlr, u8 flash, u8 on) req->data[0] = (flash << 4) | on; hid_dbg(ctlr->hdev, "setting player leds\n"); - return joycon_send_subcmd(ctlr, req, 1); + return joycon_send_subcmd(ctlr, req, 1, HZ/4); } static const u16 DFLT_STICK_CAL_CEN = 2000; @@ -474,7 +492,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr) data[4] = JC_CAL_DATA_SIZE; hid_dbg(ctlr->hdev, "requesting cal data\n"); - ret = joycon_send_subcmd(ctlr, req, 5); + ret = joycon_send_subcmd(ctlr, req, 5, HZ); if (ret) { hid_warn(ctlr->hdev, "Failed to read stick cal, using defaults; ret=%d\n", @@ -564,7 +582,7 @@ static int joycon_set_report_mode(struct joycon_ctlr *ctlr) req->data[0] = 0x30; /* standard, full report mode */ hid_dbg(ctlr->hdev, "setting controller report mode\n"); - return joycon_send_subcmd(ctlr, req, 1); + return joycon_send_subcmd(ctlr, req, 1, HZ); } static int joycon_enable_rumble(struct joycon_ctlr *ctlr) @@ -577,7 +595,7 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr) req->data[0] = 0x01; /* note: 0x00 would disable */ hid_dbg(ctlr->hdev, "enabling rumble\n"); - return joycon_send_subcmd(ctlr, req, 1); + return joycon_send_subcmd(ctlr, req, 1, HZ/4); } static s32 joycon_map_stick_val(struct joycon_stick_cal *cal, s32 val) @@ -1081,7 +1099,7 @@ static int joycon_home_led_brightness_set(struct led_classdev *led, hid_dbg(hdev, "setting home led brightness\n"); mutex_lock(&ctlr->output_mutex); - ret = joycon_send_subcmd(ctlr, req, 5); + ret = joycon_send_subcmd(ctlr, req, 5, HZ/4); mutex_unlock(&ctlr->output_mutex); return ret; @@ -1376,16 +1394,16 @@ static int nintendo_hid_probe(struct hid_device *hdev, mutex_lock(&ctlr->output_mutex); /* if handshake command fails, assume ble pro controller */ if (hdev->product == USB_DEVICE_ID_NINTENDO_PROCON && - !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE)) { + !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) { hid_dbg(hdev, "detected USB controller\n"); /* set baudrate for improved latency */ - ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M); + ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M, HZ); if (ret) { hid_err(hdev, "Failed to set baudrate; ret=%d\n", ret); goto err_mutex; } /* handshake */ - ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE); + ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ); if (ret) { hid_err(hdev, "Failed handshake; ret=%d\n", ret); goto err_mutex; @@ -1394,7 +1412,7 @@ static int nintendo_hid_probe(struct hid_device *hdev, * Set no timeout (to keep controller in USB mode). * This doesn't send a response, so ignore the timeout. */ - joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT); + joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT, HZ/10); } /* get controller calibration data, and parse it */ From patchwork Thu May 20 22:47:07 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271473 X-Patchwork-Delegate: jikos@jikos.cz 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 AE8C5C433B4 for ; Thu, 20 May 2021 22:47:38 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 938EF60200 for ; Thu, 20 May 2021 22:47:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231874AbhETWs7 (ORCPT ); Thu, 20 May 2021 18:48:59 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55786 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231871AbhETWs7 (ORCPT ); Thu, 20 May 2021 18:48:59 -0400 Received: from mail-qk1-x72e.google.com (mail-qk1-x72e.google.com [IPv6:2607:f8b0:4864:20::72e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 74660C061574 for ; Thu, 20 May 2021 15:47:36 -0700 (PDT) Received: by mail-qk1-x72e.google.com with SMTP id k4so6852886qkd.0 for ; Thu, 20 May 2021 15:47:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=NLQyHi5Vmp3PDas+1SUT6QWeiQUDJNdil/D9GYpM3ww=; b=bLbtc17q+skes7HTuSb8OHmhYqhwulTY87VCMAS1sRNZogRrw/YuF9ZJutiVIO00wk yta8L51US/eBtwcjaoO047Yo9uGYkT1srUldABzbKAmR5OyvX5XtQSMMxTP9NfBEhcFH let7d+5XxjNt7Rfvo9BdotHC8U/9uCjKMmFry1RXagt694L8e2cUI6oPXpNkFIGPtpt9 Hh0ePzbf2nOwKkAyaIcMFpxibVSUKQt7t0lqRHWvM2orVLNsRYx+2G9dOKFtNcYzz0R+ 5riJ839CEqTsfDVsLW36qEcXJKFzW02E1ZXsK46EC+OPy6oowoGCs5hMgQla9JmneagT JXbw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=NLQyHi5Vmp3PDas+1SUT6QWeiQUDJNdil/D9GYpM3ww=; b=mYoe5oZQAjCBorMtROoAiTdOlZMOCKI+WYM/6ivT1uIR1DfapOguaiosRrVoSuh19d Jhm4b2Bla2ZX9ArsoPFF3kXp+HMGHlQ/DltZqkaB2trnolryf9yS6ACuz1FfMXm/bSTj gDokBP6BPWIM+tfgm4zXxNQRcK2v1REXjsd8I4ehYIuP+4BKDvB07HdBRdlmM1gK9JtL LJaOOxLDE/pZNCiFCvz2KK9YVOCtjWJuxO7c8kQDOmfpytiooc+B/ZktdBrgyZtHea+S WGdEO84TaJWYJSrb3fQSP0L/X1lxKyZkjg6BffQeR4TMCn/miIr+ZNF92RH2JuvMppYV iyfQ== X-Gm-Message-State: AOAM530N9wMRQBehy+e5X2addekUwGAk9memZ7wN4iS+SsWXNyKqh3eC /IhB/FmyvMJo0eMDogLpuCPgj6UKo1SwCw== X-Google-Smtp-Source: ABdhPJxd5UMHbDAHmKU+o0ZXUxOlzP2miEQFqZpmmthQRc8+9GOLVKj6yUcJzTh2Ea3wgAVNgIiTeg== X-Received: by 2002:ae9:d60f:: with SMTP id r15mr8502826qkk.20.1621550855482; Thu, 20 May 2021 15:47:35 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:34 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 07/15] HID: nintendo: send subcommands after receiving input report Date: Thu, 20 May 2021 18:47:07 -0400 Message-Id: <20210520224715.680919-8-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 f475d7f3f63f0..3c450b378efa3 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -328,6 +328,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; @@ -381,6 +382,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); @@ -753,6 +774,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 Thu May 20 22:47:08 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271475 X-Patchwork-Delegate: jikos@jikos.cz 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 3EE54C43460 for ; Thu, 20 May 2021 22:47:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1A91D613AD for ; Thu, 20 May 2021 22:47:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231879AbhETWtA (ORCPT ); Thu, 20 May 2021 18:49:00 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55792 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231876AbhETWs7 (ORCPT ); Thu, 20 May 2021 18:48:59 -0400 Received: from mail-qk1-x736.google.com (mail-qk1-x736.google.com [IPv6:2607:f8b0:4864:20::736]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 55575C061761 for ; Thu, 20 May 2021 15:47:37 -0700 (PDT) Received: by mail-qk1-x736.google.com with SMTP id h20so2430179qko.11 for ; Thu, 20 May 2021 15:47:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=0BVMlO847dX6VbVdsAQsjLW9yq9tSZMZzpys90okjro=; b=f+lkfBEw25oLcXLr4DcVQVpFueKqkPrJ4CLhwtmIdwHU5GkyI+U7y5EnDkxSGZJThz LMy8npoXkmwQpVHhLSYCczm6XPFWcsDyK5JmOLk4mWw28wIYryrhnPrNnmOul1ZnwXTw p+/XbK8n3mPRTK1oo/nQNf/dS8R5Y1kr97HTDlNqlsPzdVQcZmMhUh66Z9sM6gSwd6XI dZEnoPtmG4AuApXkn1QDh/xTGxSeISlg438TjbcKda4GlZGFdCCgsbOK+Tg9bEn1jDer Tc+rkEhfdvZKXw+Ncs12HiUhBAlnd8i+VPMUkq22CFyzNx9kyi0R4QyjIipsVFAM1ba3 hBdg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=0BVMlO847dX6VbVdsAQsjLW9yq9tSZMZzpys90okjro=; b=gTuy3kvd8MFpQwcXujzi/pL0JQMrM2XHkMD/j0m0jA/Wnr2SCT/erH7AQjm/hUcVK/ i7sJPP107/jASAdjBGbqfdb3a2saOTbmRV2NW0yAiUdcC7n/mYCiDbGjKjwXW3GtGEaj A9xSGvZ5zUikaJOx4QckC+1J9mucqSvVrCgHDwPH2MsLuRM8TgK+WV/4waF0uCml5EFl aJgw4nlj6wYg+pi2UKJ+E9h0f7Ag+SNtHqbfafmShYxN3yivyEXMw/OlIDUZ8PVJtamH k0QpvTBk+5eyva0gkgUZ7BRy+gSLsfCIZNI4z9hl8ml1s4cLWALAJYnCrf3U8ouNM0MY owkA== X-Gm-Message-State: AOAM533LqQOLQET0ftyj5ALde3vvZqwhl1NwsTdppvNdUrJNDz6jn+DO V4GjUPrKHwSjUWVA3SG1vqK03835JxUJsw== X-Google-Smtp-Source: ABdhPJyQzXzkq32d5WF/G23dIS0dujDE4UtMwfUqv/bJA3RApg380F2MGgPQgmu/uc1tfVkfaCJhcg== X-Received: by 2002:a05:620a:146d:: with SMTP id j13mr8030066qkl.493.1621550856417; Thu, 20 May 2021 15:47:36 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:35 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 08/15] HID: nintendo: reduce device removal subcommand errors Date: Thu, 20 May 2021 18:47:08 -0400 Message-Id: <20210520224715.680919-9-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org This patch fixes meaningless error output from trying to send subcommands immediately after controller removal. It now disables subcommands as soon as possible on removal. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-nintendo.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 3c450b378efa3..15cdcd9ef4f54 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -230,6 +230,7 @@ static const struct joycon_rumble_amp_data joycon_rumble_amplitudes[] = { enum joycon_ctlr_state { JOYCON_CTLR_STATE_INIT, JOYCON_CTLR_STATE_READ, + JOYCON_CTLR_STATE_REMOVED, }; struct joycon_stick_cal { @@ -451,6 +452,14 @@ static int joycon_send_subcmd(struct joycon_ctlr *ctlr, unsigned long flags; spin_lock_irqsave(&ctlr->lock, flags); + /* + * If the controller has been removed, just return ENODEV so the LED + * subsystem doesn't print invalid errors on removal. + */ + if (ctlr->ctlr_state == JOYCON_CTLR_STATE_REMOVED) { + spin_unlock_irqrestore(&ctlr->lock, flags); + return -ENODEV; + } memcpy(subcmd->rumble_data, ctlr->rumble_data[ctlr->rumble_queue_tail], JC_RUMBLE_DATA_SIZE); spin_unlock_irqrestore(&ctlr->lock, flags); @@ -800,10 +809,13 @@ static void joycon_rumble_worker(struct work_struct *work) mutex_lock(&ctlr->output_mutex); ret = joycon_enable_rumble(ctlr); mutex_unlock(&ctlr->output_mutex); - if (ret < 0) - hid_warn(ctlr->hdev, "Failed to set rumble; e=%d", ret); + /* -ENODEV means the controller was just unplugged */ spin_lock_irqsave(&ctlr->lock, flags); + if (ret < 0 && ret != -ENODEV && + ctlr->ctlr_state != JOYCON_CTLR_STATE_REMOVED) + hid_warn(ctlr->hdev, "Failed to set rumble; e=%d", ret); + ctlr->rumble_msecs = jiffies_to_msecs(jiffies); if (ctlr->rumble_queue_tail != ctlr->rumble_queue_head) { if (++ctlr->rumble_queue_tail >= JC_RUMBLE_QUEUE_SIZE) @@ -1515,9 +1527,17 @@ static int nintendo_hid_probe(struct hid_device *hdev, static void nintendo_hid_remove(struct hid_device *hdev) { struct joycon_ctlr *ctlr = hid_get_drvdata(hdev); + unsigned long flags; hid_dbg(hdev, "remove\n"); + + /* Prevent further attempts at sending subcommands. */ + spin_lock_irqsave(&ctlr->lock, flags); + ctlr->ctlr_state = JOYCON_CTLR_STATE_REMOVED; + spin_unlock_irqrestore(&ctlr->lock, flags); + destroy_workqueue(ctlr->rumble_queue); + hid_hw_close(hdev); hid_hw_stop(hdev); } From patchwork Thu May 20 22:47:09 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271477 X-Patchwork-Delegate: jikos@jikos.cz 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 62A44C433ED for ; Thu, 20 May 2021 22:47:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 4757A613AD for ; Thu, 20 May 2021 22:47:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231876AbhETWtA (ORCPT ); Thu, 20 May 2021 18:49:00 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55796 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231871AbhETWtA (ORCPT ); Thu, 20 May 2021 18:49:00 -0400 Received: from mail-qv1-xf34.google.com (mail-qv1-xf34.google.com [IPv6:2607:f8b0:4864:20::f34]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4085EC061574 for ; Thu, 20 May 2021 15:47:38 -0700 (PDT) Received: by mail-qv1-xf34.google.com with SMTP id c13so8316156qvx.5 for ; Thu, 20 May 2021 15:47:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=7HnBAp7taX5fj/Y22sbYFru4HazAxOE4r4kpPAXFtzo=; b=BohA+CGGM1crQPfequOhgQ1f7ho8fPDoK6d5YBJMSd2kN+oAC6UMbWb7LHCLCCLStW SBJMs0VyRFsyye3QTnuyE01FFb+pIDxYk5c3EQZhAhjFVOzqB85D7TY/BWC2PJkCgyck 8STS7A/4JbDXFJx1ReMlr0cwsl8+WjGMXNboV55xvJDQ+nf9AzPYTeM10WWkubgrJWow EY42BT1b6MI9zOLqC5Z3ExFCJMRr4xK9b+10poJvPPqMwzNF8GygOAk4e+5l8wfA0xKJ pgrqwq0m50LC9Ixvrgaezzx/HxevJlvxkNWvpKdV5elO0T894O7Nks+kgcpic//913Zw 6IvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=7HnBAp7taX5fj/Y22sbYFru4HazAxOE4r4kpPAXFtzo=; b=VdGXC87ZkENIxBFTbdxtb40d8JBXgO8ne439fos7c6uduZTIzPEMiOdvRbWIK9XrDF 0L/6qXBU+9480+3gZ3JqLvV5EPE3AhahBJbVCC5rN5XsSCsoVF59zjn7vvQt4ay4Oq0I FMow6wiIWkAK9PolAw6bGUUHnaHLuqmkDlCyUrtOctrUlSZTK25UtYNUHshMYxWVP3Tc NzgHPIO+Q4JHM+/9g2pV4c07J7ZXG9ri7Xp5dTUI5AYq52LAswczfst+pPlBv7p5iC3Q Ih6bNw0sHtYj5x00vmySqE7S3QulLs6iioOXpyFUnJr3APLezcwjZeY53Gw97MikimQT jJig== X-Gm-Message-State: AOAM53148wavOPe2689nfi4iXD8NlUFVM5K4L90fBcQDoxhxmTMIUsSY l0v/qUnv6LebyNWNjWNazVHCNDHWda5zHg== X-Google-Smtp-Source: ABdhPJwQpwg59lwyiMgQMAJzrWflDs5b3Jkoqb8q9/jqKn1RDe3qw1Dph4AGGIIMdRewTeSP/cpChQ== X-Received: by 2002:a05:6214:702:: with SMTP id b2mr7490376qvz.43.1621550857372; Thu, 20 May 2021 15:47:37 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:36 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 09/15] HID: nintendo: patch hw version for userspace HID mappings Date: Thu, 20 May 2021 18:47:09 -0400 Message-Id: <20210520224715.680919-10-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 15cdcd9ef4f54..2eee21f275c9d 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -1421,6 +1421,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 Thu May 20 22:47:10 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271479 X-Patchwork-Delegate: jikos@jikos.cz 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 BC1FFC43470 for ; Thu, 20 May 2021 22:47:40 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9E82D613AD for ; Thu, 20 May 2021 22:47:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231889AbhETWtB (ORCPT ); Thu, 20 May 2021 18:49:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55804 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231871AbhETWtA (ORCPT ); Thu, 20 May 2021 18:49:00 -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 0E9ECC061761 for ; Thu, 20 May 2021 15:47:39 -0700 (PDT) Received: by mail-qk1-x72b.google.com with SMTP id k127so17946503qkc.6 for ; Thu, 20 May 2021 15:47:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=EkqRq+i856MP71GI5yisQ5h35w62tQwjIOVkxxvAUco=; b=MqxNjNx0VinxyAZCwLE8zqPhAB8DfpjQE/dwNHwffbj+2PCB/jnPLRr7bhttM7mVos jDJruTe6aeWjGdSsBPCmI2PjIAya4FswELGnKr/sOl4i9s3UjKfyVm7zTMTj72eIBg0Y HAXthDmtJtHfT5MUKa2cNfP3Xn4B4L2V7gh7LXbdiYRs8X7k9CC5sQkhMAxrz/TlhCCk 4F96ztJfiSPXBvOjsQLgqzPjg7gMU8g8Yzox3qCYu6TmkiXVx1myeJ8+NVhm2AcmdlKn dbgZ52xj88EaARkIjVxGFO369VWFSQMIsoziES0ztbXj8sgG2s7De6f3thL6lVrGx3R4 wRRg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=EkqRq+i856MP71GI5yisQ5h35w62tQwjIOVkxxvAUco=; b=qtJ0oCJ0WGeQLdPQSz40NdhvIkyqBsOg0OdFdPxpbva+m3RdDEgAFW3nTizEXj0Igu 5Lp61nfhsGrrb8mrE9WMNThCuFP+1LFbOcjCNNjDagA+ZCUHAXwNK4iVHCqhrRguhc8Y wxtWdpR9g732Y5psHKHzwYJbsQI8OvWLRyVTjs3ApV+Kra5LL0kFbHfvdGHrD+4dzBlJ QONy+nGeI9n7nsmli4hdPNeqzxfCwBplj8d4lPSRpivZ3+3lJOJMXXyMAPjHUXtQtchm n4/KlHcAqZZ3159MI4o9HNpBeqrZmEBlv/ui5iuyAnRbKKlcO3j3vIQqESWMSlFx8Y2t JBKw== X-Gm-Message-State: AOAM5314ltbADaa+hldZrcyJQHVqJU+A3RtFswIpWlSTDAteufB1Gkd1 yI34XpHKNGnps0eA491kD9EN1JldjKBdAg== X-Google-Smtp-Source: ABdhPJxQSWFzhuCSjNtyKBdjle+QZHxlUuE8pp6lu2kDxrjfZThzycR+9xGSrM92QGycb3c0JMNVIw== X-Received: by 2002:a05:620a:4486:: with SMTP id x6mr8277366qkp.229.1621550858116; Thu, 20 May 2021 15:47:38 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:37 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 10/15] HID: nintendo: set controller uniq to MAC Date: Thu, 20 May 2021 18:47:10 -0400 Message-Id: <20210520224715.680919-11-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 2eee21f275c9d..c49ec001212bf 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -319,6 +319,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; @@ -1005,6 +1007,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); @@ -1317,6 +1320,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) @@ -1493,6 +1531,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 Thu May 20 22:47:11 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271481 X-Patchwork-Delegate: jikos@jikos.cz 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 677EBC43461 for ; Thu, 20 May 2021 22:47:42 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 49C1C613AC for ; Thu, 20 May 2021 22:47:42 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231891AbhETWtD (ORCPT ); Thu, 20 May 2021 18:49:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55810 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231888AbhETWtD (ORCPT ); Thu, 20 May 2021 18:49:03 -0400 Received: from mail-qv1-xf32.google.com (mail-qv1-xf32.google.com [IPv6:2607:f8b0:4864:20::f32]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7F927C061574 for ; Thu, 20 May 2021 15:47:40 -0700 (PDT) Received: by mail-qv1-xf32.google.com with SMTP id h7so8846517qvs.12 for ; Thu, 20 May 2021 15:47:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=QkHWM8ZnIGh9tiEkHkggD/cwqs5Z0hBp0+f846A6KAA=; b=TvuA7OkrDv+ubzNgrkovzPrIfNfXL0y1BZ4XV+4RVhfIWW2qAQ1uFakZCVCbw3wHfc 1SwbXjbTJ0+a34cxykB07jCTuS6qI1iupQ6gsxn8MWEybQf65xHf5ao3R2xweKoa5sdF WcnBDJQeNVYO4XRQr5Oj3AXaVLpv4vEmEfPheb6ri/PutnFmyzhaax4/b+S15WZDZNjq 1I+0t/dVeTHFqOjmbO7gPEPa2nQcNcQgzc3YLbqDNGUJqzSRkSxOk9qYl1t7YlibjXFd 4fcC2I4Zjk5UYgBHY76o4m9pl9a/VjPOSAhktHL0AWacdDbLBFrQMl459MeV2eI0QkSQ zQ0w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=QkHWM8ZnIGh9tiEkHkggD/cwqs5Z0hBp0+f846A6KAA=; b=LoNaKpDSb5UX0ux8r/Ya6Nk2nqu8goj1fHvaqBQXFwX8LS1MX0AOfluW/Dyu0Z66sY dfGYqvgp9Kkxz6iN8+rLl024VrO4w+26gwC/zroR9JjoQHSqQUObTuVyNp3Mh44Q3rtv RutvwPLjwN1m+bHJxyIe0Yp8RzPotzl9HM0aqOogk03snb2B4lOeZ5rnWAfL2Iw83IHF ycaXGd4olZ/KwOOunsxqoKBiYfe7TX13raOiJVtiDwnwQrouGN0xoAPGQk8mG2ThXs2h g4G7L8nebKBO3bssHNi2Ob9ohnoGDcqSbArFHXa38hzsfBCHg+PEQaDu1wsFORIIXuDd VKeg== X-Gm-Message-State: AOAM530RsQUlbA5XYZHIRAYfuG2Qw5T4ADqis+tFm5+9GFy+yI9ZmSeV hPdE/70XxhhpVWcEI+RYfakt89TdsuMN5w== X-Google-Smtp-Source: ABdhPJxiogbmauMHa8pgFCtjhrN7ju8KWcdsPxxYBU4TzMtP4HSyFkhp7QjsAAaLAl4dlJw2V9l51w== X-Received: by 2002:a05:6214:7af:: with SMTP id v15mr8432117qvz.17.1621550859391; Thu, 20 May 2021 15:47:39 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:38 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 11/15] HID: nintendo: add support for charging grip Date: Thu, 20 May 2021 18:47:11 -0400 Message-Id: <20210520224715.680919-12-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 joy-con charging grip. The peripheral essentially behaves the same as a pro controller, but with two joy-cons attached to the grip. However the grip exposes the two joy-cons as separate hid devices, so extra handling is required. The joy-con is queried to check if it is a right or left joy-con (since the product ID is identical between left/right when using the grip). Since controller model detection is now more complicated, the various checks for hid product values have been replaced with helper macros to reduce code duplication. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-nintendo.c | 67 ++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 5f353655a47bf..6a5c43c37976b 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -910,6 +910,7 @@ #define USB_DEVICE_ID_NINTENDO_JOYCONL 0x2006 #define USB_DEVICE_ID_NINTENDO_JOYCONR 0x2007 #define USB_DEVICE_ID_NINTENDO_PROCON 0x2009 +#define USB_DEVICE_ID_NINTENDO_CHRGGRIP 0x200E #define USB_VENDOR_ID_NOVATEK 0x0603 #define USB_DEVICE_ID_NOVATEK_PCT 0x0600 diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index c49ec001212bf..62231e50ca020 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -233,6 +233,13 @@ enum joycon_ctlr_state { JOYCON_CTLR_STATE_REMOVED, }; +/* Controller type received as part of device info */ +enum joycon_ctlr_type { + JOYCON_CTLR_TYPE_JCL = 0x01, + JOYCON_CTLR_TYPE_JCR = 0x02, + JOYCON_CTLR_TYPE_PRO = 0x03, +}; + struct joycon_stick_cal { s32 max; s32 min; @@ -321,6 +328,7 @@ struct joycon_ctlr { spinlock_t lock; u8 mac_addr[6]; char *mac_addr_str; + enum joycon_ctlr_type ctlr_type; /* The following members are used for synchronous sends/receives */ enum joycon_msg_type msg_type; @@ -359,6 +367,26 @@ struct joycon_ctlr { u16 rumble_rh_freq; }; +/* Helper macros for checking controller type */ +#define jc_type_is_joycon(ctlr) \ + (ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL || \ + ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR || \ + ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP) +#define jc_type_is_procon(ctlr) \ + (ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_PROCON) +#define jc_type_is_chrggrip(ctlr) \ + (ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP) + +/* Does this controller have inputs associated with left joycon? */ +#define jc_type_has_left(ctlr) \ + (ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCL || \ + ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO) + +/* Does this controller have inputs associated with right joycon? */ +#define jc_type_has_right(ctlr) \ + (ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR || \ + ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO) + static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len) { u8 *buf; @@ -655,7 +683,6 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, unsigned long flags; u8 tmp; u32 btns; - u32 id = ctlr->hdev->product; unsigned long msecs = jiffies_to_msecs(jiffies); spin_lock_irqsave(&ctlr->lock, flags); @@ -694,7 +721,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, /* Parse the buttons and sticks */ btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24); - if (id != USB_DEVICE_ID_NINTENDO_JOYCONR) { + if (jc_type_has_left(ctlr)) { u16 raw_x; u16 raw_y; s32 x; @@ -718,7 +745,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, 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) { + if (jc_type_is_joycon(ctlr)) { /* Report the S buttons as the non-existent triggers */ input_report_key(dev, BTN_TR, btns & JC_BTN_SL_L); input_report_key(dev, BTN_TR2, btns & JC_BTN_SR_L); @@ -750,7 +777,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, input_report_abs(dev, ABS_HAT0Y, haty); } } - if (id != USB_DEVICE_ID_NINTENDO_JOYCONL) { + if (jc_type_has_right(ctlr)) { u16 raw_x; u16 raw_y; s32 x; @@ -770,7 +797,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, /* report buttons */ input_report_key(dev, BTN_TR, btns & JC_BTN_R); input_report_key(dev, BTN_TR2, btns & JC_BTN_ZR); - if (id != USB_DEVICE_ID_NINTENDO_PROCON) { + if (jc_type_is_joycon(ctlr)) { /* Report the S buttons as the non-existent triggers */ input_report_key(dev, BTN_TL, btns & JC_BTN_SL_R); input_report_key(dev, BTN_TL2, btns & JC_BTN_SR_R); @@ -989,6 +1016,12 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) case USB_DEVICE_ID_NINTENDO_PROCON: name = "Nintendo Switch Pro Controller"; break; + case USB_DEVICE_ID_NINTENDO_CHRGGRIP: + if (jc_type_has_left(ctlr)) + name = "Nintendo Switch Left Joy-Con (Grip)"; + else + name = "Nintendo Switch Right Joy-Con (Grip)"; + break; case USB_DEVICE_ID_NINTENDO_JOYCONL: name = "Nintendo Switch Left Joy-Con"; break; @@ -1011,9 +1044,8 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) ctlr->input->name = name; input_set_drvdata(ctlr->input, ctlr); - /* set up sticks and buttons */ - if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONR) { + if (jc_type_has_left(ctlr)) { input_set_abs_params(ctlr->input, ABS_X, -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, JC_STICK_FUZZ, JC_STICK_FLAT); @@ -1039,7 +1071,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) JC_DPAD_FUZZ, JC_DPAD_FLAT); } } - if (hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) { + if (jc_type_has_right(ctlr)) { input_set_abs_params(ctlr->input, ABS_RX, -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, JC_STICK_FUZZ, JC_STICK_FLAT); @@ -1207,7 +1239,7 @@ static int joycon_leds_create(struct joycon_ctlr *ctlr) mutex_unlock(&joycon_input_num_mutex); /* configure the home LED */ - if (ctlr->hdev->product != USB_DEVICE_ID_NINTENDO_JOYCONL) { + if (jc_type_has_right(ctlr)) { name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", d_name, "home"); if (!name) return -ENOMEM; @@ -1320,7 +1352,7 @@ 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) +static int joycon_read_info(struct joycon_ctlr *ctlr) { int ret; int i; @@ -1352,6 +1384,9 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr) return -ENOMEM; hid_info(ctlr->hdev, "controller MAC = %s\n", ctlr->mac_addr_str); + /* Retrieve the type so we can distinguish for charging grip */ + ctlr->ctlr_type = report->reply.data[2]; + return 0; } @@ -1485,7 +1520,7 @@ static int nintendo_hid_probe(struct hid_device *hdev, /* Initialize the controller */ mutex_lock(&ctlr->output_mutex); /* if handshake command fails, assume ble pro controller */ - if (hdev->product == USB_DEVICE_ID_NINTENDO_PROCON && + if ((jc_type_is_procon(ctlr) || jc_type_is_chrggrip(ctlr)) && !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) { hid_dbg(hdev, "detected USB controller\n"); /* set baudrate for improved latency */ @@ -1505,6 +1540,10 @@ static int nintendo_hid_probe(struct hid_device *hdev, * This doesn't send a response, so ignore the timeout. */ joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT, HZ/10); + } else if (jc_type_is_chrggrip(ctlr)) { + hid_err(hdev, "Failed charging grip handshake\n"); + ret = -ETIMEDOUT; + goto err_mutex; } /* get controller calibration data, and parse it */ @@ -1531,9 +1570,9 @@ static int nintendo_hid_probe(struct hid_device *hdev, goto err_mutex; } - ret = joycon_read_mac(ctlr); + ret = joycon_read_info(ctlr); if (ret) { - hid_err(hdev, "Failed to retrieve controller MAC; ret=%d\n", + hid_err(hdev, "Failed to retrieve controller info; ret=%d\n", ret); goto err_mutex; } @@ -1601,6 +1640,8 @@ static const struct hid_device_id nintendo_hid_devices[] = { USB_DEVICE_ID_NINTENDO_PROCON) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, + USB_DEVICE_ID_NINTENDO_CHRGGRIP) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_JOYCONL) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, From patchwork Thu May 20 22:47:12 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271483 X-Patchwork-Delegate: jikos@jikos.cz 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 AF999C433B4 for ; Thu, 20 May 2021 22:47:49 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9530B60200 for ; Thu, 20 May 2021 22:47:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231950AbhETWtJ (ORCPT ); Thu, 20 May 2021 18:49:09 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55816 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231913AbhETWtE (ORCPT ); Thu, 20 May 2021 18:49:04 -0400 Received: from mail-qv1-xf30.google.com (mail-qv1-xf30.google.com [IPv6:2607:f8b0:4864:20::f30]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3ED96C061761 for ; Thu, 20 May 2021 15:47:41 -0700 (PDT) Received: by mail-qv1-xf30.google.com with SMTP id u33so9480004qvf.9 for ; Thu, 20 May 2021 15:47:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=i0BIBWLRv0flhMJctKeDg8V6EhiWLCWQgTJ+1Lih4Ik=; b=kTyPdJ/oVjAk8ahDAdWeGfmNFwHjKZ67sgvS9/bkOpn4gFS3b6K4Gb7J1DFxaWLwE9 e2fjAKZIeONK+D5LKh49hHCiWC7WswRJuvGwc3eLF1aLUPNRt0GzRSotxl7Fnvzx/qB/ kIqcYnBzfCX1fopgO8d7t6bZeMUbO956qrG9V8NENB1WQFN0gCUWLPN83l7kjwnoQrzP Jb34jZXfyBhxR5azbQvJWy1ZHJMXt7zOiO2Cg/hGx1BsF7RRdux+tOxefzH3wcHwHaEY 3RziQOJ4sY1HRdRjcFn+vpqzjyyJ6sTqfgl33LtCQobc7yJXH1zdWD1B34mFPlXIYucJ 9BEA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=i0BIBWLRv0flhMJctKeDg8V6EhiWLCWQgTJ+1Lih4Ik=; b=L5fSQBZBNYwxzMUfNg8qHmA+pfpF57TxYTy3u61q4kE//xMRfHMxttyUKBJmnWlIQ8 93LuIs6SSrrvmDoamn4Ew+0gDeSjnWSCetutkXpeyL/AZwgdnbROVFXEB0Mi1Pds00G+ 0tyQWeFC7uxbdqO8Fkf1xrRsFXdFeVftGRhRUN3b55rqiuIUxxdqrR3TrwYHE8qu9bWe mql/AAcdLE2qHQKwSRtTcrsNyy3mhdQ9vc2ZNp7KXtRWiUs6hUTT8hWNI9LOJ3OOPVER WfNT/IxQeqa4e06ieklIg4eITxo3lVzrw8zPmsTTsTrmIThog8TUXGfRBaLOxe4UcmLq ak2Q== X-Gm-Message-State: AOAM53226KS5wA6KPD0pi3hNGG7gvN7/qj6ibkhXEFPt1bmtTcErcpiN XtYZT02N2SaLo700y10bFu6MMWNv6oUuaA== X-Google-Smtp-Source: ABdhPJyJKvj9X0gcfeKQRHfEBlw+XWe/ONOZgJXg8WD4dRnhoDPnlApWokQhmYYTiSTPfXgEbh+Mxw== X-Received: by 2002:ad4:5b81:: with SMTP id 1mr4771104qvp.12.1621550860227; Thu, 20 May 2021 15:47:40 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:39 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 12/15] HID: nintendo: add support for reading user calibration Date: Thu, 20 May 2021 18:47:12 -0400 Message-Id: <20210520224715.680919-13-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 62231e50ca020..cdb931d9bfae4 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; @@ -524,38 +537,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; @@ -565,6 +680,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; @@ -573,44 +699,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 Thu May 20 22:47:13 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271485 X-Patchwork-Delegate: jikos@jikos.cz 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 E158FC43460 for ; Thu, 20 May 2021 22:47:49 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C901E61074 for ; Thu, 20 May 2021 22:47:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231948AbhETWtK (ORCPT ); Thu, 20 May 2021 18:49:10 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55824 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231871AbhETWtF (ORCPT ); Thu, 20 May 2021 18:49:05 -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 35A4AC061574 for ; Thu, 20 May 2021 15:47:42 -0700 (PDT) Received: by mail-qt1-x82c.google.com with SMTP id 1so14086241qtb.0 for ; Thu, 20 May 2021 15:47:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=1AoeyijL6TGgV+NjdlX4S+qM2lKx4/5f3P6oGVVy9zs=; b=ZY+C5uxBkj1w3v1SK9fGVxfuHOey1huwxMHh+bSU8ulkDHXMq8tegF2BMKEouVB6qI 8tDYrA4S8Xgd15C6dII+NDBQgAao+/Vz735WQyRK/8Rdsic0jd9fCVQYY0NEibsbirWF 3ZgZ5e6qVqS66ZEIE+GmbOtBWMefxoaWrDHP9LBrK+GaitQMLfl+OCkwZm/Wg6OrDS+z ZflMmEONDogkig2v467lCKHzQ69yjUNjmnz4bmHE0rIPNbqM/vin6a4zh7J7YFoGHsIk zxIiebGt3yQWTd+S3uAWk1WcZTqvPhezeiYPbUE+w0xggpMTyP0UqMvp58C578mWrQRQ X19g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=1AoeyijL6TGgV+NjdlX4S+qM2lKx4/5f3P6oGVVy9zs=; b=kzFsOGS15wVWlEQlDPEBBGkhhg+43EsdgcT1abEINYBxvIwoIOoazBhJCIUirlUM8Z 7oyMNJGnhBLAq16sWmrW1KP+jGn8BjXDZKKrAsX2Nl6yvJtGwjabJK48+xYmcTxgYa9q aE3+7pLL8T9BXE7LBc+sPn9AgFgoi010/IFjszhvZ2CaV/QexorcrX+U56erZ79cpRgJ XbKgOXXi+q8WeMVwitETO7oGU8COJAfoTtkZfeb9EH8FjEQ3qEzlR3Aft71RS76+lidZ 8O74jFYx7mgQ/4pB0kEl07J7sdO2bD2/GIgSKR28rvB33y0NejDkR43P8HjIdZYL0qcN bVUw== X-Gm-Message-State: AOAM530DfQPTZkJrQ7URE7mJvNoB9m+sBy+IqmVt6jXzCQ8yMmVfaC8u 4YL6D8thPVUdFsL8INcSXSZ8puHP8aBY1Q== X-Google-Smtp-Source: ABdhPJxdL+yAVsWYtWKnGGHmjrV5/Y361R/pvkPDDAa767k+662PZLLC6Uqthlws8kj2V5qU1CIiEg== X-Received: by 2002:ac8:7a92:: with SMTP id x18mr7891352qtr.342.1621550861162; Thu, 20 May 2021 15:47:41 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:40 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 13/15] HID: nintendo: prevent needless queueing of the rumble worker Date: Thu, 20 May 2021 18:47:13 -0400 Message-Id: <20210520224715.680919-14-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org This patch adds a check for if the rumble queue ringbuffer is empty prior to queuing the rumble workqueue. If the current rumble setting is using a non-zero amplitude though, it will queue the worker anyway. This is because the controller will automatically disable the rumble effect if it isn't "refreshed". This change improves bluetooth communication reliability with the controller, since it reduces the amount of traffic. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-nintendo.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index cdb931d9bfae4..7e9b7ebc5a674 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -378,6 +378,7 @@ struct joycon_ctlr { u16 rumble_lh_freq; u16 rumble_rl_freq; u16 rumble_rh_freq; + bool rumble_zero_amp; }; /* Helper macros for checking controller type */ @@ -777,7 +778,9 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, spin_lock_irqsave(&ctlr->lock, flags); if (IS_ENABLED(CONFIG_NINTENDO_FF) && rep->vibrator_report && - (msecs - ctlr->rumble_msecs) >= JC_RUMBLE_PERIOD_MS) + (msecs - ctlr->rumble_msecs) >= JC_RUMBLE_PERIOD_MS && + (ctlr->rumble_queue_head != ctlr->rumble_queue_tail || + !ctlr->rumble_zero_amp)) queue_work(ctlr->rumble_queue, &ctlr->rumble_worker); /* Parse the battery status */ @@ -1036,6 +1039,8 @@ static int joycon_set_rumble(struct joycon_ctlr *ctlr, u16 amp_r, u16 amp_l, freq_r_high = ctlr->rumble_rh_freq; freq_l_low = ctlr->rumble_ll_freq; freq_l_high = ctlr->rumble_lh_freq; + /* this flag is used to reduce subcommand traffic */ + ctlr->rumble_zero_amp = (amp_l == 0) && (amp_r == 0); spin_unlock_irqrestore(&ctlr->lock, flags); /* right joy-con */ From patchwork Thu May 20 22:47:14 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271489 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 D7170C43462 for ; Thu, 20 May 2021 22:47:49 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id BBFC860200 for ; Thu, 20 May 2021 22:47:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231935AbhETWtK (ORCPT ); Thu, 20 May 2021 18:49:10 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55832 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231948AbhETWtF (ORCPT ); Thu, 20 May 2021 18:49:05 -0400 Received: from mail-qv1-xf31.google.com (mail-qv1-xf31.google.com [IPv6:2607:f8b0:4864:20::f31]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8FF2DC061763 for ; Thu, 20 May 2021 15:47:43 -0700 (PDT) Received: by mail-qv1-xf31.google.com with SMTP id u33so9480031qvf.9 for ; Thu, 20 May 2021 15:47:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=cJOT56LJylpFVgWJDzVtckvQpq9UBRZLy79t34OoPXQ=; b=HzV3HPFzQM/SHS+mHFgaZswJkGW04x0ajIWZqvLFIEOzfP+WLfcqQJFbtg6bbQ9SnZ evshTW78jxP7zmo+a/0Q9aXEcdI9TkcNTuS+FWDM1cYU/B6kvhF+heLQIm9xgzZ8PJ8p zYU9bTHrRa0BpGux+fDGbBbJT9gyTZ6L722RU3reZkG+8JegK+kwwnAR1FdM/QTVBbeP RcPwchn7o/5QoOL8/3IHuck9hM5v54QvYrlZ6s+rELJWfx+vIU1pMGw82jG+/44nt8jW qvW6qG8bIBwCx5QB5/aRMdklS7luwf4TQZWbhUoqiTKol/WmU3JmfYQHQfjyFt7YO9U+ FFaw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=cJOT56LJylpFVgWJDzVtckvQpq9UBRZLy79t34OoPXQ=; b=S10MCNTpGsVvsHTqUdS79vQGa9g5HqjmeViG7QNL8WmDT+a9atMLegMNtK2MkQzi08 oD6AlfrFsAaMTKkleQEMEzw+xPDLMx40PD3/gvb137ukG6d5vf00vyqMTsM4ZDh5FEvq xrSE9pGeYnupkkdScgb7VOcQeAp8M9paLLg31727G7WoxSi9WfSG5TZCgXxhTHs5bVSn VXQaUQoHcNuoSl7sp1h6WiY70jeGsN0SiJe4DVbhFFjZPDixtsDViuXS2b2YLWnbX+P5 y1l94TcLRdXl6nhAi2RNXQAMw3eLDH/4AdNM/Cq+EJ4urFwkDwxg/ntuPbZwaRS5IvYx Z9Rw== X-Gm-Message-State: AOAM533wrQO5NVC3F0WbnfjI8SlIMVK9NUdjiJiBFzZhP4mlLZjuhXXt B1vp6ITrz2WNJt8S0BJD5n28y7zoKpAkQw== X-Google-Smtp-Source: ABdhPJxgAvg87upVNrT8GyhyirRcTO1f+d7OpBWU8SdvACmFpAnpgFMh8/Lp33vB5Nh8ObJAMRXANQ== X-Received: by 2002:ad4:536a:: with SMTP id e10mr8836108qvv.9.1621550862196; Thu, 20 May 2021 15:47:42 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:41 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 14/15] HID: nintendo: add IMU support Date: Thu, 20 May 2021 18:47:14 -0400 Message-Id: <20210520224715.680919-15-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-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 7e9b7ebc5a674..d5027e77af06a 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) @@ -360,6 +425,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; @@ -379,6 +451,16 @@ struct joycon_ctlr { u16 rumble_rl_freq; u16 rumble_rh_freq; bool rumble_zero_amp; + + /* 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 */ @@ -563,7 +645,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; } @@ -723,6 +805,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; @@ -749,6 +919,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; @@ -767,6 +950,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=%hd g_y=%hd g_z=%hd\n", + imu_data[i].gyro_x, imu_data[i].gyro_y, + imu_data[i].gyro_z); + hid_dbg(ctlr->hdev, "raw_accel: a_x=%hd a_y=%hd a_z=%hd\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) { @@ -917,6 +1318,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) @@ -1102,6 +1507,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; @@ -1110,18 +1516,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"); @@ -1205,6 +1617,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 da8963a9f044c..aca7a12bfa540 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 Thu May 20 22:47:15 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 12271487 X-Patchwork-Delegate: jikos@jikos.cz 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 BC033C43461 for ; Thu, 20 May 2021 22:47:49 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A1FD8613AD for ; Thu, 20 May 2021 22:47:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231913AbhETWtJ (ORCPT ); Thu, 20 May 2021 18:49:09 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55836 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231983AbhETWtH (ORCPT ); Thu, 20 May 2021 18:49:07 -0400 Received: from mail-qv1-xf2d.google.com (mail-qv1-xf2d.google.com [IPv6:2607:f8b0:4864:20::f2d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 19247C0613CE for ; Thu, 20 May 2021 15:47:44 -0700 (PDT) Received: by mail-qv1-xf2d.google.com with SMTP id c13so8316242qvx.5 for ; Thu, 20 May 2021 15:47:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=zbXCxsJXucMViEztm8fCjOOu+qgOi5gXNmyr1W/IEq0=; b=CmevYP+KfpOZPAC3jAqhhYOir7cED1W8iaKjmK3rWqZc82cIpmnlECzDbouAMGC1iT iN/cUWqQmqsITOViP6SWq84ne/N6n3zR9emYpOOYViYxTcM9xJSbMPn5vyF+KZaDsBl/ 7H6vRf8h6jL6IWxwL5O9RgtVwwQOgD7F4JmVsFdRSgQOcspGzm+ubL2OO5JlcNHwWd3n JYtTYTqfMToGco7yROP+KlzEDQ5GMUzGIvq65CRZiatAOFrsTv9gs7GR7fUE30tuCltx 4iAsNcOHauy1mQDKpGOgDTz6uGzcw/A8xd4QUDujJNpGazKS6gcdCNtST+JIVUJHJe2i SsIQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=zbXCxsJXucMViEztm8fCjOOu+qgOi5gXNmyr1W/IEq0=; b=Wv0HpEUxpv0va/HGQOa90SN1EU63yrbq55Tvp65CCIeHNht8UMJCjRKK+rmqW5NZs7 83ojXL9im/V1uYmeL5rd/hiVYQDKzkbFl8XAwg5B8pMKvNOW8EfQFrbrKmKV4X/+2goY hqnDCXg25iSIIATpWUMqFAwOYW0vqBrLz5S6dCfaMYkA5iv7EBhggu7M0QmoPmBPHFWw n0jslcUepS1vdKN7agzDo56XMW5aYHj5lPLKC6wq+0CL20c7hSB5bbsPiniXawQL6HTJ h67Q1zm4F9AKwS0iRWPETdPYm0BX3J/X1PWM7p6IUF5tAXyE5+edLULIsgWIDYlE0IMn /qmQ== X-Gm-Message-State: AOAM5336msxMcSUeTFNAB1Q7WN8pzzyI6GGZVUYsAUgVgVp9OECK/xxw g7gzgeAvF5yL+uPG5OKRPXR+HGASs9vhEw== X-Google-Smtp-Source: ABdhPJzu6na6gdR2GcbH/RQaXVtj9WSOdyysc3rdxVvO1uVD1DE0L7D0VCPgWX8NKiI0/1ey5WKp2A== X-Received: by 2002:ad4:5809:: with SMTP id dd9mr9035836qvb.10.1621550863128; Thu, 20 May 2021 15:47:43 -0700 (PDT) Received: from Arrakis.djogorchock.com (pool-173-68-68-24.nycmny.fios.verizon.net. [173.68.68.24]) by smtp.gmail.com with ESMTPSA id s11sm2126251qtn.77.2021.05.20.15.47.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 20 May 2021 15:47:42 -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, "Daniel J. Ogorchock" Subject: [PATCH v13 15/15] HID: nintendo: improve rumble performance and stability Date: Thu, 20 May 2021 18:47:15 -0400 Message-Id: <20210520224715.680919-16-djogorchock@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210520224715.680919-1-djogorchock@gmail.com> References: <20210520224715.680919-1-djogorchock@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org This patch alters the method that the rumble data is sent to the controller. Rather than using the enable rumble subcommand for this purpose, the driver now employs the RUMBLE_ONLY output report. This has the advantage of not needing to receive a subcommand reply (to the major benefit of reducing IMU latency) and also seems to make the rumble vibrations more continuous. Perhaps most importantly it reduces disconnects during times of heavy rumble. Signed-off-by: Daniel J. Ogorchock --- drivers/hid/hid-nintendo.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index d5027e77af06a..3e1e3d2963062 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -348,6 +348,12 @@ enum joycon_msg_type { JOYCON_MSG_TYPE_SUBCMD, }; +struct joycon_rumble_output { + u8 output_id; + u8 packet_num; + u8 rumble_data[8]; +} __packed; + struct joycon_subcmd_request { u8 output_id; /* must be 0x01 for subcommand, 0x10 for rumble only */ u8 packet_num; /* incremented every send */ @@ -1324,6 +1330,36 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, joycon_parse_imu_report(ctlr, rep); } +static int joycon_send_rumble_data(struct joycon_ctlr *ctlr) +{ + int ret; + unsigned long flags; + struct joycon_rumble_output rumble_output = { 0 }; + + spin_lock_irqsave(&ctlr->lock, flags); + /* + * If the controller has been removed, just return ENODEV so the LED + * subsystem doesn't print invalid errors on removal. + */ + if (ctlr->ctlr_state == JOYCON_CTLR_STATE_REMOVED) { + spin_unlock_irqrestore(&ctlr->lock, flags); + return -ENODEV; + } + memcpy(rumble_output.rumble_data, + ctlr->rumble_data[ctlr->rumble_queue_tail], + JC_RUMBLE_DATA_SIZE); + spin_unlock_irqrestore(&ctlr->lock, flags); + + rumble_output.output_id = JC_OUTPUT_RUMBLE_ONLY; + rumble_output.packet_num = ctlr->subcmd_num; + if (++ctlr->subcmd_num > 0xF) + ctlr->subcmd_num = 0; + + ret = __joycon_hid_send(ctlr->hdev, (u8 *)&rumble_output, + sizeof(rumble_output)); + return ret; +} + static void joycon_rumble_worker(struct work_struct *work) { struct joycon_ctlr *ctlr = container_of(work, struct joycon_ctlr, @@ -1334,7 +1370,7 @@ static void joycon_rumble_worker(struct work_struct *work) while (again) { mutex_lock(&ctlr->output_mutex); - ret = joycon_enable_rumble(ctlr); + ret = joycon_send_rumble_data(ctlr); mutex_unlock(&ctlr->output_mutex); /* -ENODEV means the controller was just unplugged */