[RFC,1/2] regulator: core: Disable unused regulators with unknown status

Message ID 20231004-reg-smd-unused-v1-1-5d682493d555@kernkonzept.com
State New
Headers
Series regulator: qcom_smd: Disable unused regulators |

Commit Message

Stephan Gerhold Oct. 4, 2023, 2:17 p.m. UTC
  Some regulator drivers do not provide a way to check if the regulator is
currently enabled or not. That does not necessarily mean that the
regulator is always-on. For example, the regulators managed by the RPM
firmware on Qualcomm platforms can be either on or off during boot but
the initial state is not known. To sync the state the regulator should
get either explicitly enabled or explicitly disabled.

Enabling all regulators unconditionally is not safe, because we might
not know which voltages are safe. The devices supplied by those
regulators might also require a special power-up sequence where the
regulators are turned on in a certain order or with specific delay.

Disabling all unused regulators is safer. If the regulator is already
off it will just stay that way. If the regulator is on, disabling it
explicitly allows the firmware to turn it off for reduced power
consumption.

The regulator core already has functionality for disabling unused
regulators. However, at the moment it assumes that all regulators where
the .is_enabled() callback fails are actually off. There is no way to
return a special value for the "unknown" state to explicitly ask for
disabling those regulators.

Some drivers (e.g. qcom-rpmh-regulator.c) return -EINVAL for the case
where the initial status is unknown. Use that return code to assume the
initial status is unknown and try to explicitly disable the regulator
in that case.

Signed-off-by: Stephan Gerhold <stephan.gerhold@kernkonzept.com>
---
Instead of -EINVAL we could also use a different return code to indicate
the initial status is unknown. Or maybe there is some other option that
would be easier? This is working for me but I'm sending it as RFC to get
more feedback. :)
---
 drivers/regulator/core.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)
  

Comments

Konrad Dybcio Oct. 6, 2023, 9:11 p.m. UTC | #1
On 4.10.2023 16:17, Stephan Gerhold wrote:
> Some regulator drivers do not provide a way to check if the regulator is
> currently enabled or not. That does not necessarily mean that the
> regulator is always-on. For example, the regulators managed by the RPM
> firmware on Qualcomm platforms can be either on or off during boot but
> the initial state is not known. To sync the state the regulator should
> get either explicitly enabled or explicitly disabled.
> 
> Enabling all regulators unconditionally is not safe, because we might
> not know which voltages are safe. The devices supplied by those
> regulators might also require a special power-up sequence where the
> regulators are turned on in a certain order or with specific delay.
> 
> Disabling all unused regulators is safer. If the regulator is already
> off it will just stay that way. If the regulator is on, disabling it
> explicitly allows the firmware to turn it off for reduced power
> consumption.
> 
> The regulator core already has functionality for disabling unused
> regulators. However, at the moment it assumes that all regulators where
> the .is_enabled() callback fails are actually off. There is no way to
> return a special value for the "unknown" state to explicitly ask for
> disabling those regulators.
> 
> Some drivers (e.g. qcom-rpmh-regulator.c) return -EINVAL for the case
> where the initial status is unknown. Use that return code to assume the
> initial status is unknown and try to explicitly disable the regulator
> in that case.
> 
> Signed-off-by: Stephan Gerhold <stephan.gerhold@kernkonzept.com>
> ---
> Instead of -EINVAL we could also use a different return code to indicate
> the initial status is unknown. Or maybe there is some other option that
> would be easier? This is working for me but I'm sending it as RFC to get
> more feedback. :)
-EOPNOTSUPP for "doesn't support getting is_enabled state"?

I think this looks really good.. And will definitely help finding
power hogs!

At the cost of breaking booting with broken DTs. But as the name by
which I referred to them suggests, this was never really destined to
work..

Konrad
  
