/*
 *
 * Copyright (C) 2012-2021, Samsung Electronics Co., Ltd.
 *
 * STM touch device implementation
 */

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <tee_internal_api.h>
#include <tee_spi.h>
#include "board.h"
#include "bsp_common.h"
#include "dbg.h"
#include "device.h"
#include "touch_gpio.h"
#include "secmap.h"
#include "stm_driver.h"

/* STM_TS read register address */
#define CMD_SENSE_ON              0x10
#define CMD_CALIBRATION           0x13
#define READ_DEVICE_ID            0x22
#define READ_PANEL_INFO           0x23
#define READ_TS_READ_ID           0x52
#define STM_TS_READ_ONE_EVENT     0x87
#define STM_TS_READ_ALL_EVENT     0x87

/* SEC_TS cmd register address */
#define CMD_CLEAR_EVENT_STACK     0x62

#define COORDINATE_EVENT          0
#define STATUS_EVENT              1

#define COORDINATE_ACTION_NONE    0
#define COORDINATE_ACTION_PRESS   1
#define COORDINATE_ACTION_RELEASE 3

#define TOUCHTYPE_NORMAL          0
#define TOUCHTYPE_HOVER           1
#define TOUCHTYPE_FLIPCOVER       2
#define TOUCHTYPE_GLOVE           3
#define TOUCHTYPE_STYLUS          4
#define TOUCHTYPE_PALM            5
#define TOUCHTYPE_WET             6
#define TOUCHTYPE_PROXIMITY       7
#define TOUCHTYPE_JIG             8
#define TOUCHTYPE_RESERVED        9

#define TYPE_STATUS_EVENT_ERR     1
#define TYPE_STATUS_EVENT_INFO    2

/* SEC_TS_INFO : Info acknowledge event */
#define ACK_BOOT_COMPLETE         0x00

/* SEC_TS_ERROR : Error event */
#define ERR_EVENT_QUEUE_FULL      0x01

#define EVENT_BUFF_SIZE           16
#define PANEL_INFO_SIZE           11
#define DEVICE_ID_SIZE            5
#define TS_READ_ID_SIZE           3
#define MAX_EVENT_COUNT           31
#define READ_EVENT_MASK           0x1F
#define STM_PACKET_TOTAL_LEN      MAX_EVENT_COUNT * EVENT_BUFF_SIZE

#define SEC_RETRY_COUNT           5
#define WAIT_TIME                 10
#define SPI_PORT                  2

struct stm_ts_info ts_data;
static TEES_SPIHandler handler;

static unsigned char *tx_buf = NULL;
static unsigned char *rx_buf = NULL;

static int allocate_rxtx_buffers(void)
{
    tx_buf = malloc(STM_PACKET_TOTAL_LEN);
    if (!tx_buf) {
        return false;
    }

    rx_buf = malloc(STM_PACKET_TOTAL_LEN);
    if (!rx_buf) {
        free(tx_buf);
        tx_buf = NULL;
        return false;
    }

    return true;
}

static void deallocate_rxtx_buffers(void)
{
    if (tx_buf) {
        free(tx_buf);
        tx_buf = NULL;
    }
    if (rx_buf) {
        free(rx_buf);
        rx_buf = NULL;
    }
}

#ifdef DR_DBGLOG
static void print_hex(const unsigned char *buf, size_t len)
{
    size_t i;
    printf("\n");
    for (i = 0; i < len; i++) {
        if (i == 16) printf("\n");
        printf("0x%02x ", buf[i]);
    }
    printf("\n");
}
#endif

