/*
 * zt7650_driver.c
 *
 * Copyright (C) 2020, Samsung Electronics Co., Ltd.
 *
 * zt7650 (zinitix) touchscreen routines
 */

#include <errno.h>
#include <interrupt.h>
#include <string.h>
#include <tee_internal_api.h>

#include "board.h"
#include "bsp_common.h"
#include "dbg.h"
#include "device.h"
#include "i2c.h"
#include "secmap.h"
#include "touch_gpio.h"
#include "zinitix_driver.h"

#define ZT_RETRY_COUNT               3
#define MAX_WAIT_CNT                 10
#define ISR_WAIT_DELAY               20
#define MAX_SUPPORTED_FINGER_NUM     10

/* zt7650 tsp register commands */
#define ZT_SWRESET_CMD               0x0000
#define ZT_CLEAR_INT_STATUS_CMD      0x0003
#define ZT_DEBUG_REG                 0x0115
#define ZT_TOUCH_MODE                0x0010
#define ZT_CHIP_REVISION             0x0011
#define ZT_FIRMWARE_VERSION          0x0012
#define ZT_MINOR_FW_VERSION          0x0121
#define ZT_VENDOR_ID                 0x001C
#define ZT_HW_ID                     0x0014
#define ZT_X_RESOLUTION              0x00C0
#define ZT_Y_RESOLUTION              0x00C1
#define ZT_CALL_AOT_REG              0x00D3
#define ZT_STATUS_REG                0x0080
#define ZT_POINT_STATUS_REG          0x0200
#define ZT_POINT_STATUS_REG1         0x0201
#define ZT_ICON_STATUS_REG           0x00AA

#define REG_FOD_AREA_STR_X           0x013B
#define REG_FOD_AREA_STR_Y           0x013C
#define REG_FOD_AREA_END_X           0x013E
#define REG_FOD_AREA_END_Y           0x013F
#define REG_FOD_MODE_SET             0x0142
#define REG_FOD_MODE_VI_DATA         0x0143
#define REG_FOD_MODE_VI_DATA_LEN     0x0144
#define REG_FOD_MODE_VI_DATA_CH      0x0145

#define FOD_VI_DATA_LENGTH           17

union coord_byte00_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t eid : 2;
        uint8_t tid : 4;
        uint8_t touch_status : 2;
    } value;
};

union coord_byte01_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t x_coord_h : 8;
    } value;
};

union coord_byte02_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t y_coord_h : 8;
    } value;
};

union coord_byte03_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t y_coord_l : 4;
        uint8_t x_coord_l : 4;
    } value;
};

union coord_byte04_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t major : 8;
    } value;
};

union coord_byte05_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t minor : 8;
    } value;
};

union coord_byte06_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t z_value : 6;
        uint8_t touch_type23 : 2;
    } value;
};

union coord_byte07_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t left_event : 4;
        uint8_t max_energy : 2;
        uint8_t touch_type01 : 2;
    } value;
};

union coord_byte08_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t noise_level : 8;
    } value;
};

union coord_byte09_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t max_sensitivity : 8;
    } value;
};

union coord_byte10_t
{
    uint8_t value_uint8_tbit;
    struct {
        uint8_t hover_id_num  : 4;
        uint8_t location_area : 4;
    } value;
};

struct point_info {
    union coord_byte00_t byte00;
    union coord_byte01_t byte01;
    union coord_byte02_t byte02;
    union coord_byte03_t byte03;
    union coord_byte04_t byte04;
    union coord_byte05_t byte05;
    union coord_byte06_t byte06;
    union coord_byte07_t byte07;
    union coord_byte08_t byte08;
    union coord_byte09_t byte09;
    union coord_byte10_t byte10;
    uint8_t byte11;
    uint8_t byte12;
    uint8_t byte13;
    uint8_t byte14;
    uint8_t byte15;
};

struct ts_coordinate {
    uint8_t id;
    uint8_t ttype;
    uint8_t touch_status;
    uint16_t x;
    uint16_t y;
    uint8_t z;
    uint8_t hover_flag;
    uint8_t glove_flag;
    uint8_t touch_height;
    uint16_t mcount;
    uint8_t major;
    uint8_t minor;
    uint8_t noise;
    uint8_t max_sense;
    bool palm;
    int palm_count;
    uint8_t left_event;
};

struct capa_info {
    uint16_t vendor_id;
    uint16_t multi_fingers;
    uint16_t current_touch_mode;
};

struct zt7650_ts_info {
    unsigned int width;
    unsigned int height;
    struct capa_info cap_info;
    struct point_info touch_info[MAX_SUPPORTED_FINGER_NUM];
    struct ts_coordinate cur_coord[MAX_SUPPORTED_FINGER_NUM];
    uint8_t touched_finger_num;
};

