iio: humidity: hdc3020: add power management
authorJavier Carrasco <javier.carrasco.cruz@gmail.com>
Sun, 3 Mar 2024 21:54:20 +0000 (22:54 +0100)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Mon, 25 Mar 2024 20:10:13 +0000 (20:10 +0000)
The HDC3020 sensor carries out periodic measurements during normal
operation, but as long as the power supply is enabled, it will carry on
in low-power modes. In order to avoid that and reduce power consumption,
the device can be switched to Trigger-on Demand mode, and if possible,
turn off its regulator.

According to the datasheet, the maximum "Power Up Ready" is 5 ms.

Add resume/suspend pm operations to manage measurement mode and
regulator state.

Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
Link: https://lore.kernel.org/r/20240303-hdc3020-pm-v3-1-48bc02b5241b@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/humidity/hdc3020.c

index 1e5d0d4797b16eeac76698c20160ad01271c19e8..7f93024b850c2076f4f796524e5b89103c3dc0d9 100644 (file)
@@ -20,6 +20,8 @@
 #include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/regulator/consumer.h>
 #include <linux/units.h>
 
 #include <asm/unaligned.h>
@@ -68,6 +70,7 @@
 
 struct hdc3020_data {
        struct i2c_client *client;
+       struct regulator *vdd_supply;
        /*
         * Ensure that the sensor configuration (currently only heater is
         * supported) will not be changed during the process of reading
@@ -551,9 +554,45 @@ static const struct iio_info hdc3020_info = {
        .write_event_value = hdc3020_write_thresh,
 };
 
-static void hdc3020_stop(void *data)
+static int hdc3020_power_off(struct hdc3020_data *data)
 {
-       hdc3020_exec_cmd((struct hdc3020_data *)data, HDC3020_EXIT_AUTO);
+       hdc3020_exec_cmd(data, HDC3020_EXIT_AUTO);
+
+       return regulator_disable(data->vdd_supply);
+}
+
+static int hdc3020_power_on(struct hdc3020_data *data)
+{
+       int ret;
+
+       ret = regulator_enable(data->vdd_supply);
+       if (ret)
+               return ret;
+
+       fsleep(5000);
+
+       if (data->client->irq) {
+               /*
+                * The alert output is activated by default upon power up,
+                * hardware reset, and soft reset. Clear the status register.
+                */
+               ret = hdc3020_exec_cmd(data, HDC3020_S_STATUS);
+               if (ret) {
+                       hdc3020_power_off(data);
+                       return ret;
+               }
+       }
+
+       ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0);
+       if (ret)
+               hdc3020_power_off(data);
+
+       return ret;
+}
+
+static void hdc3020_exit(void *data)
+{
+       hdc3020_power_off(data);
 }
 
 static int hdc3020_probe(struct i2c_client *client)
@@ -569,6 +608,8 @@ static int hdc3020_probe(struct i2c_client *client)
        if (!indio_dev)
                return -ENOMEM;
 
+       dev_set_drvdata(&client->dev, indio_dev);
+
        data = iio_priv(indio_dev);
        data->client = client;
        mutex_init(&data->lock);
@@ -580,6 +621,20 @@ static int hdc3020_probe(struct i2c_client *client)
        indio_dev->info = &hdc3020_info;
        indio_dev->channels = hdc3020_channels;
        indio_dev->num_channels = ARRAY_SIZE(hdc3020_channels);
+
+       data->vdd_supply = devm_regulator_get(&client->dev, "vdd");
+       if (IS_ERR(data->vdd_supply))
+               return dev_err_probe(&client->dev, PTR_ERR(data->vdd_supply),
+                                    "Unable to get VDD regulator\n");
+
+       ret = hdc3020_power_on(data);
+       if (ret)
+               return dev_err_probe(&client->dev, ret, "Power on failed\n");
+
+       ret = devm_add_action_or_reset(&data->client->dev, hdc3020_exit, data);
+       if (ret)
+               return ret;
+
        if (client->irq) {
                ret = devm_request_threaded_irq(&client->dev, client->irq,
                                                NULL, hdc3020_interrupt_handler,
@@ -588,25 +643,8 @@ static int hdc3020_probe(struct i2c_client *client)
                if (ret)
                        return dev_err_probe(&client->dev, ret,
                                             "Failed to request IRQ\n");
-
-               /*
-                * The alert output is activated by default upon power up,
-                * hardware reset, and soft reset. Clear the status register.
-                */
-               ret = hdc3020_exec_cmd(data, HDC3020_S_STATUS);
-               if (ret)
-                       return ret;
        }
 
-       ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0);
-       if (ret)
-               return dev_err_probe(&client->dev, ret,
-                                    "Unable to set up measurement\n");
-
-       ret = devm_add_action_or_reset(&data->client->dev, hdc3020_stop, data);
-       if (ret)
-               return ret;
-
        ret = devm_iio_device_register(&data->client->dev, indio_dev);
        if (ret)
                return dev_err_probe(&client->dev, ret, "Failed to add device");
@@ -614,6 +652,24 @@ static int hdc3020_probe(struct i2c_client *client)
        return 0;
 }
 
+static int hdc3020_suspend(struct device *dev)
+{
+       struct iio_dev *iio_dev = dev_get_drvdata(dev);
+       struct hdc3020_data *data = iio_priv(iio_dev);
+
+       return hdc3020_power_off(data);
+}
+
+static int hdc3020_resume(struct device *dev)
+{
+       struct iio_dev *iio_dev = dev_get_drvdata(dev);
+       struct hdc3020_data *data = iio_priv(iio_dev);
+
+       return hdc3020_power_on(data);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(hdc3020_pm_ops, hdc3020_suspend, hdc3020_resume);
+
 static const struct i2c_device_id hdc3020_id[] = {
        { "hdc3020" },
        { "hdc3021" },
@@ -633,6 +689,7 @@ MODULE_DEVICE_TABLE(of, hdc3020_dt_ids);
 static struct i2c_driver hdc3020_driver = {
        .driver = {
                .name = "hdc3020",
+               .pm = pm_sleep_ptr(&hdc3020_pm_ops),
                .of_match_table = hdc3020_dt_ids,
        },
        .probe = hdc3020_probe,