diff mbox

[1/6] input: cyapa: rearchitecture driver to support function pointers

Message ID 77BC725C9062764F874D79F51E1F1A8F40C11432@S04-MBX01-01.s04.local (mailing list archive)
State New, archived
Headers show

Commit Message

Dudley Du April 14, 2014, 7:53 a.m. UTC
Re-architecture the driver to support function pointers, so it can
support and integrate new devices later in one driver. Including use
async thread in device proble to speed up system boot time.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@cypress.com>
---
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.
diff mbox

Patch

diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index b409c3d..4361ee1 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -14,15 +14,19 @@ 
  * more details.
  */

+#include <linux/async.h>
 #include <linux/delay.h>
+#include <linux/firmware.h>
 #include <linux/i2c.h>
 #include <linux/input.h>
 #include <linux/input/mt.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
+#include <linux/completion.h>
 #include <linux/slab.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_NAME   "Cypress APA Trackpad (cyapa)"
@@ -85,14 +89,19 @@ 
  * bit 7: Busy
  * bit 6 - 5: Reserved
  * bit 4: Bootloader running
- * bit 3 - 1: Reserved
+ * bit 3 - 2: Reserved
+ * bit 1: Watchdog Reset
  * bit 0: Checksum valid
  */
 #define REG_BL_STATUS        0x01
+#define BL_STATUS_REV_6_5    0x60
 #define BL_STATUS_BUSY       0x80
 #define BL_STATUS_RUNNING    0x10
-#define BL_STATUS_DATA_VALID 0x08
+#define BL_STATUS_REV_3_2    0x0c
+#define BL_STATUS_WATCHDOG   0x02
 #define BL_STATUS_CSUM_VALID 0x01
+#define BL_STATUS_REV_MASK (BL_STATUS_WATCHDOG | BL_STATUS_REV_3_2 | \
+                           BL_STATUS_REV_6_5)

 /*
  * Bootloader Error Register
@@ -112,10 +121,14 @@ 
 #define BL_ERROR_CMD_CSUM    0x10
 #define BL_ERROR_FLASH_PROT  0x08
 #define BL_ERROR_FLASH_CSUM  0x04
+#define BL_ERROR_RESERVED    0x03

 #define BL_STATUS_SIZE  3  /* length of bootloader status registers */
 #define BLK_HEAD_BYTES 32

+/* Macro for register map group offset. */
+#define CYAPA_REG_MAP_SIZE  256
+
 #define PRODUCT_ID_SIZE  16
 #define QUERY_DATA_SIZE  27
 #define REG_PROTOCOL_GEN_QUERY_OFFSET  20
@@ -134,17 +147,27 @@ 
 #define CYAPA_OFFSET_SOFT_RESET  REG_OFFSET_COMMAND_BASE

 #define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1)
+#define SET_POWER_MODE_DELAY   10000  /* unit: us */
+#define SET_POWER_MODE_TRIES   5

 #define PWR_MODE_MASK   0xfc
 #define PWR_MODE_FULL_ACTIVE (0x3f << 2)
-#define PWR_MODE_IDLE        (0x05 << 2) /* default sleep time is 50 ms. */
+#define PWR_MODE_IDLE        (0x03 << 2) /* default rt suspend scanrate: 30ms */
+#define PWR_MODE_SLEEP       (0x05 << 2) /* default suspend scanrate: 50ms */
+#define PWR_MODE_BTN_ONLY    (0x01 << 2)
 #define PWR_MODE_OFF         (0x00 << 2)

+#define BTN_ONLY_MODE_NAME   "buttononly"
+#define OFF_MODE_NAME        "off"
+
 #define PWR_STATUS_MASK      0x0c
 #define PWR_STATUS_ACTIVE    (0x03 << 2)
 #define PWR_STATUS_IDLE      (0x02 << 2)
+#define PWR_STATUS_BTN_ONLY  (0x01 << 2)
 #define PWR_STATUS_OFF       (0x00 << 2)

