/*
 * ist40xx_driver.c
 *
 * Copyright (C) 2019, Samsung Electronics Co., Ltd.
 *
 * Imagis touchscreen routines
 */

#include <errno.h>
#include <driver/interrupt/interrupt.h>
#include <string.h>
#include <tee_internal_api.h>
#include <stdlib.h>

#include "board.h"
#include "bsp_common.h"
#include "dbg.h"
#include "device.h"
#include "i2c.h"
#include "ist40xx_driver.h"
#include "secmap.h"
#include "touch_gpio.h"

#define EVENT_BUFF_SIZE           8
#define MAX_MULTI_TOUCH_SENSING   10
#define IST40XX_TOUCH_FRAME_CNT   2
#define PARSE_TOUCH_CNT(n)        ((n >> 12) & 0xF)

#define MAX_WAIT_CNT              10
#define ISR_WAIT_DELAY            20
#define IST_RETRY_COUNT           3

struct ist40xx_ts_info ist_data;
static atomic_t isr_running;

/******************************************************************************/

static int ist_i2c_write(uint8_t *addr, uint16_t acnt, uint8_t *buff, uint16_t bcnt)
{
    TEE_Result ret;
    deviceInfo_t *i2cDev = &board.i2c;
    static uint8_t local_buff[EVENT_BUFF_SIZE];
    int try_count = IST_RETRY_COUNT;
    int count = acnt + bcnt;

    if ((!addr && acnt) || (!buff && bcnt) || (size_t)count > sizeof(local_buff)) {
        errPrintf("invalid arg, count=%d\n", count);
        return -EINVAL;
    }

    memset(local_buff, 0x0, EVENT_BUFF_SIZE);
    TEE_MemMove(local_buff, addr, acnt);
    if (bcnt) {
        TEE_MemMove(local_buff + acnt, buff, bcnt);
    }

    do {
        ret = i2c_send(i2cDev, i2cDev->bus.slave_address, local_buff, WRITE_CMD_MSG_LEN);
        if (ret == TEE_SUCCESS) {
#if defined(DR_DBGLOG)
            dbgPrintf("ist4050: I2C: %s\n", __func__);
            dbgPrintf("ist4050: I2C: W: ");
            for (int i = 0; i < count; i++) {
                dbgPrintf("%02X ", local_buff[i]);
            }
            dbgPrintf("\n");
#endif
            return 0;
        }
    } while (--try_count);

    errPrintf("failed, ret=%d\n", ret);
    return -EIO;
}

static int ist_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 = IST_RETRY_COUNT;

    if (!wr_buff || !wr_size || !rd_buff || !rd_size) {
        errPrintf("null buffer or wrong size\n");
        return -EINVAL;
    }

    do {
        ret = i2c_read_write(i2cDev,
                             i2cDev->bus.slave_address,
                             wr_buff,
                             wr_size,
                             rd_buff,
                             rd_size);
        if (ret == TEE_SUCCESS) {
            return 0;
        }
    } while (--try_count);

    errPrintf("failed ret = %d\n", ret);
    return -EIO;
}

int ist40xx_read_reg(uint32_t addr, uint32_t *buf)
{
    int ret;
    uint8_t reg_buf[IST40XX_ADDR_LEN] = {};
    uint8_t data_buf[IST40XX_DATA_LEN] = {};

    reg_buf[0] = (uint8_t)((addr >> 24) & 0xFF);
    reg_buf[1] = (uint8_t)((addr >> 16) & 0xFF);
    reg_buf[2] = (uint8_t)((addr >> 8) & 0xFF);
    reg_buf[3] = (uint8_t)(addr & 0xFF);

    ret = ist_i2c_rw(reg_buf, IST40XX_ADDR_LEN, data_buf, IST40XX_DATA_LEN);
#if defined(DR_DBGLOG)
    int i;
    dbgPrintf("ist4050: I2C: %s\n", __func__);
    dbgPrintf("ist4050: I2C: W: ");
    for (i = 0; i < IST40XX_ADDR_LEN; i++) {
        dbgPrintf("%02X", reg_buf[i]);
    }
    dbgPrintf("\n");
    dbgPrintf("ist4050: I2C: R: ");
    for (i = 0; i < IST40XX_DATA_LEN; i++) {
        dbgPrintf("%02X", data_buf[i]);
    }
    dbgPrintf("\n");
#endif
    *buf = (data_buf[0] & 0xFF) << 24;
    *buf += (data_buf[1] & 0xFF) << 16;
    *buf += (data_buf[2] & 0xFF) << 8;
    *buf += (data_buf[3] & 0xFF);

    return ret;
}

