/*
 *
 * Copyright (C) 2012-2021, Samsung Electronics Co., Ltd.
 *
 * Goodix touch device implementation
 */

#include <tee_internal_api.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "board.h"
#include "bsp_common.h"
#include "dbg.h"
#include "device.h"
#include "i2c.h"
#include "touch_gpio.h"
#include "secmap.h"
#include "goodix_driver.h"

#define SEC_TS_COORDINATE_EVENT 0

#define GOODIX_MAX_TOUCH    10

#define IRQ_EVENT_HEAD_LEN  8
#define BYTES_PER_POINT     16
#define COOR_DATA_CHECKSUM_SIZE 2

#define BERLIN_REG_ADDR_SIZE    4U
#define GOODIX_BUS_RETRY_TIMES  3

#define SEC_TS_SUPPORT_TOUCH_COUNT  10

enum CHECKSUM_MODE {
    CHECKSUM_MODE_U8_LE,
    CHECKSUM_MODE_U16_LE,
};

enum coord_action {
    SEC_TS_COORDINATE_ACTION_NONE = 0,
    SEC_TS_COORDINATE_ACTION_PRESS = 1,
    SEC_TS_COORDINATE_ACTION_MOVE = 2,
    SEC_TS_COORDINATE_ACTION_RELEASE = 3,
    SEC_TS_COORDINATE_ACTION_FORCE_RELEASE = 4,
};

struct goodix_ts_info ts_data;

static int goodix_i2c_read(uint32_t addr, uint8_t *buff, uint16_t count)
{
    TEE_Result res;
    deviceInfo_t *i2cDev = &board.i2c;
    int try_count = GOODIX_BUS_RETRY_TIMES;
    uint8_t addr_buf[BERLIN_REG_ADDR_SIZE];

    if (!buff || !count) {
        return -EINVAL;
    }

    addr_buf[0] = (addr >> 24) & 0xFF;
    addr_buf[1] = (addr >> 16) & 0xFF;
    addr_buf[2] = (addr >> 8) & 0xFF;
    addr_buf[3] = addr & 0xFF;

    do {
        res = i2c_read_write(i2cDev, i2cDev->bus.slave_address, addr_buf,
                             BERLIN_REG_ADDR_SIZE, buff, count);
        if (res == TEE_SUCCESS) {
            return count;
        }
    } while(--try_count);

    return -EIO;
}

static int goodix_i2c_write(uint32_t addr, uint8_t *buff, uint16_t count)
{
    TEE_Result res;
    deviceInfo_t *i2cDev = &board.i2c;
    uint8_t put_buf[128];
    uint8_t *buf_ptr;
    int ret;
    int try_count = GOODIX_BUS_RETRY_TIMES;

    if (!buff && count) {
        return -EINVAL;
    }

    if (count + BERLIN_REG_ADDR_SIZE < sizeof(put_buf)) {
        /* code optimize,use stack memory */
        buf_ptr = &put_buf[0];
    } else {
        buf_ptr = malloc(count + BERLIN_REG_ADDR_SIZE);
        if (buf_ptr == NULL) {
            return -ENOMEM;
        }
    }

    buf_ptr[0] = (addr >> 24) & 0xFF;
    buf_ptr[1] = (addr >> 16) & 0xFF;
    buf_ptr[2] = (addr >> 8) & 0xFF;
    buf_ptr[3] = addr & 0xFF;

    memcpy(&buf_ptr[BERLIN_REG_ADDR_SIZE], buff, count);

    do {
        res = i2c_send(i2cDev, i2cDev->bus.slave_address, buf_ptr,
                       count + BERLIN_REG_ADDR_SIZE);
        if (res == TEE_SUCCESS) {
            ret = count;
            goto free;
        }
    } while(--try_count);

    ret = -EIO;

free:
    if (buf_ptr != put_buf) {
        free(buf_ptr);
    }

    return ret;
}