+#define AUTOSUSPEND_DELAY   2000 /* unit : ms */
+
 /*
  * CYAPA trackpad device states.
  * Used in register 0x00, bit1-0, DeviceStatus field.
@@ -153,6 +176,26 @@ 
 #define CYAPA_DEV_NORMAL  0x03
 #define CYAPA_DEV_BUSY    0x01

+#define MAX_TMP_BUF_SIZE (CYAPA_REG_MAP_SIZE)
+
+struct cyapa;
+typedef void (*irq_handler_func)(struct cyapa *);
+typedef int (*set_power_mode_func)(struct cyapa *, u8, u16);
+typedef int (*bl_enter_func)(struct cyapa *);
+typedef int (*bl_activate_func)(struct cyapa *);
+typedef int (*bl_verify_app_integrity_func)(struct cyapa *);
+typedef int (*bl_deactivate_func)(struct cyapa *);
+typedef int (*bl_read_fw_func)(struct cyapa *);
+typedef int (*check_fw_func)(struct cyapa *, const struct firmware *);
+typedef int (*bl_initiate_func)(struct cyapa *, const struct firmware *);
+typedef int (*update_fw_func)(struct cyapa *, const struct firmware *);
+typedef ssize_t (*show_baseline_func)(
+       struct device *, struct device_attribute *, char *);
+typedef ssize_t (*calibrate_store_func)(
+       struct device *, struct device_attribute *, const char *, size_t);
+typedef bool (*func_sort)(struct cyapa *, u8 *, int);
+typedef int (*read_raw_data_func)(struct cyapa *);
+
 enum cyapa_state {
        CYAPA_STATE_OP,
        CYAPA_STATE_BL_IDLE,
@@ -210,14 +253,41 @@  struct cyapa {
        bool irq_wake;  /* irq wake is enabled */
        bool smbus;

+       /* power mode settings */
+       u8 suspend_power_mode;
+       u16 suspend_sleep_time;
+       bool suspended;
+
        /* read from query data region. */
        char product_id[16];
+       u8 fw_maj_ver;  /* firmware major version. */
+       u8 fw_min_ver;  /* firmware minor version. */
        u8 btn_capability;
        u8 gen;
        int max_abs_x;
        int max_abs_y;
        int physical_size_x;
        int physical_size_y;
+
+       u8 gen_detecting;
+
+       atomic_t in_detecting;
+
+       u8 tmp_buf[MAX_TMP_BUF_SIZE];
+
+       check_fw_func cyapa_check_fw;
+       bl_enter_func cyapa_bl_enter;
+       bl_activate_func cyapa_bl_activate;
+       bl_initiate_func cyapa_bl_initiate;
+       update_fw_func cyapa_update_fw;
+       bl_verify_app_integrity_func cyapa_bl_verify_app_integrity;
+       bl_deactivate_func cyapa_bl_deactivate;
+       show_baseline_func cyapa_show_baseline;
+       calibrate_store_func cyapa_calibrate_store;
+       irq_handler_func cyapa_irq_handler;
+       set_power_mode_func cyapa_set_power_mode;
+       bl_read_fw_func cyapa_read_fw;
+       read_raw_data_func cyapa_read_raw_data;
 };

 static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
@@ -316,6 +386,12 @@  static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
        { CYAPA_SMBUS_BLK_HEAD, 16 },
 };

+static const char unique_str[] = "CYTRA";
+
+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);
+
 static ssize_t cyapa_i2c_reg_read_block(struct cyapa *cyapa, u8 reg, size_t len,
                                        u8 *values)
 {
@@ -417,88 +493,57 @@  static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values)
 }

 /*
- * Query device for its current operating state.
+ * Determine the Gen3 trackpad device's current operating state.
  *
  */