Stephan Gerhold Oct. 9, 2023, 8:21 p.m. UTC | #2
On Fri, Oct 06, 2023 at 11:11:48PM +0200, Konrad Dybcio wrote:
> On 4.10.2023 16:17, Stephan Gerhold wrote:
> > Some regulator drivers do not provide a way to check if the regulator is
> > currently enabled or not. That does not necessarily mean that the
> > regulator is always-on. For example, the regulators managed by the RPM
> > firmware on Qualcomm platforms can be either on or off during boot but
> > the initial state is not known. To sync the state the regulator should
> > get either explicitly enabled or explicitly disabled.
> > 
> > Enabling all regulators unconditionally is not safe, because we might
> > not know which voltages are safe. The devices supplied by those
> > regulators might also require a special power-up sequence where the
> > regulators are turned on in a certain order or with specific delay.
> > 
> > Disabling all unused regulators is safer. If the regulator is already
> > off it will just stay that way. If the regulator is on, disabling it
> > explicitly allows the firmware to turn it off for reduced power
> > consumption.
> > 
> > The regulator core already has functionality for disabling unused
> > regulators. However, at the moment it assumes that all regulators where
> > the .is_enabled() callback fails are actually off. There is no way to
> > return a special value for the "unknown" state to explicitly ask for
> > disabling those regulators.
> > 
> > Some drivers (e.g. qcom-rpmh-regulator.c) return -EINVAL for the case
> > where the initial status is unknown. Use that return code to assume the
> > initial status is unknown and try to explicitly disable the regulator
> > in that case.
> > 
> > Signed-off-by: Stephan Gerhold <stephan.gerhold@kernkonzept.com>
> > ---
> > Instead of -EINVAL we could also use a different return code to indicate
> > the initial status is unknown. Or maybe there is some other option that
> > would be easier? This is working for me but I'm sending it as RFC to get
> > more feedback. :)
>
> -EOPNOTSUPP for "doesn't support getting is_enabled state"?
> 

The way it is implemented right now the Qualcomm SMD RPM regulator does
actually support getting the .is_enabled() state. It is only unable to
determine the initial state during boot. Once the regulator has been
enabled by some consumer for the first time the .is_enabled() callback
starts returning the expected results.

Typically -EOPNOTSUPP is used when the driver callback (or similar) is
not implemented at all. I'm not sure if using -EOPNOTSUPP for the
"temporarily unable to determine state" purpose would be misleading.

Thanks,
Stephan
  
Konrad Dybcio Oct. 10, 2023, 12:14 p.m. UTC | #3
On 10/9/23 22:21, Stephan Gerhold wrote:
> On Fri, Oct 06, 2023 at 11:11:48PM +0200, Konrad Dybcio wrote:
>> On 4.10.2023 16:17, Stephan Gerhold wrote:
>>> Some regulator drivers do not provide a way to check if the regulator is
>>> currently enabled or not. That does not necessarily mean that the
>>> regulator is always-on. For example, the regulators managed by the RPM
>>> firmware on Qualcomm platforms can be either on or off during boot but
>>> the initial state is not known. To sync the state the regulator should
>>> get either explicitly enabled or explicitly disabled.
>>>
>>> Enabling all regulators unconditionally is not safe, because we might
>>> not know which voltages are safe. The devices supplied by those
>>> regulators might also require a special power-up sequence where the
>>> regulators are turned on in a certain order or with specific delay.
>>>
>>> Disabling all unused regulators is safer. If the regulator is already
>>> off it will just stay that way. If the regulator is on, disabling it
>>> explicitly allows the firmware to turn it off for reduced power
>>> consumption.
>>>
>>> The regulator core already has functionality for disabling unused
>>> regulators. However, at the moment it assumes that all regulators where
>>> the .is_enabled() callback fails are actually off. There is no way to
>>> return a special value for the "unknown" state to explicitly ask for
>>> disabling those regulators.
>>>
>>> Some drivers (e.g. qcom-rpmh-regulator.c) return -EINVAL for the case
>>> where the initial status is unknown. Use that return code to assume the
>>> initial status is unknown and try to explicitly disable the regulator
>>> in that case.
>>>
>>> Signed-off-by: Stephan Gerhold <stephan.gerhold@kernkonzept.com>
>>> ---
>>> Instead of -EINVAL we could also use a different return code to indicate
>>> the initial status is unknown. Or maybe there is some other option that
>>> would be easier? This is working for me but I'm sending it as RFC to get
>>> more feedback. :)
>>
>> -EOPNOTSUPP for "doesn't support getting is_enabled state"?
>>
> 
> The way it is implemented right now the Qualcomm SMD RPM regulator does
> actually support getting the .is_enabled() state. It is only unable to
> determine the initial state during boot. Once the regulator has been
> enabled by some consumer for the first time the .is_enabled() callback
> starts returning the expected results.
> 
> Typically -EOPNOTSUPP is used when the driver callback (or similar) is
> not implemented at all. I'm not sure if using -EOPNOTSUPP for the
> "temporarily unable to determine state" purpose would be misleading.
I'd say EOPNOTSUPP is fair here because calling is_enabled in that 
context is not supported, but I guess it's up to Mark.

