// SPDX-License-Identifier: GPL-2.0-only
/*
 * Driver for Retroid Pocket Gamepad
 *
 * Copyright (C) 2024 Molly Sophia <mollysophia379@gmail.com>
 * Copyright (C) 2024 BigfootACA <bigfoot@classfun.cn>
 * Copyright (C) 2024 Teguh Sobirin <teguh@sobir.in>
 *
 */

#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/of.h>
#include <linux/serdev.h>
#include <linux/slab.h>
#include <uapi/linux/sched/types.h>

#define DRIVER_NAME "retroid-pocket-gamepad"
#define MAX_DATA_LEN 64
#define INT_SIGN(X) ((X > 0) - (X < 0))

#define gamepad_serdev_write_seq(serdev, seq...)                      \
    do {                                                              \
        static const u8 d[] = { seq };                                \
        struct device *dev = &serdev->dev;                            \
        int ret;                                                      \
        ret = serdev_device_write_buf(serdev, d, ARRAY_SIZE(d));      \
        if (ret < 0 || ret < ARRAY_SIZE(d)) {                         \
            dev_warn_ratelimited(dev, "Unable to write data: %d",     \
                         ret);                                        \
            return;                                                   \
        }                                                             \
    } while (0)

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
};

struct gamepad_data {
    u8 header[4];
    u8 frame_number;
    u8 command;
    u16 data_len;
    u8 data[MAX_DATA_LEN];
};

struct gamepad_device {
    struct device *dev;
    struct gpio_desc *boot_gpio;
    struct gpio_desc *enable_gpio;
    struct gpio_desc *reset_gpio;
    struct input_dev *dev_input;
};


static int update_params=0;

static int trigger_left_max=0x610;
static int trigger_left_deadzone=0x0;
static int trigger_left_antideadzone=0x0;

static int trigger_right_max=0x610;
static int trigger_right_deadzone=0x0;
static int trigger_right_antideadzone=0x0;

static int axis_leftx_min=-0x580;
static int axis_leftx_center=0x0;
static int axis_leftx_max=0x580;
static int axis_leftx_deadzone=0x0;
static int axis_leftx_antideadzone=0x0;

static int axis_lefty_min=-0x580;
static int axis_lefty_center=0x0;
static int axis_lefty_max=0x580;
static int axis_lefty_deadzone=0x0;
static int axis_lefty_antideadzone=0x0;

static int axis_leftz_min=-0x580;
static int axis_leftz_center=0x0;
static int axis_leftz_max=0x580;
static int axis_leftz_deadzone=0x0;
static int axis_leftz_antideadzone=0x0;

static int axis_rightx_min=-0x580;
static int axis_rightx_center=0x0;
static int axis_rightx_max=0x580;
static int axis_rightx_deadzone=0x0;
static int axis_rightx_antideadzone=0x0;

static int axis_righty_min=-0x580;
static int axis_righty_center=0x0;
static int axis_righty_max=0x580;
static int axis_righty_deadzone=0x0;
static int axis_righty_antideadzone=0x0;

static int axis_rightz_min=-0x580;
static int axis_rightz_center=0x0;
static int axis_rightz_max=0x580;
static int axis_rightz_deadzone=0x0;
static int axis_rightz_antideadzone=0x0;

module_param(update_params,int,0660);

module_param(trigger_left_max,int,0660);
module_param(trigger_left_deadzone,int,0660);
module_param(trigger_left_antideadzone,int,0660);


module_param(trigger_right_max,int,0660);
module_param(trigger_right_deadzone,int,0660);
module_param(trigger_right_antideadzone,int,0660);

module_param(axis_leftx_min,int,0660);
module_param(axis_leftx_center,int,0660);
module_param(axis_leftx_max,int,0660);
module_param(axis_leftx_deadzone,int,0660);
module_param(axis_leftx_antideadzone,int,0660);


module_param(axis_lefty_min,int,0660);
module_param(axis_lefty_center,int,0660);
module_param(axis_lefty_max,int,0660);
module_param(axis_lefty_deadzone,int,0660);
module_param(axis_lefty_antideadzone,int,0660);