-static int cyapa_get_state(struct cyapa *cyapa)
+static int cyapa_gen3_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
 {
-       int ret;
-       u8 status[BL_STATUS_SIZE];
-
-       cyapa->state = CYAPA_STATE_NO_DEVICE;
-
-       /*
-        * Get trackpad status by reading 3 registers starting from 0.
-        * If the device is in the bootloader, this will be BL_HEAD.
-        * If the device is in operation mode, this will be the DATA regs.
-        *
-        */
-       ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
-                                      status);
-
-       /*
-        * On smbus systems in OP mode, the i2c_reg_read will fail with
-        * -ETIMEDOUT.  In this case, try again using the smbus equivalent
-        * command.  This should return a BL_HEAD indicating CYAPA_STATE_OP.
-        */
-       if (cyapa->smbus && (ret == -ETIMEDOUT || ret == -ENXIO))
-               ret = cyapa_read_block(cyapa, CYAPA_CMD_BL_STATUS, status);
-
-       if (ret != BL_STATUS_SIZE)
-               goto error;
-
-       if ((status[REG_OP_STATUS] & OP_STATUS_SRC) == OP_STATUS_SRC) {
-               switch (status[REG_OP_STATUS] & OP_STATUS_DEV) {
-               case CYAPA_DEV_NORMAL:
-               case CYAPA_DEV_BUSY:
+       /* Parse based on Gen3 characteristic regiters and bits */
+       if (reg_data[0] == 0x00 && reg_data[2] == 0x00 &&
+               (reg_data[1] == 0x11 || reg_data[1] == 0x10)) {
+               /* normal state after power on or reset,
+                * reg_data[1] == 0x11, firmware image checksum is valid.
+                * reg_data[1] == 0x10, firmware image checksum is invalid. */
+               cyapa->gen = CYAPA_GEN3;
+               cyapa->state = CYAPA_STATE_BL_IDLE;
+               return 0;
+       } else if (reg_data[0] == 0x00 &&
+               (reg_data[1] & 0x10) == 0x10) {
+               cyapa->gen = CYAPA_GEN3;
+               if (reg_data[1] & BL_STATUS_BUSY) {
+                       cyapa->state = CYAPA_STATE_BL_BUSY;
+               } else {
+                       if ((reg_data[2] & 0x20) == 0x00)
+                               cyapa->state = CYAPA_STATE_BL_IDLE;
+                       else
+                               cyapa->state = CYAPA_STATE_BL_ACTIVE;
+               }
+               return 0;
+       } else if ((reg_data[0] & 0x80) && (reg_data[1] & 0x08)) {
+               /* normal state when running in operaitonal mode,
+                * may also not in full power state or
+                * busying in command process. */
+               if (((reg_data[1] >> 4) & 0x07) <= 5) {
+                       /* only finger number data is valid. */
+                       cyapa->gen = CYAPA_GEN3;
                        cyapa->state = CYAPA_STATE_OP;
-                       break;
-               default:
-                       ret = -EAGAIN;
-                       goto error;
+                       return 0;
                }
-       } else {
-               if (status[REG_BL_STATUS] & BL_STATUS_BUSY)
-                       cyapa->state = CYAPA_STATE_BL_BUSY;
-               else if (status[REG_BL_ERROR] & BL_ERROR_BOOTLOADING)
-                       cyapa->state = CYAPA_STATE_BL_ACTIVE;
-               else
-                       cyapa->state = CYAPA_STATE_BL_IDLE;
+       } else if (reg_data[0] == 0x0C && reg_data[1] == 0x08) {
+               /* Op state when first two registers overwritten with 0x00 */
+               cyapa->gen = CYAPA_GEN3;
+               cyapa->state = CYAPA_STATE_OP;
+               return 0;
+       } else if (reg_data[1] & (BL_STATUS_RUNNING | BL_STATUS_BUSY)) {
+               cyapa->gen = CYAPA_GEN3;
+               cyapa->state = CYAPA_STATE_BL_BUSY;
+               return 0;
        }

-       return 0;
-error:
-       return (ret < 0) ? ret : -EAGAIN;
+       return -EAGAIN;
 }

-/*
- * Poll device for its status in a loop, waiting up to timeout for a response.
- *
- * When the device switches state, it usually takes ~300 ms.
- * However, when running a new firmware image, the device must calibrate its
- * sensors, which can take as long as 2 seconds.
- *
- * Note: The timeout has granularity of the polling rate, which is 100 ms.
- *
- * Returns:
- *   0 when the device eventually responds with a valid non-busy state.
- *   -ETIMEDOUT if device never responds (too many -EAGAIN)
- *   < 0    other errors
- */
-static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
-{
-       int ret;
-       int tries = timeout / 100;
-
-       ret = cyapa_get_state(cyapa);
-       while ((ret || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
-               msleep(100);
-               ret = cyapa_get_state(cyapa);
-       }
-       return (ret == -EAGAIN || ret == -ETIMEDOUT) ? -ETIMEDOUT : ret;
-}
-
-static int cyapa_bl_deactivate(struct cyapa *cyapa)
+static int cyapa_gen3_bl_deactivate(struct cyapa *cyapa)
 {
        int ret;

@@ -530,7 +575,7 @@  static int cyapa_bl_deactivate(struct cyapa *cyapa)
  *   -EAGAIN device is stuck in bootloader, b/c it has invalid firmware
  *   0       device is supported and in operational mode
  */
-static int cyapa_bl_exit(struct cyapa *cyapa)
+static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
 {
        int ret;

@@ -546,9 +591,10 @@  static int cyapa_bl_exit(struct cyapa *cyapa)
        /*
         * In addition, when a device boots for the first time after being
         * updated to new firmware, it must first calibrate its sensors, which
-        * can take up to an additional 2 seconds.
+        * can take up to an additional 2 seconds. If the device power is
+        * running low, this may take even longer.
         */
-       ret = cyapa_poll_state(cyapa, 2000);
+       ret = cyapa_poll_state(cyapa, 4000);
        if (ret < 0)
                return ret;
        if (cyapa->state != CYAPA_STATE_OP)
@@ -557,33 +603,104 @@  static int cyapa_bl_exit(struct cyapa *cyapa)
        return 0;
 }

+static u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode)
+{
+       u8 encoded_time = pwr_mode >> 2;
+       return (encoded_time < 10) ? encoded_time * 10
+                                  : (encoded_time - 5) * 20;
+}
+
+/*
+ * cyapa_get_wait_time_for_pwr_cmd
+ *
+ * Compute the amount of time we need to wait after updating the touchpad
+ * power mode. The touchpad needs to consume the incoming power mode set
+ * command at the current clock rate.
+ */
+
+static u16 cyapa_get_wait_time_for_pwr_cmd(u8 pwr_mode)
+{
+       switch (pwr_mode) {
+       case PWR_MODE_FULL_ACTIVE: return 20;
+       case PWR_MODE_BTN_ONLY: return 20;
+       case PWR_MODE_OFF: return 20;
+       default: return cyapa_pwr_cmd_to_sleep_time(pwr_mode) + 50;
+       }
+}
+
 /*
  * Set device power mode
  *
+ * Write to the field to configure power state. Power states include :
+ *   Full : Max scans and report rate.
+ *   Idle : Report rate set by user specified time.
+ *   ButtonOnly : No scans for fingers. When the button is triggered,
+ *     a slave interrupt is asserted to notify host to wake up.
+ *   Off : Only awake for i2c commands from host. No function for button
+ *     or touch sensors.
+ *
+ * The power_mode command should conform to the following :
+ *   Full : 0x3f
+ *   Idle : Configurable from 20 to 1000ms. See note below for
+ *     cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
+ *   ButtonOnly : 0x01
+ *   Off : 0x00
+ *
+ * Device power mode can only be set when device is in operational mode.
  */
-static int cyapa_set_power_mode(struct cyapa *cyapa, u8 power_mode)
+static int cyapa_gen3_set_power_mode(struct cyapa *cyapa, u8 power_mode,
+               u16 reverved)
 {
-       struct device *dev = &cyapa->client->dev;
        int ret;
        u8 power;
+       int tries = SET_POWER_MODE_TRIES;
+       u16 sleep_time;

+       /* Specific parameter for Gen4 and later trackpad devices.
+        * Avoid compile warning.
+        */
+       reverved = 0;
        if (cyapa->state != CYAPA_STATE_OP)
                return 0;

-       ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE);
+       while (true) {
+               ret = cyapa_read_byte(cyapa, CYAPA_CMD_POWER_MODE);
+               if (ret >= 0 || --tries < 1)
+                       break;
+               usleep_range(SET_POWER_MODE_DELAY, 2 * SET_POWER_MODE_DELAY);
+       }
        if (ret < 0)
                return ret;

-       power = ret & ~PWR_MODE_MASK;
+       /*
+        * Return early if the power mode to set is the same as the current
+        * one.
+        */
+       if ((ret & PWR_MODE_MASK) == power_mode)
+               return 0;
+
+       sleep_time = cyapa_get_wait_time_for_pwr_cmd(ret & PWR_MODE_MASK);
+       power = ret;
+       power &= ~PWR_MODE_MASK;
        power |= power_mode & PWR_MODE_MASK;
-       ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power);
-       if (ret < 0)
-               dev_err(dev, "failed to set power_mode 0x%02x err = %d\n",
-                       power_mode, ret);
+       while (true) {
+               ret = cyapa_write_byte(cyapa, CYAPA_CMD_POWER_MODE, power);
+               if (!ret || --tries < 1)
+                       break;
+               usleep_range(SET_POWER_MODE_DELAY, 2 * SET_POWER_MODE_DELAY);
+       }
+
+       /*
+        * Wait for the newly set power command to go in at the previous
+        * clock speed (scanrate) used by the touchpad firmware. Not
+        * doing so before issuing the next command may result in errors
+        * depending on the command's content.
+        */
+       msleep(sleep_time);
        return ret;
 }