Konrad
  
Mark Brown Oct. 23, 2023, 12:09 p.m. UTC | #4
On Wed, Oct 04, 2023 at 04:17:17PM +0200, Stephan Gerhold wrote:

> Instead of -EINVAL we could also use a different return code to indicate
> the initial status is unknown. Or maybe there is some other option that
> would be easier? This is working for me but I'm sending it as RFC to get
> more feedback. :)

The more normal thing here would be -EBUSY I think - -EINVAL kind of
indicates that the operation will never work while in reality it could
possibly work in future.  Though for the RPMH it's not really the case
that it ever supports readback, what it does is have it's own reference
counting in the driver.  Rather than doing this we should probably have
logic in the core which sees that the driver has a write operation but
no read operation and implements appropriate behaviour.
  
Bjorn Andersson Oct. 23, 2023, 11:11 p.m. UTC | #5
On Mon, Oct 23, 2023 at 01:09:11PM +0100, Mark Brown wrote:
> On Wed, Oct 04, 2023 at 04:17:17PM +0200, Stephan Gerhold wrote:
> 
> > Instead of -EINVAL we could also use a different return code to indicate
> > the initial status is unknown. Or maybe there is some other option that
> > would be easier? This is working for me but I'm sending it as RFC to get
> > more feedback. :)
> 
> The more normal thing here would be -EBUSY I think - -EINVAL kind of
> indicates that the operation will never work while in reality it could
> possibly work in future.  Though for the RPMH it's not really the case
> that it ever supports readback, what it does is have it's own reference
> counting in the driver.  Rather than doing this we should probably have
> logic in the core which sees that the driver has a write operation but
> no read operation and implements appropriate behaviour.

I like the suggestion to not implement is_enabled, and handle that in
the core instead, for all three generations of our rpm-based regulators.

Regards,
Bjorn
  
Stephan Gerhold Oct. 24, 2023, 8:57 a.m. UTC | #6
On Mon, Oct 23, 2023 at 01:09:11PM +0100, Mark Brown wrote:
> On Wed, Oct 04, 2023 at 04:17:17PM +0200, Stephan Gerhold wrote:
> 
> > Instead of -EINVAL we could also use a different return code to indicate
> > the initial status is unknown. Or maybe there is some other option that
> > would be easier? This is working for me but I'm sending it as RFC to get
> > more feedback. :)
> 
> The more normal thing here would be -EBUSY I think - -EINVAL kind of
> indicates that the operation will never work while in reality it could
> possibly work in future.  Though for the RPMH it's not really the case
> that it ever supports readback, what it does is have it's own reference
> counting in the driver.  Rather than doing this we should probably have
> logic in the core which sees that the driver has a write operation but
> no read operation and implements appropriate behaviour.

Yep, I agree that it would be nicer to handle this case in the core,
rather than duplicating the logic in all the RPM-related drivers.

I think it does not change much for this patch, though. Even when
implemented in the core we still need to represent this situation
somehow for regulator_is_enabled(). Simply returning 0 (disabled) or
1 (enabled) would be wrong. Do you think returning -EBUSY would be
appropriate for that?

