diff mbox series

[RFC] regulator: core: Allow for a base-load per client to come from dts

Message ID 20220721182622.RFC.1.I8a64b707169cfd73d9309c5eaf5d43b8bc4db988@changeid
State New
Headers show
Series [RFC] regulator: core: Allow for a base-load per client to come from dts | expand

Commit Message

Doug Anderson July 22, 2022, 1:26 a.m. UTC
Looking through the current users of regulator_set_load(), all but one
or two basically follow the pattern:

1. Get all the regulators.
2. Go through each regulator and call regulator_set_load() on it with
   some value that's sufficient to get the regulator to run in "HPM"
   mode.
3. Enable / disable the regulator as needed.

Specifically it should be noted that many devices don't really have a
need for the low power mode of regulators. Unfortunately the current
state of the world means that all drivers are cluttered with tables of
loads and extra cruft at init time to tell the regulator framework
about said loads.

Let's add a way to specify the "base load" of a regulator in the
device tree and hopefully make this a bit better.

There are lots of ways we could combine this "base load" with a load
that the driver requests. We could:
- Add them.
- Take the maximum (assume that the "base" is the min).
- Only use the "base" load if the driver didn't request one.

I have chosen the third option here. If the driver knows better then
it can override. Note that the driver can't override to "0". To do
that it would just disable the regulator.

The choice above means that we can effectively treat the "base" load
as the "normal usage" load. It's expected that with this load set we
could use the device normally. If a driver could go into a low power
state then it would only need to set a load when in that low power
state and it could effectively set its requested load back to 0
afterwards and the system would bounce back to the base
load. Presumably someone could also set the load higher than the base
before putting a device in "turbo mode".

This property is specified as a separate property from the supply
because it's expected that the load for a peripheral could be
specified in a SoC's dtsi file. As an example:

SoC.dtsi:
  nifty_phy: phy@1234 {
    vdda-phy-base-load = <21800>;
  };

Board.dts:
  &nifty_phy {
    vdda-phy-supply = <&pp1800_l4a>;
  };

NOTE: if we want to keep old binary dtb files working with new kernels
then we'd still have to keep existing crufty regulator_set_load() in
drivers, but hopefully we can stop adding new instances of this, and
perhaps eventually people will agree to deprecate old binary dtb files
and we can get rid of all the extra code.

Signed-off-by: Douglas Anderson <dianders@chromium.org>
---
I have only done incredibly light testing with this but I wanted to
get the idea out there. We'd obviously need to get device-tree folks
on board with this and adjust the dt-schema for it.

 drivers/regulator/core.c     | 65 ++++++++++++++++++++++++++++--------
 drivers/regulator/internal.h |  1 +
 2 files changed, 53 insertions(+), 13 deletions(-)

Comments

Doug Anderson July 26, 2022, 12:31 a.m. UTC | #1
Hi,

On Sat, Jul 23, 2022 at 12:11 PM Mark Brown <broonie@kernel.org> wrote:
>
> I'm familiar with the broad concept of regulators having the
> ability to operate in more efficient (but typically lower
> quality) modes, we do have the whole infrastructure for selecting
> mode based on load after all.  I'm not sure what you mean by
> devices having a *need* for it though?

All I meant by *need* was just whether it was practical for a device
to actively take advantage of the low power mode. A driver that
"needs" to know about loads is one that purposely puts the device into
a low power state so that it can re-configure the regulator. I was
arguing that most drivers don't need to do this.


> Does this need actually exist or is this just a we built it so we
> must use it thing?  There's a lot of power microoptimisation that
> goes on and sometimes it's hard to tell how much power is saved
> relative to the power consumed managing the feature.

It has always felt like a microoptimizatoin to me. I would love to see
evidence to the contrary, though.

Specifically we don't seem to use low power mode (LPM) anywhere on the
sc7180 trogdor laptops today. Maybe it would be possible to use it in
some cases, but nobody ever did the analysis. I haven't spent much
time analyzing downstream Qualcomm solutions, though.

I guess the answer is that if it's a micro-optimization then we should
be ripping more of this code out. ;-) That would go contrary to
Dmitry's request that all regulators have a load set on them...


> To the extent this is needed it does smell a bit like "these
> regulators should default to setting their load to 0 when
> disabled" or something more along those lines TBH, some of your
> previous comments suggested that the per device loads being
> specified in a lot of the driver are just random values intended
> to trigger a specific behaviour in the regulator but forced to be
> done in terms of a load due to the firmware inteface that's
> available on these platforms having an interface in those terms.

