/*
 *
 * Copyright (C) 2012-2019, Samsung Electronics Co., Ltd.
 *
 * RMI4 func.0x11 implementation
 */

#include "rmi.h"
#include "rmi_driver.h"

#define F11_MAX_NUM_OF_SENSORS                 16 /* 8 */
#define F11_MAX_NUM_OF_FINGERS                 10
#define F11_MAX_NUM_OF_TOUCH_SHAPES            16

#define F11_REL_POS_MIN                        -128
#define F11_REL_POS_MAX                        127

#define F11_FINGER_STATE_MASK                  0x03
#define F11_FINGER_STATE_SIZE                  0x02
#define F11_FINGER_STATE_MASK_N(i)             (F11_FINGER_STATE_MASK << (i%4 * F11_FINGER_STATE_SIZE))

#define F11_FINGER_STATE_VAL_N(f_state, i)     (f_state >> (i%4 * F11_FINGER_STATE_SIZE))

#define F11_TOUCH_SHAPE_MASK                   0x01
#define F11_TOUCH_SHAPE_SIZE                   0x01

#define F11_CTRL_SENSOR_MAX_X_POS_OFFSET       6
#define F11_CTRL_SENSOR_MAX_Y_POS_OFFSET       8

#define F11_CEIL(x, y) (((x) + ((y)-1)) / (y))

#define GET_MIN(x, y) (((x) <= (y)) ? (x) : (y))
#define GET_MAX(x, y) (((x) >= (y)) ? (x) : (y))
#define DO_SWAP(x, y, tmp) { tmp = x; x = y; y = tmp; }

/**
 * struct f11_axis_alignmen - target axis alignment
 * @swap_axes: set to TRUE if desired to swap x- and y-axis
 * @flip_x: set to TRUE if desired to flip direction on x-axis
 * @flip_y: set to TRUE if desired to flip direction on y-axis
 */
struct f11_2d_axis_alignment {
    bool swap_axes;
    bool flip_x;
    bool flip_y;
};

/**
 * RMI F11 - function control register parameters
 * Each register that has a specific bit-field setup has an accompanied
 * register definition so that the setting can be chosen as a one-word
 * register setting or per-bit setting.
 */
union f11_2d_ctrl0 {
    struct {
        uint8_t reporting_mode:3;
        uint8_t abs_pos_filt:1;
        uint8_t rel_pos_filt:1;
        uint8_t rel_ballistics:1;
        uint8_t dribble:1;
    };
    uint8_t reg;
};

union f11_2d_ctrl1 {
    struct {
        uint8_t palm_detect_thres:4;
        uint8_t motion_sensitivity:2;
        uint8_t man_track_en:1;
        uint8_t man_tracked_finger:1;
    };
    uint8_t reg;
};

union f11_2d_ctrl6__7 {
    struct {
        uint16_t sensor_max_x_pos:12;
    };
    uint8_t regs[2];
};

union f11_2d_ctrl8__9 {
    struct {
        uint16_t sensor_max_y_pos:12;
    };
    uint8_t regs[2];
};

union f11_2d_ctrl10 {
    struct {
        uint8_t single_tap_int_enable:1;
        uint8_t tap_n_hold_int_enable:1;
        uint8_t double_tap_int_enable:1;
        uint8_t early_tap_int_enable:1;
        uint8_t flick_int_enable:1;
        uint8_t press_int_enable:1;
        uint8_t pinch_int_enable:1;
    };
    uint8_t reg;
};

union f11_2d_ctrl11 {
    struct {
        uint8_t palm_detect_int_enable:1;
        uint8_t rotate_int_enable:1;
        uint8_t touch_shape_int_enable:1;
        uint8_t scroll_zone_int_enable:1;
        uint8_t multi_finger_scroll_int_enable:1;
    };
    uint8_t reg;
};

union f11_2d_ctrl12 {
    struct {
        uint8_t sensor_map:7;
        uint8_t xy_sel:1;
    };
    uint8_t reg;
};

union f11_2d_ctrl14 {
    struct {
        uint8_t sens_adjustment:5;
        uint8_t hyst_adjustment:3;
    };
    uint8_t reg;
};

/* The configuation is controlled as per register which means that if a register
 * is allocated for ctrl configuration one must make sure that all the bits are
 * set accordingly for that particular register.
 */
