/*
 *
 * Copyright (C) 2022, Samsung Electronics Co., Ltd.
 *
 * Focaltech ft820x touchscreen routines
 */

#include "board.h"
#include "bsp_common.h"
#include "dbg.h"
#include "device.h"
#include "focaltech_driver.h"
#include "i2c.h"
#include "secmap.h"
#include "touch_gpio.h"
#include "touch_queue.h"
#include <driver/interrupt/interrupt.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <tee_internal_api.h>

#define _FT8201AB                   0x8201B821

#define FTS_MAX_POINTS_SUPPORT      10
#define FTS_TOUCH_POINT_NUM         2
#define FTS_ONE_TCH_LEN             6
#define FTS_TOUCH_DATA_LEN          (FTS_MAX_POINTS_SUPPORT * FTS_ONE_TCH_LEN + 3)
#define FTS_GESTURE_POINTS_MAX      6
#define FTS_GESTURE_DATA_LEN        (FTS_GESTURE_POINTS_MAX * 4 + 4)
#define FTS_TOUCH_ID_POS            5
#define FTS_MAX_ID                  0x0A
#define FTS_TOUCH_X_H_POS           3
#define FTS_TOUCH_Y_H_POS           5
#define FTS_TOUCH_X_L_POS           4
#define FTS_TOUCH_Y_L_POS           6
#define FTS_TOUCH_EVENT_POS         3
#define FTS_CHIP_TYPE               _FT8201AB
#define FLAG_IDC_BIT                11
#define FTS_CMD_START_DELAY         12
#define FTS_CMD_READ_ID             0x90
#define FTS_CMD_READ_ID_LEN_INCELL  1
#define FTS_CMD_START1              0x55
#define FTS_CMD_START2              0xAA
#define FTS_KEY_DIM                 10

#define FTS_TOUCH_DOWN              0
#define FTS_TOUCH_UP                1
#define FTS_TOUCH_CONTACT           2
#define EVENT_DOWN(flag)            ((flag == FTS_TOUCH_DOWN) || (flag == FTS_TOUCH_CONTACT))
#define EVENT_UP(flag)              (flag == FTS_TOUCH_UP)

#define FOCALTECH_I2C_ADDR          0x38

struct focaltech_ts_info {
    unsigned int max_x;
    unsigned int max_y;
    unsigned int width;
    unsigned int height;
};

struct ts_event {
    int x;      /*x coordinate */
    int y;      /*y coordinate */
    int p;      /* pressure */
    int flag;   /* touch event flag: 0 -- down; 1-- up; 2 -- contact */
    int id;     /*touch ID */
};

struct focaltech_ts_info focaltech_data;

/******************************************************************************/

#define FTS_RETRY_COUNT             3
#define I2C_BUF_LENGTH              256

static int fts_read(uint8_t *cmd, uint32_t cmdlen, uint8_t *data, uint32_t datalen)
{
    TEE_Result ret;
    deviceInfo_t *i2cDev = &board.i2c;
    int try_count = FTS_RETRY_COUNT;

    if (!data || !cmd || (datalen > I2C_BUF_LENGTH) || (cmdlen > I2C_BUF_LENGTH)) {
        return -EINVAL;
    }

    do {
        ret = i2c_read_write(i2cDev, i2cDev->bus.slave_address, cmd,
                             cmdlen, data, datalen);
        if (ret == TEE_SUCCESS) {
            return 0;
        }
    } while(--try_count);
    return -EIO;
}

static int fts_write(uint8_t *cmd, uint32_t cmdlen)
{
    TEE_Result ret;
    deviceInfo_t *i2cDev = &board.i2c;
    int try_count = FTS_RETRY_COUNT;

    if (!cmd || !cmdlen) {
        return -EINVAL;
    }

    do {
        ret = i2c_send(i2cDev, i2cDev->bus.slave_address, cmd, cmdlen);
        if (ret == TEE_SUCCESS) {
            return 0;
        }
    } while(--try_count);
    return -EIO;
}

/******************************************************************************/

