[PATCH 2/6] input: cyapa: add gen5 trackpad device supported in one driver

From: Dudley Du
Date: Mon Apr 14 2014 - 03:59:44 EST


Add variable, macros, fucntions and interfaces to support gen5 trackpad device,
The gen5 trackpad device is different protocol with gen3 trackpad device.
And in order to keep compatible with old products and easy customer support,
these two type devices must be integrated and supported in one driver.
So this cyapa driver is rearchitecture to support multi-type devices.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@xxxxxxxxxxx>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 4361ee1..7b269d8 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -22,12 +22,16 @@
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/completion.h>
#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/unaligned/access_ok.h>

/* APA trackpad firmware generation */
#define CYAPA_GEN_UNKNOWN 0x00 /* unknown protocol. */
#define CYAPA_GEN3 0x03 /* support MT-protocol B with tracking ID. */
+#define CYAPA_GEN5 0x05 /* support TrueTouch GEN5 trackpad device. */

#define CYAPA_NAME "Cypress APA Trackpad (cyapa)"

@@ -178,6 +182,144 @@

#define MAX_TMP_BUF_SIZE (CYAPA_REG_MAP_SIZE)

+/* mcros of Gen5 */
+#define RECORD_EVENT_NONE 0
+#define RECORD_EVENT_TOUCHDOWN 1
+#define RECORD_EVENT_DISPLACE 2
+#define RECORD_EVENT_LIFTOFF 3
+
+#define CYAPA_TSG_FLASH_MAP_BLOCK_SIZE 0x80
+#define CYAPA_TSG_IMG_FW_HDR_SIZE 13
+#define CYAPA_TSG_FW_ROW_SIZE (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE)
+#define CYAPA_TSG_IMG_START_ROW_NUM 0x002e
+#define CYAPA_TSG_IMG_END_ROW_NUM 0x01fe
+#define CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM 0x01ff
+#define CYAPA_TSG_IMG_MAX_RECORDS (CYAPA_TSG_IMG_END_ROW_NUM - \
+ CYAPA_TSG_IMG_START_ROW_NUM + 1 + 1)
+#define CYAPA_TSG_IMG_READ_SIZE (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE / 2)
+#define CYAPA_TSG_START_OF_APPLICATION 0x1700
+#define CYAPA_TSG_APP_INTEGRITY_SIZE 60
+#define CYAPA_TSG_FLASH_MAP_METADATA_SIZE 60
+#define CYAPA_TSG_BL_KEY_SIZE 8
+
+/* Macro definitions for Gen5 trackpad device. */
+#define GEN5_TOUCH_REPORT_HEAD_SIZE 7
+#define GEN5_TOUCH_REPORT_MAX_SIZE 127
+#define GEN5_BTN_REPORT_HEAD_SIZE 6
+#define GEN5_BTN_REPORT_MAX_SIZE 14
+#define GEN5_WAKEUP_EVENT_SIZE 4
+#define GEN5_RAW_DATA_HEAD_SIZE 24
+
+#define GEN5_BL_CMD_REPORT_ID 0x40
+#define GEN5_BL_RESP_REPORT_ID 0x30
+#define GEN5_APP_CMD_REPORT_ID 0x2f
+#define GEN5_APP_RESP_REPORT_ID 0x1f
+
+#define GEN5_APP_DEEP_SLEEP_REPORT_ID 0xf0
+#define GEN5_DEEP_SLEEP_RESP_LENGTH 5
+
+#define GEN5_PARAMETER_ACT_INTERVL_ID 0x4d
+#define GEN5_PARAMETER_ACT_INTERVL_SIZE 1
+#define GEN5_PARAMETER_ACT_LFT_INTERVL_ID 0x4f
+#define GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE 2
+#define GEN5_PARAMETER_LP_INTRVL_ID 0x4c
+#define GEN5_PARAMETER_LP_INTRVL_SIZE 2
+
+#define GEN5_PARAMETER_DISABLE_PIP_REPORT 0x08
+
+#define GEN5_POWER_STATE_ACTIVE 0x01
+#define GEN5_POWER_STATE_LOOK_FOR_TOUCH 0x02
+#define GEN5_POWER_STATE_READY 0x03
+#define GEN5_POWER_STATE_IDLE 0x04
+#define GEN5_POWER_STATE_BTN_ONLY 0x05
+#define GEN5_POWER_STATE_OFF 0x06
+
+#define GEN5_DEEP_SLEEP_STATE_MASK 0x03
+#define GEN5_DEEP_SLEEP_STATE_ON 0x00
+#define GEN5_DEEP_SLEEP_STATE_OFF 0x01
+
+#define GEN5_DEEP_SLEEP_OPCODE 0x08
+#define GEN5_DEEP_SLEEP_OPCODE_MASK 0x0f
+
+#define GEN5_POWER_READY_MAX_INTRVL_TIME 50 /* unit: ms */
+#define GEN5_POWER_IDLE_MAX_INTRVL_TIME 250 /* unit: ms */
+
+#define GEN5_CMD_REPORT_ID_OFFSET 4
+
+#define GEN5_RESP_REPORT_ID_OFFSET 2
+#define GEN5_RESP_RSVD_OFFSET 3
+#define GEN5_RESP_RSVD_KEY 0x00
+#define GEN5_RESP_BL_SOP_OFFSET 4
+#define GEN5_SOP_KEY 0x01 /* Start of Packet */
+#define GEN5_EOP_KEY 0x17 /* End of Packet */
+#define GEN5_RESP_APP_CMD_OFFSET 4
+#define GET_GEN5_CMD_CODE(reg) ((reg) & 0x7f)
+
+#define GEN5_MIN_BL_CMD_LENGTH 13
+#define GEN5_MIN_BL_RESP_LENGTH 11
+#define GEN5_MIN_APP_CMD_LENGTH 7
+#define GEN5_MIN_APP_RESP_LENGTH 5
+#define GEN5_UNSUPPORTED_CMD_RESP_LENGTH 6
+
+#define GEN5_RESP_LENGTH_SIZE 2
+
+#define GEN5_HID_DESCRIPTOR_SIZE 32
+#define GEN5_BL_HID_REPORT_ID 0xff
+#define GEN5_APP_HID_REPORT_ID 0xf7
+#define GEN5_BL_MAX_OUTPUT_LENGTH 0x0100
+#define GEN5_APP_MAX_OUTPUT_LENGTH 0x00fe
+
+#define GEN5_BL_REPORT_DESCRIPTOR_SIZE 0x1d
+#define GEN5_BL_REPORT_DESCRIPTOR_ID 0xfe
+#define GEN5_APP_REPORT_DESCRIPTOR_SIZE 0xee
+#define GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE 0xfa
+#define GEN5_APP_REPORT_DESCRIPTOR_ID 0xf6
+
+#define GEN5_TOUCH_REPORT_ID 0x01
+#define GEN5_BTN_REPORT_ID 0x03
+#define GEN5_WAKEUP_EVENT_REPORT_ID 0x04
+#define GEN5_OLD_PUSH_BTN_REPORT_ID 0x05
+#define GEN5_PUSH_BTN_REPORT_ID 0x06
+
+#define GEN5_CMD_COMPLETE_SUCCESS(status) ((status) == 0x00)
+
+#define GEN5_BL_INITIATE_RESP_LEN 11
+#define GEN5_BL_FAIL_EXIT_RESP_LEN 11
+#define GEN5_BL_FAIL_EXIT_STATUS_CODE 0x0c
+#define GEN5_BL_VERIFY_INTEGRITY_RESP_LEN 12
+#define GEN5_BL_INTEGRITY_CHEKC_PASS 0x00
+#define GEN5_BL_BLOCK_WRITE_RESP_LEN 11
+#define GEN5_BL_READ_APP_INFO_RESP_LEN 31
+#define GEN5_CMD_CALIBRATE 0x28
+#define CYAPA_SENSING_MODE_MUTUAL_CAP_FINE 0x00
+#define CYAPA_SENSING_MODE_SELF_CAP 0x02
+
+#define GEN5_CMD_RETRIEVE_DATA_STRUCTURE 0x24
+#define GEN5_RETRIEVE_MUTUAL_PWC_DATA 0x00
+#define GEN5_RETRIEVE_SELF_CAP_PWC_DATA 0x01
+
+#define GEN5_RETRIEVE_DATA_ELEMENT_SIZE_MASK 0x07
+
+#define GEN5_CMD_EXECUTE_PANEL_SCAN 0x2a
+#define GEN5_CMD_RETRIEVE_PANEL_SCAN 0x2b
+#define GEN5_PANEL_SCAN_MUTUAL_RAW_DATA 0x00
+#define GEN5_PANEL_SCAN_MUTUAL_BASELINE 0x01
+#define GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT 0x02
+#define GEN5_PANEL_SCAN_SELF_RAW_DATA 0x03
+#define GEN5_PANEL_SCAN_SELF_BASELINE 0x04
+#define GEN5_PANEL_SCAN_SELF_DIFFCOUNT 0x05
+
+#define GEN5_PWC_DATA_ELEMENT_SIZE_MASK 0x07
+
+#define GEN5_NUMBER_OF_TOUCH_MASK 0x1f
+#define GEN5_GET_EVENT_ID(reg) (((reg) >> 5) & 0x03)
+#define GEN5_GET_TOUCH_ID(reg) ((reg) & 0x1f)
+
+#define GEN5_PRODUCT_FAMILY_MASK 0xf000
+#define GEN5_PRODUCT_FAMILY_TRACKPAD 0x1000
+
+#define TSG_INVALID_CMD 0xff
+
struct cyapa;
typedef void (*irq_handler_func)(struct cyapa *);
typedef int (*set_power_mode_func)(struct cyapa *, u8, u16);
@@ -200,6 +342,8 @@ enum cyapa_state {
CYAPA_STATE_OP,
CYAPA_STATE_BL_IDLE,
CYAPA_STATE_BL_ACTIVE,
+ CYAPA_STATE_GEN5_BL,
+ CYAPA_STATE_GEN5_APP,
CYAPA_STATE_BL_BUSY,
CYAPA_STATE_NO_DEVICE,
};
@@ -242,6 +386,109 @@ struct cyapa_reg_data {
struct cyapa_touch touches[5];
} __packed;

