iio: light: rpr0521 on-off sequence change for CONFIG_PM
authorMikko Koivunen <mikko.koivunen@fi.rohmeurope.com>
Thu, 18 May 2017 12:12:51 +0000 (15:12 +0300)
committerJonathan Cameron <jic23@kernel.org>
Sun, 21 May 2017 14:06:12 +0000 (15:06 +0100)
Refactor _set_power_state(), _resume() and _suspend().
Enable measurement only when needed, not in _init(). System can suspend
during measurement and measurement is continued on resume.
Pm turns off measurement when both ps and als measurements are disabled for
2 seconds. During off-time the power save is 20-500mA, typically 180mA.

Signed-off-by: Mikko Koivunen <mikko.koivunen@fi.rohmeurope.com>
Acked-by: Daniel Baluta <daniel.baluta@nxp.com>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
drivers/iio/light/rpr0521.c

index 84d8d40..9219819 100644 (file)
@@ -9,7 +9,7 @@
  *
  * IIO driver for RPR-0521RS (7-bit I2C slave address 0x38).
  *
- * TODO: illuminance channel, PM support, buffer
+ * TODO: illuminance channel, buffer
  */
 
 #include <linux/module.h>
@@ -142,9 +142,11 @@ struct rpr0521_data {
        bool als_dev_en;
        bool pxs_dev_en;
 
-       /* optimize runtime pm ops - enable device only if needed */
+       /* optimize runtime pm ops - enable/disable device only if needed */
        bool als_ps_need_en;
        bool pxs_ps_need_en;
+       bool als_need_dis;
+       bool pxs_need_dis;
 
        struct regmap *regmap;
 };
@@ -230,40 +232,32 @@ static int rpr0521_pxs_enable(struct rpr0521_data *data, u8 status)
  * @on: state to be set for devices in @device_mask
  * @device_mask: bitmask specifying for which device we need to update @on state
  *
- * We rely on rpr0521_runtime_resume to enable our @device_mask devices, but
- * if (for example) PXS was enabled (pxs_dev_en = true) by a previous call to
- * rpr0521_runtime_resume and we want to enable ALS we MUST set ALS enable
- * bit of RPR0521_REG_MODE_CTRL here because rpr0521_runtime_resume will not
- * be called twice.
+ * Calls for this function must be balanced so that each ON should have matching
+ * OFF. Otherwise pm usage_count gets out of sync.
  */
 static int rpr0521_set_power_state(struct rpr0521_data *data, bool on,
                                   u8 device_mask)
 {
 #ifdef CONFIG_PM
        int ret;
-       u8 update_mask = 0;
 
        if (device_mask & RPR0521_MODE_ALS_MASK) {
-               if (on && !data->als_ps_need_en && data->pxs_dev_en)
-                       update_mask |= RPR0521_MODE_ALS_MASK;
-               else
-                       data->als_ps_need_en = on;
+               data->als_ps_need_en = on;
+               data->als_need_dis = !on;
        }
 
        if (device_mask & RPR0521_MODE_PXS_MASK) {
-               if (on && !data->pxs_ps_need_en && data->als_dev_en)
-                       update_mask |= RPR0521_MODE_PXS_MASK;
-               else
-                       data->pxs_ps_need_en = on;
-       }
-
-       if (update_mask) {
-               ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL,
-                                        update_mask, update_mask);
-               if (ret < 0)
-                       return ret;
+               data->pxs_ps_need_en = on;
+               data->pxs_need_dis = !on;
        }
 
+       /*
+        * On: _resume() is called only when we are suspended
+        * Off: _suspend() is called after delay if _resume() is not
+        * called before that.
+        * Note: If either measurement is re-enabled before _suspend(),
+        * both stay enabled until _suspend().
+        */
        if (on) {
                ret = pm_runtime_get_sync(&data->client->dev);
        } else {
@@ -279,6 +273,23 @@ static int rpr0521_set_power_state(struct rpr0521_data *data, bool on,
 
                return ret;
        }
+
+       if (on) {
+               /* If _resume() was not called, enable measurement now. */
+               if (data->als_ps_need_en) {
+                       ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE);
+                       if (ret)
+                               return ret;
+                       data->als_ps_need_en = false;
+               }
+
+               if (data->pxs_ps_need_en) {
+                       ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE);
+                       if (ret)
+                               return ret;
+                       data->pxs_ps_need_en = false;
+               }
+       }
 #endif
        return 0;
 }
@@ -425,12 +436,14 @@ static int rpr0521_init(struct rpr0521_data *data)
                return ret;
        }
 
+#ifndef CONFIG_PM
        ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE);
        if (ret < 0)
                return ret;
        ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE);
        if (ret < 0)
                return ret;
+#endif
 
        return 0;
 }
@@ -560,9 +573,16 @@ static int rpr0521_runtime_suspend(struct device *dev)
        struct rpr0521_data *data = iio_priv(indio_dev);
        int ret;
 
-       /* disable channels and sets {als,pxs}_dev_en to false */
        mutex_lock(&data->lock);
+       /* If measurements are enabled, enable them on resume */
+       if (!data->als_need_dis)
+               data->als_ps_need_en = data->als_dev_en;
+       if (!data->pxs_need_dis)
+               data->pxs_ps_need_en = data->pxs_dev_en;
+
+       /* disable channels and sets {als,pxs}_dev_en to false */
        ret = rpr0521_poweroff(data);
+       regcache_mark_dirty(data->regmap);
        mutex_unlock(&data->lock);
 
        return ret;
@@ -574,6 +594,7 @@ static int rpr0521_runtime_resume(struct device *dev)
        struct rpr0521_data *data = iio_priv(indio_dev);
        int ret;
 
+       regcache_sync(data->regmap);
        if (data->als_ps_need_en) {
                ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE);
                if (ret < 0)
@@ -587,6 +608,7 @@ static int rpr0521_runtime_resume(struct device *dev)
                        return ret;
                data->pxs_ps_need_en = false;
        }
+       msleep(100);    //wait for first measurement result
 
        return 0;
 }