struct  f11_2d_ctrl {
    union f11_2d_ctrl0     *ctrl0;
    union f11_2d_ctrl1     *ctrl1;
    uint8_t                *ctrl2;
    uint8_t                *ctrl3;
    uint8_t                *ctrl4;
    uint8_t                *ctrl5;
    union f11_2d_ctrl6__7  *ctrl6__7;
    union f11_2d_ctrl8__9  *ctrl8__9;
    union f11_2d_ctrl10    *ctrl10;
    union f11_2d_ctrl11    *ctrl11;
    union f11_2d_ctrl12    *ctrl12;
    uint8_t                 ctrl12_size;
    union f11_2d_ctrl14    *ctrl14;
    uint8_t                *ctrl15;
    uint8_t                *ctrl16;
    uint8_t                *ctrl17;
    uint8_t                *ctrl18;
    uint8_t                *ctrl19;
};

struct f11_2d_device_query {
    union {
        struct {
            uint8_t nbr_of_touches:3;
            uint8_t has_query9:1;
            uint8_t has_query11:1;
        };
        uint8_t f11_2d_query0;
    };

    uint8_t f11_2d_query9;

    union {
        struct {
            uint8_t has_z_tuning:1;
            uint8_t has_pos_interpolation_tuning:1;
            uint8_t has_w_tuning:1;
            uint8_t has_pitch_info:1;
            uint8_t has_default_finger_width:1;
            uint8_t has_segmentation_aggressiveness:1;
            uint8_t has_tx_rw_clip:1;
            uint8_t has_drumming_correction:1;
        };
        uint8_t f11_2d_query11;
    };
};

struct qf11_2d_touch_query {
    union {
        struct {
            /* query1 */
            uint8_t number_of_fingers:3;
            uint8_t has_rel:1;
            uint8_t has_abs:1;
            uint8_t has_gestures:1;
            uint8_t has_sensitivity_adjust:1;
            uint8_t configurable:1;
            /* query2 */
            uint8_t num_of_x_electrodes:7;
            /* query3 */
            uint8_t num_of_y_electrodes:7;
            /* query4 */
            uint8_t max_electrodes:7;
        };
        uint8_t f11_2d_query1__4[4];
    };

    union {
        struct {
            uint8_t abs_data_size:3;
            uint8_t has_anchored_finger:1;
            uint8_t has_adj_hyst:1;
            uint8_t has_dribble:1;
        };
        uint8_t f11_2d_query5;
    };

    uint8_t f11_2d_query6;

    union {
        struct {
            uint8_t has_single_tap:1;
            uint8_t has_tap_n_hold:1;
            uint8_t has_double_tap:1;
            uint8_t has_early_tap:1;
            uint8_t has_flick:1;
            uint8_t has_press:1;
            uint8_t has_pinch:1;
            uint8_t padding:1;

            uint8_t has_palm_det:1;
            uint8_t has_rotate:1;
            uint8_t has_touch_shapes:1;
            uint8_t has_scroll_zones:1;
            uint8_t has_individual_scroll_zones:1;
            uint8_t has_multi_finger_scroll:1;
        };
        uint8_t f11_2d_query7__8[2];
    };

    /* Empty */
    uint8_t f11_2d_query9;

    union {
        struct {
            uint8_t nbr_touch_shapes:5;
        };
        uint8_t f11_2d_query10;
    };
};

struct f11_2d_data_0 {
    uint8_t finger_n;
};

struct f11_2d_data_1_5 {
    uint8_t x_msb;
    uint8_t y_msb;
    uint8_t x_lsb:4;
    uint8_t y_lsb:4;
    uint8_t w_y:4;
    uint8_t w_x:4;
    uint8_t z;
};

struct f11_2d_data_6_7 {
    int8_t delta_x;
    int8_t delta_y;
};

struct f11_2d_data_8 {
    uint8_t single_tap:1;
    uint8_t tap_and_hold:1;
    uint8_t double_tap:1;
    uint8_t early_tap:1;
    uint8_t flick:1;
    uint8_t press:1;
    uint8_t pinch:1;
};

struct f11_2d_data_9 {
    uint8_t palm_detect:1;
    uint8_t rotate:1;
    uint8_t shape:1;
    uint8_t scrollzone:1;
    uint8_t finger_count:3;
};

struct f11_2d_data_10 {
    uint8_t pinch_motion;
};

struct f11_2d_data_10_12 {
    uint8_t x_flick_dist;
    uint8_t y_flick_dist;
    uint8_t flick_time;
};

struct f11_2d_data_11_12 {
    uint8_t motion;
    uint8_t finger_separation;
};

struct f11_2d_data_13 {
    uint8_t shape_n;
};

struct f11_2d_data_14_15 {
    uint8_t horizontal;
    uint8_t vertical;
};