static void goodix_parse_finger(unsigned int tid, uint8_t *buf)
{
    uint8_t action;
    uint8_t t_id = 0;
    uint16_t x;
    uint16_t y;
    static char prev_action[SEC_TS_SUPPORT_TOUCH_COUNT];

    dbgPrintf("%s : ALL(%d): %02X %02X %02X %02X %02X %02X %02X %02X\n",
              __func__, tid, buf[0], buf[1], buf[2], buf[3],
            buf[4], buf[5], buf[6], buf[7]);

    action = (buf[0] & 0xC0) >> 6;
    x = buf[1] << 4 | (buf[3] & 0xF0) >> 4;
    y = buf[2] << 4 | (buf[3] & 0x0F);

    if (action == SEC_TS_COORDINATE_ACTION_RELEASE) {
        if (prev_action[tid] == SEC_TS_COORDINATE_ACTION_NONE
                || prev_action[tid] == SEC_TS_COORDINATE_ACTION_RELEASE) {
            errPrintf("%s: tID %d released without press\n", __func__, t_id);
            return;
        }
        add_touch_event(tid, x, y, tui_ts_release);
    } else if (action == SEC_TS_COORDINATE_ACTION_PRESS) {
        add_touch_event(tid, x, y, tui_ts_press);
    } else if (action == SEC_TS_COORDINATE_ACTION_MOVE) {
        if (prev_action[tid] == SEC_TS_COORDINATE_ACTION_NONE
                || prev_action[tid] == SEC_TS_COORDINATE_ACTION_RELEASE) {
            add_touch_event(t_id, x, y, tui_ts_press);
        }
    } else {
        errPrintf("Unexpected touch event: %x\n", action);
        prev_action[t_id] = SEC_TS_COORDINATE_ACTION_NONE;
        return;
    }

    prev_action[tid] = action;
}

/* checksum_cmp: check data valid or not
 * @data: data need to be check
 * @size: data length need to be check(include the checksum bytes)
 * @mode: compare with U8 or U16 mode
 * */
static int checksum_cmp(const uint8_t *data, int size, int mode)
{
    uint32_t cal_checksum = 0;
    uint32_t r_checksum = 0;
    int i;

    if (mode == CHECKSUM_MODE_U8_LE) {
        if (size < 2)
            return 1;
        for (i = 0; i < size - 2; i++)
            cal_checksum += data[i];

        r_checksum = data[size - 2] + (data[size - 1] << 8);
        return (cal_checksum & 0xFFFF) == r_checksum ? 0 : 1;
    }

    if (size < 4)
        return 1;

    for (i = 0; i < size - 4; i += 2)
        cal_checksum += data[i] + (data[i + 1] << 8);
    r_checksum = data[size - 4] + (data[size - 3] << 8) +
            (data[size - 2] << 16) + (data[size - 1] << 24);

    return cal_checksum == r_checksum ? 0 : 1;
}

