[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20200528190610.179984-3-saravanak@google.com>
Date: Thu, 28 May 2020 12:06:10 -0700
From: Saravana Kannan <saravanak@...gle.com>
To: Liam Girdwood <lgirdwood@...il.com>,
Mark Brown <broonie@...nel.org>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>
Cc: Saravana Kannan <saravanak@...gle.com>,
John Stultz <john.stultz@...aro.org>,
linux-kernel@...r.kernel.org, kernel-team@...roid.com
Subject: [PATCH v2 2/2] regulator: Add support for sync_state() callbacks
When a regulator is left on by the bootloader or anything else before
the kernel starts (let's call this a "boot on" regulator), we need to
keep it on till all the consumers of the regulator have probed. This is
especially important for regulators that might be powering more than one
consumer. Otherwise, when the first consumer probes, enables and then
disables the "boot on" regulator, it'd turn off the power to rest of the
consumers of the "boot on" regulator.
Also, if a regulator driver is loaded as a module and the regulator
device has a few "boot on" regulators, we still want them to be turned
off after all the consumers have probed (if none of them have requested
for those regulators to stay on).
The sync_state() callback that's been added to drivers is meant for
situations like this. It gets called when all the consumers of a device
have probed successfully. To ease the transition to sync_state()
callbacks, it is never called before late_initcall_sync().
sync_state() callbacks become even more useful when combined with
fw_devlink. If fw_devlink is off, sync_state() is called at
late_initcall_sync() or the regulator device probing successfully --
whichever is later. This is because, with fw_devlink off, there would be
no consumers to the regulator device when it probes.
If fw_devlink is not off (it's permissive by default now), then
sync_state() is called when all the consumers of the regulator device
have probed or at late_initcall_sync() -- whichever is later.
This commit adds a regulator_sync_state() helper function that takes
care of all the "boot on" regulator clean up for any regulator driver.
All one needs to do is add the following line to the driver struct.
.sync_state = regulator_sync_state,
Once that's done, then for any device that's probed by the driver, all
the "boot on" regulators supplied by the device will be kept on till all
the consumers of the device have probed. Once the consumers have probed,
the "boot on" regulators would be allowed to turn off if they are voted
on by any of the consumers.
Signed-off-by: Saravana Kannan <saravanak@...gle.com>
---
drivers/regulator/core.c | 112 +++++++++++++++++++++++++++++++
include/linux/regulator/driver.h | 2 +
2 files changed, 114 insertions(+)
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 941783a14b45..913d387541d6 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -1768,6 +1768,88 @@ static struct regulator_dev *regulator_dev_lookup(struct device *dev,
return ERR_PTR(-ENODEV);
}
+static void regulator_set_minimum_state(struct regulator_dev *rdev)
+{
+ struct device *dev = rdev->dev.parent;
+ struct regulation_constraints *c = rdev->constraints;
+
+ /*
+ * The device doesn't have sync_state() support, or we already took
+ * care of it.
+ */
+ if (!dev_has_sync_state(dev) || rdev->sync_supply)
+ return;
+
+ /*
+ * Wait for parent supply to be ready before trying to keep this
+ * regulator on.
+ */
+ if (rdev->supply_name && !rdev->supply)
+ return;
+
+ if (c && c->always_on)
+ return;
+
+ if (!regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS))
+ return;
+
+ /*
+ * If the regulator is disabled or has an error getting the status,
+ * assume it's not on. We won't explicitly turn it off now, that can
+ * happen later.
+ */
+ if (_regulator_is_enabled(rdev) <= 0)
+ return;
+
+ /*
+ * Get a regulator client handle to make requests on the regulator in
+ * way that'll prevent it from going below the state in which the boot
+ * loader left it in.
+ */
+ rdev->sync_supply = create_regulator(rdev, NULL, "SYNC-SUPPLY");
+
+ /*
+ * At this point, we shouldn't have trouble getting a regulator. If we
+ * do, give up.
+ */
+ if (rdev->sync_supply == NULL) {
+ dev_err(&rdev->dev, "Unable to make votes on the regulator\n");
+ rdev->sync_supply = ERR_PTR(-EINVAL);
+ return;
+ }
+ rdev->open_count++;
+
+ /* If we can't put a vote to keep the regulator enabled, we give up. */
+ if (regulator_enable(rdev->sync_supply)) {
+ dev_err(&rdev->dev, "Unable to keep the regulator on\n");
+ regulator_put(rdev->sync_supply);
+ rdev->sync_supply = ERR_PTR(-EINVAL);
+ }
+}
+
+static int regulator_rel_minimum_state(struct device *dev, void *data)
+{
+ struct regulator_dev *rdev = dev_to_rdev(dev);
+
+ if (dev->parent != data)
+ return 0;
+
+ if (IS_ERR_OR_NULL(rdev->sync_supply))
+ return 0;
+
+ regulator_disable(rdev->sync_supply);
+ regulator_put(rdev->sync_supply);
+ rdev->sync_supply = NULL;
+ return 0;
+}
+
+void regulator_sync_state(struct device *dev)
+{
+ class_for_each_device(®ulator_class, NULL, dev,
+ regulator_rel_minimum_state);
+}
+EXPORT_SYMBOL_GPL(regulator_sync_state);
+
static int regulator_resolve_supply(struct regulator_dev *rdev)
{
struct regulator_dev *r;
@@ -1826,6 +1908,8 @@ static int regulator_resolve_supply(struct regulator_dev *rdev)
return ret;
}
+ regulator_set_minimum_state(rdev);
+
/*
* In set_machine_constraints() we may have turned this regulator on
* but we couldn't propagate to the supply if it hadn't been resolved
@@ -5188,6 +5272,15 @@ regulator_register(const struct regulator_desc *regulator_desc,
!rdev->desc->fixed_uV)
rdev->is_switch = true;
+ /*
+ * I'm not too enthusiastic about this. I'd rather just go set
+ * .sync_state() in all the regulator drivers. But let's get the rest
+ * of the patch sorted out first.
+ */
+ if (dev_set_drv_sync_state(rdev->dev.parent, regulator_sync_state))
+ dev_dbg(&rdev->dev, "parent sync_state() already set\n");
+ regulator_set_minimum_state(rdev);
+
dev_set_drvdata(&rdev->dev, rdev);
ret = device_register(&rdev->dev);
if (ret != 0) {
@@ -5729,6 +5822,15 @@ static int regulator_late_cleanup(struct device *dev, void *data)
return 0;
}
+static int regulator_minimum_state_cleanup(struct device *dev, void *data)
+{
+ regulator_rel_minimum_state(dev, dev->parent);
+ return 0;
+}
+
+static unsigned int regulator_cleanup_timeout = 30000;
+core_param(regulator_cleanup_timeout, regulator_cleanup_timeout, uint, 0);
+
static void regulator_init_complete_work_function(struct work_struct *work)
{
/*
@@ -5741,6 +5843,16 @@ static void regulator_init_complete_work_function(struct work_struct *work)
class_for_each_device(®ulator_class, NULL, NULL,
regulator_register_resolve_supply);
+ /*
+ * If regulator_cleanup_timeout is set to a non-zero value, it probably
+ * means some of the consumers will never probe or the regulators have
+ * some restrictions on how long they can stay ON. So, don't wait
+ * forever for consumer devices to probe.
+ */
+ if (regulator_cleanup_timeout)
+ class_for_each_device(®ulator_class, NULL, NULL,
+ regulator_minimum_state_cleanup);
+
/* If we have a full configuration then disable any regulators
* we have permission to change the status for and which are
* not in use or always_on. This is effectively the default
diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h
index 7eb9fea8e482..2e2208e7b771 100644
--- a/include/linux/regulator/driver.h
+++ b/include/linux/regulator/driver.h
@@ -451,6 +451,7 @@ struct regulator_dev {
struct regulator *supply; /* for tree */
const char *supply_name;
struct regmap *regmap;
+ struct regulator *sync_supply;
struct delayed_work disable_work;
@@ -475,6 +476,7 @@ devm_regulator_register(struct device *dev,
const struct regulator_desc *regulator_desc,
const struct regulator_config *config);
void regulator_unregister(struct regulator_dev *rdev);
+void regulator_sync_state(struct device *dev);
void devm_regulator_unregister(struct device *dev, struct regulator_dev *rdev);
int regulator_notifier_call_chain(struct regulator_dev *rdev,
--
2.27.0.rc0.183.gde8f92d652-goog
Powered by blists - more mailing lists