+struct cyapa_gen5_touch_record {
+ /*
+ * bit 7 - 3: reserved
+ * bit 2 - 0: touch type;
+ * 0 : standard finger;
+ * 1 - 15 : reserved.
+ */
+ u8 touch_type;
+
+ /*
+ * bit 7: indicates touch liftoff status.
+ * 0 : touch is currently on the panel.
+ * 1 : touch record indicates a liftoff.
+ * bit 6 - 5: indicates an event associated with this touch instance
+ * 0 : no event
+ * 1 : touchdown
+ * 2 : significant displacement (> active distance)
+ * 3 : liftoff (record reports last known coordinates)
+ * bit 4 - 0: An arbitrary ID tag associated with a finger
+ * to alow tracking a touch as it moves around the panel.
+ */
+ u8 touch_tip_event_id;
+
+ /* bit 7 - 0 of X-axis corrinate of the touch in pixel. */
+ u8 x_lo;
+
+ /* bit 15 - 8 of X-axis corrinate of the touch in pixel. */
+ u8 x_hi;
+
+ /* bit 7 - 0 of Y-axis corrinate of the touch in pixel. */
+ u8 y_lo;
+
+ /* bit 15 - 8 of Y-axis corrinate of the touch in pixel. */
+ u8 y_hi;
+
+ /* touch intensity in counts, pressure value. */
+ u8 z;
+
+ /*
+ * The length of the major axis of the ellipse of contact between
+ * the finger and the panel (ABS_MT_TOUCH_MAJOR).
+ */
+ u8 major_axis_len;
+
+ /*
+ * The length of the minor axis of the ellipse of contact between
+ * the finger and the panel (ABS_MT_TOUCH_MINOR).
+ */
+ u8 minor_axis_len;
+
+ /*
+ * The length of the major axis of the approaching tool.
+ * (ABS_MT_WIDTH_MAJOR)
+ */
+ u8 major_tool_len;
+
+ /*
+ * The length of the minor axis of the approaching tool.
+ * (ABS_MT_WIDTH_MINOR)
+ */
+ u8 minor_tool_len;
+
+ /*
+ * The angle between the panel vertical axis and
+ * the major axis of the contact ellipse. This value is an 8-bit
+ * signed integer. The range is -127 to +127 (corresponding to
+ * -90 degree and +90 degree respectively).
+ * The positive direction is clockwise from the vertical axis.
+ * If the ellipse of contact degenerates into a circle,
+ * orientation is reported as 0.
+ */
+ u8 orientation;
+} __packed;
+
+
+struct cyapa_gen5_report_data {
+ u8 report_head[GEN5_TOUCH_REPORT_HEAD_SIZE];
+ struct cyapa_gen5_touch_record touch_records[10];
+} __packed;
+
+struct cyapa_tsg_bin_image_head {
+ u8 head_size; /* in bytes, including itself. */
+ u8 ttda_driver_major_version; /* reserved as 0. */
+ u8 ttda_driver_minor_version; /* reserved as 0. */
+ u8 fw_major_version;
+ u8 fw_minor_version;
+ u8 fw_revision_control_number[8];
+} __packed;
+
+struct cyapa_tsg_bin_image_data_record {
+ u8 flash_array_id;
+ __be16 row_number;
+ /* the number of bytes of flash data contained in this record. */
+ __be16 record_len;
+ /* the flash program data. */
+ u8 record_data[CYAPA_TSG_FW_ROW_SIZE];
+} __packed;
+
+struct cyapa_tsg_bin_image {
+ struct cyapa_tsg_bin_image_head image_head;
+ struct cyapa_tsg_bin_image_data_record records[0];
+} __packed;
+
/* The main device structure */
struct cyapa {
enum cyapa_state state;
@@ -256,6 +503,8 @@ struct cyapa {
/* power mode settings */
u8 suspend_power_mode;
u16 suspend_sleep_time;
+ u8 real_power_mode;
+ u16 real_sleep_time;
bool suspended;

/* read from query data region. */
@@ -269,10 +518,33 @@ struct cyapa {
int physical_size_x;
int physical_size_y;

- u8 gen_detecting;
+ /* used in ttsp and truetouch based trackpad devices. */
+ u8 x_origin; /* X Axis Origin: 0 = left side; 1 = rigth side. */
+ u8 y_origin; /* Y Axis Origin: 0 = top; 1 = bottom. */
+ int electrodes_x; /* Number of electrodes on the X Axis*/
+ int electrodes_y; /* Number of electrodes on the Y Axis*/
+ int electrodes_rx; /* Number of Rx electrodes */
+ int max_z;

+ u8 gen_detecting;
+ u8 in_progress_cmd;
+ func_sort resp_sort_func;
+ u8 *resp_data;
+ int *resp_len;
+
+ /* trackpad is ready for next command */
+ struct completion cmd_ready;
+ atomic_t cmd_issued;
atomic_t in_detecting;

+ /* record irq disabled/enable state. */
+ struct mutex irq_state_lock;
+ bool irq_enabled;
+ bool prev_irq_enabled;
+ struct mutex cmd_lock;
+
+ /* temple buffer to read all data out. */
+ u8 tmp_irq_buf[MAX_TMP_BUF_SIZE];
u8 tmp_buf[MAX_TMP_BUF_SIZE];

check_fw_func cyapa_check_fw;
@@ -288,6 +560,8 @@ struct cyapa {
set_power_mode_func cyapa_set_power_mode;
bl_read_fw_func cyapa_read_fw;
read_raw_data_func cyapa_read_raw_data;
+
+ struct cyapa_tsg_bin_image_head fw_img_head;
};

static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
@@ -392,6 +666,71 @@ static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout);
static void cyapa_detect(struct cyapa *cyapa);
static void cyapa_detect_async(void *data, async_cookie_t cookie);

+void cyapa_enable_irq(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (!cyapa->irq_enabled)
+ enable_irq(cyapa->irq);
+ cyapa->irq_enabled = true;
+ cyapa->prev_irq_enabled = true;
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_disable_irq(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (cyapa->irq_enabled)
+ disable_irq(cyapa->irq);
+ cyapa->irq_enabled = false;
+ cyapa->prev_irq_enabled = false;
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_enable_irq_save(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (!cyapa->irq_enabled) {
+ enable_irq(cyapa->irq);
+ cyapa->irq_enabled = true;
+ }
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_disable_irq_save(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (cyapa->irq_enabled) {
+ disable_irq(cyapa->irq);
+ cyapa->irq_enabled = false;
+ }
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+void cyapa_irq_restore(struct cyapa *cyapa)
+{
+ mutex_lock(&cyapa->irq_state_lock);
+ if (cyapa->irq_enabled != cyapa->prev_irq_enabled) {
+ if (cyapa->prev_irq_enabled) {
+ enable_irq(cyapa->irq);
+ cyapa->irq_enabled = true;
+ } else {
+ disable_irq(cyapa->irq);
+ cyapa->irq_enabled = false;
+ }
+ }
+ mutex_unlock(&cyapa->irq_state_lock);
+}
+
+bool cyapa_is_irq_enabled(struct cyapa *cyapa)
+{
+ bool enabled;
+
+ mutex_lock(&cyapa->irq_state_lock);
+ enabled = cyapa->irq_enabled;
+ mutex_unlock(&cyapa->irq_state_lock);
+ return enabled;
+}
+
static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
u8 *values)
{
@@ -603,6 +942,33 @@ static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
return 0;
}

+/*
+ * cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
+ *
+ * These are helper functions that convert to and from integer idle
+ * times and register settings to write to the PowerMode register.
+ * The trackpad supports between 20ms to 1000ms scan intervals.
+ * The time will be increased in increments of 10ms from 20ms to 100ms.
+ * From 100ms to 1000ms, time will be increased in increments of 20ms.
+ *
+ * When Idle_Time < 100, the format to convert Idle_Time to Idle_Command is:
+ * Idle_Command = Idle Time / 10;
+ * When Idle_Time >= 100, the format to convert Idle_Time to Idle_Command is:
+ * Idle_Command = Idle Time / 20 + 5;
+ */
+static u8 cyapa_sleep_time_to_pwr_cmd(u16 sleep_time)
+{
+ if (sleep_time < 20)
+ sleep_time = 20; /* minimal sleep time. */
+ else if (sleep_time > 1000)
+ sleep_time = 1000; /* maximal sleep time. */
+
+ if (sleep_time < 100)
+ return ((sleep_time / 10) << 2) & PWR_MODE_MASK;
+ else
+ return ((sleep_time / 20 + 5) << 2) & PWR_MODE_MASK;
+}
+
static u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode)
{
u8 encoded_time = pwr_mode >> 2;
@@ -734,6 +1100,8 @@ static int cyapa_gen3_get_query_data(struct cyapa *cyapa)
cyapa->physical_size_y =
((query_data[24] & 0x0f) << 8) | query_data[26];

+ cyapa->max_z = 255;
+
return 0;
}

@@ -906,6 +1274,1209 @@ static ssize_t cyapa_i2c_write(struct cyapa *cyapa, u8 reg,
return (ret == (len + 1)) ? 0 : ((ret < 0) ? ret : -EIO);
}

+
+/*******************************************************************
+ * Functions defined for Gen5 trackapd device.
+ *******************************************************************/
+
+/* Return negative errno, or else the number of bytes read. */
+static ssize_t cyapa_i2c_pip_read(struct cyapa *cyapa, u8 *buf, size_t size)
+{
+ int ret;
+
+ if (size == 0)
+ return 0;
+
+ if (!buf || size > CYAPA_REG_MAP_SIZE)
+ return -EINVAL;
+
+ ret = i2c_master_recv(cyapa->client, buf, size);
+
+ if (ret != size)
+ return (ret < 0) ? ret : -EIO;
+
+ return size;
+}
+
+/**
+ * Return a negative errno code else zero on success.
+ */
+static ssize_t cyapa_i2c_pip_write(struct cyapa *cyapa, u8 *buf, size_t size)
+{
+ int ret;
+
+ if (!buf || !size)
+ return -EINVAL;
+
+ ret = i2c_master_send(cyapa->client, buf, size);
+
+ if (ret != size)
+ return (ret < 0) ? ret : -EIO;
+
+ return 0;
+}
+
+static int cyapa_do_i2c_pip_cmd_irq_sync(
+ struct cyapa *cyapa,
+ u8 *cmd, size_t cmd_len,
+ unsigned long timeout)
+{
+ int ret;
+
+ /* wait for interrupt to set ready completion */
+ init_completion(&cyapa->cmd_ready);
+
+ atomic_inc(&cyapa->cmd_issued);
+ ret = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+ if (ret) {
+ atomic_dec(&cyapa->cmd_issued);
+ return (ret < 0) ? ret : -EIO;
+ }
+
+ /* wait for interrupt to indicate command is completed. */
+ timeout = wait_for_completion_timeout(&cyapa->cmd_ready,
+ msecs_to_jiffies(timeout));
+ if (timeout == 0) {
+ atomic_dec(&cyapa->cmd_issued);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/**
+ * this function is aimed to dump all not read data in Gen5 trackpad
+ * before send any command, otherwise, the interrupt line will be blocked.
+ */
+static int cyapa_empty_pip_output_data(struct cyapa *cyapa,
+ u8 *buf, int *len, func_sort func)
+{
+ int ret;
+ int length;
+ int report_count;
+ int empty_count;
+ int buf_len;
+
+ buf_len = 0;
+ if (len) {
+ buf_len = (*len < MAX_TMP_BUF_SIZE) ? *len : MAX_TMP_BUF_SIZE;
+ *len = 0;
+ }
+
+ report_count = 8; /* max 7 pending data before command response data */
+ empty_count = 0;
+ do {
+ /*
+ * Depnding on testing in cyapa driver, there are max 5 "02 00"
+ * packets between two valid bufferred data report in firmware.
+ * So in order to dump all buffered data out and
+ * make interrupt line release for reassert again,
+ * we must set the empty_count check value bigger than 5 to
+ * make it work. Otherwise, in some situation,
+ * the interrupt line may unable to reactive again,
+ * which will cause trackpad device unable to
+ * report data any more.
+ * for example, it may happen in EFT and ESD testing.
+ */
+ if (empty_count > 5)
+ return 0;
+
+ ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf,
+ GEN5_RESP_LENGTH_SIZE);
+ if (ret < 0)
+ return ret;
+
+ length = get_unaligned_le16(cyapa->tmp_buf);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ empty_count++;
+ continue;
+ } else if (length > MAX_TMP_BUF_SIZE) {
+ /* should not happen */
+ return -EINVAL;
+ } else if (length == 0) {
+ /* application or bootloader launch data polled out. */
+ length = GEN5_RESP_LENGTH_SIZE;
+ if (buf && buf_len && func &&
+ func(cyapa, cyapa->tmp_buf, length)) {
+ length = min(buf_len, length);
+ memcpy(buf, cyapa->tmp_buf, length);
+ *len = length;
+ /* response found, success. */
+ return 0;
+ } else {
+ continue;
+ }
+ }
+
+ ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf, length);
+ if (ret < 0)
+ return ret;
+
+ report_count--;
+ empty_count = 0;
+ length = get_unaligned_le16(cyapa->tmp_buf);
+ if (length <= GEN5_RESP_LENGTH_SIZE) {
+ empty_count++;
+ } else if (buf && buf_len && func &&
+ func(cyapa, cyapa->tmp_buf, length)) {
+ length = min(buf_len, length);
+ memcpy(buf, cyapa->tmp_buf, length);
+ *len = length;
+ /* response found, success. */
+ return 0;
+ }
+
+ ret = -EINVAL;
+ } while (report_count);
+
+ return ret;
+}
+
+static int cyapa_i2c_pip_cmd_irq_sync(
+ struct cyapa *cyapa,
+ u8 *cmd, int cmd_len,
+ u8 *resp_data, int *resp_len,
+ unsigned long timeout,
+ func_sort func)
+{
+ int ret;
+ int tries;
+ int length;
+
+ if (!cmd || !cmd_len)
+ return -EINVAL;
+
+ mutex_lock(&cyapa->cmd_lock);
+
+ cyapa->resp_sort_func = func;
+ cyapa->resp_data = resp_data;
+ cyapa->resp_len = resp_len;
+
+ if (cmd_len >= GEN5_MIN_APP_CMD_LENGTH &&
+ cmd[4] == GEN5_APP_CMD_REPORT_ID) {
+ /* application command */
+ cyapa->in_progress_cmd = cmd[6] & 0x7f;
+ } else if (cmd_len >= GEN5_MIN_BL_CMD_LENGTH &&
+ cmd[4] == GEN5_BL_CMD_REPORT_ID) {
+ /* bootloader command */
+ cyapa->in_progress_cmd = cmd[7];
+ }
+
+ /* send command data, wait and read output response data's length. */
+ if (cyapa_is_irq_enabled(cyapa)) {
+ ret = cyapa_do_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+ timeout);
+ if (ret == -ETIMEDOUT && resp_data &&
+ resp_len && *resp_len != 0 && func) {
+ /*
+ * for some old version with some unknown reasons,
+ * there was no interrupt for the command response data,
+ * so need to poll here to try to get the response data.
+ */
+ ret = cyapa_empty_pip_output_data(cyapa,
+ resp_data, resp_len, func);
+ if (ret || *resp_len == 0)
+ ret = ret ? ret : -ETIMEDOUT;
+ }
+ } else {
+ ret = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+ if (ret) {
+ ret = ret < 0 ? ret : -EIO;
+ goto out;
+ }
+
+ tries = timeout / 5;
+ length = *resp_len;
+ if (resp_data && resp_len && length != 0 && func) {
+ do {
+ usleep_range(3000, 5000);
+ *resp_len = length;
+ ret = cyapa_empty_pip_output_data(cyapa,
+ resp_data, resp_len, func);
+ if (ret || *resp_len == 0)
+ continue;
+ else
+ break;
+ } while (--tries > 0);
+ if ((ret || *resp_len == 0) || tries <= 0)
+ ret = ret ? ret : -ETIMEDOUT;
+ }
+ }
+
+out:
+ cyapa->resp_sort_func = NULL;
+ cyapa->resp_data = NULL;
+ cyapa->resp_len = NULL;
+ cyapa->in_progress_cmd = TSG_INVALID_CMD;
+
+ mutex_unlock(&cyapa->cmd_lock);
+ return ret;
+}
+
+bool cyapa_gen5_sort_tsg_pip_bl_resp_data(struct cyapa *cyapa,
+ u8 *data, int len)
+{
+ if (!data || len < GEN5_MIN_BL_RESP_LENGTH)
+ return false;
+
+ /* bootloader input report id 30h */
+ if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_BL_RESP_REPORT_ID &&
+ data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY &&
+ data[GEN5_RESP_BL_SOP_OFFSET] == GEN5_SOP_KEY)
+ return true;
+
+ return false;
+}
+
+bool cyapa_gen5_sort_tsg_pip_app_resp_data(struct cyapa *cyapa,
+ u8 *data, int len)
+{
+ int resp_len;
+
+ if (!data || len < GEN5_MIN_APP_RESP_LENGTH)
+ return false;
+
+ if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_APP_RESP_REPORT_ID &&
+ data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY) {
+ resp_len = get_unaligned_le16(&data[0]);
+ if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) == 0x00 &&
+ resp_len == GEN5_UNSUPPORTED_CMD_RESP_LENGTH &&
+ data[5] == cyapa->in_progress_cmd) {
+ /* unsupported command code */
+ return false;
+ } else if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) ==
+ cyapa->in_progress_cmd) {
+ /* correct command response received */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool cyapa_gen5_sort_application_launch_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+ return false;
+
+ if (buf[0] == 0 && buf[1] == 0)
+ return true;
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_hid_descriptor_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ int resp_len;
+ int max_output_len;
+
+ /* check hid descriptor. */
+ if (len != GEN5_HID_DESCRIPTOR_SIZE)
+ return false;
+
+ resp_len = get_unaligned_le16(&buf[0]);
+ max_output_len = get_unaligned_le16(&buf[16]);
+ if (resp_len == GEN5_HID_DESCRIPTOR_SIZE) {
+ if (buf[2] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Descriptor */
+ return true;
+ } else if (buf[2] == GEN5_APP_HID_REPORT_ID &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Descriptor */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_deep_sleep_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ if (len == GEN5_DEEP_SLEEP_RESP_LENGTH &&
+ buf[2] == GEN5_APP_DEEP_SLEEP_REPORT_ID &&
+ (buf[4] & GEN5_DEEP_SLEEP_OPCODE_MASK) ==
+ GEN5_DEEP_SLEEP_OPCODE)
+ return true;
+ return false;
+}
+
+static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
+{
+ int ret;
+ int length;
+ u8 cmd[2];
+ u8 resp_data[32];
+ int max_output_len;
+
+ /* Parse based on Gen5 characteristic regiters and bits */
+ length = get_unaligned_le16(&reg_data[0]);
+ if (length == 0 || length == GEN5_RESP_LENGTH_SIZE) {
+ /* dump all buffered data firstly, specific for the situation
+ * that when trackpad is just power on the cyapa go here. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ memset(resp_data, 0, sizeof(resp_data));
+ ret = cyapa_i2c_pip_read(cyapa, resp_data, 3);
+ if (ret != 3)
+ return -EAGAIN;
+
+ length = get_unaligned_le16(&resp_data[0]);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ /* normal state of Gen5 with no data to respose */
+ cyapa->gen = CYAPA_GEN5;
+
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ /* read HID Description from trackpad device */
+ cmd[0] = 0x01;
+ cmd[1] = 0x00;
+ length = GEN5_HID_DESCRIPTOR_SIZE;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 2,
+ resp_data, &length,
+ 300,
+ cyapa_gen5_sort_hid_descriptor_data);
+ if (ret)
+ return -EAGAIN;
+
+ length = get_unaligned_le16(&resp_data[0]);
+ max_output_len = get_unaligned_le16(&resp_data[16]);
+ if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+ length == GEN5_RESP_LENGTH_SIZE) &&
+ resp_data[2] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Description read */
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+ } else if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+ length == GEN5_RESP_LENGTH_SIZE) &&
+ resp_data[2] == GEN5_APP_HID_REPORT_ID &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Description read */
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ } else {
+ /* should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ return -EAGAIN;
+ }
+ }
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ (reg_data[2] == GEN5_BL_HID_REPORT_ID ||
+ reg_data[2] == GEN5_APP_HID_REPORT_ID)) {
+ /* 0x20 0x00 0xF7 is Gen5 Application HID Description Header;
+ * 0x20 0x00 0xFF is Gen5 Booloader HID Description Header.
+ *
+ * must read Report Description content through out,
+ * otherwise Gen5 trackpad cannot reponse next command
+ * or report any touch or button data.
+ */
+ ret = cyapa_i2c_pip_read(cyapa, resp_data,
+ GEN5_HID_DESCRIPTOR_SIZE);
+ if (ret != GEN5_HID_DESCRIPTOR_SIZE)
+ return -EAGAIN;
+ length = get_unaligned_le16(&resp_data[0]);
+ max_output_len = get_unaligned_le16(&resp_data[16]);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ if (reg_data[2] == GEN5_BL_HID_REPORT_ID) {
+ /* BL mode HID Description has been previously
+ * read out */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+ } else {
+ /* APP mode HID Description has been previously
+ * read out */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ }
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ resp_data[2] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Description read */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ resp_data[2] == GEN5_APP_HID_REPORT_ID &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Description read */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ } else {
+ /* should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ return -EAGAIN;
+ }
+ } else if ((length == GEN5_APP_REPORT_DESCRIPTOR_SIZE ||
+ length == GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE) &&
+ reg_data[2] == GEN5_APP_REPORT_DESCRIPTOR_ID) {
+ /* 0xEE 0x00 0xF6 is Gen5 APP Report Description header. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+ /*
+ * must read Report Description content through out,
+ * otherwise Gen5 trackpad cannot reponse next command
+ * or report any touch or button data.
+ */
+
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ } else if (length == GEN5_BL_REPORT_DESCRIPTOR_SIZE &&
+ reg_data[2] == GEN5_BL_REPORT_DESCRIPTOR_ID) {
+ /* 0x1D 0x00 0xFE is Gen5 BL Report Descriptior header. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+ /*
+ * must read Report Description content through out,
+ * otherwise Gen5 trackpad cannot reponse next command
+ * or report any touch or button data.
+ */
+ ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf,
+ GEN5_BL_REPORT_DESCRIPTOR_SIZE);
+ if (ret != GEN5_BL_REPORT_DESCRIPTOR_SIZE)
+ return -EAGAIN;
+
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+ } else if (reg_data[2] == GEN5_TOUCH_REPORT_ID ||
+ reg_data[2] == GEN5_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_PUSH_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_WAKEUP_EVENT_REPORT_ID) {
+ ret = 0;
+ length = get_unaligned_le16(&reg_data[0]);
+ switch (reg_data[2]) {
+ case GEN5_TOUCH_REPORT_ID:
+ if (length < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+ length > GEN5_TOUCH_REPORT_MAX_SIZE)
+ ret = -EINVAL;
+ break;
+ case GEN5_BTN_REPORT_ID:
+ case GEN5_OLD_PUSH_BTN_REPORT_ID:
+ case GEN5_PUSH_BTN_REPORT_ID:
+ if (length < GEN5_BTN_REPORT_HEAD_SIZE ||
+ length > GEN5_BTN_REPORT_MAX_SIZE)
+ ret = -EINVAL;
+ break;
+ case GEN5_WAKEUP_EVENT_REPORT_ID:
+ if (length != GEN5_WAKEUP_EVENT_SIZE)
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret < 0)
+ return -EAGAIN;
+
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+ } else if (reg_data[2] == GEN5_BL_RESP_REPORT_ID ||
+ reg_data[2] == GEN5_APP_RESP_REPORT_ID) {
+ /*
+ * must read report data through out,
+ * otherwise Gen5 trackpad cannot reponse next command
+ * or report any touch or button data.
+ */
+ length = get_unaligned_le16(reg_data);
+ ret = cyapa_i2c_pip_read(cyapa, cyapa->tmp_buf, length);
+ if (ret != length)
+ return -EAGAIN;
+
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ /* previous command has read the data through out. */
+ if (reg_data[2] == GEN5_BL_RESP_REPORT_ID) {
+ /* Gen5 BL command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else {
+ /* Gen5 APP command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ }
+ } else if (cyapa->tmp_buf[2] == GEN5_BL_RESP_REPORT_ID &&
+ cyapa->tmp_buf[3] == GEN5_RESP_RSVD_KEY &&
+ cyapa->tmp_buf[4] == GEN5_SOP_KEY &&
+ cyapa->tmp_buf[length - 1] == GEN5_EOP_KEY) {
+ /* Gen5 BL command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if (cyapa->tmp_buf[2] == GEN5_APP_RESP_REPORT_ID &&
+ cyapa->tmp_buf[3] == GEN5_RESP_RSVD_KEY) {
+ /* Gen5 APP command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ } else {
+ /* should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ }
+
+ if (cyapa->state == CYAPA_STATE_NO_DEVICE)
+ return -EAGAIN;
+ return 0;
+ }
+
+ return -EAGAIN;
+}
+
+bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
+{
+ if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+ return false;
+
+ if (buf[0] == 0 && buf[1] == 0)
+ return true;
+
+ /* exit bootloader failed for some reason. */
+ if (len == GEN5_BL_FAIL_EXIT_RESP_LEN &&
+ buf[2] == GEN5_BL_RESP_REPORT_ID &&
+ buf[3] == GEN5_RESP_RSVD_KEY &&
+ buf[4] == GEN5_SOP_KEY &&
+ buf[10] == GEN5_EOP_KEY)
+ return true;
+
+ return false;
+}
+static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
+{
+ int ret;
+ u8 resp_data[11];
+ int resp_len;
+ u8 bl_gen5_bl_exit[] = { 0x04, 0x00,
+ 0x0B, 0x00, 0x40, 0x00, 0x01, 0x3b, 0x00, 0x00,
+ 0x20, 0xc7, 0x17
+ };
+
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ bl_gen5_bl_exit, sizeof(bl_gen5_bl_exit),
+ resp_data, &resp_len,
+ 5000, cyapa_gen5_sort_bl_exit_data);
+ if (ret)
+ return ret;
+
+ if (resp_len == GEN5_BL_FAIL_EXIT_RESP_LEN ||
+ resp_data[2] == GEN5_BL_RESP_REPORT_ID)
+ return -EAGAIN;
+
+ if (resp_data[0] == 0x00 && resp_data[1] == 0x00)
+ return 0;
+
+ return -EAGAIN;
+}
+
+static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
+{
+ int ret;
+ u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 };
+ u8 resp_data[6];
+ int resp_len;
+
+ cmd[7] = power_state;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x08 ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return ret < 0 ? ret : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_set_interval_time(struct cyapa *cyapa,
+ u8 parameter_id, u16 interval_time)
+{
+ int ret;
+ u8 cmd[13];
+ int cmd_len;
+ u8 resp_data[7];
+ int resp_len;
+ u8 parameter_size;
+
+ switch (parameter_id) {
+ case GEN5_PARAMETER_ACT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_ACT_LFT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_LP_INTRVL_ID:
+ parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ cmd_len = 7 + parameter_size; /* not incuding 2 bytes address */
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ put_unaligned_le16(cmd_len, &cmd[2]);
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x06; /* set parameter command code */
+ cmd[7] = parameter_id;
+ cmd[8] = parameter_size;
+ put_unaligned_le16(interval_time, &cmd[9]);
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len + 2,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x06 ||
+ resp_data[5] != parameter_id ||
+ resp_data[6] != parameter_size)
+ return ret < 0 ? ret : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_get_interval_time(struct cyapa *cyapa,
+ u8 parameter_id, u16 *interval_time)
+{
+ int ret;
+ u8 cmd[8];
+ u8 resp_data[11];
+ int resp_len;
+ u8 parameter_size;
+ u16 mask, i;
+
+ *interval_time = 0;
+ switch (parameter_id) {
+ case GEN5_PARAMETER_ACT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_ACT_LFT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_LP_INTRVL_ID:
+ parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x06;
+ cmd[3] = 0x00;
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x05; /* get parameter command code */
+ cmd[7] = parameter_id;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x05 ||
+ resp_data[5] != parameter_id ||
+ resp_data[6] == 0)
+ return ret < 0 ? ret : -EINVAL;
+
+ mask = 0;
+ for (i = 0; i < parameter_size; i++)
+ mask |= (0xff << (i * 8));
+ *interval_time = get_unaligned_le16(&resp_data[7]) & mask;
+
+ return 0;
+}
+
+static int cyapa_gen5_disable_pip_report(struct cyapa *cyapa)
+{
+ int ret;
+ u8 cmd[10];
+ u8 resp_data[7];
+ int resp_len;
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ put_unaligned_le16(8, &cmd[2]);
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x06; /* set parameter command code */
+ cmd[7] = GEN5_PARAMETER_DISABLE_PIP_REPORT;
+ cmd[8] = 0x01;
+ cmd[9] = 0x01;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, 10,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data);
+ if (ret || resp_data[2] != GEN5_APP_RESP_REPORT_ID ||
+ resp_data[3] != GEN5_RESP_RSVD_KEY ||
+ GET_GEN5_CMD_CODE(resp_data[4]) != 0x06 ||
+ resp_data[5] != GEN5_PARAMETER_DISABLE_PIP_REPORT ||
+ resp_data[6] != 0x01)
+ return ret < 0 ? ret : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_deep_sleep(struct cyapa *cyapa, u8 state)
+{
+ int ret;
+ u8 cmd[4] = { 0x05, 0x00, 0x00, 0x08};
+ u8 resp_data[5];
+ int resp_len;
+
+ cmd[0] = 0x05;
+ cmd[1] = 0x00;
+ cmd[2] = state & GEN5_DEEP_SLEEP_STATE_MASK;
+ cmd[3] = 0x08;
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 100, cyapa_gen5_sort_deep_sleep_data);
+ if (ret || ((resp_data[3] & GEN5_DEEP_SLEEP_STATE_MASK) != state))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
+ u8 power_mode, u16 sleep_time)
+{
+ struct device *dev = &cyapa->client->dev;
+ int ret;
+ u8 power_state;
+
+ if (cyapa->state != CYAPA_STATE_GEN5_APP)
+ return 0;
+
+ /* dump all the report data before do power mode commmands. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ switch (power_mode) {
+ case PWR_MODE_OFF:
+ case PWR_MODE_FULL_ACTIVE:
+ case PWR_MODE_BTN_ONLY:
+ if (power_mode == cyapa->real_power_mode)
+ return 0;
+
+ if (power_mode == PWR_MODE_OFF) {
+ ret = cyapa_gen5_deep_sleep(cyapa,
+ GEN5_DEEP_SLEEP_STATE_OFF);
+ if (ret) {
+ dev_err(dev, "enter deep sleep fail, (%d)\n",
+ ret);
+ return ret;
+ }
+
+ cyapa->real_power_mode = PWR_MODE_OFF;
+ return 0;
+ }
+ break;
+ default:
+ if (sleep_time == cyapa->real_sleep_time &&
+ power_mode == cyapa->real_power_mode)
+ return 0;
+ }
+
+ if (cyapa->real_power_mode == PWR_MODE_OFF) {
+ ret = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_ON);
+ if (ret) {
+ dev_err(dev, "deep sleep wake fail, (%d)\n", ret);
+ return ret;
+ }
+ /* set current power state in driver. */
+ cyapa->real_power_mode = cyapa->suspend_power_mode;
+ cyapa->real_sleep_time = cyapa->suspend_sleep_time;
+ }
+
+ if (power_mode == PWR_MODE_FULL_ACTIVE) {
+ ret = cyapa_gen5_change_power_state(cyapa,
+ GEN5_POWER_STATE_ACTIVE);
+ if (ret) {
+ dev_err(dev, "change to active fail, (%d)\n", ret);
+ return ret;
+ }
+
+ cyapa->real_power_mode = PWR_MODE_FULL_ACTIVE;
+ } else if (power_mode == PWR_MODE_BTN_ONLY) {
+ ret = cyapa_gen5_change_power_state(cyapa,
+ GEN5_POWER_STATE_BTN_ONLY);
+ if (ret) {
+ dev_err(dev, "fail change to active, (%d)\n", ret);
+ return ret;
+ }
+
+ cyapa->real_power_mode = PWR_MODE_BTN_ONLY;
+ } else {
+ /* continue to change power mode even failed to set
+ * interval time, it won't affect the power mode change. */
+ cyapa_gen5_set_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID, sleep_time);
+
+ if (sleep_time <= GEN5_POWER_READY_MAX_INTRVL_TIME)
+ power_state = GEN5_POWER_STATE_READY;
+ else
+ power_state = GEN5_POWER_STATE_IDLE;
+ ret = cyapa_gen5_change_power_state(cyapa, power_state);
+ if (ret) {
+ dev_err(dev, "set power state %d fail, (%d)\n",
+ power_state, ret);
+ cyapa_gen5_set_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID,
+ cyapa->real_sleep_time);
+ return ret;
+ }
+
+ /* disable pip report for a little time, firmware will
+ * re-enable it automatically. It's used to fix the issue
+ * that trackpad unable to report signal to wake system up
+ * in the special situation that system is in suspending, and
+ * at the same time, user touch trackpad to wake system up.
+ * This function can avoid the data to be buffured when system
+ * is suspending which may cause interrput line unable to be
+ * asserted again. */
+ cyapa_gen5_disable_pip_report(cyapa);
+
+ cyapa->real_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(sleep_time);
+ cyapa->real_sleep_time = sleep_time;
+ }
+
+ return ret;
+}
+
+static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ /* check the report id and command code */
+ if (buf[2] == GEN5_APP_RESP_REPORT_ID &&
+ GET_GEN5_CMD_CODE(buf[4]) == 0x02)
+ return true;
+
+ return false;
+}
+
+static int cyapa_gen5_bl_query_data(struct cyapa *cyapa)
+{
+ int ret;
+ u8 cmd[16];
+ int cmd_len;
+ u8 resp_data[GEN5_BL_READ_APP_INFO_RESP_LEN];
+ int resp_len;
+
+ /* read application information. */
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x0b;
+ cmd[3] = 0x00;
+ cmd[4] = 0x40;
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_SOP_KEY;
+ cmd[7] = 0x3c; /* read application information command code */
+ cmd[8] = 0x00;
+ cmd[9] = 0x00;
+ cmd[10] = 0xb0;
+ cmd[11] = 0x42;
+ cmd[12] = GEN5_EOP_KEY;
+ cmd_len = 13;
+ resp_len = GEN5_BL_READ_APP_INFO_RESP_LEN;
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, cmd_len,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_bl_resp_data);
+ if (ret || resp_len != GEN5_BL_READ_APP_INFO_RESP_LEN ||
+ resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return (ret < 0) ? ret : -EIO;
+
+ memcpy(&cyapa->product_id[0], &resp_data[8], 5);
+ cyapa->product_id[5] = '-';
+ memcpy(&cyapa->product_id[6], &resp_data[13], 6);
+ cyapa->product_id[12] = '-';
+ memcpy(&cyapa->product_id[13], &resp_data[19], 2);
+ cyapa->product_id[15] = '\0';
+
+ cyapa->fw_maj_ver = resp_data[22];
+ cyapa->fw_min_ver = resp_data[23];
+
+ return 0;
+}
+
+static int cyapa_gen5_get_query_data(struct cyapa *cyapa)
+{
+ int ret;
+ u8 resp_data[71];
+ int resp_len;
+ u8 get_system_information[] = {
+ 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x02
+ };
+ u16 product_family;
+
+ resp_len = sizeof(resp_data);
+ ret = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ get_system_information, sizeof(get_system_information),
+ resp_data, &resp_len,
+ 2000, cyapa_gen5_sort_system_info_data);
+ if (ret || resp_len < sizeof(resp_data))
+ return ret;
+
+ cyapa->fw_img_head.head_size =
+ sizeof(struct cyapa_tsg_bin_image_head) - 1;
+ memcpy(&cyapa->fw_img_head.ttda_driver_major_version,
+ &resp_data[5], cyapa->fw_img_head.head_size);
+
+ product_family = get_unaligned_le16(&resp_data[7]);
+ if ((product_family & GEN5_PRODUCT_FAMILY_MASK) !=
+ GEN5_PRODUCT_FAMILY_TRACKPAD)
+ return -EINVAL;
+
+ cyapa->fw_maj_ver = resp_data[15];
+ cyapa->fw_min_ver = resp_data[16];
+
+ cyapa->electrodes_x = resp_data[52];
+ cyapa->electrodes_y = resp_data[53];
+
+ cyapa->physical_size_x = get_unaligned_le16(&resp_data[54]) / 100;
+ cyapa->physical_size_y = get_unaligned_le16(&resp_data[56]) / 100;
+
+ cyapa->max_abs_x = get_unaligned_le16(&resp_data[58]);
+ cyapa->max_abs_y = get_unaligned_le16(&resp_data[60]);
+
+ cyapa->max_z = get_unaligned_le16(&resp_data[62]);
+
+ cyapa->x_origin = resp_data[64] & 0x01;
+ cyapa->y_origin = resp_data[65] & 0x01;
+
+ cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK;
+
+ memcpy(&cyapa->product_id[0], &resp_data[33], 5);
+ cyapa->product_id[5] = '-';
+ memcpy(&cyapa->product_id[6], &resp_data[38], 6);
+ cyapa->product_id[12] = '-';
+ memcpy(&cyapa->product_id[13], &resp_data[44], 2);
+ cyapa->product_id[15] = '\0';
+
+ if (!cyapa->electrodes_x || !cyapa->electrodes_y ||
+ !cyapa->physical_size_x || !cyapa->physical_size_y ||
+ !cyapa->max_abs_x || !cyapa->max_abs_y || !cyapa->max_z)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_do_operational_check(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+ int ret;
+
+ if (cyapa->gen != CYAPA_GEN5)
+ return -EINVAL;
+
+ switch (cyapa->state) {
+ case CYAPA_STATE_GEN5_BL:
+ ret = cyapa_gen5_bl_exit(cyapa);
+ if (ret) {
+ /* try to update trackpad product information. */
+ cyapa_gen5_bl_query_data(cyapa);
+ return ret;
+ }
+
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+
+ case CYAPA_STATE_GEN5_APP:
+ /* if trackpad device in deep sleep mode,
+ * the app command will fail.
+ * So always reset trackpad device to full active when
+ * the device state is requeried.
+ */
+ if (cyapa->real_power_mode == PWR_MODE_OFF) {
+ ret = cyapa_gen5_set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ if (ret)
+ return ret;
+
+ /* set initial power state of trackpad device. */
+ cyapa->real_power_mode = PWR_MODE_FULL_ACTIVE;
+ cyapa_gen5_get_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID,
+ &cyapa->real_sleep_time);
+ cyapa->suspend_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(
+ cyapa->real_sleep_time);
+ cyapa->suspend_sleep_time = cyapa->real_sleep_time;
+ }
+
+ /* Get trackpad product information. */
+ ret = cyapa_gen5_get_query_data(cyapa);
+ if (ret)
+ return ret;
+ /* only support product ID starting with CYTRA */
+ if (memcmp(cyapa->product_id, unique_str,
+ sizeof(unique_str) - 1) != 0) {
+ dev_err(dev, "%s: unknown product ID (%s)\n",
+ __func__, cyapa->product_id);
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void cyapa_gen5_irq_handler(struct cyapa *cyapa)
+{
+ struct input_dev *input = cyapa->input;
+ struct cyapa_gen5_report_data report_data;
+ int i;
+ int ret;
+ u8 report_id;
+ u8 buttons;
+ unsigned int report_len, touch_num;
+ int x, y;
+
+ if (cyapa->gen != CYAPA_GEN5 ||
+ cyapa->state != CYAPA_STATE_GEN5_APP) {
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data,
+ GEN5_TOUCH_REPORT_HEAD_SIZE);
+ if (ret != GEN5_TOUCH_REPORT_HEAD_SIZE) {
+ /* failed to read report head data. */
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ report_len = get_unaligned_le16(&report_data.report_head[0]);
+ if (report_len <= 2) {
+ /*
+ * trackpad power up event or end of one touch packets report,
+ * no data for report.
+ */
+ if (report_len != 2)
+ async_schedule(cyapa_detect_async, cyapa);
+
+ return;
+ }
+
+ report_id = report_data.report_head[2];
+ if (report_id == GEN5_WAKEUP_EVENT_REPORT_ID &&
+ report_len == GEN5_WAKEUP_EVENT_SIZE) {
+ /* Wake event from deep sleep mode, reset power state. */
+ return;
+ } else if (report_id != GEN5_TOUCH_REPORT_ID &&
+ report_id != GEN5_BTN_REPORT_ID &&
+ report_id != GEN5_OLD_PUSH_BTN_REPORT_ID &&
+ report_id != GEN5_PUSH_BTN_REPORT_ID) {
+ /* Running in BL mode or unknown response data read. */
+
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ if (report_len > GEN5_TOUCH_REPORT_HEAD_SIZE) {
+ /* must make sure to read all data through out before return. */
+ ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data, report_len);
+ if (ret != report_len) {
+ /* failed to read report head data. */
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+ }
+
+ if (report_id == GEN5_TOUCH_REPORT_ID &&
+ (report_len < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+ report_len > GEN5_TOUCH_REPORT_MAX_SIZE)) {
+ /* Invald report data length for finger packet. */
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ if ((report_id == GEN5_BTN_REPORT_ID ||
+ report_id == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ report_id == GEN5_PUSH_BTN_REPORT_ID) &&
+ (report_len < GEN5_BTN_REPORT_HEAD_SIZE ||
+ report_len > GEN5_BTN_REPORT_MAX_SIZE)) {
+ /* Invald report data length of button packet. */
+ async_schedule(cyapa_detect_async, cyapa);
+ return;
+ }
+
+ if (report_id == GEN5_BTN_REPORT_ID ||
+ report_id == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ report_id == GEN5_PUSH_BTN_REPORT_ID) {
+ /* button report data */
+ buttons = (report_data.report_head[5] << 3) &
+ CAPABILITY_BTN_MASK;
+ if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK) {
+ input_report_key(input, BTN_LEFT,
+ !!(buttons & CAPABILITY_LEFT_BTN_MASK));
+ }
+ if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK) {
+ input_report_key(input, BTN_MIDDLE,
+ !!(buttons & CAPABILITY_MIDDLE_BTN_MASK));
+ }
+ if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK) {
+ input_report_key(input, BTN_RIGHT,
+ !!(buttons & CAPABILITY_RIGHT_BTN_MASK));
+ }
+
+ input_sync(input);
+ } else {
+ /* touch report data */
+ touch_num = report_data.report_head[5] &
+ GEN5_NUMBER_OF_TOUCH_MASK;
+
+ for (i = 0; i < touch_num; i++) {
+ const struct cyapa_gen5_touch_record *touch =
+ &report_data.touch_records[i];
+ u8 event_id =
+ GEN5_GET_EVENT_ID(touch->touch_tip_event_id);
+ int slot = GEN5_GET_TOUCH_ID(touch->touch_tip_event_id);
+
+ if (event_id == RECORD_EVENT_LIFTOFF)
+ continue;
+
+ input_mt_slot(input, slot);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ x = (touch->x_hi << 8) | touch->x_lo;
+ if (cyapa->x_origin)
+ x = cyapa->max_abs_x - x;
+ input_report_abs(input, ABS_MT_POSITION_X, x);
+ y = (touch->y_hi << 8) | touch->y_lo;
+ if (cyapa->y_origin)
+ y = cyapa->max_abs_y - y;
+ input_report_abs(input, ABS_MT_POSITION_Y, y);
+ input_report_abs(input, ABS_MT_PRESSURE,
+ touch->z);
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+ touch->major_axis_len);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR,
+ touch->minor_axis_len);
+
+ input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+ touch->major_tool_len);
+ input_report_abs(input, ABS_MT_WIDTH_MINOR,
+ touch->minor_tool_len);
+
+ input_report_abs(input, ABS_MT_ORIENTATION,
+ touch->orientation);
+ }
+
+ input_mt_sync_frame(input);
+
+ input_sync(input);
+ }
+}
+
static void cyapa_default_irq_handler(struct cyapa *cyapa)
{
async_schedule(cyapa_detect_async, cyapa);
@@ -934,6 +2505,26 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
return ret;

switch (cyapa->gen) {
+ case CYAPA_GEN5:
+ cyapa->cyapa_check_fw = NULL;
+ cyapa->cyapa_bl_enter = NULL;
+ cyapa->cyapa_bl_activate = NULL;
+ cyapa->cyapa_bl_initiate = NULL;
+ cyapa->cyapa_update_fw = NULL;
+ cyapa->cyapa_bl_verify_app_integrity = NULL;
+ cyapa->cyapa_bl_deactivate = NULL;
+ cyapa->cyapa_show_baseline = NULL;
+ cyapa->cyapa_calibrate_store = NULL;
+ cyapa->cyapa_irq_handler = cyapa_gen5_irq_handler;
+ cyapa->cyapa_set_power_mode = cyapa_gen5_set_power_mode;
+ cyapa->cyapa_read_fw = NULL;
+ cyapa->cyapa_read_raw_data = NULL;
+
+ cyapa_enable_irq_save(cyapa);
+ ret = cyapa_gen5_do_operational_check(cyapa);
+ cyapa_irq_restore(cyapa);
+
+ break;
case CYAPA_GEN3:
cyapa->cyapa_check_fw = NULL;
cyapa->cyapa_bl_enter = NULL;
@@ -983,14 +2574,75 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
if (device_may_wakeup(dev))
pm_wakeup_event(dev, 0);

+ if (atomic_read(&cyapa->cmd_issued)) {
+ if (cyapa->gen_detecting == CYAPA_GEN5) {
+ /*
+ * read out all none command response data.
+ * these output data may caused by user put finger on
+ * trackpad when host waiting the command response.
+ */
+ cyapa_i2c_pip_read(cyapa, cyapa->tmp_irq_buf, 2);
+ length = get_unaligned_le16(cyapa->tmp_irq_buf);
+ length = (length <= 2) ? 2 : length;
+ if (length > 2)
+ cyapa_i2c_pip_read(cyapa,
+ cyapa->tmp_irq_buf, length);
+ if (!(cyapa->resp_sort_func &&
+ cyapa->resp_sort_func(cyapa,
+ cyapa->tmp_irq_buf, length))) {
+ /*
+ * Cover the Gen5 V1 firmware issue.
+ * The issue is there is no interrut will be
+ * asserted to notityf host to read a command
+ * data out when always has finger touch on
+ * trackpad during the command is issued to
+ * trackad device.
+ * This issue has the scenario is that,
+ * user always has his fingers touched on
+ * trackpad device when booting/rebooting
+ * their chrome book.
+ */
+ length = *cyapa->resp_len;
+ cyapa_empty_pip_output_data(cyapa,
+ cyapa->resp_data,
+ &length,
+ cyapa->resp_sort_func);
+ if (cyapa->resp_len && length != 0) {
+ *cyapa->resp_len = length;
+ complete(&cyapa->cmd_ready);
+ atomic_dec(&cyapa->cmd_issued);
+ }
+ goto out;
+ }
+
+ if (cyapa->resp_data && cyapa->resp_len) {
+ *cyapa->resp_len = (*cyapa->resp_len < length) ?
+ *cyapa->resp_len : length;
+ memcpy(cyapa->resp_data, cyapa->tmp_irq_buf,
+ *cyapa->resp_len);
+ }
+ }
+ complete(&cyapa->cmd_ready);
+ atomic_dec(&cyapa->cmd_issued);
+ goto out;
+ }
+
/*
* Don't read input if input device has not been configured.
* This check solves a race during probe() between irq_request()
* and irq_disable(), since there is no way to request an irq that is
* initially disabled.
*/
- if (!input || atomic_read(&cyapa->in_detecting))
+ if (!input || atomic_read(&cyapa->in_detecting)) {
+ if (cyapa->gen_detecting == CYAPA_GEN5) {
+ cyapa_i2c_pip_read(cyapa, cyapa->tmp_irq_buf, 2);
+ length = get_unaligned_le16(cyapa->tmp_irq_buf);
+ if (length > 2)
+ cyapa_i2c_pip_read(cyapa,
+ cyapa->tmp_irq_buf, length);
+ }
goto out;
+ }

if (cyapa->cyapa_irq_handler)
cyapa->cyapa_irq_handler(cyapa);
@@ -1051,6 +2703,22 @@ static int cyapa_get_state(struct cyapa *cyapa)
goto out_detected;
cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
}
+ if ((cyapa->gen == CYAPA_GEN_UNKNOWN ||
+ cyapa->gen == CYAPA_GEN5) &&
+ !smbus && even_addr) {
+ cyapa->gen_detecting = CYAPA_GEN5;
+
+ cyapa_enable_irq_save(cyapa);
+ ret = cyapa_gen5_state_parse(cyapa,
+ status, BL_STATUS_SIZE);
+ cyapa_irq_restore(cyapa);
+ if (ret == 0) {
+ cyapa_empty_pip_output_data(cyapa,
+ NULL, NULL, NULL);
+ goto out_detected;
+ }
+ cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
+ }

/*
* cannot detect communication protocol based on current
@@ -1157,7 +2825,27 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, cyapa->max_abs_y, 0,
0);
- input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, cyapa->max_z, 0, 0);
+ if (cyapa->gen > CYAPA_GEN3) {
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+ /* orientation is the angle between the vertial axis and
+ * the major axis of the contact ellipse.
+ * The range is -127 to 127.
+ * the positive direction is clockwise form the vertical axis.
+ * If the ellipse of contact degenerates into a circle,
+ * orientation is reported as 0.
+ *
+ * Also, for Gen5 trackpad the accurate of this orientation
+ * value is value + (-30 ~ 30).
+ */
+ input_set_abs_params(input, ABS_MT_ORIENTATION,
+ -127, 127, 0, 0);
+ }
+ if (cyapa->gen >= CYAPA_GEN5) {
+ input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
+ input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 255, 0, 0);
+ }

