summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorMiquel Raynal <[email protected]>2025-04-25 08:49:32 +0200
committerFabio Estevam <[email protected]>2025-04-28 10:41:19 -0300
commit9086b64ca07166b1e72b29ad04e3fe2a483c5454 (patch)
treec0831e6897098ae547796fe77beb0666d94743b4 /include
parentddd6d6f8dbd940edf3b62e1b37bc86734aec236c (diff)
power-domain: Add support for refcounting (again)
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 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. Some drivers implement their own mechanism for that, but it is probably best to add this feature in the uclass and share the common code across drivers. In order to avoid breaking existing drivers, refcounting is only enabled if the number of subdomains a device node supports is explicitly set in the probe function. ->xlate() callbacks will return the power domain ID which is then being used as the array index to reach the correct refcounter. As we do not want to break existing users while stile getting interesting error codes, the implementation is split between: - a low-level helper reporting error codes if the requested transition could not be operated, - a higher-level helper ignoring the "non error" codes, like EALREADY and EBUSY. CI tests using power domains are slightly updated to make sure the count of on/off calls is even and the results match what we *now* expect. They are also extended to test the low-level functions. Signed-off-by: Miquel Raynal <[email protected]>
Diffstat (limited to 'include')
-rw-r--r--include/power-domain.h69
1 files changed, 61 insertions, 8 deletions
diff --git a/include/power-domain.h b/include/power-domain.h
index 18525073e5e..7fd2c5e365b 100644
--- a/include/power-domain.h
+++ b/include/power-domain.h
@@ -66,6 +66,15 @@ struct power_domain {
};
/**
+ * struct power_domain_plat - Per device accessible structure
+ * @subdomains: Number of subdomains covered by this device, required
+ * for refcounting
+ */
+struct power_domain_plat {
+ int subdomains;
+};
+
+/**
* power_domain_get - Get/request the power domain for a device.
*
* This looks up and requests a power domain. Each device is assumed to have
@@ -147,38 +156,82 @@ static inline int power_domain_free(struct power_domain *power_domain)
#endif
/**
- * power_domain_on - Enable power to a power domain.
+ * power_domain_on_lowlevel - Enable power to a power domain (with refcounting)
*
* @power_domain: A power domain struct that was previously successfully
* requested by power_domain_get().
- * Return: 0 if OK, or a negative error code.
+ * Return: 0 if the transition has been performed correctly,
+ * -EALREADY if the domain is already on,
+ * a negative error code otherwise.
*/
#if CONFIG_IS_ENABLED(POWER_DOMAIN)
-int power_domain_on(struct power_domain *power_domain);
+int power_domain_on_lowlevel(struct power_domain *power_domain);
#else
-static inline int power_domain_on(struct power_domain *power_domain)
+static inline int power_domain_on_lowlevel(struct power_domain *power_domain)
{
return -ENOSYS;
}
#endif
/**
- * power_domain_off - Disable power to a power domain.
+ * power_domain_on - Enable power to a power domain (ignores the actual state
+ * of the power domain)
*
* @power_domain: A power domain struct that was previously successfully
* requested by power_domain_get().
- * Return: 0 if OK, or a negative error code.
+ * Return: a negative error code upon error during the transition, 0 otherwise.
+ */
+static inline int power_domain_on(struct power_domain *power_domain)
+{
+ int ret;
+
+ ret = power_domain_on_lowlevel(power_domain);
+ if (ret == -EALREADY)
+ ret = 0;
+
+ return ret;
+}
+
+/**
+ * power_domain_off_lowlevel - Disable power to a power domain (with refcounting)
+ *
+ * @power_domain: A power domain struct that was previously successfully
+ * requested by power_domain_get().
+ * Return: 0 if the transition has been performed correctly,
+ * -EALREADY if the domain is already off,
+ * -EBUSY if another device is keeping the domain on (but the refcounter
+ * is decremented),
+ * a negative error code otherwise.
*/
#if CONFIG_IS_ENABLED(POWER_DOMAIN)
-int power_domain_off(struct power_domain *power_domain);
+int power_domain_off_lowlevel(struct power_domain *power_domain);
#else
-static inline int power_domain_off(struct power_domain *power_domain)
+static inline int power_domain_off_lowlevel(struct power_domain *power_domain)
{
return -ENOSYS;
}
#endif
/**
+ * power_domain_off - Disable power to a power domain (ignores the actual state
+ * of the power domain)
+ *
+ * @power_domain: A power domain struct that was previously successfully
+ * requested by power_domain_get().
+ * Return: a negative error code upon error during the transition, 0 otherwise.
+ */
+static inline int power_domain_off(struct power_domain *power_domain)
+{
+ int ret;
+
+ ret = power_domain_off_lowlevel(power_domain);
+ if (ret == -EALREADY || ret == -EBUSY)
+ ret = 0;
+
+ return ret;
+}
+
+/**
* dev_power_domain_on - Enable power domains for a device .
*
* @dev: The client device.