staging: iio: isl29028: add runtime power management support
authorBrian Masney <masneyb@onstation.org>
Tue, 17 Jan 2017 09:25:02 +0000 (04:25 -0500)
committerJonathan Cameron <jic23@kernel.org>
Sun, 22 Jan 2017 13:21:40 +0000 (13:21 +0000)
This patch adds runtime power management support to the isl29028 driver.
It defaults to powering off the device after two seconds of inactivity.

isl29028_chip_init_and_power_on() currently only zeros the CONFIGURE
register on the chip, which will cause the chip to turn off. This patch
also renames that function to isl29028_clear_configure_reg() since it is
now used in several places.

Signed-off-by: Brian Masney <masneyb@onstation.org>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
drivers/staging/iio/light/isl29028.c

index 598a5a5a8641bf54e755449562d9f3e74c07582a..6bb6d37cc7d13a36b795d4a221251e73b520429c 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/regmap.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
+#include <linux/pm_runtime.h>
 
 #define ISL29028_CONV_TIME_MS                  100
 
@@ -60,6 +61,8 @@
 
 #define ISL29028_NUM_REGS                      (ISL29028_REG_TEST2_MODE + 1)
 
+#define ISL29028_POWER_OFF_DELAY_MS            2000
+
 enum isl29028_als_ir_mode {
        ISL29028_MODE_NONE = 0,
        ISL29028_MODE_ALS,
@@ -297,6 +300,23 @@ static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data)
        return isl29028_read_als_ir(chip, ir_data);
 }
 