struct zinitix_ts_info zinitix_data;
struct zt7650_ts_info zt7650_data;

static atomic_t isr_running;
/******************************************************************************/

static int zt_i2c_write_cmd(uint8_t *wr_buff, int wr_size)
{
    TEE_Result ret;
    deviceInfo_t *i2cDev = &board.i2c;
    int try_count = ZT_RETRY_COUNT;

    if (!wr_buff || !wr_size) {
        errPrintf("null buffer or wrong size\n");
        return -EINVAL;
    }

    do {
        ret = i2c_send(i2cDev, i2cDev->bus.slave_address, wr_buff, wr_size);
        if (ret == TEE_SUCCESS) {
            return 0;
        }
    } while(--try_count);

    return -EIO;
}

static int zt_i2c_rw(uint8_t *wr_buff, int wr_size, uint8_t *rd_buff, int rd_size)
{
    TEE_Result ret;
    deviceInfo_t *i2cDev = &board.i2c;
    int try_count = ZT_RETRY_COUNT;

    if (!wr_buff || !wr_size || !rd_buff || !rd_size) {
        errPrintf("null buffer or wrong size\n");
        return -EINVAL;
    }

    do {
        ret = i2c_send(i2cDev, i2cDev->bus.slave_address, wr_buff, wr_size);
        if (ret == TEE_SUCCESS) {
            ret = i2c_receive(i2cDev, i2cDev->bus.slave_address, rd_buff, rd_size);
            if (ret == TEE_SUCCESS) {
                return 0;
            }
        }
    } while (--try_count);

    errPrintf("failed ret = %d\n", ret);

    return -EIO;
}

static int ts_read_coord(struct zt7650_ts_info *info)
{
    int retry_cnt;
    uint16_t cmd;
    dbgPrintf(">> %s\n", __func__);

    memset(&info->touch_info[0], 0x0, sizeof(struct point_info) * MAX_SUPPORTED_FINGER_NUM);

    retry_cnt = 0;
    cmd = ZT_POINT_STATUS_REG;
    while(retry_cnt < MAX_SUPPORTED_FINGER_NUM) {
        if (zt_i2c_rw((uint8_t *)&cmd, 2, (uint8_t *)(&info->touch_info[0]), sizeof(struct point_info)) < 0) {
            errPrintf("Failed to read point info, retry_cnt = %d\n", ++retry_cnt);
            continue;
        } else
            break;
    }

    if (retry_cnt >= MAX_SUPPORTED_FINGER_NUM)
        return -1;

    if (info->touch_info[0].byte00.value.eid == COORDINATE_EVENT) {
        info->touched_finger_num = info->touch_info[0].byte07.value.left_event;
        dbgPrintf("<< %s, COORDINATE_EVENT finger %d\n", __func__, info->touched_finger_num);
        if (info->touched_finger_num > 0) {
            retry_cnt = 0;
            cmd = ZT_POINT_STATUS_REG1;
            while(retry_cnt < MAX_SUPPORTED_FINGER_NUM) {
                if (zt_i2c_rw((uint8_t *)&cmd, 2, (uint8_t *)(&info->touch_info[1]),
                            (info->touched_finger_num)*sizeof(struct point_info)) < 0) {
                    errPrintf("Failed to read touched point info, retry_cnt = %d\n", ++retry_cnt);
                    continue;
                } else
                    break;
            }
            if (retry_cnt >= MAX_SUPPORTED_FINGER_NUM)
                return -1;
        }
    }
    dbgPrintf("<< %s\n", __func__);
    return 0;
}

