/*
 *
 * Copyright (C) 2021, Samsung Electronics Co., Ltd.
 *
 * Novatek touchscreen routines
 */

#include <driver/interrupt/interrupt.h>
#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 "novatek_driver.h"
#include "secmap.h"
#include "touch_gpio.h"

#define NVT_EVENT_BUF_ADDR                      0x11A00
#define NVT_EVENT_MAP_FWINFO                    0x78
#define NVT_TOUCH_MAX_FINGER_NUM                10
#define NVT_RELEASE_CLIENT_ID                   0x1F
#define NVT_MAX_TOUCH_COUNT                     10

#define NVT_FINGER_ENTER                        0x01
#define NVT_FINGER_MOVING                       0x02
#define NVT_GLOVE_TOUCH                         0x06
#define NVT_RELEASE                             0xFF

//---SPI READ/WRITE---
#define NVT_SPI_WRITE_MASK(a)                   (a | 0x80)
#define NVT_SPI_READ_MASK(a)                    (a & 0x7F)
#define DUMMY_BYTES                             (1)

#define SPI_PORT                                1

struct novatek_ts_info novatek_data;
static TEES_SPIHandler handler;
static unsigned char *tx_buf = NULL;
static unsigned char *rx_buf = NULL;

int press_x[NVT_TOUCH_MAX_FINGER_NUM];
int press_y[NVT_TOUCH_MAX_FINGER_NUM];

#if POINT_DATA_CHECKSUM
static int32_t nvt_ts_point_data_checksum(uint8_t *buf, uint8_t length)
{
    uint8_t checksum = 0;
    int32_t i = 0;

    // Generate checksum
    for (i = 0; i < length - 1; i++) {
        checksum += buf[i + 1];
    }
    checksum = (~checksum + 1);

    // Compare ckecksum and dump fail data
    if (checksum != buf[length]) {
        errPrintf("spi packet checksum not match. (point_data[%d]=0x%02X, checksum=0x%02X)\n",
                  length, buf[length], checksum);

        for (i = 0; i < 10; i++) {
            errPrintf("%02X %02X %02X %02X %02X %02X\n",
                      buf[1 + i*6], buf[2 + i*6], buf[3 + i*6], buf[4 + i*6], buf[5 + i*6], buf[6 + i*6]);
        }

        errPrintf("%02X %02X %02X %02X %02X\n", buf[61], buf[62], buf[63], buf[64], buf[65]);

        return -1;
    }

    return 0;
}
#endif /* POINT_DATA_CHECKSUM */

