diff options
Diffstat (limited to 'drivers/hwmon/ina3221.c')
-rw-r--r-- | drivers/hwmon/ina3221.c | 93 |
1 files changed, 74 insertions, 19 deletions
diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c index 07dd6ef58d3e..17a57dbc0424 100644 --- a/drivers/hwmon/ina3221.c +++ b/drivers/hwmon/ina3221.c @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> +#include <linux/pm_runtime.h> #include <linux/regmap.h> #define INA3221_DRIVER_NAME "ina3221" @@ -53,6 +54,7 @@ #define INA3221_CONFIG_CHs_EN_MASK GENMASK(14, 12) #define INA3221_CONFIG_CHx_EN(x) BIT(14 - (x)) +#define INA3221_CONFIG_DEFAULT 0x7127 #define INA3221_RSHUNT_DEFAULT 10000 enum ina3221_fields { @@ -103,6 +105,7 @@ struct ina3221_input { /** * struct ina3221_data - device specific information + * @pm_dev: Device pointer for pm runtime * @regmap: Register map of the device * @fields: Register fields of the device * @inputs: Array of channel input source specific structures @@ -110,6 +113,7 @@ struct ina3221_input { * @reg_config: Register value of INA3221_CONFIG */ struct ina3221_data { + struct device *pm_dev; struct regmap *regmap; struct regmap_field *fields[F_MAX_FIELDS]; struct ina3221_input inputs[INA3221_NUM_CHANNELS]; @@ -119,7 +123,8 @@ struct ina3221_data { static inline bool ina3221_is_enabled(struct ina3221_data *ina, int channel) { - return ina->reg_config & INA3221_CONFIG_CHx_EN(channel); + return pm_runtime_active(ina->pm_dev) && + (ina->reg_config & INA3221_CONFIG_CHx_EN(channel)); } /* Lookup table for Bus and Shunt conversion times in usec */ @@ -290,21 +295,48 @@ static int ina3221_write_enable(struct device *dev, int channel, bool enable) { struct ina3221_data *ina = dev_get_drvdata(dev); u16 config, mask = INA3221_CONFIG_CHx_EN(channel); + u16 config_old = ina->reg_config & mask; int ret; config = enable ? mask : 0; + /* Bypass if enable status is not being changed */ + if (config_old == config) + return 0; + + /* For enabling routine, increase refcount and resume() at first */ + if (enable) { + ret = pm_runtime_get_sync(ina->pm_dev); + if (ret < 0) { + dev_err(dev, "Failed to get PM runtime\n"); + return ret; + } + } + /* Enable or disable the channel */ ret = regmap_update_bits(ina->regmap, INA3221_CONFIG, mask, config); if (ret) - return ret; + goto fail; /* Cache the latest config register value */ ret = regmap_read(ina->regmap, INA3221_CONFIG, &ina->reg_config); if (ret) - return ret; + goto fail; + + /* For disabling routine, decrease refcount or suspend() at last */ + if (!enable) + pm_runtime_put_sync(ina->pm_dev); return 0; + +fail: + if (enable) { + dev_err(dev, "Failed to enable channel %d: error %d\n", + channel, ret); + pm_runtime_put_sync(ina->pm_dev); + } + + return ret; } static int ina3221_read(struct device *dev, enum hwmon_sensor_types type, @@ -631,44 +663,65 @@ static int ina3221_probe(struct i2c_client *client, return ret; } - ret = regmap_field_write(ina->fields[F_RST], true); - if (ret) { - dev_err(dev, "Unable to reset device\n"); - return ret; - } - - /* Sync config register after reset */ - ret = regmap_read(ina->regmap, INA3221_CONFIG, &ina->reg_config); - if (ret) - return ret; + /* The driver will be reset, so use reset value */ + ina->reg_config = INA3221_CONFIG_DEFAULT; /* Disable channels if their inputs are disconnected */ for (i = 0; i < INA3221_NUM_CHANNELS; i++) { if (ina->inputs[i].disconnected) ina->reg_config &= ~INA3221_CONFIG_CHx_EN(i); } - ret = regmap_write(ina->regmap, INA3221_CONFIG, ina->reg_config); - if (ret) - return ret; + ina->pm_dev = dev; mutex_init(&ina->lock); dev_set_drvdata(dev, ina); + /* Enable PM runtime -- status is suspended by default */ + pm_runtime_enable(ina->pm_dev); + + /* Initialize (resume) the device */ + for (i = 0; i < INA3221_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + continue; + /* Match the refcount with number of enabled channels */ + ret = pm_runtime_get_sync(ina->pm_dev); + if (ret < 0) + goto fail; + } + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, ina, &ina3221_chip_info, ina3221_groups); if (IS_ERR(hwmon_dev)) { dev_err(dev, "Unable to register hwmon device\n"); - mutex_destroy(&ina->lock); - return PTR_ERR(hwmon_dev); + ret = PTR_ERR(hwmon_dev); + goto fail; } return 0; + +fail: + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + /* pm_runtime_put_noidle() will decrease the PM refcount until 0 */ + for (i = 0; i < INA3221_NUM_CHANNELS; i++) + pm_runtime_put_noidle(ina->pm_dev); + mutex_destroy(&ina->lock); + + return ret; } static int ina3221_remove(struct i2c_client *client) { struct ina3221_data *ina = dev_get_drvdata(&client->dev); + int i; + + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + + /* pm_runtime_put_noidle() will decrease the PM refcount until 0 */ + for (i = 0; i < INA3221_NUM_CHANNELS; i++) + pm_runtime_put_noidle(ina->pm_dev); mutex_destroy(&ina->lock); @@ -726,7 +779,9 @@ static int __maybe_unused ina3221_resume(struct device *dev) } static const struct dev_pm_ops ina3221_pm = { - SET_SYSTEM_SLEEP_PM_OPS(ina3221_suspend, ina3221_resume) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(ina3221_suspend, ina3221_resume, NULL) }; static const struct of_device_id ina3221_of_match_table[] = { |