]> git.proxmox.com Git - mirror_ubuntu-disco-kernel.git/commitdiff
Input: cyapa - add gen6 device module support
authorDudley Du <dudl@cypress.com>
Mon, 20 Jul 2015 23:53:30 +0000 (16:53 -0700)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Fri, 24 Jul 2015 00:34:04 +0000 (17:34 -0700)
Based on the cyapa core, add support for basic functionality of the gen6
trackpad devices. The driver can automatically determine what protocol
(gen3, gen5, or gen6) should be used with the attached trackpad device.

Signed-off-by: Dudley Du <dudl@cypress.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/input/mouse/Makefile
drivers/input/mouse/cyapa.c
drivers/input/mouse/cyapa.h
drivers/input/mouse/cyapa_gen5.c
drivers/input/mouse/cyapa_gen6.c [new file with mode: 0644]

index 793300bfbddda44aba927fc4ce28650da3389b0b..ee6a6e9563d486e015c91ceb259549c246368a46 100644 (file)
@@ -24,7 +24,7 @@ obj-$(CONFIG_MOUSE_SYNAPTICS_I2C)     += synaptics_i2c.o
 obj-$(CONFIG_MOUSE_SYNAPTICS_USB)      += synaptics_usb.o
 obj-$(CONFIG_MOUSE_VSXXXAA)            += vsxxxaa.o
 
-cyapatp-objs := cyapa.o cyapa_gen3.o cyapa_gen5.o
+cyapatp-objs := cyapa.o cyapa_gen3.o cyapa_gen5.o cyapa_gen6.o
 psmouse-objs := psmouse-base.o synaptics.o focaltech.o
 
 psmouse-$(CONFIG_MOUSE_PS2_ALPS)       += alps.o
