/*
 *
 * Copyright (C) 2012-2019, Samsung Electronics Co., Ltd.
 *
 * Himax HX83102 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 "himax_driver.h"
#include "i2c.h"
#include "secmap.h"
#include "touch_gpio.h"

/* Touch controller commands */
#define hx_addr_ahb_addr             0x11
#define hx_data_ahb_dis              0x00
#define hx_data_ahb_en               0x01
#define hx_addr_event_addr           0x30

#define MAX_WAIT_CNT                 10
#define ISR_WAIT_DELAY               20

struct hx_ts_info hx_data;
static atomic_t isr_running;
/******************************************************************************/

static int himax_i2c_read(uint8_t addr, uint8_t *buff, uint16_t count)
{
    TEE_Result ret;
    deviceInfo_t *i2cDev = &board.i2c;
    int try_count = HIMAX_RETRY_COUNT;

    if (!buff || !count) {
        errPrintf("invalid arg, count=%d\n", count);
        return -EINVAL;
    }

    do {
        ret = i2c_read_write(i2cDev,
                             i2cDev->bus.slave_address,
                             &addr,
                             1,
                             buff,
                             count);
        if (ret == TEE_SUCCESS) {
            return 0;
        }
    } while (--try_count);

    errPrintf("failed, ret=%d\n", ret);

    return -EIO;
}

static int himax_i2c_write(uint8_t addr, uint8_t *buff, uint16_t count)
{
    TEE_Result ret;
    deviceInfo_t *i2cDev = &board.i2c;
    static uint8_t local_buff[256];
    int try_count = HIMAX_RETRY_COUNT;

    if ((!buff && count) || (size_t)count >= sizeof(local_buff)) {
        errPrintf("invalid arg, count=%d\n", count);
        return -EINVAL;
    }

    local_buff[0] = addr;
    if (count) {
        TEE_MemMove(local_buff + 1, buff, count);
    }

    do {
        ret = i2c_send(i2cDev, i2cDev->bus.slave_address, local_buff, count + 1);
        if (ret == TEE_SUCCESS) {
            return 0;
        }
    } while (--try_count);

    errPrintf("failed, ret=%d\n", ret);

    return -EIO;
}

static int himax_burst_enable(uint8_t enable)
{
    uint8_t cmd = 0x31;
    if (himax_i2c_write(0x13, &cmd, 1)) {
        errPrintf("himax_i2c_write failed\n");
        return -EIO;
    }

    cmd = (0x10 | enable);
    if (himax_i2c_write(0x0D, &cmd, 1)) {
        errPrintf("himax_i2c_write failed\n");
        return -EIO;
    }

    return 0;
}

static int himax_register_read(uint8_t *addr, int length, uint8_t *data)
{
    uint8_t cmd;

    if (length > 256) {
        errPrintf("length is over 256\n");
        return -EINVAL;
    }

    if (length > 4) {
        if (himax_burst_enable(1)) {
            errPrintf("burst enable failed\n");
            return -EIO;
        }
    } else {
        if (himax_burst_enable(0)) {
            errPrintf("burst disable failed\n");
            return -EIO;
        }
    }

    if (himax_i2c_write(0x00, addr, 4)) {
        errPrintf("himax_i2c_write failed\n");
        return -EIO;
    }

    cmd = 0x00;
    if (himax_i2c_write(0x0C, &cmd, 1)) {
        errPrintf("himax_i2c_write failed\n");
        return -EIO;
    }

    if (himax_i2c_read(0x08, data, length)) {
        errPrintf("himax_i2c_read failed\n");
        return -EIO;
    }

    if (length > 4) {
        if (himax_burst_enable(0)) {
            errPrintf("burst disable failed\n");
            return -EIO;
        }
    }

    return 0;
}

static int himax_read_event(uint8_t *buf, uint8_t length)
{
    uint8_t cmd = hx_data_ahb_dis;
    if (himax_i2c_write(hx_addr_ahb_addr, &cmd, 1)) {
        errPrintf("himax_i2c_write failed\n");
        return -EIO;
    }

    if (himax_i2c_read(hx_addr_event_addr, buf, length)) {
        errPrintf("himax_i2c_read failed\n");
        return -EIO;
    }

    cmd = hx_data_ahb_en;
    if (himax_i2c_write(hx_addr_ahb_addr, &cmd, 1)) {
        errPrintf("himax_i2c_write failed\n");
        return -EIO;
    }

    return 0;
}

