[PATCH 3/6] input: cyapa: add power mode sleep and runtime power mode supported.

From: Dudley Du
Date: Wed Apr 16 2014 - 04:38:27 EST


In order to save power when the trackpad device is not used, the sleep power mode and runtime power mode must be supported.
And the enter sleep time can be configured in the sysfs system.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <dudl@xxxxxxxxxxx>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 7b269d8..6820b3f 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -27,6 +27,7 @@
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/unaligned/access_ok.h>
+#include <linux/pm_runtime.h>

/* APA trackpad firmware generation */
#define CYAPA_GEN_UNKNOWN 0x00 /* unknown protocol. */
@@ -505,6 +506,10 @@ struct cyapa {
u16 suspend_sleep_time;
u8 real_power_mode;
u16 real_sleep_time;
+#ifdef CONFIG_PM_RUNTIME
+ u8 runtime_suspend_power_mode;
+ u16 runtime_suspend_sleep_time;
+#endif /* CONFIG_PM_RUNTIME */
bool suspended;

/* read from query data region. */
@@ -1873,6 +1878,13 @@ static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
return -EAGAIN;
}

+static int cyapa_gen5_sleep_time_check(u16 sleep_time)
+{
+ if (sleep_time > 1000)
+ sleep_time = 1000;
+ return sleep_time;
+}
+
static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
{
int ret;
@@ -2571,6 +2583,9 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
struct input_dev *input = cyapa->input;
int length;

+ pm_runtime_get_sync(dev);
+ pm_runtime_mark_last_busy(dev);
+
if (device_may_wakeup(dev))
pm_wakeup_event(dev, 0);

@@ -2646,6 +2661,7 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)

if (cyapa->cyapa_irq_handler)
cyapa->cyapa_irq_handler(cyapa);
+ pm_runtime_put_sync_autosuspend(dev);

out:
return IRQ_HANDLED;
@@ -2945,6 +2961,152 @@ static void cyapa_detect(struct cyapa *cyapa)
cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
}

+
+/*
+ * Sysfs Interface.
+ */
+
+#ifdef CONFIG_PM_SLEEP
+static ssize_t cyapa_show_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u8 pwr_cmd = cyapa->suspend_power_mode;
+ u16 sleep_time;
+ int len;
+
+ if (pwr_cmd == PWR_MODE_BTN_ONLY)
+ len = scnprintf(buf, PAGE_SIZE, "%s\n", BTN_ONLY_MODE_NAME);
+ else if (pwr_cmd == PWR_MODE_OFF)
+ len = scnprintf(buf, PAGE_SIZE, "%s\n", OFF_MODE_NAME);
+ else {
+ if (cyapa->gen == CYAPA_GEN3)
+ sleep_time = cyapa_pwr_cmd_to_sleep_time(pwr_cmd);
+ else
+ sleep_time = cyapa->suspend_sleep_time;
+ len = scnprintf(buf, PAGE_SIZE, "%u\n", sleep_time);
+ }
+
+ return len;
+}
+
+static ssize_t cyapa_update_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u16 sleep_time;
+
+ if (buf == NULL || count == 0)
+ goto invalidparam;
+
+ if (sysfs_streq(buf, BTN_ONLY_MODE_NAME))
+ cyapa->suspend_power_mode = PWR_MODE_BTN_ONLY;
+ else if (sysfs_streq(buf, OFF_MODE_NAME))
+ cyapa->suspend_power_mode = PWR_MODE_OFF;
+ else if (!kstrtou16(buf, 10, &sleep_time)) {
+ cyapa->suspend_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(sleep_time);
+ if (cyapa->gen > CYAPA_GEN3)
+ cyapa->suspend_sleep_time =
+ cyapa_gen5_sleep_time_check(sleep_time);
+ } else
+ goto invalidparam;
+
+ return count;
+
+invalidparam:
+ dev_err(dev, "invalid suspend scanrate ms parameters\n");
+ return -EINVAL;
+}
+
+static DEVICE_ATTR(suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+ cyapa_show_suspend_scanrate,
+ cyapa_update_suspend_scanrate);
+
+static struct attribute *cyapa_power_wakeup_entries[] = {
+ &dev_attr_suspend_scanrate_ms.attr,
+ NULL,
+};
+
+static const struct attribute_group cyapa_power_wakeup_group = {
+ .name = power_group_name,
+ .attrs = cyapa_power_wakeup_entries,
+};
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+static ssize_t cyapa_show_rt_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u8 pwr_cmd = cyapa->runtime_suspend_power_mode;
+
+ if (cyapa->gen == CYAPA_GEN3)
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ cyapa_pwr_cmd_to_sleep_time(pwr_cmd));
+ else
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ cyapa->runtime_suspend_sleep_time);
+}
+
+static ssize_t cyapa_update_rt_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u16 time;
+
+ if (buf == NULL || count == 0 || kstrtou16(buf, 10, &time)) {
+ dev_err(dev, "invalid runtime suspend scanrate ms parameter\n");
+ return -EINVAL;
+ }
+
+ /*
+ * When the suspend scanrate is changed, pm_runtime_get to resume
+ * a potentially suspended device, update to the new pwr_cmd
+ * and then pm_runtime_put to suspend into the new power mode.
+ */
+ pm_runtime_get_sync(dev);
+ cyapa->runtime_suspend_power_mode = cyapa_sleep_time_to_pwr_cmd(time);
+ if (cyapa->gen > CYAPA_GEN3)
+ cyapa->runtime_suspend_sleep_time = time;
+ pm_runtime_put_sync_autosuspend(dev);
+ return count;
+}
+
+static DEVICE_ATTR(runtime_suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+ cyapa_show_rt_suspend_scanrate,
+ cyapa_update_rt_suspend_scanrate);
+
+static struct attribute *cyapa_power_runtime_entries[] = {
+ &dev_attr_runtime_suspend_scanrate_ms.attr,
+ NULL,
+};
+
+static const struct attribute_group cyapa_power_runtime_group = {
+ .name = power_group_name,
+ .attrs = cyapa_power_runtime_entries,
+};
+
+static void cyapa_start_runtime(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+
+ cyapa->runtime_suspend_power_mode = PWR_MODE_IDLE;
+ if (sysfs_merge_group(&dev->kobj, &cyapa_power_runtime_group))
+ dev_warn(dev, "error creating wakeup runtime entries.\n");
+ pm_runtime_set_active(dev);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY);
+ pm_runtime_enable(dev);
+}
+#else
+static void cyapa_start_runtime(struct cyapa *cyapa) {}
+#endif /* CONFIG_PM_RUNTIME */
+
static void cyapa_detect_async(void *data, async_cookie_t cookie)
{
struct cyapa *cyapa = (struct cyapa *)data;
@@ -2957,6 +3119,15 @@ static void cyapa_detect_async(void *data, async_cookie_t cookie)
atomic_dec(&cyapa->in_detecting);
}

