Input: cyapa - fix for losing events during device power transitions

When changing the scan rate as part of runtime-resume process we may lose
some of the events, because:

1) for gen3 trackpads, the driver must msleep() some time to ensure that
the device is ready to accept next command;

2) for gen5 and later trackpads, the queue dumping function will simply
ignore the events when waiting for the set power mode command response.

The solution is to keep polling and report those valid events when the set
power mode command is in progress.

Signed-off-by: Dudley Du <dudl@cypress.com>
Tested-by: Jeremiah Mahler <jmmahler@gmail.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
This commit is contained in:
Dudley Du 2016-03-04 11:23:09 -08:00 committed by Dmitry Torokhov
parent 5186b8c41a
commit 3cd4786943
5 changed files with 188 additions and 59 deletions

View File

@ -383,7 +383,7 @@ static int cyapa_open(struct input_dev *input)
* when in operational mode.
*/
error = cyapa->ops->set_power_mode(cyapa,
PWR_MODE_FULL_ACTIVE, 0, false);
PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE);
if (error) {
dev_warn(dev, "set active power failed: %d\n", error);
goto out;
@ -424,7 +424,8 @@ static void cyapa_close(struct input_dev *input)
pm_runtime_set_suspended(dev);
if (cyapa->operational)
cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0, false);
cyapa->ops->set_power_mode(cyapa,
PWR_MODE_OFF, 0, CYAPA_PM_DEACTIVE);
mutex_unlock(&cyapa->state_sync_lock);
}
@ -534,7 +535,7 @@ static void cyapa_enable_irq_for_cmd(struct cyapa *cyapa)
*/
if (!input || cyapa->operational)
cyapa->ops->set_power_mode(cyapa,
PWR_MODE_FULL_ACTIVE, 0, false);
PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE);
/* Gen3 always using polling mode for command. */
if (cyapa->gen >= CYAPA_GEN5)
enable_irq(cyapa->client->irq);
@ -550,7 +551,7 @@ static void cyapa_disable_irq_for_cmd(struct cyapa *cyapa)
disable_irq(cyapa->client->irq);
if (!input || cyapa->operational)
cyapa->ops->set_power_mode(cyapa,
PWR_MODE_OFF, 0, false);
PWR_MODE_OFF, 0, CYAPA_PM_ACTIVE);
}
}
@ -617,7 +618,8 @@ static int cyapa_initialize(struct cyapa *cyapa)
/* Power down the device until we need it. */
if (cyapa->operational)
cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0, false);
cyapa->ops->set_power_mode(cyapa,
PWR_MODE_OFF, 0, CYAPA_PM_ACTIVE);
return 0;
}
@ -634,7 +636,7 @@ static int cyapa_reinitialize(struct cyapa *cyapa)
/* Avoid command failures when TP was in OFF state. */
if (cyapa->operational)
cyapa->ops->set_power_mode(cyapa,
PWR_MODE_FULL_ACTIVE, 0, false);
PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE);
error = cyapa_detect(cyapa);
if (error)
@ -654,7 +656,7 @@ static int cyapa_reinitialize(struct cyapa *cyapa)
/* Reset to power OFF state to save power when no user open. */
if (cyapa->operational)
cyapa->ops->set_power_mode(cyapa,
PWR_MODE_OFF, 0, false);
PWR_MODE_OFF, 0, CYAPA_PM_DEACTIVE);
} else if (!error && cyapa->operational) {
/*
* Make sure only enable runtime PM when device is
@ -1392,7 +1394,7 @@ static int __maybe_unused cyapa_suspend(struct device *dev)
power_mode = device_may_wakeup(dev) ? cyapa->suspend_power_mode
: PWR_MODE_OFF;
error = cyapa->ops->set_power_mode(cyapa, power_mode,
cyapa->suspend_sleep_time, true);
cyapa->suspend_sleep_time, CYAPA_PM_SUSPEND);
if (error)
dev_err(dev, "suspend set power mode failed: %d\n",
error);
@ -1447,7 +1449,7 @@ static int __maybe_unused cyapa_runtime_suspend(struct device *dev)
error = cyapa->ops->set_power_mode(cyapa,
cyapa->runtime_suspend_power_mode,
cyapa->runtime_suspend_sleep_time,
false);
CYAPA_PM_RUNTIME_SUSPEND);
if (error)
dev_warn(dev, "runtime suspend failed: %d\n", error);
@ -1460,7 +1462,7 @@ static int __maybe_unused cyapa_runtime_resume(struct device *dev)
int error;
error = cyapa->ops->set_power_mode(cyapa,
PWR_MODE_FULL_ACTIVE, 0, false);
PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_RUNTIME_RESUME);
if (error)
dev_warn(dev, "runtime resume failed: %d\n", error);

View File

@ -250,6 +250,15 @@ struct cyapa;
typedef bool (*cb_sort)(struct cyapa *, u8 *, int);
enum cyapa_pm_stage {
CYAPA_PM_DEACTIVE,
CYAPA_PM_ACTIVE,
CYAPA_PM_SUSPEND,
CYAPA_PM_RESUME,
CYAPA_PM_RUNTIME_SUSPEND,
CYAPA_PM_RUNTIME_RESUME,
};
struct cyapa_dev_ops {
int (*check_fw)(struct cyapa *, const struct firmware *);
int (*bl_enter)(struct cyapa *);
@ -273,7 +282,7 @@ struct cyapa_dev_ops {
int (*sort_empty_output_data)(struct cyapa *,
u8 *, int *, cb_sort);
int (*set_power_mode)(struct cyapa *, u8, u16, bool);
int (*set_power_mode)(struct cyapa *, u8, u16, enum cyapa_pm_stage);
int (*set_proximity)(struct cyapa *, bool);
};
@ -289,6 +298,9 @@ struct cyapa_pip_cmd_states {
u8 *resp_data;
int *resp_len;
enum cyapa_pm_stage pm_stage;
struct mutex pm_stage_lock;
u8 irq_cmd_buf[CYAPA_REG_MAP_SIZE];
u8 empty_buf[CYAPA_REG_MAP_SIZE];
};

View File

@ -269,6 +269,7 @@ static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
{ CYAPA_SMBUS_MIN_BASELINE, 1 }, /* CYAPA_CMD_MIN_BASELINE */
};
static int cyapa_gen3_try_poll_handler(struct cyapa *cyapa);
/*
* cyapa_smbus_read_block - perform smbus block read command
@ -950,12 +951,14 @@ static u16 cyapa_get_wait_time_for_pwr_cmd(u8 pwr_mode)
* Device power mode can only be set when device is in operational mode.
*/
static int cyapa_gen3_set_power_mode(struct cyapa *cyapa, u8 power_mode,
u16 always_unused, bool is_suspend_unused)
u16 always_unused, enum cyapa_pm_stage pm_stage)
{
int ret;
struct input_dev *input = cyapa->input;
u8 power;
int tries;
u16 sleep_time;
int sleep_time;
int interval;
int ret;
if (cyapa->state != CYAPA_STATE_OP)
return 0;
@ -977,7 +980,7 @@ static int cyapa_gen3_set_power_mode(struct cyapa *cyapa, u8 power_mode,
if ((ret & PWR_MODE_MASK) == power_mode)
return 0;
sleep_time = cyapa_get_wait_time_for_pwr_cmd(ret & PWR_MODE_MASK);
sleep_time = (int)cyapa_get_wait_time_for_pwr_cmd(ret & PWR_MODE_MASK);
power = ret;
power &= ~PWR_MODE_MASK;
power |= power_mode & PWR_MODE_MASK;
@ -995,7 +998,23 @@ static int cyapa_gen3_set_power_mode(struct cyapa *cyapa, u8 power_mode,
* doing so before issuing the next command may result in errors
* depending on the command's content.
*/
msleep(sleep_time);
if (cyapa->operational && input && input->users &&
(pm_stage == CYAPA_PM_RUNTIME_SUSPEND ||
pm_stage == CYAPA_PM_RUNTIME_RESUME)) {
/* Try to polling in 120Hz, read may fail, just ignore it. */
interval = 1000 / 120;
while (sleep_time > 0) {
if (sleep_time > interval)
msleep(interval);
else
msleep(sleep_time);
sleep_time -= interval;
cyapa_gen3_try_poll_handler(cyapa);
}
} else {
msleep(sleep_time);
}
return ret;
}
@ -1112,7 +1131,7 @@ static int cyapa_gen3_do_operational_check(struct cyapa *cyapa)
* may cause problems, so we set the power mode first here.
*/
error = cyapa_gen3_set_power_mode(cyapa,
PWR_MODE_FULL_ACTIVE, 0, false);
PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE);
if (error)
dev_err(dev, "%s: set full power mode failed: %d\n",
__func__, error);
@ -1168,32 +1187,16 @@ static bool cyapa_gen3_irq_cmd_handler(struct cyapa *cyapa)
return false;
}
static int cyapa_gen3_irq_handler(struct cyapa *cyapa)
static int cyapa_gen3_event_process(struct cyapa *cyapa,
struct cyapa_reg_data *data)
{
struct input_dev *input = cyapa->input;
struct device *dev = &cyapa->client->dev;
struct cyapa_reg_data data;
int num_fingers;
int ret;
int i;
ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data);
if (ret != sizeof(data)) {
dev_err(dev, "failed to read report data, (%d)\n", ret);
return -EINVAL;
}
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) {
dev_err(dev, "invalid device state bytes, %02x %02x\n",
data.device_status, data.finger_btn);
return -EINVAL;
}
num_fingers = (data.finger_btn >> 4) & 0x0f;
num_fingers = (data->finger_btn >> 4) & 0x0f;
for (i = 0; i < num_fingers; i++) {
const struct cyapa_touch *touch = &data.touches[i];
const struct cyapa_touch *touch = &data->touches[i];
/* Note: touch->id range is 1 to 15; slots are 0 to 14. */
int slot = touch->id - 1;
@ -1210,18 +1213,65 @@ static int cyapa_gen3_irq_handler(struct cyapa *cyapa)
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);
return 0;
}
static int cyapa_gen3_irq_handler(struct cyapa *cyapa)
{
struct device *dev = &cyapa->client->dev;
struct cyapa_reg_data data;
int ret;
ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data);
if (ret != sizeof(data)) {
dev_err(dev, "failed to read report data, (%d)\n", ret);
return -EINVAL;
}
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) {
dev_err(dev, "invalid device state bytes: %02x %02x\n",
data.device_status, data.finger_btn);
return -EINVAL;
}
return cyapa_gen3_event_process(cyapa, &data);
}
/*
* This function will be called in the cyapa_gen3_set_power_mode function,
* and it's known that it may failed in some situation after the set power
* mode command was sent. So this function is aimed to avoid the knwon
* and unwanted output I2C and data parse error messages.
*/
static int cyapa_gen3_try_poll_handler(struct cyapa *cyapa)
{
struct cyapa_reg_data data;
int ret;
ret = cyapa_read_block(cyapa, CYAPA_CMD_GROUP_DATA, (u8 *)&data);
if (ret != sizeof(data))
return -EINVAL;
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)
return -EINVAL;
return cyapa_gen3_event_process(cyapa, &data);
}
static int cyapa_gen3_initialize(struct cyapa *cyapa) { return 0; }
static int cyapa_gen3_bl_initiate(struct cyapa *cyapa,
const struct firmware *fw) { return 0; }