input_abs_set_res(input, ABS_MT_POSITION_X,
cyapa->max_abs_x / cyapa->physical_size_x);
@@ -1202,6 +2890,14 @@ static void cyapa_detect(struct cyapa *cyapa)
char *envp[] = {"ERROR=1", NULL};
int ret;

+ /*
+ * Try to dump all bufferred data if it's known gen5 trackpad
+ * before detecting. Because the irq routine may disabled
+ * before enter this routine.
+ */
+ if (cyapa->gen == CYAPA_GEN5)
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
ret = cyapa_check_is_operational(cyapa);
if (ret == -ETIMEDOUT)
dev_err(dev, "no device detected, %d\n", ret);
@@ -1219,7 +2915,8 @@ static void cyapa_detect(struct cyapa *cyapa)
dev_err(dev, "create input_dev instance failed, %d\n",
ret);

- enable_irq(cyapa->irq);
+ cyapa_enable_irq(cyapa);
+
/*
* On some systems, a system crash / warm boot does not reset
* the device's current power mode to FULL_ACTIVE.
@@ -1238,6 +2935,14 @@ static void cyapa_detect(struct cyapa *cyapa)
ret);
}
}
+
+ /*
+ * Try to dump all bufferred data if it's known gen5 trackpad before
+ * detecting. Because the irq routine may disabled before
+ * leave this routine.
+ */
+ if (cyapa->gen == CYAPA_GEN5)
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
}