It's actually _not_ the firmware interface as far as I can tell, at
least for newer Qualcomm chips (those using RPMH). The firmware
interface seems to be for modes. See, for instance,
rpmh_regulator_vrm_set_load() which translates loads into modes before
passing to the firmware. Ironically, my memory tells me that Qualcomm
actually said that this turned out to be a problem in the past for
them, though, since some rails went to both the main apps processor
(AP) and the modem processor. Each could independently decide that low
power mode was fine but the total of both usages could bump you into
needing high power mode...

As far as whether the numbers are random values or mean something: I
don't know that. I'd personally have a bit of a hard time trusting
them, though. Mostly ending up at LPM when you need HPM seems like it
could be really hard to debug.

One point of evidence of these numbers being pointless and just noise
is code that seems to have made its way upstream to set a "disable
load". This hasn't done anything since 2018 when we landed commit
5451781dadf8 ("regulator: core: Only count load for enabled
consumers") but is still instructive of Qualcomm's thinking. Taking a
sampling of the loads in the tables in the DSI driver / phy, I see:
* Many specify 100 uA.
* Some seem to pick based on throwing a dart at a dartboard. 16 uA, 2
uA, 4 uA, 32 uA, etc.

I can't imagine any of these numbers having done anything useful even
prior to the 2018 commit. From `qcom-rpmh-regulator.c` the changeover
from LPM to HPM is at 10000 uA or 30000 uA, so even a load of 100 uA
is really just a rounding error. Yet despite their uselessness,
Engineers at Qualcomm have dutifully filled in all these numbers...

Looking at the loads passed to "active" devices, I see almost nothing
at all specifying a load that's less than 10mA. That means it's gonna
be really hard to use any of that class of regulators in LPM.

If we happen to be using an LDO that changes over at 30 mA, though,
these ones _could_ use LDO. I guess this is where the whole
"specifying in uA" makes sense? If you've got a regulator that changes
at 30mA and only one ~20mA consumer is active then it can stay in LPM.
When two ~20mA consumers are active then it needs to switch to HPM?
Having lots of consumers on a given rail is really common w/ Qulaocmm
setups. On trogdor, rail "L4A" is all of these:

vdd_qlink_lv:
vdd_qlink_lv_ck:
vdd_qusb_hs0_core:
vdd_ufs1_core:
vdda_mipi_csi0_0p9:
vdda_mipi_csi1_0p9:
vdda_mipi_csi2_0p9:
vdda_mipi_csi3_0p9:
vdda_mipi_dsi0_pll:
vdda_pll_cc_ebi01:
vdda_qrefs_0p9:
vdda_usb_ss_dp_core:

...and that's a regulator that can go up to 30mA in LDO mode... Of
course "MIPI DSI", by complete chance I'm sure, says its load is
_just_ over the 30 mA threshold...


> It didn't sound like they were actual specified/measured physical
> values for the on-SoC stuff, some of the panel drivers do look
> like they have plausuble numbers from datasheets though.

I just don't know. :(


> It might even make sense to have the regulator drivers implement
> the mode interface, that's possibly more useful to work with here
> even if it's not what the interfaces say, it does feel like
> practicaly how people are working with this stuff.  There's
> obviously issues there if anyone *does* work usefully with loads
> and how that integrates, though I think in this day and age the
> need for active management outside of super idle states is
> typically minimal.  As a first pass you could just disable the
> DRMS stuff if mode setting permission is enabled, I suspect
> that'd work fine in these cases.  Or just model the modes as a
> vote for "add X to the load" if they're set.

The current RPMH driver _does_ implement the mode interface. ;-)


I guess the above doesn't really give us a lot of good answers. :(

I guess the problem is that, like a lot of Qualcomm stuff, I still
don't have a good big picture despite having been working on Qualcomm
SoCs for years now. I do know that code keeps showing up to set
regulator loads all over the tree and, I presume, most maintainers
just take the code without questioning the need to set the load at
all.

Perhaps the low hanging fruit is to just accept that the current API
of setting the load is here to stay, even if it does seem mostly
pointless in many cases. I can submit a patch that adds the load to
the "bulk" API and at least it would clean up a bunch of stuff even if
it doesn't fundamentally overhaul the system...