module_param(axis_leftz_min,int,0660);
module_param(axis_leftz_center,int,0660);
module_param(axis_leftz_max,int,0660);
module_param(axis_leftz_deadzone,int,0660);
module_param(axis_leftz_antideadzone,int,0660);

module_param(axis_rightx_min,int,0660);
module_param(axis_rightx_center,int,0660);
module_param(axis_rightx_max,int,0660);
module_param(axis_rightx_deadzone,int,0660);
module_param(axis_rightx_antideadzone,int,0660);

module_param(axis_righty_min,int,0660);
module_param(axis_righty_center,int,0660);
module_param(axis_righty_max,int,0660);
module_param(axis_righty_deadzone,int,0660);
module_param(axis_righty_antideadzone,int,0660);

module_param(axis_rightz_min,int,0660);
module_param(axis_rightz_center,int,0660);
module_param(axis_rightz_max,int,0660);
module_param(axis_rightz_deadzone,int,0660);
module_param(axis_rightz_antideadzone,int,0660);


static u8 gamepad_data_checksum(const u8 *data, size_t count)
{
    const u8 *ptr = data;
    u8 ret = data[4];

    for (int i = 5; i < count - 1; i++)
        ret ^= ptr[i];

    return ret;
}

static void gamepad_send_init_sequence(struct serdev_device *serdev)
{
    gamepad_serdev_write_seq(serdev, 0xA5, 0xD3, 0x5A, 0x3D, 0x0, 0x1, 0x2,
                 0x0, 0x7, 0x1, 0x5);
    msleep(100);
    gamepad_serdev_write_seq(serdev, 0xA5, 0xD3, 0x5A, 0x3D, 0x1, 0x1, 0x1,
                 0x0, 0x6, 0x7);
    msleep(100);
    gamepad_serdev_write_seq(serdev, 0xA5, 0xD3, 0x5A, 0x3D, 0x2, 0x1, 0x1, 0x0, 0x2, 0x0);
    msleep(100);
    gamepad_serdev_write_seq(serdev, 0xa5, 0xd3, 0x5a, 0x3d, 0x3, 0x01,
                 0x0a, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x28,
                 0x00, 0x00, 0x00, 0x07, 0x23);
    msleep(100);
    gamepad_serdev_write_seq(serdev, 0xA5, 0xD3, 0x5A, 0x3D, 0x4, 0x1, 0x1,
                 0x0, 0x6, 0x2);
    msleep(100);
    gamepad_serdev_write_seq(serdev, 0xA5, 0xD3, 0x5A, 0x3D, 0x5, 0x1, 0x1,
                 0x0, 0x2, 0x7);
    msleep(100);
}

static void gamepad_input_handler(struct gamepad_device *dev,
                  struct gamepad_data *data)
{
    static unsigned long prev_states;
    unsigned long keys = data->data[0] | (data->data[1] << 8);
    unsigned long current_states = keys, changes;
    int i;
    struct input_dev *indev;

    indev = dev->dev_input;
    if (!indev)
        return;