static int himax_checksum_cal(uint8_t *buf, uint8_t length)
{
    uint16_t check_sum_cal = 0;
    int zero_cnt = 0;
    int ret = 0;

    for (int i = 0; i < length; i++) {
        check_sum_cal += buf[i];
        if (buf[i] == 0x00) {
            zero_cnt++;
        }
    }

    if (check_sum_cal % 0x100 != 0) {
        errPrintf("checksum failed : check_sum_cal=0x%02X\n", check_sum_cal);
        ret = -1;
    } else if (zero_cnt == length) {
        errPrintf("all zero event\n");
        ret = -1;
    }

    return ret;
}

static void himax_parse_event(void)
{
    uint8_t *buf = hx_data.coord_buf;
    uint16_t x = 0;
    uint16_t y = 0;
    int base = 0;

    for (unsigned int i = 0; i < MAX_SUPPORTED_FINGER_NUM; i++) {
        base = i * 4;
        x = buf[base] << 8 | buf[base + 1];
        y = buf[base + 2] << 8 | buf[base + 3];

        if (x <= hx_data.width && y <= hx_data.height) {
            dbgPrintf("%s: press or update, finger=%d, x=%d, y=%d\n",
                      __func__, i, x, y);
            if (get_finger_state(i) == tui_ts_release) {
                add_touch_event(i, x, y, tui_ts_press);
            } else {
                add_touch_event(i, x, y, tui_ts_update);
            }
        } else {
            if (get_finger_state(i) != tui_ts_release) {
                dbgPrintf("%s: release, finger=%d\n", __func__, i);
                add_touch_event(i,
                                get_finger_xpos(i),
                                get_finger_ypos(i),
                                tui_ts_release);
            }
        }
    }
}

/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
int himax_irq_process(void)
{
    uint8_t *buf = hx_data.coord_buf;
    uint8_t *state_info = hx_data.state_info;
    uint8_t info_size = HX_TOUCH_INFO_SIZE;
    uint8_t state_info_pos = info_size - 3;
    uint16_t hx_point_num;
    int ret = 0;

    dbgPrintf(">> %s\n", __func__);

    if (atomic_read(isr_running) == -1) {
        dbgPrintf("%s driver was released and isr is not allowed\n", __func__);
        return 0;
    }
    atomic_init(isr_running, 1);

    if (get_touch_queue_wsize() < MAX_SUPPORTED_FINGER_NUM) {
        ret = -1;
        goto out;
    }

    ret = himax_read_event(buf, info_size);
    if (ret) {
        errPrintf("himax_read_event failed\n");
        goto out;
    }
    if (buf[state_info_pos] != 0xFF && buf[state_info_pos + 1] != 0xFF) {
        memcpy(state_info, &buf[state_info_pos], 2);
    } else {
        dbgPrintf("%s state_info both 0xFF\n", __func__);
        memset(state_info, 0x00, 2);
    }

    ret = himax_checksum_cal(buf, info_size);
    if (ret) {
        errPrintf("himax_checksum_cal failed\n");
        goto out;
    }

    if (buf[HX_TOUCH_INFO_POINT_CNT] == 0xff) {
        hx_point_num = 0;
    } else {
        hx_point_num = buf[HX_TOUCH_INFO_POINT_CNT] & 0x0f;
    }

    if (hx_point_num == 0) {
        dbgPrintf("%s: hx_point_num = 0!\n", __func__);
        release_all_fingers();
        goto out;
    }

    himax_parse_event();

out:
    atomic_init(isr_running, 0);
    dbgPrintf("<< %s\n", __func__);

    return ret;
}

/**
 * Initialize himax device driver
 * @param[in] himax_dev himax device pointer
 * @return error status
 */
int himax_driver_init(struct hx_ts_info *hx_data)
{
    uint8_t addr[4] = {0xD0, 0x00, 0x00, 0x90};
    uint8_t id[4];

    (void)hx_data;

    if (himax_register_read(addr, 4, id)) {
        errPrintf("himax_register_read failed\n");
        return -1;
    }
    dbgPrintf("%s:read ID = %X,%X,%X\n", __func__, id[3], id[2], id[1]);

    init_touch_queue();

    atomic_init(isr_running, 0);

    return 0;
}

/**
 * Uninitialize himax device driver
 * @return error status
 */
int himax_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;
}