-static int cyapa_get_query_data(struct cyapa *cyapa)
+static int cyapa_gen3_get_query_data(struct cyapa *cyapa)
 {
        u8 query_data[QUERY_DATA_SIZE];
        int ret;
@@ -592,10 +709,8 @@  static int cyapa_get_query_data(struct cyapa *cyapa)
                return -EBUSY;

        ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_QUERY, query_data);
-       if (ret < 0)
-               return ret;
        if (ret != QUERY_DATA_SIZE)
-               return -EIO;
+               return (ret < 0) ? ret : -EIO;

        memcpy(&cyapa->product_id[0], &query_data[0], 5);
        cyapa->product_id[5] = '-';
@@ -604,6 +719,9 @@  static int cyapa_get_query_data(struct cyapa *cyapa)
        memcpy(&cyapa->product_id[13], &query_data[11], 2);
        cyapa->product_id[15] = '\0';

+       cyapa->fw_maj_ver = query_data[15];
+       cyapa->fw_min_ver = query_data[16];
+
        cyapa->btn_capability = query_data[19] & CAPABILITY_BTN_MASK;

        cyapa->gen = query_data[20] & 0x0f;
@@ -633,30 +751,38 @@  static int cyapa_get_query_data(struct cyapa *cyapa)
  *   -EINVAL device is in operational mode, but not supported by this driver
  *   0       device is supported
  */