index 4c4429598adbb02c0c84154573212790bad34134..6952ed1c92c7bdf89475a52eb75695c8d8671ff1 100644 (file)
@@ -41,6 +41,9 @@ static int cyapa_reinitialize(struct cyapa *cyapa);
 
 bool cyapa_is_pip_bl_mode(struct cyapa *cyapa)
 {
+       if (cyapa->gen == CYAPA_GEN6 && cyapa->state == CYAPA_STATE_GEN6_BL)
+               return true;
+
        if (cyapa->gen == CYAPA_GEN5 && cyapa->state == CYAPA_STATE_GEN5_BL)
                return true;
 
@@ -49,6 +52,9 @@ bool cyapa_is_pip_bl_mode(struct cyapa *cyapa)
 
 bool cyapa_is_pip_app_mode(struct cyapa *cyapa)
 {
+       if (cyapa->gen == CYAPA_GEN6 && cyapa->state == CYAPA_STATE_GEN6_APP)
+               return true;
+
        if (cyapa->gen == CYAPA_GEN5 && cyapa->state == CYAPA_STATE_GEN5_APP)
                return true;
 
@@ -204,6 +210,15 @@ static int cyapa_get_state(struct cyapa *cyapa)
                        if (!error)
                                goto out_detected;
                }
+               if (cyapa->gen == CYAPA_GEN_UNKNOWN ||
+                               cyapa->gen == CYAPA_GEN6 ||
+                               cyapa->gen == CYAPA_GEN5) {
+                       error = cyapa_pip_state_parse(cyapa,
+                                       status, BL_STATUS_SIZE);
+                       if (!error)
+                               goto out_detected;
+               }
+               /* For old Gen5 trackpads detecting. */
                if ((cyapa->gen == CYAPA_GEN_UNKNOWN ||
                                cyapa->gen == CYAPA_GEN5) &&
                        !smbus && even_addr) {
@@ -300,6 +315,9 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
                return error;
 
        switch (cyapa->gen) {
+       case CYAPA_GEN6:
+               cyapa->ops = &cyapa_gen6_ops;
+               break;
        case CYAPA_GEN5:
                cyapa->ops = &cyapa_gen5_ops;
                break;
@@ -579,6 +597,8 @@ static int cyapa_initialize(struct cyapa *cyapa)
        error = cyapa_gen3_ops.initialize(cyapa);
        if (!error)
                error = cyapa_gen5_ops.initialize(cyapa);
+       if (!error)
+               error = cyapa_gen6_ops.initialize(cyapa);
        if (error)
                return error;
 
@@ -1136,9 +1156,11 @@ static char *cyapa_state_to_string(struct cyapa *cyapa)
        case CYAPA_STATE_BL_ACTIVE:
                return "bootloader active";
        case CYAPA_STATE_GEN5_BL:
+       case CYAPA_STATE_GEN6_BL:
                return "bootloader";
        case CYAPA_STATE_OP:
        case CYAPA_STATE_GEN5_APP:
+       case CYAPA_STATE_GEN6_APP:
                return "operational";  /* Normal valid state. */
        default:
                return "invalid mode";
index d019d1d2d1e419497f724d1d2b3904937d1f7523..3a211c0769e9c6835cdaa58b9ea7bd2d552e80fb 100644 (file)
@@ -19,6 +19,7 @@
 #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_GEN6   0x06   /* support TrueTouch GEN6 trackpad device. */
 
 #define CYAPA_NAME   "Cypress APA Trackpad (cyapa)"
 
 #define PIP_BL_APP_INFO_RESP_LENGTH    30
 #define PIP_BL_GET_INFO_RESP_LENGTH    19
 
+#define PIP_BL_PLATFORM_VER_SHIFT      4
+#define PIP_BL_PLATFORM_VER_MASK       0x0f
+
 #define PIP_PRODUCT_FAMILY_MASK                0xf000
 #define PIP_PRODUCT_FAMILY_TRACKPAD    0x1000
 
@@ -299,6 +303,14 @@ enum cyapa_state {
        CYAPA_STATE_OP,
        CYAPA_STATE_GEN5_BL,
        CYAPA_STATE_GEN5_APP,
+       CYAPA_STATE_GEN6_BL,
+       CYAPA_STATE_GEN6_APP,
+};
+
+struct gen6_interval_setting {
+       u16 active_interval;
+       u16 lp1_interval;
+       u16 lp2_interval;
 };
 
 /* The main device structure */
@@ -320,9 +332,11 @@ struct cyapa {
        u16 runtime_suspend_sleep_time;
        u8 dev_pwr_mode;
        u16 dev_sleep_time;
+       struct gen6_interval_setting gen6_interval_setting;
 
        /* Read from query data region. */
        char product_id[16];
+       u8 platform_ver;  /* Platform version. */
        u8 fw_maj_ver;  /* Firmware major version. */
        u8 fw_min_ver;  /* Firmware minor version. */
        u8 btn_capability;
@@ -411,5 +425,6 @@ extern u8 pip_bl_read_app_info[];
 extern const char product_id[];
 extern const struct cyapa_dev_ops cyapa_gen3_ops;
 extern const struct cyapa_dev_ops cyapa_gen5_ops;
+extern const struct cyapa_dev_ops cyapa_gen6_ops;
 
 #endif
index 9d75c6f9f307ded02dec20f4fb6ce4a8c9ff8f6a..4e19dce1dd62ddff399f2cf74c83e3aef19a8394 100644 (file)
@@ -133,7 +133,9 @@ struct cyapa_pip_touch_record {
         * Bit 7 - 3: reserved
         * Bit 2 - 0: touch type;
         *            0 : standard finger;
-        *            1 - 15 : reserved.
+        *            1 : proximity (Start supported in Gen5 TP).
+        *            2 : finger hover (defined, but not used yet.)
+        *            3 - 15 : reserved.
         */
        u8 touch_type;
 
@@ -167,6 +169,9 @@ struct cyapa_pip_touch_record {
         * The meaning of this value is different when touch_type is different.
         * For standard finger type:
         *      Touch intensity in counts, pressure value.
+        * For proximity type (Start supported in Gen5 TP):
+        *      The distance, in surface units, between the contact and
+        *      the surface.
         **/
        u8 z;
 
@@ -218,6 +223,12 @@ struct cyapa_tsg_bin_image_head {
        u8 fw_major_version;
        u8 fw_minor_version;
        u8 fw_revision_control_number[8];
+       u8 silicon_id_hi;
+       u8 silicon_id_lo;
+       u8 chip_revision;
+       u8 family_id;
+       u8 bl_ver_maj;
+       u8 bl_ver_min;
 } __packed;
 
 struct cyapa_tsg_bin_image_data_record {
@@ -1134,6 +1145,39 @@ int cyapa_pip_bl_enter(struct cyapa *cyapa)
        cyapa->operational = false;
        if (cyapa->gen == CYAPA_GEN5)
                cyapa->state = CYAPA_STATE_GEN5_BL;
+       else if (cyapa->gen == CYAPA_GEN6)
+               cyapa->state = CYAPA_STATE_GEN6_BL;
+       return 0;
+}
+
+static int cyapa_pip_fw_head_check(struct cyapa *cyapa,
+               struct cyapa_tsg_bin_image_head *image_head)
+{
+       if (image_head->head_size != 0x0C && image_head->head_size != 0x12)
+               return -EINVAL;
+
+       switch (cyapa->gen) {
+       case CYAPA_GEN6:
+               if (image_head->family_id != 0x9B ||
+                   image_head->silicon_id_hi != 0x0B)
+                       return -EINVAL;
+               break;
+       case CYAPA_GEN5:
+               /* Gen5 without proximity support. */
+               if (cyapa->platform_ver < 2) {
+                       if (image_head->head_size == 0x0C)
+                               break;
+                       return -EINVAL;
+               }
+
+               if (image_head->family_id != 0x91 ||
+                   image_head->silicon_id_hi != 0x02)
+                       return -EINVAL;
+               break;
+       default:
+               return -EINVAL;
+       }
+
        return 0;
 }
 
@@ -1150,6 +1194,14 @@ int cyapa_pip_check_fw(struct cyapa *cyapa, const struct firmware *fw)
        u16 app_integrity_crc;
        int i;
 
+       /* Verify the firmware image not miss-used for Gen5 and Gen6. */
+       if (cyapa_pip_fw_head_check(cyapa,
+               (struct cyapa_tsg_bin_image_head *)fw->data)) {
+               dev_err(dev, "%s: firmware image not match TP device.\n",
+                            __func__);
+               return -EINVAL;
+       }
+
        image_records =
                cyapa_get_image_record_data_num(fw, &flash_records_count);
 
@@ -2339,6 +2391,9 @@ static int cyapa_gen5_bl_query_data(struct cyapa *cyapa)
        cyapa->fw_maj_ver = resp_data[22];
        cyapa->fw_min_ver = resp_data[23];
 
+       cyapa->platform_ver = (resp_data[26] >> PIP_BL_PLATFORM_VER_SHIFT) &
+                             PIP_BL_PLATFORM_VER_MASK;
+
        return 0;
 }
 
@@ -2362,8 +2417,16 @@ static int cyapa_gen5_get_query_data(struct cyapa *cyapa)
                PIP_PRODUCT_FAMILY_TRACKPAD)
                return -EINVAL;
 
-       cyapa->fw_maj_ver = resp_data[15];
-       cyapa->fw_min_ver = resp_data[16];
+       cyapa->platform_ver = (resp_data[49] >> PIP_BL_PLATFORM_VER_SHIFT) &
+                             PIP_BL_PLATFORM_VER_MASK;
+       if (cyapa->gen == CYAPA_GEN5 && cyapa->platform_ver < 2) {
+               /* Gen5 firmware that does not support proximity. */
+               cyapa->fw_maj_ver = resp_data[15];
+               cyapa->fw_min_ver = resp_data[16];
+       } else {
+               cyapa->fw_maj_ver = resp_data[9];
+               cyapa->fw_min_ver = resp_data[10];
+       }
 
        cyapa->electrodes_x = resp_data[52];
        cyapa->electrodes_y = resp_data[53];
diff --git a/drivers/input/mouse/cyapa_gen6.c b/drivers/input/mouse/cyapa_gen6.c
new file mode 100644 (file)
index 0000000..8a6aa73
--- /dev/null
@@ -0,0 +1,730 @@
+/*
+ * Cypress APA trackpad with I2C interface
+ *
+ * Author: Dudley Du <dudl@cypress.com>
+ *
+ * Copyright (C) 2015 Cypress Semiconductor, Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/mutex.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+#include <linux/crc-itu-t.h>
+#include "cyapa.h"
+
+
+#define GEN6_ENABLE_CMD_IRQ    0x41
+#define GEN6_DISABLE_CMD_IRQ   0x42
+#define GEN6_ENABLE_DEV_IRQ    0x43
+#define GEN6_DISABLE_DEV_IRQ   0x44
+
+#define GEN6_POWER_MODE_ACTIVE         0x01
+#define GEN6_POWER_MODE_LP_MODE1       0x02
+#define GEN6_POWER_MODE_LP_MODE2       0x03
+#define GEN6_POWER_MODE_BTN_ONLY       0x04
+
+#define GEN6_SET_POWER_MODE_INTERVAL   0x47
+#define GEN6_GET_POWER_MODE_INTERVAL   0x48
+
+#define GEN6_MAX_RX_NUM 14
+#define GEN6_RETRIEVE_DATA_ID_RX_ATTENURATOR_IDAC      0x00
+#define GEN6_RETRIEVE_DATA_ID_ATTENURATOR_TRIM         0x12
+
+
+struct pip_app_cmd_head {
+       __le16 addr;
+       __le16 length;
+       u8 report_id;
+       u8 resv;  /* Reserved, must be 0 */
+       u8 cmd_code;  /* bit7: resv, set to 0; bit6~0: command code.*/
+} __packed;
+
+struct pip_app_resp_head {
+       __le16 length;
+       u8 report_id;
+       u8 resv;  /* Reserved, must be 0 */
+       u8 cmd_code;  /* bit7: TGL; bit6~0: command code.*/
+       /*
+        * The value of data_status can be the first byte of data or
+        * the command status or the unsupported command code depending on the
+        * requested command code.
+       */
+       u8 data_status;
+} __packed;
+
+struct pip_fixed_info {
+       u8 silicon_id_high;
+       u8 silicon_id_low;
+       u8 family_id;
+};
+
+static u8 pip_get_bl_info[] = {
+       0x04, 0x00, 0x0B, 0x00, 0x40, 0x00, 0x01, 0x38,
+       0x00, 0x00, 0x70, 0x9E, 0x17
+};
+
+static bool cyapa_sort_pip_hid_descriptor_data(struct cyapa *cyapa,
+               u8 *buf, int len)
+{
+       if (len != PIP_HID_DESCRIPTOR_SIZE)
+               return false;
+
+       if (buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_APP_REPORT_ID ||
+               buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID)
+               return true;
+
+       return false;
+}
+
+static int cyapa_get_pip_fixed_info(struct cyapa *cyapa,
+               struct pip_fixed_info *pip_info, bool is_bootloader)
+{
+       u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH];
+       int resp_len;
+       u16 product_family;
+       int error;
+
+       if (is_bootloader) {
+               /* Read Bootloader Information to determine Gen5 or Gen6. */
+               resp_len = sizeof(resp_data);
+               error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                               pip_get_bl_info, sizeof(pip_get_bl_info),
+                               resp_data, &resp_len,
+                               2000, cyapa_sort_tsg_pip_bl_resp_data,
+                               false);
+               if (error || resp_len < PIP_BL_GET_INFO_RESP_LENGTH)
+                       return error ? error : -EIO;
+
+               pip_info->family_id = resp_data[8];
+               pip_info->silicon_id_low = resp_data[10];
+               pip_info->silicon_id_high = resp_data[11];
+
+               return 0;
+       }
+
+       /* Get App System Information to determine Gen5 or Gen6. */
+       resp_len = sizeof(resp_data);
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH,
+                       resp_data, &resp_len,
+                       2000, cyapa_pip_sort_system_info_data, false);
+       if (error || resp_len < PIP_READ_SYS_INFO_RESP_LENGTH)
+               return error ? error : -EIO;
+
+       product_family = get_unaligned_le16(&resp_data[7]);
+       if ((product_family & PIP_PRODUCT_FAMILY_MASK) !=
+               PIP_PRODUCT_FAMILY_TRACKPAD)
+               return -EINVAL;
+
+       pip_info->family_id = resp_data[19];
+       pip_info->silicon_id_low = resp_data[21];
+       pip_info->silicon_id_high = resp_data[22];
+
+       return 0;
+
+}
+
+int cyapa_pip_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
+{
+       u8 cmd[] = { 0x01, 0x00};
+       struct pip_fixed_info pip_info;
+       u8 resp_data[PIP_HID_DESCRIPTOR_SIZE];
+       int resp_len;
+       bool is_bootloader;
+       int error;
+
+       cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+       /* Try to wake from it deep sleep state if it is. */
+       cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON);
+
+       /* Empty the buffer queue to get fresh data with later commands. */
+       cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+       /*
+        * Read description info from trackpad device to determine running in
+        * APP mode or Bootloader mode.
+        */
+       resp_len = PIP_HID_DESCRIPTOR_SIZE;
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       300,
+                       cyapa_sort_pip_hid_descriptor_data,
+                       false);
+       if (error)
+               return error;
+
+       if (resp_data[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID)
+               is_bootloader = true;
+       else if (resp_data[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_APP_REPORT_ID)
+               is_bootloader = false;
+       else
+               return -EAGAIN;
+
+       /* Get PIP fixed information to determine Gen5 or Gen6. */
+       memset(&pip_info, 0, sizeof(struct pip_fixed_info));
+       error = cyapa_get_pip_fixed_info(cyapa, &pip_info, is_bootloader);
+       if (error)
+               return error;
+
+       if (pip_info.family_id == 0x9B && pip_info.silicon_id_high == 0x0B) {
+               cyapa->gen = CYAPA_GEN6;
+               cyapa->state = is_bootloader ? CYAPA_STATE_GEN6_BL
+                                            : CYAPA_STATE_GEN6_APP;
+       } else if (pip_info.family_id == 0x91 &&
+                  pip_info.silicon_id_high == 0x02) {
+               cyapa->gen = CYAPA_GEN5;
+               cyapa->state = is_bootloader ? CYAPA_STATE_GEN5_BL
+                                            : CYAPA_STATE_GEN5_APP;
+       }
+
+       return 0;
+}
+
+static int cyapa_gen6_read_sys_info(struct cyapa *cyapa)
+{
+       u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH];
+       int resp_len;
+       u16 product_family;
+       u8 rotat_align;
+       int error;
+
+       /* Get App System Information to determine Gen5 or Gen6. */
+       resp_len = sizeof(resp_data);
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH,
+                       resp_data, &resp_len,
+                       2000, cyapa_pip_sort_system_info_data, false);
+       if (error || resp_len < sizeof(resp_data))
+               return error ? error : -EIO;
+
+       product_family = get_unaligned_le16(&resp_data[7]);
+       if ((product_family & PIP_PRODUCT_FAMILY_MASK) !=
+               PIP_PRODUCT_FAMILY_TRACKPAD)
+               return -EINVAL;
+
+       cyapa->platform_ver = (resp_data[67] >> PIP_BL_PLATFORM_VER_SHIFT) &
+                             PIP_BL_PLATFORM_VER_MASK;
+       cyapa->fw_maj_ver = resp_data[9];
+       cyapa->fw_min_ver = resp_data[10];
+
+       cyapa->electrodes_x = resp_data[33];
+       cyapa->electrodes_y = resp_data[34];
+
+       cyapa->physical_size_x =  get_unaligned_le16(&resp_data[35]) / 100;
+       cyapa->physical_size_y = get_unaligned_le16(&resp_data[37]) / 100;
+
+       cyapa->max_abs_x = get_unaligned_le16(&resp_data[39]);
+       cyapa->max_abs_y = get_unaligned_le16(&resp_data[41]);
+
+       cyapa->max_z = get_unaligned_le16(&resp_data[43]);
+
+       cyapa->x_origin = resp_data[45] & 0x01;
+       cyapa->y_origin = resp_data[46] & 0x01;
+
+       cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK;
+
+       memcpy(&cyapa->product_id[0], &resp_data[51], 5);
+       cyapa->product_id[5] = '-';
+       memcpy(&cyapa->product_id[6], &resp_data[56], 6);
+       cyapa->product_id[12] = '-';
+       memcpy(&cyapa->product_id[13], &resp_data[62], 2);
+       cyapa->product_id[15] = '\0';
+
+       rotat_align = resp_data[68];
+       if (rotat_align) {
+               cyapa->electrodes_rx = cyapa->electrodes_y;
+               cyapa->electrodes_rx = cyapa->electrodes_y;
+       } else {
+               cyapa->electrodes_rx = cyapa->electrodes_x;
+               cyapa->electrodes_rx = cyapa->electrodes_y;
+       }
+       cyapa->aligned_electrodes_rx = (cyapa->electrodes_rx + 3) & ~3u;
+
+       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_gen6_bl_read_app_info(struct cyapa *cyapa)
+{
+       u8 resp_data[PIP_BL_APP_INFO_RESP_LENGTH];
+       int resp_len;
+       int error;
+
+       resp_len = sizeof(resp_data);
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       pip_bl_read_app_info, PIP_BL_READ_APP_INFO_CMD_LENGTH,
+                       resp_data, &resp_len,
+                       500, cyapa_sort_tsg_pip_bl_resp_data, false);
+       if (error || resp_len < PIP_BL_APP_INFO_RESP_LENGTH ||
+               !PIP_CMD_COMPLETE_SUCCESS(resp_data))
+               return error ? error : -EIO;
+
+       cyapa->fw_maj_ver = resp_data[8];
+       cyapa->fw_min_ver = resp_data[9];
+
+       cyapa->platform_ver = (resp_data[12] >> PIP_BL_PLATFORM_VER_SHIFT) &
+                             PIP_BL_PLATFORM_VER_MASK;
+
+       memcpy(&cyapa->product_id[0], &resp_data[13], 5);
+       cyapa->product_id[5] = '-';
+       memcpy(&cyapa->product_id[6], &resp_data[18], 6);
+       cyapa->product_id[12] = '-';
+       memcpy(&cyapa->product_id[13], &resp_data[24], 2);
+       cyapa->product_id[15] = '\0';
+
+       return 0;
+
+}
+
+static int cyapa_gen6_config_dev_irq(struct cyapa *cyapa, u8 cmd_code)
+{
+       u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, cmd_code };
+       u8 resp_data[6];
+       int resp_len;
+       int error;
+
+       resp_len = sizeof(resp_data);
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       500, cyapa_sort_tsg_pip_app_resp_data, false);
+       if (error || !VALID_CMD_RESP_HEADER(resp_data, cmd_code) ||
+                       !PIP_CMD_COMPLETE_SUCCESS(resp_data)
+                       )
+               return error < 0 ? error : -EINVAL;
+
+       return 0;
+}
+
+static int cyapa_gen6_change_power_state(struct cyapa *cyapa, u8 power_mode)
+{
+       u8 cmd[] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x46, power_mode };
+       u8 resp_data[6];
+       int resp_len;
+       int error;
+
+       resp_len = sizeof(resp_data);
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       500, cyapa_sort_tsg_pip_app_resp_data, false);
+       if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x46))
+               return error < 0 ? error : -EINVAL;
+
+       /* New power state applied in device not match the set power state. */
+       if (resp_data[5] != power_mode)
+               return -EAGAIN;
+
+       return 0;
+}
+
+static int cyapa_gen6_set_interval_setting(struct cyapa *cyapa,
+               struct gen6_interval_setting *interval_setting)
+{
+       struct gen6_set_interval_cmd {
+               __le16 addr;
+               __le16 length;
+               u8 report_id;
+               u8 rsvd;  /* Reserved, must be 0 */
+               u8 cmd_code;
+               __le16 active_interval;
+               __le16 lp1_interval;
+               __le16 lp2_interval;
+       } __packed set_interval_cmd;
+       u8 resp_data[11];
+       int resp_len;
+       int error;
+
+       memset(&set_interval_cmd, 0, sizeof(set_interval_cmd));
+       put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &set_interval_cmd.addr);
+       put_unaligned_le16(sizeof(set_interval_cmd) - 2,
+                          &set_interval_cmd.length);
+       set_interval_cmd.report_id = PIP_APP_CMD_REPORT_ID;
+       set_interval_cmd.cmd_code = GEN6_SET_POWER_MODE_INTERVAL;
+       put_unaligned_le16(interval_setting->active_interval,
+                          &set_interval_cmd.active_interval);
+       put_unaligned_le16(interval_setting->lp1_interval,
+                          &set_interval_cmd.lp1_interval);
+       put_unaligned_le16(interval_setting->lp2_interval,
+                          &set_interval_cmd.lp2_interval);
+
+       resp_len = sizeof(resp_data);
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                       (u8 *)&set_interval_cmd, sizeof(set_interval_cmd),
+                       resp_data, &resp_len,
+                       500, cyapa_sort_tsg_pip_app_resp_data, false);
+       if (error ||
+               !VALID_CMD_RESP_HEADER(resp_data, GEN6_SET_POWER_MODE_INTERVAL))
+               return error < 0 ? error : -EINVAL;
+
+       /* Get the real set intervals from response. */
+       interval_setting->active_interval = get_unaligned_le16(&resp_data[5]);
+       interval_setting->lp1_interval = get_unaligned_le16(&resp_data[7]);
+       interval_setting->lp2_interval = get_unaligned_le16(&resp_data[9]);
+
+       return 0;
+}
+
+static int cyapa_gen6_get_interval_setting(struct cyapa *cyapa,
+               struct gen6_interval_setting *interval_setting)
+{
+       u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00,
+                    GEN6_GET_POWER_MODE_INTERVAL };
+       u8 resp_data[11];
+       int resp_len;
+       int error;
+
+       resp_len = sizeof(resp_data);
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+                       resp_data, &resp_len,
+                       500, cyapa_sort_tsg_pip_app_resp_data, false);
+       if (error ||
+               !VALID_CMD_RESP_HEADER(resp_data, GEN6_GET_POWER_MODE_INTERVAL))
+               return error < 0 ? error : -EINVAL;
+
+       interval_setting->active_interval = get_unaligned_le16(&resp_data[5]);
+       interval_setting->lp1_interval = get_unaligned_le16(&resp_data[7]);
+       interval_setting->lp2_interval = get_unaligned_le16(&resp_data[9]);
+
+       return 0;
+}
+
+static int cyapa_gen6_deep_sleep(struct cyapa *cyapa, u8 state)
+{
+       u8 ping[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x00 };
+
+       if (state == PIP_DEEP_SLEEP_STATE_ON)
+               /*
+                * Send ping command to notify device prepare for wake up
+                * when it's in deep sleep mode. At this time, device will
+                * response nothing except an I2C NAK.
+                */
+               cyapa_i2c_pip_write(cyapa, ping, sizeof(ping));
+
+       return cyapa_pip_deep_sleep(cyapa, state);
+}
+
+static int cyapa_gen6_set_power_mode(struct cyapa *cyapa,
+               u8 power_mode, u16 sleep_time)
+{
+       struct device *dev = &cyapa->client->dev;
+       struct gen6_interval_setting *interval_setting =
+                       &cyapa->gen6_interval_setting;
+       u8 lp_mode;
+       int error;
+
+       if (cyapa->state != CYAPA_STATE_GEN6_APP)
+               return 0;
+
+       if (PIP_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) {
+               /*
+                * Assume TP in deep sleep mode when driver is loaded,
+                * avoid driver unload and reload command IO issue caused by TP
+                * has been set into deep sleep mode when unloading.
+                */
+               PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
+       }
+
+       if (PIP_DEV_UNINIT_SLEEP_TIME(cyapa) &&
+               PIP_DEV_GET_PWR_STATE(cyapa) != PWR_MODE_OFF)
+               PIP_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME);
+
+       if (PIP_DEV_GET_PWR_STATE(cyapa) == power_mode) {
+               if (power_mode == PWR_MODE_OFF ||
+                       power_mode == PWR_MODE_FULL_ACTIVE ||
+                       power_mode == PWR_MODE_BTN_ONLY ||
+                       PIP_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) {
+                       /* Has in correct power mode state, early return. */
+                       return 0;
+               }
+       }
+
+       if (power_mode == PWR_MODE_OFF) {
+               cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ);
+
+               error = cyapa_gen6_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_OFF);
+               if (error) {
+                       dev_err(dev, "enter deep sleep fail: %d\n", error);
+                       return error;
+               }
+
+               PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
+               return 0;
+       }
+
+       /*
+        * When trackpad in power off mode, it cannot change to other power
+        * state directly, must be wake up from sleep firstly, then
+        * continue to do next power sate change.
+        */
+       if (PIP_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) {
+               error = cyapa_gen6_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON);
+               if (error) {
+                       dev_err(dev, "deep sleep wake fail: %d\n", error);
+                       return error;
+               }
+       }
+
+       /*
+        * Disable device assert interrupts for command response to avoid
+        * disturbing system suspending or hibernating process.
+        */
+       cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ);
+
+       if (power_mode == PWR_MODE_FULL_ACTIVE) {
+               error = cyapa_gen6_change_power_state(cyapa,
+                               GEN6_POWER_MODE_ACTIVE);
+               if (error) {
+                       dev_err(dev, "change to active fail: %d\n", error);
+                       goto out;
+               }
+
+               PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE);
+
+               /* Sync the interval setting from device. */
+               cyapa_gen6_get_interval_setting(cyapa, interval_setting);
+
+       } else if (power_mode == PWR_MODE_BTN_ONLY) {
+               error = cyapa_gen6_change_power_state(cyapa,
+                               GEN6_POWER_MODE_BTN_ONLY);
+               if (error) {
+                       dev_err(dev, "fail to button only mode: %d\n", error);
+                       goto out;
+               }
+
+               PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY);
+       } else {
+               /*
+                * Gen6 internally supports to 2 low power scan interval time,
+                * so can help to switch power mode quickly.
+                * such as runtime suspend and system suspend.
+                */
+               if (interval_setting->lp1_interval == sleep_time) {
+                       lp_mode = GEN6_POWER_MODE_LP_MODE1;
+               } else if (interval_setting->lp2_interval == sleep_time) {
+                       lp_mode = GEN6_POWER_MODE_LP_MODE2;
+               } else {
+                       if (interval_setting->lp1_interval == 0) {
+                               interval_setting->lp1_interval = sleep_time;
+                               lp_mode = GEN6_POWER_MODE_LP_MODE1;
+                       } else {
+                               interval_setting->lp2_interval = sleep_time;
+                               lp_mode = GEN6_POWER_MODE_LP_MODE2;
+                       }
+                       cyapa_gen6_set_interval_setting(cyapa,
+                                                       interval_setting);
+               }
+
+               error = cyapa_gen6_change_power_state(cyapa, lp_mode);
+               if (error) {
+                       dev_err(dev, "set power state to 0x%02x failed: %d\n",
+                               lp_mode, error);
+                       goto out;
+               }
+
+               PIP_DEV_SET_SLEEP_TIME(cyapa, sleep_time);
+               PIP_DEV_SET_PWR_STATE(cyapa,
+                       cyapa_sleep_time_to_pwr_cmd(sleep_time));
+       }
+
+out:
+       cyapa_gen6_config_dev_irq(cyapa, GEN6_ENABLE_CMD_IRQ);
+       return error;
+}
+
+static int cyapa_gen6_initialize(struct cyapa *cyapa)
+{
+       return 0;
+}
+
+static int cyapa_pip_retrieve_data_structure(struct cyapa *cyapa,
+               u16 read_offset, u16 read_len, u8 data_id,
+               u8 *data, int *data_buf_lens)
+{
+       struct retrieve_data_struct_cmd {
+               struct pip_app_cmd_head head;
+               __le16 read_offset;
+               __le16 read_length;
+               u8 data_id;
+       } __packed cmd;
+       u8 resp_data[GEN6_MAX_RX_NUM + 10];
+       int resp_len;
+       int error;
+
+       memset(&cmd, 0, sizeof(cmd));
+       put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &cmd.head.addr);
+       put_unaligned_le16(sizeof(cmd), &cmd.head.length - 2);
+       cmd.head.report_id = PIP_APP_CMD_REPORT_ID;
+       cmd.head.cmd_code = PIP_RETRIEVE_DATA_STRUCTURE;
+       put_unaligned_le16(read_offset, &cmd.read_offset);
+       put_unaligned_le16(read_len, &cmd.read_length);
+       cmd.data_id = data_id;
+
+       resp_len = sizeof(resp_data);
+       error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+                               (u8 *)&cmd, sizeof(cmd),
+                               resp_data, &resp_len,
+                               500, cyapa_sort_tsg_pip_app_resp_data,
+                               true);
+       if (error || !PIP_CMD_COMPLETE_SUCCESS(resp_data) ||
+               resp_data[6] != data_id ||
+               !VALID_CMD_RESP_HEADER(resp_data, PIP_RETRIEVE_DATA_STRUCTURE))
+               return (error < 0) ? error : -EAGAIN;
+
+       read_len = get_unaligned_le16(&resp_data[7]);
+       if (*data_buf_lens < read_len) {
+               *data_buf_lens = read_len;
+               return -ENOBUFS;
+       }
+
+       memcpy(data, &resp_data[10], read_len);
+       *data_buf_lens = read_len;
+       return 0;
+}
+
+static ssize_t cyapa_gen6_show_baseline(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct cyapa *cyapa = dev_get_drvdata(dev);
+       u8 data[GEN6_MAX_RX_NUM];
+       int data_len;
+       int size = 0;
+       int i;
+       int error;
+       int resume_error;
+
+       if (!cyapa_is_pip_app_mode(cyapa))
+               return -EBUSY;
+
+       /* 1. Suspend Scanning*/
+       error = cyapa_pip_suspend_scanning(cyapa);
+       if (error)
+               return error;
+
+       /* 2. IDAC and RX Attenuator Calibration Data (Center Frequency). */
+       data_len = sizeof(data);
+       error = cyapa_pip_retrieve_data_structure(cyapa, 0, data_len,
+                       GEN6_RETRIEVE_DATA_ID_RX_ATTENURATOR_IDAC,
+                       data, &data_len);
+       if (error)
+               goto resume_scanning;
+
+       size = scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d ",
+                       data[0],  /* RX Attenuator Mutual */
+                       data[1],  /* IDAC Mutual */
+                       data[2],  /* RX Attenuator Self RX */
+                       data[3],  /* IDAC Self RX */
+                       data[4],  /* RX Attenuator Self TX */
+                       data[5]   /* IDAC Self TX */
+                       );
+
+       /* 3. Read Attenuator Trim. */
+       data_len = sizeof(data);
+       error = cyapa_pip_retrieve_data_structure(cyapa, 0, data_len,
+                       GEN6_RETRIEVE_DATA_ID_ATTENURATOR_TRIM,
+                       data, &data_len);
+       if (error)
+               goto resume_scanning;
+
+       /* set attenuator trim values. */
+       for (i = 0; i < data_len; i++)
+               size += scnprintf(buf + size, PAGE_SIZE - size, "%d ", data[i]);
+       size += scnprintf(buf + size, PAGE_SIZE - size, "\n");
+
+resume_scanning:
+       /* 4. Resume Scanning*/
+       resume_error = cyapa_pip_resume_scanning(cyapa);
+       if (resume_error || error) {
+               memset(buf, 0, PAGE_SIZE);
+               return resume_error ? resume_error : error;
+       }
+
+       return size;
+}
+
+static int cyapa_gen6_operational_check(struct cyapa *cyapa)
+{
+       struct device *dev = &cyapa->client->dev;
+       int error;
+
+       if (cyapa->gen != CYAPA_GEN6)
+               return -ENODEV;
+
+       switch (cyapa->state) {
+       case CYAPA_STATE_GEN6_BL:
+               error = cyapa_pip_bl_exit(cyapa);
+               if (error) {
+                       /* Try to update trackpad product information. */
+                       cyapa_gen6_bl_read_app_info(cyapa);
+                       goto out;
+               }
+
+               cyapa->state = CYAPA_STATE_GEN6_APP;
+
+       case CYAPA_STATE_GEN6_APP:
+               /*
+                * If trackpad device in deep sleep mode,
+                * the app command will fail.
+                * So always try to reset trackpad device to full active when
+                * the device state is required.
+                */
+               error = cyapa_gen6_set_power_mode(cyapa,
+                               PWR_MODE_FULL_ACTIVE, 0);
+               if (error)
+                       dev_warn(dev, "%s: failed to set power active mode.\n",
+                               __func__);
+
+               /* Get trackpad product information. */
+               error = cyapa_gen6_read_sys_info(cyapa);
+               if (error)
+                       goto out;
+               /* Only support product ID starting with CYTRA */
+               if (memcmp(cyapa->product_id, product_id,
+                               strlen(product_id)) != 0) {
+                       dev_err(dev, "%s: unknown product ID (%s)\n",
+                               __func__, cyapa->product_id);
+                       error = -EINVAL;
+               }
+               break;
+       default:
+               error = -EINVAL;
+       }
+
+out:
+       return error;
+}
+
+const struct cyapa_dev_ops cyapa_gen6_ops = {
+       .check_fw = cyapa_pip_check_fw,
+       .bl_enter = cyapa_pip_bl_enter,
+       .bl_initiate = cyapa_pip_bl_initiate,
+       .update_fw = cyapa_pip_do_fw_update,
+       .bl_activate = cyapa_pip_bl_activate,
+       .bl_deactivate = cyapa_pip_bl_deactivate,
+
+       .show_baseline = cyapa_gen6_show_baseline,
+       .calibrate_store = cyapa_pip_do_calibrate,
+
+       .initialize = cyapa_gen6_initialize,
+
+       .state_parse = cyapa_pip_state_parse,
+       .operational_check = cyapa_gen6_operational_check,
+
+       .irq_handler = cyapa_pip_irq_handler,
+       .irq_cmd_handler = cyapa_pip_irq_cmd_handler,
+       .sort_empty_output_data = cyapa_empty_pip_output_data,
+       .set_power_mode = cyapa_gen6_set_power_mode,
+};