static int allocate_rxtx_buffers(void)
{
    tx_buf = malloc(SPI_BUF_LEN);
    rx_buf = malloc(SPI_BUF_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;
    for (i = 0; i < len; i++) {
        printf("0x%x ", buf[i]);
    }
    printf("\n");
    printf("\n");
}
#endif

/*******************************************************
Description:
    Novatek touchscreen spi read function.

return:
    Executive outcomes. 2---succeed. -5---I/O error
*******************************************************/
int32_t nvt_spi_read(uint8_t *buf, uint16_t len)
{
    int res = -1;
    int retries = 0;

    buf[0] = NVT_SPI_READ_MASK(buf[0]);

    memset(tx_buf, 0, SPI_BUF_LEN);
    memset(rx_buf, 0, SPI_BUF_LEN);
    TEE_MemMove(tx_buf, buf, len + DUMMY_BYTES);

    TEES_SPITransfer tx = {
        .bufAddr = tx_buf,
        .bufLen = len + DUMMY_BYTES,
        .transferredLen = 0
    };
    TEES_SPITransfer rx = {
        .bufAddr = rx_buf,
        .bufLen = len + DUMMY_BYTES,
        .transferredLen = 0
    };

    while (retries < 5) {
        /* read fw info */
        res = TEES_SPIWriteRead(handler, &tx, &rx);
        if (res == TEE_SUCCESS) break;
        retries++;
    }

    if (retries == 5) {
        errPrintf("%s, xdata read fail %d\n", __func__, res);
        res = -EIO;
    } else {
        memcpy((buf+1), (rx_buf+2), (len -1));
    }

    return res;
}


/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
int nvt_irq_process(void)
{
    TEE_Result res = 0;
    uint8_t point_data[SPI_BUF_LEN] = {0,};
    int finger_cnt;
    int i;
    struct nvt_ts_event_coord *p_event_coord;
    int x = 0, y = 0;
    uint8_t id, status;

#ifdef MT_PROTOCOL_B
    static uint8_t prev_press_id[NVT_TOUCH_MAX_FINGER_NUM];
#else /* MT_PROTOCOL_B */
    bool release = false;
#endif /* MT_PROTOCOL_B */

    bool press_id[NVT_TOUCH_MAX_FINGER_NUM] = {0,};

    dbgPrintf("<< %s\n", __func__);

    if (get_touch_queue_wsize() < 1) {
        return -1;
    }

    res = nvt_spi_read(point_data, NVT_POINT_DATA_LEN + 1);
    if (res != TEE_SUCCESS) {
        errPrintf("nvt_spi_read failed.(%x)\n", res);
        return -1;
    }
#ifdef DR_DBGLOG
    dbgPrintf("Dump Hexa\n");
    print_hex(point_data, SPI_BUF_LEN);
#endif /* DR_DBGLOG */

#if PROXIMITY_FUNCTION
    if ((uint8_t)(point_data[1] >> 3) == DATA_PROTOCOL) {
        return 0;
    }
#endif /* PROXIMITY_FUNCTION */

#if POINT_DATA_CHECKSUM
   if (NVT_POINT_DATA_LEN >= POINT_DATA_CHECKSUM_LEN) {
       if (nvt_ts_point_data_checksum(point_data, POINT_DATA_CHECKSUM_LEN)) {
           errPrintf("incorrect checksum\n");
           return -1;
       }
   }
#endif /* POINT_DATA_CHECKSUM */

    for (i = 0, finger_cnt = 0; i < NVT_MAX_TOUCH_COUNT; i++) {
        p_event_coord = (struct nvt_ts_event_coord *)&point_data[1 + 6 * i];
        id = p_event_coord->id;

#ifndef MT_PROTOCOL_B
        release = ((p_event_coord->id << 3 | p_event_coord->reserved_1 << 2 | p_event_coord->status) == NVT_RELEASE);
#endif /* MT_PROTOCOL_B */

        if (!id || (id > 10 && id != NVT_RELEASE_CLIENT_ID)) {
            continue;
        }

        id = id - 1;
        status = p_event_coord->status;
        dbgPrintf("<< %s, COORDINATE_EVENT finger %d, %d\n", __func__, id, status);

        if ((status == NVT_FINGER_ENTER) || (status == NVT_FINGER_MOVING) || (status == NVT_GLOVE_TOUCH)) {
            x = (uint32_t)(p_event_coord->x_11_4 << 4) + (uint32_t)(p_event_coord->x_3_0);
            y = (uint32_t)(p_event_coord->y_11_4 << 4) + (uint32_t)(p_event_coord->y_3_0);
            if (x < 0 || y < 0) {
                errPrintf("%s: invaild coordinate (%d, %d)\n", __func__, x, y);
                continue;
            }

            dbgPrintf("<< %s, pressed x= %d, y= %d\n", __func__, x, y);
            press_id[id] = true;
#ifdef MT_PROTOCOL_B
            prev_press_id[id] = true;
#endif /* MT_PROTOCOL_B */
            press_x[id] = x;
            press_y[id] = y;
            add_touch_event(id, x, y, tui_ts_press);
            finger_cnt++;
        } else {
#ifndef MT_PROTOCOL_B
            if (release && 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;
            }
#endif /* MT_PROTOCOL_B */
        }
    }

#ifdef MT_PROTOCOL_B
    for (i = 0; i < NVT_MAX_TOUCH_COUNT; i++) {
        if (!press_id[i] && prev_press_id[i]) {
            prev_press_id[i] = false;
            add_touch_event(i, press_x[i], press_y[i], tui_ts_release);
        }
    }
#endif /* MT_PROTOCOL_B */

    return 0;
}

/**
 * Initialize NVT device driver
 * @param[in] nvt_dev nvy device pointer
 * @return error status
 */
int nvt_driver_init(struct novatek_ts_info *nvt_dev)
{
    int res = 0;
    uint8_t cmd_buf[SPI_BUF_LEN] = {0,};

    TEES_SPIConfig cfg = {
        .speedHz = 9600000,
        .CPOL = 1,
        .CPHA = 1,
        .bitsPerWord = 0,
        .isDMAMode = 1,
#if defined (TUI_MODEL_A13X) || defined(TUI_MODEL_A22X)
        .manualClockControl = 0,
#else
        .manualClockControl = 1,
#endif
    };

    dbgPrintf("%s resolution info x=%d y=%d\n",
              __func__, nvt_dev->width, nvt_dev->height);

    if (allocate_rxtx_buffers() == false) {
        res = TEE_ERROR_OUT_OF_MEMORY;
        return -res;
    }

    res = TEES_SPIInit(SPI_PORT, &cfg, &handler);
    if (res != TEE_SUCCESS) {
        errPrintf("Failed to initalize SPI, tee_err=%#x\n", res);
        goto out;
    }

    /* check spi comunication */
    res = nvt_spi_read(cmd_buf, NVT_POINT_DATA_LEN + 1);
    if (res < 0) {
        errPrintf("NVT_SPI_READ failed.(%x)\n", res);
        goto out;
    }

#ifdef DR_DBGLOG
    dbgPrintf("Dump Hexa\n");
    print_hex(cmd_buf, SPI_BUF_LEN);
#endif

    nvt_dev->max_x = nvt_dev->width;
    nvt_dev->max_y = nvt_dev->height;

    init_touch_queue();
    return 0;
out:

    deallocate_rxtx_buffers();
    return -1;
}

/**
 * Uninitialize NVT device driver
 * @return error status
 */
int nvt_driver_release(void)
{
     if (TEES_SPIExit(handler)) {
        errPrintf("Failed to close SPI device\n");
        TEE_Panic(0);
    }
    deallocate_rxtx_buffers();
    return 0;
}

TEE_Result nvt_get_info(drTouchInfo_ptr touchSize)
{
    touchSize->width = novatek_data.max_x;
    touchSize->height = novatek_data.max_y;
    return TEE_SUCCESS;
}

TEE_Result nvt_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;

    /* Initialization */
    retHal = touch_gpio_init(gpio_dev);
    if (retHal != TEE_SUCCESS) {
        errPrintf("touch_gpio_init() Failed\n");
        goto err_gpio_init;
    }

    if (touch_dev->state == DEV_UNCONFIGURED) {
        /* Init touch module */
        dbgPrintf("Touch Init Start\n");
        TEE_MemFill(&novatek_data, 0, sizeof(novatek_data));

        novatek_data.width = width;
        novatek_data.height = height;

        /* Initialize NOVATEK touchscreen */
        ret = nvt_driver_init(&novatek_data);
        if (ret < 0) {
            errPrintf("nvt_driver_init() : retval = %d", ret);
            retHal = E_TUI_HAL_IO;
            goto err_init;
        }
        touch_dev->state |= DEV_SFR_CONFIGURED;
    }

    sec_release();
    dbgPrintf("<< %s\n", __func__);
    return retHal;

err_init:
    ret = touch_gpio_release(gpio_dev);
    if (ret != TEE_SUCCESS) {
        errPrintf("touch_gpio_unregister_int Failed!\n");
    }
err_gpio_init:
    sec_release();
    dbgPrintf("<< %s, err\n", __func__);
    return retHal;
}

TEE_Result nvt_close(void)
{
    dbgPrintf(">> %s\n", __func__);
    deviceInfo_t *touch_dev = &board.touch;
    deviceInfo_t *gpio_dev = &board.gpio;
    TEE_Result ret;

    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 = nvt_driver_release();
        if (ret != TEE_SUCCESS) {
            errPrintf("nvt_driver_release Failed!\n");
        } else {
            touch_dev->state &= ~DEV_SFR_CONFIGURED;
        }
    }

    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 nvt_process(void)
{
    int retval = 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) {
        retval = touch_gpio_register_int(gpio_dev, nvt_irq_process);
        if (retval != TEE_SUCCESS) {
            errPrintf("touch_gpio_register_int() : retval = %d\n", retval);
            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 */
    retval = nvt_irq_process();
#endif /* USE_TOUCH_INTERRUPT */
    if (retval != 0) {
        return TEE_ERROR_GENERIC;
    }
    return TEE_SUCCESS;
}