    if (update_params)
    {
        input_set_abs_params(indev, ABS_X,
            INT_SIGN(axis_leftx_min) * ( abs(axis_leftx_min) - axis_leftx_antideadzone ),
            INT_SIGN(axis_leftx_max) * ( abs(axis_leftx_max) - axis_leftx_antideadzone ),
            0, 0);
        input_set_abs_params(indev, ABS_Y,
            INT_SIGN(axis_lefty_min) * ( abs(axis_lefty_min) - axis_leftx_antideadzone ),
            INT_SIGN(axis_lefty_max) * ( abs(axis_lefty_max) - axis_leftx_antideadzone ),
             0, 0);
        input_set_abs_params(indev, ABS_Z,
            INT_SIGN(axis_leftz_min) * ( abs(axis_leftz_min) - axis_leftx_antideadzone ),
            INT_SIGN(axis_leftz_max) * ( abs(axis_leftz_max) - axis_leftx_antideadzone ),
             0, 0);

        input_set_abs_params(indev, ABS_RX,
            INT_SIGN(axis_rightx_min) * ( abs(axis_rightx_min) - axis_leftx_antideadzone ),
            INT_SIGN(axis_rightx_max) * ( abs(axis_rightx_max) - axis_leftx_antideadzone ),
            0, 0);
        input_set_abs_params(indev, ABS_RY,
            INT_SIGN(axis_righty_min) * ( abs(axis_righty_min) - axis_leftx_antideadzone ),
            INT_SIGN(axis_righty_max) * ( abs(axis_righty_max) - axis_leftx_antideadzone ),
             0, 0);
        input_set_abs_params(indev, ABS_RZ,
            INT_SIGN(axis_rightz_min) * ( abs(axis_rightz_min) - axis_leftx_antideadzone ),
            INT_SIGN(axis_rightz_max) * ( abs(axis_rightz_max) - axis_leftx_antideadzone ),
             0, 0);

        input_set_abs_params(indev, ABS_HAT2X, 0, trigger_left_max - trigger_left_antideadzone, 0, 30);
        input_set_abs_params(indev, ABS_HAT2Y, 0, trigger_right_max - trigger_right_antideadzone, 0, 30);
        update_params = 0;
    }
    

    bitmap_xor(&changes, &current_states, &prev_states, ARRAY_SIZE(keymap));

    for_each_set_bit(i, &changes, ARRAY_SIZE(keymap)) {
        input_report_key(indev, keymap[i], (current_states & BIT(i)));
    }

    int16_t value;

    value = (int16_t)(trigger_left_max - (data->data[2] | (data->data[3] << 8)));
    input_report_abs(indev, ABS_HAT2X,
            ( value < trigger_left_deadzone )? 0 : value - trigger_left_antideadzone);

    value = (int16_t)(trigger_right_max - (data->data[4] | (data->data[5] << 8)));
    input_report_abs(indev, ABS_HAT2Y,
            ( value < trigger_right_deadzone )? 0 : value - trigger_right_antideadzone);

    value = -(int16_t)(data->data[6] | (data->data[7] << 8));
    value += axis_leftx_center;
    input_report_abs(indev, ABS_X,
            ( abs(value) < axis_leftx_deadzone )? 0 : INT_SIGN(value) * ( abs(value) - axis_leftx_antideadzone ));

    value = -(int16_t)(data->data[8] | (data->data[9] << 8));
    value += axis_lefty_center;
    input_report_abs(indev, ABS_Y,
            ( abs(value) < axis_lefty_deadzone )? 0 : INT_SIGN(value) * ( abs(value) - axis_lefty_antideadzone ));

    value = -(int16_t)(data->data[10] | (data->data[11] << 8));
    value += axis_rightx_center;
    input_report_abs(indev, ABS_RX,
            ( abs(value) < axis_rightx_deadzone )? 0 : INT_SIGN(value) * ( abs(value) - axis_rightx_antideadzone ));

    value = -(int16_t)(data->data[12] | (data->data[13] << 8));
    value += axis_righty_center;
    input_report_abs(indev, ABS_RY,
            ( abs(value) < axis_righty_deadzone )? 0 : INT_SIGN(value) * ( abs(value) - axis_righty_antideadzone ));

    input_sync(indev);
    prev_states = keys;
}

static void gamepad_data_handler(struct serdev_device *serdev,
                 struct gamepad_data *data)
{
    struct gamepad_device *dev = serdev_device_get_drvdata(serdev);

    switch (data->command) {
    case 0x2:
        gamepad_input_handler(dev, data);
        break;
    default:
        break;
    }
}

static size_t gamepad_mcu_uart_rx_bytes(struct serdev_device *serdev,
                    const u8 *data, size_t count)
{
    struct device *dev = &serdev->dev;
    struct gamepad_data recv_data_buffer;
    u8 checksum;

    if (!data || count < 7) {
        dev_warn_ratelimited(dev, "invalid packet");
        return count;
    }

    checksum = gamepad_data_checksum(data, count);
    if (checksum != *(data + count - 1)) {
        //dev_warn_ratelimited(dev, "packet checksum failed");
        return count;
    }

    memcpy(recv_data_buffer.header, data, 4);
    recv_data_buffer.frame_number = *(data + 4);
    recv_data_buffer.command = *(data + 5);
    recv_data_buffer.data_len = *(data + 6) | (*(data + 7) << 8);

    if (recv_data_buffer.data_len) {
        memset(&recv_data_buffer.data[0], 0,
               sizeof(recv_data_buffer.data));
        memcpy(&recv_data_buffer.data[0], data + 8,
               recv_data_buffer.data_len);
    }

    gamepad_data_handler(serdev, &recv_data_buffer);

    return count;
}

