From ed034f67c11fa2dc93b777ba12a4770af8567ce4 Mon Sep 17 00:00:00 2001 From: Du, Dudley Date: Fri, 4 Mar 2011 15:03:51 -0500 Subject: [PATCH] Add new i2c-based input mouse driver for Cypress i2c-based trackpad devices into input subsystem. Change-Id: I108ad1505f3d34ab24658921a772e937940a1b88 Signed-off-by: Du, Dudley --- drivers/input/mouse/Kconfig | 11 + drivers/input/mouse/Makefile | 1 + drivers/input/mouse/cypress_i2c.c | 3463 +++++++++++++++++++++++++++++++++++++ include/linux/cyapa.h | 60 + 4 files changed, 3535 insertions(+), 0 deletions(-) create mode 100644 drivers/input/mouse/cypress_i2c.c create mode 100644 include/linux/cyapa.h diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig index bf5fd7f..845d05a 100644 --- a/drivers/input/mouse/Kconfig +++ b/drivers/input/mouse/Kconfig @@ -322,4 +322,15 @@ config MOUSE_SYNAPTICS_I2C To compile this driver as a module, choose M here: the module will be called synaptics_i2c. +config MOUSE_CYPRESS_I2C + tristate "Cypress I2C Touchpad support" + depends on I2C + help + This driver supports Cypress I2C touchpad on Chrome OS platform. + + Say y here if you have Chrome OS that support Cypress I2C Touchpad. + + To compile this driver as a module, choose M here: the + module will be called cypress_i2c. + endif diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile index 570c84a..706f261 100644 --- a/drivers/input/mouse/Makefile +++ b/drivers/input/mouse/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_MOUSE_PXA930_TRKBALL) += pxa930_trkball.o obj-$(CONFIG_MOUSE_RISCPC) += rpcmouse.o obj-$(CONFIG_MOUSE_SERIAL) += sermouse.o obj-$(CONFIG_MOUSE_SYNAPTICS_I2C) += synaptics_i2c.o +obj-$(CONFIG_MOUSE_CYPRESS_I2C) += cypress_i2c.o obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o psmouse-objs := psmouse-base.o synaptics.o diff --git a/drivers/input/mouse/cypress_i2c.c b/drivers/input/mouse/cypress_i2c.c new file mode 100644 index 0000000..e794691 --- /dev/null +++ b/drivers/input/mouse/cypress_i2c.c @@ -0,0 +1,3463 @@ +/* + * Cypress APA touchpad with I2C interface + * + * Copyright (C) 2009 Compulab, Ltd. + * Dudley Du + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/* Debug macro */ +#define CYAPA_DBG 0 +#if CYAPA_DBG + #define DBGPRINTK(x) printk x + #define DBG_CYAPA_READ_BLOCK_DATA +#else + #define DBGPRINTK(x) +#endif + +#define CYAPA_USE_I2C_REG_WRITE_BLOCK 0 + +/* Cypress I2C APA trackpad driver version is defined as bellow: +** CYAPA_MAJOR_VER.CYAPA_MINOR_VER.CYAPA_REVISIOIN_VER . */ +#define CYAPA_MAJOR_VER 0 +#define CYAPA_MINOR_VER 9 +#define CYAPA_REVISIOIN_VER 4 + +#define CYAPA_FILTER_EMPTY_DATA 0x7FFFFFFF + +/* macro definication for gestures. */ +/* --------------------------------------------------------------- */ +/* |- bit 7 - 5 -|- bit 4 -0 -| */ +/* |------------------------------|----------------------------- | */ +/* |- finger number -|- gesture id -| */ +/* --------------------------------------------------------------- */ +#define GESTURE_FINGERS(x) ((((x) & 0x07) << 5) & 0xE0) +#define GESTURE_INDEX(x) (((x) & 0x1F)) +#define GESTURE_ID_CODE(finger, index) \ + (GESTURE_FINGERS(finger) | GESTURE_INDEX(index)) + +#define GESTURE_NONE 0x00 +/* 0-finger gestures. */ +#define GESTURE_PALM_REJECTIOIN GESTURE_ID_CODE(0, 1) +/* 1-finger gestures. */ +#define GESTURE_SINGLE_TAP GESTURE_ID_CODE(1, 0) +#define GESTURE_DOUBLE_TAP GESTURE_ID_CODE(1, 1) +/* +** one finger click and hold for more than definitioin time, +** then to do something. +*/ +#define GESTURE_TAP_AND_HOLD GESTURE_ID_CODE(1, 2) +#define GESTURE_EDGE_MOTION GESTURE_ID_CODE(1, 3) +#define GESTURE_FLICK GESTURE_ID_CODE(1, 4) +/* GESTURE_DRAG : double click and hold, then move for drag.*/ +#define GESTURE_DRAG GESTURE_ID_CODE(1, 5) +/* Depending on PSOC user module, it will give four different ID when scroll.*/ +#define GESTURE_SCROLL_UP GESTURE_ID_CODE(1, 6) +#define GESTURE_SCROLL_DOWN GESTURE_ID_CODE(1, 7) +#define GESTURE_SCROLL_LEFT GESTURE_ID_CODE(1, 8) +#define GESTURE_SCROLL_RIGHT GESTURE_ID_CODE(1, 9) + +/* 2-finger gestures */ +#define GESTURE_2F_ZOOM_IN GESTURE_ID_CODE(2, 0) +#define GESTURE_2F_ZOOM_OUT GESTURE_ID_CODE(2, 1) +#define GESTURE_2F_SCROLL_UP GESTURE_ID_CODE(2, 2) +#define GESTURE_2F_SCROLL_DOWN GESTURE_ID_CODE(2, 3) +#define GESTURE_2F_SCROLL_LEFT GESTURE_ID_CODE(2, 4) +#define GESTURE_2F_SCROLL_RIGHT GESTURE_ID_CODE(2, 5) +#define GESTURE_2F_ROTATE GESTURE_ID_CODE(2, 6) +#define GESTURE_2F_PINCH GESTURE_ID_CODE(2, 7) +/* Activates the Right Click action */ +#define GESTURE_2F_TAP GESTURE_ID_CODE(2, 8) +/* Single-Finger click and hold while a second finger is moving for dragging. */ +#define GESTURE_2F_DRAG GESTURE_ID_CODE(2, 9) +#define GESTURE_2F_FLICK GESTURE_ID_CODE(2, 10) + +/* 3-finger gestures */ +#define GESTURE_3F_FLICK GESTURE_ID_CODE(3, 0) + +/* 4-finger gestures */ +#define GESTURE_4F_FLICK GESTURE_ID_CODE(4, 0) + +/* 5-finger gestures */ +#define GESTURE_5F_FLICK GESTURE_ID_CODE(5, 0) + +/* swith of the gesture, */ +#define GESTURE_MULTI_TOUCH_ONE_CLICK 0 + +#define GESTURE_DECODE_FINGERS(x) (((x) >> 5) & 0x07) +#define GESTURE_DECODE_INDEX(x) ((x) & 0x1F) + +/* max gesture index value for each fingers type is 31. 0~21.*/ +#define MAX_FINGERS 5 + + +/* parameter value for input_report_key(BTN_TOOL_WIDTH) */ +#define CYAPA_TOOL_WIDTH 50 + +/* When in IRQ mode read the device every THREAD_IRQ_SLEEP_SECS */ +#define CYAPA_THREAD_IRQ_SLEEP_SECS 2 +#define CYAPA_THREAD_IRQ_SLEEP_MSECS \ + (CYAPA_THREAD_IRQ_SLEEP_SECS * MSEC_PER_SEC) + +/* + * When in Polling mode and no data received for CYAPA_NO_DATA_THRES msecs + * reduce the polling rate to CYAPA_NO_DATA_SLEEP_MSECS + */ +#define CYAPA_NO_DATA_THRES (MSEC_PER_SEC) +#define CYAPA_NO_DATA_SLEEP_MSECS (MSEC_PER_SEC / 4) + +/* report data start reg offset address. */ +#define DATA_REG_START_OFFSET 0x0000 +/* relative data report data size. */ +#define CYAPA_REL_REG_DATA_SIZE 5 + + +/* Device Sleep Modes */ +#define DEV_POWER_REG 0x0009 +#define INTERRUPT_MODE_MASK 0x01 +#define PWR_LEVEL_MASK 0x06 +#define PWR_BITS_SHITF 1 +#define GET_PWR_LEVEL(reg) \ + ((((unsigned char)(reg))&PWR_LEVEL_MASK)>>PWR_BITS_SHITF) + +/* protocol V1. */ +#define REG_GESTURES 0x0B + +/* definition to store platfrom data. */ +static struct cyapa_platform_data cyapa_i2c_platform_data = { + .flag = 0, + .gen = CYAPA_GEN2, + .power_state = CYAPA_PWR_ACTIVE, + .use_absolute_mode = true, + .use_polling_mode = false, + .polling_interval_time_active = CYAPA_ACTIVE_POLLING_INTVAL_TIME, + .polling_interval_time_lowpower = CYAPA_LOWPOWER_POLLING_INTVAL_TIME, + .active_touch_timeout = CYAPA_ACTIVE_TOUCH_TIMEOUT, + .name = CYAPA_I2C_NAME, + .irq_gpio = -1, + .report_rate = CYAPA_REPORT_RATE, +}; + + +/* +** APA trackpad device states. +** Used in register 0x00, bit1-0, DeviceStatus field. +*/ +enum cyapa_devicestate { + CYAPA_DEV_NORNAL = 0x03, + /* + ** After trackpad booted, and can report data, it should set this value. + ** 0ther values stand for trackpad device is in abnormal state. + ** maybe need to do reset operation to it. + ** Other values are defined later if needed. + */ +}; + +#define CYAPA_MAX_TOUCHS (MAX_FINGERS) +/* +** only 1 gesture can be reported one time right now. +*/ +#define CYAPA_ONE_TIME_GESTURES (1) +struct cyapa_touch_gen1 { + u8 rel_xy; + u8 rel_x; + u8 rel_y; +}; + +struct cyapa_reg_data_gen1 { + u8 tap_motion; + s8 deltax; + s8 deltay; + u8 reserved1; + u8 reserved2; + + struct cyapa_touch_gen1 touch1; + u8 touch_fingers; + u8 feature_config; + u8 avg_pressure; /* average of all touched fingers. */ + u8 gesture_status; + struct cyapa_touch_gen1 touchs[CYAPA_MAX_TOUCHS-1]; +}; + +struct cyapa_touch_gen2 { + u8 xy; + u8 x; + u8 y; + u8 id; +}; + +struct cyapa_gesture { + u8 id; + u8 param1; + u8 param2; +}; + +struct cyapa_reg_data_gen2 { + u8 device_status; + u8 relative_flags; + s8 deltax; + s8 deltay; + u8 avg_pressure; + u8 touch_fingers; + u8 reserved1; + u8 reserved2; + struct cyapa_touch_gen2 touchs[CYAPA_MAX_TOUCHS]; + u8 gesture_count; + struct cyapa_gesture gesture[CYAPA_ONE_TIME_GESTURES]; +}; + +union cyapa_reg_data { + struct cyapa_reg_data_gen1 gen1_data; + struct cyapa_reg_data_gen2 gen2_data; +}; + +struct cyapa_touch { + int x; + int y; + int id; +}; + +struct cyapa_report_data { + u8 button; + u8 reserved1; + u8 reserved2; + u8 avg_pressure; + int rel_deltaX; + int rel_deltaY; + + int touch_fingers; + struct cyapa_touch touchs[CYAPA_MAX_TOUCHS]; + + /* in gen1 and gen2, only 1 gesture one time supported. */ + int gestures_count; + struct cyapa_gesture gestures[CYAPA_ONE_TIME_GESTURES]; +}; + +struct speed_preferences { + int default_threshold; /* small scroll speed threshold. */ + int middle_threshold; + int fast_threshold; +}; + +struct mouse_ballistic_params { + /* + ** platfrom display aspect ratio adjustment parameters. + */ + int abs_rise_y; + int abs_run_y; + + /* + ** ABS FIR filter algorithm parameters. + */ + int fir_abs_enabled; + + int fir_abs_depth_max; + int fir_abs_depth_slew_count; + + /* Eccentricity Limits. */ + int fir_gentle_curve_eccentricity_max; + int fir_moderate_curve_eccentricity_max; + + int fir_slow_speed_limit; + int fir_moderate_speed_limit; + + int fir_gentle_curve_depth; + int fir_moderate_curve_depth; + int fir_slow_speed_depth; + int fir_moderate_speed_depth; + + /* + ** ABS IIR filter algorithm parameters. + */ + int iir_abs_enabled; + int iir_numerator; + int iir_denominator; + + /* + ** REL motion speed/acceleration control filter algorithm parameters. + */ + int rel_stroke_history_depth; + + int rel_medium_speed_threshold; + int rel_fast_speed_threshold; + int rel_flick_speed_threshold; + + int rel_motion_numerator; + int rel_medium_speed_numerator; + int rel_motion_denominator; + + int rel_acceleration_numerator; + int rel_acceleration_denominator; + + /* + ** REL IIR filter algorithm parameters. + */ + int rel_iir_acceleration_numerator; + int rel_iir_acceleration_denominator; + + int rel_max_acceleration_speed; +}; + +struct cyapa_preferences { + struct speed_preferences vscroll; + struct speed_preferences hscroll; + struct speed_preferences zoom; + + struct mouse_ballistic_params mouse_ballistic; +}; + +enum fir_curve_mode { + + FIR_CURVE_SLOW_SPEED_LINE = 0, + FIR_CURVE_MODERATE_SPEED_LINE = 1, + FIR_CURVE_FAST_SPEED_LINE = 2, + FIR_CURVE_GENTLE_CURVE = 3, + FIR_CURVE_MODERATE_CURVE = 4, + FIR_CURVE_FULL_STOP = 5, + FIR_CURVE_CIRCLE = 6, + FIR_CURVE_UNINITIALIZED = 0xFFFFFFFF +}; + +enum rel_stroke_speed { + REL_STROKE_SPEED_SLOW, + REL_STROKE_SPEED_MEDIUM, + REL_STROKE_SPEED_FAST, + REL_STROKE_SPEED_WARP +}; + +struct point { + int x; + int y; +}; + +/* MAX_FIR_DEPTH: +** A macro defines the number of entries in the FIR FIFO. */ +#define MAX_FIR_DEPTH 20 +/* STROKE_HISTORY_RECS: +** A macro which defines the number of samples stored +** in the stroke history buffer. */ +#define REL_STROKE_HISTORY_RECS 10 +struct cyapa_cursor_filters { + enum fir_curve_mode fir_curve_type; + + /* The cursor FIR filter FIFO for the X finger vector. */ + int fir_vectors_x[MAX_FIR_DEPTH]; + /* The cursor FIR filter FIFO for the Y finger vector. */ + int fir_vectors_y[MAX_FIR_DEPTH]; + + /* The remnant from the previous X FIR averaging computation. + ** Re-scaled when FIR depth changes. */ + int fir_x_mod; + /* The remnant from the previous Y FIR averaging computation. + ** Re-scaled when FIR depth changes. */ + int fir_y_mod; + + /* Current depth of the cursor FIR filter's 'X' FIFO. */ + int fir_abs_depth_x; + /* Current depth of the cursor FIR filter's 'Y' FIFO. */ + int fir_abs_depth_y; + + /* Previous depth of the cursor FIR filter's 'X' FIFO. + ** Used to rescale modular residue when FIFO depth is dynamically + ** changed. */ + int fir_abs_prev_depth_x; + + /* Previous depth of the cursor FIR filter's 'Y' FIFO. + ** Used to rescale modular residue when FIFO depth is dynamically + */ + int fir_abs_prev_depth_y; + + /* Counter used to implement the cursor FIR X vector filter's slew rate. + ** The FIR filter's slew rate determines the speed at which the FIR + ** depth increases over time, + ** limited by cursor speed and arc severity. */ + int fir_abs_depth_slew_counter_x; + + /* Counter used to implement the cursor FIR Y vector filter's slew rate. + ** The FIR filter's slew rate determines the speed at which the FIR + ** depth increases over time, + ** limited by cursor speed and arc severity. */ + int fir_abs_depth_slew_counter_y; + + int fir_effective_max_depth; + + /* The current state for one finger IIR filtering, X position. */ + int iir_x; + + /* The current state for one finger IIR filtering, Y position. */ + int iir_y; + + /* The modular remnant of the previous IIR division for + ** one finger X position IIR calculations. */ + int iir_mod_x; + + /* The modular remnant of the previous IIR division for + ** one finger Y position IIR calculations. */ + int iir_mod_y; + + struct point rel_stroke_history[REL_STROKE_HISTORY_RECS]; + int rel_stroke_depth; + int rel_stroke_speed; + + /* The running modular remainder from the Velocity X vector computation + ** the purpose of keeping the residue between report periods is to both + ** enhance precision and prevent uncontrolled 'losiness' of velocity. */ + int rel_residue_x; + + /* The running modular remainder from the Velocity Y vector computation + ** The purpose of keeping the residue between report periods is to both + ** enhance precision and prevent uncontrolled 'losiness' of velocity. */ + int rel_residue_y; + + /* The running modular remainder from the acceleration X vector + ** computation. */ + int rel_prev_residue_accel_x; + + /* The running modular remainder from the acceleration Y vector + ** computation. */ + int rel_prev_residue_accel_y; + + /* The running accelerated cursor motion IIR filter's X vector. */ + int rel_prev_accel_iir_x; + + /* The running accelerated cursor motion IIR filter's X vector's modular + ** residue. Prevents loss (slip) which would otherwise result from + ** the IIR computation. */ + int rel_prev_accel_iir_mod_x; + + /* The running accelerated cursor motion IIR filter's X vector. */ + int rel_prev_accel_iir_y; + + /* The running accelerated cursor motion IIR filter's X vector's modular + ** residue. Prevents loss (slip) which would otherwise result from + ** the IIR computation. */ + int rel_prev_accel_iir_mod_y; +}; + +/* The main device structure */ +struct cyapa_i2c { + struct i2c_client *client; + struct input_dev *input; + struct input_dev *input_wheel; + struct input_dev *input_kbd; + struct delayed_work dwork; + spinlock_t lock; + int no_data_count; + int scan_ms; + int read_pending; + int open_count; + + int irq; + struct cyapa_platform_data *platform_data; + unsigned short data_base_offset; + unsigned short control_base_offset; + unsigned short command_base_offset; + unsigned short query_base_offset; + + struct cyapa_preferences preferences; + + int zoomin_delta; + int zoomout_delta; + int hscroll_left; + int hscroll_right; + int hscroll_canceled; + int delta_scroll_up; + int delta_scroll_down; + int delta_scroll_left; + int delta_scroll_right; + int zoom_trigged; + + int abs_x; + int abs_y; + int prev_abs_x; + int prev_abs_y; + int rel_x; + int rel_y; + int prev_rel_x; + int prev_rel_y; + struct cyapa_cursor_filters cursor_filters; + unsigned char xy_touchs_included_bits; + unsigned char gesture_2F_drag_started; + + unsigned long cur_active_gestures[MAX_FINGERS]; + unsigned long prev_active_gestures[MAX_FINGERS]; + + int prev_touch_fingers; + + /* read from query data region. */ + char product_id[16]; + unsigned char capability[14]; + unsigned char fm_maj_ver; /* firmware major version. */ + unsigned char fm_min_ver; /* firmware minor version. */ + unsigned char hw_maj_ver; /* hardware major version. */ + unsigned char hw_min_ver; /* hardware minor version. */ + int max_absolution_x; + int max_absolution_y; + int physical_size_x; + int physical_size_y; +}; + + +#ifdef DBG_CYAPA_READ_BLOCK_DATA +void cyapa_print_data_block(const char *func, u8 reg, u8 length, void *data) +{ + char buf[512]; + unsigned buf_len = sizeof(buf); + char *p = buf; + int i; + int l; + + l = snprintf(p, buf_len, "reg 0x%04x: ", reg); + buf_len -= l; + p += l; + for (i = 0; i < length && buf_len; i++, p += l, buf_len -= l) + l = snprintf(p, buf_len, "%02x ", *((char *)data + i)); + printk(KERN_INFO "%s: data block length = %d\n", func, length); + printk(KERN_INFO "%s: %s\n", func, buf); +} + +void cyapa_print_report_data(const char *func, + struct cyapa_report_data *report_data) +{ + int i; + + printk(KERN_INFO "%s: ------------------------------------\n", func); + printk(KERN_INFO "%s: report_data.button = 0x%02x\n", + func, report_data->button); + printk(KERN_INFO "%s: report_data.avg_pressure = %d\n", + func, report_data->avg_pressure); + printk(KERN_INFO "%s: report_data.touch_fingers = %d\n", + func, report_data->touch_fingers); + for (i = 0; i < report_data->touch_fingers; i++) { + printk(KERN_INFO "%s: report_data.touchs[%d].x = %d\n", + func, i, report_data->touchs[i].x); + printk(KERN_INFO "%s: report_data.touchs[%d].y = %d\n", + func, i, report_data->touchs[i].y); + printk(KERN_INFO "%s: report_data.touchs[%d].id = %d\n", + func, i, report_data->touchs[i].id); + } + printk(KERN_INFO "%s: report_data.gestures_count = %d\n", + func, report_data->gestures_count); + for (i = 0; i < report_data->gestures_count; i++) { + printk(KERN_INFO "%s: report_data.gestures[%d].id = 0x%02x\n", + func, i, report_data->gestures[i].id); + printk(KERN_INFO "%s: report_data.gestures[%d].param1 = 0x%02x\n", + func, i, report_data->gestures[i].param1); + printk(KERN_INFO "%s: report_data.gestures[%d].param2 = 0x%02x\n", + func, i, report_data->gestures[i].param2); + } + printk(KERN_INFO "%s: -------------------------------------\n", func); +} + +void cyapa_print_paltform_data(const char *func, + struct cyapa_platform_data *cyapa_i2c_platform_data) +{ + printk(KERN_INFO "%s: -----------------------------------------\n", + func); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.flag = 0x%08x\n", + func, cyapa_i2c_platform_data->flag); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.gen = 0x%02x\n", + func, cyapa_i2c_platform_data->gen); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.power_state = 0x%02x\n", + func, cyapa_i2c_platform_data->power_state); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.use_absolute_mode = %s\n", + func, + cyapa_i2c_platform_data->use_absolute_mode ? "true" : "false"); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.use_polling_mode = %s\n", + func, cyapa_i2c_platform_data->use_polling_mode + ? "true" : "false"); + printk(KERN_INFO "%s: cyapa_i2c_platform_data. \ + polling_interval_time_active = %d\n", + func, cyapa_i2c_platform_data->polling_interval_time_active); + printk(KERN_INFO "%s: cyapa_i2c_platform_data \ + .polling_interval_time_lowpower = %d\n", + func, cyapa_i2c_platform_data->polling_interval_time_lowpower); + printk(KERN_INFO "%s: cyapa_i2c_platform_data \ + .active_touch_timeout = %d\n", + func, cyapa_i2c_platform_data->active_touch_timeout); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.name = %s\n", + func, cyapa_i2c_platform_data->name); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.irq_gpio = %d\n", + func, cyapa_i2c_platform_data->irq_gpio); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.report_rate = %d\n", + func, cyapa_i2c_platform_data->report_rate); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.init = %s%p\n", + func, cyapa_i2c_platform_data->init ? "0x" : "", + cyapa_i2c_platform_data->init); + printk(KERN_INFO "%s: cyapa_i2c_platform_data.wakeup = %s%p\n", + func, cyapa_i2c_platform_data->wakeup ? "0x" : "", + cyapa_i2c_platform_data->wakeup); + printk(KERN_INFO "%s: -----------------------------------------\n", + func); +} +#else +void cyapa_print_data_block(const char *func, u8 reg, u8 length, void *data) {} +void cyapa_print_report_data(const char *func, + struct cyapa_report_data *report_data) {} +void cyapa_print_paltform_data(const char *func, + struct cyapa_platform_data *cyapa_i2c_platform_data) {} +#endif + + +/* + * Driver's initial design makes no race condition possible on i2c bus, + * so there is no need in any locking. + * Keep it in mind, while playing with the code. + */ +static s32 cyapa_i2c_reg_read_byte(struct i2c_client *client, u16 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, (u8)reg & 0xff); + + return ((ret < 0) ? 0 : ret); +} + +static s32 cyapa_i2c_reg_write_byte(struct i2c_client *client, u16 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, (u8)reg & 0xff, val); + + return ((ret < 0) ? 0 : ret); +} + +static s32 cyapa_i2c_reg_read_block(struct i2c_client *client, u16 reg, + int length, u8 *values) +{ + int retval; + u8 buf[1]; + + /* + ** depending on PSOC easy I2C read operations. + ** step1: set read pointer of easy I2C. + ** step2: read data. + */ + /* step1: set read pointer of easy I2C. */ + memset(buf, 0, 1); + buf[0] = (u8)(((u8)reg) & 0xff); + retval = i2c_master_send(client, buf, 1); + if (retval < 0) { + DBGPRINTK(("%s: i2c_master_send error, retval=%d\n", + __func__, retval)); + return retval; + } + + /* step2: read data. */ + retval = i2c_master_recv(client, values, length); + if (retval < 0) { + DBGPRINTK(("%s: i2c_master_recv error, retval=%d\n", + __func__, retval)); + return retval; + } + + /* debug message */ + cyapa_print_data_block(__func__, (u8)reg, retval, values); + + if (retval != length) { + dev_warn(&client->dev, + "%s: warning I2C block read bytes \ + [%d] not equal to required bytes [%d].\n", + __func__, retval, length); + } + + return retval; +} + +#if CYAPA_USE_I2C_REG_WRITE_BLOCK +static s32 cyapa_i2c_reg_write_block(struct i2c_client *client, u16 reg, + u8 length, const u8 *values) + +{ + int retval; + int i; + u8 buf[256]; + + if ((length+1) > 256) { + DBGPRINTK(("%s: invalid write data length, length=%d\n", + __func__, length)); + return -EINVAL; + } + + /* + ** depending on PSOC easy I2C read operations. + ** step1: write data to easy I2C in one command. + */ + /* step1: write data to easy I2C in one command. */ + memset(buf, 0, 256); + buf[0] = (u8)(((u8)reg) & 0xff); + /* move data shoud be write to I2C slave device. */ + for (i = 1; i < length; i++) + buf[i] = values[i-1]; + + retval = i2c_master_send(client, buf, length+1); + if (retval < 0) { + DBGPRINTK(("%s: i2c_master_send error, retval=%d\n", + __func__, retval)); + return retval; + } + + if (retval != (length+1)) { + dev_warn(&client->dev, + "%s: warning I2C block write bytes \ + [%d] not equal to required bytes [%d].\n", + __func__, retval, length); + } + + return retval; +} +#endif + +#define REG_OFFSET_DATA_BASE 0x0000 +#define REG_OFFSET_CONTROL_BASE 0x0029 +#define REG_OFFSET_COMMAND_BASE 0x0049 +#define REG_OFFSET_QUERY_BASE 0x004B +static void cyapa_get_reg_offset(struct cyapa_i2c *touch) +{ + touch->data_base_offset = REG_OFFSET_DATA_BASE; + touch->control_base_offset = REG_OFFSET_CONTROL_BASE; + touch->command_base_offset = REG_OFFSET_COMMAND_BASE; + touch->query_base_offset = REG_OFFSET_QUERY_BASE; + + /* this function will be updated later depending firmware support. */ +} + +static void cyapa_get_query_data(struct cyapa_i2c *touch) +{ + unsigned char query_data[40]; + int ret_read_size = 0; + int i; + + /* query data has been supported in GEN1 protocol.*/ + if (touch->platform_data->gen == CYAPA_GEN2) { + memset(query_data, 0, 40); + ret_read_size = cyapa_i2c_reg_read_block(touch->client, + touch->query_base_offset, + 38, + (u8 *)&query_data); + + touch->product_id[0] = query_data[0]; + touch->product_id[1] = query_data[1]; + touch->product_id[2] = query_data[2]; + touch->product_id[3] = query_data[3]; + touch->product_id[4] = query_data[4]; + touch->product_id[5] = '-'; + touch->product_id[6] = query_data[5]; + touch->product_id[7] = query_data[6]; + touch->product_id[8] = query_data[7]; + touch->product_id[9] = query_data[8]; + touch->product_id[10] = query_data[9]; + touch->product_id[11] = query_data[10]; + touch->product_id[12] = '-'; + touch->product_id[13] = query_data[11]; + touch->product_id[14] = query_data[12]; + touch->product_id[15] = '\0'; + + touch->fm_maj_ver = query_data[15]; + touch->fm_min_ver = query_data[16]; + touch->hw_maj_ver = query_data[17]; + touch->hw_min_ver = query_data[18]; + + for (i = 0; i < 13; i++) + touch->capability[i] = query_data[19+i]; + + touch->max_absolution_x = + (((query_data[32] & 0xF0) << 4) | query_data[33]); + touch->max_absolution_y = + (((query_data[32] & 0x0F) << 8) | query_data[34]); + if (!touch->max_absolution_x || !touch->max_absolution_y) { + if (!strcmp(touch->product_id, "CYTRA-014001-00")) { + touch->max_absolution_x = 1600; + touch->max_absolution_y = 900; + } else { + touch->max_absolution_x = 1200; + touch->max_absolution_y = 600; + } + } + + touch->physical_size_x = + (((query_data[35] & 0xF0) << 4) | query_data[36]); + touch->physical_size_y = + (((query_data[35] & 0x0F) << 8) | query_data[37]); + if (!touch->physical_size_x || !touch->physical_size_y) { + touch->physical_size_x = 105; + touch->physical_size_y = 60; + } + + printk(KERN_INFO "Cypress Trackpad Information:\n"); + printk(KERN_INFO "\t\t\tProduction ID: %s\n", + touch->product_id); + printk(KERN_INFO "\t\t\tFirmware version: %d.%d\n", + touch->fm_maj_ver, touch->fm_min_ver); + printk(KERN_INFO "\t\t\tHardware version: %d.%d\n", + touch->hw_maj_ver, touch->hw_min_ver); + printk(KERN_INFO "\t\t\tDriver Version: %d.%d.%d\n", + CYAPA_MAJOR_VER, CYAPA_MINOR_VER, CYAPA_REVISIOIN_VER); + printk(KERN_INFO "\t\t\tResolution X,Y: %d,%d\n", + touch->max_absolution_x, touch->max_absolution_y); + printk(KERN_INFO "\t\t\tPhysical Size X,Y: %d,%d\n", + touch->physical_size_x, touch->physical_size_y); + } +} + +static int cyapa_i2c_reconfig(struct cyapa_i2c *touch) +{ + struct i2c_client *client = touch->client; + int regval = 0; + int retval = 0; + + if (touch->platform_data->gen == CYAPA_GEN1) { + /* trackpad gen1 firmware. */ + DBGPRINTK(("%s: trackpad support gen1 firmware.\n", __func__)); + + regval = cyapa_i2c_reg_read_byte(client, DEV_POWER_REG); + DBGPRINTK(("%s: read trackpad interrupt bit = 0x%02x\n", + __func__, regval&INTERRUPT_MODE_MASK)); + + if ((touch->platform_data->use_polling_mode == true) + && ((regval & INTERRUPT_MODE_MASK) + == INTERRUPT_MODE_MASK)) { + /* reset trackpad to polling mode. */ + regval &= (~INTERRUPT_MODE_MASK); + retval = cyapa_i2c_reg_write_byte(client, DEV_POWER_REG, + (u8)(regval & 0xff)); + if (retval) { + DBGPRINTK(("%s: set to polliing mode failed,\ + retval=%d.\n", __func__, retval)); + /* + * Though firmware has set interrupt mode bit. + * but since platfrom doesn't support + * interrupt mode, so also use polling mode here + * do nothing. + */ + } + } else if ((touch->platform_data->use_polling_mode == false) + && ((regval & INTERRUPT_MODE_MASK) + != INTERRUPT_MODE_MASK)) { + /* reset trackpad to interrupt mode. */ + regval |= INTERRUPT_MODE_MASK; + retval = cyapa_i2c_reg_write_byte(client, DEV_POWER_REG, + (u8)(regval & 0xff)); + if (retval) { + DBGPRINTK(("%s: set to interrup mode failed, \ + retval=%d.\n", __func__, retval)); + touch->platform_data->use_polling_mode = true; + } + } + + DBGPRINTK(("%s: trackpad interrupt bit = 0x%02x\n", __func__, + (u8)cyapa_i2c_reg_read_byte(client, DEV_POWER_REG))); + } else { + /* trackpad gen2 firmware. default is interrupt mode. */ + DBGPRINTK(("%s: trackpad support gen2 firmware.\n", __func__)); + + cyapa_get_reg_offset(touch); + cyapa_get_query_data(touch); + } + + DBGPRINTK(("%s: use %s mode.\n", __func__, + ((touch->platform_data->use_polling_mode == true) + ? "polling" : "interrupt"))); + return retval; +} + +static int cyapa_i2c_reset_config(struct cyapa_i2c *touch) +{ + int ret = 0; + + DBGPRINTK(("%s: ...\n", __func__)); + + return ret; +} + +static int cyapa_verify_data_device(struct cyapa_i2c *touch, + union cyapa_reg_data *reg_data) +{ + struct cyapa_reg_data_gen1 *data_gen1 = NULL; + struct cyapa_reg_data_gen2 *data_gen2 = NULL; + + if (touch->platform_data->gen == CYAPA_GEN1) { + data_gen1 = ®_data->gen1_data; + if ((data_gen1->tap_motion & 0x08) != 0x08) { + /* invalid data. */ + DBGPRINTK(("%s: invalid data reg address 0x00, \ + bit3 is not set.\n", __func__)); + return -EINVAL; + } + } else { + data_gen2 = ®_data->gen2_data; + if ((data_gen2->device_status & 0x80) != 0x80) { + /* invalid data. */ + DBGPRINTK(("%s: invalid data reg address 0x00, \ + bit7 is not set.\n", __func__)); + return -EINVAL; + } + + if ((data_gen2->device_status & 0x03) != CYAPA_DEV_NORNAL) { + DBGPRINTK(("%s: invalid device status = 0x%02x, \ + wait for device ready.\n", + __func__, (data_gen2->device_status & 0x03))); + return -EBUSY; + } + } + + return 0; +} + +static inline void cyapa_calculate_abs_xy(struct cyapa_i2c *touch, + struct cyapa_report_data *report_data) +{ + int i; + int sum_x = 0, sum_y = 0; + + /* invalid input data. */ + if (!touch->xy_touchs_included_bits || !report_data->touch_fingers) { + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + return; + } + + for (i = 0; i < CYAPA_MAX_TOUCHS; i++) { + if (touch->xy_touchs_included_bits & (0x01 << i)) { + sum_x += report_data->touchs[i].x; + sum_y += report_data->touchs[i].y; + } + } + + touch->abs_x = sum_x / report_data->touch_fingers; + touch->abs_y = sum_y / report_data->touch_fingers; + /* x, y directory of Cypress trackpad is in negative direction of screen + ** for some platform it maybe different. */ + /*** + touch->abs_x = touch->platform_data->max_touchpad_x - touch->abs_x; + touch->abs_y = touch->platform_data->max_touchpad_y - touch->abs_y; + ***/ + + /* use simple filtr to make cursor move smoother. */ + if (touch->prev_abs_x != -1) { + touch->abs_x = (touch->abs_x * 3 + touch->prev_abs_x) >> 2; + touch->abs_y = (touch->abs_y * 3 + touch->prev_abs_y) >> 2; + } + + touch->prev_abs_x = touch->abs_x; + touch->prev_abs_y = touch->abs_y; +} + +static inline int cyapa_sqrt(int delta_x, int delta_y) +{ + int Xk0 = 0; + int Xk1; + int multi; + + multi = Xk1 = delta_x*delta_x + delta_y*delta_y; + + while (abs(Xk0 - Xk1) > 1) { + Xk0 = Xk1; + Xk1 = (Xk0 + (multi / Xk0)) / 2; + } + + return Xk1; +} + +static void cyapa_parse_gen1_data(struct cyapa_i2c *touch, + struct cyapa_reg_data_gen1 *reg_data, + struct cyapa_report_data *report_data) +{ + int i; + int gesture_report_index = 0; + int gesture_fingers = 0; + int gesture_index = 0; + + /* parse gestures and button data */ + report_data->button = reg_data->tap_motion & 0x01; + + /* get relative delta X and delta Y. */ + report_data->rel_deltaX = reg_data->deltax; + /* The Y directory of trackpad is the oppsite of Screen. */ + report_data->rel_deltaY = -reg_data->deltay; + + if (reg_data->tap_motion & 0x02) { + report_data->gestures[gesture_report_index++].id + = GESTURE_SINGLE_TAP; + + gesture_fingers = GESTURE_DECODE_FINGERS(GESTURE_SINGLE_TAP); + gesture_index = GESTURE_DECODE_INDEX(GESTURE_SINGLE_TAP); + touch->cur_active_gestures[gesture_fingers] + |= (1UL << gesture_index); + } + + if (reg_data->tap_motion & 0x04) { + report_data->gestures[gesture_report_index++].id + = GESTURE_DOUBLE_TAP; + + gesture_fingers = GESTURE_DECODE_FINGERS(GESTURE_DOUBLE_TAP); + gesture_index = GESTURE_DECODE_INDEX(GESTURE_DOUBLE_TAP); + touch->cur_active_gestures[gesture_fingers] + |= (1UL << gesture_index); + } + + report_data->gestures_count = gesture_report_index; + + /* pase fingers touch data */ + report_data->touch_fingers + = ((reg_data->touch_fingers > CYAPA_MAX_TOUCHS) ? + (CYAPA_MAX_TOUCHS) : (reg_data->touch_fingers)); + report_data->avg_pressure = reg_data->avg_pressure; + report_data->touchs[0].x = + ((reg_data->touch1.rel_xy & 0xF0) << 4) + | reg_data->touch1.rel_x; + report_data->touchs[0].y = + ((reg_data->touch1.rel_xy & 0x0F) << 8) + | reg_data->touch1.rel_y; + report_data->touchs[0].id = 0; + + for (i = 0; i < (CYAPA_MAX_TOUCHS-1); i++) { + report_data->touchs[i+1].x = + ((reg_data->touchs[i].rel_xy & 0xF0) << 4) + | reg_data->touchs[i].rel_x; + report_data->touchs[i+1].y = + ((reg_data->touchs[i].rel_xy & 0x0F) << 8) + | reg_data->touchs[i].rel_y; + report_data->touchs[i+1].id = i+1; + } + + cyapa_print_report_data(__func__, report_data); +} + +static void cyapa_parse_gen2_data(struct cyapa_i2c *touch, + struct cyapa_reg_data_gen2 *reg_data, + struct cyapa_report_data *report_data) +{ + int i; + int gesture_fingers = 0; + int gesture_index = 0; + + /* bit2-middle button; bit1-right button; bit0-left buttom. */ + report_data->button = reg_data->relative_flags & 0x07; + + /* get relative delta X and delta Y. */ + report_data->rel_deltaX = reg_data->deltax; + /* The Y directory of trackpad is the oppsite of Screen. */ + report_data->rel_deltaY = -reg_data->deltay; + + /* copy fingers touch data */ + report_data->avg_pressure = reg_data->avg_pressure; + report_data->touch_fingers + = ((reg_data->touch_fingers > CYAPA_MAX_TOUCHS) ? + (CYAPA_MAX_TOUCHS) : (reg_data->touch_fingers)); + for (i = 0; i < report_data->touch_fingers; i++) { + report_data->touchs[i].x = + ((reg_data->touchs[i].xy & 0xF0) << 4) + | reg_data->touchs[i].x; + report_data->touchs[i].y = + ((reg_data->touchs[i].xy & 0x0F) << 8) + | reg_data->touchs[i].y; + report_data->touchs[i].id = reg_data->touchs[i].id; + } + + /* parse gestures */ + report_data->gestures_count = + (((reg_data->gesture_count) > CYAPA_ONE_TIME_GESTURES) ? + CYAPA_ONE_TIME_GESTURES : reg_data->gesture_count); + for (i = 0; i < report_data->gestures_count; i++) { + report_data->gestures[i].id = reg_data->gesture[i].id; + report_data->gestures[i].param1 = reg_data->gesture[i].param1; + report_data->gestures[i].param2 = reg_data->gesture[i].param2; + + gesture_fingers + = GESTURE_DECODE_FINGERS(report_data->gestures[i].id); + gesture_index + = GESTURE_DECODE_INDEX(report_data->gestures[i].id); + touch->cur_active_gestures[gesture_fingers] + |= (1UL << gesture_index); + } + + cyapa_print_report_data(__func__, report_data); +} + +void cyapa_set_preferences(struct cyapa_i2c *touch) +{ + struct cyapa_preferences *preferences = &touch->preferences; + + /* set default setting for hscroll. */ + preferences->vscroll.default_threshold = 4; + preferences->vscroll.middle_threshold = 8; + preferences->vscroll.fast_threshold = 16; + + /* set default setting for vscroll. */ + preferences->hscroll.default_threshold = 4; + preferences->hscroll.middle_threshold = 8; + preferences->hscroll.fast_threshold = 16; + + /* set default setting for vscroll. */ + preferences->zoom.default_threshold = 8; + preferences->zoom.middle_threshold = 16; + preferences->zoom.fast_threshold = 32; + + /* set default setting for platform display aspect ratio adjustment. */ + preferences->mouse_ballistic.abs_rise_y = 16; + preferences->mouse_ballistic.abs_run_y = 14; + + /* set default setting for ABS FIR filter parameters. */ + preferences->mouse_ballistic.fir_abs_enabled = 1; + + preferences->mouse_ballistic.fir_abs_depth_slew_count = 5; + preferences->mouse_ballistic.fir_gentle_curve_eccentricity_max = 2; + preferences->mouse_ballistic.fir_moderate_curve_eccentricity_max = 3; + + preferences->mouse_ballistic.fir_slow_speed_limit = 4; + preferences->mouse_ballistic.fir_moderate_speed_limit = 7; + + preferences->mouse_ballistic.fir_abs_depth_max = 7; + preferences->mouse_ballistic.fir_slow_speed_depth = 7; + preferences->mouse_ballistic.fir_moderate_speed_depth = 7; + preferences->mouse_ballistic.fir_gentle_curve_depth = 5; + preferences->mouse_ballistic.fir_moderate_curve_depth = 6; + + /* set default setting for ABS IIR filter parameters. */ + preferences->mouse_ballistic.iir_abs_enabled = 1; + preferences->mouse_ballistic.iir_numerator = 1; + preferences->mouse_ballistic.iir_denominator = 2; + + /* set default setting for REL speed/acceleration filter parameters. */ + preferences->mouse_ballistic.rel_stroke_history_depth = 4; + preferences->mouse_ballistic.rel_medium_speed_threshold = 7; + preferences->mouse_ballistic.rel_fast_speed_threshold = 10; + preferences->mouse_ballistic.rel_flick_speed_threshold = 80; + preferences->mouse_ballistic.rel_motion_numerator = 8; + preferences->mouse_ballistic.rel_medium_speed_numerator = 7; + preferences->mouse_ballistic.rel_motion_denominator = 12; + preferences->mouse_ballistic.rel_acceleration_numerator = 16; + preferences->mouse_ballistic.rel_acceleration_denominator = 100; + + /* set default setting for REL IIR filter parameters. */ + preferences->mouse_ballistic.rel_iir_acceleration_numerator = 1; + preferences->mouse_ballistic.rel_iir_acceleration_denominator = 8; + preferences->mouse_ballistic.rel_max_acceleration_speed = 900; +} + +void cyapa_reset_cursor_filters_data(struct cyapa_i2c *touch) +{ + int i; + struct cyapa_cursor_filters *filter = &touch->cursor_filters; + + /* reset previous motion data. */ + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + touch->prev_rel_x = 0; + touch->prev_rel_y = 0; + + /* reset data used in FIR filter to ABS data. */ + filter->fir_curve_type = FIR_CURVE_UNINITIALIZED; + for (i = 0; i < MAX_FIR_DEPTH; i++) { + filter->fir_vectors_x[i] = 0; + filter->fir_vectors_y[i] = 0; + } + filter->fir_x_mod = 0; + filter->fir_y_mod = 0; + filter->fir_abs_depth_x = 0; + filter->fir_abs_depth_y = 0; + filter->fir_abs_prev_depth_x = -1; + filter->fir_abs_prev_depth_y = -1; + filter->fir_abs_depth_slew_counter_x = 1; + filter->fir_abs_depth_slew_counter_y = 1; + + /* reset data used in IIR filter to ABS data. */ + filter->iir_x = 0xFFFFFFFF; + filter->iir_y = 0xFFFFFFFF; + filter->iir_mod_x = 0; + filter->iir_mod_y = 0; + + /* reset data used in compluting relative movement speed category. */ + filter->rel_stroke_depth = 0; + filter->rel_stroke_speed = REL_STROKE_SPEED_FAST; + + /* reset data used in relative speed/acceleration control. */ + filter->rel_residue_x = 0; + filter->rel_residue_y = 0; + filter->rel_prev_residue_accel_x = 0; + filter->rel_prev_residue_accel_y = 0; + + /* reset data used in relative IIR filter. */ + filter->rel_prev_accel_iir_x = CYAPA_FILTER_EMPTY_DATA; + filter->rel_prev_accel_iir_y = CYAPA_FILTER_EMPTY_DATA; + filter->rel_prev_accel_iir_mod_x = 0; + filter->rel_prev_accel_iir_mod_y = 0; +} + +/*Converts modular residues to new scale. +** This enables residues to persist across interations where various divisors +** which are used by data filters are modified. +*/ +inline int cyapa_fir_rescale_modulus(int mod_in, int old_scale, int new_scale) +{ + int temp; + + if (old_scale <= 1) + return mod_in; + + if (new_scale <= old_scale) + return mod_in; + + temp = mod_in * new_scale; + temp = temp / old_scale; + + return temp; +} + +/* Determines the ecentricity, a primitive measure of the degree of curvature, +** which is used to select FIR characteristics. +*/ +int cyapa_fir_get_curve_type(struct cyapa_i2c *touch) +{ + int i; + int curve_type; + int cur_speed; + int max_speed, min_speed; + int max_speed_x, max_speed_y; + int eccentricity_x, eccentricity_y; + int zero_count_x = 0; + int zero_count_y = 0; + struct cyapa_cursor_filters *filter = &touch->cursor_filters; + struct mouse_ballistic_params *mouse_ballistic + = &touch->preferences.mouse_ballistic; + + if (filter->fir_curve_type == FIR_CURVE_UNINITIALIZED) { + if ((filter->fir_abs_depth_x < 2) + || (filter->fir_abs_depth_y < 2)) { + filter->fir_curve_type = FIR_CURVE_MODERATE_SPEED_LINE; + return FIR_CURVE_MODERATE_SPEED_LINE; + } + + return filter->fir_curve_type; + } + + /* find max speed in X direction. */ + max_speed = -100000; + min_speed = 100000; + for (i = 0; i < (filter->fir_abs_depth_x - 1); i++) { + cur_speed = filter->fir_vectors_x[i] + - filter->fir_vectors_x[i+1]; + + if (cur_speed < min_speed) + min_speed = cur_speed; + + if (cur_speed > max_speed) + max_speed = cur_speed; + + /* find the count of previous data packages + ** that X direction keeps not moving. */ + if ((min_speed == 0) && (max_speed == 0)) + zero_count_x = i; + } + + if (i > 0) + eccentricity_x = (max_speed - min_speed) / i; + else + eccentricity_x = 0; + + max_speed_x = max_speed; + + /* find max speed in Y direction. */ + max_speed = -100000; + min_speed = 100000; + for (i = 0; i < (filter->fir_abs_depth_y - 1); i++) { + cur_speed = filter->fir_vectors_y[i] + - filter->fir_vectors_y[i+1]; + + if (cur_speed < min_speed) + min_speed = cur_speed; + + if (cur_speed > max_speed) + max_speed = cur_speed; + + /* find the count of previous data packages + ** that Y direction keeps not moving. */ + if ((min_speed == 0) && (max_speed == 0)) + zero_count_y = i; + } + + if (i > 0) + eccentricity_y = (max_speed - min_speed) / i; + else + eccentricity_y = 0; + + max_speed_y = max_speed; + + /* After previous 3 or more data packages reports, + ** finger movement still not detected. */ + if ((zero_count_x > 2) && (zero_count_y > 2)) { + filter->fir_curve_type = FIR_CURVE_FULL_STOP; + return FIR_CURVE_FULL_STOP; + } + + /* calculate combined speed vectors. */ + cur_speed = abs(max_speed_x) + abs(max_speed_y); + + /* sort curve line type. */ + if (cur_speed < mouse_ballistic->fir_slow_speed_limit) + curve_type = FIR_CURVE_SLOW_SPEED_LINE; + else if (cur_speed < mouse_ballistic->fir_moderate_speed_limit) + curve_type = FIR_CURVE_MODERATE_SPEED_LINE; + else + curve_type = FIR_CURVE_FAST_SPEED_LINE; + + /* sort fast speed curve type much more detail + ** based on eccentricity x and y value. */ + if (curve_type == FIR_CURVE_FAST_SPEED_LINE) { + if ((eccentricity_x == 1) && (eccentricity_y == 1)) { + curve_type = FIR_CURVE_GENTLE_CURVE; + } else if ((eccentricity_x + == mouse_ballistic->fir_gentle_curve_eccentricity_max) + && (eccentricity_y == 1)) { + curve_type = FIR_CURVE_GENTLE_CURVE; + } else if ((eccentricity_x == 1) + && (eccentricity_y == + mouse_ballistic->fir_gentle_curve_eccentricity_max)) { + curve_type = FIR_CURVE_GENTLE_CURVE; + } else if ((eccentricity_x + > mouse_ballistic->fir_gentle_curve_eccentricity_max) + && (eccentricity_y + > mouse_ballistic->fir_gentle_curve_eccentricity_max)) { + curve_type = FIR_CURVE_MODERATE_CURVE; + } else if ((eccentricity_x + >= mouse_ballistic->fir_moderate_curve_eccentricity_max) + || (eccentricity_y >= + mouse_ballistic->fir_moderate_curve_eccentricity_max)) { + curve_type = FIR_CURVE_MODERATE_CURVE; + } + } + + filter->fir_curve_type = curve_type; + + return curve_type; +} + +/* Obtain the FIR curve type and obtain the FIR filter depth. +** Filter depth is a tuning parameter. +*/ +int cyapa_fir_reset_fifo_depth(struct cyapa_i2c *touch) +{ + int curve_type; + int fir_depth; + struct cyapa_cursor_filters *filter = &touch->cursor_filters; + struct mouse_ballistic_params *mouse_ballistic + = &touch->preferences.mouse_ballistic; + + curve_type = cyapa_fir_get_curve_type(touch); + + switch (curve_type) { + case FIR_CURVE_SLOW_SPEED_LINE: + fir_depth = mouse_ballistic->fir_slow_speed_depth; + break; + case FIR_CURVE_MODERATE_SPEED_LINE: + fir_depth = mouse_ballistic->fir_moderate_speed_depth; + break; + case FIR_CURVE_FAST_SPEED_LINE: + fir_depth = mouse_ballistic->fir_gentle_curve_depth; + break; + case FIR_CURVE_GENTLE_CURVE: + fir_depth = mouse_ballistic->fir_gentle_curve_depth; + break; + case FIR_CURVE_MODERATE_CURVE: + fir_depth = mouse_ballistic->fir_moderate_curve_depth; + break; + case FIR_CURVE_FULL_STOP: + fir_depth = 2; + break; + case FIR_CURVE_CIRCLE: + fir_depth = 0; + break; + default: + fir_depth = mouse_ballistic->fir_slow_speed_depth; + break; + } + + /* avoid invalid parameter setting. */ + if (fir_depth > mouse_ballistic->fir_abs_depth_max) + fir_depth = mouse_ballistic->fir_abs_depth_max; + + filter->fir_effective_max_depth = fir_depth; + + return fir_depth; +} + +static int cyapa_filter_fir_abs_x(struct cyapa_i2c *touch) +{ + int i; + int sum_x, average_x; + int effective_max_depth; + struct cyapa_cursor_filters *filter = &touch->cursor_filters; + struct mouse_ballistic_params *mouse_ballistic + = &touch->preferences.mouse_ballistic; + + effective_max_depth = filter->fir_effective_max_depth; + + /* set slow speed FIR depth. */ + if ((effective_max_depth < filter->fir_abs_depth_x) + && (effective_max_depth < 2)) { + filter->fir_abs_depth_x = effective_max_depth; + filter->fir_abs_depth_slew_counter_x = 0; + } + + if (effective_max_depth > filter->fir_abs_depth_x) { + /* count depth change slew value, if less or equal to 0, + ** make depth changed and reset slew value for next time. */ + filter->fir_abs_depth_slew_counter_x--; + if (filter->fir_abs_depth_slew_counter_x <= 0) { + filter->fir_abs_depth_slew_counter_x + = mouse_ballistic->fir_abs_depth_slew_count; + filter->fir_abs_depth_x++; + } + } + + if (effective_max_depth < filter->fir_abs_depth_x) { + /* count depth change slew value, if less or equal to 0, + ** make depth changed and reset slew value for next time. */ + filter->fir_abs_depth_slew_counter_x--; + if (filter->fir_abs_depth_slew_counter_x <= 0) { + filter->fir_abs_depth_slew_counter_x + = mouse_ballistic->fir_abs_depth_slew_count; + filter->fir_abs_depth_x--; + } + } + + /* insert new X data. */ + for (i = mouse_ballistic->fir_abs_depth_max-1; i > 0; i--) + filter->fir_vectors_x[i] = filter->fir_vectors_x[i-1]; + filter->fir_vectors_x[0] = touch->abs_x; + + /* calculate and apply ABS FIR filter algorithm to X data. */ + for (i = 0; i < filter->fir_abs_depth_x; i++) + sum_x += filter->fir_vectors_x[i]; + + if (filter->fir_abs_depth_x > 1) { + /* rescale modulus based on ABS FIR depth. */ + filter->fir_x_mod = cyapa_fir_rescale_modulus(filter->fir_x_mod, + filter->fir_abs_prev_depth_x, filter->fir_abs_depth_x); + average_x = (sum_x + filter->fir_x_mod) + / filter->fir_abs_depth_x; + filter->fir_x_mod = (sum_x + filter->fir_x_mod) + % filter->fir_abs_depth_x; + } else { + average_x = touch->abs_x; + filter->fir_x_mod = 0; + } + + /* store current ABS FIR depth.*/ + filter->fir_abs_prev_depth_x = filter->fir_abs_depth_x; + + return average_x; +} + +static int cyapa_filter_fir_abs_y(struct cyapa_i2c *touch) +{ + int i; + int sum_y, average_y; + int effective_max_depth; + struct cyapa_cursor_filters *filter = &touch->cursor_filters; + struct mouse_ballistic_params *mouse_ballistic + = &touch->preferences.mouse_ballistic; + + effective_max_depth = filter->fir_effective_max_depth; + + /* set slow speed FIR depth. */ + if ((effective_max_depth < filter->fir_abs_depth_y) + && (effective_max_depth < 2)) { + filter->fir_abs_depth_y = effective_max_depth; + filter->fir_abs_depth_slew_counter_y = 0; + } + + if (effective_max_depth > filter->fir_abs_depth_y) { + /* count depth change slew value, if less or equal to 0, + ** make depth changed and reset slew value for next time. */ + filter->fir_abs_depth_slew_counter_y--; + if (filter->fir_abs_depth_slew_counter_y <= 0) { + filter->fir_abs_depth_slew_counter_y + = mouse_ballistic->fir_abs_depth_slew_count; + filter->fir_abs_depth_y++; + } + } + + if (effective_max_depth < filter->fir_abs_depth_y) { + /* count depth change slew value, if less or equal to 0, + ** make depth changed and reset slew value for next time. */ + filter->fir_abs_depth_slew_counter_y--; + if (filter->fir_abs_depth_slew_counter_y <= 0) { + filter->fir_abs_depth_slew_counter_y + = mouse_ballistic->fir_abs_depth_slew_count; + filter->fir_abs_depth_y--; + } + } + + /* insert new Y data. */ + for (i = mouse_ballistic->fir_abs_depth_max-1; i > 0; i--) + filter->fir_vectors_y[i] = filter->fir_vectors_y[i-1]; + filter->fir_vectors_y[0] = touch->abs_y; + + /* calculate and apply ABS FIR filter algorithm to Y data. */ + for (i = 0; i < filter->fir_abs_depth_y; i++) + sum_y += filter->fir_vectors_y[i]; + + if (filter->fir_abs_depth_y > 1) { + /* rescale modulus based on ABS FIR depth. */ + filter->fir_y_mod = cyapa_fir_rescale_modulus(filter->fir_y_mod, + filter->fir_abs_prev_depth_y, filter->fir_abs_depth_y); + average_y = (sum_y + filter->fir_y_mod) + / filter->fir_abs_depth_y; + filter->fir_y_mod = (sum_y + filter->fir_y_mod) + % filter->fir_abs_depth_y; + } else { + average_y = touch->abs_y; + filter->fir_y_mod = 0; + } + + /* store current ABS FIR depth.*/ + filter->fir_abs_prev_depth_y = filter->fir_abs_depth_y; + + return average_y; +} + +static int cyapa_filter_iir_abs(struct cyapa_i2c *touch, + int *prev_iir, + int *prev_iir_mod, + int pos, + int numerator, + int denominator) +{ + int old_iir; + int new_iir; + int iir_mod_prev; + int iir_mod_next; + + if (*prev_iir == 0xFFFFFFFF) { + *prev_iir = pos; + *prev_iir_mod = 0; + return pos; + } + + if ((numerator == 1) && (denominator == 1)) { + *prev_iir = pos; + *prev_iir_mod = 0; + return pos; + } + + if ((numerator > denominator) || (denominator <= 0)) { + *prev_iir = pos; + *prev_iir_mod = 0; + return pos; + } + + old_iir = *prev_iir; + iir_mod_prev = *prev_iir_mod; + + /* calculate and apply IIR filter to ABS postion value 'pos'. */ + new_iir = old_iir * numerator; + pos = pos * (denominator - numerator); + new_iir = new_iir + pos + iir_mod_prev; + iir_mod_next = new_iir % denominator; + new_iir = new_iir / denominator; + + /* store current IIR value and IIR mode value + ** for next time calculating.*/ + *prev_iir = new_iir; + *prev_iir_mod = iir_mod_next; + + return new_iir; +} + +inline int cyapa_compute_rel_motion(int abs_pos, int prev_abs_pos) +{ + if (prev_abs_pos == -1) + return 0; + + return abs_pos - prev_abs_pos; +} + +static int cyapa_rel_get_accel_stoke_type(struct cyapa_i2c *touch, + int delta_x, + int delta_y) +{ + int i; + int rel_stroke_speed; + int speed_x, speed_y; + int max_speed_x, max_speed_y; + struct cyapa_cursor_filters *filter = &touch->cursor_filters; + struct mouse_ballistic_params *mouse_ballistic + = &touch->preferences.mouse_ballistic; + + /* verify mouse ballistic setting for stroke history depth, + ** update it if invliad. */ + if (mouse_ballistic->rel_stroke_history_depth + > (REL_STROKE_HISTORY_RECS-1)) { + mouse_ballistic->rel_stroke_history_depth + = REL_STROKE_HISTORY_RECS - 1; + } + + for (i = mouse_ballistic->rel_stroke_history_depth-1; i > 0; i--) { + filter->rel_stroke_history[i].x + = filter->rel_stroke_history[i-1].x; + filter->rel_stroke_history[i].y + = filter->rel_stroke_history[i-1].y; + } + filter->rel_stroke_history[0].x = abs(delta_x); + filter->rel_stroke_history[0].y = abs(delta_y); + + /* upate storke depth. */ + if (filter->rel_stroke_depth + < mouse_ballistic->rel_stroke_history_depth) { + filter->rel_stroke_depth++; + } else { + filter->rel_stroke_depth + = mouse_ballistic->rel_stroke_history_depth; + } + + /* find max speed in stroke history. */ + max_speed_x = max_speed_y = -100000; + for (i = 0; i < filter->rel_stroke_depth; i++) { + speed_x = filter->rel_stroke_history[i].x; + speed_y = filter->rel_stroke_history[i].y; + + if (speed_x > max_speed_x) + max_speed_x = speed_x; + + if (speed_y > max_speed_y) + max_speed_y = speed_y; + } + + if (max_speed_y > max_speed_x) + max_speed_x = max_speed_y; + + /* sort storke speed range based on max stroke history speed. */ + if (max_speed_x < mouse_ballistic->rel_medium_speed_threshold) { + rel_stroke_speed = REL_STROKE_SPEED_SLOW; + } else if (max_speed_x > mouse_ballistic->rel_fast_speed_threshold) { + if (max_speed_x < mouse_ballistic->rel_flick_speed_threshold) + rel_stroke_speed = REL_STROKE_SPEED_FAST; + else + rel_stroke_speed = REL_STROKE_SPEED_WARP; + } else { + rel_stroke_speed = REL_STROKE_SPEED_MEDIUM; + } + + return rel_stroke_speed; +} + +int cyapa_filter_iir_rel(struct cyapa_i2c *touch, + int rel_accel_pos, + int *rel_prev_accel_pos, + int *rel_prev_accel_iir_mod) +{ + int old_iir; + int new_iir; + int iir_mod_prev; + int iir_mod_next; + int iir_numerator, iir_denominator; + struct mouse_ballistic_params *mouse_ballistic + = &touch->preferences.mouse_ballistic; + + iir_numerator = mouse_ballistic->rel_iir_acceleration_numerator; + iir_denominator = mouse_ballistic->rel_iir_acceleration_denominator; + + old_iir = *rel_prev_accel_pos; + iir_mod_prev = *rel_prev_accel_iir_mod; + + if ((iir_denominator > 0) && (iir_denominator != iir_numerator)) { + if (old_iir == CYAPA_FILTER_EMPTY_DATA) { + old_iir = rel_accel_pos; + iir_mod_prev = 0; + } + + old_iir *= iir_numerator; + new_iir = rel_accel_pos * (iir_denominator - iir_numerator); + new_iir = new_iir + old_iir + iir_mod_prev; + iir_mod_next = new_iir % iir_denominator; + new_iir = new_iir / iir_denominator; + + *rel_prev_accel_pos = new_iir; + *rel_prev_accel_iir_mod = iir_mod_next; + } else { + *rel_prev_accel_pos = new_iir = rel_accel_pos; + *rel_prev_accel_iir_mod = 0; + } + + return new_iir; +} + +/* Adjusts the Y data as reported by the trackpad FW to meet the shape +** of the user's screen. This is useful to meet some customer test +** requirements, where a diagonal stroke on the TP is supposed to +** cause a like-angled diaginal stroke on the PC display device. +*/ +inline int cyapa_filter_platform_display_aspect_ratio_adjust( + struct cyapa_i2c *touch, + int abs_y) +{ + struct mouse_ballistic_params *mouse_ballistic + = &touch->preferences.mouse_ballistic; + + abs_y *= mouse_ballistic->abs_rise_y; + if ((mouse_ballistic->abs_run_y > 1) + && (mouse_ballistic->abs_run_y < 10000)) { + abs_y = abs_y / mouse_ballistic->abs_run_y; + } + + return abs_y; +} + +static void cyapa_filter_cursor_movement(struct cyapa_i2c *touch, + struct cyapa_report_data *report_data) +{ + int abs_x, abs_y; + int delta_x, delta_y; + int speed_numerator, speed_denominator; + int acceleration_vector_amplitude = 1; + int acceleration_denominator; + int delta_acceleration_x = 0; + int delta_acceleration_y = 0; + struct cyapa_cursor_filters *filter = &touch->cursor_filters; + struct mouse_ballistic_params *mouse_ballistic + = &touch->preferences.mouse_ballistic; + + /* + ** 1. Input ABS raw data. + */ + touch->abs_x = abs_x = report_data->touchs[0].x; + touch->abs_y = abs_y = report_data->touchs[0].y; + + /* adjust Y data as reported by firware to meet the + ** shape of the usre's screen firstly. */ + touch->abs_y = cyapa_filter_platform_display_aspect_ratio_adjust(touch, + touch->abs_y); + + /* + ** 2. Apply FIR filter to ABS raw data. + */ + if (mouse_ballistic->fir_abs_enabled) { + cyapa_fir_reset_fifo_depth(touch); + abs_x = cyapa_filter_fir_abs_x(touch); + abs_y = cyapa_filter_fir_abs_y(touch); + } + + /* + ** 3. Apply IIR filter to FIR filtered ABS data. + */ + if (mouse_ballistic->iir_abs_enabled) { + abs_x = cyapa_filter_iir_abs(touch, + &filter->iir_x, + &filter->iir_mod_x, + abs_x, + mouse_ballistic->iir_numerator, + mouse_ballistic->iir_denominator); + abs_y = cyapa_filter_iir_abs(touch, + &filter->iir_y, + &filter->iir_mod_y, + abs_y, + mouse_ballistic->iir_numerator, + mouse_ballistic->iir_denominator); + touch->abs_x = abs_x; + touch->abs_y = abs_y; + } + + /* + ** 4. Calculate delta movement value. + */ + delta_x = cyapa_compute_rel_motion(touch->abs_x, touch->prev_abs_x); + delta_y = cyapa_compute_rel_motion(touch->abs_y, touch->prev_abs_y); + touch->prev_abs_x = touch->abs_x; + touch->prev_abs_y = touch->abs_y; + + /* + ** 5. Apply speed/acceleration control. + */ + /* Obtain relative movement speed category + ** for use in computing speed coefficients. */ + filter->rel_stroke_speed + = cyapa_rel_get_accel_stoke_type(touch, delta_x, delta_y); + + /* Compute Acceleration Component. */ + if ((touch->prev_rel_x == CYAPA_FILTER_EMPTY_DATA) + || (touch->prev_rel_y == CYAPA_FILTER_EMPTY_DATA)) { + /* debounce before start filter.*/ + touch->prev_rel_x = delta_x; + touch->prev_rel_y = delta_y; + filter->rel_residue_x = 0; + filter->rel_residue_y = 0; + filter->rel_prev_accel_iir_x = CYAPA_FILTER_EMPTY_DATA; + filter->rel_prev_accel_iir_y = CYAPA_FILTER_EMPTY_DATA; + filter->rel_prev_accel_iir_mod_x = 0; + filter->rel_prev_accel_iir_mod_y = 0; + filter->rel_prev_residue_accel_x = 0; + filter->rel_prev_residue_accel_y = 0; + + touch->rel_x = delta_x; + touch->rel_y = delta_y; + report_data->rel_deltaX = delta_x; + report_data->rel_deltaY = delta_y; + + return; + } + + /* sort speed numerator and denominator. */ + if (filter->rel_stroke_speed == REL_STROKE_SPEED_SLOW) { + speed_numerator = mouse_ballistic->rel_motion_numerator; + speed_denominator = mouse_ballistic->rel_motion_denominator; + } else { + speed_numerator = mouse_ballistic->rel_medium_speed_numerator; + speed_denominator = mouse_ballistic->rel_motion_denominator; + } + + /* apply speed/acceleration control. */ + delta_x *= speed_numerator; + delta_y *= speed_numerator; + + if (speed_denominator > 1) { + delta_x += filter->rel_residue_x; + filter->rel_residue_x = delta_x % speed_denominator; + delta_x /= speed_denominator; + + delta_y += filter->rel_residue_y; + filter->rel_residue_y = delta_y % speed_denominator; + delta_y /= speed_denominator; + } + + if (filter->rel_stroke_speed >= REL_STROKE_SPEED_FAST) { + acceleration_vector_amplitude = 10; + acceleration_vector_amplitude + *= mouse_ballistic->rel_acceleration_numerator; + acceleration_denominator + = mouse_ballistic->rel_acceleration_denominator; + + touch->prev_rel_x = delta_x; + touch->prev_rel_y = delta_y; + + delta_acceleration_x = delta_x * acceleration_vector_amplitude; + delta_acceleration_y = delta_y * acceleration_vector_amplitude; + + if (acceleration_denominator > 1) { + delta_acceleration_x + += filter->rel_prev_residue_accel_x; + filter->rel_prev_residue_accel_x = delta_acceleration_x + % acceleration_denominator; + delta_acceleration_x /= acceleration_denominator; + + delta_acceleration_y + += filter->rel_prev_residue_accel_y; + filter->rel_prev_residue_accel_y = delta_acceleration_y + % acceleration_denominator; + delta_acceleration_y /= acceleration_denominator; + } + + delta_x = delta_acceleration_x; + delta_y = delta_acceleration_y; + } + + /* + ** 6. Apply IIR filter to relative data. + */ + delta_x = cyapa_filter_iir_rel(touch, delta_x, + &filter->rel_prev_accel_iir_x, + &filter->rel_prev_accel_iir_mod_x); + delta_y = cyapa_filter_iir_rel(touch, delta_y, + &filter->rel_prev_accel_iir_y, + &filter->rel_prev_accel_iir_mod_y); + + if (delta_x > mouse_ballistic->rel_max_acceleration_speed) + delta_x = mouse_ballistic->rel_max_acceleration_speed; + else if (delta_x < -mouse_ballistic->rel_max_acceleration_speed) + delta_x = -mouse_ballistic->rel_max_acceleration_speed; + + if (delta_y > mouse_ballistic->rel_max_acceleration_speed) + delta_y = mouse_ballistic->rel_max_acceleration_speed; + else if (delta_y < -mouse_ballistic->rel_max_acceleration_speed) + delta_y = -mouse_ballistic->rel_max_acceleration_speed; + + touch->rel_x = delta_x; + touch->rel_y = delta_y; + report_data->rel_deltaX = delta_x; + report_data->rel_deltaY = delta_y; + + return; +} + +static inline void cyapa_report_fingers(struct input_dev *input, int fingers) +{ + if (fingers) { + input_report_key(input, BTN_TOOL_FINGER, (fingers == 1)); + input_report_key(input, BTN_TOOL_DOUBLETAP, (fingers == 2)); + input_report_key(input, BTN_TOOL_TRIPLETAP, (fingers == 3)); + input_report_key(input, BTN_TOOL_QUADTAP, (fingers > 3)); + } else { + input_report_key(input, BTN_TOOL_FINGER, 0); + input_report_key(input, BTN_TOOL_DOUBLETAP, 0); + input_report_key(input, BTN_TOOL_TRIPLETAP, 0); + input_report_key(input, BTN_TOOL_QUADTAP, 0); + } +} + +static void cyapa_process_prev_gesture_report(struct cyapa_i2c *touch, + struct cyapa_report_data *report_data) +{ + int i, j; + unsigned long gesture_diff; + struct input_dev *input = touch->input; + struct input_dev *input_kbd = touch->input_kbd; + + for (i = 0; i < MAX_FINGERS; i++) { + /* get all diffenent gestures in prev and cur. */ + gesture_diff + = touch->prev_active_gestures[i] + ^ touch->cur_active_gestures[i]; + /* get all prev gestures that has been canceled in cur. */ + gesture_diff = gesture_diff & touch->prev_active_gestures[i]; + if (gesture_diff) { + for (j = 0; j < (sizeof(unsigned long)*8); j++) { + /* cancel previous exists gesture. */ + if ((gesture_diff >> j) & 1UL) { + switch (GESTURE_ID_CODE(i, j)) { + case GESTURE_PALM_REJECTIOIN: + break; + case GESTURE_SINGLE_TAP: + break; + case GESTURE_DOUBLE_TAP: + break; + case GESTURE_TAP_AND_HOLD: + break; + case GESTURE_EDGE_MOTION: + break; + case GESTURE_DRAG: + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + + if (touch->platform_data + ->use_absolute_mode) { + input_report_key(input, + BTN_TOUCH, 0); + input_report_abs(input, + ABS_PRESSURE, + 0); + cyapa_report_fingers( + input, 0); + input_report_key(input, + BTN_LEFT, 0); + input_sync(input); + } + + cyapa_reset_cursor_filters_data( + touch); + + break; + case GESTURE_2F_ZOOM_IN: + touch->zoomin_delta = 0; + touch->zoom_trigged = 0; + input_report_key(input_kbd, + KEY_LEFTCTRL, 1); + input_sync(input_kbd); + input_report_key(input_kbd, + KEY_LEFTCTRL, 0); + input_sync(input_kbd); + break; + case GESTURE_2F_ZOOM_OUT: + touch->zoomout_delta = 0; + touch->zoom_trigged = 0; + input_report_key(input_kbd, + KEY_LEFTCTRL, 1); + input_sync(input_kbd); + input_report_key(input_kbd, + KEY_LEFTCTRL, 0); + input_sync(input_kbd); + break; + case GESTURE_SCROLL_UP: + case GESTURE_2F_SCROLL_UP: + touch->delta_scroll_up = 0; + break; + case GESTURE_SCROLL_DOWN: + case GESTURE_2F_SCROLL_DOWN: + touch->delta_scroll_down = 0; + break; + case GESTURE_SCROLL_LEFT: + case GESTURE_2F_SCROLL_LEFT: + input_report_key(input_kbd, + KEY_LEFTSHIFT, 1); + input_sync(input_kbd); + input_report_key(input_kbd, + KEY_LEFTSHIFT, 0); + input_sync(input_kbd); + touch->hscroll_canceled = 1; + touch->hscroll_left = 0; + touch->delta_scroll_left = 0; + break; + case GESTURE_SCROLL_RIGHT: + case GESTURE_2F_SCROLL_RIGHT: + input_report_key(input_kbd, + KEY_LEFTSHIFT, 1); + input_sync(input_kbd); + input_report_key(input_kbd, + KEY_LEFTSHIFT, 0); + input_sync(input_kbd); + touch->hscroll_canceled = 1; + touch->hscroll_right = 0; + touch->delta_scroll_right = 0; + break; + case GESTURE_2F_ROTATE: + break; + case GESTURE_2F_PINCH: + break; + case GESTURE_2F_TAP: + break; + case GESTURE_2F_DRAG: + if (touch->platform_data + ->use_absolute_mode) { + input_report_key(input, + BTN_TOUCH, 0); + input_report_abs(input, + ABS_PRESSURE, + 0); + input_report_key(input, + BTN_LEFT, 0); + cyapa_report_fingers( + input, 0); + input_sync(input); + } + + touch->gesture_2F_drag_started + = 0; + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + break; + case GESTURE_FLICK: + case GESTURE_2F_FLICK: + case GESTURE_3F_FLICK: + case GESTURE_4F_FLICK: + case GESTURE_5F_FLICK: + break; + default: + break; + } + } + } + } + } +} + +static void cyapa_gesture_report(struct cyapa_i2c *touch, + struct cyapa_report_data *report_data, + struct cyapa_gesture *gesture) +{ + struct input_dev *input = touch->input; + struct input_dev *input_wheel = touch->input_wheel; + struct input_dev *input_kbd = touch->input_kbd; + int delta = 0; + struct cyapa_preferences *preferences = &touch->preferences; + int threshold = 0; + int value = 0; + + switch (gesture->id) { + case GESTURE_PALM_REJECTIOIN: + /* when palm rejection gesture is trigged, do not move cursor + ** any more, just operation as no finger touched on trackpad. + */ + if (touch->platform_data->use_absolute_mode) { + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_report_abs(input, ABS_TOOL_WIDTH, 0); + cyapa_report_fingers(input, 0); + } + + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + + input_report_key(input, BTN_LEFT, report_data->button & 0x01); + input_report_key(input, BTN_RIGHT, report_data->button & 0x02); + input_report_key(input, BTN_MIDDLE, report_data->button & 0x04); + + input_sync(input); + + DBGPRINTK(("%s: report palm rejection\n", __func__)); + break; + case GESTURE_SINGLE_TAP: + if (touch->platform_data->use_absolute_mode) { + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_report_key(input, BTN_LEFT, 0); + input_sync(input); + + /* in absolute mode use cyapa_report_fingers(input, N) + ** to trigger click. + ** when N change from small to large, + ** clieck with be trigged.*/ + break; + } + + input_report_key(input, BTN_LEFT, 1); + input_sync(input); + + input_report_key(input, BTN_LEFT, 0); + input_sync(input); + + DBGPRINTK(("%s: report single tap\n", __func__)); + break; + case GESTURE_DOUBLE_TAP: + if (touch->platform_data->use_absolute_mode) { + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_report_key(input, BTN_LEFT, 0); + input_report_key(input, BTN_RIGHT, 0); + input_sync(input); + } + + input_report_key(input, BTN_LEFT, 1); + input_sync(input); + + input_report_key(input, BTN_LEFT, 0); + input_sync(input); + + input_report_key(input, BTN_LEFT, 1); + input_sync(input); + + input_report_key(input, BTN_LEFT, 0); + input_sync(input); + + DBGPRINTK(("%s: report double tap\n", __func__)); + break; + case GESTURE_TAP_AND_HOLD: + /* one finger click and hold for more than definitioin time, + ** then to do something. */ + DBGPRINTK(("%s: no gesture for Tap and hold yet.\n", __func__)); + break; + case GESTURE_EDGE_MOTION: + DBGPRINTK(("%s: no gesture for edge motion yet.\n", __func__)); + break; + case GESTURE_DRAG: + /* 1-finger drag. 1-finger double click and hold, + ** then move the finger. */ + if (touch->platform_data->use_absolute_mode) { + touch->xy_touchs_included_bits = 0x01; + cyapa_calculate_abs_xy(touch, report_data); + + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_X, touch->abs_x); + input_report_abs(input, ABS_Y, touch->abs_y); + input_report_abs(input, ABS_PRESSURE, + report_data->avg_pressure); + cyapa_report_fingers(input, 1); + input_report_key(input, BTN_LEFT, 1); + input_sync(input); + } else { + cyapa_filter_cursor_movement(touch, report_data); + + input_report_rel(input, REL_X, report_data->rel_deltaX); + input_report_rel(input, REL_Y, report_data->rel_deltaY); + input_report_key(input, BTN_LEFT, 1); + input_sync(input); + } + + DBGPRINTK(("%s: 1 finger drag.\n", __func__)); + break; + case GESTURE_2F_ZOOM_IN: + delta = gesture->param2; + /* no zoom delta. */ + if (delta <= 0) + break; + touch->zoomin_delta += delta; + + if (touch->zoom_trigged == 0) { + input_report_key(input_kbd, KEY_LEFTCTRL, 0); + input_sync(input_kbd); + input_report_key(input_kbd, KEY_LEFTCTRL, 1); + input_sync(input_kbd); + touch->zoom_trigged = 1; + } else { + if (touch->zoomin_delta + <= preferences->zoom.default_threshold) { + threshold = 0; + value = 1; + } else if (touch->zoomin_delta + > preferences->zoom.fast_threshold) { + threshold = 2; + value = touch->zoomin_delta + / preferences->zoom.fast_threshold; + touch->zoomin_delta + %= preferences->zoom.fast_threshold; + } else { + threshold = 1; + value = 1; + touch->zoomin_delta = 0; + } + + while (threshold > 0) { + input_report_rel(input_wheel, REL_WHEEL, value); + input_sync(input_wheel); + threshold--; + } + } + + DBGPRINTK(("%s: 2F zoom in.\n", __func__)); + break; + case GESTURE_2F_ZOOM_OUT: + delta = gesture->param2; + /* no zoom delta. */ + if (delta <= 0) + break; + touch->zoomout_delta += delta; + + if (touch->zoom_trigged == 0) { + input_report_key(input_kbd, KEY_LEFTCTRL, 0); + input_sync(input_kbd); + input_report_key(input_kbd, KEY_LEFTCTRL, 1); + input_sync(input_kbd); + touch->zoom_trigged = 1; + } else { + value = 1; + if (touch->zoomout_delta + <= preferences->zoom.default_threshold) { + threshold = 0; + value = 1; + } else if (touch->zoomout_delta + > preferences->zoom.fast_threshold) { + threshold = 2; + value = touch->zoomout_delta + / preferences->zoom.fast_threshold; + touch->zoomout_delta + %= preferences->zoom.fast_threshold; + } else { + threshold = 1; + value = 1; + touch->zoomout_delta = 0; + } + + while (threshold > 0) { + input_report_rel(input_wheel, + REL_WHEEL, + -value); + input_sync(input_wheel); + threshold--; + } + } + + DBGPRINTK(("%s: 2F zoom out.\n", __func__)); + break; + case GESTURE_SCROLL_UP: + case GESTURE_2F_SCROLL_UP: + if (touch->hscroll_canceled) { + /* avoid VScroll miss trigged as HScroll. */ + touch->hscroll_canceled = 0; + input_report_key(input_kbd, KEY_LEFTSHIFT, 0); + input_sync(input_kbd); + + break; + } + + delta = gesture->param2; + threshold = preferences->vscroll.default_threshold; + value = 1; + touch->delta_scroll_up += delta; + + if (touch->delta_scroll_up <= 0) { + /* no scroll move, it's not need to report. */ + touch->delta_scroll_up = 0; + break; + } + + if (touch->delta_scroll_up < threshold) { + /* keep small movement also can work. */ + input_report_rel(input_wheel, REL_WHEEL, value); + input_sync(input_wheel); + + touch->delta_scroll_up = 0; + break; + } + + if (touch->delta_scroll_up + > preferences->vscroll.fast_threshold) { + /* fast scroll, reset threshold value. */ + threshold = 1; + value = 16; + } else { + /* middle scroll speed. */ + threshold = 2; + value = 2; + } + + while (touch->delta_scroll_up >= threshold) { + input_report_rel(input_wheel, + REL_WHEEL, + value*2/threshold); + input_sync(input_wheel); + + touch->delta_scroll_up -= threshold*value; + } + + DBGPRINTK(("%s: scroll up, fingers=%d\n", + __func__, report_data->touch_fingers)); + break; + case GESTURE_SCROLL_DOWN: + case GESTURE_2F_SCROLL_DOWN: + if (touch->hscroll_canceled) { + /* avoid VScroll miss trigged as HScroll. */ + touch->hscroll_canceled = 0; + input_report_key(input_kbd, KEY_LEFTSHIFT, 0); + input_sync(input_kbd); + + break; + } + + delta = gesture->param2; + threshold = preferences->vscroll.default_threshold; + value = 1; + touch->delta_scroll_down += delta; + + if (touch->delta_scroll_down <= 0) { + /* no scroll move, it's not need to report. */ + touch->delta_scroll_down = 0; + break; + } + + if (touch->delta_scroll_down < threshold) { + /* keep small movement also can work. */ + input_report_rel(input_wheel, REL_WHEEL, -value); + input_sync(input_wheel); + + touch->delta_scroll_down = 0; + break; + } + + if (touch->delta_scroll_down + > preferences->vscroll.fast_threshold) { + /* fast scroll, reset threshold value. */ + threshold = 1; + value = 16; + } else { + /* middle scroll speed. */ + threshold = 2; + value = 2; + } + + while (touch->delta_scroll_down >= threshold) { + input_report_rel(input_wheel, + REL_WHEEL, + -value*2/threshold); + input_sync(input_wheel); + + touch->delta_scroll_down -= threshold*value; + } + + DBGPRINTK(("%s: scroll down, finger=%d\n", + __func__, report_data->touch_fingers)); + break; + case GESTURE_SCROLL_LEFT: + case GESTURE_2F_SCROLL_LEFT: + delta = gesture->param2; + if (0 == touch->hscroll_left) { + /* to report left shift firstly inorder to avoid miss + ** trig vscroll. because, for 0.9.101 chromium os, + ** it seems that when left shift is pressed, + ** then scroll soon, the combined gesture won't + ** take effect soon, it will have some delay. + ** So add a debounce to avoid this issue. + */ + input_report_key(input_kbd, KEY_LEFTSHIFT, 0); + input_sync(input_kbd); + input_report_key(input_kbd, KEY_LEFTSHIFT, 1); + input_sync(input_kbd); + touch->hscroll_left = delta; + } else { + threshold = preferences->hscroll.default_threshold; + value = 1; + touch->delta_scroll_left += delta; + + if (touch->delta_scroll_left <= 0) { + /* no scroll move, it's not need to report. */ + touch->delta_scroll_left = 0; + break; + } + + if (touch->delta_scroll_left < threshold) { + /* keep small movement also can work. */ + input_report_rel(input_wheel, REL_WHEEL, value); + input_sync(input_wheel); + + touch->delta_scroll_left = 0; + break; + } + + if (touch->delta_scroll_left + > preferences->hscroll.fast_threshold) { + /* fast scroll, reset threshold value. */ + threshold = 1; + value = 16; + } else { + /* middle scroll speed. */ + threshold = 2; + value = 2; + } + + while (touch->delta_scroll_left >= threshold) { + input_report_rel(input_wheel, + REL_WHEEL, + value*2/threshold); + input_sync(input_wheel); + + touch->delta_scroll_left -= threshold*value; + } + } + + DBGPRINTK(("%s: scroll left, finger=%d\n", + __func__, report_data->touch_fingers)); + break; + case GESTURE_SCROLL_RIGHT: + case GESTURE_2F_SCROLL_RIGHT: + delta = gesture->param2; + if (0 == touch->hscroll_right) { + input_report_key(input_kbd, KEY_LEFTSHIFT, 0); + input_sync(input_kbd); + input_report_key(input_kbd, KEY_LEFTSHIFT, 1); + input_sync(input_kbd); + touch->hscroll_right = delta; + } else { + threshold = preferences->hscroll.default_threshold; + value = 1; + touch->delta_scroll_right += delta; + + if (touch->delta_scroll_right <= 0) { + /* no scroll move, it's not need to report. */ + touch->delta_scroll_right = 0; + break; + } + + if (touch->delta_scroll_right < threshold) { + /* keep small movement also can work. */ + input_report_rel(input_wheel, + REL_WHEEL, + -value); + input_sync(input_wheel); + + touch->delta_scroll_right = 0; + break; + } + + if (touch->delta_scroll_right + > preferences->hscroll.fast_threshold) { + /* fast scroll, reset threshold value. */ + threshold = 1; + value = 16; + } else { + /* middle scroll speed. */ + threshold = 2; + value = 2; + } + + while (touch->delta_scroll_right >= threshold) { + input_report_rel(input_wheel, + REL_WHEEL, + -value*2/threshold); + input_sync(input_wheel); + + touch->delta_scroll_right -= threshold*value; + } + } + + DBGPRINTK(("%s: scroll right, finger=%d\n", + __func__, report_data->touch_fingers)); + break; + case GESTURE_2F_ROTATE: + DBGPRINTK(("%s: 2 finger rotate.\n", __func__)); + break; + case GESTURE_2F_PINCH: + DBGPRINTK(("%s: 2 finger pinch.\n", __func__)); + break; + case GESTURE_2F_TAP: + /* 2-finger tap, active like right button press and relase. */ + if (touch->platform_data->use_absolute_mode) { + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_report_key(input, BTN_LEFT, 0); + input_report_key(input, BTN_RIGHT, 0); + input_sync(input); + } + + input_report_key(input, BTN_RIGHT, 1); + input_sync(input); + + input_report_key(input, BTN_RIGHT, 0); + input_sync(input); + + DBGPRINTK(("%s: report 2 fingers tap, \ + active like right button.\n", __func__)); + break; + case GESTURE_2F_DRAG: + /* first finger click and hold, + ** and second finger moving for dragging. */ + if (touch->gesture_2F_drag_started == 0) { + touch->xy_touchs_included_bits = 0x01; + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + cyapa_calculate_abs_xy(touch, report_data); + + /* firstly, move move cursor to the target for drag. */ + input_report_key(input, BTN_TOUCH, 1); + if (touch->platform_data->use_absolute_mode) { + input_report_abs(input, ABS_X, touch->abs_x); + input_report_abs(input, ABS_Y, touch->abs_y); + input_report_abs(input, + ABS_PRESSURE, + report_data->avg_pressure); + cyapa_report_fingers(input, 1); + } + input_report_key(input, BTN_LEFT, 0); + input_report_key(input, BTN_RIGHT, 0); + input_sync(input); + + /* second, stop cursor on the target for drag. */ + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + if (touch->platform_data->use_absolute_mode) { + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_sync(input); + } + + /* third, select the target for drag. */ + input_report_key(input, BTN_LEFT, 1); + input_sync(input); + + /* go to step four. */ + touch->gesture_2F_drag_started = 1; + } + + /* fourth, move cursor for dragging. */ + touch->xy_touchs_included_bits = 0x02; + cyapa_calculate_abs_xy(touch, report_data); + + if (touch->platform_data->use_absolute_mode) { + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_X, touch->abs_x); + input_report_abs(input, ABS_Y, touch->abs_y); + input_report_abs(input, + ABS_PRESSURE, + report_data->avg_pressure); + cyapa_report_fingers(input, 1); + } else { + input_report_rel(input, REL_X, report_data->rel_deltaX); + input_report_rel(input, REL_Y, report_data->rel_deltaY); + input_sync(input); + } + input_report_key(input, BTN_LEFT, 1); + input_sync(input); + + DBGPRINTK(("%s: report 2 fingers drag\n", __func__)); + break; + case GESTURE_FLICK: + case GESTURE_2F_FLICK: + case GESTURE_3F_FLICK: + case GESTURE_4F_FLICK: + case GESTURE_5F_FLICK: + touch->xy_touchs_included_bits = report_data->touch_fingers; + DBGPRINTK(("%s: no flick gesture supported yet, , finger=%d\n", + __func__, report_data->touch_fingers)); + break; + default: + DBGPRINTK(("%s: default, unknown gesture for reporting.\n", + __func__)); + break; + } +} + +static int cyapa_rel_input_report_data(struct cyapa_i2c *touch, + struct cyapa_report_data *report_data) +{ + int i; + struct input_dev *input = touch->input; + + /* step 1: process gestures firstly if trigged. */ + cyapa_process_prev_gesture_report(touch, report_data); + if (report_data->gestures_count > 0) { + DBGPRINTK(("%s: do gesture report, gestures_count = %d\n", + __func__, report_data->gestures_count)); + /* gesture trigged */ + for (i = 0; i < report_data->gestures_count; i++) { + cyapa_gesture_report(touch, + report_data, + &report_data->gestures[i]); + } + + /* when gestures are trigged, cursor should not move. */ + } else { + /* when multi-fingers touched, cursour should also not move. */ + if (report_data->touch_fingers == 1) { + /* apply cursor movement filters to + ** improve cursor performance. */ + cyapa_filter_cursor_movement(touch, report_data); + + /* Report the deltas */ + input_report_rel(input, REL_X, report_data->rel_deltaX); + input_report_rel(input, REL_Y, report_data->rel_deltaY); + } else { + cyapa_reset_cursor_filters_data(touch); + } + + /* Report the button event */ + input_report_key(input, BTN_LEFT, + (report_data->button & 0x01)); + input_report_key(input, BTN_RIGHT, + (report_data->button & 0x02)); + input_report_key(input, BTN_MIDDLE, + (report_data->button & 0x04)); + input_sync(input); + } + + DBGPRINTK(("%s: deltax = %d\n", __func__, report_data->rel_deltaX)); + DBGPRINTK(("%s: deltay = %d\n", __func__, report_data->rel_deltaY)); + DBGPRINTK(("%s: left_btn = %d\n", + __func__, report_data->button & 0x01)); + DBGPRINTK(("%s: right_btn = %d\n", + __func__, report_data->button & 0x02)); + DBGPRINTK(("%s: middle_btn = %d\n", + __func__, report_data->button & 0x04)); + + /* store current active gestures array into + ** prev active gesture array. */ + for (i = 0; i < MAX_FINGERS; i++) + touch->prev_active_gestures[i] = touch->cur_active_gestures[i]; + touch->prev_touch_fingers = report_data->touch_fingers; + + return report_data->gestures_count | report_data->rel_deltaX + |report_data->rel_deltaY | report_data->button; +} + +static int cyapa_abs_input_report_data(struct cyapa_i2c *touch, + struct cyapa_report_data *report_data) +{ + int i; + int have_data = 0; + struct input_dev *input = touch->input; + + DBGPRINTK(("%s: ...\n", __func__)); + + cyapa_process_prev_gesture_report(touch, report_data); + if (report_data->gestures_count > 0) { + DBGPRINTK(("%s: do gesture report, gestures_count = %d\n", + __func__, report_data->gestures_count)); + /* gesture trigged */ + for (i = 0; i < report_data->gestures_count; i++) { + cyapa_gesture_report(touch, report_data, + &report_data->gestures[i]); + } + } else if (report_data->touch_fingers) { + /* no gesture trigged, report touchs move data. */ + if (report_data->touch_fingers > 1) { + DBGPRINTK(("%s: more then 1 finger touch, \ + touch_fingers = %d\n", + __func__, report_data->touch_fingers)); + /* + ** two and much more finger on trackpad are used for + ** gesture only, so even no gesture are trigged, + ** do not make cursor move also. + ** Here, must keep on report finger touched, otherwise, + ** when multi-finger touch not in same time will + ** triiged clikc. + */ + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_PRESSURE, + report_data->avg_pressure); + input_report_abs(input, ABS_TOOL_WIDTH, + CYAPA_TOOL_WIDTH); + #if GESTURE_MULTI_TOUCH_ONE_CLICK + cyapa_report_fingers(input, report_data->touch_fingers); + #else + cyapa_report_fingers(input, 1); + #endif + + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + + input_report_key(input, BTN_LEFT, + report_data->button & 0x01); + input_report_key(input, BTN_RIGHT, + report_data->button & 0x02); + input_report_key(input, BTN_MIDDLE, + report_data->button & 0x04); + + input_sync(input); + } else { + DBGPRINTK(("%s: 1 finger touch, make cursor move\n", + __func__)); + /* avoid cursor jump, when touched finger changed + ** from multi-touch to one finger touch. */ + if (touch->prev_touch_fingers > 1) { + /* cheat system or application that no finger + ** has touched to may them + ** lock the cursor when later only one finger + ** touched on trackpad. */ + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_report_abs(input, ABS_TOOL_WIDTH, 0); + cyapa_report_fingers(input, 0); + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + input_report_key(input, BTN_LEFT, + report_data->button & 0x01); + input_report_key(input, BTN_RIGHT, + report_data->button & 0x02); + input_report_key(input, BTN_MIDDLE, + report_data->button & 0x04); + input_sync(input); + } else { + /* only 1 finger can make cursor move. */ + touch->xy_touchs_included_bits = 0x01; + cyapa_calculate_abs_xy(touch, report_data); + + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_X, touch->abs_x); + input_report_abs(input, ABS_Y, touch->abs_y); + input_report_abs(input, + ABS_PRESSURE, + report_data->avg_pressure); + input_report_abs(input, ABS_TOOL_WIDTH, + CYAPA_TOOL_WIDTH); + + cyapa_report_fingers(input, + report_data->touch_fingers); + + input_report_key(input, BTN_LEFT, + report_data->button & 0x01); + input_report_key(input, BTN_RIGHT, + report_data->button & 0x02); + input_report_key(input, BTN_MIDDLE, + report_data->button & 0x04); + + input_sync(input); + } + } + } else { + /* + ** 1. two or more fingers on trackpad are used for gesture only, + ** so even no gesture are trigged, do not make cursor move also. + ** 2. no gesture and no touch on trackpad. + */ + DBGPRINTK(("%s: no finger touch.\n", __func__)); + + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_report_abs(input, ABS_TOOL_WIDTH, 0); + cyapa_report_fingers(input, 0); + + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + + input_report_key(input, BTN_LEFT, report_data->button & 0x01); + input_report_key(input, BTN_RIGHT, report_data->button & 0x02); + input_report_key(input, BTN_MIDDLE, report_data->button & 0x04); + + input_sync(input); + } + + /* store current active gestures array into + ** prev active gesture array. */ + for (i = 0; i < MAX_FINGERS; i++) + touch->prev_active_gestures[i] = touch->cur_active_gestures[i]; + touch->prev_touch_fingers = report_data->touch_fingers; + + have_data = (report_data->gestures_count + + report_data->touch_fingers + report_data->button); + + DBGPRINTK(("%s: gesture count = %d, touch finger =%d, \ + button = 0x%02x\n", __func__, report_data->gestures_count, + report_data->touch_fingers, report_data->button)); + return have_data; +} + +static bool cyapa_i2c_get_input(struct cyapa_i2c *touch) +{ + int i; + int ret_read_size = -1; + int read_length = 0; + union cyapa_reg_data reg_data; + struct cyapa_reg_data_gen1 *gen1_data; + struct cyapa_reg_data_gen2 *gen2_data; + struct cyapa_report_data report_data; + + DBGPRINTK(("%s: start ...\n", __func__)); + + memset(®_data, 0, sizeof(union cyapa_reg_data)); + + /* read register data from trackpad. */ + gen1_data = ®_data.gen1_data; + gen2_data = ®_data.gen2_data; + read_length = CYAPA_REL_REG_DATA_SIZE; + if (touch->platform_data->gen == CYAPA_GEN1) + read_length = (int)sizeof(struct cyapa_reg_data_gen1); + else + read_length = (int)sizeof(struct cyapa_reg_data_gen2); + DBGPRINTK(("%s: read gen%d data, read length=%d\n", __func__, + ((touch->platform_data->gen == CYAPA_GEN1) ? 1 : 2), + read_length)); + ret_read_size = cyapa_i2c_reg_read_block(touch->client, + DATA_REG_START_OFFSET, + read_length, + (u8 *)®_data); + if (ret_read_size < 0) { + DBGPRINTK(("%s: I2C read data from trackpad error = %d\n", + __func__, ret_read_size)); + return 0; + } + + if (cyapa_verify_data_device(touch, ®_data)) { + DBGPRINTK(("%s: verify data device failed, \ + invalid data, skip.\n", __func__)); + return 0; + } + + /* process and parse raw data that read from Trackpad. */ + memset(&report_data, 0, sizeof(struct cyapa_report_data)); + touch->xy_touchs_included_bits = 0; + /* initialize current active gestures array. */ + for (i = 0; i < MAX_FINGERS; i++) + touch->cur_active_gestures[i] = 0; + + if (touch->platform_data->gen == CYAPA_GEN1) + cyapa_parse_gen1_data(touch, gen1_data, &report_data); + else + cyapa_parse_gen2_data(touch, gen2_data, &report_data); + + /* report data to input subsystem. */ + if (touch->platform_data->use_absolute_mode == false) + return cyapa_rel_input_report_data(touch, &report_data); + else + return cyapa_abs_input_report_data(touch, &report_data); +} + +static void cyapa_i2c_reschedule_work(struct cyapa_i2c *touch, + unsigned long delay) +{ + unsigned long flags; + + spin_lock_irqsave(&touch->lock, flags); + + /* + * If work is already scheduled then subsequent schedules will not + * change the scheduled time that's why we have to cancel it first. + */ + __cancel_delayed_work(&touch->dwork); + schedule_delayed_work(&touch->dwork, delay); + + spin_unlock_irqrestore(&touch->lock, flags); +} + +static irqreturn_t cyapa_i2c_irq(int irq, void *dev_id) +{ + struct cyapa_i2c *touch = dev_id; + + DBGPRINTK(("%s: trackpad interrupt captured. \ + report_rate=%d; read_pending=%d\n", + __func__, touch->platform_data->report_rate, + touch->read_pending)); + + if (touch->platform_data->report_rate == 0) { + /* + ** no limitatioin for data reporting. + ** the report rate depending on trackpad max report rate. + ** this is the default report mode. + */ + cyapa_i2c_reschedule_work(touch, 0); + } else { + /* + ** when use limited report rate, some important data packages + ** may be lost. Such as a tap or double tap gesture may be lost. + ** So firmware need to keep this data until there data is read. + */ + if (!touch->read_pending) { + touch->read_pending = 1; + cyapa_i2c_reschedule_work(touch, touch->scan_ms); + } + } + + return IRQ_HANDLED; +} + +/* Control the Device polling rate / Work Handler sleep time */ +static unsigned long cyapa_i2c_adjust_delay(struct cyapa_i2c *touch, + bool have_data) +{ + unsigned long delay, nodata_count_thres; + + if (touch->platform_data->use_polling_mode) { + delay = touch->platform_data->polling_interval_time_active; + if (have_data) { + touch->no_data_count = 0; + } else { + nodata_count_thres + = CYAPA_NO_DATA_THRES / touch->scan_ms; + if (touch->no_data_count < nodata_count_thres) + touch->no_data_count++; + else + delay = CYAPA_NO_DATA_SLEEP_MSECS; + } + return msecs_to_jiffies(delay); + } else { + delay = msecs_to_jiffies(CYAPA_THREAD_IRQ_SLEEP_MSECS); + return round_jiffies_relative(delay); + } +} + +/* Work Handler */ +static void cyapa_i2c_work_handler(struct work_struct *work) +{ + bool have_data; + struct cyapa_i2c *touch + = container_of(work, struct cyapa_i2c, dwork.work); + unsigned long delay; + + DBGPRINTK(("%s: start ...\n", __func__)); + + have_data = cyapa_i2c_get_input(touch); + + /* + * While interrupt driven, there is no real need to poll the device. + * But touchpads are very sensitive, so there could be errors + * related to physical environment and the attention line isn't + * neccesarily asserted. In such case we can lose the touchpad. + * We poll the device once in CYAPA_THREAD_IRQ_SLEEP_SECS and + * if error is detected, we try to reset and reconfigure the touchpad. + */ + delay = cyapa_i2c_adjust_delay(touch, have_data); + /* if needs fixed interval time trackpad scan, open it. + cyapa_i2c_reschedule_work(touch, delay); + */ + + touch->read_pending = 0; + + DBGPRINTK(("%s: done ...\n", __func__)); +} + +static int cyapa_i2c_open(struct input_dev *input) +{ + struct cyapa_i2c *touch = input_get_drvdata(input); + int retval; + + if (0 == touch->open_count) { + /* Since input_dev mouse, wheel, and kbd will all use same open + ** and close routines. But indeed, reset config to trackpad + ** once is enought,So when trackpad is open for the first time, + ** reset it. for other time not do it. + */ + retval = cyapa_i2c_reset_config(touch); + if (retval) { + DBGPRINTK(("%s: failed to reset i2c trackpad. \ + error = %d\n", __func__, retval)); + return retval; + } + } + touch->open_count++; + + if (touch->platform_data->use_polling_mode) { + /* + ** for the firstly time, it is set to CYAPA_NO_DATA_SLEEP_MSECS, + ** when data is read from trackpad, the read speed will + ** be pull up. + */ + cyapa_i2c_reschedule_work(touch, + msecs_to_jiffies(CYAPA_NO_DATA_SLEEP_MSECS)); + } + + DBGPRINTK(("%s: touch->open_count = %d ...\n", + __func__, touch->open_count)); + + return 0; +} + +static void cyapa_i2c_close(struct input_dev *input) +{ + struct cyapa_i2c *touch = input_get_drvdata(input); + + touch->open_count--; + + if (0 == touch->open_count) { + /* Since input_dev mouse, wheel, and kbd will all use same open + ** and close routines. + ** so when all mouse, wheel and kbd input_dev is closed, + ** then cancel the delayed work routine. + */ + cancel_delayed_work_sync(&touch->dwork); + } + + DBGPRINTK(("%s: touch->open_count=%d\n", __func__, touch->open_count)); +} + +static struct cyapa_i2c *cyapa_i2c_touch_create(struct i2c_client *client) +{ + struct cyapa_i2c *touch; + + touch = kzalloc(sizeof(struct cyapa_i2c), GFP_KERNEL); + if (!touch) + return NULL; + + DBGPRINTK(("%s: client=0x%p, allocate memory for touch successfully.\n", + __func__, client)); + + touch->platform_data = &cyapa_i2c_platform_data; + if (client->dev.platform_data) { + DBGPRINTK(("%s: client->dev.platform_data is set, copy it.\n", + __func__)); + *touch->platform_data + = *(struct cyapa_platform_data *) + client->dev.platform_data; + } + + cyapa_print_paltform_data(__func__, touch->platform_data); + + if (touch->platform_data->use_polling_mode && + (touch->platform_data->report_rate == 0)) { + /* when user miss setting platform data, + ** ensure that system is robust. + ** no divid zero error. */ + touch->platform_data->report_rate + = CYAPA_POLLING_REPORTRATE_DEFAULT; + } + touch->scan_ms = touch->platform_data->report_rate + ? (1000 / touch->platform_data->report_rate) : 0; + touch->open_count = 0; + touch->prev_abs_x = -1; + touch->prev_abs_y = -1; + touch->client = client; + touch->zoomin_delta = 0; + touch->zoomout_delta = 0; + touch->hscroll_left = 0; + touch->hscroll_right = 0; + touch->prev_touch_fingers = 0; + + cyapa_set_preferences(touch); + cyapa_reset_cursor_filters_data(touch); + + INIT_DELAYED_WORK(&touch->dwork, cyapa_i2c_work_handler); + spin_lock_init(&touch->lock); + + return touch; +} + +static int cyapa_create_input_dev_mouse(struct cyapa_i2c *touch) +{ + int retval = 0; + struct input_dev *input = NULL; + + input = touch->input = input_allocate_device(); + if (!touch->input) { + dev_err(&touch->client->dev, + "%s: Allocate memory for Input device failed: %d\n", + __func__, retval); + return -ENOMEM; + } + + input->name = "cyapa_i2c_trackpad"; + input->phys = touch->client->adapter->name; + input->id.bustype = BUS_I2C; + input->id.version = 1; + input->dev.parent = &touch->client->dev; + + input->open = cyapa_i2c_open; + input->close = cyapa_i2c_close; + input_set_drvdata(input, touch); + + if (touch->platform_data->use_absolute_mode) { + /* absolution data report mode. */ + __set_bit(EV_ABS, input->evbit); + __set_bit(EV_KEY, input->evbit); + + input_set_abs_params(input, ABS_X, 0, + touch->max_absolution_x, 0, 0); + input_set_abs_params(input, ABS_Y, 0, + touch->max_absolution_y, 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, 255, 0, 0); + input_set_abs_params(input, ABS_TOOL_WIDTH, 0, 255, 0, 0); + + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + __set_bit(BTN_TOOL_DOUBLETAP, input->keybit); + __set_bit(BTN_TOOL_TRIPLETAP, input->keybit); + __set_bit(BTN_TOOL_QUADTAP, input->keybit); + + __set_bit(BTN_LEFT, input->keybit); + __set_bit(BTN_RIGHT, input->keybit); + __set_bit(BTN_MIDDLE, input->keybit); + + __clear_bit(EV_REL, input->evbit); + __clear_bit(REL_X, input->relbit); + __clear_bit(REL_Y, input->relbit); + __clear_bit(BTN_TRIGGER, input->keybit); + + input_abs_set_res(input, + ABS_X, + touch->max_absolution_x/touch->physical_size_x); + input_abs_set_res(input, + ABS_Y, + touch->max_absolution_y/touch->physical_size_y); + + DBGPRINTK(("%s: Use absolute data reporting mode.\n", + __func__)); + } else { + /* relative data reporting mode. */ + __set_bit(EV_REL, input->evbit); + __set_bit(REL_X, input->relbit); + __set_bit(REL_Y, input->relbit); + + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_LEFT, input->keybit); + __set_bit(BTN_RIGHT, input->keybit); + __set_bit(BTN_MIDDLE, input->keybit); + + __clear_bit(EV_ABS, input->evbit); + + DBGPRINTK(("%s: Use relative data reporting mode.\n", + __func__)); + } + + /* Register the device in input subsystem */ + retval = input_register_device(touch->input); + if (retval) { + dev_err(&touch->client->dev, + "%s: Input device register failed: %d\n", + __func__, retval); + + input_free_device(input); + return retval; + } + + return 0; +} + +static int cyapa_create_input_dev_wheel(struct cyapa_i2c *touch) +{ + int retval = 0; + struct input_dev *input_wheel = NULL; + + input_wheel = touch->input_wheel = input_allocate_device(); + if (!touch->input_wheel) { + dev_err(&touch->client->dev, + "%s: Allocate memory for Input device failed: %d\n", + __func__, retval); + return -ENOMEM; + } + + input_wheel->name = "cyapa_i2c_wheel"; + input_wheel->phys = touch->client->adapter->name; + input_wheel->id.bustype = BUS_I2C; + input_wheel->id.version = 1; + input_wheel->dev.parent = &touch->client->dev; + input_wheel->open = cyapa_i2c_open; + input_wheel->close = cyapa_i2c_close; + input_set_drvdata(input_wheel, touch); + + __set_bit(EV_KEY, input_wheel->evbit); + __set_bit(EV_REL, input_wheel->evbit); + __set_bit(REL_WHEEL, input_wheel->relbit); + + retval = input_register_device(touch->input_wheel); + if (retval) { + dev_err(&touch->client->dev, + "%s: Input device register failed: %d\n", + __func__, retval); + + input_free_device(input_wheel); + return retval; + } + + return 0; +} + +#define MAX_NR_SCANCODES 128 + +static unsigned char cyapa_virtual_keycode[MAX_NR_SCANCODES] = { +/* Bellow keys are supported. +KEY_ENTER 28 +KEY_LEFTCTRL 29 +KEY_LEFTSHIFT 42 +KEY_RIGHTSHIFT 54 +KEY_LEFTALT 56 +KEY_KPMINUS 74 +KEY_KPPLUS 78 +KEY_RIGHTCTRL 97 +KEY_RIGHTALT 100 +KEY_HOME 102 +KEY_UP 103 +KEY_PAGEUP 104 +KEY_LEFT 105 +KEY_RIGHT 106 +KEY_END 107 +KEY_DOWN 108 +KEY_PAGEDOWN 109 +*/ + 28, 29, 42, 54, 56, 74, 78, 97, 100, + 102, 103, 104, 105, 106, 107, 108, 109 +}; + +static int cyapa_create_input_dev_kbd(struct cyapa_i2c *touch) +{ + int retval = 0; + int i; + struct input_dev *input_kbd = NULL; + + input_kbd = touch->input_kbd = input_allocate_device(); + if (!touch->input_kbd) { + dev_err(&touch->client->dev, + "%s: Allocate memory for Input device failed: %d\n", + __func__, retval); + return -ENOMEM; + } + + input_kbd->name = "cyapa_i2c_virtual_kbd"; + input_kbd->phys = touch->client->adapter->name; + input_kbd->id.bustype = BUS_I2C; + input_kbd->id.version = 1; + input_kbd->dev.parent = &touch->client->dev; + input_kbd->open = cyapa_i2c_open; + input_kbd->close = cyapa_i2c_close; + input_set_drvdata(input_kbd, touch); + + input_kbd->keycode = &cyapa_virtual_keycode; + input_kbd->keycodesize = sizeof(unsigned char); + input_kbd->keycodemax = ARRAY_SIZE(cyapa_virtual_keycode); + + __set_bit(EV_KEY, input_kbd->evbit); + __set_bit(EV_REP, input_kbd->evbit); + + for (i = 0; i < ARRAY_SIZE(cyapa_virtual_keycode); i++) + __set_bit(cyapa_virtual_keycode[i], input_kbd->keybit); + __clear_bit(KEY_RESERVED, input_kbd->keybit); + + retval = input_register_device(touch->input_kbd); + if (retval) { + dev_err(&touch->client->dev, + "%s: Input device register failed: %d\n", + __func__, retval); + + input_free_device(input_kbd); + return retval; + } + + return 0; +} + +static int __devinit cyapa_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int retval = 0; + struct cyapa_i2c *touch; + + DBGPRINTK(("%s: start ...\n", __func__)); + touch = cyapa_i2c_touch_create(client); + if (!touch) + return -ENOMEM; + + /* do platfrom initialize firstly. */ + if (touch->platform_data->init) + retval = touch->platform_data->init(); + if (retval) + goto err_mem_free; + + /* set irq number if not using polling mode. */ + if (touch->platform_data->use_polling_mode == true) { + touch->irq = -1; + } else { + if (touch->platform_data->irq_gpio == -1) { + if (client->irq) { + touch->irq = client->irq; + } else { + /* irq mode is not supported by system. */ + touch->platform_data->use_polling_mode = true; + touch->irq = -1; + } + } else { + touch->irq + = gpio_to_irq(touch->platform_data->irq_gpio); + } + } + DBGPRINTK(("%s: irq=%d, client->irq=%d\n", + __func__, touch->irq, client->irq)); + + if (touch->platform_data->use_polling_mode == false) { + DBGPRINTK(("%s: request interrupt riq.\n", __func__)); + + set_irq_type(touch->irq, IRQF_TRIGGER_FALLING); + retval = request_irq(touch->irq, + cyapa_i2c_irq, + 0, + CYAPA_I2C_NAME, + touch); + if (retval) { + dev_warn(&touch->client->dev, + "%s: IRQ request failed: \ + %d, falling back to polling mode.\n", + __func__, retval); + + touch->platform_data->use_polling_mode = true; + } + } + + /* reconfig trackpad depending on platfrom setting. */ + /* Should disable interrupt to protect this polling read operation. + ** Ohterwise, this I2C read will be interrupt by other reading, + ** and failed. */ + disable_irq(touch->irq); + cyapa_i2c_reconfig(touch); + enable_irq(touch->irq); + + /* create an input_dev instance for virtual mouse trackpad. */ + retval = cyapa_create_input_dev_mouse(touch); + if (retval) { + DBGPRINTK(("%s: create mouse input_dev instance filed.\n", + __func__)); + goto err_mem_free; + } + + /* create an input_dev instances for virtual wheel device + ** and virtual keyboard device. */ + retval = cyapa_create_input_dev_wheel(touch); + if (retval) { + DBGPRINTK(("%s: create input_dev instance for wheel filed.\n", + __func__)); + goto err_mem_free; + } + + retval = cyapa_create_input_dev_kbd(touch); + if (retval) { + DBGPRINTK(("%s: create keyboad input_dev instance filed.\n", + __func__)); + goto err_mem_free; + } + + i2c_set_clientdata(client, touch); + + DBGPRINTK(("%s: Done successfully.\n", __func__)); + + return 0; + +err_mem_free: + /* release previous allocated input_dev instances. */ + if (touch->input) { + input_free_device(touch->input); + touch->input = NULL; + } + + if (touch->input_wheel) { + input_free_device(touch->input_wheel); + touch->input_wheel = NULL; + } + + if (touch->input_kbd) { + input_free_device(touch->input_kbd); + touch->input_kbd = NULL; + } + + kfree(touch); + + DBGPRINTK(("%s: exist with error %d.\n", __func__, retval)); + return retval; +} + +static int __devexit cyapa_i2c_remove(struct i2c_client *client) +{ + struct cyapa_i2c *touch = i2c_get_clientdata(client); + + if (!touch->platform_data->use_polling_mode) + free_irq(client->irq, touch); + + if (touch->input) + input_unregister_device(touch->input); + if (touch->input_wheel) + input_unregister_device(touch->input); + if (touch->input_kbd) + input_unregister_device(touch->input); + kfree(touch); + + DBGPRINTK(("%s: ...\n", __func__)); + + return 0; +} + +#ifdef CONFIG_PM +static int cyapa_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cyapa_i2c *touch = i2c_get_clientdata(client); + + DBGPRINTK(("%s: ...\n", __func__)); + cancel_delayed_work_sync(&touch->dwork); + + return 0; +} + +static int cyapa_i2c_resume(struct i2c_client *client) +{ + int ret; + struct cyapa_i2c *touch = i2c_get_clientdata(client); + + ret = cyapa_i2c_reset_config(touch); + DBGPRINTK(("%s: ...\n", __func__)); + if (ret) + return ret; + + cyapa_i2c_reschedule_work(touch, + msecs_to_jiffies(CYAPA_NO_DATA_SLEEP_MSECS)); + + return 0; +} +#else +#define cyapa_i2c_suspend NULL +#define cyapa_i2c_resume NULL +#endif + +static const struct i2c_device_id cypress_i2c_id_table[] = { + { CYAPA_I2C_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, cypress_i2c_id_table); + +static struct i2c_driver cypress_i2c_driver = { + .driver = { + .name = CYAPA_I2C_NAME, + .owner = THIS_MODULE, + }, + + .probe = cyapa_i2c_probe, + .remove = __devexit_p(cyapa_i2c_remove), + + .suspend = cyapa_i2c_suspend, + .resume = cyapa_i2c_resume, + .id_table = cypress_i2c_id_table, +}; + +static int __init cyapa_i2c_init(void) +{ + DBGPRINTK(("%s: start ...\n", __func__)); + return i2c_add_driver(&cypress_i2c_driver); +} + +static void __exit cyapa_i2c_exit(void) +{ + DBGPRINTK(("%s: exit ...\n", __func__)); + i2c_del_driver(&cypress_i2c_driver); +} + +module_init(cyapa_i2c_init); +module_exit(cyapa_i2c_exit); + +MODULE_DESCRIPTION("Cypress I2C Trackpad Driver"); +MODULE_AUTHOR("Dudley Du "); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/cyapa.h b/include/linux/cyapa.h new file mode 100644 index 0000000..888fab0 --- /dev/null +++ b/include/linux/cyapa.h @@ -0,0 +1,60 @@ +#ifndef CYAPA_H +#define CYAPA_H + + +#define CYAPA_I2C_NAME "cypress_i2c_apa" + +/* Active power state scanning/processing refresh interval time. unit: ms. */ +#define CYAPA_ACTIVE_POLLING_INTVAL_TIME 0x00 +/* Low power state scanning/processing refresh interval time. unit: ms. */ +#define CYAPA_LOWPOWER_POLLING_INTVAL_TIME 0x10 +/* Touch timeout for active power state. unit: ms. */ +#define CYAPA_ACTIVE_TOUCH_TIMEOUT 0xFF + +/* Max report rate limited for Cypress Trackpad. */ +#define CYAPA_NO_LIMITED_REPORT_RATE 0 +#define CYAPA_REPORT_RATE (CYAPA_NO_LIMITED_REPORT_RATE) +#define CYAPA_POLLING_REPORTRATE_DEFAULT 125 + + +/* APA trackpad firmware generation */ +enum cyapa_gen { + CYAPA_GEN1 = 0x01, + CYAPA_GEN2 = 0x02, +}; + +/* +** APA trackpad power states. +** Used in register 0x00, bit3-2, PowerMode field. +*/ +enum cyapa_powerstate { + CYAPA_PWR_ACTIVE = 0x01, + CYAPA_PWR_LIGHT_SLEEP = 0x02, + CYAPA_PWR_MEDIUM_SLEEP = 0x03, + CYAPA_PWR_DEEP_SLEEP = 0x04, +}; + +struct cyapa_platform_data { + u32 flag; /* reserved for future use. */ + enum cyapa_gen gen; /* trackpad firmware generation. */ + enum cyapa_powerstate power_state; + + /* use absolute data report or relative data report. */ + unsigned use_absolute_mode:1; + /* use polling mode or interrupt mode. */ + unsigned use_polling_mode:1; + /* active mode, polling refresh interval; ms */ + u8 polling_interval_time_active; + /* low power mode, polling refresh interval; ms */ + u8 polling_interval_time_lowpower; + u8 active_touch_timeout; /* active touch timeout; ms */ + char *name; /* device name of Cypress I2C trackpad. */ + /* the gpio id used for interrupt to notify host data is ready. */ + s16 irq_gpio; + u32 report_rate; /* max limitation of data report rate. */ + + int (*wakeup)(void); + int (*init)(void); +}; + +#endif /* #ifndef CYAPA_H */ -- 1.7.0.4