-static int cyapa_check_is_operational(struct cyapa *cyapa)
+static int cyapa_gen3_do_operational_check(struct cyapa *cyapa)
 {
        struct device *dev = &cyapa->client->dev;
-       static const char unique_str[] = "CYTRA";
        int ret;

-       ret = cyapa_poll_state(cyapa, 2000);
-       if (ret < 0)
-               return ret;
        switch (cyapa->state) {
        case CYAPA_STATE_BL_ACTIVE:
-               ret = cyapa_bl_deactivate(cyapa);
-               if (ret)
+               ret = cyapa_gen3_bl_deactivate(cyapa);
+               if (ret) {
+                       dev_err(dev, "failed to bl_deactivate. %d\n", ret);
                        return ret;
+               }

        /* Fallthrough state */
        case CYAPA_STATE_BL_IDLE:
-               ret = cyapa_bl_exit(cyapa);
-               if (ret)
+               ret = cyapa_gen3_bl_exit(cyapa);
+               if (ret) {
+                       dev_err(dev, "failed to bl_exit. %d\n", ret);
                        return ret;
+               }

        /* Fallthrough state */
        case CYAPA_STATE_OP:
-               ret = cyapa_get_query_data(cyapa);
+               /*
+                * Reading query data before going back to the full mode
+                * may cause problems, so we set the power mode first here.
+                */
+               ret = cyapa_gen3_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE, 0);
+               if (ret)
+                       dev_err(dev, "%s: set full power mode failed, (%d)\n",
+                               __func__, ret);
+               ret = cyapa_gen3_get_query_data(cyapa);
                if (ret < 0)
                        return ret;

@@ -682,27 +808,25 @@  static int cyapa_check_is_operational(struct cyapa *cyapa)
        return 0;
 }

-static irqreturn_t cyapa_irq(int irq, void *dev_id)
+static void cyapa_gen3_irq_handler(struct cyapa *cyapa)
 {
-       struct cyapa *cyapa = dev_id;
-       struct device *dev = &cyapa->client->dev;
        struct input_dev *input = cyapa->input;
        struct cyapa_reg_data data;
        int i;
        int ret;
        int num_fingers;

-       if (device_may_wakeup(dev))
-               pm_wakeup_event(dev, 0);
-
        ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data);
-       if (ret != sizeof(data))
-               goto out;
+       if (ret != sizeof(data)) {
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
+       }

        if ((data.device_status & OP_STATUS_SRC) != OP_STATUS_SRC ||
            (data.device_status & OP_STATUS_DEV) != CYAPA_DEV_NORMAL ||
            (data.finger_btn & OP_DATA_VALID) != OP_DATA_VALID) {
-               goto out;
+               async_schedule(cyapa_detect_async, cyapa);
+               return;
        }

        num_fingers = (data.finger_btn >> 4) & 0x0f;
@@ -724,22 +848,271 @@  static irqreturn_t cyapa_irq(int irq, void *dev_id)

        if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK)
                input_report_key(input, BTN_LEFT,
-                                data.finger_btn & OP_DATA_LEFT_BTN);
-
+                                !!(data.finger_btn & OP_DATA_LEFT_BTN));
        if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK)
                input_report_key(input, BTN_MIDDLE,
-                                data.finger_btn & OP_DATA_MIDDLE_BTN);
-
+                                !!(data.finger_btn & OP_DATA_MIDDLE_BTN));
        if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK)
                input_report_key(input, BTN_RIGHT,
-                                data.finger_btn & OP_DATA_RIGHT_BTN);
-
+                                !!(data.finger_btn & OP_DATA_RIGHT_BTN));
        input_sync(input);