static int fts_input_report_key(struct ts_event *events, int index)
{
    int x = events[index].x;
    int y = events[index].y;

    dbgPrintf("Key %d(%d,%d) action :%d!\n", index, x, y, events[index].flag);

    if (EVENT_DOWN(events[index].flag)) {
        add_touch_event(index, x, y, tui_ts_press);
        return 0;
    } else if (EVENT_UP(events[index].flag)) {
        add_touch_event(index, x, y, tui_ts_release);
        return 0;
    } else {
        errPrintf("invalid touch event!\n");
    }

    return -EINVAL;
}

static int fts_input_report_b(struct ts_event *events, unsigned int touch_point)
{
    unsigned int i = 0;

    for (i = 0; i < touch_point; i++) {
        if (fts_input_report_key(events, i) == 0) {
            continue;
        }
    }

    return 0;
}

#ifdef DR_DBGLOG
static void fts_dump_touch_buffer(uint8_t *data, int datalen)
{
    int i = 0;
    dbgPrintf("point buffer(%d): ", datalen);

    for (i = 0; i < datalen; i++) {
        dbgPrintf("%02X,", data[i]);
    }
    dbgPrintf("\n\n");
}
#endif

static int fts_read_touchdata(uint8_t *buf)
{
    int ret = 0;
    int pnt_buf_size = FTS_TOUCH_DATA_LEN + FTS_GESTURE_DATA_LEN;

    memset(buf, 0xFF, pnt_buf_size);
    buf[0] = 0x01;

    ret = fts_read(buf, 1, buf + 1, pnt_buf_size - 1);
    if (ret < 0) {
        errPrintf("touch data(%x) abnormal,ret:%d\n", buf[1], ret);
        return -EIO;
    }
#ifdef DR_DBGLOG
    fts_dump_touch_buffer(buf, pnt_buf_size);
#endif
    return 0;
}

/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
static int fts_irq_process(void)
{
    static uint8_t buf[FTS_TOUCH_DATA_LEN + FTS_GESTURE_DATA_LEN + 1];

    int ret = 0;
    int i = 0;
    uint8_t pointid = 0;
    int base = 0;
    int point_num;
    struct ts_event events[FTS_MAX_POINTS_SUPPORT];
    unsigned int touch_point = 0;

    ret = fts_read_touchdata(buf);
    if (ret) {
        errPrintf("failed to read touch data\n");
        return ret;
    }

    point_num = buf[FTS_TOUCH_POINT_NUM] & 0x0F;
    dbgPrintf("<< %s, 0x%x\n", __func__, point_num);

    if ((point_num == 0x0F) && (buf[2] == 0xFF) && (buf[3] == 0xFF)) {
        errPrintf("touch buff is 0xff, need recovery state\n");
        release_all_fingers();
        return -EIO;
    }

    if (point_num > FTS_MAX_POINTS_SUPPORT) {
        errPrintf("invalid point_num(%d)\n", point_num);
        return -EIO;
    }

    for (i = 0; i < FTS_MAX_POINTS_SUPPORT; i++) {
        base = FTS_ONE_TCH_LEN * i;
        pointid = (buf[FTS_TOUCH_ID_POS + base]) >> 4;
        if (pointid >= FTS_MAX_ID) {
            break;
        } else if (pointid >= FTS_MAX_POINTS_SUPPORT) {
            errPrintf("ID(%d) beyond max_touch_number\n", pointid);
            return -EINVAL;
        }

        touch_point++;
        events[i].x = ((buf[FTS_TOUCH_X_H_POS + base] & 0x0F) << 8) +
            (buf[FTS_TOUCH_X_L_POS + base] & 0xFF);
        events[i].y = ((buf[FTS_TOUCH_Y_H_POS + base] & 0x0F) << 8) +
            (buf[FTS_TOUCH_Y_L_POS + base] & 0xFF);
        events[i].flag = buf[FTS_TOUCH_EVENT_POS + base] >> 6;
        events[i].id = buf[FTS_TOUCH_ID_POS + base] >> 4;
        if (EVENT_DOWN(events[i].flag) && (point_num == 0)) {
            errPrintf("abnormal touch data from fw\n");
            return -EIO;
        }
    }

    if (touch_point == 0) {
        errPrintf("no touch point information(%02x)\n", buf[2]);
        return -EIO;
    }

    return fts_input_report_b(events, touch_point);
}

