Skip to content

Commit

Permalink
Input: cyapa - fix for losing events during device power transitions
Browse files Browse the repository at this point in the history
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 <[email protected]>
Tested-by: Jeremiah Mahler <[email protected]>
Signed-off-by: Dmitry Torokhov <[email protected]>
  • Loading branch information
Dudley Du authored and dtor committed Mar 4, 2016
1 parent 5186b8c commit 3cd4786
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 59 deletions.
22 changes: 12 additions & 10 deletions drivers/input/mouse/cyapa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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;
}
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand Down
14 changes: 13 additions & 1 deletion drivers/input/mouse/cyapa.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand All @@ -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);
};
Expand All @@ -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];
};
Expand Down
108 changes: 79 additions & 29 deletions drivers/input/mouse/cyapa_gen3.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand All @@ -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; }
Expand Down
Loading

0 comments on commit 3cd4786

Please sign in to comment.