/*
 *
 * Copyright (C) 2012-2020, Samsung Electronics Co., Ltd.
 *
 * Ilitek touchscreen routines
 */

#include <errno.h>
#include <macros.h>
#include <string.h>
#include <stdlib.h>
#include <tee_internal_api.h>
#include <tee_spi.h>
#include "board.h"
#include "bsp_common.h"
#include "dbg.h"
#include "device.h"
#include "ilitek_driver.h"
#include "secmap.h"
#include "touch_gpio.h"

#define TPD_WIDTH                               2048
#define TPD_HEIGHT                              2048

#define P5_X_I2CUART_PACKET_ID                  0x7A
#define P5_X_INFO_HEADER_PACKET_ID              0xB7
#define P5_X_DEMO_AXIS_PACKET_ID                0x5B
#define P5_X_DEMO_PROXUMITY_ID                  0xBC
#define P5_X_INFO_HEADER_LENGTH                 3
#define P5_X_DEMO_MODE_PACKET_INFO_LEN          3
#define P5_X_DEMO_MODE_PACKET_LEN               43
#define P5_X_DEMO_MODE_AXIS_LEN                 50
#define P5_X_DEMO_MODE_STATE_INFO               16
#define TP_DATA_LEN                             (P5_X_DEMO_MODE_PACKET_INFO_LEN + \
        P5_X_DEMO_MODE_PACKET_LEN + P5_X_DEMO_MODE_AXIS_LEN + P5_X_DEMO_MODE_STATE_INFO)

#define ILITEK_PACKET_TOTAL_LEN                 115
#define ILI_TSP_HEADER_MASK(a)                  (a & 0x5B)
#define ILITEK_SPI_WRITE                        0x82
#define ILITEK_SPI_READ                         0x83
#define SPI_PORT                                1
#define WAIT_TIME                               20

static struct ilitek_touch_info ili_touch_info[MAX_TOUCH_COUNT];
struct ilitek_ts_info ilitek_data;
static unsigned char *tx_buf = NULL;
static unsigned char *rx_buf = NULL;
static TEES_SPIHandler handler;

bool press_id[MAX_TOUCH_COUNT];
int press_x[MAX_TOUCH_COUNT];
int press_y[MAX_TOUCH_COUNT];