static int fts_read_bootid(void)
{
    int ret = 0;
    uint8_t chip_id[2] = {};
    uint8_t id_cmd[4] = {};
    uint32_t id_cmd_len = 0;

    id_cmd[0] = FTS_CMD_START1;
    id_cmd[1] = FTS_CMD_START2;
    ret = fts_write(id_cmd, 2);
    if (ret < 0) {
        errPrintf("start cmd write fail");
        return ret;
    }

    TEE_Wait(FTS_CMD_START_DELAY);
    id_cmd[0] = FTS_CMD_READ_ID;
    chip_id[0] = chip_id[1] = 0x00;

    id_cmd_len = FTS_CMD_READ_ID_LEN_INCELL;

    ret = fts_read(id_cmd, id_cmd_len, chip_id, 2);
    if ((ret < 0) || (chip_id[0] == 0x00)) {
        errPrintf("read boot id fail,read:0x%02x%02x\n", chip_id[0], chip_id[1]);
        return -EIO;
    }

    /* fts_get_chip_types: verify id:0x5452 */
    infoPrintf("read boot id:0x%02x%02x\n", chip_id[0], chip_id[1]);
    return 0;
}

/**
 * Initialize Focaltech device driver
 * @param[in] fts_dev nvy device pointer
 * @return error status
 */
static int fts_driver_init(struct focaltech_ts_info *fts_dev)
{
    int res = 0;
    dbgPrintf("%s, resolution info x=%d y=%d\n",
              __func__, fts_dev->width, fts_dev->height);

    /* check spi comunication */
    res = fts_read_bootid();
    if (res < 0) {
        errPrintf("fts_read_bootid failed.(%x)\n", res);
        return -1;
    }

    fts_dev->max_x = SENSOR_MAX_X_DEFAULT;
    fts_dev->max_y = SENSOR_MAX_Y_DEFAULT;
    init_touch_queue();

    return 0;
}

/**
 * Uninitialize Focaltech device driver
 * @return error status
 */
static int fts_driver_release(void)
{
    return 0;
}

TEE_Result fts_get_info(drTouchInfo_ptr touchSize)
{
    touchSize->width = focaltech_data.max_x;
    touchSize->height = focaltech_data.max_y;
    return TEE_SUCCESS;
}

TEE_Result fts_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;

    i2c_dev->bus.slave_address = FOCALTECH_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(&focaltech_data, 0, sizeof(focaltech_data));

        focaltech_data.width = width;
        focaltech_data.height = height;

        /* Initialize Focaltech touchscreen */
        ret = fts_driver_init(&focaltech_data);
        if (ret < 0) {
            errPrintf("fts_driver_init() : ret = %d", ret);
            retHal = TEE_ERROR_BUSY;
            goto err_init;
        }
        touch_dev->state |= DEV_SFR_CONFIGURED;
    }

    sec_release();
    dbgPrintf("<< %s\n", __func__);
    return retHal;

err_init:
    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 fts_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;
    }

    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 = fts_driver_release();
        if (ret != TEE_SUCCESS) {
            errPrintf("fts_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 fts_process(void)
{
    int ret = 0;
    deviceInfo_t *touch_dev = &board.touch;
    dbgPrintf("<< %s\n", __func__);

    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) {
        ret = touch_gpio_register_int(gpio_dev, fts_irq_process);
        if (ret != TEE_SUCCESS) {
            errPrintf("touch_gpio_register_int() : ret = %d\n", ret);
            return TEE_ERROR_GENERIC;
        }
        touch_dev->state = DEV_CONFIGURED;
        dbgPrintf(" %s, nvt_irq_process int handler registered!\n", __func__);
    }
    gpio_wait_touch_irq();
#else /* USE_TOUCH_INTERRUPT */
    ret = fts_irq_process();
#endif /* USE_TOUCH_INTERRUPT */
    if (ret != 0) {
        return TEE_ERROR_GENERIC;
    }
    return TEE_SUCCESS;
}