-Doug
Mark Brown July 26, 2022, 12:12 p.m. UTC | #2
On Mon, Jul 25, 2022 at 05:31:34PM -0700, Doug Anderson wrote:
> On Sat, Jul 23, 2022 at 12:11 PM Mark Brown <broonie@kernel.org> wrote:

> I guess the answer is that if it's a micro-optimization then we should
> be ripping more of this code out. ;-) That would go contrary to
> Dmitry's request that all regulators have a load set on them...

I've not seen this request.

> > To the extent this is needed it does smell a bit like "these
> > regulators should default to setting their load to 0 when
> > disabled" or something more along those lines TBH, some of your
> > previous comments suggested that the per device loads being
> > specified in a lot of the driver are just random values intended
> > to trigger a specific behaviour in the regulator but forced to be
> > done in terms of a load due to the firmware inteface that's
> > available on these platforms having an interface in those terms.

> It's actually _not_ the firmware interface as far as I can tell, at
> least for newer Qualcomm chips (those using RPMH). The firmware
> interface seems to be for modes. See, for instance,
> rpmh_regulator_vrm_set_load() which translates loads into modes before
> passing to the firmware. Ironically, my memory tells me that Qualcomm
> actually said that this turned out to be a problem in the past for
> them, though, since some rails went to both the main apps processor
> (AP) and the modem processor. Each could independently decide that low
> power mode was fine but the total of both usages could bump you into
> needing high power mode...

Oh, that's just completely broken.  The set_load() support should be
removed and the drivers should be implementing get_optimum_mode(), it's
actually got an open coded version of it in there already.  That'll only
have snuck past due to being hidden behind a layer of abstraction that
shouldn't be there.

> consumers") but is still instructive of Qualcomm's thinking. Taking a
> sampling of the loads in the tables in the DSI driver / phy, I see:
> * Many specify 100 uA.
> * Some seem to pick based on throwing a dart at a dartboard. 16 uA, 2
> uA, 4 uA, 32 uA, etc.

The load for a PHY is going to be immaterial when you're driving a
panel.

> If we happen to be using an LDO that changes over at 30 mA, though,
> these ones _could_ use LDO. I guess this is where the whole
> "specifying in uA" makes sense? If you've got a regulator that changes
> at 30mA and only one ~20mA consumer is active then it can stay in LPM.
> When two ~20mA consumers are active then it needs to switch to HPM?
> Having lots of consumers on a given rail is really common w/ Qulaocmm
> setups. On trogdor, rail "L4A" is all of these:

We specify in uA and uV partly to ensure we never run into the bottom
limit of resolution and partly because for loads uA is a very relevant
number, as you've noticed it's where things tend to transition into
their lowest power modes and it's also commonly a relevant unit for the
draw from devices in idle modes.  Something like an audio CODEC doing
jack detection will probably be able to get into a low power mode for
example.

> I guess the above doesn't really give us a lot of good answers. :(

All the load setting that doesn't get passed to the firmware in the
regulator drivers should be removed and replaced with get_optimum_mode()
operations, this has no impact on the client drivers and removes some
open coding from the regulator drivers.  Load management in client
drivers that doesn't go below say 100uA is probably noise but it's also
fairly harmless.

> Perhaps the low hanging fruit is to just accept that the current API
> of setting the load is here to stay, even if it does seem mostly
> pointless in many cases. I can submit a patch that adds the load to
> the "bulk" API and at least it would clean up a bunch of stuff even if
> it doesn't fundamentally overhaul the system...

Adding loads to the bulk API would also be useful.
diff mbox series

Patch

diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 1e54a833f2cf..cc817f5efc77 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -94,7 +94,8 @@  static int regulator_balance_voltage(struct regulator_dev *rdev,
 				     suspend_state_t state);
 static struct regulator *create_regulator(struct regulator_dev *rdev,
 					  struct device *dev,
-					  const char *supply_name);
+					  const char *supply_name,
+					  int uA_base_load);
 static void destroy_regulator(struct regulator *regulator);
 static void _regulator_put(struct regulator *regulator);
 
@@ -417,6 +418,29 @@  static struct device_node *of_get_regulator(struct device *dev, const char *supp
 	return regnode;
 }
 