static int32_t stm_spi_read(uint8_t *reg, int tlen, uint8_t *buf, int rlen)
{
    int res = -1;
    int retries = 0;
    int msg_len;

    memset(tx_buf, 0, STM_PACKET_TOTAL_LEN);
    memset(rx_buf, 0, STM_PACKET_TOTAL_LEN);
    memcpy(&tx_buf[0], reg, tlen);
    msg_len = rlen + tlen + 3;

    TEES_SPITransfer tx = {
        .bufAddr = tx_buf,
        .bufLen = msg_len,
        .transferredLen = 0
    };
    TEES_SPITransfer rx = {
        .bufAddr = rx_buf,
        .bufLen = msg_len,
        .transferredLen = 0
    };

    while (retries < SEC_RETRY_COUNT) {
        res = TEES_SPIWriteRead(handler, &tx, &rx);
        if (res == TEE_SUCCESS) break;
        retries++;
        TEE_Wait(WAIT_TIME);
    }

    if (retries == 5) {
        errPrintf("%s, xdata read fail %d\n", __func__, res);
        res = -EIO;
    } else {
#ifdef DR_DBGLOG
        dbgPrintf("Dump tx_buf Hexa\n");
        print_hex(tx_buf, msg_len);
        dbgPrintf("Dump rx_buf Hexa\n");
        print_hex(rx_buf, msg_len);
#endif
        memcpy(buf, (rx_buf + 2), rlen);
    }
    return rlen;
}

static int stm_spi_write(uint8_t *addr)
{
    int res = -1;
    int retries = 0;
    memset(tx_buf, 0, STM_PACKET_TOTAL_LEN);

    tx_buf[0] = 0xC0;
    memcpy(&tx_buf[1], addr, 1);

    TEES_SPITransfer tx = {
        .bufAddr = tx_buf,
        .bufLen = 2,
        .transferredLen = 0
    };

    do {
        res = TEES_SPIWrite(handler, &tx);
        if (res == TEE_SUCCESS) break;
        retries++;
    } while (retries < SEC_RETRY_COUNT);

    if (retries == SEC_RETRY_COUNT) {
        errPrintf("%s, failed to read data %d\n", __func__, res);
        res = -EIO;
    }

    return res;
}

/**
 * Compute x, y Coordinate
 * @param[in] sec_ts_event_coordinate
 * @param[out] x current x coordinate
 * @param[out] y current y coordinate
 * @return none
 */
static void compute_coordinate(struct stm_ts_event_coordinate *p_event_coord,
                               uint16_t *x, uint16_t *y)
{
    uint16_t tx;
    uint16_t ty;

    tx = (p_event_coord->x_11_4 << 4) | (p_event_coord->x_3_0);
    ty = (p_event_coord->y_11_4 << 4) | (p_event_coord->y_3_0);

    if (ts_data.max_x != ts_data.width && ts_data.max_x != 0) {
        *x = (tx * ts_data.width) / ts_data.max_x;
    } else {
        *x = tx;
    }

    if (ts_data.max_y != ts_data.height && ts_data.max_y != 0) {
        *y =  (ty * ts_data.height) / ts_data.max_y;
    } else {
        *y = ty;
    }
}

/**
 * Get touch data from controller and push it to the touch queue
 * @param[in] sec_dev sec device pointer
 * @return error status
 */