int ist40xx_burst_read(uint32_t addr, uint32_t *buf32, uint32_t len)
{
    int ret = 0;
    register unsigned int i;
    uint8_t reg_buf[IST40XX_ADDR_LEN] = {};
    uint8_t data_buf[IST40XX_DATA_LEN * MAX_MULTI_TOUCH_SENSING * IST40XX_TOUCH_FRAME_CNT] = {};
    unsigned int data_len = len * IST40XX_DATA_LEN;

    if ((size_t)data_len > sizeof(data_buf)) {
        errPrintf("invalid arg, data_len = %d\n", data_len);
        return -EINVAL;
    }

	/* enable burst read */
	addr = IST40XX_BA_ADDR(addr);

    reg_buf[0] = (uint8_t)((addr >> 24) & 0xFF);
    reg_buf[1] = (uint8_t)((addr >> 16) & 0xFF);
    reg_buf[2] = (uint8_t)((addr >> 8) & 0xFF);
    reg_buf[3] = (uint8_t)(addr & 0xFF);

    ret = ist_i2c_rw(reg_buf, IST40XX_ADDR_LEN, data_buf, data_len);
#if defined(DR_DBGLOG)
    dbgPrintf("ist4050: I2C: %s\n", __func__);
    dbgPrintf("ist4050: I2C: W: ");
    for (i = 0; i < IST40XX_ADDR_LEN; i++) {
        dbgPrintf("%02X", reg_buf[i]);
    }
    dbgPrintf("\n");

    dbgPrintf("ist4050: I2C: R: ");
    for (i = 0; i < data_len; i++) {
        dbgPrintf("%02X", data_buf[i]);
    }
    dbgPrintf("\n");
#endif
    /* Align the data */
    for (i = 0; i < data_len; i = i + sizeof(uint32_t)) {
        *(buf32 + (i / sizeof(uint32_t))) = (data_buf[i + 0] & 0xFF) << 24;
        *(buf32 + (i / sizeof(uint32_t))) += (data_buf[i + 1] & 0xFF) << 16;
        *(buf32 + (i / sizeof(uint32_t))) += (data_buf[i + 2] & 0xFF) << 8;
        *(buf32 + (i / sizeof(uint32_t))) += (data_buf[i + 3] & 0xFF);
        dbgPrintf("buf32 = 0x%08x\n", *(buf32 + (i / sizeof(uint32_t))));
    }
    dbgPrintf("%s <<\n", __func__);
    return ret;
}

int ist40xx_write_cmd(uint32_t cmd, uint32_t val)
{
    int ret;
    uint8_t cmd_buf[IST40XX_ADDR_LEN];
    uint8_t msg_buf[IST40XX_DATA_LEN];

    cmd_buf[0] = (uint8_t)((cmd >> 24) & 0xFF);
    cmd_buf[1] = (uint8_t)((cmd >> 16) & 0xFF);
    cmd_buf[2] = (uint8_t)((cmd >> 8) & 0xFF);
    cmd_buf[3] = (uint8_t)(cmd & 0xFF);

    msg_buf[0] = (uint8_t)((val >> 24) & 0xFF);
    msg_buf[1] = (uint8_t)((val >> 16) & 0xFF);
    msg_buf[2] = (uint8_t)((val >> 8) & 0xFF);
    msg_buf[3] = (uint8_t)(val & 0xFF);

    ret = ist_i2c_write(cmd_buf, IST40XX_ADDR_LEN, msg_buf, IST40XX_DATA_LEN);
    return ret;
}


int ist40xx_cmd_hold(int enable)
{
    int ret;
    ret = ist40xx_write_cmd(IST40XX_HIB_CMD,
            (HCOM_FW_HOLD << 16) | (enable & 0xFFFF));

    return ret;
}

int ist40xx_read_cmd(uint32_t cmd, uint32_t *buf)
{
    int ret;

    ret = ist40xx_cmd_hold(IST40XX_ENABLE);
    if (ret)
        return ret;

    ret = ist40xx_read_reg(cmd, buf);
    if (ret)
        return ret;

    ret = ist40xx_cmd_hold(IST40XX_DISABLE);
    return ret;
}

