From f83ee8dde91f1a8516daad2ec8f8fc57aa97273d Mon Sep 17 00:00:00 2001 From: CTCaer Date: Thu, 22 Dec 2022 16:48:09 +0200 Subject: [PATCH] input: joycon: update serial driver - Add Sio support (Switch Lite gamepad) - Refactor init and correct it for Joy-Con and Hori - Fix rumble hangs - Fix Hori analog stick calibration - Various bug fixes --- drivers/input/joystick/joycon-serdev.c | 1184 +++++++++++++++++++++--- 1 file changed, 1072 insertions(+), 112 deletions(-) diff --git a/drivers/input/joystick/joycon-serdev.c b/drivers/input/joystick/joycon-serdev.c index 641373076caa..cb90985d6d17 100644 --- a/drivers/input/joystick/joycon-serdev.c +++ b/drivers/input/joystick/joycon-serdev.c @@ -2,7 +2,8 @@ /* * serial driver for Nintendo Switch Joy-Cons * - * Copyright (c) 2019 Daniel J. Ogorchock + * Copyright (c) 2019-2020 Daniel J. Ogorchock + * Copyright (c) 2021-2022 CTCaer * * The following resources/projects were referenced for this driver: * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering @@ -14,7 +15,7 @@ * hid-logitech-hidpp driver * hid-sony driver * - * This driver supports the Nintendo Switch Joy-Cons over uart. + * This driver supports the Nintendo Switch Joy-Cons and Lite gamepad over uart. * * Much of this driver is ported from the hid-nintendo usb/bt joy-con driver. * @@ -23,14 +24,19 @@ * */ +#include +#include #include #include #include +#include #include +#include #include #include #include #include +#include #include #include #include @@ -52,10 +58,10 @@ static const u8 JC_CMD_HORIINPUTREPORT = 0x9A; /* Used in handshake */ static const u8 JC_INIT_MAC = 0x01; -static const u8 JC_INIT_BAUDRATE = 0x20; -static const u8 JC_INIT_BRATE_DONE = 0x11; -static const u8 JC_INIT_DONE = 0x10; -static const u8 JC_INIT_RATE = 0x12; +static const u8 JC_INIT_HID_CONNECT = 0x10; +static const u8 JC_INIT_HID_DISCONNECT = 0x11; +static const u8 JC_INIT_SET_HID_RATE = 0x12; +static const u8 JC_INIT_SET_BAUDRATE = 0x20; /* Input Reports */ static const u8 JC_INPUT_EXT_ACK = 0x1F; @@ -97,6 +103,18 @@ 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; +/* Sio uart header commands */ +static const u8 JC_SIO_OUTPUT_RPT = 0x91; +static const u8 JC_SIO_INPUT_RPT = 0x92; +static const u8 JC_SIO_CMD_ACK = 0x80; +static const u8 JC_SIO_STATUS_OK = 0x00; + +/* Sio CMDs */ +static const u8 JC_SIO_CMD_INIT = 0x01; +static const u8 JC_SIO_CMD_VER_RPT = 0x03; +static const u8 JC_SIO_CMD_INPUTREPORT = 0x41; +static const u8 JC_SIO_CMD_IAP_VER = 0x42; + /* 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; @@ -120,10 +138,54 @@ static const u16 JC_MAX_STICK_MAG = 32767; static const u16 JC_STICK_FUZZ = 250; static const u16 JC_STICK_FLAT = 500; +/* 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 defines and tables for rumble */ static const u32 JC_RUMBLE_MASK = 0xFF80FE00; static const u32 JC_RUMBLE_ZERO_AMP = 0x40000000; +#define JC_VIB_RPT_GET_BUF_STS_RIGHT(val) ((val) & 7) +#define JC_VIB_RPT_GET_BUF_STS_LEFT(val) (((val) >> 4) & 7) + struct joycon_rumble_freq_data { u16 high; u8 low; @@ -247,6 +309,11 @@ struct joycon_stick_cal { s32 center; }; +struct joycon_imu_cal { + s16 offset[3]; + s16 scale[3]; +}; + /* States for controller state machine */ enum joycon_ctlr_state { JOYCON_CTLR_STATE_INIT, @@ -257,6 +324,7 @@ enum joycon_ctlr_type { JOYCON_TYPE_UNKNOWN, JOYCON_TYPE_LEFT, JOYCON_TYPE_RIGHT, + JOYCON_TYPE_SIO }; /* @@ -316,7 +384,7 @@ struct joycon_subcmd_request { u8 packet_num; /* incremented every send */ u8 rumble_data[8]; u8 subcmd_id; - u8 data[0]; /* length depends on the subcommand */ + u8 data[]; /* length depends on the subcommand */ } __packed; struct joycon_subcmd_reply { @@ -332,7 +400,7 @@ struct joycon_input_report { u8 button_status[3]; u8 left_stick[3]; u8 right_stick[3]; - u8 sixaxis_report; + u8 vibrator_report; /* * If support for firmware updates, gyroscope data, and/or NFC/IR @@ -341,17 +409,56 @@ struct joycon_input_report { struct joycon_subcmd_reply reply; } __packed; +struct sio_uart_packet { + u8 cmd; + u8 subcmd; + u16 payload_len; + u8 data[2]; + u8 crc_payload; + u8 crc_hdr; + u8 payload[]; +} __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 sio_input_report +{ + u8 type; + u8 pkt_id; + u8 unk; + u8 button_status[3]; + u8 left_stick[3]; + u8 right_stick[3]; + u8 sixaxis_report_num; // Max 15. + // Each report is 800 us? + union { + struct joycon_imu_data sixaxis[15]; + /* IMU input reports contain 3 samples */ + u8 imu_raw_bytes[sizeof(struct joycon_imu_data) * 15]; + }; +} __packed; + struct joycon_led_queue_item { struct led_classdev *led; enum led_brightness brightness; }; -#define JC_MAX_RESP_SIZE (sizeof(struct joycon_input_report) + 35) -#define JC_MAX_UART_PKT_SIZE (sizeof(struct joycon_uart_packet) + JC_MAX_RESP_SIZE) -#define JC_NUM_LEDS 4 -#define JC_RUMBLE_DATA_SIZE 8 -#define JC_RUMBLE_QUEUE_SIZE 8 -#define JC_LED_QUEUE_SIZE 20 +#define JC_MAX_RESP_SIZE (sizeof(struct joycon_input_report) + 35) +#define JC_MAX_UART_PKT_SIZE (sizeof(struct joycon_uart_packet) + JC_MAX_RESP_SIZE) +#define JC_NUM_LEDS 4 +#define JC_RUMBLE_DATA_SIZE 8 +#define JC_RUMBLE_QUEUE_SIZE 8 +#define JC_LED_QUEUE_SIZE 20 + +#define JC_SIO_MAX_RESP_SIZE (sizeof(struct sio_input_report) + 15) +#define JC_SIO_MAX_UART_PKT_SIZE (sizeof(struct sio_uart_packet) + JC_SIO_MAX_RESP_SIZE) static const u16 JC_RUMBLE_DFLT_LOW_FREQ = 160; static const u16 JC_RUMBLE_DFLT_HIGH_FREQ = 320; @@ -375,6 +482,16 @@ struct joycon_ctlr { char *mac_addr_str; bool suspending; + int detect_en_gpio; + int detect_gpio; + + /* Lite gamepad */ + bool is_sio; + int sio_home_gpio; + int sio_por_gpio; + int sio_rst_gpio; + struct clk *sio_clk; + /* third party protocol */ bool is_hori; @@ -391,7 +508,7 @@ struct joycon_ctlr { enum joycon_msg_type msg_type; u8 subcmd_num; struct mutex output_mutex; - u8 input_buf[JC_MAX_RESP_SIZE]; + u8 input_buf[JC_SIO_MAX_RESP_SIZE]; wait_queue_head_t wait; bool received_resp; u8 subcmd_ack_match; @@ -400,7 +517,7 @@ struct joycon_ctlr { bool ctlr_removed; /* buffering partially received uart packets */ - u8 partial_pkt[JC_MAX_UART_PKT_SIZE]; + u8 partial_pkt[JC_SIO_MAX_UART_PKT_SIZE]; size_t partial_pkt_len; /* joy-con detection */ @@ -415,6 +532,15 @@ struct joycon_ctlr { /* factory calibration data */ struct joycon_stick_cal stick_cal_x; struct joycon_stick_cal stick_cal_y; + 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; @@ -436,7 +562,18 @@ struct joycon_ctlr { u16 rumble_lh_freq; u16 rumble_rl_freq; u16 rumble_rh_freq; + u32 rumble_buf_avail; bool rumble_keep_alive; + + /* 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; }; static int joycon_serdev_send(struct joycon_ctlr *ctlr, u8 *data, @@ -481,7 +618,7 @@ static int joycon_serdev_send_sync(struct joycon_ctlr *ctlr, u8 *data, spin_lock_irqsave(&ctlr->lock, flags); if (!ret && !ctlr->ctlr_removed) { dev_warn(&ctlr->sdev->dev, - "timeout waiting for input report\n"); + "Timeout waiting for input report\n"); ret = -ETIMEDOUT; spin_unlock_irqrestore(&ctlr->lock, flags); goto err; @@ -491,7 +628,7 @@ static int joycon_serdev_send_sync(struct joycon_ctlr *ctlr, u8 *data, ret = joycon_serdev_send(ctlr, data, len, timeout); if (ret) { - memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE); + memset(ctlr->input_buf, 0, JC_SIO_MAX_RESP_SIZE); return ret; } @@ -500,13 +637,13 @@ static int joycon_serdev_send_sync(struct joycon_ctlr *ctlr, u8 *data, if (!ret) { if (ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) { dev_info(&ctlr->sdev->dev, - "synchronous send/receive timed out\n"); + "Synchronous send/receive timed out\n"); } if (tries) { dev_dbg(&ctlr->sdev->dev, - "retrying sync send after timeout\n"); + "Retrying sync send after timeout\n"); } - memset(ctlr->input_buf, 0, JC_MAX_RESP_SIZE); + memset(ctlr->input_buf, 0, JC_SIO_MAX_RESP_SIZE); ret = -ETIMEDOUT; } else { ret = 0; @@ -572,6 +709,56 @@ static int joycon_send_packet(struct joycon_ctlr *ctlr, u8 command, return ret; } + +static int sio_send_packet(struct joycon_ctlr *ctlr, u8 command, + u8 *hdata, size_t hdata_size, + u8 *data, size_t data_size, u32 timeout, + bool sync) +{ + struct sio_uart_packet *packet; + struct device *dev = &ctlr->sdev->dev; + int ret = 0; + + packet = kzalloc(sizeof(*packet) + data_size, GFP_KERNEL); + if (!packet) + return -ENOMEM; + + packet->cmd = JC_SIO_OUTPUT_RPT; + packet->subcmd = command; + packet->payload_len = data_size; + + if (hdata) { + if (hdata_size > sizeof(packet->data)) { + dev_err(dev, "Header data size too large\n"); + ret = -EINVAL; + goto err; + } + memcpy(packet->data, hdata, hdata_size); + } + + if (data_size) + packet->crc_payload = crc8(ctlr->joycon_crc_table, data, data_size, JC_CRC8_INIT); + packet->crc_hdr = crc8(ctlr->joycon_crc_table, (u8 *)packet, sizeof(*packet) - 1, JC_CRC8_INIT); + + if (data) + memcpy(packet->data, data, data_size); + + if (sync) + ret = joycon_serdev_send_sync(ctlr, (u8 *)packet, + sizeof(*packet) + data_size, + timeout); + else + ret = joycon_serdev_send(ctlr, (u8 *)packet, + sizeof(*packet) + data_size, timeout); + + if (ret && ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) + dev_err(dev, "Failed sending uart packet; ret=%d\n", ret); + +err: + kfree(packet); + return ret; +} + /* caller must set ctlr->uart_cmd_match prior to calling */ static int joycon_send_command(struct joycon_ctlr *ctlr, u8 command, u8 *data, size_t data_len, u32 timeout) @@ -583,7 +770,7 @@ static int joycon_send_command(struct joycon_ctlr *ctlr, u8 command, ret = joycon_send_packet(ctlr, command, data, data_len, NULL, 0, timeout, true); if (ret < 0) - dev_dbg(dev, "send command failed; ret=%d\n", ret); + dev_dbg(dev, "Send command failed; ret=%d\n", ret); return ret; } @@ -615,7 +802,7 @@ static int joycon_send_subcmd(struct joycon_ctlr *ctlr, (u8 *)subcmd, sizeof(*subcmd) + data_len, timeout, true); if (ret < 0) - dev_dbg(dev, "send subcommand failed; ret=%d\n", ret); + dev_dbg(dev, "Send subcommand failed; ret=%d\n", ret); return ret; } @@ -628,7 +815,7 @@ static int joycon_set_hci_state(struct joycon_ctlr *ctlr, u8 state) req->subcmd_id = JC_SUBCMD_SET_HCI_STATE; req->data[0] = state; - dev_dbg(&ctlr->sdev->dev, "setting hci state = %u\n", state); + dev_dbg(&ctlr->sdev->dev, "Setting hci state = %u\n", state); /* We won't get a reply. Just use a short timeout. */ return joycon_send_subcmd(ctlr, req, 1, HZ/10); } @@ -643,7 +830,7 @@ static int joycon_get_ctlr_info(struct joycon_ctlr *ctlr) req.subcmd_id = JC_SUBCMD_REQ_DEV_INFO; ret = joycon_send_subcmd(ctlr, &req, 0, HZ); if (ret) { - dev_err(dev, "Failed to get joycon info; ret=%d\n", ret); + dev_err(dev, "Failed to get Joy-con info; ret=%d\n", ret); return ret; } @@ -676,7 +863,7 @@ static int joycon_set_player_leds(struct joycon_ctlr *ctlr, u8 flash, u8 on) req->subcmd_id = JC_SUBCMD_SET_PLAYER_LIGHTS; req->data[0] = (flash << 4) | on; - dev_dbg(&ctlr->sdev->dev, "setting player leds\n"); + dev_dbg(&ctlr->sdev->dev, "Setting player leds\n"); return joycon_send_subcmd(ctlr, req, 1, HZ/4); } @@ -724,10 +911,10 @@ static int joycon_request_spi_flash_read(struct joycon_ctlr *ctlr, data[3] = 0xFF & (start_addr >> 24); data[4] = size; - dev_dbg(dev, "requesting SPI flash data\n"); + dev_dbg(dev, "Requesting SPI flash data\n"); ret = joycon_send_subcmd(ctlr, req, 5, HZ); if (ret) { - dev_err(dev, "failed reading SPI flash; ret=%d\n", ret); + dev_err(dev, "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 */ @@ -808,7 +995,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr) enum joycon_ctlr_type type = ctlr->ctlr_type; int ret; - dev_dbg(dev, "requesting cal data\n"); + dev_dbg(dev, "Requesting cal data\n"); stick_addr = (type == JOYCON_TYPE_LEFT) ? JC_CAL_FCT_DATA_LEFT_ADDR : JC_CAL_FCT_DATA_RIGHT_ADDR; @@ -817,14 +1004,14 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr) if (type == JOYCON_TYPE_LEFT && !joycon_check_for_cal_magic(ctlr, JC_CAL_USR_LEFT_MAGIC_ADDR)) { stick_addr = JC_CAL_USR_LEFT_DATA_ADDR; - dev_info(dev, "using user cal for left stick\n"); + dev_info(dev, "Using user cal for left stick\n"); } else if (type == JOYCON_TYPE_RIGHT && !joycon_check_for_cal_magic(ctlr, JC_CAL_USR_RIGHT_MAGIC_ADDR)) { stick_addr = JC_CAL_USR_RIGHT_DATA_ADDR; - dev_info(dev, "using user cal for right stick\n"); + dev_info(dev, "Using user cal for right stick\n"); } else { - dev_info(dev, "using factory cal for stick\n"); + dev_info(dev, "Using factory cal for stick\n"); } /* read the stick calibration data */ @@ -852,7 +1039,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr) ctlr->stick_cal_x.center = DFLT_STICK_CAL_CEN; ctlr->stick_cal_x.max = DFLT_STICK_CAL_MAX; ctlr->stick_cal_x.min = DFLT_STICK_CAL_MIN; - dev_warn(dev, "Boguous stick calibration for x axis, using defaults"); + dev_warn(dev, "Empty stick calibration for x axis, using defaults"); } if (ctlr->stick_cal_y.center == 0 && @@ -861,7 +1048,7 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr) ctlr->stick_cal_y.center = DFLT_STICK_CAL_CEN; ctlr->stick_cal_y.max = DFLT_STICK_CAL_MAX; ctlr->stick_cal_y.min = DFLT_STICK_CAL_MIN; - dev_warn(dev, "Boguous stick calibration for y axis, using defaults"); + dev_warn(dev, "Empty stick calibration for y axis, using defaults"); } dev_dbg(&ctlr->sdev->dev, "calibration:\n" @@ -886,7 +1073,7 @@ static int joycon_enable_rumble(struct joycon_ctlr *ctlr, bool enable) req->subcmd_id = JC_SUBCMD_ENABLE_VIBRATION; req->data[0] = enable ? 0x01 : 0x00; - dev_dbg(dev, "%s rumble\n", enable ? "enabling" : "disabling"); + dev_dbg(dev, "%s rumble\n", enable ? "Enabling" : "Disabling"); return joycon_send_subcmd(ctlr, req, 1, HZ/4); } @@ -921,6 +1108,21 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, dev_dbg(&ctlr->sdev->dev, "parse_report()\n"); spin_lock_irqsave(&ctlr->lock, flags); + + switch (type) { + case JOYCON_TYPE_LEFT: + ctlr->rumble_buf_avail = + JC_VIB_RPT_GET_BUF_STS_LEFT(rep->vibrator_report); + break; + case JOYCON_TYPE_RIGHT: + ctlr->rumble_buf_avail = + JC_VIB_RPT_GET_BUF_STS_RIGHT(rep->vibrator_report); + break; + default: + ctlr->rumble_buf_avail = 0; + break; + } + if (!ctlr->is_hori && IS_ENABLED(CONFIG_JOYCON_SERDEV_FF) && (msecs - ctlr->rumble_msecs) >= JC_RUMBLE_PERIOD_MS && (ctlr->rumble_queue_head != ctlr->rumble_queue_tail || @@ -1052,6 +1254,306 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, wake_up(&ctlr->wait); } +static void sio_input_report_parse_imu_data(struct joycon_ctlr *ctlr, + struct sio_input_report *rep, + struct joycon_imu_data *imu_data) +{ + u8 *raw = rep->imu_raw_bytes; + int i; + + for (i = 0; i < rep->sixaxis_report_num; 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 sio_parse_imu_report(struct joycon_ctlr *ctlr, + struct sio_input_report *rep) +{ + struct device *dev = &ctlr->sdev->dev; + struct joycon_imu_data imu_data[15] = {0}; /* 15 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]; + + /* + * Only max of 15 is allowed for num of reports. + * On errors, that value is normally 0xF0; + */ + if (!rep->sixaxis_report_num || rep->sixaxis_report_num > 15) + return; + + sio_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; + dev_warn(dev, + "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 */ + dev_dbg(dev, + "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) { + dev_info(dev, + "Compensating for %u dropped IMU reports\n", + dropped_pkts); + dev_info(dev, + "delta=%u avg_delta=%u\n", + delta, ctlr->imu_avg_delta_ms); + } + } + ctlr->imu_last_pkt_ms = msecs; + + /* Each IMU input report contains 15 samples max */ + for (i = 0; i < rep->sixaxis_report_num; 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]; + + dev_dbg(dev, "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); + dev_dbg(dev, "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 sio gamepad has 2 axes swapped, X and Y. This is due to + * the orientation of the IMU in the controller which is in + * in portrait. We also negate X axis value for that. + * Proper output based on orientation: + * X: positive is pointing to right of console + * Y: positive is pointing to top + * Z: positive is pointing up (out of screen) + * The axes follow the right-hand rule with index pointing right. + */ + + /* Set gyro */ + input_report_abs(idev, ABS_RX, -value[1]); + input_report_abs(idev, ABS_RY, value[0]); + input_report_abs(idev, ABS_RZ, value[2]); + /* Set acceleration */ + input_report_abs(idev, ABS_X, -value[4]); + input_report_abs(idev, ABS_Y, value[3]); + input_report_abs(idev, ABS_Z, value[5]); + input_sync(idev); + + /* convert to micros and divide by samples per report. */ + ctlr->imu_timestamp_us += ctlr->imu_avg_delta_ms * 1000 / + rep->sixaxis_report_num; + } +} + +static void sio_parse_report(struct joycon_ctlr *ctlr, + struct sio_input_report *rep) +{ + struct input_dev *dev = ctlr->input; + unsigned long flags; + int btn_home = 0; + u32 btns; + u16 raw_x, raw_y, raw_rx, raw_ry; + s32 x, y, rx, ry; + + dev_dbg(&ctlr->sdev->dev, "parse_report()\n"); + + spin_lock_irqsave(&ctlr->lock, flags); + + ctlr->last_input_report_msecs = jiffies_to_msecs(jiffies); + + spin_unlock_irqrestore(&ctlr->lock, flags); + + /* Parse the buttons and sticks */ + btns = field_extract(rep->button_status, 0, 24); + btn_home = !gpio_get_value(ctlr->sio_home_gpio); + + /* get raw stick values */ + raw_x = field_extract(rep->left_stick, 0, 12); + raw_y = field_extract(rep->left_stick + 1, 4, 12); + /* map the stick values */ + x = joycon_map_stick_val(&ctlr->stick_cal_x, raw_x); + y = joycon_map_stick_val(&ctlr->stick_cal_y, raw_y); + /* report sticks */ + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + + /* get raw stick values */ + raw_rx = field_extract(rep->right_stick, 0, 12); + raw_ry = field_extract(rep->right_stick + 1, 4, 12); + /* map stick values */ + rx = -joycon_map_stick_val(&ctlr->right_stick_cal_x, raw_rx); + ry = -joycon_map_stick_val(&ctlr->right_stick_cal_y, raw_ry); + /* report sticks */ + input_report_abs(dev, ABS_RX, rx); + input_report_abs(dev, ABS_RY, ry); + + /* 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); + 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); + + /* report buttons */ + input_report_key(dev, BTN_TR, btns & JC_BTN_R); + input_report_key(dev, BTN_TR2, btns & JC_BTN_ZR); + 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, 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); + + /* + * 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. + */ + spin_lock_irqsave(&ctlr->lock, flags); + ctlr->received_input_report = true; + spin_unlock_irqrestore(&ctlr->lock, flags); + wake_up(&ctlr->wait); + + /* parse IMU data if present */ + sio_parse_imu_report(ctlr, rep); +} + static int joycon_request_input_report(struct joycon_ctlr *ctlr) { u8 hdata[] = {0x00, 0x01, 0x00, 0x00, 0x69}; @@ -1059,7 +1561,7 @@ static int joycon_request_input_report(struct joycon_ctlr *ctlr) struct device *dev = &ctlr->sdev->dev; int ret; - dev_dbg(dev, "requesting input report\n"); + dev_dbg(dev, "Requesting input report\n"); /* * Intentionally don't lock the output mutex. We want to send no matter * what. We don't want to deadlock with other senders which are waiting @@ -1072,13 +1574,33 @@ static int joycon_request_input_report(struct joycon_ctlr *ctlr) return ret; } +static int sio_request_input_report(struct joycon_ctlr *ctlr) +{ + struct device *dev = &ctlr->sdev->dev; + int ret; + + dev_dbg(dev, "Requesting sio input report\n"); + /* + * Intentionally don't lock the output mutex. We want to send no matter + * what. We don't want to deadlock with other senders which are waiting + * to be woken up from parse_report(). + */ + ctlr->msg_type = JOYCON_MSG_TYPE_SUBCMD; + ctlr->uart_cmd_match = JC_SIO_CMD_INPUTREPORT | JC_SIO_CMD_ACK; + ret = sio_send_packet(ctlr, JC_SIO_CMD_INPUTREPORT, NULL, 0, NULL, 0, + HZ/4, false); + if (ret) + dev_err(dev, "Failed to request input report; ret=%d\n", ret); + return ret; +} + static int hori_request_input_report(struct joycon_ctlr *ctlr) { int ret; u8 hdata[] = {0x01}; struct device *dev = &ctlr->sdev->dev; - dev_dbg(dev, "requesting hori input report\n"); + dev_dbg(dev, "Requesting hori input report\n"); /* * Intentionally don't lock the output mutex. We want to send no matter * what. We don't want to deadlock with other senders which are waiting @@ -1097,14 +1619,14 @@ static void joycon_disconnect(struct joycon_ctlr *ctlr) int i; unsigned long flags; - dev_info(dev, "joycon disconnected - unregistering\n"); + dev_info(dev, "Joy-con disconnected - unregistering\n"); spin_lock_irqsave(&ctlr->lock, flags); ctlr->ctlr_removed = true; ctlr->ctlr_state = JOYCON_CTLR_STATE_INIT; spin_unlock_irqrestore(&ctlr->lock, flags); - dev_info(dev, "removing input device\n"); + dev_info(dev, "Removing input device\n"); /* remove input device */ if (ctlr->input) { input_unregister_device(ctlr->input); @@ -1115,8 +1637,8 @@ static void joycon_disconnect(struct joycon_ctlr *ctlr) ctlr->mac_addr_str = NULL; } - if (!ctlr->is_hori) { - dev_info(dev, "removing LEDs\n"); + if (!ctlr->is_hori && !ctlr->is_sio) { + dev_info(dev, "Removing LEDs\n"); flush_workqueue(ctlr->detection_queue); /* remove LEDS */ for (i = 0; i < JC_NUM_LEDS; i++) { @@ -1141,7 +1663,7 @@ static void joycon_disconnect(struct joycon_ctlr *ctlr) } } - dev_info(dev, "removing power supply\n"); + dev_info(dev, "Removing power supply\n"); /* remove power supply */ if (ctlr->battery) { power_supply_unregister(ctlr->battery); @@ -1152,6 +1674,10 @@ static void joycon_disconnect(struct joycon_ctlr *ctlr) } } } + + /* set POR for sio */ + if (ctlr->is_sio) + gpio_set_value(ctlr->sio_por_gpio, 0); } struct serport { @@ -1213,23 +1739,30 @@ static void joycon_input_poller(struct work_struct *work) input_worker); struct device *dev = &ctlr->sdev->dev; unsigned int timediff; - bool suspending; + bool suspending, removed; unsigned long flags; spin_lock_irqsave(&ctlr->lock, flags); suspending = ctlr->suspending; + removed = ctlr->ctlr_removed; spin_unlock_irqrestore(&ctlr->lock, flags); + if (suspending || removed) + return; + timediff = jiffies_to_msecs(jiffies) - ctlr->last_input_report_msecs; - if (timediff > 500 && !suspending) { - dev_info(dev, "joy-con disconnected\n"); + if (timediff > 500) { + dev_info(dev, "Joy-con disconnected\n"); joycon_disconnect(ctlr); if (joycon_enter_detection(ctlr)) - dev_err(dev, "failed to re-enter detection\n"); + dev_err(dev, "Failed to re-enter detection\n"); return; } - if (!ctlr->is_hori) { + + if (!ctlr->is_hori && !ctlr->is_sio) { joycon_request_input_report(ctlr); + } else if (ctlr->is_sio) { + sio_request_input_report(ctlr); } else { hori_request_input_report(ctlr); } @@ -1244,7 +1777,7 @@ static int joycon_send_rumble_data(struct joycon_ctlr *ctlr) __be16 subcmd_size; struct device *dev = &ctlr->sdev->dev; struct joycon_subcmd_request *subcmd; - u8 buffer[sizeof(*subcmd) - 1] = { 0 }; + u8 buffer[sizeof(*subcmd)] = { 0 }; u32 rumble[2]; subcmd = (struct joycon_subcmd_request *)buffer; @@ -1258,6 +1791,15 @@ static int joycon_send_rumble_data(struct joycon_ctlr *ctlr) spin_unlock_irqrestore(&ctlr->lock, flags); return -ENODEV; } + + /* Check if device has available buffers */ + /*! TODO: find out how it works. + if (!ctlr->rumble_buf_avail) { + spin_unlock_irqrestore(&ctlr->lock, flags); + return -EBUSY; + } + */ + memcpy(subcmd->rumble_data, ctlr->rumble_data[ctlr->rumble_queue_tail], JC_RUMBLE_DATA_SIZE); /* Set keep alive flag */ @@ -1283,7 +1825,7 @@ static int joycon_send_rumble_data(struct joycon_ctlr *ctlr) (u8 *)subcmd, sizeof(*subcmd) - 1, ctlr->rumble_keep_alive ? HZ : HZ/4, false); if (ret < 0) - dev_dbg(dev, "send subcommand failed; ret=%d\n", ret); + dev_dbg(dev, "Send subcommand failed; ret=%d\n", ret); return ret; } @@ -1293,14 +1835,14 @@ static void joycon_rumble_worker(struct work_struct *work) rumble_worker); unsigned long flags; bool again = true; - int ret; + int ret = 0; while (again) { - mutex_lock(&ctlr->output_mutex); ret = joycon_send_rumble_data(ctlr); - mutex_unlock(&ctlr->output_mutex); - /* -ENODEV means the controller was just unplugged */ - if (ret == -ENODEV) + /* -ENODEV means the controller was just unplugged + * -EBUSY means the controller has no buffer available + */ + if (ret == -ENODEV || ret == -EBUSY) break; spin_lock_irqsave(&ctlr->lock, flags); @@ -1480,23 +2022,63 @@ static const unsigned int joycon_button_inputs_right[] = { 0 /* 0 signals end of array */ }; +static const unsigned int joycon_button_inputs_sio[] = { + BTN_SELECT, BTN_Z, BTN_THUMBL, + BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, + BTN_TL, BTN_TL2, + BTN_START, BTN_MODE, BTN_THUMBR, + BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST, + BTN_TR, BTN_TR2, + 0 /* 0 signals end of array */ +}; + +static 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 void sio_set_imu_calibration(struct joycon_ctlr *ctlr) +{ + int i; + + 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; + } + + 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 int joycon_input_create(struct joycon_ctlr *ctlr) { struct device *dev = &ctlr->sdev->dev; enum joycon_ctlr_type type = ctlr->ctlr_type; const char *name; + u16 pid = 0; int ret; int i; switch (type) { case JOYCON_TYPE_LEFT: name = "Nintendo Switch Left Joy-Con Serial"; + pid = 0x2006; break; case JOYCON_TYPE_RIGHT: name = "Nintendo Switch Right Joy-Con Serial"; + pid = 0x2007; + break; + case JOYCON_TYPE_SIO: + name = "Nintendo Switch Lite Gamepad"; + pid = 0xF123; /* Custom PID */ break; default: /* Should be impossible */ - dev_err(dev, "Invalid joycon type\n"); + dev_err(dev, "Invalid Joy-con type\n"); return -EINVAL; } @@ -1505,7 +2087,7 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) return -ENOMEM; ctlr->input->id.bustype = BUS_VIRTUAL; ctlr->input->id.vendor = 0x57e; - ctlr->input->id.product = type == JOYCON_TYPE_LEFT ? 0x2006 : 0x2007; + ctlr->input->id.product = pid; ctlr->input->id.version = 0; ctlr->input->name = name; ctlr->input->uniq = ctlr->mac_addr_str; @@ -1538,25 +2120,102 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) for (i = 0; joycon_button_inputs_right[i] > 0; i++) input_set_capability(ctlr->input, EV_KEY, joycon_button_inputs_right[i]); + } else if (type == JOYCON_TYPE_SIO) { + /* left analog stick */ + 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); + /* right analog stick */ + input_set_abs_params(ctlr->input, ABS_RX, + -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, + JC_STICK_FUZZ, JC_STICK_FLAT); + input_set_abs_params(ctlr->input, ABS_RY, + -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, + JC_STICK_FUZZ, JC_STICK_FLAT); + + /* set up buttons */ + for (i = 0; joycon_button_inputs_sio[i] > 0; i++) + input_set_capability(ctlr->input, EV_KEY, + joycon_button_inputs_sio[i]); } #if IS_ENABLED(CONFIG_JOYCON_SERDEV_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); + if (!ctlr->is_hori && !ctlr->is_sio) { + 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; + if (ctlr->is_sio) { + /* configure the imu input device */ + ctlr->imu_input = devm_input_allocate_device(dev); + if (!ctlr->imu_input) + return -ENOMEM; + + /* set calibration data */ + sio_set_imu_calibration(ctlr); + + ctlr->imu_input->dev.parent = dev; + ctlr->imu_input->id.bustype = BUS_VIRTUAL; + ctlr->imu_input->id.vendor = 0x57e; + ctlr->imu_input->id.product = pid; + ctlr->imu_input->id.version = 0; + ctlr->imu_input->uniq = ctlr->mac_addr_str; + ctlr->imu_input->name = "Nintendo Switch Lite Gamepad IMU"; + 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; } @@ -1660,7 +2319,7 @@ static int joycon_home_led_brightness_set(struct led_classdev *led, data[3] = 0x11; data[4] = 0x11; - dev_dbg(dev, "setting home led brightness\n"); + dev_dbg(dev, "Setting home led brightness\n"); mutex_lock(&ctlr->output_mutex); ret = joycon_send_subcmd(ctlr, req, 5, HZ/4); mutex_unlock(&ctlr->output_mutex); @@ -1724,8 +2383,10 @@ static int joycon_leds_create(struct joycon_ctlr *ctlr) 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) + if (!name) { + mutex_unlock(&joycon_input_num_mutex); return -ENOMEM; + } led = &ctlr->leds[i]; ctlr->led_names[i] = name; @@ -1738,6 +2399,7 @@ static int joycon_leds_create(struct joycon_ctlr *ctlr) ret = devm_led_classdev_register(dev, led); if (ret) { dev_err(dev, "Failed registering %s LED\n", led->name); + mutex_unlock(&joycon_input_num_mutex); return ret; } } @@ -1919,20 +2581,20 @@ static int joycon_read_mac(struct joycon_ctlr *ctlr) if (!ctlr->mac_addr_str) return -ENOMEM; - dev_info(&ctlr->sdev->dev, "joycon MAC = %s\n", ctlr->mac_addr_str); + dev_info(&ctlr->sdev->dev, "Joy-con MAC = %s\n", ctlr->mac_addr_str); return 0; } -/* configures the joy-con to use 3000000bps */ +/* Configures the Joy-con to use 3000000bps */ static int joycon_change_baud(struct joycon_ctlr *ctlr) { int ret; - u8 hdata[] = {JC_INIT_BAUDRATE, 0x08, 0x00, 0x00, 0xBD}; + u8 hdata[] = {JC_INIT_SET_BAUDRATE, 0x08, 0x00, 0x00, 0xBD}; u8 data[] = {0xC0, 0xC6, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00}; /* 3000000bps */ struct joycon_uart_packet *packet; - dev_info(&ctlr->sdev->dev, "Increasing joy-con baud to 3000000bps\n"); + dev_info(&ctlr->sdev->dev, "Increasing Joy-con baud to 3000000bps\n"); ctlr->uart_cmd_match = JC_CMD_INITRET; ctlr->msg_type = JOYCON_MSG_TYPE_UART_CMD; @@ -1940,12 +2602,12 @@ static int joycon_change_baud(struct joycon_ctlr *ctlr) hdata, sizeof(hdata), data, sizeof(data), HZ, true); if (ret) { - dev_err(&ctlr->sdev->dev, "Failed to set joycon baud; ret=%d", + dev_err(&ctlr->sdev->dev, "Failed to set Joy-con baud; ret=%d", ret); return ret; } packet = (struct joycon_uart_packet *) ctlr->input_buf; - if (packet->header_data[0] != JC_INIT_BAUDRATE) { + if (packet->header_data[0] != JC_INIT_SET_BAUDRATE) { dev_err(&ctlr->sdev->dev, "Invalid baudrate set resp; val=0x%02x\n", packet->header_data[0]); @@ -1989,11 +2651,11 @@ static int joycon_init_handshake(struct joycon_ctlr *ctlr, u8 handshake) return 0; } -/* The final step in the handshake is to set input report rate */ -static int joycon_init_report_rate(struct joycon_ctlr *ctlr) +/* The final step in the handshake is to set hid interval */ +static int joycon_init_hid_rate(struct joycon_ctlr *ctlr) { int ret; - u8 hdata[] = {JC_INIT_RATE, 0x04, 0x00, 0x00, 0x12}; + u8 hdata[] = {JC_INIT_SET_HID_RATE, 0x04, 0x00, 0x00, 0x12}; u8 data[] = {0x0F, 0x00, 0x00, 0x00}; struct joycon_uart_packet *packet; @@ -2009,7 +2671,7 @@ static int joycon_init_report_rate(struct joycon_ctlr *ctlr) return ret; } packet = (struct joycon_uart_packet *) ctlr->input_buf; - if (packet->header_data[0] != JC_INIT_RATE) { + if (packet->header_data[0] != JC_INIT_SET_HID_RATE) { dev_err(&ctlr->sdev->dev, "Invalid set rate resp; val=0x%02x\n", packet->header_data[0]); @@ -2039,7 +2701,7 @@ static int joycon_handshake(struct joycon_ctlr *ctlr) ret = joycon_send_command(ctlr, JC_CMD_HANDSHAKE, handshake_cmd_data, sizeof(handshake_cmd_data), HZ / 10); if (ret) { - dev_dbg(dev, "did not receive handshake response; ret=%d", + dev_dbg(dev, "Did not receive handshake response; ret=%d", ret); goto exit; } @@ -2054,21 +2716,21 @@ static int joycon_handshake(struct joycon_ctlr *ctlr) if (ret) goto exit_restore_baud; - /* send host baudrate change confirmation */ - ret = joycon_init_handshake(ctlr, JC_INIT_BRATE_DONE); + /* send hid disconnect */ + ret = joycon_init_handshake(ctlr, JC_INIT_HID_DISCONNECT); if (ret) goto exit_restore_baud; - /* send initialization done */ - ret = joycon_init_handshake(ctlr, JC_INIT_DONE); + /* send hid connect */ + ret = joycon_init_handshake(ctlr, JC_INIT_HID_CONNECT); if (ret) goto exit_restore_baud; - /* set input report rate */ - ret = joycon_init_report_rate(ctlr); + /* set hid interval */ + ret = joycon_init_hid_rate(ctlr); if (ret) goto exit_restore_baud; - dev_info(dev, "completed handshake\n"); + dev_info(dev, "completed handshake - Joycon\n"); goto exit; } else { ctlr->stick_cal_x.center = DFLT_HORI_STICK_CAL_CEN; @@ -2078,12 +2740,12 @@ static int joycon_handshake(struct joycon_ctlr *ctlr) ctlr->stick_cal_y.center = DFLT_HORI_STICK_CAL_CEN; ctlr->stick_cal_y.max = DFLT_HORI_STICK_CAL_MAX; ctlr->stick_cal_y.min = DFLT_HORI_STICK_CAL_MIN; - dev_info(dev, "completed handshake - HORI\n"); + dev_info(dev, "completed handshake - Hori\n"); goto exit; } exit_restore_baud: - dev_info(dev, "returning to low baudrate for detection\n"); + dev_info(dev, "Returning to low baudrate for detection\n"); baudret = joycon_set_baudrate(ctlr, JC_UART_BAUD_LOW); if (baudret != JC_UART_BAUD_LOW) { dev_err(&ctlr->sdev->dev, @@ -2094,13 +2756,85 @@ static int joycon_handshake(struct joycon_ctlr *ctlr) return ret; } +static int sio_handshake(struct joycon_ctlr *ctlr) +{ + int ret; + struct device *dev = &ctlr->sdev->dev; + u8 mac[] = {0x98, 0xb6, 0xe9, 0x53, 0x49, 0x4f}; + u8 version[] = {0x03, 0x04}; + + mutex_lock(&ctlr->output_mutex); + + /* set Sio POR low to configure BOOT0 mode. */ + gpio_set_value(ctlr->sio_por_gpio, 0); + usleep_range(300, 300); + gpio_set_value(ctlr->sio_por_gpio, 1); + msleep(100); + + /* send init command */ + ctlr->msg_type = JOYCON_MSG_TYPE_UART_CMD; + ctlr->uart_cmd_match = JC_SIO_CMD_INIT | JC_SIO_CMD_ACK; + ret = sio_send_packet(ctlr, JC_SIO_CMD_INIT, NULL, 0, NULL, 0, HZ, true); + if (ret) { + dev_err(dev, "Did not receive init response; ret=%d", + ret); + goto exit; + } + + /* send report version command */ + ctlr->msg_type = JOYCON_MSG_TYPE_UART_CMD; + ctlr->uart_cmd_match = JC_SIO_CMD_VER_RPT | JC_SIO_CMD_ACK; + ret = sio_send_packet(ctlr, JC_SIO_CMD_VER_RPT, version, sizeof(version), + NULL, 0, HZ, true); + if (ret) { + dev_err(dev, "Did not receive rpt version response; ret=%d", + ret); + goto exit; + } + + ctlr->ctlr_type = JOYCON_TYPE_SIO; + memcpy(ctlr->mac_addr, mac, sizeof(mac)); + ctlr->mac_addr_str = devm_kasprintf(&ctlr->sdev->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; + + ctlr->stick_cal_x.center = DFLT_STICK_CAL_CEN; + ctlr->stick_cal_x.max = DFLT_STICK_CAL_MAX; + ctlr->stick_cal_x.min = DFLT_STICK_CAL_MIN; + + ctlr->stick_cal_y.center = DFLT_STICK_CAL_CEN; + ctlr->stick_cal_y.max = DFLT_STICK_CAL_MAX; + ctlr->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; + + dev_info(dev, "Completed handshake - Sio\n"); + +exit: + mutex_unlock(&ctlr->output_mutex); + return ret; +} + static int joycon_post_handshake(struct joycon_ctlr *ctlr) { int ret; int baudret; struct device *dev = &ctlr->sdev->dev; - if (!ctlr->is_hori) { + if (!ctlr->is_hori && !ctlr->is_sio) { mutex_lock(&ctlr->output_mutex); /* determine which type of controller this is */ ret = joycon_get_ctlr_info(ctlr); @@ -2141,10 +2875,10 @@ static int joycon_post_handshake(struct joycon_ctlr *ctlr) goto error; } } else { - /* hori doesn't have any of: - - home, player leds - - rumble - - battery + /* sio and hori doesn't have any of: + - home, player leds + - rumble + - battery and doesn't seem to require calibration. */ } @@ -2160,10 +2894,11 @@ static int joycon_post_handshake(struct joycon_ctlr *ctlr) ctlr->ctlr_removed = false; queue_delayed_work(ctlr->input_queue, &ctlr->input_worker, 0); - dev_info(dev, "post detect - success\n"); + dev_info(dev, "Post detect - success\n"); return 0; + error: - dev_info(dev, "returning to low baudrate for detection\n"); + dev_info(dev, "Returning to low baudrate for detection\n"); baudret = joycon_set_baudrate(ctlr, JC_UART_BAUD_LOW); if (baudret != JC_UART_BAUD_LOW) { dev_err(&ctlr->sdev->dev, @@ -2181,11 +2916,15 @@ static void joycon_detection_poller(struct work_struct *work) struct device *dev = &ctlr->sdev->dev; int ret; - dev_dbg(dev, "polling for joy-con\n"); + dev_dbg(dev, "Polling for Joy-con\n"); + + if (!ctlr->is_sio) + ret = joycon_handshake(ctlr); + else + ret = sio_handshake(ctlr); - ret = joycon_handshake(ctlr); if (ret) { - dev_dbg(dev, "handshake failed; ret=%d\n", ret); + dev_dbg(dev, "Handshake failed; ret=%d\n", ret); goto retry; } ret = joycon_post_handshake(ctlr); @@ -2208,7 +2947,7 @@ static int joycon_serdev_receive_buf(struct serdev_device *serdev, struct device *dev = &ctlr->sdev->dev; struct joycon_input_report *r; - dev_dbg(dev, "received uart data of size=%lu\n", len); + dev_dbg(dev, "Received uart data of size=%lu\n", len); /* check if this is beginning of new packet */ if (!ctlr->partial_pkt_len) { /* Obird workaround (they send some zeros in the beginning). */ @@ -2223,14 +2962,14 @@ static int joycon_serdev_receive_buf(struct serdev_device *serdev, packet->magic[1] != JC_UART_MAGIC_RX_1 || packet->magic[2] != JC_UART_MAGIC_RX_2) { /* Toss out this packet if the magic is wrong */ - dev_warn(dev, "received pkt has wrong magic\n"); + dev_warn(dev, "Received pkt has wrong magic\n"); return len; } /* Proceed to process the packet without buffering */ - dev_dbg(dev, "received whole uart packet\n"); + dev_dbg(dev, "Received whole uart packet\n"); } else if (len-j > JC_MAX_RESP_SIZE) { /* Toss out this packet if malformed */ - dev_warn(dev, "received pkt is malformed\n"); + dev_warn(dev, "Received pkt is malformed\n"); return len; } else { /* This isn't yet the whole packet. */ @@ -2258,14 +2997,14 @@ static int joycon_serdev_receive_buf(struct serdev_device *serdev, packet->magic[1] != JC_UART_MAGIC_RX_1 || packet->magic[2] != JC_UART_MAGIC_RX_2) { /* Toss out this packet if the magic is wrong */ - dev_warn(dev, "received pkt has wrong magic\n"); + dev_warn(dev, "Received pkt has wrong magic\n"); return len; } /* Proceed to process the packet */ - dev_dbg(dev, "finished receiving uart packet\n"); + dev_dbg(dev, "Finished receiving uart packet\n"); } else { /* packet still in progress */ - dev_dbg(dev, "rx of packet still in progres\n"); + dev_dbg(dev, "rx of packet still in progress\n"); return len; } } @@ -2327,15 +3066,134 @@ static int joycon_serdev_receive_buf(struct serdev_device *serdev, return len; } +static int sio_serdev_receive_buf(struct serdev_device *serdev, + const unsigned char *buf, size_t len) +{ + struct joycon_ctlr *ctlr = serdev_device_get_drvdata(serdev); + struct sio_uart_packet *packet = (struct sio_uart_packet *) buf; + struct device *dev = &ctlr->sdev->dev; + u8 subcmd = 0; + + dev_dbg(dev, "Received uart data of size=%lu\n", len); + /* check if this is beginning of new packet */ + if (!ctlr->partial_pkt_len) { + /* Have we received the entire packet? */ + if (len >= 4 && packet->payload_len + 8 <= len) { + if (packet->cmd != JC_SIO_INPUT_RPT) { + /* Toss out this packet if the magic is wrong */ + dev_warn(dev, "Received pkt has wrong magic\n"); + return len; + } else if (packet->payload_len > JC_SIO_MAX_RESP_SIZE) { + /* Toss out this packet if too big */ + dev_warn(dev, "Received pkt payload too big\n"); + return len; + } + + /* Proceed to process the packet without buffering */ + dev_dbg(dev, "Received whole uart packet\n"); + } else if (len > JC_SIO_MAX_RESP_SIZE) { + /* Toss out this packet if malformed */ + dev_warn(dev, "Received pkt is malformed\n"); + return len; + } else { + /* This isn't yet the whole packet. */ + memcpy(ctlr->partial_pkt, buf, len); + ctlr->partial_pkt_len = len; + return len; + } + } else { + if (ctlr->partial_pkt_len + len > JC_SIO_MAX_UART_PKT_SIZE) { + dev_warn(dev, "Packet length too large; ignoring\n"); + ctlr->partial_pkt_len = 0; + return len; + } + memcpy(ctlr->partial_pkt + ctlr->partial_pkt_len, buf, len); + ctlr->partial_pkt_len += len; + + /* need to be able to check packet size */ + if (ctlr->partial_pkt_len < 4) + return len; + + packet = (struct sio_uart_packet *) ctlr->partial_pkt; + if (packet->payload_len + 8 <= ctlr->partial_pkt_len) { + ctlr->partial_pkt_len = 0; + if (packet->cmd != JC_SIO_INPUT_RPT) { + /* Toss out this packet if the magic is wrong */ + dev_warn(dev, "Received pkt has wrong magic\n"); + return len; + } + /* Proceed to process the packet */ + dev_dbg(dev, "Finished receiving uart packet\n"); + } else { + /* packet still in progress */ + dev_dbg(dev, "rx of packet still in progres\n"); + return len; + } + } + + /* could check package crc here, + calculation is the same as for sending */ + + if (unlikely(mutex_is_locked(&ctlr->output_mutex))) { + switch (ctlr->msg_type) { + case JOYCON_MSG_TYPE_UART_CMD: + if (packet->subcmd == ctlr->uart_cmd_match && + packet->data[0] == JC_SIO_STATUS_OK) { + memcpy(ctlr->input_buf, buf, + min(len, (size_t)JC_SIO_MAX_RESP_SIZE)); + ctlr->msg_type = JOYCON_MSG_TYPE_NONE; + ctlr->received_resp = true; + wake_up(&ctlr->wait); + return len; + } + break; + case JOYCON_MSG_TYPE_SUBCMD: + if (packet->subcmd != ctlr->uart_cmd_match || + packet->data[0] != JC_SIO_STATUS_OK) { + return len; + } + ctlr->msg_type = JOYCON_MSG_TYPE_NONE; + break; + default: + break; + } + } + + if (ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) { + subcmd = packet->subcmd & (~JC_SIO_CMD_ACK); + if (subcmd == JC_SIO_CMD_INPUTREPORT) { + dev_dbg(dev, "JC_SIO_CMD_INPUTREPORT\n"); + sio_parse_report(ctlr, + (struct sio_input_report *)packet->payload); + } else if (subcmd == JC_SIO_CMD_INIT) { + dev_dbg(dev, "JC_SIO_CMD_INIT\n"); + } else if (subcmd == JC_SIO_CMD_VER_RPT) { + dev_dbg(dev, "JC_SIO_CMD_VER_RPT\n"); + } else if (subcmd == JC_SIO_CMD_IAP_VER) { + dev_dbg(dev, "JC_SIO_CMD_IAP_VER\n"); + } else { + dev_dbg(dev, "Unknown uart cmd=%u\n", subcmd); + } + } + + return len; +} + static struct serdev_device_ops joycon_serdev_ops = { .receive_buf = joycon_serdev_receive_buf, .write_wakeup = serdev_device_write_wakeup, }; +static struct serdev_device_ops sio_serdev_ops = { + .receive_buf = sio_serdev_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + static int joycon_serdev_probe(struct serdev_device *serdev) { struct joycon_ctlr *ctlr; struct device *dev = &serdev->dev; + struct clk *sio_pclk = NULL, *sio_pclk_mux = NULL; int ret = 0; dev_info(dev, "joycon_serdev_probe\n"); @@ -2343,13 +3201,50 @@ static int joycon_serdev_probe(struct serdev_device *serdev) if (!ctlr) return -ENOMEM; + ctlr->is_sio = of_property_read_bool(dev->of_node, "nintendo,sio"); + if (ctlr->is_sio) { + ctlr->sio_home_gpio = of_get_named_gpio(dev->of_node, "nintendo,sio-home-gpio", 0); + if (!gpio_is_valid(ctlr->sio_home_gpio)) { + dev_err(dev, "Could not get sio-home-gpio\n"); + return ctlr->sio_home_gpio; + } + ctlr->sio_por_gpio = of_get_named_gpio(dev->of_node, "nintendo,sio-por-gpio", 0); + if (!gpio_is_valid(ctlr->sio_por_gpio)) { + dev_err(dev, "Could not get sio-por-gpio\n"); + return ctlr->sio_por_gpio; + } + ctlr->sio_rst_gpio = of_get_named_gpio(dev->of_node, "nintendo,sio-rst-gpio", 0); + if (!gpio_is_valid(ctlr->sio_rst_gpio)) { + dev_err(dev, "Could not get sio-rst-gpio\n"); + return ctlr->sio_rst_gpio; + } + sio_pclk = devm_clk_get(dev, "sio_pclk"); + if (IS_ERR(sio_pclk)) { + dev_err(dev, "Could not get sio parent clock\n"); + return PTR_ERR(sio_pclk); + } + sio_pclk_mux = devm_clk_get(dev, "sio_pclk_mux"); + if (IS_ERR(sio_pclk_mux)) { + dev_err(dev, "Could not get sio parent clock mux\n"); + return PTR_ERR(sio_pclk_mux); + } + ctlr->sio_clk = devm_clk_get(dev, "sio_clk"); + if (IS_ERR(ctlr->sio_clk)) { + dev_err(dev, "Could not get sio clock\n"); + return PTR_ERR(ctlr->sio_clk); + } + } + crc8_populate_msb(ctlr->joycon_crc_table, JC_CRC8_POLY); ctlr->charger_reg = devm_regulator_get_optional(dev, "charger"); if (PTR_ERR(ctlr->charger_reg) == -EPROBE_DEFER) return -EPROBE_DEFER; if (IS_ERR_OR_NULL(ctlr->charger_reg)) - dev_warn(dev, "Continuing without charger regulator\n"); + dev_info(dev, "Continuing without charger regulator\n"); + + ctlr->detect_gpio = of_get_named_gpio(dev->of_node, "detect-gpios", 0); + ctlr->detect_en_gpio = of_get_named_gpio(dev->of_node, "detect-en-gpios", 0); ctlr->sdev = serdev; serdev_device_set_drvdata(serdev, ctlr); @@ -2358,12 +3253,24 @@ static int joycon_serdev_probe(struct serdev_device *serdev) spin_lock_init(&ctlr->lock); ctlr->rumble_queue = alloc_ordered_workqueue("joycon-serdev-rumble_wq", WQ_FREEZABLE | WQ_MEM_RECLAIM); + if (!ctlr->rumble_queue) { + ret = -ENOMEM; + goto err; + } INIT_WORK(&ctlr->rumble_worker, joycon_rumble_worker); ctlr->detection_queue = alloc_ordered_workqueue("joycon-serdev-detection_wq", WQ_MEM_RECLAIM); + if (!ctlr->detection_queue) { + ret = -ENOMEM; + goto err; + } INIT_DELAYED_WORK(&ctlr->detection_worker, joycon_detection_poller); ctlr->input_queue = alloc_ordered_workqueue("joycon-serdev-input_wq", WQ_MEM_RECLAIM); + if (!ctlr->input_queue) { + ret = -ENOMEM; + goto err; + } INIT_DELAYED_WORK(&ctlr->input_worker, joycon_input_poller); INIT_WORK(&ctlr->led_worker, joycon_led_worker); @@ -2372,9 +3279,59 @@ static int joycon_serdev_probe(struct serdev_device *serdev) dev_err(dev, "Failed to open serdev device; ret=%d\n", ret); goto err; } - serdev_device_set_client_ops(serdev, &joycon_serdev_ops); + serdev_device_set_client_ops(serdev, !ctlr->is_sio ? + &joycon_serdev_ops : &sio_serdev_ops); serdev_device_set_flow_control(serdev, true); + if (ctlr->is_sio) { + ret = devm_gpio_request(dev, ctlr->sio_home_gpio, "jc-sio-home"); + if (ret) { + dev_err(dev, "Failed to request gpio jc-sio-home\n"); + goto err_sdev_close; + } + ret = devm_gpio_request(dev, ctlr->sio_por_gpio, "jc-por-home"); + if (ret) { + dev_err(dev, "Failed to request gpio jc-por-home\n"); + goto err_sdev_close; + } + ret = devm_gpio_request(dev, ctlr->sio_rst_gpio, "jc-rst-home"); + if (ret) { + dev_err(dev, "Failed to request gpio jc-rst-home\n"); + goto err_sdev_close; + } + + ret = clk_prepare_enable(sio_pclk); + if (ret) { + dev_err(dev, "Failed to enable SIO clock parent\n"); + goto err_sdev_close; + } + ret = clk_set_parent(sio_pclk_mux, sio_pclk); + if (ret) { + dev_err(dev, "Failed to set SIO clock parent\n"); + goto err_sdev_close; + } + ret = clk_prepare_enable(ctlr->sio_clk); + if (ret) { + dev_err(dev, "Failed to enable SIO clock\n"); + goto err_sdev_close; + } + + gpio_direction_input(ctlr->sio_home_gpio); + gpio_direction_input(ctlr->sio_rst_gpio); + gpio_direction_output(ctlr->sio_por_gpio, 0); + } + + if (gpio_is_valid(ctlr->detect_gpio)) { + // devm_gpio_request(dev, ctlr->detect_gpio, "jc-detect"); + // if (request_irq(ctlr->detect_gpio, handler, + // IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + // "jc-detect", dev)) { + // gpio_free(ctlr->detect_gpio); + // dev_err(dev, "Failed to open serdev device; ret=%d\n", ret); + // goto err_sdev_close; + // } + } + ret = joycon_enter_detection(ctlr); if (ret) { dev_err(dev, @@ -2383,7 +3340,7 @@ static int joycon_serdev_probe(struct serdev_device *serdev) goto err_sdev_close; } - dev_info(dev, "probe complete\n"); + dev_info(dev, "Probe complete\n"); return 0; err_sdev_close: @@ -2394,7 +3351,7 @@ static int joycon_serdev_probe(struct serdev_device *serdev) static void joycon_stop_queues(struct joycon_ctlr *ctlr) { - dev_info(&ctlr->sdev->dev, "stopping queues\n"); + dev_info(&ctlr->sdev->dev, "Stopping queues\n"); if (ctlr->input_queue) { cancel_delayed_work_sync(&ctlr->input_worker); flush_workqueue(ctlr->input_queue); @@ -2409,7 +3366,7 @@ static void joycon_stop_queues(struct joycon_ctlr *ctlr) static void joycon_free_queues(struct joycon_ctlr *ctlr) { - dev_info(&ctlr->sdev->dev, "freeing queues\n"); + dev_info(&ctlr->sdev->dev, "Freeing queues\n"); if (ctlr->input_queue) destroy_workqueue(ctlr->input_queue); if (ctlr->rumble_queue) @@ -2422,6 +3379,8 @@ static void joycon_serdev_remove(struct serdev_device *serdev) { struct joycon_ctlr *ctlr = serdev_device_get_drvdata(serdev); + dev_info(&serdev->dev, "Remove\n"); + serdev_device_close(serdev); if (ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) joycon_disconnect(ctlr); @@ -2443,7 +3402,7 @@ static int __maybe_unused joycon_serdev_suspend(struct device *dev) struct joycon_ctlr *ctlr = dev_get_drvdata(dev); unsigned long flags; - dev_info(dev, "suspend\n"); + dev_info(dev, "Suspend\n"); spin_lock_irqsave(&ctlr->lock, flags); if (ctlr->suspending) { @@ -2460,7 +3419,8 @@ static int __maybe_unused joycon_serdev_suspend(struct device *dev) if (ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) { /* attempt telling the joy-con to sleep to decrease battery drain */ - joycon_set_hci_state(ctlr, 0); + if (!ctlr->is_sio) + joycon_set_hci_state(ctlr, 0); joycon_disconnect(ctlr); } @@ -2474,7 +3434,7 @@ static int __maybe_unused joycon_serdev_resume(struct device *dev) struct joycon_ctlr *ctlr = dev_get_drvdata(dev); unsigned long flags; - dev_info(dev, "resume\n"); + dev_info(dev, "Resume\n"); spin_lock_irqsave(&ctlr->lock, flags); ctlr->suspending = false; spin_unlock_irqrestore(&ctlr->lock, flags);