+static void cyapa_detect_and_start(void *data, async_cookie_t cookie)
+{
+ struct cyapa *cyapa = data;
+
+ cyapa_detect_async(data, cookie);
+
+ cyapa_start_runtime(cyapa);
+}
+
static int cyapa_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
@@ -3017,7 +3188,13 @@ static int cyapa_probe(struct i2c_client *client,
}
cyapa_disable_irq(cyapa);

- async_schedule(cyapa_detect_async, cyapa);
+#ifdef CONFIG_PM_SLEEP
+ if (device_can_wakeup(dev) &&
+ sysfs_merge_group(&client->dev.kobj, &cyapa_power_wakeup_group))
+ dev_warn(dev, "error creating wakeup power entries.\n");
+#endif /* CONFIG_PM_SLEEP */
+
+ async_schedule(cyapa_detect_and_start, cyapa);
return 0;

err_unregister_device:
@@ -3032,6 +3209,16 @@ static int cyapa_remove(struct i2c_client *client)
{
struct cyapa *cyapa = i2c_get_clientdata(client);

+ pm_runtime_disable(&client->dev);
+
+#ifdef CONFIG_PM_SLEEP
+ sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_wakeup_group);
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+ sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_runtime_group);
+#endif
+
free_irq(cyapa->irq, cyapa);
input_unregister_device(cyapa->input);

@@ -3090,12 +3277,56 @@ static int cyapa_resume(struct device *dev)

async_schedule(cyapa_detect_async, cyapa);

+ /* runtime set active to reflect active state. */
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
cyapa->suspended = false;
return 0;
}
#endif /* CONFIG_PM_SLEEP */

-static SIMPLE_DEV_PM_OPS(cyapa_pm_ops, cyapa_suspend, cyapa_resume);
+#ifdef CONFIG_PM_RUNTIME
+static int cyapa_runtime_suspend(struct device *dev)
+{
+ int ret;
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+
+ if (cyapa->cyapa_set_power_mode) {
+ /* set trackpad device to idle mode */
+ ret = cyapa->cyapa_set_power_mode(cyapa,
+ cyapa->runtime_suspend_power_mode,
+ cyapa->runtime_suspend_sleep_time);
+ if (ret)
+ dev_err(dev, "runtime suspend failed, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cyapa_runtime_resume(struct device *dev)
+{
+ int ret;
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+
+ if (cyapa->cyapa_set_power_mode) {
+ /* resume to full active mode */
+ ret = cyapa->cyapa_set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ if (ret)
+ dev_err(dev, "runtime resume failed, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM_RUNTIME */
+
+static const struct dev_pm_ops cyapa_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(cyapa_suspend, cyapa_resume)
+ SET_RUNTIME_PM_OPS(cyapa_runtime_suspend, cyapa_runtime_resume, NULL)
+};

static const struct i2c_device_id cyapa_id_table[] = {
{ "cyapa", 0 },
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.