int ist40xx_start_scan(void)
{
    int ret;

    ret = ist40xx_write_cmd(IST40XX_HIB_CMD,
            (HCOM_SET_SMODE << 16) | (1 & 0xFFFF));
    if (ret)
        return ret;

    ret = ist40xx_write_cmd(IST40XX_HIB_CMD,
            (HCOM_SET_JIG_MODE << 16) | (HCOM_SET_JIG_MODE & 0xFFFF));
    if (ret)
        return ret;

    ret = ist40xx_write_cmd(IST40XX_HIB_CMD,
            (HCOM_SLEEP_MODE_EN << 16) | (0x0000));
    if (ret)
        return ret;

    ret = ist40xx_cmd_hold(IST40XX_DISABLE);
    if (ret)
        return ret;

    ret = ist40xx_write_cmd(IST40XX_HIB_CMD,
            (HCOM_FW_START << 16) | (0x0000));
    return ret;
}

int ist40xx_fod_enable(int enable)
{
    int ret;
    ret = ist40xx_write_cmd(IST40XX_HIB_CMD,
            (HCOM_SET_FOD_ENABLE << 16) | (enable & 0xFFFF));

    return ret;
}

int ist40xx_special_cmd(void)
{
    int ret;
    gesture_msg g_msg;
    dbgPrintf("%s size : %ld\n", __func__, sizeof(g_msg.full));
    ret = ist40xx_burst_read(IST40XX_HIB_GESTURE_MSG, g_msg.full, sizeof(g_msg.full) / IST40XX_DATA_LEN);
    if (ret < 0) {
        errPrintf("IST40XX_HIB_GESTURE_MSG ret = %d\n", ret);
    }
    mdelay(10);

    return ret;
}

static int ist_coordinate(uint32_t *data, uint32_t fcnt)
{
    int ret;
    uint32_t x, y, status, offset;
    uint8_t mode = TOUCH_STATUS_NORMAL_MODE;
    struct stTouchCoord_t *tsp;

    ret = ist40xx_read_reg(IST40XX_TOUCH_STATUS, &status);
    if (ret < 0) {
        errPrintf("%s: i2c touch_status failed, ret = %d\n", __func__, ret);
        return ret;
    }
    dbgPrintf("touch screen touch_status: 0x%08x\n", status);
    if ((status & TOUCH_STATUS_MASK) == TOUCH_STATUS_MAGIC) {
        if (GET_NOISE_MODE(status))
            mode |= TOUCH_STATUS_NOISE_MODE;
        if (GET_WET_MODE(status))
            mode |= TOUCH_STATUS_WET_MODE;
    }

    for(uint32_t i = 0; i < fcnt; i++) {
        offset = i * IST40XX_TOUCH_FRAME_CNT;
        if (offset >= MAX_MULTI_TOUCH_SENSING * IST40XX_TOUCH_FRAME_CNT) {
            errPrintf("invalid offset, offset=%d\n", offset);
            return -EINVAL;
        }
        tsp = malloc(sizeof(struct stTouchCoord_t));
        if (!tsp) {
            return -ENOMEM;
        }
        dbgPrintf("allocated: 0x%lx, 0x%x, %d/%d\n", sizeof(struct stTouchCoord_t), data[offset], i+1, fcnt);
        memset(tsp, 0, sizeof(struct stTouchCoord_t));
        memcpy(tsp, &data[offset], sizeof(struct stTouchCoord_t));
        y = tsp->X;
        x = tsp->Y;
        status = tsp->Status;
        if (x > ist_data.width || y > ist_data.height) {
            errPrintf("invalid coord finger, x=%d, y=%d\n", x, y);
            free(tsp);
            return -EINVAL;
        }
        if (status == TOUCH_PRESS) {
            if (get_finger_state(i) != tui_ts_press) {
                dbgPrintf("%s: Pressed, x : %d, y : %d\n", __func__, x, y);
                add_touch_event(i, x, y, tui_ts_press);
            }
        }
        else if (status == TOUCH_RELEASE) {
            if (get_finger_state(i) != tui_ts_release) {
                dbgPrintf("%s: Released, x : %d, y : %d\n", __func__, x, y);
                add_touch_event(i, x, y, tui_ts_release);
            }
        }
        else {
            if (get_finger_state(i) != tui_ts_update) {
                dbgPrintf("%s: Updated, x : %d, y : %d\n", __func__, x, y);
                add_touch_event(i, x, y, tui_ts_update);
            }
        }
        free(tsp);
    }
    return TEE_SUCCESS;
}