The second challenge I see on a quick look is that both
qcom_smd-regulator.c and qcom-rpmh-regulator.c use their reference
counter internally in other function (e.g. to decide if a voltage change
should be sent, see "vreg->enabled" checks). I think we would also need
to add some rdev_is_enabled() function that would expose the core
reference counter to the driver?

Tracking the enable state in the driver (the way it is right now) is not
that much code, so I'm not entirely sure if we might actually end up
with more code/complexity when moving this to the core.

Thanks,
  
Mark Brown Oct. 25, 2023, 5:49 p.m. UTC | #7
On Tue, Oct 24, 2023 at 10:57:38AM +0200, Stephan Gerhold wrote:

> I think it does not change much for this patch, though. Even when
> implemented in the core we still need to represent this situation
> somehow for regulator_is_enabled(). Simply returning 0 (disabled) or
> 1 (enabled) would be wrong. Do you think returning -EBUSY would be
> appropriate for that?

In these cases where we simply can't read the expectation is that we'll
always be using the logical state - one way of thinking about it is that
the operation is mostly a bootstrapping helper to figure out what the
initial state is.  A quick survey of users suggest they'll pretty much
all be buggy if we start returning errors, and I frankly even if all the
current users were fixed I'd expect that to continue to be a common
error.  I suppose that the effect of ignoring the possibility of error
is like the current behaviour though.

> The second challenge I see on a quick look is that both
> qcom_smd-regulator.c and qcom-rpmh-regulator.c use their reference
> counter internally in other function (e.g. to decide if a voltage change
> should be sent, see "vreg->enabled" checks). I think we would also need
> to add some rdev_is_enabled() function that would expose the core
> reference counter to the driver?

> Tracking the enable state in the driver (the way it is right now) is not
> that much code, so I'm not entirely sure if we might actually end up
> with more code/complexity when moving this to the core.

We have to do the reference count in the core anyway since it's a
reference count not just a simple on/off so it doesn't really cost us
anything to make it available to drivers.
  
Stephan Gerhold Oct. 25, 2023, 7:51 p.m. UTC | #8
On Wed, Oct 25, 2023 at 06:49:47PM +0100, Mark Brown wrote:
> On Tue, Oct 24, 2023 at 10:57:38AM +0200, Stephan Gerhold wrote:
> 
> > I think it does not change much for this patch, though. Even when
> > implemented in the core we still need to represent this situation
> > somehow for regulator_is_enabled(). Simply returning 0 (disabled) or
> > 1 (enabled) would be wrong. Do you think returning -EBUSY would be
> > appropriate for that?
> 
> In these cases where we simply can't read the expectation is that we'll
> always be using the logical state - one way of thinking about it is that
> the operation is mostly a bootstrapping helper to figure out what the
> initial state is.  A quick survey of users suggest they'll pretty much
> all be buggy if we start returning errors, and I frankly even if all the
> current users were fixed I'd expect that to continue to be a common
> error.  I suppose that the effect of ignoring the possibility of error
> is like the current behaviour though.
> 

regulator_is_enabled() already returns error codes in various cases,
e.g. regulator_is_enabled_regmap() returns the original error code from
the regmap_read() call if that fails. So if users ignore that and
interpret the value as logical one they either don't care (which is
probably fine in some cases?) or already use it wrong. Or am I missing
something?

> > qcom_smd-regulator.c and qcom-rpmh-regulator.c use their reference
> > counter internally in other function (e.g. to decide if a voltage change
> > should be sent, see "vreg->enabled" checks). I think we would also need
> > to add some rdev_is_enabled() function that would expose the core
> > reference counter to the driver?
>
> > Tracking the enable state in the driver (the way it is right now) is not
> > that much code, so I'm not entirely sure if we might actually end up
> > with more code/complexity when moving this to the core.
>
> We have to do the reference count in the core anyway since it's a
> reference count not just a simple on/off so it doesn't really cost us
> anything to make it available to drivers.

I assume you're referring to "use_count" as the reference counter?