View File

@ -342,6 +342,9 @@ u8 pip_bl_read_app_info[] = { 0x04, 0x00, 0x0b, 0x00, 0x40, 0x00,
static u8 cyapa_pip_bl_cmd_key[] = { 0xa5, 0x01, 0x02, 0x03,
0xff, 0xfe, 0xfd, 0x5a };
static int cyapa_pip_event_process(struct cyapa *cyapa,
struct cyapa_pip_report_data *report_data);
int cyapa_pip_cmd_state_initialize(struct cyapa *cyapa)
{
struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip;
@ -350,6 +353,9 @@ int cyapa_pip_cmd_state_initialize(struct cyapa *cyapa)
atomic_set(&pip->cmd_issued, 0);
mutex_init(&pip->cmd_lock);
mutex_init(&pip->pm_stage_lock);
pip->pm_stage = CYAPA_PM_DEACTIVE;
pip->resp_sort_func = NULL;
pip->in_progress_cmd = PIP_INVALID_CMD;
pip->resp_data = NULL;
@ -397,6 +403,38 @@ ssize_t cyapa_i2c_pip_write(struct cyapa *cyapa, u8 *buf, size_t size)
return 0;
}
static void cyapa_set_pip_pm_state(struct cyapa *cyapa,
enum cyapa_pm_stage pm_stage)
{
struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip;
mutex_lock(&pip->pm_stage_lock);
pip->pm_stage = pm_stage;
mutex_unlock(&pip->pm_stage_lock);
}
static void cyapa_reset_pip_pm_state(struct cyapa *cyapa)
{
struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip;
/* Indicates the pip->pm_stage is not valid. */
mutex_lock(&pip->pm_stage_lock);
pip->pm_stage = CYAPA_PM_DEACTIVE;
mutex_unlock(&pip->pm_stage_lock);
}
static enum cyapa_pm_stage cyapa_get_pip_pm_state(struct cyapa *cyapa)
{
struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip;
enum cyapa_pm_stage pm_stage;
mutex_lock(&pip->pm_stage_lock);
pm_stage = pip->pm_stage;
mutex_unlock(&pip->pm_stage_lock);
return pm_stage;
}
/**
* This function is aimed to dump all not read data in Gen5 trackpad
* before send any command, otherwise, the interrupt line will be blocked.
@ -404,7 +442,9 @@ ssize_t cyapa_i2c_pip_write(struct cyapa *cyapa, u8 *buf, size_t size)
int cyapa_empty_pip_output_data(struct cyapa *cyapa,
u8 *buf, int *len, cb_sort func)
{
struct input_dev *input = cyapa->input;
struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip;
enum cyapa_pm_stage pm_stage = cyapa_get_pip_pm_state(cyapa);
int length;
int report_count;
int empty_count;
@ -478,6 +518,12 @@ int cyapa_empty_pip_output_data(struct cyapa *cyapa,
*len = length;
/* Response found, success. */
return 0;
} else if (cyapa->operational && input && input->users &&
(pm_stage == CYAPA_PM_RUNTIME_RESUME ||
pm_stage == CYAPA_PM_RUNTIME_SUSPEND)) {
/* Parse the data and report it if it's valid. */
cyapa_pip_event_process(cyapa,
(struct cyapa_pip_report_data *)pip->empty_buf);
}
error = -EINVAL;
@ -1566,15 +1612,17 @@ int cyapa_pip_deep_sleep(struct cyapa *cyapa, u8 state)
}
static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
u8 power_mode, u16 sleep_time, bool is_suspend)
u8 power_mode, u16 sleep_time, enum cyapa_pm_stage pm_stage)
{
struct device *dev = &cyapa->client->dev;
u8 power_state;
int error;
int error = 0;
if (cyapa->state != CYAPA_STATE_GEN5_APP)
return 0;
cyapa_set_pip_pm_state(cyapa, pm_stage);
if (PIP_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) {
/*
* Assume TP in deep sleep mode when driver is loaded,
@ -1597,7 +1645,7 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
power_mode == PWR_MODE_BTN_ONLY ||
PIP_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) {
/* Has in correct power mode state, early return. */
return 0;
goto out;
}
}
@ -1605,11 +1653,11 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
error = cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_OFF);
if (error) {
dev_err(dev, "enter deep sleep fail: %d\n", error);
return error;
goto out;
}
PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
return 0;
goto out;
}
/*
@ -1621,7 +1669,7 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
error = cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON);
if (error) {
dev_err(dev, "deep sleep wake fail: %d\n", error);
return error;
goto out;
}
}
@ -1630,7 +1678,7 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
GEN5_POWER_STATE_ACTIVE);
if (error) {
dev_err(dev, "change to active fail: %d\n", error);
return error;
goto out;
}
PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE);
@ -1639,7 +1687,7 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
GEN5_POWER_STATE_BTN_ONLY);
if (error) {
dev_err(dev, "fail to button only mode: %d\n", error);
return error;
goto out;
}
PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY);
@ -1664,7 +1712,7 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
if (error) {
dev_err(dev, "set power state to 0x%02x failed: %d\n",
power_state, error);
return error;
goto out;
}
/*
@ -1677,14 +1725,16 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
* is suspending which may cause interrupt line unable to be
* asserted again.
*/
if (is_suspend)
if (pm_stage == CYAPA_PM_SUSPEND)
cyapa_gen5_disable_pip_report(cyapa);
PIP_DEV_SET_PWR_STATE(cyapa,
cyapa_sleep_time_to_pwr_cmd(sleep_time));
}
return 0;
out:
cyapa_reset_pip_pm_state(cyapa);
return error;
}
int cyapa_pip_resume_scanning(struct cyapa *cyapa)
@ -2513,7 +2563,7 @@ static int cyapa_gen5_do_operational_check(struct cyapa *cyapa)
* the device state is required.
*/
error = cyapa_gen5_set_power_mode(cyapa,
PWR_MODE_FULL_ACTIVE, 0, false);
PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE);
if (error)
dev_warn(dev, "%s: failed to set power active mode.\n",
__func__);
@ -2715,7 +2765,6 @@ int cyapa_pip_irq_handler(struct cyapa *cyapa)
struct device *dev = &cyapa->client->dev;
struct cyapa_pip_report_data report_data;
unsigned int report_len;
u8 report_id;
int ret;
if (!cyapa_is_pip_app_mode(cyapa)) {
@ -2752,7 +2801,23 @@ int cyapa_pip_irq_handler(struct cyapa *cyapa)
return -EINVAL;
}
report_id = report_data.report_head[PIP_RESP_REPORT_ID_OFFSET];
return cyapa_pip_event_process(cyapa, &report_data);
}
static int cyapa_pip_event_process(struct cyapa *cyapa,
struct cyapa_pip_report_data *report_data)
{
struct device *dev = &cyapa->client->dev;
unsigned int report_len;
u8 report_id;
report_len = get_unaligned_le16(
&report_data->report_head[PIP_RESP_LENGTH_OFFSET]);
/* Idle, no data for report. */
if (report_len == PIP_RESP_LENGTH_SIZE)
return 0;
report_id = report_data->report_head[PIP_RESP_REPORT_ID_OFFSET];
if (report_id == PIP_WAKEUP_EVENT_REPORT_ID &&
report_len == PIP_WAKEUP_EVENT_SIZE) {
/*
@ -2805,11 +2870,11 @@ int cyapa_pip_irq_handler(struct cyapa *cyapa)
}
if (report_id == PIP_TOUCH_REPORT_ID)
cyapa_pip_report_touches(cyapa, &report_data);
cyapa_pip_report_touches(cyapa, report_data);
else if (report_id == PIP_PROXIMITY_REPORT_ID)
cyapa_pip_report_proximity(cyapa, &report_data);
cyapa_pip_report_proximity(cyapa, report_data);
else
cyapa_pip_report_buttons(cyapa, &report_data);
cyapa_pip_report_buttons(cyapa, report_data);
return 0;
}

View File

@ -425,7 +425,7 @@ static int cyapa_gen6_deep_sleep(struct cyapa *cyapa, u8 state)
}
static int cyapa_gen6_set_power_mode(struct cyapa *cyapa,
u8 power_mode, u16 sleep_time, bool is_suspend)
u8 power_mode, u16 sleep_time, enum cyapa_pm_stage pm_stage)
{
struct device *dev = &cyapa->client->dev;
struct gen6_interval_setting *interval_setting =
@ -689,7 +689,7 @@ static int cyapa_gen6_operational_check(struct cyapa *cyapa)
* the device state is required.
*/
error = cyapa_gen6_set_power_mode(cyapa,
PWR_MODE_FULL_ACTIVE, 0, false);
PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE);
if (error)
dev_warn(dev, "%s: failed to set power active mode.\n",
__func__);