From 61dd4e3969ca4fbf99ce64a5ffd94a3c82cec99e Mon Sep 17 00:00:00 2001
From: shinyquagsire23 <[email protected]>
Date: Wed, 16 May 2018 16:26:15 -0600
Subject: [PATCH] input: working Joy-Con rails driver
Initial ver by Shiny Quagsquire
Crash fixes by @CTCear
Fix for serdev return status by @bylaws
---
drivers/input/joystick/Kconfig | 12 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/joycon.c | 950 ++++++++++++++++++++++++++++++++
3 files changed, 963 insertions(+)
create mode 100644 drivers/input/joystick/joycon.c
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index d8f9c6e1fc08..14120ce75057 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -277,6 +277,18 @@ config JOYSTICK_JOYDUMP
To compile this driver as a module, choose M here: the
module will be called joydump.
+config JOYSTICK_JOYCON
+ tristate "Joy-Con Rails"
+ select SERIAL_DEV_BUS
+ select POWER_SUPPLY
+ select CRC8
+ help
+ Say Y here if you have a one or more Joy-Con connected over UART
+
+ To compile this driver as a module, choose M here: the
+ module will be called joycon.
+
+
config JOYSTICK_XPAD
tristate "X-Box gamepad support"
depends on USB_ARCH_HAS_HCD
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index dd0492ebbed7..6a45ee91801f 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_JOYSTICK_GRIP_MP) += grip_mp.o
obj-$(CONFIG_JOYSTICK_GUILLEMOT) += guillemot.o
obj-$(CONFIG_JOYSTICK_IFORCE) += iforce/
obj-$(CONFIG_JOYSTICK_INTERACT) += interact.o
+obj-$(CONFIG_JOYSTICK_JOYCON) += joycon.o
obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o
obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o
obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o
diff --git a/drivers/input/joystick/joycon.c b/drivers/input/joystick/joycon.c
new file mode 100644
index 000000000000..062eb0eb523a
--- /dev/null
+++ b/drivers/input/joystick/joycon.c
@@ -0,0 +1,950 @@
+/*
+ * Copyright (c) 2018 Max Thomas
+ */
+
+/*
+ * Nintendo Joy-Con serial gamepad driver for Linux
+ */
+
+/*
+ * This program is free warftware; 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <[email protected]>, or by paper mail:
+ * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/of.h>
+#include <linux/tty.h>
+#include <linux/serdev.h>
+#include <linux/platform_device.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/crc8.h>
+#include <linux/power_supply.h>
+#include <linux/gpio/consumer.h>
+
+#define DRIVER_DESC "Nintendo Joy-Con serial gamepad driver"
+
+MODULE_AUTHOR("Max Thomas <[email protected]>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+
+/*
+ * Constants.
+ */
+
+
+static const u8 switch_baud[0x14] = {0x19, 0x01, 0x03, 0x0F, 0x00, 0x91, 0x20, 0x08, 0x00, 0x00, 0xBD, 0xB1, 0xC0, 0xC6, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00};
+static const u8 controller_status[0xD] = {0x19, 0x01, 0x03, 0x08, 0x00, 0x92, 0x00, 0x01, 0x00, 0x00, 0x69, 0x2D, 0x1F};
+static const u8 unk_3[0x10] = {0x19, 0x01, 0x03, 0x0B, 0x00, 0x91, 0x12, 0x04, 0x00, 0x00, 0x12, 0xA6, 0x0F, 0x00, 0x00, 0x00};
+
+#define JOYCON_COMMAND_EXTSEND (0x91)
+#define JOYCON_COMMAND_EXTRET (0x92)
+#define JOYCON_COMMAND_INITRET (0x94)
+#define JOYCON_COMMAND_HANDSHAKE (0xA5)
+
+#define JOYCON_INIT_MAC (0x1)
+#define JOYCON_INIT_BAUDRATE (0x20)
+#define JOYCON_INIT_UNK1 (0x11)
+#define JOYCON_INIT_UNK2 (0x10)
+#define JOYCON_INIT_UNK3 (0x12)
+
+#define JOYCON_EXT_ACK (0x1F)
+#define JOYCON_EXT_INPUT (0x30)
+#define JOYCON_EXT_HIDCOMMAND (0x21)
+
+#define JOYCON_HID_DEVICE_INFO (0x2)
+#define JOYCON_HID_SPI_READ (0x10)
+#define JOYCON_HID_GET_REGVOLT (0x50)
+
+#define JOYCON_SPI_FACTORY_CAL_2 (0x603D)
+#define JOYCON_SPI_FACTORY_CAL_3 (0x6080)
+#define JOYCON_SPI_STICK_USERCAL (0x8010)
+
+#define JOYCON_BATT_MV_MIN (3300)
+#define JOYCON_BATT_MV_MAX (4200)
+
+#define JOYCON_BUTTONS_LEFT (0xFFE900)
+#define JOYCON_BUTTONS_RIGHT (0x76FF)
+#define JOYCON_BUTTONS_ALL (JOYCON_BUTTONS_LEFT | JOYCON_BUTTONS_RIGHT)
+
+#define JOYCON_BUTTONSET_LEFT BIT(0)
+#define JOYCON_BUTTONSET_RIGHT BIT(1)
+
+#define JOYCON_CRC8_POLY (0x8D)
+#define JOYCON_CRC8_INIT (0x00)
+
+typedef struct joycon_uart_initial
+{
+ u8 magic[3];
+ u8 total_size;
+ u8 pad;
+} joycon_uart_initial;
+
+typedef struct joycon_uart_header
+{
+ joycon_uart_initial initial;
+ u8 command;
+ u8 data[5];
+ u8 crc;
+} joycon_uart_header;
+
+typedef struct joycon_subcmd_packet
+{
+ joycon_uart_header pre;
+ u8 command;
+ u8 data[38];
+} joycon_subcmd_packet;
+
+struct joycon_stick_calib
+{
+ u16 x_max;
+ u16 y_max;
+ u16 x_cent;
+ u16 y_cent;
+ u16 x_min;
+ u16 y_min;
+};
+
+/* Joy-Con button mappings */
+static const signed short joycon_common_btn[] = {
+ BTN_Y, BTN_X, BTN_B, BTN_A,
+ BTN_0, BTN_1, BTN_TR, BTN_TR2,
+ BTN_SELECT, BTN_START,
+ BTN_THUMBR, BTN_THUMBL,
+ BTN_MODE, BTN_Z,
+ BTN_4, BTN_5, /* These two buttons do not exist on retail controllers. */
+ BTN_DPAD_DOWN, BTN_DPAD_UP, BTN_DPAD_RIGHT, BTN_DPAD_LEFT,
+ BTN_2, BTN_3, BTN_TL, BTN_TL2,
+ -1 /* terminating entry */
+};
+
+/* Driver data */
+struct joycon_device {
+ struct list_head list;
+
+ struct device *dev;
+
+ const char *name;
+ int irq;
+};
+
+/*
+ * Per-Joy-Con data.
+ */
+
+struct joycon {
+ struct serdev_device *serdev;
+ struct input_dev *input_dev;
+ struct power_supply *batt_dev;
+ struct device *dev;
+ struct power_supply_desc batt_desc;
+ struct gpio_desc *charge_gpio;
+
+ bool handshaken;
+ bool initialized;
+ bool factory_cal_2_parsed;
+ bool factory_cal_3_parsed;
+ bool usercal_parsed;
+
+ int timeout_samples;
+ int num_samples;
+ int reconnect_threshold;
+
+ u8 button_set;
+ struct joycon_stick_calib l_calib;
+ struct joycon_stick_calib r_calib;
+
+ struct workqueue_struct *wq;
+
+ int idx;
+ u16 regvolt;
+ u32 buttons;
+ u8 mac[6];
+};
+
+/*
+ * Globals
+ */
+static DEFINE_MUTEX(joycon_device_lock);
+static DEFINE_MUTEX(joycon_input_lock);
+static LIST_HEAD(joycon_device_list);
+
+static u8 crc_table[CRC8_TABLE_SIZE];
+
+static struct serdev_device_ops joycon_ops;
+static struct input_dev *input_dev;
+static u32 buttons;
+static int stick_lx, stick_ly, stick_rx, stick_ry;
+static int joycon_increment;
+
+
+struct work_timeout_poll {
+ struct delayed_work work;
+ struct joycon* joycon;
+};
+
+struct work_input_poll {
+ struct delayed_work work;
+ struct joycon* joycon;
+};
+
+struct work_sync_poll {
+ struct delayed_work work;
+ struct joycon* joycon;
+};
+
+u8 joycon_crc8(u8* data, size_t len)
+{
+ return crc8(crc_table, data, len, JOYCON_CRC8_INIT);
+}
+
+/*
+ * Battery stuff
+ */
+
+static enum power_supply_property joycon_battery_props[] = {
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+};
+
+static int joycon_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct joycon *joycon = power_supply_get_drvdata(psy);
+ u32 regvolt_scaled, total_scaled;
+
+ switch (psp)
+ {
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ regvolt_scaled = ((joycon->regvolt - JOYCON_BATT_MV_MIN) * 100);
+ total_scaled = (JOYCON_BATT_MV_MAX - JOYCON_BATT_MV_MIN);
+ val->intval = regvolt_scaled / total_scaled;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = joycon->regvolt * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = JOYCON_BATT_MV_MAX * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = JOYCON_BATT_MV_MIN * 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Packet crafting
+ */
+
+u16 joycon_packet_craft(u8 *out, u8 command, u8 *data, u16 size)
+{
+ u16 packet_size = sizeof(joycon_uart_header);
+ joycon_uart_header *pre = (joycon_uart_header*)out;
+
+ pre->initial.magic[0] = 0x19;
+ pre->initial.magic[1] = 0x01;
+ pre->initial.magic[2] = 0x3;
+
+ pre->initial.total_size = 7;
+ pre->command = command;
+
+ if (data)
+ memcpy(pre->data, data, size);
+
+ pre->crc = joycon_crc8((void*)pre + 0x4, 0x7);
+
+ return packet_size;
+}
+
+u16 joycon_extcommand_craft(u8 *out, u8 command, u8 *data, u16 size)
+{
+ u16 packet_size;
+ struct
+ {
+ u8 size[2];
+ } subcmd_data;
+
+ joycon_subcmd_packet* subcmd_packet = (joycon_subcmd_packet*)out;
+
+ subcmd_data.size[0] = (size + sizeof(u8)) >> 8;
+ subcmd_data.size[1] = (size + sizeof(u8)) & 0xFF;
+ packet_size = joycon_packet_craft(out, 0x92, (u8*)&subcmd_data, sizeof(subcmd_data));
+
+ subcmd_packet->pre.initial.total_size += size;
+ subcmd_packet->command = command;
+ if (data)
+ memcpy(subcmd_packet->data, data, size);
+
+ packet_size += size + sizeof(u8);
+ return packet_size;
+}
+
+void joycon_send_command(struct serdev_device *serdev, u8 command, u8 *data, u16 size)
+{
+ //TODO: memory corruption somewhere
+ u8 temp[0x100]; //TODO: why did I make this static again
+ memset(temp, 0, sizeof(temp));
+
+ size = joycon_packet_craft(temp, command, data, size);
+
+ //print_bytes(temp, size);
+ serdev_device_write(serdev, temp, size, msecs_to_jiffies(200));
+}
+
+void joycon_send_extcommand(struct serdev_device *serdev, u8 command, u8 *data, u16 size)
+{
+ //u8 *temp = devm_kzalloc(&serdev->dev, size+0x1000, GFP_KERNEL);
+ u8 temp[0x100];
+ memset(temp, 0, sizeof(temp));
+
+ //TODO: memory corruption somewhere
+ size = joycon_extcommand_craft(temp, command, data, size);
+
+ //print_bytes(temp, size);
+ serdev_device_write(serdev, temp, size, msecs_to_jiffies(200));
+ //kfree(temp);
+}
+
+void joycon_send_hidcommand(struct serdev_device *serdev, u8 command, u8 *data, u16 size)
+{
+ u8 data_gen[0x100];
+ u8 rumble[8] = {0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40};
+
+ memset(data_gen, 0, sizeof(data_gen));
+
+ //TODO: memory corruption somewhere
+ //u8 *data_gen = devm_kzalloc(&serdev->dev, size+10+0x1000, GFP_KERNEL);
+ data_gen[0] = 0; //inc
+ memcpy(&data_gen[1], rumble, sizeof(rumble));
+ data_gen[9] = command;
+
+ if (data)
+ memcpy(&data_gen[10], data, size);
+
+ joycon_send_extcommand(serdev, 1, data_gen, 0x30);
+ //kfree(data_gen);
+}
+
+/*
+ * Handlers
+ */
+
+static void timeout_handler(struct work_struct *work)
+{
+ struct work_timeout_poll * data = (struct work_timeout_poll *)work;
+ struct joycon *joycon = data->joycon;
+ struct serdev_device *serdev = joycon->serdev;
+ int err;
+
+ // Inactive for 600ms, try handshaking again
+ if (joycon->num_samples - joycon->timeout_samples == 0)
+ joycon->reconnect_threshold++;
+ else
+ joycon->reconnect_threshold = 0;
+
+ // We haven't gotten any samples within 600ms, we've probably been disconnected.
+ if (joycon->reconnect_threshold > 3)
+ {
+ joycon->handshaken = false;
+ joycon->initialized = false;
+ joycon->regvolt = 0;
+ gpiod_set_value(joycon->charge_gpio, 0);
+
+ err = serdev_device_write(serdev, (u8[]){0xA1, 0xA2, 0xA3, 0xA4}, 4, msecs_to_jiffies(200));
+ if (err < 0) goto done;
+
+ joycon_send_command(serdev, JOYCON_COMMAND_HANDSHAKE, (u8[]){0x02, 0x01, 0x7E}, 3); //TODO: error passing
+
+ serdev_device_write_flush(serdev);
+
+ joycon->reconnect_threshold = 0;
+ printk("Sent handshake\n");
+ }
+
+ if (!joycon->initialized) goto done;
+
+ joycon_send_hidcommand(serdev, JOYCON_HID_GET_REGVOLT, NULL, 0);
+
+done:
+ joycon->timeout_samples = joycon->num_samples;
+ INIT_DELAYED_WORK(&data->work,timeout_handler);
+ queue_delayed_work(joycon->wq, &data->work, msecs_to_jiffies(200));
+}
+
+static void input_handler(struct work_struct *work)
+{
+ struct work_input_poll * data = (struct work_input_poll *)work;
+ struct joycon *joycon = data->joycon;
+
+ if (!joycon->initialized) goto done;
+ serdev_device_write(joycon->serdev, controller_status, sizeof(controller_status), msecs_to_jiffies(200));
+
+ if (!joycon->button_set)
+ joycon_send_hidcommand(joycon->serdev, JOYCON_HID_DEVICE_INFO, NULL, 0);
+
+ if (joycon->button_set && !joycon->factory_cal_2_parsed)
+ joycon_send_hidcommand(joycon->serdev, JOYCON_HID_SPI_READ, (u8[]){0x3D, 0x60, 0x00, 0x00, 0x1E}, 5); //TODO nicer
+
+ if (joycon->factory_cal_2_parsed && !joycon->usercal_parsed)
+ joycon_send_hidcommand(joycon->serdev, JOYCON_HID_SPI_READ, (u8[]){0x10, 0x80, 0x00, 0x00, 0x2F}, 5); //TODO nicer
+
+ if (joycon->factory_cal_2_parsed && !joycon->factory_cal_3_parsed)
+ joycon_send_hidcommand(joycon->serdev, JOYCON_HID_SPI_READ, (u8[]){0x80, 0x60, 0x00, 0x00, 0x17}, 5); //TODO nicer
+
+done:
+ INIT_DELAYED_WORK(&data->work, input_handler);
+ queue_delayed_work(joycon->wq, &data->work, msecs_to_jiffies(16));
+}
+
+static void sync_handler(struct work_struct *work)
+{
+ struct work_sync_poll * data = (struct work_sync_poll *)work;
+ struct joycon *joycon = data->joycon;
+ int i;
+
+ mutex_lock(&joycon_input_lock);
+ for (i = 0; joycon_common_btn[i] >= 0; i++)
+ {
+ input_report_key(input_dev, joycon_common_btn[i], buttons & BIT(i));
+ }
+
+ input_report_abs(input_dev, ABS_X, stick_lx);
+ input_report_abs(input_dev, ABS_Y, stick_ly);
+ input_report_abs(input_dev, ABS_RX, stick_rx);
+ input_report_abs(input_dev, ABS_RY, stick_ry);
+ input_sync(input_dev);
+ mutex_unlock(&joycon_input_lock);
+
+ INIT_DELAYED_WORK(&data->work, sync_handler);
+ queue_delayed_work(joycon->wq, &data->work, msecs_to_jiffies(10));
+}
+
+/*
+ * Packet parsing
+ */
+
+static void joycon_parse_stick_cal(struct joycon_stick_calib *calib, const u8* packed)
+{
+ //min->max cent->min max->cent
+
+ calib->x_cent = ((packed[1] << 8) & 0xF00) | packed[0];
+ calib->y_cent = (packed[2] << 4) | (packed[1] >> 4);
+ calib->x_min = ((packed[4] << 8) & 0xF00) | packed[3];
+ calib->y_min = (packed[5] << 4) | (packed[4] >> 4);
+ calib->x_max = ((packed[7] << 8) & 0xF00) | packed[6];
+ calib->y_max = (packed[8] << 4) | (packed[7] >> 4);
+}
+
+static void joycon_calib_absinfo(struct input_absinfo *absinfo, unsigned int ax,
+ unsigned int ay, struct joycon_stick_calib *calib)
+{
+ absinfo[ax].minimum = calib->x_cent - calib->x_min;
+ absinfo[ax].maximum = calib->x_cent + calib->x_max;
+ absinfo[ay].minimum = calib->y_cent - calib->y_min;
+ absinfo[ay].maximum = calib->y_cent + calib->y_max;
+}
+
+static void joycon_parse_spi_read(struct serdev_device *serdev, const u8* packet, u32 size)
+{
+ struct joycon *joycon = serdev_device_get_drvdata(serdev);
+ u32 addr = (packet[4] << 24) | (packet[3] << 16) | (packet[2] << 8)
+ | packet[1];
+ u8 bytes_read = packet[5];
+
+ if (addr == JOYCON_SPI_FACTORY_CAL_2 && bytes_read >= 0x1E)
+ {
+ joycon_parse_stick_cal(&joycon->l_calib, packet + 5);
+ joycon_parse_stick_cal(&joycon->r_calib, packet + 14);
+
+ //TODO: Colors
+
+ if (joycon->button_set & JOYCON_BUTTONSET_LEFT)
+ {
+ joycon_calib_absinfo(input_dev->absinfo, ABS_X, ABS_Y,
+ &joycon->l_calib);
+ printk("Parsed left joystick configs\n");
+ }
+
+ if (joycon->button_set & JOYCON_BUTTONSET_RIGHT)
+ {
+ joycon_calib_absinfo(input_dev->absinfo, ABS_RX, ABS_RY,
+ &joycon->r_calib);
+ printk("Parsed right joystick configs\n");
+ }
+
+ joycon->factory_cal_2_parsed = true;
+ }
+ else if (addr == JOYCON_SPI_STICK_USERCAL)
+ {
+ if (packet[6] == 0xB2 && packet[7] == 0xA1
+ && joycon->button_set & JOYCON_BUTTONSET_LEFT)
+ {
+ joycon_parse_stick_cal(&joycon->l_calib, packet + 7);
+ joycon_calib_absinfo(input_dev->absinfo, ABS_X, ABS_Y,
+ &joycon->l_calib);
+ printk("Parsed left joystick user configs\n");
+ }
+
+ if (packet[16] == 0xB2 && packet[17] == 0xA1
+ && joycon->button_set & JOYCON_BUTTONSET_RIGHT)
+ {
+ joycon_parse_stick_cal(&joycon->r_calib, packet + 17);
+ joycon_calib_absinfo(input_dev->absinfo, ABS_RX, ABS_RY,
+ &joycon->r_calib);
+ printk("Parsed right joystick user configs\n");
+ }
+
+ //TODO: six-axis
+
+ joycon->usercal_parsed = true;
+ }
+ else if (addr == JOYCON_SPI_FACTORY_CAL_3)
+ {
+ u16 dead_zone = packet[6+4] | (packet[6+5] << 8);
+ printk("Dead zone %x\n", dead_zone);
+
+ /*if (joycon->button_set & JOYCON_BUTTONSET_LEFT)
+ {
+ input_dev->absinfo[ABS_X].flat = dead_zone;
+ input_dev->absinfo[ABS_Y].flat = dead_zone;
+ }
+
+ if (joycon->button_set & JOYCON_BUTTONSET_RIGHT)
+ {
+ input_dev->absinfo[ABS_RX].flat = dead_zone;
+ input_dev->absinfo[ABS_RY].flat = dead_zone;
+ }*/
+
+ joycon->factory_cal_3_parsed = true;
+ }
+}
+
+static void joycon_hidret_parse(struct serdev_device *serdev, const u8* packet, u32 size)
+{
+ struct joycon *joycon = serdev_device_get_drvdata(serdev);
+ switch (packet[0])
+ {
+ case JOYCON_HID_DEVICE_INFO:
+ printk("Joy-Con firmware version %X.%02X\n", packet[1], packet[2]);
+ joycon->button_set = packet[3];
+ if (packet[3] == 1)
+ printk("Joy-Con is a left controller\n");
+ else if (packet[3] == 2)
+ printk("Joy-Con is a right controller\n");
+ else if (packet[3] == 3)
+ printk("Joy-Con is a pro controller\n");
+ else
+ printk("Joy-Con is unknown\n");
+ break;
+ case JOYCON_HID_SPI_READ:
+ joycon_parse_spi_read(serdev, packet, size);
+ break;
+ case JOYCON_HID_GET_REGVOLT:
+ // The actual regulated battery voltage is the raw * 2.5
+ joycon->regvolt = packet[1] | packet[2] << 8;
+ joycon->regvolt *= 5;
+ joycon->regvolt /= 2;
+ break;
+ default:
+ printk("Unknown hidret %x\n", packet[0]);
+ break;
+ }
+
+ //print_bytes(packet, size);
+}
+
+static void joycon_extret_parse(struct serdev_device *serdev, const u8* packet, u32 size)
+{
+ struct joycon *joycon = serdev_device_get_drvdata(serdev);
+
+ switch (packet[0])
+ {
+ case JOYCON_EXT_INPUT:
+ joycon->buttons = packet[3] | packet[4] << 8 | packet[5] << 16;
+
+ mutex_lock(&joycon_input_lock);
+ if (joycon->button_set & JOYCON_BUTTONSET_LEFT)
+ {
+ buttons &= ~JOYCON_BUTTONS_LEFT;;
+ buttons |= (joycon->buttons & JOYCON_BUTTONS_LEFT);
+ }
+ if (joycon->button_set & JOYCON_BUTTONSET_RIGHT)
+ {
+ buttons &= ~JOYCON_BUTTONS_RIGHT;
+ buttons |= (joycon->buttons & JOYCON_BUTTONS_RIGHT);
+ }
+
+ if (joycon->button_set & JOYCON_BUTTONSET_LEFT)
+ {
+ stick_lx = packet[6+0] | ((packet[6+1] & 0xF) << 8);
+ stick_ly = (packet[6+1] >> 4) | (packet[6+2] << 4);
+ }
+
+ if (joycon->button_set & JOYCON_BUTTONSET_RIGHT)
+ {
+ stick_rx = packet[9+0] | ((packet[9+1] & 0xF) << 8);
+ stick_ry = (packet[9+1] >> 4) | (packet[9+2] << 4);
+ }
+ mutex_unlock(&joycon_input_lock);
+
+
+ joycon->num_samples++;
+ break;
+ case JOYCON_EXT_HIDCOMMAND:
+ joycon_hidret_parse(serdev, packet+14, size-14);
+ break;
+ default:
+ printk("Unknown extret %x\n", packet[0]);
+ break;
+ }
+}
+
+static void joycon_initret_parse(struct serdev_device *serdev, const u8* packet, u32 size)
+{
+ struct joycon *joycon = serdev_device_get_drvdata(serdev);
+ int i, j;
+
+ switch (packet[0])
+ {
+ case JOYCON_INIT_MAC:
+ for (i = 0xC, j = 0; i >= 0x6; i--)
+ joycon->mac[j++] = packet[i];
+ printk("Joy-Con with MAC %02X::%02X::%02X::%02X::%02X::%02X\n",
+ joycon->mac[0], joycon->mac[1], joycon->mac[2],
+ joycon->mac[3], joycon->mac[4], joycon->mac[5]);
+
+ // Next init command
+ //serdev_device_write(serdev, switch_baud, sizeof(switch_baud), msecs_to_jiffies(200));
+ joycon_send_command(serdev, JOYCON_COMMAND_EXTSEND, (u8[]){JOYCON_INIT_UNK1}, 1);
+ break;
+ case JOYCON_INIT_UNK1:
+ joycon_send_command(serdev, JOYCON_COMMAND_EXTSEND, (u8[]){JOYCON_INIT_UNK2}, 1);
+ break;
+ case JOYCON_INIT_UNK2:
+ serdev_device_write(serdev, unk_3, sizeof(unk_3), msecs_to_jiffies(200));
+ break;
+ case JOYCON_INIT_UNK3:
+ joycon->button_set = 0;
+ joycon->num_samples = 0;
+ joycon->factory_cal_2_parsed = false;
+ joycon->factory_cal_3_parsed = false;
+ joycon->usercal_parsed = false;
+ joycon->initialized = true;
+
+ gpiod_set_value(joycon->charge_gpio, 1);
+ break;
+ case JOYCON_INIT_BAUDRATE:
+ serdev_device_set_baudrate(serdev, 3000000);
+ printk("High-speed baudrate shifted...\n");
+ break;
+ default:
+ printk("Unknown initret %x\n", packet[0]);
+ break;
+ }
+}
+
+static void joycon_packet_parse(struct serdev_device *serdev, const u8* packet, size_t size)
+{
+ struct joycon *joycon = serdev_device_get_drvdata(serdev);
+ joycon_uart_header* header = (joycon_uart_header*) packet;
+
+ switch (header->command)
+ {
+ case JOYCON_COMMAND_EXTRET:
+ joycon_extret_parse(serdev, packet + sizeof(joycon_uart_header), (header->data[0] << 8) | header->data[1]);
+ break;
+ case JOYCON_COMMAND_INITRET:
+ joycon_initret_parse(serdev, packet + sizeof(joycon_uart_initial) + 1, size - sizeof(joycon_uart_initial) - 1);
+ break;
+ case JOYCON_COMMAND_HANDSHAKE:
+ printk("Got handshake response\n");
+ joycon->handshaken = true;
+
+ // Get MAC
+ joycon_send_command(serdev, JOYCON_COMMAND_EXTSEND, (u8[]){JOYCON_INIT_MAC}, 1);
+ break;
+ default:
+ printk("Unknown command %x\n", header->command);
+ break;
+ }
+}
+
+static int joycon_serdev_receive_buf(struct serdev_device *serdev, const unsigned char *buf, size_t len)
+{
+ struct joycon *joycon = serdev_device_get_drvdata(serdev);
+
+ if (!joycon || serdev != joycon->serdev) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ joycon_packet_parse(serdev, buf, len);
+
+ return len;
+}
+
+static void joycon_serdev_write_wakeup(struct serdev_device *serdev)
+{
+ //printk("Wakeup\n");
+}
+
+/*
+ * Serdev driver
+ */
+
+static int joycon_serdev_probe(struct serdev_device *serdev)
+{
+ struct joycon *joycon;
+
+ struct work_timeout_poll *work_timeout;
+ struct work_input_poll *work_input;
+ struct work_sync_poll *work_sync;
+ struct power_supply_config psy_cfg = {};
+ int i;
+ int err = -ENOMEM;
+
+ printk("Joy-Con probe!\n");
+
+ joycon = devm_kzalloc(&serdev->dev, sizeof(*joycon), GFP_KERNEL);
+ if (!joycon)
+ goto fail2;
+
+ joycon->wq = create_workqueue("joycon_wq");
+
+ if (input_dev) goto input_exists;
+
+ // TODO: For USB/BT this should be an array w/ Joy-Con
+ // being matched to input devices by halves
+ input_dev = input_allocate_device();
+ if (!input_dev) goto fail2;
+
+ input_dev->name = "Joy-Con Rails";
+ input_dev->id.bustype = BUS_VIRTUAL;
+ input_dev->id.vendor = 0x057E;
+ input_dev->id.product = 0x2008;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serdev->dev;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+
+ /* set up standard buttons */
+ for (i = 0; joycon_common_btn[i] >= 0; i++)
+ __set_bit(joycon_common_btn[i], input_dev->keybit);
+
+ //TODO: dead zones
+ input_set_abs_params(input_dev, ABS_X, 0x400, 0xC00, 0, 180);
+ input_set_abs_params(input_dev, ABS_Y, 0x400, 0xC00, 0, 180);
+ input_set_abs_params(input_dev, ABS_RX, 0x400, 0xC00, 0, 180);
+ input_set_abs_params(input_dev, ABS_RY, 0x400, 0xC00, 0, 180);
+
+ err = input_register_device(input_dev);
+ if (err) goto fail1;
+
+ // Start input sync polling
+ work_sync = kmalloc(sizeof(struct work_sync_poll), GFP_KERNEL);
+ INIT_DELAYED_WORK(&work_sync->work, sync_handler);
+ work_sync->joycon = joycon;
+ queue_delayed_work(joycon->wq, &work_sync->work, msecs_to_jiffies(10));
+
+input_exists:
+
+ psy_cfg.of_node = serdev->dev.of_node;
+ psy_cfg.drv_data = joycon;
+
+ joycon->batt_desc.name = kasprintf(GFP_KERNEL, "joycon_battery_%u", joycon_increment++);
+ joycon->batt_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ joycon->batt_desc.properties = joycon_battery_props;
+ joycon->batt_desc.num_properties = ARRAY_SIZE(joycon_battery_props);
+ joycon->batt_desc.get_property = joycon_battery_get_property;
+
+ joycon->batt_dev = devm_power_supply_register(&serdev->dev, &joycon->batt_desc,
+ &psy_cfg);
+
+ err = PTR_ERR_OR_ZERO(joycon->batt_dev);
+ if (err) {
+ dev_err(&serdev->dev, "failed to register power supply\n");
+ goto fail1;
+ }
+
+ joycon->serdev = serdev;
+ joycon->dev = &serdev->dev;
+ joycon->input_dev = input_dev;
+
+ serdev_device_set_drvdata(serdev, joycon);
+
+ //TODO: error checking
+ serdev_device_open(serdev);
+ serdev_device_set_flow_control(serdev, true);
+ serdev_device_set_baudrate(serdev, 1000000);
+
+ serdev_device_set_client_ops(serdev, &joycon_ops);
+
+ // Get charging GPIO
+ joycon->charge_gpio = devm_gpiod_get(&serdev->dev, "charge", GPIOD_OUT_LOW);
+ if (IS_ERR(joycon->charge_gpio)) {
+ err = PTR_ERR(joycon->charge_gpio);
+ dev_err(&serdev->dev, "cannot get charge-gpio %d\n", err);
+ goto fail3;
+ }
+
+ // Set up work polling
+ work_timeout = kmalloc(sizeof(struct work_timeout_poll), GFP_KERNEL);
+ INIT_DELAYED_WORK(&work_timeout->work, timeout_handler);
+ work_timeout->joycon = joycon;
+ queue_delayed_work(joycon->wq, &work_timeout->work, msecs_to_jiffies(200));
+
+ work_input = kmalloc(sizeof(struct work_input_poll), GFP_KERNEL);
+ work_input->joycon = joycon;
+ INIT_DELAYED_WORK(&work_input->work, input_handler);
+ queue_delayed_work(joycon->wq, &work_input->work, msecs_to_jiffies(200));
+
+ return 0;
+
+ fail3: serdev_device_set_drvdata(serdev, NULL);
+ fail1: input_free_device(input_dev);
+ fail2:
+ kfree(joycon);
+ return err;
+}
+
+static void joycon_serdev_remove(struct serdev_device *serdev)
+{
+ struct joycon *joycon = serdev_device_get_drvdata(serdev);
+ printk("Joy-Con remove!\n");
+
+ input_unregister_device(joycon->input_dev);
+
+ flush_workqueue(joycon->wq);
+ destroy_workqueue(joycon->wq);
+
+ kfree(joycon->batt_desc.name);
+ kfree(joycon);
+}
+
+/*
+ * The serdev driver structure.
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id joycon_uart_of_match[] = {
+ { .compatible = "nintendo,joycon-uart" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, joycon_uart_of_match);
+#endif
+
+static struct serdev_device_driver joycon_serdev_driver = {
+ .probe = joycon_serdev_probe,
+ .remove = joycon_serdev_remove,
+ .driver = {
+ .name = "joycon-uart",
+ .of_match_table = of_match_ptr(joycon_uart_of_match),
+ },
+};
+
+static struct serdev_device_ops joycon_ops = {
+ .receive_buf = joycon_serdev_receive_buf,
+ .write_wakeup = joycon_serdev_write_wakeup,
+};
+
+/*
+ * Joy-Con base driver
+ */
+
+static int joycon_probe(struct platform_device *pdev)
+{
+ struct joycon_device *dev;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->dev = &pdev->dev;
+ dev->irq = platform_get_irq(pdev, 0);
+
+ platform_set_drvdata(pdev, dev);
+
+ dev_info(&pdev->dev, "%s device registered.\n", dev->name);
+
+ /* Place this instance on the device list */
+ mutex_lock(&joycon_device_lock);
+ list_add_tail(&dev->list, &joycon_device_list);
+ mutex_unlock(&joycon_device_lock);
+
+ return 0;
+}
+
+static int joycon_remove(struct platform_device *pdev)
+{
+ struct joycon_device *dev = platform_get_drvdata(pdev);
+
+ mutex_lock(&joycon_device_lock);
+ list_del(&dev->list);
+ mutex_unlock(&joycon_device_lock);
+
+ dev_info(&pdev->dev, "%s device unregistered.\n", dev->name);
+
+ return 0;
+}
+
+static struct platform_driver joycon_driver = {
+ .probe = joycon_probe,
+ .remove = joycon_remove,
+ .driver = {
+ .name = "joycon",
+ },
+};
+
+int __init joycon_init(void)
+{
+ platform_driver_register(&joycon_driver);
+ serdev_device_driver_register(&joycon_serdev_driver);
+
+ joycon_increment = 0;
+ crc8_populate_lsb(crc_table, JOYCON_CRC8_POLY);
+
+ return 0;
+}
+
+void __exit joycon_exit(void)
+{
+ platform_driver_unregister(&joycon_driver);
+ serdev_device_driver_unregister(&joycon_serdev_driver);
+}
+
+module_init(joycon_init);
+module_exit(joycon_exit);
--
2.20.1