From 63cf9c45ac695a062d47213b5bc962785b6f9147 Mon Sep 17 00:00:00 2001 From: Teguh Sobirin Date: Thu, 20 Feb 2025 14:50:30 +0800 Subject: [PATCH] input: Add driver for RSInput Gamepad Signed-off-by: Teguh Sobirin --- drivers/input/joystick/Kconfig | 4 + drivers/input/joystick/Makefile | 1 + drivers/input/joystick/rsinput.c | 418 +++++++++++++++++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 drivers/input/joystick/rsinput.c diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig index 7755e5b454d2..0da3c0f44ecf 100644 --- a/drivers/input/joystick/Kconfig +++ b/drivers/input/joystick/Kconfig @@ -383,6 +383,10 @@ config JOYSTICK_QWIIC To compile this driver as a module, choose M here: the module will be called qwiic-joystick. +config JOYSTICK_RSINPUT + tristate "UART Based gamepad driver that found in AYN and Retroid Pocket products" + depends on SERIAL_DEV_BUS + config JOYSTICK_FSIA6B tristate "FlySky FS-iA6B RC Receiver" select SERIO diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile index 9976f596a920..3de503e29489 100644 --- a/drivers/input/joystick/Makefile +++ b/drivers/input/joystick/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_JOYSTICK_N64) += n64joy.o obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o obj-$(CONFIG_JOYSTICK_QWIIC) += qwiic-joystick.o +obj-$(CONFIG_JOYSTICK_RSINPUT) += rsinput.o obj-$(CONFIG_JOYSTICK_SEESAW) += adafruit-seesaw.o obj-$(CONFIG_JOYSTICK_SENSEHAT) += sensehat-joystick.o obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o diff --git a/drivers/input/joystick/rsinput.c b/drivers/input/joystick/rsinput.c new file mode 100644 index 000000000000..4a7096407712 --- /dev/null +++ b/drivers/input/joystick/rsinput.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RSInput Gamepad Driver + * + * Copyright (C) 2024 Teguh Sobirin + * + */ +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FRAME_HEAD_1 0xA5 +#define FRAME_HEAD_2 0xD3 +#define FRAME_HEAD_3 0x5A +#define FRAME_HEAD_4 0x3D + +#define CMD_COMMOD 0x01 +#define CMD_STATUS 0x02 + +#define DATA_COMMOD_VERSION 0x02 +#define DATA_COMMOD_SET_PAR 0x05 + +#define FRAME_POS_SEQ 4 +#define FRAME_POS_CMD 5 +#define FRAME_POS_LEN_L 6 +#define FRAME_POS_LEN_H 7 +#define FRAME_POS_DATA_1 8 +#define FRAME_POS_DATA_2 9 +#define FRAME_POS_DATA_3 10 +#define FRAME_POS_DATA_4 11 +#define FRAME_POS_DATA_5 12 +#define FRAME_POS_DATA_6 13 +#define FRAME_POS_DATA_7 14 +#define FRAME_POS_DATA_8 15 +#define FRAME_POS_DATA_9 16 +#define FRAME_POS_DATA_10 17 +#define FRAME_POS_DATA_11 18 +#define FRAME_POS_DATA_12 19 +#define FRAME_POS_DATA_13 20 +#define FRAME_POS_DATA_14 21 + +#define MCU_PKT_SIZE_MIN 9 + +#define MCU_VERSION_MAX_LEN 64 + +struct rsinput_driver { + struct serdev_device *serdev; + struct input_dev *input; + struct gpio_desc *boot_gpio; + struct gpio_desc *enable_gpio; + struct gpio_desc *reset_gpio; + uint8_t rx_buf[256]; + uint8_t sequence_number; +}; + +static const unsigned int keymap[] = { + BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, + BTN_NORTH, BTN_WEST, BTN_EAST, BTN_SOUTH, + BTN_TL, BTN_TR, BTN_SELECT, BTN_START, + BTN_THUMBL, BTN_THUMBR, BTN_MODE, BTN_BACK +}; + +static uint8_t compute_checksum(const uint8_t *data, size_t len) { + uint8_t checksum = 0; + + for (size_t i = FRAME_POS_SEQ; i < len - 1; i++) { + checksum ^= data[i]; + } + + return checksum; +} + +static int rsinput_send_command(struct rsinput_driver *drv, uint8_t cmd, const uint8_t *data, size_t len) { + uint8_t frame[256]; + uint8_t checksum = 0; + size_t frame_len = 0; + + frame[frame_len++] = FRAME_HEAD_1; + frame[frame_len++] = FRAME_HEAD_2; + frame[frame_len++] = FRAME_HEAD_3; + frame[frame_len++] = FRAME_HEAD_4; + + frame[frame_len++] = drv->sequence_number; + drv->sequence_number++; + + frame[frame_len++] = cmd; + + frame[frame_len++] = len & 0xFF; + frame[frame_len++] = (len >> 8) & 0xFF; + + if (data && len) { + memcpy(&frame[frame_len], data, len); + frame_len += len; + } + + checksum = compute_checksum(frame, frame_len + 1); + frame[frame_len++] = checksum; + + return serdev_device_write_buf(drv->serdev, frame, frame_len); +} + +static int rsinput_init_commands(struct rsinput_driver *drv) { + int error; + + msleep(100); + uint8_t version_request[] = {DATA_COMMOD_VERSION}; + error = rsinput_send_command(drv, CMD_COMMOD, version_request, sizeof(version_request)); + if (error < 0) { + dev_err(&drv->serdev->dev, "Failed to request MCU version: %d\n", error); + return error; + } + + msleep(100); + uint8_t mcu_params[] = { + DATA_COMMOD_SET_PAR, + 0x01, + 0x00, 0x00, 0x00, 0x28, + 0x00, 0x00, 0x00, 0x07 + }; + error = rsinput_send_command(drv, CMD_COMMOD, mcu_params, sizeof(mcu_params)); + if (error < 0) { + dev_err(&drv->serdev->dev, "Failed to set MCU parameters: %d\n", error); + return error; + } + + return 0; +} + +static void handle_cmd_commod(struct rsinput_driver *drv, const uint8_t *data, size_t payload_length) { + switch (data[FRAME_POS_DATA_1]) { + case DATA_COMMOD_VERSION: + if (payload_length >= 1) { + char mcu_version[MCU_VERSION_MAX_LEN] = {0}; + size_t version_length = payload_length; + if (version_length > MCU_VERSION_MAX_LEN - 1) { + version_length = MCU_VERSION_MAX_LEN - 1; + } + memcpy(mcu_version, &data[FRAME_POS_DATA_1], version_length); + mcu_version[version_length] = '\0'; + dev_info(&drv->serdev->dev, "MCU Version: %s\n", mcu_version); + } else { + dev_err(&drv->serdev->dev, "Invalid MCU version response length\n"); + } + break; + case DATA_COMMOD_SET_PAR: + dev_info(&drv->serdev->dev, "MCU parameters set successfully\n"); + break; + default: + dev_warn(&drv->serdev->dev, "Unhandled CMD_COMMOD sub-command: 0x%02x\n", data[FRAME_POS_DATA_1]); + break; + } +} + +static void handle_cmd_status(struct rsinput_driver *drv, const uint8_t *data, size_t payload_length) { + if (payload_length >= 6) { + static unsigned long prev_states; + unsigned long keys = data[FRAME_POS_DATA_1] | (data[FRAME_POS_DATA_2] << 8); + unsigned long current_states = keys, changes; + int i; + + bitmap_xor(&changes, ¤t_states, &prev_states, ARRAY_SIZE(keymap)); + + for_each_set_bit(i, &changes, ARRAY_SIZE(keymap)) { + input_report_key(drv->input, keymap[i], (current_states & BIT(i))); + } + + input_report_abs(drv->input, ABS_HAT2X, + 0x650 - (data[FRAME_POS_DATA_3] | (data[FRAME_POS_DATA_4] << 8))); + input_report_abs(drv->input, ABS_HAT2Y, + 0x650 - (data[FRAME_POS_DATA_5] | (data[FRAME_POS_DATA_6] << 8))); + input_report_abs(drv->input, ABS_X, + -(int16_t)(data[FRAME_POS_DATA_7] | (data[FRAME_POS_DATA_8] << 8))); + input_report_abs(drv->input, ABS_Y, + -(int16_t)(data[FRAME_POS_DATA_9] | (data[FRAME_POS_DATA_10] << 8))); + input_report_abs(drv->input, ABS_RX, + -(int16_t)(data[FRAME_POS_DATA_11] | (data[FRAME_POS_DATA_12] << 8))); + input_report_abs(drv->input, ABS_RY, + -(int16_t)(data[FRAME_POS_DATA_13] | (data[FRAME_POS_DATA_14] << 8))); + + input_sync(drv->input); + prev_states = keys; + + } else { + dev_warn(&drv->serdev->dev, "Invalid CMD_STATUS response length\n"); + } +} + +static void rsinput_process_data(struct rsinput_driver *drv, const uint8_t *data, size_t len) { + while (len >= MCU_PKT_SIZE_MIN) { + uint16_t payload_length = data[FRAME_POS_LEN_L] | (data[FRAME_POS_LEN_H] << 8); + size_t frame_length = MCU_PKT_SIZE_MIN + payload_length; + + if (len < frame_length) { + return; + } + + uint8_t received_checksum = data[frame_length - 1]; + uint8_t computed_checksum = compute_checksum(data, frame_length); + if (computed_checksum != received_checksum) { + data += frame_length; + len -= frame_length; + continue; + } + + switch (data[FRAME_POS_CMD]) { + case CMD_COMMOD: + handle_cmd_commod(drv, data, payload_length); + break; + case CMD_STATUS: + handle_cmd_status(drv, data, payload_length); + break; + default: + dev_warn(&drv->serdev->dev, "Unhandled command: 0x%02X\n", data[FRAME_POS_CMD]); + break; + } + + data += frame_length; + len -= frame_length; + } + + if (len > 0) { + dev_warn(&drv->serdev->dev, "Trailing bytes after processing: %zu\n", len); + } +} + +static size_t rsinput_rx(struct serdev_device *serdev, const u8 *data, size_t count) { + struct rsinput_driver *drv = serdev_device_get_drvdata(serdev); + uint8_t received_checksum, computed_checksum; + + if (!drv || !data || count == 0) { + dev_warn_ratelimited(&serdev->dev, "Invalid RX data\n"); + goto error; + } + + if (count > sizeof(drv->rx_buf)) { + dev_warn_ratelimited(&serdev->dev, "RX buffer overflow\n"); + goto error; + } + + memcpy(drv->rx_buf, data, count); + + if (count < MCU_PKT_SIZE_MIN) { + dev_warn_ratelimited(&serdev->dev, "Frame too short for checksum validation\n"); + goto error; + } + + received_checksum = drv->rx_buf[count - 1]; + + computed_checksum = compute_checksum(drv->rx_buf, count); + + if (computed_checksum != received_checksum) { + rsinput_init_commands(drv); + goto error; + } + + rsinput_process_data(drv, drv->rx_buf, count); + +error: + return count; +} + +static const struct serdev_device_ops rsinput_rx_ops = { + .receive_buf = rsinput_rx, +}; + +static int rsinput_probe(struct serdev_device *serdev) { + struct rsinput_driver *drv; + u32 gamepad_bus = 0; + u32 gamepad_vid = 0; + u32 gamepad_pid = 0; + u32 gamepad_rev = 0; + int error; + + drv = devm_kzalloc(&serdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + drv->boot_gpio = + devm_gpiod_get_optional(&serdev->dev, "boot", GPIOD_OUT_HIGH); + if (IS_ERR(drv->boot_gpio)) { + error = PTR_ERR(drv->boot_gpio); + dev_warn(&serdev->dev, "Unable to get boot gpio: %d\n", error); + } + + drv->enable_gpio = + devm_gpiod_get_optional(&serdev->dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(drv->enable_gpio)) { + error = PTR_ERR(drv->enable_gpio); + dev_warn(&serdev->dev, "Unable to get enable gpio: %d\n", error); + } + + drv->reset_gpio = + devm_gpiod_get_optional(&serdev->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(drv->reset_gpio)) { + error = PTR_ERR(drv->reset_gpio); + dev_warn(&serdev->dev, "Unable to get reset gpio: %d\n", error); + } + + if (drv->boot_gpio) + gpiod_set_value_cansleep(drv->boot_gpio, 0); + + if (drv->reset_gpio) + gpiod_set_value_cansleep(drv->reset_gpio, 0); + + msleep(100); + + if (drv->enable_gpio) + gpiod_set_value_cansleep(drv->enable_gpio, 1); + + if (drv->reset_gpio) + gpiod_set_value_cansleep(drv->reset_gpio, 1); + + msleep(100); + + error = serdev_device_open(serdev); + if (error) + return dev_err_probe(&serdev->dev, error, "Unable to open UART device\n"); + + drv->serdev = serdev; + drv->sequence_number = 0; + + serdev_device_set_drvdata(serdev, drv); + + error = serdev_device_set_baudrate(serdev, 115200); + if (error < 0) + return dev_err_probe(&serdev->dev, error, "Failed to set up host baud rate\n"); + + serdev_device_set_flow_control(serdev, false); + + drv->input = devm_input_allocate_device(&serdev->dev); + if (!drv->input) + return -ENOMEM; + + drv->input->phys = "rsinput-gamepad/input0"; + + error = device_property_read_string(&serdev->dev, "gamepad-name", &drv->input->name); + if (error) { + drv->input->name = "RSInput Gamepad"; + } + + device_property_read_u32(&serdev->dev, "gamepad-bus", &gamepad_bus); + device_property_read_u32(&serdev->dev, "gamepad-vid", &gamepad_vid); + device_property_read_u32(&serdev->dev, "gamepad-pid", &gamepad_pid); + device_property_read_u32(&serdev->dev, "gamepad-rev", &gamepad_rev); + + drv->input->id.bustype = (u16)gamepad_bus; + drv->input->id.vendor = (u16)gamepad_vid; + drv->input->id.product = (u16)gamepad_pid; + drv->input->id.version = (u16)gamepad_rev; + + __set_bit(EV_KEY, drv->input->evbit); + for (int i = 0; i < ARRAY_SIZE(keymap); i++) + input_set_capability(drv->input, EV_KEY, keymap[i]); + + __set_bit(EV_ABS, drv->input->evbit); + for (int i = ABS_X; i <= ABS_RZ; i++) + input_set_abs_params(drv->input, i, -0x580, 0x580, + 0, 0); + + input_set_abs_params(drv->input, ABS_HAT2X, 0, 1830, 0, 30); + input_set_abs_params(drv->input, ABS_HAT2Y, 0, 1830, 0, 30); + + error = input_register_device(drv->input); + if (error) + return error; + + serdev_device_set_client_ops(serdev, &rsinput_rx_ops); + + error = rsinput_init_commands(drv); + if (error < 0) { + serdev_device_close(serdev); + return error; + } + + return 0; +} + +static void rsinput_remove(struct serdev_device *serdev) { + struct rsinput_driver *drv = serdev_device_get_drvdata(serdev); + + serdev_device_close(serdev); + input_unregister_device(drv->input); + if (drv->enable_gpio) + gpiod_set_value_cansleep(drv->enable_gpio, 0); + + if (drv->reset_gpio) + gpiod_set_value_cansleep(drv->reset_gpio, 0); +} + +static const struct of_device_id rsinput_of_match[] = { + { .compatible = "gamepad,rsinput" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rsinput_of_match); + +static struct serdev_device_driver rsinput_driver = { + .probe = rsinput_probe, + .remove = rsinput_remove, + .driver = { + .name = "rsinput", + .of_match_table = rsinput_of_match, + }, +}; + +module_serdev_device_driver(rsinput_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("RSInput Gamepad Driver"); +MODULE_AUTHOR("Teguh Sobirin "); -- 2.34.1