/*
 *
 * Copyright (C) 2012-2019, Samsung Electronics Co., Ltd.
 *
 * STMicroelectronix FTS Touch protocol implementation
 */

#include <tee_internal_api.h>
#include <errno.h>
#include <string.h>
#include "board.h"
#include "bsp_common.h"
#include "dbg.h"
#include "fts_driver.h"

/* Somewhy 'event id' is shifted right on one byte on istor */
#ifdef TUI_MODEL_ISTOR
#define TUI_EVENT_ID_OFFSET    1
#else /* TUI_MODEL_ISTOR */
#define TUI_EVENT_ID_OFFSET    0
#endif /* TUI_MODEL_ISTOR */

struct fts_ts_info fts_data;

enum fts_command_e {
    FTS_CMD_READ_STATUS = 0x84,
    FTS_CMD_READ_ONE_EVENT = 0x85,
    FTS_CMD_READ_ALL_EVENT = 0x86,
    FTS_CMD_SLEEP_IN = 0x90,
    FTS_CMD_SLEEP_OUT = 0x91,
    FTS_CMD_MS_MT_SENSE_OFF = 0x92,
    FTS_CMD_MS_MT_SENSE_ON = 0x93,
    FTS_CMD_SS_HOVER_SENSE_OFF = 0x94,
    FTS_CMD_SS_HOVER_SENSE_ON = 0x95,
    FTS_CMD_TRIM_LOW_POWER_OSCILLATOR = 0x97,
    FTS_CMD_CLEAR_EVENT_STACK = 0xA1,
    FTS_CMD_FULL_FORCE_CALIBRATION = 0xA2,
    FTS_CMD_MS_CX_TUNING = 0xA3,
    FTS_CMD_SS_CX_TUNING = 0xA4,
    FTS_CMD_ITO_CHECK = 0xA7,
    FTS_CMD_EXTERNAL_RELEASE_INFORMATION_REQUEST = 0xAA
};

enum {
    FTS_EVENT_SIZE = 8
};

enum fts_event_e {
    FTS_EV_NO_EVENT = 0x00,
    FTS_EV_MULTI_TOUCH_ENTER = 0x03,	/* 0:4 bit only */
    FTS_EV_MULTI_TOUCH_LEAVE = 0x04,	/* 0:4 bit only */
    FTS_EV_MULTI_TOUCH_MOTION = 0x05,	/* 0:4 bit only */
    FTS_EV_HOVER_ENTER = 0x07,
    FTS_EV_HOVER_LEAVE = 0x08,
    FTS_EV_HOVER_MOTION = 0x09,
    FTS_EV_BUTTON_STATUS = 0x0E,
    FTS_EV_ERROR = 0x0F,
    FTS_EV_CONTROLLER_READY = 0x10,
    FTS_EV_STATUS = 0x16,
    FTS_EV_INTERNAL_INFORMATION = 0x19,
    FTS_EV_EXTERNAL_INFORMATION = 0x1A
};


/******************************************************************************
 * LOCAL TYPES
 ******************************************************************************/

struct stack_report_fmt {
    uint8_t left_event;
    uint8_t eo;
    uint8_t er;
    uint8_t reserved;
};

struct mtouch_event_fmt {
    uint8_t event;
    uint8_t touch_id;
    uint16_t x_coord;
    uint16_t y_coord;
    uint8_t major_axys;
    uint8_t minor_axys;
    uint8_t orientation;
    uint8_t area;
};

struct internal_information_fmt {
    uint8_t event;
    uint8_t chip_ver;
    uint8_t ftsAchip_ver;
    uint16_t fw_ver;
    uint16_t config_id;
    struct stack_report_fmt report;
};

struct external_information_fmt {
    uint8_t event;
    uint32_t ex_release;
    uint8_t reserved0;
    uint8_t reserved1;
    struct stack_report_fmt report;
};

/******************************************************************************
 * EXTERN FUNCTIONS
 ******************************************************************************/

int fts_i2c_read(uint8_t cmd, uint8_t *params, int params_len);
int fts_i2c_read_data(uint8_t *params, int params_len);
int fts_i2c_write(uint8_t cmd, uint8_t *params, int params_len);

/******************************************************************************
 * LOCAL FUNCTIONS
 ******************************************************************************/

/**
 * @brief Convert input buffer to stack_report_fmt
 * @param buf - raw buffer
 * @param stack_report - pointer on output struct
 */
static void convto_stack_report(uint8_t buf, struct stack_report_fmt *stack_report)
{
    /*               Report format
     * |    7    |  6 |  5 | 4 | 3 | 2 | 1 | 0 |
     * |reserved | er | eo |    left event     |
     */
    stack_report->left_event = buf & 0x1f;
    stack_report->eo         = (buf >> 5) & 1;
    stack_report->er         = (buf >> 6) & 1;
    stack_report->reserved   = buf >> 7;
}