static int stm_irq_process(void)
{
    int ret;
    int t_id;
    int event_id;
    int remain_event_count = 0;
    uint16_t x;
    uint16_t y;
    uint8_t address;
    uint8_t left_event_count;
    uint8_t *event_buff;
    uint8_t read_event_buff[MAX_EVENT_COUNT][EVENT_BUFF_SIZE];
    uint8_t ttype;
    uint8_t curr_pos = 0;
    struct stm_ts_event_coordinate *p_event_coord;
    struct stm_ts_event_status *p_event_status;
    dbgPrintf("%s +\n", __func__);

    if (get_touch_queue_wsize() < 1) {
        return -1;
    }

    address = STM_TS_READ_ONE_EVENT;
    ret = stm_spi_read(&address, 1, read_event_buff[0], EVENT_BUFF_SIZE);

    if (ret != EVENT_BUFF_SIZE) {
        errPrintf("%s: spi read one event failed, ret = %d\n", __func__, ret);
        return ret;
    }
    dbgPrintf("%s: READ_ONE_EVENT = %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
        __func__, read_event_buff[0][0], read_event_buff[0][1], read_event_buff[0][2], read_event_buff[0][3],
        read_event_buff[0][4], read_event_buff[0][5], read_event_buff[0][6], read_event_buff[0][7],
        read_event_buff[0][8], read_event_buff[0][9], read_event_buff[0][10], read_event_buff[0][11],
        read_event_buff[0][12], read_event_buff[0][13], read_event_buff[0][14], read_event_buff[0][15]);

    if (read_event_buff[0][0] == 0) {
        return 0;
    }

    left_event_count = read_event_buff[0][7] & READ_EVENT_MASK;
    remain_event_count = left_event_count;
    if (left_event_count > (MAX_EVENT_COUNT - 1)) {
        errPrintf("%s: event buffer overflow\n", __func__);
        release_all_fingers();
        address = CMD_CLEAR_EVENT_STACK;
        ret = stm_spi_write(&address);
        if (ret < 0) {
            errPrintf("%s: i2c write clear event failed, ret = %d\n", __func__, ret);
            return ret;
        }
        return 0;
    }

    if (get_touch_queue_wsize() < (left_event_count + 1)) {
        left_event_count = 0;
        remain_event_count = 0;
    }
    dbgPrintf("%s: left= %d, remain=%d\n",__func__, left_event_count, remain_event_count);

    if (remain_event_count > 0) {
        address = STM_TS_READ_ALL_EVENT;
        ret = stm_spi_read(&address, 1, read_event_buff[1], (EVENT_BUFF_SIZE * remain_event_count));
        if (ret < 0) {
            errPrintf("%s: spi read all events failed, ret = %d\n", __func__, ret);
            return ret;
        }
        dbgPrintf("%s: READ_ALL_EVENT = %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
            __func__, read_event_buff[1][0], read_event_buff[1][1], read_event_buff[1][2], read_event_buff[1][3],
            read_event_buff[1][4], read_event_buff[1][5], read_event_buff[1][6], read_event_buff[1][7],
            read_event_buff[1][8], read_event_buff[1][9], read_event_buff[1][10], read_event_buff[1][11],
            read_event_buff[1][12], read_event_buff[1][13], read_event_buff[1][14], read_event_buff[1][15]);		
    }

    do {
        event_buff = read_event_buff[curr_pos];
        event_id = event_buff[0] & 0x3;
        dbgPrintf("%s: event id= %d, curr_pos=%d, remain=%d\n",__func__, event_id, curr_pos, remain_event_count);

        switch (event_id) {
        case STATUS_EVENT:
            p_event_status = (struct stm_ts_event_status *)event_buff;
            if (p_event_status->stype > 0) {
                dbgPrintf("%s: STATUS %x %x %x %x %x %x %x %x\n", __func__,
                          event_buff[0], event_buff[1], event_buff[2],
                          event_buff[3], event_buff[4], event_buff[5],
                          event_buff[6], event_buff[7]);
            }

            /* watchdog reset -> send SENSEON command */
            if ((p_event_status->stype == TYPE_STATUS_EVENT_INFO) &&
                (p_event_status->status_id == ACK_BOOT_COMPLETE) &&
                (p_event_status->status_data_1 == 0x20)) {
                errPrintf("%s: watchdog reset event\n", __func__);

                release_all_fingers();
                address = CMD_SENSE_ON;
                ret = stm_spi_write(&address);
                if (ret < 0) {
                    errPrintf("%s: fail to write sense_on\n", __func__);
                }
            }

            /* event queue full-> all finger release */
            if ((p_event_status->stype == TYPE_STATUS_EVENT_ERR) &&
                (p_event_status->status_id == ERR_EVENT_QUEUE_FULL)) {
                errPrintf("%s: IC event queue is full\n", __func__);

                release_all_fingers();
            }
            break;

        case COORDINATE_EVENT:
            p_event_coord = (struct stm_ts_event_coordinate *)event_buff;
            t_id = p_event_coord->tid;
            ttype = p_event_coord->ttype_3_2 << 2 | p_event_coord->ttype_1_0 << 0;

            if (t_id < TUI_MAX_NUM_OF_FINGERS) {
                compute_coordinate(p_event_coord, &x, &y);
                if ((ttype == TOUCHTYPE_NORMAL) || (ttype == TOUCHTYPE_PALM)
                    || (ttype == TOUCHTYPE_GLOVE) || (ttype == TOUCHTYPE_WET)) {
                    if (p_event_coord->tchsta == COORDINATE_ACTION_RELEASE) {
                        add_touch_event(t_id, x, y, tui_ts_release);
                    } else if (p_event_coord->tchsta == COORDINATE_ACTION_PRESS) {
                        add_touch_event(t_id, x, y, tui_ts_press);
                    } else {
                        dbgPrintf("TOUCH %s %d %d\n", __func__, __LINE__, p_event_coord->tchsta);
                    }
                }
            } else {
                dbgPrintf("TOUCH %s tid(%d) is out of range\n", __func__, t_id);
            }
            break;

        default:
            dbgPrintf("TOUCH %s, default case event_id = %d\n", __func__, event_id);
            break;
        }

        ++curr_pos;
        --remain_event_count;
    } while (remain_event_count >= 0);
    dbgPrintf("%s -\n", __func__);
    return 0;
}