+static int isl29028_set_pm_runtime_busy(struct isl29028_chip *chip, bool on)
+{
+       struct device *dev = regmap_get_device(chip->regmap);
+       int ret;
+
+       if (on) {
+               ret = pm_runtime_get_sync(dev);
+               if (ret < 0)
+                       pm_runtime_put_noidle(dev);
+       } else {
+               pm_runtime_mark_last_busy(dev);
+               ret = pm_runtime_put_autosuspend(dev);
+       }
+
+       return ret;
+}
+
 /* Channel IO */
 static int isl29028_write_raw(struct iio_dev *indio_dev,
                              struct iio_chan_spec const *chan,
@@ -304,9 +324,15 @@ static int isl29028_write_raw(struct iio_dev *indio_dev,
 {
        struct isl29028_chip *chip = iio_priv(indio_dev);
        struct device *dev = regmap_get_device(chip->regmap);
-       int ret = -EINVAL;
+       int ret;
+
+       ret = isl29028_set_pm_runtime_busy(chip, true);
+       if (ret < 0)
+               return ret;
 
        mutex_lock(&chip->lock);
+
+       ret = -EINVAL;
        switch (chan->type) {
        case IIO_PROXIMITY:
                if (mask != IIO_CHAN_INFO_SAMP_FREQ) {
@@ -350,6 +376,13 @@ static int isl29028_write_raw(struct iio_dev *indio_dev,
 
        mutex_unlock(&chip->lock);
 
+       if (ret < 0)
+               return ret;
+
+       ret = isl29028_set_pm_runtime_busy(chip, false);
+       if (ret < 0)
+               return ret;
+
        return ret;
 }
 
@@ -359,9 +392,15 @@ static int isl29028_read_raw(struct iio_dev *indio_dev,
 {
        struct isl29028_chip *chip = iio_priv(indio_dev);
        struct device *dev = regmap_get_device(chip->regmap);
-       int ret = -EINVAL;
+       int ret, pm_ret;
+
+       ret = isl29028_set_pm_runtime_busy(chip, true);
+       if (ret < 0)
+               return ret;
 
        mutex_lock(&chip->lock);
+
+       ret = -EINVAL;
        switch (mask) {
        case IIO_CHAN_INFO_RAW:
        case IIO_CHAN_INFO_PROCESSED:
@@ -405,6 +444,18 @@ static int isl29028_read_raw(struct iio_dev *indio_dev,
 
        mutex_unlock(&chip->lock);
 
+       if (ret < 0)
+               return ret;
+
+       /**
+        * Preserve the ret variable if the call to
+        * isl29028_set_pm_runtime_busy() is successful so the reading
+        * (if applicable) is returned to user space.
+        */
+       pm_ret = isl29028_set_pm_runtime_busy(chip, false);
+       if (pm_ret < 0)
+               return pm_ret;
+
        return ret;
 }
 
@@ -445,17 +496,18 @@ static const struct iio_info isl29028_info = {
        .write_raw = isl29028_write_raw,
 };
 
-static int isl29028_chip_init_and_power_on(struct isl29028_chip *chip)
+static int isl29028_clear_configure_reg(struct isl29028_chip *chip)
 {
        struct device *dev = regmap_get_device(chip->regmap);
        int ret;
 
        ret = regmap_write(chip->regmap, ISL29028_REG_CONFIGURE, 0x0);
-       if (ret < 0) {
+       if (ret < 0)
                dev_err(dev, "%s(): Error %d clearing the CONFIGURE register\n",
                        __func__, ret);
-               return ret;
-       }
+
+       chip->als_ir_mode = ISL29028_MODE_NONE;
+       chip->enable_prox = false;
 
        return ret;
 }
@@ -509,7 +561,6 @@ static int isl29028_probe(struct i2c_client *client,
        chip->enable_prox  = false;
        chip->prox_sampling = 20;
        chip->lux_scale = 2000;
-       chip->als_ir_mode = ISL29028_MODE_NONE;
 
        ret = regmap_write(chip->regmap, ISL29028_REG_TEST1_MODE, 0x0);
        if (ret < 0) {
@@ -527,7 +578,7 @@ static int isl29028_probe(struct i2c_client *client,
                return ret;
        }
 
-       ret = isl29028_chip_init_and_power_on(chip);
+       ret = isl29028_clear_configure_reg(chip);
        if (ret < 0)
                return ret;
 
@@ -538,6 +589,11 @@ static int isl29028_probe(struct i2c_client *client,
        indio_dev->dev.parent = &client->dev;
        indio_dev->modes = INDIO_DIRECT_MODE;
 
+       pm_runtime_enable(&client->dev);
+       pm_runtime_set_autosuspend_delay(&client->dev,
+                                        ISL29028_POWER_OFF_DELAY_MS);
+       pm_runtime_use_autosuspend(&client->dev);
+
        ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev);
        if (ret < 0) {
                dev_err(&client->dev,
@@ -549,6 +605,50 @@ static int isl29028_probe(struct i2c_client *client,
        return 0;
 }
 
+static int isl29028_remove(struct i2c_client *client)
+{
+       struct iio_dev *indio_dev = i2c_get_clientdata(client);
+       struct isl29028_chip *chip = iio_priv(indio_dev);
+
+       iio_device_unregister(indio_dev);
+
+       pm_runtime_disable(&client->dev);
+       pm_runtime_set_suspended(&client->dev);
+       pm_runtime_put_noidle(&client->dev);
+
+       return isl29028_clear_configure_reg(chip);
+}
+
+static int __maybe_unused isl29028_suspend(struct device *dev)
+{
+       struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+       struct isl29028_chip *chip = iio_priv(indio_dev);
+       int ret;
+
+       mutex_lock(&chip->lock);
+
+       ret = isl29028_clear_configure_reg(chip);
+
+       mutex_unlock(&chip->lock);
+
+       return ret;
+}
+
+static int __maybe_unused isl29028_resume(struct device *dev)
+{
+       /**
+        * The specific component (ALS/IR or proximity) will enable itself as
+        * needed the next time that the user requests a reading. This is done
+        * above in isl29028_set_als_ir_mode() and isl29028_enable_proximity().
+        */
+       return 0;
+}
+
+static const struct dev_pm_ops isl29028_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(isl29028_suspend, isl29028_resume)
+       SET_RUNTIME_PM_OPS(isl29028_suspend, isl29028_resume, NULL)
+};
+
 static const struct i2c_device_id isl29028_id[] = {
        {"isl29028", 0},
        {}
@@ -565,9 +665,11 @@ MODULE_DEVICE_TABLE(of, isl29028_of_match);
 static struct i2c_driver isl29028_driver = {
        .driver  = {
                .name = "isl29028",
+               .pm = &isl29028_pm_ops,
                .of_match_table = isl29028_of_match,
        },
        .probe   = isl29028_probe,
+       .remove  = isl29028_remove,
        .id_table = isl29028_id,
 };