struct f11_2d_data_14_17 {
    uint8_t x_low;
    uint8_t y_right;
    uint8_t x_upper;
    uint8_t y_left;
};

struct f11_2d_data {
    const struct f11_2d_data_0     *f_state;
    const struct f11_2d_data_1_5   *abs_pos;
    const struct f11_2d_data_6_7   *rel_pos;
    const struct f11_2d_data_8     *gest_1;
    const struct f11_2d_data_9     *gest_2;
    const struct f11_2d_data_10    *pinch;
    const struct f11_2d_data_10_12 *flick;
    const struct f11_2d_data_11_12 *rotate;
    const struct f11_2d_data_13    *shapes;
    const struct f11_2d_data_14_15 *multi_scroll;
    const struct f11_2d_data_14_17 *scroll_zones;
};

struct f11_2d_touch {
    struct f11_2d_axis_alignment axis_align;
    struct qf11_2d_touch_query sens_query;
    struct f11_2d_data data;
    uint16_t max_x;
    uint16_t max_y;
    uint8_t nbr_fingers;
    uint8_t finger_tracker[F11_MAX_NUM_OF_FINGERS];
    uint8_t *data_pkt;
    int pkt_size;
};

struct f11_data {
    struct f11_2d_device_query dev_query;
    struct f11_2d_touch touches[F11_MAX_NUM_OF_SENSORS];
};

static int rmi_f11_init(struct rmi_function_container *fc);
static int rmi_f11_attention(rmi_function_container_t *fc, uint8_t irq_bits);
static void rmi_f11_remove(rmi_function_container_t *fc);

static rmi_function_container_t function_handler = {
    .func      = 0x11,
    .init      = rmi_f11_init,
    .attention = rmi_f11_attention,
    .remove    = rmi_f11_remove
};

static void rmi_f11_rel_pos_report(struct f11_2d_touch *touch, uint8_t n_finger)
{
    struct f11_2d_data *data = &touch->data;
    struct f11_2d_axis_alignment *axis_align = &touch->axis_align;
    int8_t x, y;
    int8_t temp;

    x = data->rel_pos[n_finger].delta_x;
    y = data->rel_pos[n_finger].delta_y;

    x = GET_MIN(F11_REL_POS_MAX, GET_MAX(F11_REL_POS_MIN, (int)x));
    y = GET_MIN(F11_REL_POS_MAX, GET_MAX(F11_REL_POS_MIN, (int)y));

    if (axis_align->swap_axes)
        DO_SWAP(x, y, temp);
}

static void rmi_f11_abs_pos_report(struct f11_2d_touch *touch, uint8_t state, uint8_t n_finger)
{
    struct f11_2d_data *data = &touch->data;
    struct f11_2d_axis_alignment *axis_align = &touch->axis_align;
    int prev_state = touch->finger_tracker[n_finger];
    int x = 0, y = 0;

    if (prev_state && !state)
        add_touch_event(n_finger, x, y, tui_ts_release); /* this is a release */
    else if (!prev_state && !state)
        return; /* nothing to report */
    else {
        x = ((data->abs_pos[n_finger].x_msb << 4) | data->abs_pos[n_finger].x_lsb);
        y = ((data->abs_pos[n_finger].y_msb << 4) | data->abs_pos[n_finger].y_lsb);
        /* z = data->abs_pos[n_finger].z;
                 * int w_x = data->abs_pos[n_finger].w_x;
                 * int w_y = data->abs_pos[n_finger].w_y;
                 * w_max = GET_MAX(w_x, w_y);
                 * w_min = GET_MIN(w_x, w_y); */

        if (axis_align->swap_axes) {
            int temp;
            DO_SWAP(x, y, temp);
            /*DO_SWAP(w_x, w_y, temp);*/
        }

        /*orient = w_x > w_y ? 1 : 0;*/
        if (axis_align->flip_x)
            x = GET_MAX(touch->max_x - x, 0);
        if (axis_align->flip_y)
            y = GET_MAX(touch->max_y - y, 0);
    }
    add_touch_event(n_finger, x, y, (prev_state ? tui_ts_update : tui_ts_press));
    touch->finger_tracker[n_finger] = state;
}

static void rmi_f11_finger_handler(struct f11_2d_touch *touch)
{
    const struct f11_2d_data_0 *f_state = touch->data.f_state;
    uint8_t state;
    uint8_t i;

    for (i = 0; i < touch->nbr_fingers; i++) {
        /* Possible of having 4 fingers per f_statet register */
        state = (f_state[i >> 2].finger_n & F11_FINGER_STATE_MASK_N(i));
        state = F11_FINGER_STATE_VAL_N(state, i);

        if (touch->data.abs_pos)
            rmi_f11_abs_pos_report(touch, state, i);
        if (touch->data.rel_pos)
            rmi_f11_rel_pos_report(touch, i);
    }
}

