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 */