/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
int zt_irq_process(void)
{
    int ret = 0;
    unsigned int i;
    uint8_t tid = 0;
    uint8_t ttype, tstatus;
    uint16_t cmd;
    uint16_t x, y;

    dbgPrintf(">> %s\n", __func__);
    if (atomic_read(isr_running) == -1) {
        return 0;
    }
    atomic_init(isr_running, 1);

    if (get_touch_queue_wsize() < zt7650_data.cap_info.multi_fingers) {
        ret = -1;
        goto out;
    }

    if (ts_read_coord(&zt7650_data)) {
        errPrintf("failed to read info coord\n");
        ret = -1;
        goto out;
    }

    /* invalid : maybe periodical repeated int. */
    if (zt7650_data.touch_info[0].byte00.value.eid == CUSTOM_EVENT) {
        errPrintf("invalid status (0x%x)\n", zt7650_data.touch_info[0].byte00.value.eid);
        ret = -1;
        goto out;
    }

    for (i = 0; i < zt7650_data.cap_info.multi_fingers; i++) {
        memset(&zt7650_data.cur_coord[i], 0, sizeof(struct ts_coordinate));
    }

    for (i = 0; i < zt7650_data.cap_info.multi_fingers; i++) {
        ttype = (zt7650_data.touch_info[i].byte06.value.touch_type23 << 2)
                                           | (zt7650_data.touch_info[i].byte07.value.touch_type01);
        tstatus = zt7650_data.touch_info[i].byte00.value.touch_status;

        if (tstatus == FINGER_NONE && ttype != TOUCH_PROXIMITY) 
            continue;

        tid = zt7650_data.touch_info[i].byte00.value.tid;

        zt7650_data.cur_coord[tid].id = tid;
        zt7650_data.cur_coord[tid].touch_status = tstatus;
        zt7650_data.cur_coord[tid].ttype = ttype;
        zt7650_data.cur_coord[tid].x = (zt7650_data.touch_info[i].byte01.value.x_coord_h << 4)
                                               | (zt7650_data.touch_info[i].byte03.value.x_coord_l);
        zt7650_data.cur_coord[tid].y = (zt7650_data.touch_info[i].byte02.value.y_coord_h << 4)
                                               | (zt7650_data.touch_info[i].byte03.value.y_coord_l);
    }

    for (i = 0; i < zt7650_data.cap_info.multi_fingers; i++) {
        if (zt7650_data.cur_coord[i].touch_status == FINGER_NONE)
            continue;

        x = zt7650_data.cur_coord[i].x;
        y = zt7650_data.cur_coord[i].y;

        if (x > zt7650_data.width || y > zt7650_data.height) {
            errPrintf("Invalid coord %d : x=%d, y=%d\n", i, x, y);
            continue;
        }
        dbgPrintf("coord finger %d, x=%d, y=%d, event=%d\n", i, x, y, zt7650_data.cur_coord[i].touch_status);

        if (zt7650_data.cur_coord[i].touch_status == FINGER_PRESS) {
            dbgPrintf("press finger %d, x=%d, y=%d\n", i, x, y);
            add_touch_event(i, x, y, tui_ts_press);
        } else if (zt7650_data.cur_coord[i].touch_status == FINGER_RELEASE) {
            dbgPrintf("release finger %d, x = %d , y = %d\n", i, x, y);
            add_touch_event(i, x, y, tui_ts_release);
        } else if (zt7650_data.cur_coord[i].touch_status == FINGER_MOVE) {
            dbgPrintf("update finger %d, x = %d , y = %d\n", i, x, y);
            add_touch_event(i, x, y, tui_ts_update);
        }
    }

out:
    cmd = ZT_CLEAR_INT_STATUS_CMD;
    if (zt_i2c_write_cmd((uint8_t *)&cmd, 2)) {
        errPrintf("failed to clear status\n");
    }

    atomic_init(isr_running, 0);
    dbgPrintf("<< %s\n", __func__);
    return ret;
}

/**
 * Initialize ZT device driver
 * @param[in] zt_dev zt device pointer
 * @return error status
 */
int zt_driver_init(struct zinitix_ts_info *zt_dev)
{
    int ret;
    uint16_t cmd;

    TEE_MemFill(&zt7650_data, 0, sizeof(zt7650_data));

    zt7650_data.cap_info.multi_fingers = MAX_SUPPORTED_FINGER_NUM;
    zt7650_data.width = zt_dev->width;
    zt7650_data.height = zt_dev->height;

    cmd = ZT_VENDOR_ID;
    ret = zt_i2c_rw((uint8_t *)&cmd, 2, (uint8_t *)&zt7650_data.cap_info.vendor_id, 2);
    if (ret < 0) {
        errPrintf("reading vendoir id fail %d\n", ret);
        return ret;
    }

    dbgPrintf("%s 0x%x\n", __func__, zt7650_data.cap_info.vendor_id);
    dbgPrintf("%s res X = %d\n", __func__, zt7650_data.width);
    dbgPrintf("%s res Y = %d\n", __func__, zt7650_data.height);

    init_touch_queue();

    atomic_init(isr_running, 0);

    return ret;
}

/**
 * Uninitialize zinitix device driver
 * @return error status
 */
int zt_driver_release(void)
{
    int count = 0;

    while ((atomic_read(isr_running) == 1) && (count < MAX_WAIT_CNT)) {
        TEE_Wait(ISR_WAIT_DELAY);
        count++;
    }

    if (atomic_read(isr_running) == 1) {
        errPrintf("isr was not finished\n");
    }
    atomic_init(isr_running, -1);

    return 0;
}