static void cyapa_detect_async(void *data, async_cookie_t cookie)
@@ -1290,7 +2995,16 @@ static int cyapa_probe(struct i2c_client *client,
cyapa->state = CYAPA_STATE_NO_DEVICE;
cyapa->suspend_power_mode = PWR_MODE_SLEEP;

+ init_completion(&cyapa->cmd_ready);
+ atomic_set(&cyapa->cmd_issued, 0);
+ mutex_init(&cyapa->irq_state_lock);
+ mutex_init(&cyapa->cmd_lock);
+ atomic_set(&cyapa->in_detecting, 0);
+ cyapa->resp_sort_func = NULL;
+ cyapa->in_progress_cmd = TSG_INVALID_CMD;
+
cyapa->irq = client->irq;
+ cyapa->irq_enabled = true;
ret = request_threaded_irq(cyapa->irq,
NULL,
cyapa_irq,
@@ -1301,7 +3015,7 @@ static int cyapa_probe(struct i2c_client *client,
dev_err(dev, "IRQ request failed: %d\n, ", ret);
goto err_unregister_device;
}
- disable_irq(cyapa->irq);
+ cyapa_disable_irq(cyapa);

async_schedule(cyapa_detect_async, cyapa);
return 0;
@@ -1336,7 +3050,7 @@ static int cyapa_suspend(struct device *dev)
u8 power_mode;
struct cyapa *cyapa = dev_get_drvdata(dev);

- disable_irq(cyapa->irq);
+ cyapa_disable_irq(cyapa);
cyapa->suspended = true;

/*
@@ -1365,7 +3079,7 @@ static int cyapa_resume(struct device *dev)

if (device_may_wakeup(dev) && cyapa->irq_wake)
disable_irq_wake(cyapa->irq);
- enable_irq(cyapa->irq);
+ cyapa_enable_irq(cyapa);

if (cyapa->cyapa_set_power_mode) {
ret = cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE,
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.