/**
 * @brief Convert input buffer to mtouch_event_fmt
 * @param buf - raw buffer
 * @param mtouch_event - pointer on output struct
 */
static void convto_mtouch_event(uint8_t buf[8], struct mtouch_event_fmt *mtouch_event)
{
    /*          Multi touch event format
     *        | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
     * byte0: |   touch_id    |    event      |
     * byte1: |           x_coord             |
     * byte2: |    y_coord    |    x_coord    |
     * byte3: |           y_coord             |
     * byte4: |          major_axys           |
     * byte5: |          minor_axys           |
     * byte6: |          orientation          |
     * byte7: |             area              |
     */
    mtouch_event->event       = buf[0] & 0x0f;
    mtouch_event->touch_id    = (buf[0] >> 4) & 0x0f;
    mtouch_event->x_coord     = buf[1] | (((uint16_t)buf[2] & 0x0f) << 8);
    mtouch_event->y_coord     = ((buf[2] & 0xf0) >> 4) | (((uint16_t)buf[3]) << 4);
    mtouch_event->major_axys  = buf[4];
    mtouch_event->minor_axys  = buf[5];
    mtouch_event->orientation = buf[6];
    mtouch_event->area        = buf[7];
}

/**
 * @brief Convert input buffer to internal_information_fmt
 * @param buf - raw buffer
 * @param inter_inf - pointer on output struct
 */
static void convto_internal_information(uint8_t buf[8], struct internal_information_fmt *inter_inf)
{
    /*          Internal information format
     *        | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
     * byte0: |            event              |
     * byte1: |           chip_ver            |
     * byte2: |          ftsAchip_ver         |
     * byte3: |            fw_ver             |
     * byte4: |            fw_ver             |
     * byte5: |          config_id0           |
     * byte6: |          config_id0           |
     * byte7: |            report             |
     */
    inter_inf->event    = buf[0];
    inter_inf->chip_ver = buf[1];
    inter_inf->ftsAchip_ver = buf[2];
    inter_inf->fw_ver    = ((uint16_t)buf[3] << 8) | buf[4];
    inter_inf->config_id = ((uint16_t)buf[6] << 8) | buf[5];
    convto_stack_report(buf[7], &inter_inf->report);
}

/**
 * @brief Convert input buffer to external_information_fmt
 * @param buf - raw buffer
 * @param extern_inf - pointer on output struct
 */
static void convto_external_information(uint8_t buf[8], struct external_information_fmt *extern_inf)
{
    /*          External information format
         *        | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
         * byte0: |            event              |
         * byte1: |          ex_release0          |
         * byte2: |          ex_release1          |
         * byte3: |          ex_release2          |
         * byte4: |          ex_release3          |
         * byte5: |           reserved0           |
         * byte6: |           reserved1           |
         * byte7: |            report             |
         */
    extern_inf->event = buf[0];
    extern_inf->ex_release = ((uint32_t)buf[4] << 24)
            | ((uint32_t)buf[3] << 16)
            | ((uint32_t)buf[2] << 8) | buf[1];
    extern_inf->reserved0  = buf[5];
    extern_inf->reserved0  = buf[6];
    convto_stack_report(buf[7], &extern_inf->report);
}

/**
 * @brief Send fts command to fts device.
 * @param dev - pointer on fts device
 * @param cmd - fts command
 * @param params[out] - output buffer for writing result of fts command if it is available.
 * @param params_len - length of params buffer
 * @return 0 if success and error status otherwise
 */
static int fts_command(enum fts_command_e cmd, uint8_t *params, uint32_t params_len)
{
    int ret = 0;
    if (params && params_len >= FTS_EVENT_SIZE) {
        ret = fts_i2c_read(cmd & 0xFF, params, params_len);
    } else if (!params && !params_len) {
        ret = fts_i2c_write(cmd & 0xFF, NULL, 0);
    } else {
        ret = -EINVAL;
    }
    return ret;
}

static int get_fts_info(struct fts_version *ver)
{
    uint8_t buf[FTS_EVENT_SIZE] = { };
    int ret;

    ret = fts_command(FTS_CMD_EXTERNAL_RELEASE_INFORMATION_REQUEST, NULL, 0);
    if (!ret) {
        do {
            ret = fts_command(FTS_CMD_READ_ONE_EVENT, buf, FTS_EVENT_SIZE);
            if (ret) {
                break;
            }
            if (buf[0] == FTS_EV_INTERNAL_INFORMATION) {
                struct internal_information_fmt inter_inf = {};
                convto_internal_information(buf, &inter_inf);
                ver->fw_version_of_ic = inter_inf.fw_ver;
                ver->config_version_of_ic = inter_inf.config_id;
            }
            if (buf[0] == FTS_EV_EXTERNAL_INFORMATION) {
                struct external_information_fmt extern_inf = {};
                convto_external_information(buf, &extern_inf);
                ver->fw_main_version_of_ic = extern_inf.ex_release;
                break;
            }
        } while (1);
    }
    return ret;
}