static int allocate_rxtx_buffers(void)
{
    tx_buf = malloc(ILITEK_PACKET_TOTAL_LEN);
    rx_buf = malloc(ILITEK_PACKET_TOTAL_LEN);

    if (!tx_buf || !rx_buf) {
        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 ili_spi_read(uint8_t *buf, uint16_t len)
{
    int res = -1;
    int retries = 0;

    memset(tx_buf, 0, ILITEK_PACKET_TOTAL_LEN);
    memset(rx_buf, 0, ILITEK_PACKET_TOTAL_LEN);
    TEE_MemMove(tx_buf, buf, len);

    TEES_SPITransfer tx = {
        .bufAddr = tx_buf,
        .bufLen = ILITEK_PACKET_TOTAL_LEN,
        .transferredLen = 0
    };
    TEES_SPITransfer rx = {
        .bufAddr = rx_buf,
        .bufLen = ILITEK_PACKET_TOTAL_LEN,
        .transferredLen = 0
    };

    while (retries < 5) {
        /* read fw info */
        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 {
        memcpy((buf), (rx_buf + 3), (len));
    }

    return res;
}


static uint8_t ili_calc_packet_checksum(uint8_t *packet, int len)
{
    int i;
    int32_t sum = 0;

    for (i = 0; i < len; i++)
        sum += packet[i];

    return (uint8_t) ((-sum) & 0xFF);
}

static void ili_report_ap_mode(uint8_t *buf)
{
    int i = 0;
    uint32_t xop = 0, yop = 0;
    int x = 0, y = 0, id = 0;

    memset(ili_touch_info, 0x0, sizeof(struct ilitek_touch_info));
    ilitek_data.finger = 0;

    for (i = 0; i < MAX_TOUCH_COUNT; i++) {
        if ((buf[(4 * i) + 1] == 0xFF) && (buf[(4 * i) + 2] == 0xFF)
                && (buf[(4 * i) + 3] == 0xFF)) {
            ilitek_data.curt_touch[i] = 0;
            continue;
        }

        xop = (((buf[(4 * i) + 1] & 0xF0) << 4) | (buf[(4 * i) + 2]));
        yop = (((buf[(4 * i) + 1] & 0x0F) << 8) | (buf[(4 * i) + 3]));

        if (ilitek_data.trans_xy) {
            ili_touch_info[ilitek_data.finger].x = xop;
            ili_touch_info[ilitek_data.finger].y = yop;
        } else {
            ili_touch_info[ilitek_data.finger].x = xop * ilitek_data.panel_wid / TPD_WIDTH;
            ili_touch_info[ilitek_data.finger].y = yop * ilitek_data.panel_hei / TPD_HEIGHT;
        }

        ili_touch_info[ilitek_data.finger].id = i;

        dbgPrintf("%s original x = %d, y = %d\n", __func__, xop, yop);

        ilitek_data.finger++;
        ilitek_data.curt_touch[i] = 1;
    }

    dbgPrintf("%s figner number = %d, LastTouch = %d\n", __func__, ilitek_data.finger,
              ilitek_data.last_touch);

    if (ilitek_data.finger) {
        for (i = 0; i < ilitek_data.finger; i++) {
            id = ili_touch_info[i].id;
            x = ili_touch_info[i].x;
            y = ili_touch_info[i].y;

            dbgPrintf("<< %s, pressed x= %d, y= %d\n", __func__, x, y);
            press_id[id] = true;
            press_x[id] = x;
            press_y[id] = y;
            add_touch_event(id, x, y, tui_ts_press);
        }

        for (i = 0; i < MAX_TOUCH_COUNT; i++) {
            if (ilitek_data.curt_touch[i] == 0 && ilitek_data.prev_touch[i] == 1 && press_id[i]) {
                dbgPrintf("<< %s, released x= %d, y= %d\n", __func__, press_x[i], press_y[i]);
                add_touch_event(i, press_x[i], press_y[i], tui_ts_release);
                press_id[i] = false;
                press_x[i] = 0;
                press_y[i] = 0;
            }

            ilitek_data.prev_touch[i] = ilitek_data.curt_touch[i];
        }

        ilitek_data.last_touch = ilitek_data.finger;
    } else if (ilitek_data.last_touch) {
        for (i = 0; i < MAX_TOUCH_COUNT; i++) {
            if (ilitek_data.curt_touch[i] == 0 && ilitek_data.prev_touch[i] == 1 && press_id[i]) {
                dbgPrintf("<< %s, released x= %d, y= %d\n", __func__, press_x[i], press_y[i]);
                add_touch_event(i, press_x[i], press_y[i], tui_ts_release);
                press_id[i] = false;
                press_x[i] = 0;
                press_y[i] = 0;
            }
            ilitek_data.prev_touch[i] = ilitek_data.curt_touch[i];
        }

        ilitek_data.last_touch = 0;
    }
}

/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
int ili_irq_process(void)
{
    int res = 0;
    uint8_t point_data[TP_DATA_LEN] = {0, };
    uint8_t checksum = 0, pack_checksum = 0;
    int pid;
    uint8_t *trdata = point_data;

    if (get_touch_queue_wsize() < 1) {
        return -1;
    }

    memset(point_data, 0x0, TP_DATA_LEN);

    /* read tsp info */
    point_data[0] = ILITEK_SPI_READ;
    res = ili_spi_read(point_data, TP_DATA_LEN);
    if (res < 0) {
        errPrintf("ili_spi_read failed.(%x)\n", res);
        return -1;
    }
#ifdef DR_DBGLOG
    dbgPrintf("Dump Hexa\n");	
	print_hex(point_data, TP_DATA_LEN);
#endif
//    point_data[0] = ILI_TSP_HEADER_MASK(point_data[0]);
    checksum = ili_calc_packet_checksum(point_data, TP_DATA_LEN - 1);
    pack_checksum = point_data[TP_DATA_LEN - 1];

    pid = point_data[0];
    dbgPrintf("%s Packet ID = %x\n", __func__, pid);

    if (checksum != pack_checksum) {
        errPrintf("%s Checksum Error (0x%X)! Pack = 0x%X, len = %d\n",
                  __func__, checksum, pack_checksum, TP_DATA_LEN);
        return 0;
    }

    if (pid == P5_X_INFO_HEADER_PACKET_ID) {
        trdata = point_data + P5_X_INFO_HEADER_LENGTH;
        pid = trdata[0];
    }

    switch (pid) {
    case P5_X_DEMO_AXIS_PACKET_ID:
            ili_report_ap_mode(&trdata[P5_X_DEMO_MODE_PACKET_INFO_LEN]);
        break;
    default:
        errPrintf("not supported packet id: %d\n", pid);
        return -EINVAL;
    }

    return 0;
}

/**
 * Initialize Ilitek device driver
 * @param[in] ili_dev device pointer
 * @return error status
 */
int ili_driver_init(struct ilitek_ts_info *ili_dev)
{
    int res = 0;
    uint8_t cmd_buf[TP_DATA_LEN] = {0, };
    uint8_t checksum = 0, pack_checksum = 0;

    dbgPrintf("%s resolution info x=%d y=%d\n",
              __func__, ili_dev->width, ili_dev->height);

    if (allocate_rxtx_buffers() == false) {
        res = TEE_ERROR_OUT_OF_MEMORY;
        return -res;
    }

    /* initialize spi configration */
    TEES_SPIConfig cfg = {
        .speedHz = 10000000,
        .CPOL = 1,
        .CPHA = 1,
        .bitsPerWord = 0,
        .isDMAMode = 1,
        .manualClockControl = 1,
    };
    res = TEES_SPIInit(SPI_PORT, &cfg, &handler);
    if (res != TEE_SUCCESS) {
        errPrintf("Failed to initalize SPI, tee_err=%#x\n", res);
        goto out;
    }

    /* read tsp info to check spi comunication */
    cmd_buf[0] = ILITEK_SPI_READ;
    res = ili_spi_read(cmd_buf, TP_DATA_LEN);
    if (res < 0) {
        errPrintf("ili_spi_read failed.(%x)\n", res);
        goto out;
    }
#ifdef DR_DBGLOG
    dbgPrintf("Dump Hexa\n");
    print_hex(cmd_buf, TP_DATA_LEN);
#endif

    /* check tsp data */
    cmd_buf[0] = ILI_TSP_HEADER_MASK(cmd_buf[0]);
    checksum = ili_calc_packet_checksum(cmd_buf, TP_DATA_LEN - 1);
    pack_checksum = cmd_buf[TP_DATA_LEN - 1];

    if (checksum != pack_checksum) {
        errPrintf("%s Checksum Error (0x%X)! Pack = 0x%X, len = %d\n",
                  __func__, checksum, pack_checksum, TP_DATA_LEN);
        goto out;
    }

    /* update panel info */
    ili_dev->panel_wid = ili_dev->width;
    ili_dev->panel_hei = ili_dev->height;
    ili_dev->trans_xy = 0;

    init_touch_queue();
    return 0;

out:
    deallocate_rxtx_buffers();
    return -EINVAL;
}

/**
 * Uninitialize Ilitek device driver
 * @return error status
 */
int ili_driver_release(void)
{
     if (TEES_SPIExit(handler)) {
        errPrintf("Failed to close SPI device\n");
        TEE_Panic(0);
    }

    deallocate_rxtx_buffers();
    return 0;
}
