It is very surprising that such an uclass, specifically designed to
handle resources that may be shared by different devices, is not keeping
the count of the number of times a power domain has been
enabled/disabled to avoid shutting it down unexpectedly or disabling it
several times.

Doing this causes troubles on eg. i.MX8MP because disabling power
domains can be done in a recursive loops were the same power domain
disabled up to 4 times in a row. PGCs seem to have tight FSM internal
timings to respect and it is easy to produce a race condition that puts
the power domains in an unstable state, leading to ADB400 errors and
later crashes in Linux.

Signed-off-by: Miquel Raynal <[email protected]>
---
 drivers/power/domain/power-domain-uclass.c | 37 ++++++++++++++++++++++++++++--
 include/power-domain.h                     |  4 ++--
 2 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/drivers/power/domain/power-domain-uclass.c 
b/drivers/power/domain/power-domain-uclass.c
index 
938bd8cbc9ffd1ba2109d702f886b6a99288d063..55a3d95d83c5aaac31acbd877199c87f8f6fb880
 100644
--- a/drivers/power/domain/power-domain-uclass.c
+++ b/drivers/power/domain/power-domain-uclass.c
@@ -12,6 +12,10 @@
 #include <power-domain-uclass.h>
 #include <dm/device-internal.h>
 
+struct power_domain_priv {
+       int on_count;
+};
+
 static inline struct power_domain_ops *power_domain_dev_ops(struct udevice 
*dev)
 {
        return (struct power_domain_ops *)dev->driver->ops;
@@ -110,19 +114,47 @@ int power_domain_free(struct power_domain *power_domain)
 int power_domain_on(struct power_domain *power_domain)
 {
        struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev);
+       struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev);
+       int ret;
 
        debug("%s(power_domain=%p)\n", __func__, power_domain);
 
-       return ops->on ? ops->on(power_domain) : 0;
+       if (priv->on_count++ > 0)
+               return 0;
+
+       ret = ops->on ? ops->on(power_domain) : 0;
+       if (ret) {
+               priv->on_count--;
+               return ret;
+       }
+
+       return 0;
 }
 
 int power_domain_off(struct power_domain *power_domain)
 {
        struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev);
+       struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev);
+       int ret;
 
        debug("%s(power_domain=%p)\n", __func__, power_domain);
 
-       return ops->off ? ops->off(power_domain) : 0;
+       if (priv->on_count <= 0) {
+               debug("Power domain %s already off.\n", 
power_domain->dev->name);
+               priv->on_count--;
+               return 0;
+       }
+
+       if (priv->on_count-- > 1)
+               return 0;
+
+       ret = ops->off ? ops->off(power_domain) : 0;
+       if (ret) {
+               priv->on_count++;
+               return ret;
+       }
+
+       return 0;
 }
 
 #if CONFIG_IS_ENABLED(OF_REAL)
@@ -180,4 +212,5 @@ int dev_power_domain_off(struct udevice *dev)
 UCLASS_DRIVER(power_domain) = {
        .id             = UCLASS_POWER_DOMAIN,
        .name           = "power_domain",
+       .per_device_auto = sizeof(struct power_domain_priv),
 };
diff --git a/include/power-domain.h b/include/power-domain.h
index 
18525073e5e3534fcbac6fae4e18462f29a4dc49..0266fc2f864ef3645d2a8b3c6fa66fdbd868799c
 100644
--- a/include/power-domain.h
+++ b/include/power-domain.h
@@ -147,7 +147,7 @@ static inline int power_domain_free(struct power_domain 
*power_domain)
 #endif
 
 /**
- * power_domain_on - Enable power to a power domain.
+ * power_domain_on - Enable power to a power domain (with refcounting)
  *
  * @power_domain:      A power domain struct that was previously successfully
  *             requested by power_domain_get().
@@ -163,7 +163,7 @@ static inline int power_domain_on(struct power_domain 
*power_domain)
 #endif
 
 /**
- * power_domain_off - Disable power to a power domain.
+ * power_domain_off - Disable power to a power domain (with refcounting)
  *
  * @power_domain:      A power domain struct that was previously successfully
  *             requested by power_domain_get().

-- 
2.47.0

Reply via email to