diff options
author | Ahmad Fatoum <a.fatoum@pengutronix.de> | 2022-07-24 21:00:04 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2022-08-11 08:33:41 +0200 |
commit | 324bd9bbe7e8acb94c6dbc1c51dc7065bd508768 (patch) | |
tree | 4b045b9ae0ecd77df9a619ed0dcc1bdd11aae2d1 | |
parent | d353ffd5c6e6d4cee6eee72726125c965a0814f0 (diff) | |
download | barebox-324bd9bbe7e8acb94c6dbc1c51dc7065bd508768.tar.gz barebox-324bd9bbe7e8acb94c6dbc1c51dc7065bd508768.tar.xz |
regulator: recursively enable/disable regulator dependency tree
Regulators may themself have other regulators supplying them.
The regulators need to be enabled recursively for proper operation.
Linux handles this by allows drivers to provide
struct regulator_desc::supply_name, which will be requested when the
regulator itself is requested and enabled/disabled as necessary.
As no driver yet uses this new member, this should introduce no
functional change.
Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
Link: https://lore.barebox.org/20220724190006.2160802-4-a.fatoum@pengutronix.de
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
-rw-r--r-- | drivers/regulator/core.c | 61 | ||||
-rw-r--r-- | include/regulator.h | 5 |
2 files changed, 63 insertions, 3 deletions
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 74324772ed..60432e0c3d 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -9,6 +9,7 @@ #include <of.h> #include <malloc.h> #include <linux/err.h> +#include <deep-probe.h> static LIST_HEAD(regulator_list); @@ -42,6 +43,7 @@ static int regulator_map_voltage(struct regulator_dev *rdev, int min_uV, static int regulator_enable_internal(struct regulator_internal *ri) { + struct regulator_dev *rdev = ri->rdev; int ret; if (ri->enable_count) { @@ -49,13 +51,20 @@ static int regulator_enable_internal(struct regulator_internal *ri) return 0; } - if (!ri->rdev->desc->ops->enable) + if (!rdev->desc->ops->enable) return -ENOSYS; - ret = ri->rdev->desc->ops->enable(ri->rdev); + /* turn on parent regulator */ + ret = regulator_enable(rdev->supply); if (ret) return ret; + ret = rdev->desc->ops->enable(ri->rdev); + if (ret) { + regulator_disable(rdev->supply); + return ret; + } + if (ri->enable_time_us) udelay(ri->enable_time_us); @@ -85,7 +94,7 @@ static int regulator_disable_internal(struct regulator_internal *ri) ri->enable_count--; - return 0; + return regulator_disable(ri->rdev->supply); } static int regulator_set_voltage_internal(struct regulator_internal *ri, @@ -115,6 +124,41 @@ static int regulator_set_voltage_internal(struct regulator_internal *ri, return -ENOSYS; } +static int regulator_resolve_supply(struct regulator_dev *rdev) +{ + struct regulator *supply; + const char *supply_name; + + if (!rdev || rdev->supply) + return 0; + + supply_name = rdev->desc->supply_name; + if (!supply_name) + return 0; + + supply = regulator_get(rdev->dev, supply_name); + if (IS_ERR(supply)) { + if (deep_probe_is_supported()) + return PTR_ERR(supply); + + /* For historic reasons, some regulator consumers don't handle + * -EPROBE_DEFER (e.g. vmmc-supply). If we now start propagating + * parent EPROBE_DEFER, previously requested vmmc-supply with + * always-on parent that worked before will end up not being + * requested breaking MMC use. So for non-deep probe systems, + * just make best effort to resolve, but don't fail the get if + * we couldn't. If you want to get rid of this warning, consider + * migrating your platform to have deep probe support. + */ + dev_warn(rdev->dev, "Failed to get '%s' regulator (ignored).\n", + supply_name); + return 0; + } + + rdev->supply = supply; + return 0; +} + static struct regulator_internal * __regulator_register(struct regulator_dev *rd, const char *name) { struct regulator_internal *ri; @@ -131,6 +175,10 @@ static struct regulator_internal * __regulator_register(struct regulator_dev *rd ri->name = xstrdup(name); if (rd->boot_on || rd->always_on) { + ret = regulator_resolve_supply(ri->rdev); + if (ret < 0) + goto err; + ret = regulator_enable_internal(ri); if (ret && ret != -ENOSYS) goto err; @@ -333,6 +381,7 @@ struct regulator *regulator_get(struct device_d *dev, const char *supply) { struct regulator_internal *ri = NULL; struct regulator *r; + int ret; if (dev->device_node) { ri = of_regulator_get(dev, supply); @@ -349,6 +398,10 @@ struct regulator *regulator_get(struct device_d *dev, const char *supply) if (!ri) return NULL; + ret = regulator_resolve_supply(ri->rdev); + if (ret < 0) + return ERR_PTR(ret); + r = xzalloc(sizeof(*r)); r->ri = ri; r->dev = dev; @@ -592,6 +645,8 @@ int regulator_get_voltage(struct regulator *regulator) ret = rdev->desc->fixed_uV; } else if (ri->min_uv && ri->min_uv == ri->max_uv) { ret = ri->min_uv; + } else if (rdev->supply) { + ret = regulator_get_voltage(rdev->supply); } else { return -EINVAL; } diff --git a/include/regulator.h b/include/regulator.h index dfdfbf0332..1ae31ad7cf 100644 --- a/include/regulator.h +++ b/include/regulator.h @@ -32,6 +32,8 @@ struct regulator_bulk_data { * structure contains the non-varying parts of the regulator * description. * + * @supply_name: Identifying the supply of this regulator + * * @n_voltages: Number of selectors available for ops.list_voltage(). * @ops: Regulator operations table. * @@ -57,6 +59,7 @@ struct regulator_bulk_data { */ struct regulator_desc { + const char *supply_name; unsigned n_voltages; const struct regulator_ops *ops; @@ -88,6 +91,8 @@ struct regulator_dev { bool always_on; /* the device this regulator device belongs to */ struct device_d *dev; + /* The regulator powering this device */ + struct regulator *supply; }; struct regulator_ops { |