+}
+
+/* Returns the number of read bytes or a negative errno code. */
+static ssize_t cyapa_i2c_read(struct cyapa *cyapa, u8 reg, size_t len,
+                                       u8 *values)
+{
+       int ret;
+       struct i2c_client *client = cyapa->client;
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = client->addr,
+                       .flags = 0,
+                       .len = 1,
+                       .buf = &reg,
+               },
+               {
+                       .addr = client->addr,
+                       .flags = I2C_M_RD,
+                       .len = len,
+                       .buf = values,
+               },
+       };
+
+       ret = i2c_transfer(client->adapter, msgs, 2);
+
+       return (ret == 2) ? len : ((ret < 0) ? ret : -EIO);
+}
+
+/**
+ * cyapa_i2c_write - execute i2c block data write operation
+ * @cyapa: Handle to this driver
+ * @ret: Offset of the data to written in the register map
+ * @len: the data length of bytes to written.
+ * @values: Data to be written
+ *
+ * This executes returns a negative errno code else zero on success.
+ */
+static ssize_t cyapa_i2c_write(struct cyapa *cyapa, u8 reg,
+                                        size_t len, const u8 *values)
+{
+       int ret;
+       struct i2c_client *client = cyapa->client;
+
+       cyapa->tmp_buf[0] = reg;
+       memcpy(&cyapa->tmp_buf[1], values, len);
+       ret = i2c_master_send(client, cyapa->tmp_buf, len + 1);
+
+       return (ret == (len + 1)) ? 0 : ((ret < 0) ? ret : -EIO);
+}
+
+static void cyapa_default_irq_handler(struct cyapa *cyapa)
+{
+       async_schedule(cyapa_detect_async, cyapa);
+}
+
+/*
+ * Check if device is operational.
+ *
+ * An operational device is responding, has exited bootloader, and has
+ * firmware supported by this driver.
+ *
+ * Returns:
+ *   -EBUSY  no device or in bootloader
+ *   -EIO    failure while reading from device
+ *   -EAGAIN device is still in bootloader
+ *           if ->state = CYAPA_STATE_BL_IDLE, device has invalid firmware
+ *   -EINVAL device is in operational mode, but not supported by this driver
+ *   0       device is supported
+ */
+static int cyapa_check_is_operational(struct cyapa *cyapa)
+{
+       int ret;
+
+       ret = cyapa_poll_state(cyapa, 4000);
+       if (ret)
+               return ret;
+
+       switch (cyapa->gen) {
+       case CYAPA_GEN3:
+               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 = cyapa_gen3_bl_deactivate;
+               cyapa->cyapa_show_baseline = NULL;
+               cyapa->cyapa_calibrate_store = NULL;
+               cyapa->cyapa_irq_handler = cyapa_gen3_irq_handler;
+               cyapa->cyapa_set_power_mode = cyapa_gen3_set_power_mode;
+               cyapa->cyapa_read_fw = NULL;
+               cyapa->cyapa_read_raw_data = NULL;
+
+               ret = cyapa_gen3_do_operational_check(cyapa);
+               break;
+       default:
+               cyapa->gen = CYAPA_GEN_UNKNOWN;
+               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_show_baseline = NULL;
+               cyapa->cyapa_calibrate_store = NULL;
+               cyapa->cyapa_irq_handler = cyapa_default_irq_handler;
+               cyapa->cyapa_set_power_mode = NULL;
+               cyapa->cyapa_read_fw = NULL;
+               cyapa->cyapa_read_raw_data = NULL;
+
+               ret = -EAGAIN;
+               break;
+       }
+
+       return ret;
+}
+
+static irqreturn_t cyapa_irq(int irq, void *dev_id)
+{
+       struct cyapa *cyapa = dev_id;
+       struct device *dev = &cyapa->client->dev;
+       struct input_dev *input = cyapa->input;
+       int length;
+
+       if (device_may_wakeup(dev))
+               pm_wakeup_event(dev, 0);
+
+       /*
+        * 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))
+               goto out;
+
+       if (cyapa->cyapa_irq_handler)
+               cyapa->cyapa_irq_handler(cyapa);

 out:
        return IRQ_HANDLED;
 }

+/*
+ * Query device for its current operating state.
+ *
+ */
+static int cyapa_get_state(struct cyapa *cyapa)
+{
+       int ret;
+       u8 status[BL_STATUS_SIZE];
+       u8 cmd[32];
+       /* The i2c address of gen4 and gen5 trackpad device must be even. */
+       bool even_addr = ((cyapa->client->addr & 0x0001) == 0);
+       bool smbus = false;
+       int retires = 2;
+
+       cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+       /*
+        * Get trackpad status by reading 3 registers starting from 0.
+        * If the device is in the bootloader, this will be BL_HEAD.
+        * If the device is in operation mode, this will be the DATA regs.
+        *
+        */
+       ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
+                                      status);
+
+       /*
+        * On smbus systems in OP mode, the i2c_reg_read will fail with
+        * -ETIMEDOUT.  In this case, try again using the smbus equivalent
+        * command.  This should return a BL_HEAD indicating CYAPA_STATE_OP.
+        */
+       if (cyapa->smbus && (ret == -ETIMEDOUT || ret == -ENXIO)) {
+               if (!even_addr)
+                       ret = cyapa_read_block(cyapa,
+                                       CYAPA_CMD_BL_STATUS, status);
+               smbus = true;
+       }
+       if (ret != BL_STATUS_SIZE)
+               goto error;
+
+       /*
+        * detect trackpad protocol based on characristic registers and bits.
+        */
+       do {
+               if (cyapa->gen == CYAPA_GEN_UNKNOWN ||
+                               cyapa->gen == CYAPA_GEN3) {
+                       cyapa->gen_detecting = CYAPA_GEN3;
+                       ret = cyapa_gen3_state_parse(cyapa,
+                                       status, BL_STATUS_SIZE);
+                       if (ret == 0)
+                               goto out_detected;
+                       cyapa->gen_detecting = CYAPA_GEN_UNKNOWN;
+               }
+
+               /*
+                * cannot detect communication protocol based on current
+                * charateristic registers and bits.
+                * So write error command to do further detection.
+                * this method only valid on I2C bus.
+                * for smbus interface, it won't have overwrite issue.
+                */
+               if (!smbus) {
+                       cmd[0] = 0x00;
+                       cmd[1] = 0x00;
+                       ret = cyapa_i2c_write(cyapa, 0x00, 2, cmd);
+                       if (ret)
+                               goto error;
+
+                       msleep(50);
+
+                       ret = cyapa_i2c_read(cyapa, BL_HEAD_OFFSET,
+                                       BL_STATUS_SIZE, status);
+                       if (ret != BL_STATUS_SIZE)
+                               goto error;
+               }
+       } while (--retires > 0 && !smbus);
+
+       goto error;
+
+out_detected:
+       return 0;
+
+error:
+       return (ret < 0) ? ret : -EAGAIN;
+}
+
+/*
+ * Poll device for its status in a loop, waiting up to timeout for a response.
+ *
+ * When the device switches state, it usually takes ~300 ms.
+ * However, when running a new firmware image, the device must calibrate its
+ * sensors, which can take as long as 2 seconds.
+ *
+ * Note: The timeout has granularity of the polling rate, which is 100 ms.
+ *
+ * Returns:
+ *   0 when the device eventually responds with a valid non-busy state.
+ *   -ETIMEDOUT if device never responds (too many -EAGAIN)
+ *   < 0    other errors
+ */
+static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
+{
+       int ret;
+       int tries = timeout / 100;
+
+       ret = cyapa_get_state(cyapa);
+
+       while ((ret || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
+               msleep(100);
+               ret = cyapa_get_state(cyapa);
+       }
+
+       return (ret == -EAGAIN || ret == -ETIMEDOUT) ? -ETIMEDOUT : ret;
+}
+
 static u8 cyapa_check_adapter_functionality(struct i2c_client *client)
 {
        u8 ret = CYAPA_ADAPTER_FUNC_NONE;
@@ -823,6 +1196,62 @@  err_free_device:
        return ret;
 }

+static void cyapa_detect(struct cyapa *cyapa)
+{
+       struct device *dev = &cyapa->client->dev;
+       char *envp[] = {"ERROR=1", NULL};
+       int ret;
+
+       ret = cyapa_check_is_operational(cyapa);
+       if (ret == -ETIMEDOUT)
+               dev_err(dev, "no device detected, %d\n", ret);
+       else if (ret)
+               dev_err(dev, "device detected, but not operational, %d\n", ret);
+
+       if (ret) {
+               kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+               return;
+       }
+
+       if (!cyapa->input) {
+               ret = cyapa_create_input_dev(cyapa);
+               if (ret)
+                       dev_err(dev, "create input_dev instance failed, %d\n",
+                               ret);
+
+               enable_irq(cyapa->irq);
+               /*
+                * On some systems, a system crash / warm boot does not reset
+                * the device's current power mode to FULL_ACTIVE.
+                * If such an event happens during suspend, after the device
+                * has been put in a low power mode, the device will still be
+                * in low power mode on a subsequent boot, since there was
+                * never a matching resume().
+                * Handle this by always forcing full power here, when a
+                * device is first detected to be in operational mode.
+                */
+               if (cyapa->cyapa_set_power_mode) {
+                       ret = cyapa->cyapa_set_power_mode(cyapa,
+                                       PWR_MODE_FULL_ACTIVE, 0);
+                       if (ret)
+                               dev_warn(dev, "set active power failed, %d\n",
+                                               ret);
+               }
+       }
+}
+
+static void cyapa_detect_async(void *data, async_cookie_t cookie)
+{
+       struct cyapa *cyapa = (struct cyapa *)data;
+
+       if (atomic_read(&cyapa->in_detecting))
+               return;
+
+       atomic_inc(&cyapa->in_detecting);
+       cyapa_detect(cyapa);
+       atomic_dec(&cyapa->in_detecting);
+}
+
 static int cyapa_probe(struct i2c_client *client,
                       const struct i2c_device_id *dev_id)
 {
@@ -830,6 +1259,7 @@  static int cyapa_probe(struct i2c_client *client,
        u8 adapter_func;
        struct cyapa *cyapa;
        struct device *dev = &client->dev;
+       union i2c_smbus_data dummy;

        adapter_func = cyapa_check_adapter_functionality(client);
        if (adapter_func == CYAPA_ADAPTER_FUNC_NONE) {
@@ -837,13 +1267,18 @@  static int cyapa_probe(struct i2c_client *client,
                return -EIO;
        }

+       /* Make sure there is something at this address */
+       if (dev->of_node && i2c_smbus_xfer(client->adapter, client->addr, 0,
+                       I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0)
+               return -ENODEV;
+
        cyapa = kzalloc(sizeof(struct cyapa), GFP_KERNEL);
        if (!cyapa) {
                dev_err(dev, "allocate memory for cyapa failed\n");
                return -ENOMEM;
        }

-       cyapa->gen = CYAPA_GEN3;
+       cyapa->gen = CYAPA_GEN_UNKNOWN;
        cyapa->client = client;
        i2c_set_clientdata(client, cyapa);
        sprintf(cyapa->phys, "i2c-%d-%04x/input0", client->adapter->nr,
@@ -853,23 +1288,7 @@  static int cyapa_probe(struct i2c_client *client,
        if (adapter_func == CYAPA_ADAPTER_FUNC_SMBUS)
                cyapa->smbus = true;
        cyapa->state = CYAPA_STATE_NO_DEVICE;
-       ret = cyapa_check_is_operational(cyapa);
-       if (ret) {
-               dev_err(dev, "device not operational, %d\n", ret);
-               goto err_mem_free;
-       }
-
-       ret = cyapa_create_input_dev(cyapa);
-       if (ret) {
-               dev_err(dev, "create input_dev instance failed, %d\n", ret);
-               goto err_mem_free;
-       }
-
-       ret = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
-       if (ret) {
-               dev_err(dev, "set active power failed, %d\n", ret);
-               goto err_unregister_device;
-       }
+       cyapa->suspend_power_mode = PWR_MODE_SLEEP;

        cyapa->irq = client->irq;
        ret = request_threaded_irq(cyapa->irq,
@@ -882,12 +1301,14 @@  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);

+       async_schedule(cyapa_detect_async, cyapa);
        return 0;

 err_unregister_device:
        input_unregister_device(cyapa->input);
-err_mem_free:
+       i2c_set_clientdata(client, NULL);
        kfree(cyapa);

        return ret;
@@ -899,7 +1320,10 @@  static int cyapa_remove(struct i2c_client *client)

        free_irq(cyapa->irq, cyapa);
        input_unregister_device(cyapa->input);
-       cyapa_set_power_mode(cyapa, PWR_MODE_OFF);
+
+       if (cyapa->cyapa_set_power_mode)
+               cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_OFF, 0);
+       i2c_set_clientdata(client, NULL);
        kfree(cyapa);

        return 0;
@@ -913,16 +1337,21 @@  static int cyapa_suspend(struct device *dev)
        struct cyapa *cyapa = dev_get_drvdata(dev);

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

        /*
         * Set trackpad device to idle mode if wakeup is allowed,
         * otherwise turn off.
         */
-       power_mode = device_may_wakeup(dev) ? PWR_MODE_IDLE
+       power_mode = device_may_wakeup(dev) ? cyapa->suspend_power_mode
                                            : PWR_MODE_OFF;
-       ret = cyapa_set_power_mode(cyapa, power_mode);
-       if (ret < 0)
-               dev_err(dev, "set power mode failed, %d\n", ret);
+       if (cyapa->cyapa_set_power_mode) {
+               ret = cyapa->cyapa_set_power_mode(cyapa, power_mode,
+                               cyapa->suspend_sleep_time);
+               if (ret < 0)
+                       dev_err(dev, "suspend set power mode failed, %d\n",
+                                       ret);
+       }

        if (device_may_wakeup(dev))
                cyapa->irq_wake = (enable_irq_wake(cyapa->irq) == 0);
@@ -936,12 +1365,18 @@  static int cyapa_resume(struct device *dev)

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

-       ret = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
-       if (ret)
-               dev_warn(dev, "resume active power failed, %d\n", ret);
+       if (cyapa->cyapa_set_power_mode) {
+               ret = cyapa->cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE,
+                               cyapa->suspend_sleep_time);
+               if (ret)
+                       dev_warn(dev, "resume active power failed, %d\n", ret);
+       }

-       enable_irq(cyapa->irq);
+       async_schedule(cyapa_detect_async, cyapa);
+
+       cyapa->suspended = false;
        return 0;
 }
 #endif /* CONFIG_PM_SLEEP */