Skip to content

Commit

Permalink
HID: i2c-hid: add runtime PM support
Browse files Browse the repository at this point in the history
This patch adds runtime PM support for the HID over I2C driver. When the
i2c-hid device is first opened we power it on and on the last close we
power it off. This is actually what the driver is already doing but in
addition it allows subsystems, like ACPI power domain to power off the
device during runtime PM suspend, which should save even more power.

The implementation is not the most power efficient because it needs some
interaction from the userspace (e.g close the device node whenever we are
no more interested in getting events), nevertheless it allows us to save
some power and works with devices that are not wake capable.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
  • Loading branch information
Mika Westerberg authored and Jiri Kosina committed Jan 30, 2014
1 parent 4988abf commit 34f439e
Showing 1 changed file with 57 additions and 11 deletions.
68 changes: 57 additions & 11 deletions drivers/hid/i2c-hid/i2c-hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/device.h>
#include <linux/wait.h>
#include <linux/err.h>
Expand Down Expand Up @@ -454,10 +455,18 @@ static void i2c_hid_init_reports(struct hid_device *hid)
return;
}

/*
* The device must be powered on while we fetch initial reports
* from it.
*/
pm_runtime_get_sync(&client->dev);

list_for_each_entry(report,
&hid->report_enum[HID_FEATURE_REPORT].report_list, list)
i2c_hid_init_report(report, inbuf, ihid->bufsize);

pm_runtime_put(&client->dev);

kfree(inbuf);
}

Expand Down Expand Up @@ -703,16 +712,16 @@ static int i2c_hid_open(struct hid_device *hid)

mutex_lock(&i2c_hid_open_mut);
if (!hid->open++) {
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
if (ret) {
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0) {
hid->open--;
goto done;
}
set_bit(I2C_HID_STARTED, &ihid->flags);
}
done:
mutex_unlock(&i2c_hid_open_mut);
return ret;
return ret < 0 ? ret : 0;
}

static void i2c_hid_close(struct hid_device *hid)
Expand All @@ -729,7 +738,7 @@ static void i2c_hid_close(struct hid_device *hid)
clear_bit(I2C_HID_STARTED, &ihid->flags);

/* Save some power */
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
pm_runtime_put(&client->dev);
}
mutex_unlock(&i2c_hid_open_mut);
}
Expand All @@ -738,19 +747,18 @@ static int i2c_hid_power(struct hid_device *hid, int lvl)
{
struct i2c_client *client = hid->driver_data;
struct i2c_hid *ihid = i2c_get_clientdata(client);
int ret = 0;

i2c_hid_dbg(ihid, "%s lvl:%d\n", __func__, lvl);

switch (lvl) {
case PM_HINT_FULLON:
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
pm_runtime_get_sync(&client->dev);
break;
case PM_HINT_NORMAL:
ret = i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
pm_runtime_put(&client->dev);
break;
}
return ret;
return 0;
}

static struct hid_ll_driver i2c_hid_ll_driver = {
Expand Down Expand Up @@ -987,13 +995,17 @@ static int i2c_hid_probe(struct i2c_client *client,
if (ret < 0)
goto err;

pm_runtime_get_noresume(&client->dev);
pm_runtime_set_active(&client->dev);
pm_runtime_enable(&client->dev);

ret = i2c_hid_fetch_hid_descriptor(ihid);
if (ret < 0)
goto err;
goto err_pm;

ret = i2c_hid_init_irq(client);
if (ret < 0)
goto err;
goto err_pm;

hid = hid_allocate_device();
if (IS_ERR(hid)) {
Expand Down Expand Up @@ -1024,6 +1036,7 @@ static int i2c_hid_probe(struct i2c_client *client,
goto err_mem_free;
}

pm_runtime_put(&client->dev);
return 0;

err_mem_free:
Expand All @@ -1032,6 +1045,10 @@ static int i2c_hid_probe(struct i2c_client *client,
err_irq:
free_irq(client->irq, ihid);

err_pm:
pm_runtime_put_noidle(&client->dev);
pm_runtime_disable(&client->dev);

err:
i2c_hid_free_buffers(ihid);
kfree(ihid);
Expand All @@ -1043,6 +1060,11 @@ static int i2c_hid_remove(struct i2c_client *client)
struct i2c_hid *ihid = i2c_get_clientdata(client);
struct hid_device *hid;

pm_runtime_get_sync(&client->dev);
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
pm_runtime_put_noidle(&client->dev);

hid = ihid->hid;
hid_destroy_device(hid);

Expand Down Expand Up @@ -1088,7 +1110,31 @@ static int i2c_hid_resume(struct device *dev)
}
#endif

static SIMPLE_DEV_PM_OPS(i2c_hid_pm, i2c_hid_suspend, i2c_hid_resume);
#ifdef CONFIG_PM_RUNTIME
static int i2c_hid_runtime_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);

i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
disable_irq(client->irq);
return 0;
}

static int i2c_hid_runtime_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);

enable_irq(client->irq);
i2c_hid_set_power(client, I2C_HID_PWR_ON);
return 0;
}
#endif

static const struct dev_pm_ops i2c_hid_pm = {
SET_SYSTEM_SLEEP_PM_OPS(i2c_hid_suspend, i2c_hid_resume)
SET_RUNTIME_PM_OPS(i2c_hid_runtime_suspend, i2c_hid_runtime_resume,
NULL)
};

static const struct i2c_device_id i2c_hid_id_table[] = {
{ "hid", 0 },
Expand Down

0 comments on commit 34f439e

Please sign in to comment.