forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
746 lines
20 KiB
746 lines
20 KiB
/* |
|
* Cypress APA trackpad with I2C interface |
|
* |
|
* Author: Dudley Du <[email protected]> |
|
* |
|
* 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'; |
|
|
|
/* Get the number of Rx electrodes. */ |
|
rotat_align = resp_data[68]; |
|
cyapa->electrodes_rx = |
|
rotat_align ? cyapa->electrodes_y : cyapa->electrodes_x; |
|
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_set_proximity(struct cyapa *cyapa, bool enable) |
|
{ |
|
int error; |
|
|
|
cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); |
|
error = cyapa_pip_set_proximity(cyapa, enable); |
|
cyapa_gen6_config_dev_irq(cyapa, GEN6_ENABLE_CMD_IRQ); |
|
|
|
return error; |
|
} |
|
|
|
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, enum cyapa_pm_stage pm_stage) |
|
{ |
|
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) - 2, &cmd.head.length); |
|
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; |
|
fallthrough; |
|
|
|
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, CYAPA_PM_ACTIVE); |
|
if (error) |
|
dev_warn(dev, "%s: failed to set power active mode.\n", |
|
__func__); |
|
|
|
/* By default, the trackpad proximity function is enabled. */ |
|
error = cyapa_pip_set_proximity(cyapa, true); |
|
if (error) |
|
dev_warn(dev, "%s: failed to enable proximity.\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, |
|
|
|
.set_proximity = cyapa_gen6_set_proximity, |
|
};
|
|
|