/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
int ist_irq_process(void)
{
    int ret = 0;
    int read_cnt;
    uint32_t touch_data;
    uint32_t msg[MAX_MULTI_TOUCH_SENSING * IST40XX_TOUCH_FRAME_CNT] = {0,};
    dbgPrintf(">> %s\n", __func__);

    if (atomic_read(isr_running) == -1) {
        dbgPrintf("%s driver was released or irq is running, isr is not allowed\n", __func__);
        return 0;
    }
    atomic_init(isr_running, 1);

    if (get_touch_queue_wsize() < MAX_MULTI_TOUCH_SENSING) {
        ret = -1;
        goto out;
    }

    ret = ist40xx_read_reg(IST40XX_TOUCH_INFO, &touch_data);
    if (ret < 0) {
        errPrintf("%s: i2c 1st_data failed, ret = %d\n", __func__, ret);
        ret = -1;
        goto out;
    }
    dbgPrintf("touch screen 1st_data: 0x%08x\n", touch_data);
    if (touch_data == 0 || touch_data == 0xFFFFFFFF) { /* Unknown CMD */
        ret = -1;
        goto out;
    }

    read_cnt = PARSE_TOUCH_CNT(touch_data);
    dbgPrintf("%s, Read Cnt:%d\n", __func__, read_cnt);
    if (read_cnt == 0) {
        errPrintf("Error : Touch Count is %d.\n", read_cnt);
        goto out;
    }
    else if (read_cnt >= MAX_MULTI_TOUCH_SENSING) {
        dbgPrintf("FOD : Touch Count is %d.\n", read_cnt);
        ist40xx_special_cmd();
        goto out;
    }

    ret = ist40xx_burst_read(IST40XX_COORD_SCREEN, msg, read_cnt * IST40XX_TOUCH_FRAME_CNT);
    if (ret < 0) {
        errPrintf("%s: i2c read touch_data failed, ret = %d\n", __func__, ret);
        ret = -1;
        goto out;
    }
    dbgPrintf("Touch Test Result : \n");
    dbgPrintf("Number Of Touch Info : %d\n", read_cnt);

    /* Parse Screen Touch Info, enque touch event */
    ret = ist_coordinate(msg, read_cnt);
    if (ret < 0)
        errPrintf("%s: failed, ret = %d\n", __func__, ret);

out:
    atomic_init(isr_running, 0);
    dbgPrintf("<< %s\n", __func__);

    return ret;
}

/**
 * Initialize imagis tsp device driver
 * @param[in] ist_dev ist40xx device pointer
 * @return error status
 */
int ist_driver_init(struct ist40xx_ts_info *ist_dev)
{
    int ret = 0;
    int retry = MAX_WAIT_CNT;
    uint32_t chip_id = 0;

    do {
        ret = ist40xx_read_cmd(HCOM_GET_CHIP_ID, &chip_id);
        if (ret < 0) {
            errPrintf("failed to read CHIP_ID info ret = %d\n", ret);
            return ret;
        }

        dbgPrintf("TOUCH %s, chip id = %X\n", __func__, chip_id);
        if (chip_id == IST40XX_CHIP_ID) {
            ist_dev->check_tsp = true;
            infoPrintf("TOUCH %s, check chipid success!!\n", __func__);
            break;
        }
        else
            TEE_Wait(ISR_WAIT_DELAY);
    } while (retry--);

    /* Set X,Y Resolution from IC information. */
    ist_dev->max_x = SENSOR_MAX_X_DEFAULT;
    ist_dev->max_y = SENSOR_MAX_Y_DEFAULT;

    init_touch_queue();

//  ist40xx_fod_enable(0);

    ret = ist40xx_start_scan();
    if (ret < 0) {
        errPrintf("failed ist40xx_start_scan ret = %d\n", ret);
        return ret;
    }

    atomic_init(isr_running, 0);
    dbgPrintf("%s: success!\n", __func__);
    return ret;
}

/**
 * release imagis tsp device driver
 * @return error status
 */
int ist_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;
}
