/*
 *
 * Copyright (C) 2012-2019, Samsung Electronics Co., Ltd.
 *
 * RMI4 compatible touch device implementation
 */

/******************************************************************************
 * INCLUDE
 ******************************************************************************/

#include "rmi_driver.h"

/******************************************************************************
 * LOCAL DEFINITIONS
 ******************************************************************************/

#define PDT_START_SCAN_LOCATION 0x00e9
#define PDT_END_SCAN_LOCATION   0x0005

#define RMI4_END_OF_PDT(id)     ((id) == 0x00 || (id) == 0xff)
#define RMI4_MAX_PAGE           0xff
#define RMI4_PAGE_SIZE          0x100

/******************************************************************************
 * LOCAL TYPES
 ******************************************************************************/

/******************************************************************************
 * LOCAL DATA
 ******************************************************************************/

rmi_device_t tui_ts_dev;
static const uint8_t rmi_fn_ids[3] = {0x01, 0x11, 0x12};

/******************************************************************************
 * EXPORTED FUNCTIONS
 ******************************************************************************/

/**
 * Get touch data from controller and push it to the touch queue
 * @return error status
 */
int rmi_irq_process(void)
{
    rmi_function_container_t *fn_ptr;
    uint8_t irq_status = 0;
    int irq_bits, i;
    int error;

    /* for each function */
    for (i = 0; i < (int)(sizeof(rmi_fn_ids) / sizeof(rmi_fn_ids[0])); i++) {
        int fn_id = rmi_fn_ids[i];

        fn_ptr = rmi_bus_get_function(&tui_ts_dev, fn_id);
        if (fn_id == 0x01) {
            if (!fn_ptr) {
                return ERROR_RMI_FAILED;
            }
            error = rmi_read_byte(&tui_ts_dev, fn_ptr->fd.data_base_addr + 1, &irq_status);
            if (error < 0) {
                return error;
            }
        } else if (!fn_ptr) {
            continue;
        }

        irq_bits = (irq_status >> fn_ptr->fd.irq_pos) & ((1 << fn_ptr->fd.num_of_irqs) - 1);
        if (irq_bits && fn_ptr->attention) {
            error = fn_ptr->attention(fn_ptr, (uint8_t)irq_bits);
            if (error < 0) {
                RMI_ERR("f%02x attention handler failed: %d", fn_ptr->func, error);
                return error;
            }
        }
    }
    return 0;
}

/**
 * Initialize RMI4 device driver
 * @param[in] rmi_dev rmi device pointer
 * @return error status
 */
int rmi_driver_init(rmi_device_t *rmi_dev)
{
    struct pdt_entry_s {
        uint8_t query_base_addr;
        uint8_t command_base_addr;
        uint8_t control_base_addr;
        uint8_t data_base_addr;
        uint8_t interrupt_source_count;
        uint8_t function_number;
    } PACKED_STRUCT pdt_entry;
    rmi_function_descriptor_t fd;
    int error;
    int i;
    int page;
    int irq_count = 0;
    bool done = false;
    void *fn1_ptr, *fn2_ptr;

    fn1_ptr = rmi_dev->i2c_read;
    fn2_ptr = rmi_dev->i2c_write;
    TEE_MemFill(rmi_dev, 0, sizeof(rmi_device_t));
    rmi_dev->i2c_read = fn1_ptr;
    rmi_dev->i2c_write = fn2_ptr;

    if (!fn1_ptr || !fn2_ptr) {
        return ERROR_RMI_INVAL;
    }
    error = rmi_transport_init(rmi_dev);
    if (error < 0) {
        return error;
    }

    /* parse the PDT */
    for (page = 0; (page <= RMI4_MAX_PAGE) && !done; page++) {
        uint16_t page_start = RMI4_PAGE_SIZE * page;
        uint16_t pdt_start = page_start + PDT_START_SCAN_LOCATION;
        uint16_t pdt_end = page_start + PDT_END_SCAN_LOCATION;
        done = true;
        for (i = pdt_start; i >= pdt_end; i -= sizeof(pdt_entry)) {
            error = rmi_read_block(rmi_dev, i, (uint8_t *)&pdt_entry, sizeof(pdt_entry));
            if (error != sizeof(pdt_entry))
                goto err_free_data;
            if (RMI4_END_OF_PDT(pdt_entry.function_number)) {
                break;
            }
            done = false;

            fd.query_base_addr = pdt_entry.query_base_addr + page_start;
            fd.command_base_addr = pdt_entry.command_base_addr + page_start;
            fd.control_base_addr = pdt_entry.control_base_addr + page_start;
            fd.data_base_addr = pdt_entry.data_base_addr + page_start;
            fd.function_number = pdt_entry.function_number;
            fd.interrupt_source_count = pdt_entry.interrupt_source_count;

            fd.num_of_irqs = pdt_entry.interrupt_source_count & 0x07;
            fd.irq_pos = irq_count;
            irq_count += fd.num_of_irqs;

            switch (pdt_entry.function_number) {
            case 0x01:
                error = rmi_fn01_register(rmi_dev, &fd);
                if (error != 0) {
                    goto err_free_data;
                }
                break;
            case 0x11:
                error = rmi_fn11_register(rmi_dev, &fd);
                if (error != 0) {
                    goto err_free_data;
                }
                break;
            case 0x12:
                error = rmi_fn12_register(rmi_dev, &fd);
                if (error != 0) {
                    goto err_free_data;
                }
                break;
            }
        }
    }

    if (!rmi_bus_get_function(rmi_dev, 0x01)) {
        RMI_ERR("missing f01 function!");
        error = ERROR_RMI_FAILED;
        goto err_free_data;
    }

    init_touch_queue();

    RMI_DBG("RMI device manufacturer: %s, product: %s",
            (rmi_dev->manufacturer_id == 1) ? "synaptics" : "unknown",
            rmi_dev->product_id);
    return 0;

err_free_data:
    rmi_bus_unregister_all(rmi_dev);
    return error;
}

/**
 * Uninitialize RMI4 device driver
 * @param[in] rmi_dev rmi device pointer
 * @return error status
 */
int rmi_driver_remove(rmi_device_t *rmi_dev)
{
    rmi_bus_unregister_all(rmi_dev);
    return 0;
}
