summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmad Fatoum <a.fatoum@pengutronix.de>2022-07-24 21:00:04 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2022-08-11 08:33:41 +0200
commit324bd9bbe7e8acb94c6dbc1c51dc7065bd508768 (patch)
tree4b045b9ae0ecd77df9a619ed0dcc1bdd11aae2d1
parentd353ffd5c6e6d4cee6eee72726125c965a0814f0 (diff)
downloadbarebox-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.c61
-rw-r--r--include/regulator.h5
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 {