/*
 *
 * Copyright (C) 2012, Samsung Electronics Co., Ltd.
 *
 * Synaptics touchscreen routines
 */

#include <driver/interrupt/interrupt.h>
#include <errno.h>
#include <stdlib.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 "synaptics_driver.h"
#include "secmap.h"
#include "touch_gpio.h"

#define SYNAPTICS_TS_V1_MESSAGE_MARKER 0xa5
#define SYNAPTICS_TS_V1_MESSAGE_PADDING 0x5a

#define SYNAPTICS_TS_MESSAGE_HEADER_SIZE 4
#define SYNAPTICS_TS_I2C_RETRY_CNT  5

#define RD_CHUNK_SIZE (512)

#define SEC_TS_SUPPORT_TOUCH_COUNT  10

#define SYNAPTICS_TS_TOUCHTYPE_NORMAL       0
#define SYNAPTICS_TS_TOUCHTYPE_HOVER        1
#define SYNAPTICS_TS_TOUCHTYPE_FLIPCOVER    2
#define SYNAPTICS_TS_TOUCHTYPE_GLOVE        3
#define SYNAPTICS_TS_TOUCHTYPE_STYLUS       4
#define SYNAPTICS_TS_TOUCHTYPE_PALM         5
#define SYNAPTICS_TS_TOUCHTYPE_WET          6
#define SYNAPTICS_TS_TOUCHTYPE_PROXIMITY    7
#define SYNAPTICS_TS_TOUCHTYPE_JIG          8

#define SYNAPTICS_MAX_X                               4095
#define SYNAPTICS_MAX_Y                               4095

/**
 * TouchComm Status Codes
 *
 * Define the following status codes for all command responses.
 *  0x00: (v1)      no commands are pending and no reports are available.
 *  0x01: (v1 & v2) the previous command succeeded.
 *  0x03: (v1 & v2) the payload continues a previous response.
 *  0x04: (v2)      command was written, but no reports were available.
 *  0x07: (v2)      the previous write was successfully received.
 *  0x08: (v2)      the previous write was corrupt. The host should resend.
 *  0x09: (v2)      the previous command failed.
 *  0x0c: (v1 & v2) write was larger than the device's receive buffer.
 *  0x0d: (v1 & v2) a command was sent before the previous command completed.
 *  0x0e: (v1 & v2) the requested command is not implemented.
 *  0x0f: (v1 & v2) generic communication error, probably incorrect payload.
 *
 *  0xfe: self-defined status for a corrupted packet.
 *  0xff: self-defined status for an invalid data.
 */
enum tcm_status_code {
    STATUS_IDLE = 0x00,
    STATUS_OK = 0x01,
    STATUS_CONTINUED_READ = 0x03,
    STATUS_NO_REPORT_AVAILABLE = 0x04,
    STATUS_ACK = 0x07,
    STATUS_RETRY_REQUESTED = 0x08,
    STATUS_CMD_FAILED = 0x09,
    STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c,
    STATUS_PREVIOUS_COMMAND_PENDING = 0x0d,
    STATUS_NOT_IMPLEMENTED = 0x0e,
    STATUS_ERROR = 0x0f,
    STATUS_PACKET_CORRUPTED = 0xfe,
    STATUS_INVALID = 0xff,
};

enum custom_report_type {
    REPORT_SEC_SPONGE_GESTURE = 0xc0,
    REPORT_SEC_COORDINATE_EVENT = 0xc1,
    REPORT_SEC_STATUS_EVENT = 0xc2,
};

enum coord_action {
    SEC_TS_COORDINATE_ACTION_NONE = 0,
    SEC_TS_COORDINATE_ACTION_PRESS = 1,
    SEC_TS_COORDINATE_ACTION_MOVE = 2,
    SEC_TS_COORDINATE_ACTION_RELEASE = 3,
    SEC_TS_COORDINATE_ACTION_FORCE_RELEASE = 4,
};

/**
 * Header of TouchComm v1 Message Packet
 * The 4-byte header in the TouchComm v1 packet
 */
struct synaptics_ts_v1_message_header {
    unsigned char marker;
    unsigned char code;
    unsigned char length[2];
};

/**
 * context of SEC touch/coordinate event data
 */
struct sec_touch_event_data {
    union {
        struct {
            unsigned char eid:2;
            unsigned char tid:4;
            unsigned char tchsta:2;
            unsigned char x_11_4;
            unsigned char y_11_4;
            unsigned char y_3_0:4;
            unsigned char x_3_0:4;
            unsigned char major;
            unsigned char minor;
            unsigned char z:6;
            unsigned char ttype_3_2:2;
            unsigned char left_event:5;
            unsigned char max_energy_flag:1;
            unsigned char ttype_1_0:2;
            unsigned char noise_level;
            unsigned char max_strength;
            unsigned char hover_id_num:4;
            unsigned char noise_status:2;
            unsigned char reserved10:2;
            unsigned char reserved_byte11;
            unsigned char reserved_byte12;
            unsigned char reserved_byte13;
            unsigned char reserved_byte14;
            unsigned char reserved_byte15;
        } __attribute__((__packed__));
        unsigned char data[16];
    };
};

