/*
 * zinitix_driver.c
 *
 * Copyright (C) 2019, Samsung Electronics Co., Ltd.
 *
 * bt532 (zinitix) touchscreen routines
 */

#include <errno.h>
#include <driver/interrupt/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     5

/* BT532 Touch controller commands */
#define BT532_CLEAR_INT_STATUS_CMD   0x0003
#define BT532_VENDOR_ID              0x001C
#define BT532_POINT_STATUS_REG       0x0080
#define BT532_DEBUG_REG              0x0115

#define BIT_MUST_ZERO                13
#define SUB_BIT_EXIST                0
#define SUB_BIT_DOWN                 1
#define SUB_BIT_MOVE                 2
#define SUB_BIT_UP                   3

struct capa_info {
    uint16_t vendor_id;
    uint16_t multi_fingers;
};

struct coord {
    uint16_t x;
    uint16_t y;
    uint8_t width;
    uint8_t sub_status;
};

struct point_info {
    uint16_t status;
    uint8_t finger_cnt;
    uint8_t time_stamp;
    struct coord coord[MAX_SUPPORTED_FINGER_NUM];
};

struct bt532_ts_info {
    unsigned int width;
    unsigned int height;
    struct capa_info cap_info;
    struct point_info touch_info;
};

struct zinitix_ts_info zinitix_data;
struct bt532_ts_info bt532_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 bt532_ts_info *info)
{
    int ret;
    unsigned int i;
    uint16_t cmd;
    uint16_t *point_info_ptr;

    point_info_ptr = &info->touch_info.status;
    cmd = BT532_POINT_STATUS_REG;
    ret = zt_i2c_rw((uint8_t *)&cmd, 2, (uint8_t *)&info->touch_info,
                    sizeof(struct point_info));
    if (ret < 0) {
        errPrintf("failed to read point info ret = %d\n", ret);
        return ret;
    }

    if (zinitix_bit_test(info->touch_info.status, BIT_MUST_ZERO)
      || info->touch_info.status == 0x1) {
        errPrintf("abnormal point info read\n");
        return -1;
    }

    for (i = 0; i < sizeof(struct point_info) / 2; i++) {
        if (point_info_ptr[i] == 0xffff) {
            errPrintf("point info 0xffff\n");
            info->touch_info.status = 0xffff;
            return -1;
        }
    }

    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 sub_status;
    uint8_t prev_sub_status;
    uint16_t cmd;
    uint16_t ic_status;
    uint32_t x;
    uint32_t y;

    dbgPrintf(">> %s\n", __func__);

    if (atomic_read(isr_running) == -1) {
        return 0;
    }
    atomic_init(isr_running, 1);

    if (get_touch_queue_wsize() < bt532_data.cap_info.multi_fingers) {
        ret = -1;
        goto out;
    }

    cmd = BT532_DEBUG_REG;
    ret = zt_i2c_rw((uint8_t *)&cmd, 2, (uint8_t *)&ic_status, 2);
    if (ret < 0) {
        errPrintf("reading ic status fail %d\n", ret);
        goto out;
    }

    if (ts_read_coord(&bt532_data)) {
        errPrintf("failed to read info coord (0x%x)\n", ic_status);
        ret = -1;
        goto out;
    }

    /* invalid : maybe periodical repeated int. */
    if (bt532_data.touch_info.status == 0x0) {
        errPrintf("invalid status (0x%x)\n", bt532_data.touch_info.status);
        ret = -1;
        goto out;
    }

    for (i = 0; i < bt532_data.cap_info.multi_fingers; i++) {
        sub_status = bt532_data.touch_info.coord[i].sub_status;
        prev_sub_status = get_finger_state(i);
        x = bt532_data.touch_info.coord[i].x;
        y = bt532_data.touch_info.coord[i].y;

        if (zinitix_bit_test(sub_status, SUB_BIT_EXIST)) {
            if (x > zinitix_data.width || y > zinitix_data.height) {
                errPrintf("invalid coord finger %d, x=%d, y=%d\n", i, x, y);
                continue;
            }

            if (zinitix_bit_test(sub_status, SUB_BIT_DOWN)) {
                dbgPrintf("press finger %d, x = %d , y = %d\n", i, x, y);
                add_touch_event(i, x, y, tui_ts_press);
            } else if (zinitix_bit_test(sub_status, SUB_BIT_MOVE)) {
                dbgPrintf("update finger %d, x = %d , y = %d\n", i, x, y);
                add_touch_event(i, x, y, tui_ts_update);
            }
        } else if (zinitix_bit_test(sub_status, SUB_BIT_UP)
                 || zinitix_bit_test(prev_sub_status, SUB_BIT_EXIST)) {
            dbgPrintf("release finger %d, x = %d , y = %d\n", i, x, y);
            add_touch_event(i, x, y, tui_ts_release);
        }
    }

out:
    cmd = BT532_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(&bt532_data, 0, sizeof(bt532_data));

    bt532_data.cap_info.multi_fingers = MAX_SUPPORTED_FINGER_NUM;
    bt532_data.width = zt_dev->width;
    bt532_data.height = zt_dev->height;

    cmd = BT532_VENDOR_ID;
    ret = zt_i2c_rw((uint8_t *)&cmd, 2, (uint8_t *)&bt532_data.cap_info.vendor_id, 2);
    if (ret < 0) {
        errPrintf("reading vendoir id fail %d\n", ret);
        return ret;
    }

    dbgPrintf("%s %d\n", __func__, bt532_data.cap_info.vendor_id);

    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;
}