On a closer look I think it cannot be used as-is for my purpose:

 1. With "regulator-boot-on", set_machine_constraints() explicitly
    enables the regulator, but doesn't increase the use_count.
    In that case we should return true in ->is_enabled(). I'm not sure
    how we would know, just based on use_count = 0.

 2. To cleanup unused regulators that may or may not be enabled we need
    to know if the regulator was ever explicitly enabled/disabled before.
    It's pointless to send a disable request for a regulator that we
    already disabled explicitly before (after a enable -> disable cycle).
    use_count just tells us if there is currently a user, but not if
    there was one before.

I think I would literally need to move the existing "enabled" field from
the RPM regulator drivers to the core and manage it similarly there
based on ->enable() and ->disable() calls. Which would be a (slight)
overhead for all regulators rather than being isolated for the few RPM
regulator drivers.

Thanks,
Stephan
  
Mark Brown Oct. 25, 2023, 8:07 p.m. UTC | #9
On Wed, Oct 25, 2023 at 09:51:51PM +0200, Stephan Gerhold wrote:
> On Wed, Oct 25, 2023 at 06:49:47PM +0100, Mark Brown wrote:

> > In these cases where we simply can't read the expectation is that we'll
> > always be using the logical state - one way of thinking about it is that
> > the operation is mostly a bootstrapping helper to figure out what the
> > initial state is.  A quick survey of users suggest they'll pretty much
> > all be buggy if we start returning errors, and I frankly even if all the
> > current users were fixed I'd expect that to continue to be a common
> > error.  I suppose that the effect of ignoring the possibility of error
> > is like the current behaviour though.

> regulator_is_enabled() already returns error codes in various cases,
> e.g. regulator_is_enabled_regmap() returns the original error code from
> the regmap_read() call if that fails. So if users ignore that and
> interpret the value as logical one they either don't care (which is
> probably fine in some cases?) or already use it wrong. Or am I missing
> something?

That's broadly what I just indicated.  Expecting anybody to do anything
useful with an error report is probably optimistic, but it's probably
going to give the same behaviour as we have currently so it's probably
fine.

> > We have to do the reference count in the core anyway since it's a
> > reference count not just a simple on/off so it doesn't really cost us
> > anything to make it available to drivers.

> I assume you're referring to "use_count" as the reference counter?

Yes.

> On a closer look I think it cannot be used as-is for my purpose:

>  1. With "regulator-boot-on", set_machine_constraints() explicitly
>     enables the regulator, but doesn't increase the use_count.
>     In that case we should return true in ->is_enabled(). I'm not sure
>     how we would know, just based on use_count = 0.

OK, so use_count plus other information we also already have to hand.
Or OTOH it's not that much overhead to track the enable state explicitly
for hardware without readback as you're suggesting below if it ends up
being too much hassle.

>  2. To cleanup unused regulators that may or may not be enabled we need
>     to know if the regulator was ever explicitly enabled/disabled before.
>     It's pointless to send a disable request for a regulator that we
>     already disabled explicitly before (after a enable -> disable cycle).
>     use_count just tells us if there is currently a user, but not if
>     there was one before.

It's pointless, but equally well it's not huge overhead.

> I think I would literally need to move the existing "enabled" field from
> the RPM regulator drivers to the core and manage it similarly there
> based on ->enable() and ->disable() calls. Which would be a (slight)
> overhead for all regulators rather than being isolated for the few RPM
> regulator drivers.

These aren't the only regulators with this limitation, we've also got
similar open coding for GPIO controlled regulators like the fixed
regualtor for example.
  

Patch

diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 3137e40fcd3e..182e3727651a 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -6207,8 +6207,13 @@  static int regulator_late_cleanup(struct device *dev, void *data)
 	if (rdev->use_count)
 		goto unlock;
 
-	/* If reading the status failed, assume that it's off. */
-	if (_regulator_is_enabled(rdev) <= 0)
+	/*
+	 * If reading the status failed, assume that it's off.
+	 * If the current status is unknown (-EINVAL), assume that the
+	 * regulator might be on and try to explicitly disable it.
+	 */
+	ret = _regulator_is_enabled(rdev);
+	if (ret <= 0 && ret != -EINVAL)
 		goto unlock;
 
 	if (have_full_constraints()) {