summaryrefslogtreecommitdiff
path: root/drivers/input
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input')
-rw-r--r--drivers/input/mouse/Makefile2
-rw-r--r--drivers/input/mouse/cyapa.c22
-rw-r--r--drivers/input/mouse/cyapa.h15
-rw-r--r--drivers/input/mouse/cyapa_gen5.c69
-rw-r--r--drivers/input/mouse/cyapa_gen6.c730
5 files changed, 834 insertions, 4 deletions
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index 793300bfbddd..ee6a6e9563d4 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -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
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 4c4429598adb..6952ed1c92c7 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -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";
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index d019d1d2d1e4..3a211c0769e9 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -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)"
@@ -198,6 +199,9 @@
#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
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
index 9d75c6f9f307..4e19dce1dd62 100644
--- a/drivers/input/mouse/cyapa_gen5.c
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -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
index 000000000000..8a6aa73b5373
--- /dev/null
+++ b/drivers/input/mouse/cyapa_gen6.c
@@ -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,
+};