+static int of_get_regulator_base_load(struct device *dev, const char *supply)
+{
+	char prop_name[64]; /* 64 is max size of property name */
+	u32 pval = 0;
+
+	snprintf(prop_name, 64, "%s-base-load", supply);
+	of_property_read_u32(dev->of_node, prop_name, &pval);
+
+	dev_dbg(dev, "Looking up %s-base-load from device tree: %d\n",
+		supply, (int)pval);
+
+	return pval;
+}
+
+static int regulator_eff_load(const struct regulator *regulator)
+{
+	/*
+	 * If a load has been specified then use that; otherwise fall back
+	 * to the base load.
+	 */
+	return regulator->uA_load ? regulator->uA_load : regulator->uA_base_load;
+}
+
 /* Platform voltage constraint check */
 int regulator_check_voltage(struct regulator_dev *rdev,
 			    int *min_uV, int *max_uV)
@@ -774,7 +798,7 @@  static ssize_t requested_microamps_show(struct device *dev,
 	regulator_lock(rdev);
 	list_for_each_entry(regulator, &rdev->consumer_list, list) {
 		if (regulator->enable_count)
-			uA += regulator->uA_load;
+			uA += regulator_eff_load(regulator);
 	}
 	regulator_unlock(rdev);
 	return sprintf(buf, "%d\n", uA);
@@ -965,7 +989,7 @@  static int drms_uA_update(struct regulator_dev *rdev)
 	/* calc total requested load */
 	list_for_each_entry(sibling, &rdev->consumer_list, list) {
 		if (sibling->enable_count)
-			current_uA += sibling->uA_load;
+			current_uA += regulator_eff_load(sibling);
 	}
 
 	current_uA += rdev->constraints->system_load;
@@ -1604,13 +1628,14 @@  static int set_machine_constraints(struct regulator_dev *rdev)
  * set_supply - set regulator supply regulator
  * @rdev: regulator name
  * @supply_rdev: supply regulator name
+ * @uA_base_load: default load to apply when we need the supply
  *
  * Called by platform initialisation code to set the supply regulator for this
  * regulator. This ensures that a regulators supply will also be enabled by the
  * core if it's child is enabled.
  */
 static int set_supply(struct regulator_dev *rdev,
-		      struct regulator_dev *supply_rdev)
+		      struct regulator_dev *supply_rdev, int uA_base_load)
 {
 	int err;
 
@@ -1619,7 +1644,8 @@  static int set_supply(struct regulator_dev *rdev,
 	if (!try_module_get(supply_rdev->owner))
 		return -ENODEV;
 
-	rdev->supply = create_regulator(supply_rdev, &rdev->dev, "SUPPLY");
+	rdev->supply = create_regulator(supply_rdev, &rdev->dev, "SUPPLY",
+					uA_base_load);
 	if (rdev->supply == NULL) {
 		err = -ENOMEM;
 		return err;
@@ -1769,7 +1795,8 @@  static const struct file_operations constraint_flags_fops = {
 
 static struct regulator *create_regulator(struct regulator_dev *rdev,
 					  struct device *dev,
-					  const char *supply_name)
+					  const char *supply_name,
+					  int uA_base_load)
 {
 	struct regulator *regulator;
 	int err = 0;
@@ -1800,6 +1827,7 @@  static struct regulator *create_regulator(struct regulator_dev *rdev,
 
 	regulator->rdev = rdev;
 	regulator->supply_name = supply_name;
+	regulator->uA_base_load = uA_base_load;
 
 	regulator_lock(rdev);
 	list_add(&regulator->list, &rdev->consumer_list);
@@ -1825,6 +1853,8 @@  static struct regulator *create_regulator(struct regulator_dev *rdev,
 	} else {
 		debugfs_create_u32("uA_load", 0444, regulator->debugfs,
 				   &regulator->uA_load);
+		debugfs_create_u32("uA_base_load", 0444, regulator->debugfs,
+				   &regulator->uA_base_load);
 		debugfs_create_u32("min_uV", 0444, regulator->debugfs,
 				   &regulator->voltage[PM_SUSPEND_ON].min_uV);
 		debugfs_create_u32("max_uV", 0444, regulator->debugfs,
@@ -1968,6 +1998,7 @@  static int regulator_resolve_supply(struct regulator_dev *rdev)
 {
 	struct regulator_dev *r;
 	struct device *dev = rdev->dev.parent;
+	int uA_base_load;
 	int ret = 0;
 
 	/* No supply to resolve? */
@@ -1997,6 +2028,8 @@  static int regulator_resolve_supply(struct regulator_dev *rdev)
 		}
 	}
 
+	uA_base_load = of_get_regulator_base_load(dev, rdev->supply_name);
+
 	if (r == rdev) {
 		dev_err(dev, "Supply for %s (%s) resolved to itself\n",
 			rdev->desc->name, rdev->supply_name);
@@ -2043,7 +2076,7 @@  static int regulator_resolve_supply(struct regulator_dev *rdev)
 		goto out;
 	}
 
-	ret = set_supply(rdev, r);
+	ret = set_supply(rdev, r, uA_base_load);
 	if (ret < 0) {
 		regulator_unlock(rdev);
 		put_device(&r->dev);
@@ -2077,6 +2110,7 @@  struct regulator *_regulator_get(struct device *dev, const char *id,
 	struct regulator_dev *rdev;
 	struct regulator *regulator;
 	struct device_link *link;
+	int uA_base_load;
 	int ret;
 
 	if (get_type >= MAX_GET_TYPE) {
@@ -2128,6 +2162,8 @@  struct regulator *_regulator_get(struct device *dev, const char *id,
 		}
 	}
 
+	uA_base_load = of_get_regulator_base_load(dev, id);
+
 	if (rdev->exclusive) {
 		regulator = ERR_PTR(-EPERM);
 		put_device(&rdev->dev);
@@ -2163,7 +2199,7 @@  struct regulator *_regulator_get(struct device *dev, const char *id,
 		return regulator;
 	}
 
-	regulator = create_regulator(rdev, dev, id);
+	regulator = create_regulator(rdev, dev, id, uA_base_load);
 	if (regulator == NULL) {
 		regulator = ERR_PTR(-ENOMEM);
 		module_put(rdev->owner);
@@ -2737,7 +2773,8 @@  static int _regulator_handle_consumer_enable(struct regulator *regulator)
 	lockdep_assert_held_once(&rdev->mutex.base);
 
 	regulator->enable_count++;
-	if (regulator->uA_load && regulator->enable_count == 1)
+	if ((regulator->uA_load || regulator->uA_base_load) &&
+	    regulator->enable_count == 1)
 		return drms_uA_update(rdev);
 
 	return 0;
@@ -2763,7 +2800,8 @@  static int _regulator_handle_consumer_disable(struct regulator *regulator)
 	}
 
 	regulator->enable_count--;
-	if (regulator->uA_load && regulator->enable_count == 0)
+	if ((regulator->uA_load || regulator->uA_base_load) &&
+	    regulator->enable_count == 0)
 		return drms_uA_update(rdev);
 
 	return 0;
@@ -5850,6 +5888,7 @@  static void regulator_summary_show_subtree(struct seq_file *s,
 	struct regulator *consumer;
 	struct summary_data summary_data;
 	unsigned int opmode;
+	int uA_load;
 
 	if (!rdev)
 		return;
@@ -5893,11 +5932,11 @@  static void regulator_summary_show_subtree(struct seq_file *s,
 
 		switch (rdev->desc->type) {
 		case REGULATOR_VOLTAGE:
+			uA_load = regulator_eff_load(consumer);
 			seq_printf(s, "%3d %33dmA%c%5dmV %5dmV",
 				   consumer->enable_count,
-				   consumer->uA_load / 1000,
-				   consumer->uA_load && !consumer->enable_count ?
-				   '*' : ' ',
+				   uA_load / 1000,
+				   uA_load && !consumer->enable_count ? '*' : ' ',
 				   consumer->voltage[PM_SUSPEND_ON].min_uV / 1000,
 				   consumer->voltage[PM_SUSPEND_ON].max_uV / 1000);
 			break;
diff --git a/drivers/regulator/internal.h b/drivers/regulator/internal.h
index 1e9c71642143..8e20808bfbba 100644
--- a/drivers/regulator/internal.h
+++ b/drivers/regulator/internal.h
@@ -49,6 +49,7 @@  struct regulator {
 	unsigned int bypass:1;
 	unsigned int device_link:1;
 	int uA_load;
+	int uA_base_load;
 	unsigned int enable_count;
 	unsigned int deferred_disables;
 	struct regulator_voltage voltage[REGULATOR_STATES_NUM];