static int rmi_f11_2d_construct_data(struct f11_2d_touch *touch)
{
    struct qf11_2d_touch_query *q = &touch->sens_query;
    struct f11_2d_data *data = &touch->data;
    int i;

    touch->nbr_fingers = (q->number_of_fingers == 5 ? 10 : q->number_of_fingers + 1);
    touch->pkt_size = F11_CEIL(touch->nbr_fingers, 4);

    if (q->has_abs)
        touch->pkt_size += (touch->nbr_fingers * 5);

    if (q->has_rel)
        touch->pkt_size +=  (touch->nbr_fingers * 2);

    /* Check if F11_2D_Query7 is non-zero */
    if (q->f11_2d_query7__8[0] != 0)
        touch->pkt_size += sizeof(uint8_t);

    /* Check if F11_2D_Query7 or F11_2D_Query8 is non-zero */
    if (q->f11_2d_query7__8[0] != 0 || q->f11_2d_query7__8[1] != 0)
        touch->pkt_size += sizeof(uint8_t);

    if (q->has_pinch || q->has_flick || q->has_rotate) {
        touch->pkt_size += 3;
        if (!q->has_flick)
            touch->pkt_size--;
        if (!q->has_rotate)
            touch->pkt_size--;
    }

    if (q->has_touch_shapes)
        touch->pkt_size += F11_CEIL(q->nbr_touch_shapes + 1, 8);

    touch->data_pkt = TEE_Malloc(touch->pkt_size, 0);
    if (!touch->data_pkt) {
        touch->pkt_size = 0;
        return ERROR_RMI_NOMEM;
    }

    data->f_state = (struct f11_2d_data_0 *)touch->data_pkt;
    i = F11_CEIL(touch->nbr_fingers, 4);

    if (q->has_abs) {
        data->abs_pos = (struct f11_2d_data_1_5 *)&touch->data_pkt[i];
        i += (touch->nbr_fingers * 5);
    }

    if (q->has_rel) {
        data->rel_pos = (struct f11_2d_data_6_7 *)&touch->data_pkt[i];
        i += (touch->nbr_fingers * 2);
    }

    if (q->f11_2d_query7__8[0])	{
        data->gest_1 = (struct f11_2d_data_8 *)&touch->data_pkt[i];
        i++;
    }

    if (q->f11_2d_query7__8[0] || q->f11_2d_query7__8[1]) {
        data->gest_2 = (struct f11_2d_data_9 *)&touch->data_pkt[i];
        i++;
    }

    if (q->has_pinch) {
        data->pinch = (struct f11_2d_data_10 *)&touch->data_pkt[i];
        i++;
    }

    if (q->has_flick) {
        if (q->has_pinch) {
            data->flick = (struct f11_2d_data_10_12 *)data->pinch;
            i += 2;
        } else {
            data->flick = (struct f11_2d_data_10_12 *)&touch->data_pkt[i];
            i += 3;
        }
    }

    if (q->has_rotate) {
        if (q->has_flick)
            data->rotate = (struct f11_2d_data_11_12 *)(data->flick + 1);
        else {
            data->rotate = (struct f11_2d_data_11_12 *)&touch->data_pkt[i];
            i += 2;
        }
    }

    if (q->has_touch_shapes)
        data->shapes = (struct f11_2d_data_13 *)&touch->data_pkt[i];

    return 0;
}

static void rmi_f11_2d_free_data(struct f11_2d_touch *touch)
{
    if (touch)	{
        if (touch->data_pkt) {
            TEE_Free(touch->data_pkt);
            touch->data_pkt = NULL;
            touch->pkt_size = 0;
        }
    }
}

static int rmi_f11_get_query_parameters(rmi_device_t *rmi_dev,
                        struct qf11_2d_touch_query *query, uint8_t query_base_addr)
{
    int query_size;
    int rc;

    rc = rmi_read_block(rmi_dev, query_base_addr, query->f11_2d_query1__4, sizeof(query->f11_2d_query1__4));
    if (rc < 0)
        return rc;

    query_size = rc;
    if (query->has_abs)
        rc = rmi_read_byte(rmi_dev, query_base_addr + query_size, &query->f11_2d_query5);
    if (rc < 0)
        return rc;

    query_size++;
    if (query->has_rel)
        rc = rmi_read_byte(rmi_dev, query_base_addr + query_size, &query->f11_2d_query6);
    if (rc < 0)
        return rc;

