From patchwork Mon Jan 14 01:03:41 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Ogorchock X-Patchwork-Id: 10761581 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DB5F813B4 for ; Mon, 14 Jan 2019 01:04:32 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BB12B288BF for ; Mon, 14 Jan 2019 01:04:32 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id AEFF2288C5; Mon, 14 Jan 2019 01:04:32 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 94A70288BF for ; Mon, 14 Jan 2019 01:04:30 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726635AbfANBE2 (ORCPT ); Sun, 13 Jan 2019 20:04:28 -0500 Received: from mail-yb1-f196.google.com ([209.85.219.196]:44837 "EHLO mail-yb1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726634AbfANBE2 (ORCPT ); Sun, 13 Jan 2019 20:04:28 -0500 Received: by mail-yb1-f196.google.com with SMTP id e1so8018862ybn.11 for ; Sun, 13 Jan 2019 17:04:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=s3csWxn7vEDwZTGE2lv12qMCLZEVCECfWnNPOAr5q0I=; b=tydzwpvygjdDpZGLC5bvgsShSdanbgyS0K5rB5/qLfoc891vfDjBu3an7oxrlxjOq6 p3sCzPgXRWW6uhjUa9gkvk9nfcbTZvOqbnDExfAE0Uw7DSjjcNXWoJRuwmLBlYOutPCz UU5k0y/DKQIP/3NufWX5zgInVqxS8t3M0HL25aBaIq6dY177tC5Ima50FvzEJjjY4caY QBgVRLGMZ1BUtZNYTBzCmWsj3V5qKH8Y453Xaa5ocFNCVkJtkNMLm2GeiTN7v9mGHyEM q2o1mX1pptmVIDvWXIoU883Frjh5POyQYbBU5FdHUpVpUjmi5Go1kayRx7Dr/lgGExSw +Orw== 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:mime-version :content-transfer-encoding; bh=s3csWxn7vEDwZTGE2lv12qMCLZEVCECfWnNPOAr5q0I=; b=JN1vbsymqqBL2yL0atqx1zxvdCG8G6xkm+UwuVx3aT3q+azG2wfdVqBl6ycFnT1IVH dsNakaZitIWY5JtYk2TduoQLXVjqtHHa3HRWjR7V2UTolYpdHQnhX9stCr8+UCsYUsg/ ZSrzje6x3Mv8akoNC9aRNQ8S18gQ7hMI7vT+MOjujJzXHSf0PZjAENGzSPvZprIJ+zkH v+J6NFRy953kTpc8vKse9B+m41a2q7tBCHI3H7isA9IG+RwjORFt9bu07fb/pf189C9h YNgewQWfcv8eR+qB2Gt7clncMbzyyVZvLsBXjuSWBqlwsdBal1fCSFEp7AUiP6M78nfZ Y8xw== X-Gm-Message-State: AJcUukcFmS3qTthLNg6Zsho8e5ZCudEITQY8N56iMsd3cT9cgtgO6R5F FvT6+uFODMsExOFI7UBd3SjZPKNp X-Google-Smtp-Source: ALg8bN5/4cttJbLYfWLy7dxoQJcH54YU5RQvspa/6mB17RgPt2B4mAFpPi5asSj/7vysLD83pip0QA== X-Received: by 2002:a25:af51:: with SMTP id c17mr21703991ybj.147.1547427865321; Sun, 13 Jan 2019 17:04:25 -0800 (PST) Received: from localhost.localdomain ([136.61.179.39]) by smtp.gmail.com with ESMTPSA id z139sm38793784ywa.36.2019.01.13.17.04.24 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 13 Jan 2019 17:04:24 -0800 (PST) From: "Daniel J. Ogorchock" To: linux-input@vger.kernel.org Cc: jikos@kernel.org, benjamin.tissoires@redhat.com, djogorchock@gmail.com Subject: [PATCH] HID: switchcon: add nintendo switch controller driver Date: Sun, 13 Jan 2019 19:03:41 -0600 Message-Id: <20190114010341.19894-1-djogorchock@gmail.com> X-Mailer: git-send-email 2.20.1 MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The switchcon driver supports the Nintendo Switch Pro Controllers and the Joy-Cons. The Pro Controllers can be used over USB or Bluetooth. The Joy-Cons can be used as a pair or individually in their horizontal mode. Signed-off-by: Daniel J. Ogorchock --- MAINTAINERS | 6 + drivers/hid/Kconfig | 13 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-quirks.c | 6 + drivers/hid/hid-switchcon.c | 1156 +++++++++++++++++++++++++++++++++++ 6 files changed, 1185 insertions(+) create mode 100644 drivers/hid/hid-switchcon.c diff --git a/MAINTAINERS b/MAINTAINERS index 39e75bbefc3d..9fc55efd63bc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14572,6 +14572,12 @@ F: kernel/dma/swiotlb.c F: arch/*/kernel/pci-swiotlb.c F: include/linux/swiotlb.h +SWITCHCON HID DRIVER +M: Daniel J. Ogorchock +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-switchcon* + SWITCHDEV M: Jiri Pirko M: Ivan Vecera diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 41e9935fc584..3360267eb5b0 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -943,6 +943,19 @@ config SMARTJOYPLUS_FF Say Y here if you have a SmartJoy PLUS PS2/USB adapter and want to enable force feedback support for it. +config HID_SWITCHCON + tristate "Nintendo Joy-Con and Pro Controller support" + depends on HID + depends on USB_HID + depends on BT_HIDP + 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-switchcon. + config HID_TIVO tristate "TiVo Slide Bluetooth remote control support" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 896a51ce7ce0..e708e682c4b8 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -103,6 +103,7 @@ obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o obj-$(CONFIG_HID_STEAM) += hid-steam.o obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o +obj-$(CONFIG_HID_SWITCHCON) += hid-switchcon.o obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o obj-$(CONFIG_HID_TIVO) += hid-tivo.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 518fa76414f5..cd41e971801e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -848,6 +848,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-quirks.c b/drivers/hid/hid-quirks.c index 94088c0ed68a..c22adffa1233 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -512,6 +512,12 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE2) }, #endif +#if IS_ENABLED(CONFIG_HID_SWITCHCON) + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_JOYCONL) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_JOYCONR) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON) }, +#endif #if IS_ENABLED(CONFIG_HID_NTI) { HID_USB_DEVICE(USB_VENDOR_ID_NTI, USB_DEVICE_ID_USB_SUN) }, #endif diff --git a/drivers/hid/hid-switchcon.c b/drivers/hid/hid-switchcon.c new file mode 100644 index 000000000000..126f3b39cbfe --- /dev/null +++ b/drivers/hid/hid-switchcon.c @@ -0,0 +1,1156 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HID driver for Nintendo Switch Joy-Cons and Pro Controllers + * + * Copyright (c) 2019 Daniel J. Ogorchock + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * 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 + * + * This driver supports the Nintendo Switch Joy-Cons and Pro Controllers. The + * Pro Controllers can either be used over USB or Bluetooth. The Joy-Cons can + * be used either as a pair or individually in their horizontal mode. + * + * When a Joy-Con is paired, its LEDs will begin flashing to signal that it is + * in its "searching" mode. By holding the triggers of two Joy-Cons at the same + * time, they will be combined into one input. To use a Joy-Con alone in its + * horizontal mode, hold SL and SR at the same time while it is in the + * "searching" mode. + * + * 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 */ +#define SC_OUTPUT_RUMBLE_AND_SUBCMD 0x01 +#define SC_OUTPUT_FW_UPDATE_PKT 0x03 +#define SC_OUTPUT_RUMBLE_ONLY 0x10 +#define SC_OUTPUT_MCU_DATA 0x11 +#define SC_OUTPUT_USB_CMD 0x80 + +/* Subcommand IDs */ +#define SC_SUBCMD_STATE 0x00 +#define SC_SUBCMD_MANUAL_BT_PAIRING 0x01 +#define SC_SUBCMD_REQ_DEV_INFO 0x02 +#define SC_SUBCMD_SET_REPORT_MODE 0x03 +#define SC_SUBCMD_TRIGGERS_ELAPSED 0x04 +#define SC_SUBCMD_GET_PAGE_LIST_STATE 0x05 +#define SC_SUBCMD_SET_HCI_STATE 0x06 +#define SC_SUBCMD_RESET_PAIRING_INFO 0x07 +#define SC_SUBCMD_LOW_POWER_MODE 0x08 +#define SC_SUBCMD_SPI_FLASH_READ 0x10 +#define SC_SUBCMD_SPI_FLASH_WRITE 0x11 +#define SC_SUBCMD_RESET_MCU 0x20 +#define SC_SUBCMD_SET_MCU_CONFIG 0x21 +#define SC_SUBCMD_SET_MCU_STATE 0x22 +#define SC_SUBCMD_SET_PLAYER_LIGHTS 0x30 +#define SC_SUBCMD_GET_PLAYER_LIGHTS 0x31 +#define SC_SUBCMD_SET_HOME_LIGHT 0x38 +#define SC_SUBCMD_ENABLE_IMU 0x40 +#define SC_SUBCMD_SET_IMU_SENSITIVITY 0x41 +#define SC_SUBCMD_WRITE_IMU_REG 0x42 +#define SC_SUBCMD_READ_IMU_REG 0x43 +#define SC_SUBCMD_ENABLE_VIBRATION 0x48 +#define SC_SUBCMD_GET_REGULATED_VOLTAGE 0x50 + +/* Input Reports */ +#define SC_INPUT_BUTTON_EVENT 0x3F +#define SC_INPUT_SUBCMD_REPLY 0x21 +#define SC_INPUT_IMU_DATA 0x30 +#define SC_INPUT_MCU_DATA 0x31 +#define SC_INPUT_USB_RESPONSE 0x81 + +/* Feature Reports */ +#define SC_FEATURE_LAST_SUBCMD 0x02 +#define SC_FEATURE_OTA_FW_UPGRADE 0x70 +#define SC_FEATURE_SETUP_MEM_READ 0x71 +#define SC_FEATURE_MEM_READ 0x72 +#define SC_FEATURE_ERASE_MEM_SECTOR 0x73 +#define SC_FEATURE_MEM_WRITE 0x74 +#define SC_FEATURE_LAUNCH 0x75 + +/* USB Commands */ +#define SC_USB_CMD_CONN_STATUS 0x01 +#define SC_USB_CMD_HANDSHAKE 0x02 +#define SC_USB_CMD_BAUDRATE_3M 0x03 +#define SC_USB_CMD_NO_TIMEOUT 0x04 +#define SC_USB_CMD_EN_TIMEOUT 0x05 +#define SC_USB_RESET 0x06 +#define SC_USB_PRE_HANDSHAKE 0x91 +#define SC_USB_SEND_UART 0x92 + +/* SPI storage addresses of factory calibration data */ +#define SC_CAL_DATA_START 0x603d +#define SC_CAL_DATA_END 0x604e +#define SC_CAL_DATA_SIZE (SC_CAL_DATA_END - SC_CAL_DATA_START + 1) + + +/* The raw analog joystick values will be mapped in terms of this magnitude */ +#define SC_MAX_STICK_MAG 32767 +#define SC_STICK_FUZZ 250 +#define SC_STICK_FLAT 500 + +struct switchcon_ctlr; +struct switchcon_input; + +/* States for controller state machine */ +enum switchcon_ctlr_state { + SWITCHCON_CTLR_STATE_INIT, + SWITCHCON_CTLR_STATE_USB_SET_BAUD, + SWITCHCON_CTLR_STATE_USB_HANDSHAKE, + SWITCHCON_CTLR_STATE_CALIBRATION, + SWITCHCON_CTLR_STATE_POST_CALIBRATION, + SWITCHCON_CTLR_STATE_SEARCH, + SWITCHCON_CTLR_STATE_READ, +}; + +/* Function pointers will differ based on controller type */ +struct switchcon_impl { + int (*init)(struct switchcon_ctlr *ctlr); + void (*deinit)(struct switchcon_ctlr *ctlr); + int (*handle_event)(struct switchcon_ctlr *ctlr, u8 *data, int size); +}; + +struct switchcon_output { + u8 *data; + u8 size; +}; + +struct switchcon_stick_cal { + s32 x_max; + s32 x_min; + s32 x_center; + s32 y_max; + s32 y_min; + s32 y_center; +}; + +#define SC_OUTPUT_BUF_SIZE 50 +/* Each physical controller is associated with a switchcon_ctlr struct */ +struct switchcon_ctlr { + struct hid_device *hdev; + struct switchcon_input *switchcon_in; + const struct switchcon_impl *impl; + bool is_right_joycon; + bool searching; + enum switchcon_ctlr_state ctlr_state; + u8 subcmd_num; + struct switchcon_output output_buf[SC_OUTPUT_BUF_SIZE]; + u8 output_head; + u8 output_tail; + spinlock_t output_lock; + struct work_struct output_worker; + struct work_struct create_input_worker; + struct work_struct detect_pair_worker; + struct work_struct create_horiz_worker; + struct mutex mutex; + + /* factory calibration data */ + struct switchcon_stick_cal left_stick_cal; + struct switchcon_stick_cal right_stick_cal; + + /* buttons/sticks */ + u16 left_stick_x; + u16 left_stick_y; + u16 right_stick_x; + u16 right_stick_y; + bool but_y; + bool but_x; + bool but_b; + bool but_a; + bool but_sr_right_jc; + bool but_sl_right_jc; + bool but_r; + bool but_zr; + bool but_l; + bool but_zl; + bool but_minus; + bool but_plus; + bool but_rstick; + bool but_lstick; + bool but_home; + bool but_capture; + bool but_down; + bool but_up; + bool but_right; + bool but_left; + bool but_sr_left_jc; + bool but_sl_left_jc; + +}; + +enum switchcon_input_type { + SWITCHCON_INPUT_TYPE_PROCON, + SWITCHCON_INPUT_TYPE_JOYCONS, + SWITCHCON_INPUT_TYPE_JOYCON_H, +}; + +static const char * const switchcon_input_names[] = { + "Nintendo Switch Pro Controller", + "Nintendo Switch Joy-Cons", + "Nintendo Switch Joy-Con Horizontal", +}; + +/* Each linux input device is associated with a switchcon_input */ +struct switchcon_input { + enum switchcon_input_type type; + struct input_dev *input; + struct switchcon_ctlr *ctlr_left; /* left joycon or pro controller */ + struct switchcon_ctlr *ctlr_right; /* only used for joycons */ + struct mutex mutex; + struct work_struct input_worker; +}; + +static int switchcon_ctlr_init(struct switchcon_ctlr *ctlr) +{ + if (ctlr->impl && ctlr->impl->init) + return ctlr->impl->init(ctlr); + else + return -EINVAL; +} + +static void switchcon_ctlr_deinit(struct switchcon_ctlr *ctlr) +{ + if (ctlr->impl && ctlr->impl->deinit) + ctlr->impl->deinit(ctlr); +} + +static int switchcon_ctlr_handle_event(struct switchcon_ctlr *ctlr, u8 *data, + int size) +{ + if (ctlr->impl && ctlr->impl->handle_event) + return ctlr->impl->handle_event(ctlr, data, size); + else + return -EINVAL; +} + +static int switchcon_hid_queue_send(struct switchcon_ctlr *ctlr, + const u8 *buffer, size_t count) +{ + struct switchcon_output output; + + output.size = count; + output.data = kmemdup(buffer, count, GFP_ATOMIC); + if (!output.data) + return -ENOMEM; + + spin_lock(&ctlr->output_lock); + + ctlr->output_buf[ctlr->output_head++] = output; + if (ctlr->output_head == SC_OUTPUT_BUF_SIZE) + ctlr->output_head = 0; + schedule_work(&ctlr->output_worker); + + spin_unlock(&ctlr->output_lock); + return 0; +} + +static int switchcon_queue_subcommand(struct switchcon_ctlr *ctlr, + const u8 *buffer, size_t count) +{ + /* header including packet number and empty rumble data */ + u8 header[10] = {SC_OUTPUT_RUMBLE_AND_SUBCMD, ctlr->subcmd_num}; + struct switchcon_output output; + u8 *buf; + + /* the packet number loops after 0xF */ + ctlr->subcmd_num++; + if (ctlr->subcmd_num > 0xF) + ctlr->subcmd_num = 0; + + buf = kzalloc(sizeof(header) + count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, header, sizeof(header)); + memcpy(buf + sizeof(header), buffer, count); + output.data = buf; + output.size = count + sizeof(header); + + spin_lock(&ctlr->output_lock); + + ctlr->output_buf[ctlr->output_head++] = output; + if (ctlr->output_head == SC_OUTPUT_BUF_SIZE) + ctlr->output_head = 0; + schedule_work(&ctlr->output_worker); + + spin_unlock(&ctlr->output_lock); + return 0; +} + +static void switchcon_set_player_leds(struct switchcon_ctlr *ctlr, + u8 flash, u8 on) +{ + u8 buffer[] = {SC_SUBCMD_SET_PLAYER_LIGHTS, (flash << 4) | on}; + + switchcon_queue_subcommand(ctlr, buffer, sizeof(buffer)); +} + +static void switchcon_request_calibration(struct switchcon_ctlr *ctlr) +{ + u8 cal_subcmd[] = {SC_SUBCMD_SPI_FLASH_READ, + 0xFF & SC_CAL_DATA_START, + 0xFF & (SC_CAL_DATA_START >> 8), + 0, 0, SC_CAL_DATA_SIZE}; + + switchcon_queue_subcommand(ctlr, cal_subcmd, sizeof(cal_subcmd)); + hid_dbg(ctlr->hdev, "requested cal data\n"); +} + +static void switchcon_request_report_mode(struct switchcon_ctlr *ctlr) +{ + u8 inputmode_subcmd[] = {SC_SUBCMD_SET_REPORT_MODE, 0x30}; + + switchcon_queue_subcommand(ctlr, inputmode_subcmd, + sizeof(inputmode_subcmd)); + hid_dbg(ctlr->hdev, "requested 0x30 report mode\n"); +} + +static int switchcon_map_x_stick_val(struct switchcon_stick_cal *cal, s32 val) +{ + s32 center = cal->x_center; + s32 min = cal->x_min; + s32 max = cal->x_max; + s64 newVal; + + if (val > center) { + newVal = (val - center) * SC_MAX_STICK_MAG; + do_div(newVal, max - center); + } else { + newVal = (center - val) * SC_MAX_STICK_MAG; + do_div(newVal, center - min); + newVal *= -1; + } + if (newVal > SC_MAX_STICK_MAG) + newVal = SC_MAX_STICK_MAG; + if (newVal < -SC_MAX_STICK_MAG) + newVal = -SC_MAX_STICK_MAG; + return (int)newVal; +} + +static int switchcon_map_y_stick_val(struct switchcon_stick_cal *cal, s32 val) +{ + s32 center = cal->y_center; + s32 min = cal->y_min; + s32 max = cal->y_max; + s64 newVal; + + if (val > center) { + newVal = (val - center) * SC_MAX_STICK_MAG; + do_div(newVal, max - center); + } else { + newVal = (center - val) * SC_MAX_STICK_MAG; + do_div(newVal, center - min); + newVal *= -1; + } + if (newVal > SC_MAX_STICK_MAG) + newVal = SC_MAX_STICK_MAG; + if (newVal < -SC_MAX_STICK_MAG) + newVal = -SC_MAX_STICK_MAG; + return (int)newVal; +} + +static void switchcon_update_input_handler(struct work_struct *ws) +{ + struct switchcon_input *sc_input = container_of(ws, + struct switchcon_input, input_worker); + struct input_dev *dev = sc_input->input; + struct switchcon_ctlr *ctlr_l = sc_input->ctlr_left; + struct switchcon_ctlr *ctlr_r = sc_input->ctlr_right; + int s_l_x; + int s_l_y; + int s_r_x; + int s_r_y; + + if (!ctlr_r) + ctlr_r = ctlr_l; + + mutex_lock(&sc_input->mutex); + mutex_lock(&ctlr_l->mutex); + if (sc_input->ctlr_right) + mutex_lock(&sc_input->ctlr_right->mutex); + + /* map the stick values */ + s_l_x = switchcon_map_x_stick_val(&ctlr_l->left_stick_cal, + ctlr_l->left_stick_x); + s_l_y = switchcon_map_y_stick_val(&ctlr_l->left_stick_cal, + ctlr_l->left_stick_y); + s_r_x = switchcon_map_x_stick_val(&ctlr_r->right_stick_cal, + ctlr_r->right_stick_x); + s_r_y = switchcon_map_y_stick_val(&ctlr_r->right_stick_cal, + ctlr_r->right_stick_y); + + if (sc_input->type == SWITCHCON_INPUT_TYPE_JOYCON_H) { + if (ctlr_l->is_right_joycon) { + /* report stick */ + input_report_abs(dev, ABS_X, s_r_y); + input_report_abs(dev, ABS_Y, -s_r_x); + + /* report buttons */ + input_report_key(dev, BTN_WEST, ctlr_r->but_b); + input_report_key(dev, BTN_NORTH, ctlr_r->but_y); + input_report_key(dev, BTN_EAST, ctlr_r->but_x); + input_report_key(dev, BTN_SOUTH, ctlr_r->but_a); + input_report_key(dev, BTN_THUMBL, ctlr_r->but_rstick); + input_report_key(dev, BTN_START, ctlr_r->but_plus); + input_report_key(dev, BTN_MODE, ctlr_r->but_home); + input_report_key(dev, BTN_TR, + ctlr_r->but_sr_right_jc); + input_report_key(dev, BTN_TL, + ctlr_r->but_sl_right_jc); + } else { + /* report stick */ + input_report_abs(dev, ABS_X, -s_l_y); + input_report_abs(dev, ABS_Y, s_l_x); + + /* report buttons */ + input_report_key(dev, BTN_WEST, ctlr_l->but_up); + input_report_key(dev, BTN_NORTH, ctlr_l->but_right); + input_report_key(dev, BTN_EAST, ctlr_l->but_down); + input_report_key(dev, BTN_SOUTH, ctlr_l->but_left); + input_report_key(dev, BTN_THUMBL, ctlr_l->but_lstick); + input_report_key(dev, BTN_START, ctlr_l->but_minus); + input_report_key(dev, BTN_MODE, ctlr_l->but_capture); + input_report_key(dev, BTN_TR, + ctlr_l->but_sr_left_jc); + input_report_key(dev, BTN_TL, + ctlr_l->but_sl_left_jc); + } + } else { + /* report sticks */ + input_report_abs(dev, ABS_X, s_l_x); + input_report_abs(dev, ABS_Y, s_l_y); + input_report_abs(dev, ABS_RX, s_r_x); + input_report_abs(dev, ABS_RY, s_r_y); + + /* report buttons */ + input_report_key(dev, BTN_WEST, ctlr_r->but_y); + input_report_key(dev, BTN_NORTH, ctlr_r->but_x); + input_report_key(dev, BTN_EAST, ctlr_r->but_a); + input_report_key(dev, BTN_SOUTH, ctlr_r->but_b); + input_report_key(dev, BTN_TR, ctlr_r->but_r); + input_report_key(dev, BTN_TR2, ctlr_r->but_zr); + input_report_key(dev, BTN_TL, ctlr_l->but_l); + input_report_key(dev, BTN_TL2, ctlr_l->but_zl); + input_report_key(dev, BTN_SELECT, ctlr_l->but_minus); + input_report_key(dev, BTN_START, ctlr_r->but_plus); + input_report_key(dev, BTN_THUMBL, ctlr_l->but_lstick); + input_report_key(dev, BTN_THUMBR, ctlr_r->but_rstick); + input_report_key(dev, BTN_MODE, ctlr_r->but_home); + input_report_key(dev, BTN_Z, ctlr_l->but_capture); + input_report_key(dev, BTN_DPAD_DOWN, ctlr_l->but_down); + input_report_key(dev, BTN_DPAD_UP, ctlr_l->but_up); + input_report_key(dev, BTN_DPAD_RIGHT, ctlr_l->but_right); + input_report_key(dev, BTN_DPAD_LEFT, ctlr_l->but_left); + } + + input_sync(dev); + + mutex_unlock(&ctlr_l->mutex); + if (sc_input->ctlr_right) + mutex_unlock(&sc_input->ctlr_right->mutex); + mutex_unlock(&sc_input->mutex); +} + +static void switchcon_input_destroy(struct switchcon_input *sc_input) +{ + hid_dbg(sc_input->ctlr_left->hdev, "destroying input\n"); + mutex_lock(&sc_input->mutex); + + sc_input->ctlr_left->switchcon_in = NULL; + mutex_lock(&sc_input->ctlr_left->mutex); + if (sc_input->ctlr_right) { + mutex_lock(&sc_input->ctlr_right->mutex); + sc_input->ctlr_right->switchcon_in = NULL; + } + /* set the joycon states to searching */ + sc_input->ctlr_left->ctlr_state = SWITCHCON_CTLR_STATE_POST_CALIBRATION; + if (sc_input->ctlr_right) + sc_input->ctlr_right->ctlr_state = + SWITCHCON_CTLR_STATE_POST_CALIBRATION; + + input_unregister_device(sc_input->input); + + mutex_unlock(&sc_input->ctlr_left->mutex); + if (sc_input->ctlr_right) + mutex_unlock(&sc_input->ctlr_right->mutex); + mutex_unlock(&sc_input->mutex); + mutex_destroy(&sc_input->mutex); + kfree(sc_input); +} + +static const unsigned int switchcon_button_inputs[] = { + BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST, + BTN_TL, BTN_TR, BTN_START, BTN_MODE, BTN_THUMBL, + /* the following buttons do not apply to a single, horizontal joycon */ + BTN_TL2, BTN_TR2, BTN_SELECT, BTN_Z, BTN_THUMBR, + BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, + 0 /* 0 signals end of array */ +}; + +DEFINE_MUTEX(switchcon_input_num_mutex); +static struct switchcon_input *switchcon_input_create( + enum switchcon_input_type type, + struct switchcon_ctlr *ctlr_l, + struct switchcon_ctlr *ctlr_r) +{ + struct switchcon_input *sc_input; + struct hid_device *hdev; + static int input_num = 1; + int i; + int ret; + + if (!ctlr_l) + return ERR_PTR(-EINVAL); + hdev = ctlr_l->hdev; + sc_input = kzalloc(sizeof(*sc_input), GFP_KERNEL); + if (!sc_input) + return ERR_PTR(-ENOMEM); + + sc_input->type = type; + sc_input->ctlr_left = ctlr_l; + sc_input->ctlr_right = ctlr_r; + if (!ctlr_r) + ctlr_r = ctlr_l; + + sc_input->input = input_allocate_device(); + if (!sc_input->input) { + ret = -ENOMEM; + goto err; + } + sc_input->input->dev.parent = &hdev->dev; + sc_input->input->id.bustype = hdev->bus; + sc_input->input->id.vendor = hdev->vendor; + sc_input->input->id.product = hdev->product; + sc_input->input->id.version = hdev->version; + sc_input->input->name = switchcon_input_names[sc_input->type]; + input_set_drvdata(sc_input->input, sc_input); + + /* set up button inputs */ + for (i = 0; switchcon_button_inputs[i] > 0; i++) { + input_set_capability(sc_input->input, EV_KEY, + switchcon_button_inputs[i]); + if (switchcon_button_inputs[i] == BTN_THUMBL + && sc_input->type == SWITCHCON_INPUT_TYPE_JOYCON_H) + break; + } + + /* set up sticks */ + input_set_abs_params(sc_input->input, ABS_X, + -SC_MAX_STICK_MAG, SC_MAX_STICK_MAG, + SC_STICK_FUZZ, SC_STICK_FLAT); + input_set_abs_params(sc_input->input, ABS_Y, + -SC_MAX_STICK_MAG, SC_MAX_STICK_MAG, + SC_STICK_FUZZ, SC_STICK_FLAT); + /* set up right stick if necessary for controller type */ + if (sc_input->type != SWITCHCON_INPUT_TYPE_JOYCON_H) { + input_set_abs_params(sc_input->input, ABS_RX, + -SC_MAX_STICK_MAG, SC_MAX_STICK_MAG, + SC_STICK_FUZZ, SC_STICK_FLAT); + input_set_abs_params(sc_input->input, ABS_RY, + -SC_MAX_STICK_MAG, SC_MAX_STICK_MAG, + SC_STICK_FUZZ, SC_STICK_FLAT); + } + + ret = input_register_device(sc_input->input); + if (ret) + goto err_input; + + mutex_init(&sc_input->mutex); + INIT_WORK(&sc_input->input_worker, switchcon_update_input_handler); + + /* Set the controller player leds based on input number */ + mutex_lock(&switchcon_input_num_mutex); + /* Need to send multiple times to make sure it's not ignored */ + for (i = 0; i < 5; i++) { + switchcon_set_player_leds(ctlr_l, 0, 0xF >> (4 - input_num)); + switchcon_set_player_leds(ctlr_r, 0, 0xF >> (4 - input_num)); + } + if (++input_num > 4) + input_num = 1; + mutex_unlock(&switchcon_input_num_mutex); + + return sc_input; +err_input: + input_free_device(sc_input->input); +err: + kfree(sc_input); + return ERR_PTR(ret); +} + +static void switchcon_create_procon_input_handler(struct work_struct *ws) +{ + struct switchcon_ctlr *ctlr = container_of(ws, struct switchcon_ctlr, + create_input_worker); + hid_dbg(ctlr->hdev, "create_procon_input_handler\n"); + + mutex_lock(&ctlr->mutex); + + ctlr->switchcon_in = switchcon_input_create(SWITCHCON_INPUT_TYPE_PROCON, + ctlr, NULL); + if (IS_ERR(ctlr->switchcon_in)) { + hid_err(ctlr->hdev, "failed to create input\n"); + ctlr->switchcon_in = NULL; + } + + mutex_unlock(&ctlr->mutex); +} + +DEFINE_MUTEX(switchcon_detect_pair_mutex); +static void switchcon_detect_pair_handler(struct work_struct *ws) +{ + struct switchcon_ctlr *ctlr = container_of(ws, struct switchcon_ctlr, + detect_pair_worker); + static struct switchcon_ctlr *ctlr_l; + static struct switchcon_ctlr *ctlr_r; + + mutex_lock(&switchcon_detect_pair_mutex); + mutex_lock(&ctlr->mutex); + /* Check if this is a controller no longer searching for partner */ + if (!ctlr->searching) { + if (ctlr == ctlr_l) + ctlr_l = NULL; + if (ctlr == ctlr_r) + ctlr_r = NULL; + } else { + if (!ctlr->is_right_joycon && ctlr_l == NULL) + ctlr_l = ctlr; + if (ctlr->is_right_joycon && ctlr_r == NULL) + ctlr_r = ctlr; + + /* see if there's a pair */ + if (ctlr_l && ctlr_r) { + if (ctlr == ctlr_l) + mutex_lock(&ctlr_r->mutex); + else + mutex_lock(&ctlr_l->mutex); + hid_info(ctlr->hdev, "Joy-Con pair found\n"); + + ctlr_l->switchcon_in = switchcon_input_create( + SWITCHCON_INPUT_TYPE_JOYCONS, + ctlr_l, ctlr_r); + ctlr_r->switchcon_in = ctlr_l->switchcon_in; + ctlr_l->ctlr_state = SWITCHCON_CTLR_STATE_READ; + ctlr_r->ctlr_state = SWITCHCON_CTLR_STATE_READ; + + if (IS_ERR(ctlr_l->switchcon_in)) { + hid_err(ctlr->hdev, "Failed creating input\n"); + ctlr_l->switchcon_in = NULL; + ctlr_r->switchcon_in = NULL; + } + + if (ctlr == ctlr_l) + mutex_unlock(&ctlr_r->mutex); + else + mutex_unlock(&ctlr_l->mutex); + ctlr_l = NULL; + ctlr_r = NULL; + } + } + + mutex_unlock(&ctlr->mutex); + mutex_unlock(&switchcon_detect_pair_mutex); +} + +static void switchcon_create_horiz_handler(struct work_struct *ws) +{ + struct switchcon_ctlr *ctlr = container_of(ws, struct switchcon_ctlr, + create_horiz_worker); + + mutex_lock(&ctlr->mutex); + + ctlr->switchcon_in = switchcon_input_create( + SWITCHCON_INPUT_TYPE_JOYCON_H, ctlr, NULL); + if (IS_ERR(ctlr->switchcon_in)) { + hid_err(ctlr->hdev, "failed to create input\n"); + ctlr->switchcon_in = NULL; + } + + mutex_unlock(&ctlr->mutex); +} + +static void switchcon_send_work_handler(struct work_struct *ws) +{ + struct switchcon_ctlr *ctlr = container_of(ws, struct switchcon_ctlr, + output_worker); + struct switchcon_output output; + struct hid_device *hdev = ctlr->hdev; + int ret; + + spin_lock(&ctlr->output_lock); + while (ctlr->output_tail != ctlr->output_head) { + output = ctlr->output_buf[ctlr->output_tail]; + ctlr->output_buf[ctlr->output_tail].data = NULL; + ctlr->output_tail++; + if (ctlr->output_tail == SC_OUTPUT_BUF_SIZE) + ctlr->output_tail = 0; + spin_unlock(&ctlr->output_lock); + if (!output.data) { + hid_warn(ctlr->hdev, "invalid output in buffer\n"); + } else { + ret = hid_hw_output_report(hdev, output.data, + output.size); + if (ret < 0) + hid_warn(hdev, "failed output report ret=%d", + ret); + kfree(output.data); + + } + spin_lock(&ctlr->output_lock); + } + spin_unlock(&ctlr->output_lock); +} + +static int switchcon_hid_event(struct hid_device *hdev, + struct hid_report *report, u8 *raw_data, int size) +{ + struct switchcon_ctlr *ctlr = hid_get_drvdata(hdev); + + if (size < 1) + return -EINVAL; + + return switchcon_ctlr_handle_event(ctlr, raw_data, size); +} + +/* data input must have at least 9 bytes */ +static void switchcon_parse_lstick_calibration(u8 *data, + struct switchcon_stick_cal *cal) +{ + s32 x_max_above; + s32 x_min_below; + s32 y_max_above; + s32 y_min_below; + + x_max_above = ((data[1] << 8) & 0xF00) | data[0]; + y_max_above = (data[2] << 4) | (data[1] >> 4); + x_min_below = ((data[7] << 8) & 0xF00) | data[6]; + y_min_below = (data[8] << 4) | (data[7] >> 4); + cal->x_center = ((data[4] << 8) & 0xF00) | data[3]; + cal->y_center = (data[5] << 4) | (data[4] >> 4); + 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; +} + +/* data input must have at least 9 bytes */ +static void switchcon_parse_rstick_calibration(u8 *data, + struct switchcon_stick_cal *cal) +{ + s32 x_max_above; + s32 x_min_below; + s32 y_max_above; + s32 y_min_below; + + cal->x_center = ((data[1] << 8) & 0xF00) | data[0]; + cal->y_center = (data[2] << 4) | (data[1] >> 4); + x_max_above = ((data[7] << 8) & 0xF00) | data[6]; + y_max_above = (data[8] << 4) | (data[7] >> 4); + x_min_below = ((data[4] << 8) & 0xF00) | data[3]; + y_min_below = (data[5] << 4) | (data[4] >> 4); + 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; +} + +/* Common handler for parsing inputs */ +static int switchcon_ctlr_read_handler(struct switchcon_ctlr *ctlr, + u8 *data, int size) +{ + int ret = 0; + + switch (data[0]) { + case SC_INPUT_SUBCMD_REPLY: + case SC_INPUT_IMU_DATA: + case SC_INPUT_MCU_DATA: + if (size < 12) /* make sure it contains the input report */ + break; + + /* Parse analog sticks */ + ctlr->left_stick_x = data[6] | ((data[7] & 0xF) << 8); + ctlr->left_stick_y = (data[7] >> 4) | (data[8] << 4); + ctlr->right_stick_x = data[9] | ((data[10] & 0xF) << 8); + ctlr->right_stick_y = (data[10] >> 4) | (data[11] << 4); + + /* Parse digital buttons */ + ctlr->but_y = 0x01 & data[3]; + ctlr->but_x = 0x02 & data[3]; + ctlr->but_b = 0x04 & data[3]; + ctlr->but_a = 0x08 & data[3]; + ctlr->but_sr_right_jc = 0x10 & data[3]; + ctlr->but_sl_right_jc = 0x20 & data[3]; + ctlr->but_r = 0x40 & data[3]; + ctlr->but_zr = 0x80 & data[3]; + ctlr->but_l = 0x40 & data[5]; + ctlr->but_zl = 0x80 & data[5]; + ctlr->but_minus = 0x01 & data[4]; + ctlr->but_plus = 0x02 & data[4]; + ctlr->but_rstick = 0x04 & data[4]; + ctlr->but_lstick = 0x08 & data[4]; + ctlr->but_home = 0x10 & data[4]; + ctlr->but_capture = 0x20 & data[4]; + ctlr->but_down = 0x01 & data[5]; + ctlr->but_up = 0x02 & data[5]; + ctlr->but_right = 0x04 & data[5]; + ctlr->but_left = 0x08 & data[5]; + ctlr->but_sr_left_jc = 0x10 & data[5]; + ctlr->but_sl_left_jc = 0x20 & data[5]; + + break; + case SC_INPUT_BUTTON_EVENT: + /* the controller is in wrong request mode */ + switchcon_request_report_mode(ctlr); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int switchcon_ctlr_procon_init(struct switchcon_ctlr *ctlr) +{ + int ret; + u8 baud_cmd[] = {SC_OUTPUT_USB_CMD, SC_USB_CMD_BAUDRATE_3M}; + + mutex_lock(&ctlr->mutex); + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_USB_SET_BAUD; + ret = switchcon_hid_queue_send(ctlr, baud_cmd, sizeof(baud_cmd)); + mutex_unlock(&ctlr->mutex); + return ret; +} + +static int switchcon_ctlr_joyconl_init(struct switchcon_ctlr *ctlr) +{ + mutex_lock(&ctlr->mutex); + switchcon_request_calibration(ctlr); + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_CALIBRATION; + ctlr->is_right_joycon = false; + mutex_unlock(&ctlr->mutex); + return 0; +} + +static int switchcon_ctlr_joyconr_init(struct switchcon_ctlr *ctlr) +{ + mutex_lock(&ctlr->mutex); + switchcon_request_calibration(ctlr); + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_CALIBRATION; + ctlr->is_right_joycon = true; + switchcon_request_calibration(ctlr); + mutex_unlock(&ctlr->mutex); + return 0; +} + +void switchcon_ctlr_general_deinit(struct switchcon_ctlr *ctlr) +{ + if (ctlr->switchcon_in) + switchcon_input_destroy(ctlr->switchcon_in); + mutex_lock(&ctlr->mutex); + flush_work(&ctlr->output_worker); + mutex_unlock(&ctlr->mutex); + mutex_destroy(&ctlr->mutex); +} + +static int switchcon_ctlr_general_handle_event(struct switchcon_ctlr *ctlr, + u8 *data, int size) +{ + mutex_lock(&ctlr->mutex); + switch (ctlr->ctlr_state) { + case SWITCHCON_CTLR_STATE_CALIBRATION: + if (data[0] != SC_INPUT_SUBCMD_REPLY || size < 38 + || data[13] != 0x90 || data[14] != 0x10) { + /* resend request if it wasn't received */ + switchcon_request_calibration(ctlr); + break; + } + switchcon_parse_lstick_calibration(data + 20, + &ctlr->left_stick_cal); + switchcon_parse_rstick_calibration(data + 29, + &ctlr->right_stick_cal); + + hid_info(ctlr->hdev, "l_x_c=%d l_x_max=%d l_x_min=%d\n", + ctlr->left_stick_cal.x_center, + ctlr->left_stick_cal.x_max, + ctlr->left_stick_cal.x_min); + hid_info(ctlr->hdev, "l_y_c=%d l_y_max=%d l_y_min=%d\n", + ctlr->left_stick_cal.y_center, + ctlr->left_stick_cal.y_max, + ctlr->left_stick_cal.y_min); + hid_info(ctlr->hdev, "r_x_c=%d r_x_max=%d r_x_min=%d\n", + ctlr->right_stick_cal.x_center, + ctlr->right_stick_cal.x_max, + ctlr->right_stick_cal.x_min); + hid_info(ctlr->hdev, "r_y_c=%d r_y_max=%d r_y_min=%d\n", + ctlr->right_stick_cal.y_center, + ctlr->right_stick_cal.y_max, + ctlr->right_stick_cal.y_min); + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_POST_CALIBRATION; + switchcon_request_report_mode(ctlr); + break; + case SWITCHCON_CTLR_STATE_READ: + switchcon_ctlr_read_handler(ctlr, data, size); + if (ctlr->switchcon_in) + schedule_work(&ctlr->switchcon_in->input_worker); + break; + default: + break; + } + mutex_unlock(&ctlr->mutex); + + return 0; +} + +static int switchcon_ctlr_procon_handle_event(struct switchcon_ctlr *ctlr, + u8 *data, int size) +{ + int ret; + u8 handshake_cmd[] = {SC_OUTPUT_USB_CMD, SC_USB_CMD_HANDSHAKE}; + u8 timeout_cmd[] = {SC_OUTPUT_USB_CMD, SC_USB_CMD_NO_TIMEOUT}; + + mutex_lock(&ctlr->mutex); + switch (ctlr->ctlr_state) { + case SWITCHCON_CTLR_STATE_USB_SET_BAUD: + if (size < 2 || data[0] != SC_INPUT_USB_RESPONSE + || data[1] != SC_USB_CMD_BAUDRATE_3M) { + hid_dbg(ctlr->hdev, "No usb response, assume ble\n"); + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_CALIBRATION; + switchcon_request_calibration(ctlr); + break; + } + + ret = switchcon_hid_queue_send(ctlr, handshake_cmd, + sizeof(handshake_cmd)); + if (!ret) { + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_USB_HANDSHAKE; + hid_dbg(ctlr->hdev, "sent handshake\n"); + } + break; + case SWITCHCON_CTLR_STATE_USB_HANDSHAKE: + if (size < 2 || data[0] != SC_INPUT_USB_RESPONSE + || data[1] != SC_USB_CMD_HANDSHAKE) + break; + ret = switchcon_hid_queue_send(ctlr, timeout_cmd, + sizeof(timeout_cmd)); + if (!ret) { + hid_dbg(ctlr->hdev, "sent timeout disable\n"); + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_CALIBRATION; + switchcon_request_calibration(ctlr); + } + break; + case SWITCHCON_CTLR_STATE_POST_CALIBRATION: + schedule_work(&ctlr->create_input_worker); + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_READ; + break; + default: + mutex_unlock(&ctlr->mutex); + return switchcon_ctlr_general_handle_event(ctlr, data, size); + } + mutex_unlock(&ctlr->mutex); + return 0; +} + +static int switchcon_ctlr_joycon_handle_event(struct switchcon_ctlr *ctlr, + u8 *data, int size) +{ + bool last_searching; + int i; + + mutex_lock(&ctlr->mutex); + switch (ctlr->ctlr_state) { + case SWITCHCON_CTLR_STATE_POST_CALIBRATION: + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_SEARCH; + /* flashing leds to indicate searching */ + for (i = 0; i < 10; i++) + /* command multiple times to ensure it works */ + switchcon_set_player_leds(ctlr, 0b1111, 0); + /* intentional fall-through */ + case SWITCHCON_CTLR_STATE_SEARCH: + last_searching = ctlr->searching; + switchcon_ctlr_read_handler(ctlr, data, size); + ctlr->searching = ctlr->but_r || ctlr->but_zr + || ctlr->but_l || ctlr->but_zl; + if (ctlr->searching != last_searching) { + schedule_work(&ctlr->detect_pair_worker); + } else if ((ctlr->but_sr_left_jc && ctlr->but_sl_left_jc) + || (ctlr->but_sr_right_jc && ctlr->but_sl_right_jc)) { + schedule_work(&ctlr->create_horiz_worker); + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_READ; + } + break; + default: + mutex_unlock(&ctlr->mutex); + return switchcon_ctlr_general_handle_event(ctlr, data, size); + } + mutex_unlock(&ctlr->mutex); + return 0; +} + +/* Implementations for each supported controller type */ +static const struct switchcon_impl switchcon_impl_procon = { + .init = switchcon_ctlr_procon_init, + .deinit = switchcon_ctlr_general_deinit, + .handle_event = switchcon_ctlr_procon_handle_event, +}; + +static const struct switchcon_impl switchcon_impl_joycon_l = { + .init = switchcon_ctlr_joyconl_init, + .deinit = switchcon_ctlr_general_deinit, + .handle_event = switchcon_ctlr_joycon_handle_event, +}; + +static const struct switchcon_impl switchcon_impl_joycon_r = { + .init = switchcon_ctlr_joyconr_init, + .deinit = switchcon_ctlr_general_deinit, + .handle_event = switchcon_ctlr_joycon_handle_event, +}; + +static struct switchcon_ctlr *switchcon_ctlr_create(struct hid_device *hdev) +{ + struct switchcon_ctlr *ctlr; + + ctlr = devm_kzalloc(&hdev->dev, sizeof(*ctlr), GFP_KERNEL); + if (!ctlr) + return ERR_PTR(-ENOMEM); + + switch (hdev->product) { + case USB_DEVICE_ID_NINTENDO_PROCON: + hid_info(hdev, "detected pro controller\n"); + ctlr->impl = &switchcon_impl_procon; + break; + case USB_DEVICE_ID_NINTENDO_JOYCONL: + hid_info(hdev, "detected left joy-con\n"); + ctlr->impl = &switchcon_impl_joycon_l; + break; + case USB_DEVICE_ID_NINTENDO_JOYCONR: + hid_info(hdev, "detected right joy-con\n"); + ctlr->impl = &switchcon_impl_joycon_r; + break; + default: + hid_err(hdev, "unknown product id\n"); + return ERR_PTR(-EINVAL); + } + ctlr->hdev = hdev; + ctlr->ctlr_state = SWITCHCON_CTLR_STATE_INIT; + hid_set_drvdata(hdev, ctlr); + spin_lock_init(&ctlr->output_lock); + mutex_init(&ctlr->mutex); + INIT_WORK(&ctlr->output_worker, switchcon_send_work_handler); + INIT_WORK(&ctlr->create_input_worker, + switchcon_create_procon_input_handler); + INIT_WORK(&ctlr->detect_pair_worker, switchcon_detect_pair_handler); + INIT_WORK(&ctlr->create_horiz_worker, switchcon_create_horiz_handler); + return ctlr; +} + +static int switchcon_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + struct switchcon_ctlr *ctlr; + + hid_dbg(hdev, "probe - start\n"); + hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS; + + ctlr = switchcon_ctlr_create(hdev); + if (IS_ERR(ctlr)) { + hid_err(hdev, "Failed to create new controller\n"); + ret = PTR_ERR(ctlr); + goto err; + } + + 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; + } + + ret = switchcon_ctlr_init(ctlr); + if (ret) { + hid_err(hdev, "failed to initialize ctlr\n"); + goto err_close; + } + + hid_dbg(hdev, "probe - success\n"); + return 0; + +err_close: + switchcon_ctlr_deinit(ctlr); + hid_hw_close(hdev); +err_stop: + hid_hw_stop(hdev); +err: + hid_err(hdev, "probe - fail = %d\n", ret); + return ret; +} + +static void switchcon_hid_remove(struct hid_device *hdev) +{ + struct switchcon_ctlr *ctlr = hid_get_drvdata(hdev); + + hid_dbg(hdev, "remove\n"); + hid_hw_close(hdev); + hid_hw_stop(hdev); + switchcon_ctlr_deinit(ctlr); +} + +static const struct hid_device_id switchcon_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, switchcon_hid_devices); + +static struct hid_driver switchcon_hid_driver = { + .name = "switchcon", + .id_table = switchcon_hid_devices, + .probe = switchcon_hid_probe, + .remove = switchcon_hid_remove, + .raw_event = switchcon_hid_event, +}; +module_hid_driver(switchcon_hid_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel J. Ogorchock "); +MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");