static const struct serdev_device_ops gamepad_mcu_uart_client_ops = {
    .receive_buf = gamepad_mcu_uart_rx_bytes,
};

static int gamepad_mcu_uart_probe(struct serdev_device *serdev)
{
    struct device *dev = &serdev->dev;
    struct gamepad_device *gamepad_dev;
    u32 gamepad_bus = 0;
    u32 gamepad_vid = 0;
    u32 gamepad_pid = 0;
    u32 gamepad_rev = 0;
    int ret;

    gamepad_dev = devm_kzalloc(dev, sizeof(*gamepad_dev), GFP_KERNEL);
    if (!gamepad_dev)
        return dev_err_probe(dev, -ENOMEM, "could not allocate memory for device data\n");

    gamepad_dev->boot_gpio =
        devm_gpiod_get_optional(dev, "boot", GPIOD_OUT_HIGH);
    if (IS_ERR(gamepad_dev->boot_gpio)) {
        ret = PTR_ERR(gamepad_dev->boot_gpio);
        dev_warn(dev, "Unable to get boot gpio: %d\n", ret);
    }

    gamepad_dev->enable_gpio =
        devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
    if (IS_ERR(gamepad_dev->enable_gpio)) {
        ret = PTR_ERR(gamepad_dev->enable_gpio);
        dev_warn(dev, "Unable to get enable gpio: %d\n", ret);
    }

    gamepad_dev->reset_gpio =
        devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
    if (IS_ERR(gamepad_dev->reset_gpio)) {
        ret = PTR_ERR(gamepad_dev->reset_gpio);
        dev_warn(dev, "Unable to get reset gpio: %d\n", ret);
    }

    if (gamepad_dev->boot_gpio)
        gpiod_set_value_cansleep(gamepad_dev->boot_gpio, 0);

    if (gamepad_dev->reset_gpio)
        gpiod_set_value_cansleep(gamepad_dev->reset_gpio, 0);

    msleep(100);

    if (gamepad_dev->enable_gpio)
        gpiod_set_value_cansleep(gamepad_dev->enable_gpio, 1);

    if (gamepad_dev->reset_gpio)
        gpiod_set_value_cansleep(gamepad_dev->reset_gpio, 1);

    msleep(100);

    ret = devm_serdev_device_open(dev, serdev);
    if (ret)
        return dev_err_probe(dev, ret, "Unable to open UART device\n");
    gamepad_dev->dev = dev;

    serdev_device_set_drvdata(serdev, gamepad_dev);

    ret = serdev_device_set_baudrate(serdev, 115200);
    if (ret < 0)
        return dev_err_probe(dev, ret, "Failed to set up host baud rate\n");

    serdev_device_set_flow_control(serdev, false);

    gamepad_dev->dev_input = devm_input_allocate_device(dev);
    if (!gamepad_dev->dev_input)
        return dev_err_probe(dev, -ENOMEM, "Not enough memory for input device\n");

    gamepad_dev->dev_input->phys = DRIVER_NAME"/input0";

    ret = device_property_read_string(dev, "gamepad-name", &gamepad_dev->dev_input->name);
    if (ret) {
        gamepad_dev->dev_input->name = "Retroid Pocket Gamepad";
    }

    device_property_read_u32(dev, "gamepad-bus", &gamepad_bus);
    device_property_read_u32(dev, "gamepad-vid", &gamepad_vid);
    device_property_read_u32(dev, "gamepad-pid", &gamepad_pid);
    device_property_read_u32(dev, "gamepad-rev", &gamepad_rev);

    gamepad_dev->dev_input->id.bustype = (u16)gamepad_bus;
    gamepad_dev->dev_input->id.vendor  = (u16)gamepad_vid;
    gamepad_dev->dev_input->id.product = (u16)gamepad_pid;
    gamepad_dev->dev_input->id.version = (u16)gamepad_rev;

    __set_bit(EV_KEY, gamepad_dev->dev_input->evbit);
    for (int i = 0; i < ARRAY_SIZE(keymap); i++)
        input_set_capability(gamepad_dev->dev_input, EV_KEY, keymap[i]);

    __set_bit(EV_ABS, gamepad_dev->dev_input->evbit);

    input_set_abs_params(gamepad_dev->dev_input, ABS_X,
        INT_SIGN(axis_leftx_min) * ( abs(axis_leftx_min) - axis_leftx_antideadzone ),
        INT_SIGN(axis_leftx_max) * ( abs(axis_leftx_max) - axis_leftx_antideadzone ),
        0, 0);
    input_set_abs_params(gamepad_dev->dev_input, ABS_Y,
        INT_SIGN(axis_lefty_min) * ( abs(axis_lefty_min) - axis_leftx_antideadzone ),
        INT_SIGN(axis_lefty_max) * ( abs(axis_lefty_max) - axis_leftx_antideadzone ),
        0, 0);
    input_set_abs_params(gamepad_dev->dev_input, ABS_Z,
        INT_SIGN(axis_leftz_min) * ( abs(axis_leftz_min) - axis_leftx_antideadzone ),
        INT_SIGN(axis_leftz_max) * ( abs(axis_leftz_max) - axis_leftx_antideadzone ),
        0, 0);

    input_set_abs_params(gamepad_dev->dev_input, ABS_RX,
        INT_SIGN(axis_rightx_min) * ( abs(axis_rightx_min) - axis_leftx_antideadzone ),
        INT_SIGN(axis_rightx_max) * ( abs(axis_rightx_max) - axis_leftx_antideadzone ),
        0, 0);
    input_set_abs_params(gamepad_dev->dev_input, ABS_RY,
        INT_SIGN(axis_righty_min) * ( abs(axis_righty_min) - axis_leftx_antideadzone ),
        INT_SIGN(axis_righty_max) * ( abs(axis_righty_max) - axis_leftx_antideadzone ),
        0, 0);
    input_set_abs_params(gamepad_dev->dev_input, ABS_RZ,
        INT_SIGN(axis_rightz_min) * ( abs(axis_rightz_min) - axis_leftx_antideadzone ),
        INT_SIGN(axis_rightz_max) * ( abs(axis_rightz_max) - axis_leftx_antideadzone ),
        0, 0);

        input_set_abs_params(gamepad_dev->dev_input, ABS_HAT2X, 0, trigger_left_max - trigger_left_antideadzone, 0, 30);
        input_set_abs_params(gamepad_dev->dev_input, ABS_HAT2Y, 0, trigger_right_max - trigger_right_antideadzone, 0, 30);

    ret = input_register_device(gamepad_dev->dev_input);
    if (ret)
        return dev_err_probe(dev, ret, "Could not register input device\n");

    serdev_device_set_client_ops(serdev, &gamepad_mcu_uart_client_ops);

    gamepad_send_init_sequence(serdev);

    return 0;
}

static const struct of_device_id gamepad_mcu_uart_of_match[] = {
    { .compatible = "retroid,retroid-pocket-gamepad" },
    {}
};
MODULE_DEVICE_TABLE(of, gamepad_mcu_uart_of_match);

static struct serdev_device_driver gamepad_mcu_uart_driver = {
    .driver = {
        .name = DRIVER_NAME,
        .of_match_table = gamepad_mcu_uart_of_match,
    },
    .probe = gamepad_mcu_uart_probe,
};

module_serdev_device_driver(gamepad_mcu_uart_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Gamepad driver for Retroid Pocket");
MODULE_AUTHOR("Molly Sophia <mollysophia379@gmail.com>");
MODULE_AUTHOR("BigfootACA <bigfoot@classfun.cn>");
MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in>");
MODULE_ALIAS("platform:" DRIVER_NAME);