/******************************************************************************
 * EXPORTED FUNCTIONS
 ******************************************************************************/

/**
 * @brief Process IRQ interupt. Read events from touch device if device has available events
 * @param none
 * @return 0 if success
 */
int fts_irq_process(void)
{
    int ret = 0;
    unsigned int x;
    unsigned int y;
    uint8_t buf[FTS_EVENT_SIZE*2];
    uint8_t event;
    struct mtouch_event_fmt mt_ev = {};

    while (1) {
        if (get_touch_queue_wsize() < 1) {
            return -1;
        }

        ret = fts_command(FTS_CMD_READ_ONE_EVENT, buf, FTS_EVENT_SIZE+1);
        if (ret) {
            return ret;
        }

        event = buf[TUI_EVENT_ID_OFFSET];
        if (event == FTS_EV_NO_EVENT) {
            return ret;
        }

        convto_mtouch_event(buf + TUI_EVENT_ID_OFFSET, &mt_ev);

        x = (((unsigned int)mt_ev.x_coord) * fts_data.width) / (SENSOR_MAX_X_DEFAULT + 1);
        y = (((unsigned int)mt_ev.y_coord) * fts_data.height) / (SENSOR_MAX_Y_DEFAULT + 1);

        if (mt_ev.event != FTS_EV_MULTI_TOUCH_ENTER &&
            mt_ev.event != FTS_EV_MULTI_TOUCH_LEAVE &&
            mt_ev.event != FTS_EV_MULTI_TOUCH_MOTION) {
            continue;
        }

        if (mt_ev.touch_id > TUI_MAX_NUM_OF_FINGERS) {
            continue;
        }

        if (mt_ev.event == FTS_EV_MULTI_TOUCH_ENTER) {
            add_touch_event(mt_ev.touch_id, x, y, tui_ts_press);
        } else if (mt_ev.event == FTS_EV_MULTI_TOUCH_LEAVE) {
            if (get_finger_count() <= 0) {
                errPrintf("%s: release event but 0 finger count\n", __func__);
                release_all_fingers();
                continue;
            }

            add_touch_event(mt_ev.touch_id, x, y, tui_ts_release);
        } else {
            if (get_finger_count() == 0) {
                errPrintf("%s: update event but 0 finger count\n", __func__);
                release_all_fingers();
                continue;
            }

            if (get_finger_state(mt_ev.touch_id) == tui_ts_release) {
                errPrintf("%s: release state but point is moved\n", __func__);
                continue;
            }

            add_touch_event(mt_ev.touch_id, x, y, tui_ts_update);
        }
    }
    return ret;
}

/**
 * @brief Initialize FTS drtiver
 * @param dev - pinter on fts device
 * @return 0 if success
 */
int fts_driver_init(void)
{
    int ret;

    fts_data.max_x = fts_data.width - 1;
    fts_data.max_y = fts_data.height - 1;

    ret = get_fts_info(&fts_data.version);
    if (!ret) {
        dbgPrintf("FTS Version Info: %04x,%08x,%04x\n",
                  fts_data.version.config_version_of_ic,
                  fts_data.version.fw_main_version_of_ic,
                  fts_data.version.fw_version_of_ic);
    }

    /*
     * TODO: 'get_fts_info' doesn't work properly on dream2 when
     * 'FTS_CMD_CLEAR_EVENT_STACK' used. Looks like TSC required
     * some additional initialization if 'FTS_CMD_CLEAR_EVENT_STACK'
     * was used.
     */
#ifndef TUI_MODEL_DREAM2
    ret = fts_command(FTS_CMD_CLEAR_EVENT_STACK, NULL, 0);
    if (ret < 0) {
        errPrintf("%s: i2c write clear event failed %d\n", __func__, ret);
    }
#endif

    init_touch_queue();
    return ret;
}

/**
 * @brief Release FTS driver
 * @param dev - pinter on fts device
 * @return 0 if success
 */
int fts_driver_release(void)
{
    int ret = 0;

#ifndef TUI_MODEL_DREAM2
    ret = fts_command(FTS_CMD_CLEAR_EVENT_STACK, NULL, 0);
    if (ret < 0) {
        errPrintf("%s: i2c write clear event failed %d\n", __func__, ret);
    }
#endif
    return ret;
}