    query_size++;
    if (query->has_gestures)
        rc = rmi_read_block(rmi_dev, query_base_addr + query_size, query->f11_2d_query7__8,
                            sizeof(query->f11_2d_query7__8));
    if (rc < 0)
        return rc;

    query_size += rc;
    if (query->has_touch_shapes) {
        rc = rmi_read_byte(rmi_dev, query_base_addr + query_size, &query->f11_2d_query10);
        query_size++;
    }
    if (rc < 0)
        return rc;

    return query_size;
}

static int rmi_f11_init(struct rmi_function_container *fc)
{
    struct rmi_device *rmi_dev = fc->rmi_dev;
    struct f11_data *f11;
    uint8_t query_offset;
    uint8_t query_base_addr;
    uint8_t control_base_addr;
    uint16_t max_x_pos, max_y_pos;
    int rc;
    int i;

    f11 = TEE_Malloc(sizeof(struct f11_data), 0);
    if (!f11)
        return ERROR_RMI_NOMEM;

    query_base_addr = fc->fd.query_base_addr;
    control_base_addr = fc->fd.control_base_addr;

    rc = rmi_read_byte(rmi_dev, query_base_addr, &f11->dev_query.f11_2d_query0);
    if (rc < 0)
        goto err_free_data;

    query_offset = (query_base_addr + 1);
    /* Increase with one since number of sensors is zero based */
    for (i = 0; i < (f11->dev_query.nbr_of_touches + 1); i++) {
        rc = rmi_f11_get_query_parameters(rmi_dev, &f11->touches[i].sens_query, query_offset);
        if (rc < 0)
            goto err_free_data;

        query_offset += rc;

        rc = rmi_read_block(rmi_dev, control_base_addr + F11_CTRL_SENSOR_MAX_X_POS_OFFSET, (uint8_t *)&max_x_pos, sizeof(max_x_pos));
        if (rc < 0)
            goto err_free_data;

        rc = rmi_read_block(rmi_dev, control_base_addr + F11_CTRL_SENSOR_MAX_Y_POS_OFFSET, (uint8_t *)&max_y_pos, sizeof(max_y_pos));
        if (rc < 0)
            goto err_free_data;

        f11->touches[i].max_x = max_x_pos;
        f11->touches[i].max_y = max_y_pos;
        rmi_dev->max_x = max_x_pos;
        rmi_dev->max_y = max_y_pos;
        RMI_DBG("%s() : Function %02x max x = %d max y = %d", __func__, (int)fc->func, rmi_dev->max_x, rmi_dev->max_y);

        rc = rmi_f11_2d_construct_data(&f11->touches[i]);
        if (rc < 0)
            goto err_free_data;
    }

    fc->data = f11;
    RMI_DBG("F11 Function init done.");
    return 0;

err_free_data:
    if (f11) {
        TEE_Free(f11);
    }
    fc->data = NULL;
    return rc;
}

static int rmi_f11_attention(rmi_function_container_t *fc, uint8_t irq_bits)
{
    struct rmi_device *rmi_dev = fc->rmi_dev;
    struct f11_data *f11 = (struct f11_data *)fc->data;
    uint16_t data_base_addr = fc->fd.data_base_addr;
    int data_base_addr_offset = 0;
    int i;

    (void)irq_bits;

    for (i = 0; i < (f11->dev_query.nbr_of_touches + 1); i++) {
        int error = rmi_read_block(rmi_dev,
                                   data_base_addr + data_base_addr_offset,
                                   f11->touches[i].data_pkt,
                                   f11->touches[i].pkt_size);
        if (error <= 0)
            return (!error) ? ERROR_RMI_FAILED : error;

        rmi_f11_finger_handler(&f11->touches[i]);
        data_base_addr_offset += f11->touches[i].pkt_size;
    }
    return 0;
}

static void rmi_f11_remove(rmi_function_container_t *fc)
{
    struct f11_data *data = (struct f11_data *)fc->data;

    if (data) {
        int i;

        for (i = 0; i < (data->dev_query.nbr_of_touches + 1); i++)
            rmi_f11_2d_free_data(&data->touches[i]);

        TEE_Free(data);
        fc->data = NULL;
    }
}

int rmi_fn11_register(rmi_device_t *rmi_dev, rmi_function_descriptor_t *fd)
{
    int error;

    error = rmi_bus_register_function(rmi_dev, &function_handler, fd);
    if (error < 0) {
        RMI_ERR("registration failed. err = %d", error);
        return error;
    }
    return 0;
}