struct synaptics_ts_info synaptics_dev;

/**
 * Convert 2-byte data in little-endianness to an unsigned integer
 */
static inline unsigned int synaptics_ts_le2_to_uint(const unsigned char *src)
{
    return (unsigned int)src[0] +
        (unsigned int)src[1] * 0x100;
}

static inline unsigned int synaptics_ts_pal_ceil_div(unsigned int dividend,
        unsigned int divisor)
{
    return (dividend + divisor - 1) / divisor;
}

int synaptics_i2c_read(uint8_t *rd_buff, int rd_size)
{
    TEE_Result ret;
    deviceInfo_t *i2cDev = &board.i2c;
    int try_count = SYNAPTICS_TS_I2C_RETRY_CNT;

    if (!rd_buff || !rd_size) {
        errPrintf("null buffer or wrong size\n");
        return -EINVAL;
    }

    do {
        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 void synaptics_ts_coordinate_event(uint8_t *event_buff)
{
    struct sec_touch_event_data *p_event_coord;
    uint8_t t_id = 0;
    uint16_t x;
    uint16_t y;
    uint8_t ttype;
    static char prev_action[SEC_TS_SUPPORT_TOUCH_COUNT];

    p_event_coord = (struct sec_touch_event_data *)event_buff;
    t_id = p_event_coord->tid;

    if (t_id >= SEC_TS_SUPPORT_TOUCH_COUNT) {
        errPrintf("tid(%d) is out of range\n", t_id);
        return;
    }

    x = (p_event_coord->x_11_4 << 4) | (p_event_coord->x_3_0);
    y = (p_event_coord->y_11_4 << 4) | (p_event_coord->y_3_0);
    ttype = p_event_coord->ttype_3_2 << 2 | p_event_coord->ttype_1_0 << 0;

    /* compute x,y position */
    if (synaptics_dev.max_x != synaptics_dev.width && synaptics_dev.max_x != 0) {
        x = (x * synaptics_dev.width) / synaptics_dev.max_x;
    }
    if (synaptics_dev.max_y != synaptics_dev.height && synaptics_dev.max_y != 0) {
        y =  (y * synaptics_dev.height) / synaptics_dev.max_y;
    }

    if ((ttype == SYNAPTICS_TS_TOUCHTYPE_NORMAL)
            || (ttype == SYNAPTICS_TS_TOUCHTYPE_PALM)
            || (ttype == SYNAPTICS_TS_TOUCHTYPE_WET)
            || (ttype == SYNAPTICS_TS_TOUCHTYPE_GLOVE)) {
        if (p_event_coord->tchsta == SEC_TS_COORDINATE_ACTION_RELEASE) {
            add_touch_event(t_id, x, y, tui_ts_release);
        } else if (p_event_coord->tchsta == SEC_TS_COORDINATE_ACTION_PRESS) {
            add_touch_event(t_id, x, y, tui_ts_press);
        } else if (p_event_coord->tchsta == SEC_TS_COORDINATE_ACTION_MOVE) {
            if (prev_action[t_id] == SEC_TS_COORDINATE_ACTION_NONE ||
                    prev_action[t_id]== SEC_TS_COORDINATE_ACTION_RELEASE) {
                    add_touch_event(t_id, x, y, tui_ts_press);
            }
        } else {
            errPrintf("Unexpected touch event: %x\n", p_event_coord->tchsta);
            prev_action[t_id] = SEC_TS_COORDINATE_ACTION_NONE;
            return;
        }
    } else {
        errPrintf("do not support coordinate type(%d)\n", ttype);
        prev_action[t_id] = SEC_TS_COORDINATE_ACTION_NONE;
        return;
    }
    prev_action[t_id] = p_event_coord->tchsta;
}

static int synaptics_ts_v1_continued_read(unsigned int payload_length, int report_code)
{
    static uint8_t tmp_buff[RD_CHUNK_SIZE];
    static uint8_t buff[RD_CHUNK_SIZE];
    uint8_t *out_buff = buff;
    uint8_t *event_buff;
    unsigned int offset = 0;
    unsigned int total_length;
    unsigned int remaining_length;
    unsigned int chunk_space;
    unsigned int chunks;
    unsigned int idx;
    unsigned char marker;
    unsigned char code;
    unsigned int xfer_length;
    int ret = 0;
    int remain_event_count;
    int curr_pos = 0;

    /*
     * continued read packet contains the header, payload,
     * and a padding
     */
    total_length = SYNAPTICS_TS_MESSAGE_HEADER_SIZE + payload_length + 1;
    remaining_length = total_length - SYNAPTICS_TS_MESSAGE_HEADER_SIZE;

    if (total_length + 1 > RD_CHUNK_SIZE) {
        out_buff = malloc(total_length + 1);
        if (!out_buff) {
            errPrintf("Unable to allocate memory\n");
            return -ENOMEM;
        }
    }

    /*
     * available chunk space for payload =
     *      total chunk size - (header marker byte + header status byte)
     */
    chunk_space = synaptics_dev.max_rd_size - 2;

    chunks = synaptics_ts_pal_ceil_div(remaining_length, chunk_space);
    chunks = chunks == 0 ? 1 : chunks;

    for (idx = 0; idx < chunks; idx++) {
        if (remaining_length > chunk_space)
            xfer_length = chunk_space;
        else
            xfer_length = remaining_length;

        if (xfer_length == 1) {
            out_buff[offset] = SYNAPTICS_TS_V1_MESSAGE_PADDING;
            offset += xfer_length;
            remaining_length -= xfer_length;
            break;
        }

        /*
         * retrieve data from the bus
         * data should include header marker and status code
         */
        ret = synaptics_i2c_read(tmp_buff, xfer_length + 2);
        if (ret < 0) {
            errPrintf("Unable to read data, ret: %d\n", ret);
            goto free;
        }

        /* check the data content */
        marker = tmp_buff[0];
        code = tmp_buff[1];

        if (marker != SYNAPTICS_TS_V1_MESSAGE_MARKER) {
            errPrintf("Incorrect header marker, read: %x, expected: %x\n",
                      marker, SYNAPTICS_TS_V1_MESSAGE_MARKER);
            ret = -EIO;
            goto free;
        }

        if (code != STATUS_CONTINUED_READ) {
            errPrintf("Incorrect status code: %x\n", code);
            ret = -EIO;
            goto free;
        }

        /* copy data from internal buffer */
        memcpy(&out_buff[offset], &tmp_buff[2], xfer_length);

        offset += xfer_length;
        remaining_length -= xfer_length;
    }

    /*
     * Hosts should always read one byte beyond the payload.
     * If this byte is not $5A, the host should discard the packet.
     */
    if (out_buff[offset - 1] != SYNAPTICS_TS_V1_MESSAGE_PADDING) {
        errPrintf("Unexpected padding\n");
        ret = 0;
        goto free;
    }

    if (report_code != REPORT_SEC_COORDINATE_EVENT) {
        dbgPrintf("Not supported status/report code: %x\n", report_code);
        ret = 0;
        goto free;
    }

    remain_event_count = payload_length / sizeof(struct sec_touch_event_data);

    if (get_touch_queue_wsize() < (remain_event_count + 1)) {
        errPrintf("Too small queue size\n");
        goto free;
    }

    do {
        event_buff = &out_buff[curr_pos * sizeof(struct sec_touch_event_data)];
        synaptics_ts_coordinate_event(event_buff);
        curr_pos++;
        remain_event_count--;
    } while (remain_event_count > 0);

free:
    if (out_buff != buff) {
        free(out_buff);
    }

    return ret;
}

/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
int synaptics_irq_process(void)
{
    struct synaptics_ts_v1_message_header *header;
    uint8_t header_buff[SYNAPTICS_TS_MESSAGE_HEADER_SIZE];
    int ret;
    unsigned int payload_length;

    ret = synaptics_i2c_read(header_buff, SYNAPTICS_TS_MESSAGE_HEADER_SIZE);
    if (ret) {
        errPrintf("Read header failed, ret: %d\n", ret);
        return ret;
    }

    /* check the message header */
    header = (struct synaptics_ts_v1_message_header *)header_buff;
    if (header->marker != SYNAPTICS_TS_V1_MESSAGE_MARKER) {
        errPrintf("Incorrect header marker, read: %x, expected: %x\n",
                  header->marker, SYNAPTICS_TS_V1_MESSAGE_MARKER);
        return -1;
    }

    payload_length = synaptics_ts_le2_to_uint(header->length);

    if (header->code == STATUS_CONTINUED_READ)
    {
        errPrintf("Out-of-sync continued read\n");
        return 0;
    }

    if (header->code == STATUS_IDLE) {
        return 0;
    }

    if (payload_length == 0) {
        dbgPrintf("No data available\n");
        return 0;
    }

    /* retrieve the remaining data */
    return synaptics_ts_v1_continued_read(payload_length, header->code);
}

/**
 * Initialize synaptics device driver
 * @param[in] synaptics_dev synaptics device pointer
 * @return error status
 */
int synaptics_driver_init(struct synaptics_ts_info *synaptics_dev)
{
    synaptics_dev->max_x = SYNAPTICS_MAX_X;
    synaptics_dev->max_y = SYNAPTICS_MAX_Y;

    synaptics_dev->max_rd_size = RD_CHUNK_SIZE;
    init_touch_queue();

    return 0;
}

/**
 * Uninitialize synaptics device driver
 * @return error status
 */
int synaptics_driver_release(void)
{
    return 0;
}