/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
static int goodix_irq_process(void)
{
    uint8_t buffer[IRQ_EVENT_HEAD_LEN + BYTES_PER_POINT * 16 + 2];
    uint8_t sync_clean = 0;
    int pre_read_len;   // read 2 finger data at first time
    int event_num;
    int ret;

    dbgPrintf("%s +\n", __func__);

    if (get_touch_queue_wsize() < 1) {
        return -1;
    }

    pre_read_len = IRQ_EVENT_HEAD_LEN + BYTES_PER_POINT * 2 + COOR_DATA_CHECKSUM_SIZE;
    ret = goodix_i2c_read(ts_data.touch_data_addr, buffer, pre_read_len);
    if (ret < 0) {
        errPrintf("failed get event head data\n");
        return ret;
    }

    if (buffer[0] == 0) {  // when user doesn't touch.
        dbgPrintf("empty buffer[0] data\n");
        return 0;
    }
    dbgPrintf("buffer = [0x%X 0x%X 0x%X 0x%X 0x%X 0x%X 0x%X 0x%X]\n", buffer[0],
        buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);

    // read done
    if (ts_data.tools_ctrl_sync) {
        ret = goodix_i2c_write(ts_data.touch_data_addr, &sync_clean, 1);
        if (ret < 0) {
            errPrintf("failed to write data\n");
        }
    }

    if (checksum_cmp(buffer, IRQ_EVENT_HEAD_LEN, CHECKSUM_MODE_U8_LE)) {
        errPrintf("touch head checksum err[0x%X 0x%X 0x%X 0x%X 0x%X 0x%X 0x%X 0x%X]\n",
                buffer[0], buffer[1], buffer[2], buffer[3],
                buffer[4], buffer[5], buffer[6], buffer[7]);
        return -EINVAL;
    }

    event_num = buffer[2] & 0x0F;
    if (event_num > 2) {
        dbgPrintf("read remain event event_num(%d)\n", event_num);
        ret = goodix_i2c_read(ts_data.touch_data_addr + pre_read_len,
                              &buffer[pre_read_len],
                                (event_num - 2) * BYTES_PER_POINT);
        if (ret) {
            errPrintf("failed to get remain event\n");
            return ret;
        }
    }

    if (event_num > 0) {
        int event_read_len = event_num * BYTES_PER_POINT + COOR_DATA_CHECKSUM_SIZE;

        if (checksum_cmp(&buffer[IRQ_EVENT_HEAD_LEN], event_read_len, CHECKSUM_MODE_U8_LE)) {
            errPrintf("event data checksum error\n");
            return -EINVAL;
        }
    }

    /* handler all event */
    if ((buffer[0] & 0x80) && (event_num > 0)) {
        unsigned char *event_data = buffer + IRQ_EVENT_HEAD_LEN;
        uint8_t eid;
        unsigned int tid;

        do {
            eid = event_data[0] & 0x03;

            switch (eid) {
            case SEC_TS_COORDINATE_EVENT:
                tid = (event_data[0] & 0x3C) >> 2;

                if (tid >= GOODIX_MAX_TOUCH) {
                    errPrintf("invalid touch id = %d\n", tid);
                    break;
                }

                goodix_parse_finger(tid, event_data);
                break;
            default:
                dbgPrintf("invalid event id[%x]\n", eid);
                break;
            }

            event_data += BYTES_PER_POINT;
        } while (--event_num);
    } else if ((buffer[0] & 0x80) && (event_num == 0)) {
        /* if event_num equal 0, realease all fingers */
        dbgPrintf("event num is zero [%02x%02x%02x]\n", buffer[0], buffer[1], buffer[2]);
        release_all_fingers();
    } else {
        errPrintf("invalid event flag[%x]\n", buffer[0]);
    }

    dbgPrintf("%s -\n", __func__);
    return 0;
}

/**
 * Initialize Goodix device driver
 * @param[in] sec_dev sec device pointer
 * @return error status
 */
static int goodix_driver_init(void)
{
    int ret;
    uint8_t data[10];
    ts_data.touch_data_addr = 0x10014;
    static unsigned char rom_id[] = {'B', 'E', 'R', 'L', 'I', 'N'}; /* rom PID (BERLIN) */

    ret = goodix_i2c_read(ts_data.touch_data_addr, data, sizeof(rom_id));
    if (ret < 0) {
        errPrintf("%s: read id fail %d\n", __func__, ret);
        return ret;
    }
    if (!memcmp(rom_id, data, sizeof(rom_id))) {
        dbgPrintf("%s: tsc pid matched!!!\n", __func__);
    } else {
        errPrintf("%s: tsc pid mismatch %d\n", __func__, ret);
        return ret;
    }

    ts_data.max_x = SENSOR_MAX_X_DEFAULT;
    ts_data.max_y = SENSOR_MAX_Y_DEFAULT;

    ts_data.tools_ctrl_sync = 1;
    ts_data.touch_data_addr = 0x10308;

    init_touch_queue();

    return 0;
}

/**
 * Uninitialize Goodix device driver
 * @param[in] sec_dev sec device pointer
 * @return error status
 */
static int goodix_driver_release(void)
{
    return 0;
}

TEE_Result goodix_get_info(drTouchInfo_ptr touchSize)
{
    touchSize->width = ts_data.max_x;
    touchSize->height = ts_data.max_y;
    return TEE_SUCCESS;
}