/**
 * Initialize STM device driver
 * @param[in] sec_dev sec device pointer
 * @return error status
 */
static int stm_driver_init(void)
{
    int ret;
    uint8_t address;

    if (allocate_rxtx_buffers() == false) {
        ret = TEE_ERROR_OUT_OF_MEMORY;
        return -ret;
    }

    /* Set X,Y Resolution from IC information. */
    ts_data.max_x = SENSOR_MAX_X_DEFAULT;
    ts_data.max_y = SENSOR_MAX_Y_DEFAULT;

    address = CMD_SENSE_ON;
    ret = stm_spi_write(&address);
    if (ret < 0) {
        errPrintf("%s: spi write 'sense on' event failed %d\n", __func__, ret);
    }

    init_touch_queue();
    return 0;
}

/**
 * Uninitialize STM device driver
 * @param[in] sec_dev sec device pointer
 * @return error status
 */
static int stm_driver_release(void)
{
    int ret = 0;
    uint8_t address;

    if (ts_data.initialized) {
        address = CMD_CLEAR_EVENT_STACK;
        ret = stm_spi_write(&address);
        if (ret < 0) {
            errPrintf("%s: i2c write clear event failed %d\n", __func__, ret);
        }
    }
    deallocate_rxtx_buffers();
    return ret;
}

TEE_Result stm_get_info(drTouchInfo_ptr touchSize)
{
    touchSize->width = ts_data.max_x;
    touchSize->height = ts_data.max_y;
    return TEE_SUCCESS;
}

TEE_Result stm_open(uint32_t width, uint32_t height)
{
    deviceInfo_t *touch_dev = &board.touch;
    deviceInfo_t *gpio_dev = &board.gpio;
    TEE_Result retHal = TEE_ERROR_GENERIC;
    int ret = TEE_ERROR_GENERIC;
    ts_data.initialized = 0;

    TEES_SPIConfig cfg = {
        .speedHz = 1000000,
        .CPOL = 0,
        .CPHA = 0,
        .bitsPerWord = 0,
        .isDMAMode = 1,
        .manualClockControl = 1,
    };

    /* Initialization */
    retHal = touch_gpio_init(gpio_dev);
    if (retHal != TEE_SUCCESS) {
        errPrintf("touch_gpio_init() Failed\n");
        goto err_gpio_init;
    }

    retHal = TEES_SPIInit(SPI_PORT, &cfg, &handler);
    if (retHal != TEE_SUCCESS) {
        errPrintf("Failed to initalize SPI, tee_err=%#x\n", retHal);
        goto err_init_spi;
    }

    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 = stm_driver_init();
        if (ret < 0) {
            errPrintf("stm_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 = TEES_SPIExit(handler);
    if (ret != TEE_SUCCESS) {
        errPrintf("spi release failed!\n");
    }

err_init_spi:
    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 stm_close(void)
{
    dbgPrintf(">> %s\n", __func__);
    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 = stm_driver_release();
        if (ret != TEE_SUCCESS) {
            errPrintf("sec_driver_release Failed!\n");
        } else {
            touch_dev->state &= ~DEV_SFR_CONFIGURED;
        }
    }
    ret = TEES_SPIExit(handler);
    if (ret != TEE_SUCCESS) {
        errPrintf("spi 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 stm_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, stm_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 = stm_irq_process();
#endif /* USE_TOUCH_INTERRUPT */
    if (retval != 0) {
        return TEE_ERROR_GENERIC;
    }
    return TEE_SUCCESS;
}