TEE_Result goodix_open(uint32_t width, uint32_t height)
{
    deviceInfo_t *touch_dev = &board.touch;
    deviceInfo_t *i2c_dev = &board.i2c;
    deviceInfo_t *gpio_dev = &board.gpio;
    TEE_Result retHal = TEE_ERROR_GENERIC;
    int ret = TEE_ERROR_GENERIC;
    ts_data.initialized = 0;

    i2c_dev->bus.slave_address = GOODIX_I2C_ADDR;
    dbgPrintf(">> %s, i2c slave addr = 0x%02x\n", __func__, i2c_dev->bus.slave_address);

    /* Initialization */
    retHal = touch_gpio_init(gpio_dev);
    if (retHal != TEE_SUCCESS) {
        errPrintf("touch_gpio_init() Failed\n");
        goto err_gpio_init;
    }

    retHal = i2c_init(i2c_dev);
    if (retHal != TEE_SUCCESS) {
        errPrintf("i2c_init() Failed\n");
        goto err_init_i2c;
    }

    if (touch_dev->state == DEV_UNCONFIGURED) {
        /* Init touch module */
        dbgPrintf("Touch Init Start\n");
        TEE_MemFill(&ts_data, 0, sizeof(ts_data));

        ts_data.width = width;
        ts_data.height = height;

        /* Initialize SEC touchscreen */
        ret = goodix_driver_init();
        if (ret < 0) {
            errPrintf("goodix_driver_init() : retval = %d\n", ret);
            retHal = E_TUI_HAL_IO;
            goto err_init_sec;
        }
        ts_data.initialized = 1;
        touch_dev->state |= DEV_SFR_CONFIGURED;
    }

    sec_release();
    dbgPrintf("<< %s\n", __func__);
    return retHal;

err_init_sec:
    ret = i2c_release(i2c_dev);
    if (ret != TEE_SUCCESS) {
        errPrintf("i2c_release Failed!\n");
    }
err_init_i2c:
    ret = touch_gpio_release(gpio_dev);
    if (ret != TEE_SUCCESS) {
        errPrintf("touch_gpio_unregister_int Failed!\n");
    }
err_gpio_init:
    sec_release();
    dbgPrintf("<< %s\n", __func__);
    return retHal;
}

TEE_Result goodix_close(void)
{
    dbgPrintf(">> %s\n", __func__);
    deviceInfo_t *i2c_dev = &board.i2c;
    deviceInfo_t *touch_dev = &board.touch;
    deviceInfo_t *gpio_dev = &board.gpio;
    TEE_Result ret;

    if (touch_dev->state == DEV_UNCONFIGURED)
        return TEE_SUCCESS;

    ts_data.initialized = 0;
    ret = touch_gpio_unregister_int(gpio_dev);
    if (ret != TEE_SUCCESS) {
        errPrintf("touch_gpio_unregister_int Failed!\n");
    }
    if (touch_dev->state & DEV_SFR_CONFIGURED) {
        ret = goodix_driver_release();
        if (ret != TEE_SUCCESS) {
            errPrintf("sec_driver_release Failed!\n");
        } else {
            touch_dev->state &= ~DEV_SFR_CONFIGURED;
        }
    }
    ret = i2c_release(i2c_dev);
    if (ret != TEE_SUCCESS) {
        errPrintf("i2c_release Failed!\n");
    }
    ret = touch_gpio_release(gpio_dev);
    if (ret != TEE_SUCCESS) {
        errPrintf("touch_gpio_release Failed!\n");
    }

    sec_release();
    touch_dev->state = DEV_UNCONFIGURED;
    dbgPrintf("<< %s\n", __func__);
    return TEE_SUCCESS;
}

TEE_Result goodix_process(void)
{
    dbgPrintf(">> %s\n", __func__);

    int retval = 0;
    deviceInfo_t *touch_dev = &board.touch;

    if (touch_dev->state != DEV_SFR_CONFIGURED && touch_dev->state != DEV_CONFIGURED) {
        return TEE_ERROR_GENERIC;
    }

#ifdef USE_TOUCH_INTERRUPT
    deviceInfo_t *gpio_dev = &board.gpio;

    if (touch_dev->state == DEV_SFR_CONFIGURED) {
        retval = touch_gpio_register_int(gpio_dev, goodix_irq_process);
        if (retval != TEE_SUCCESS) {
            errPrintf("touch_gpio_register_int() : retval = %d\n", retval);
            return TEE_ERROR_GENERIC;
        }
        touch_dev->state = DEV_CONFIGURED;
    }
    gpio_wait_touch_irq();
#else /* USE_TOUCH_INTERRUPT */
    retval = goodix_irq_process();
#endif /* USE_TOUCH_INTERRUPT */
    if (retval != 0) {
        return TEE_ERROR_GENERIC;
    }
    return TEE_SUCCESS;
}
