---
arch/sandbox/include/asm/power-domain.h | 2 +
drivers/firmware/scmi/sandbox-scmi_devices.c | 1 +
drivers/power/domain/power-domain-uclass.c | 90 ++++++++++++++++++++++--
drivers/power/domain/sandbox-power-domain-test.c | 15 ++++
drivers/power/domain/sandbox-power-domain.c | 4 ++
include/power-domain.h | 69 +++++++++++++++---
test/dm/power-domain.c | 11 ++-
7 files changed, 177 insertions(+), 15 deletions(-)
diff --git a/arch/sandbox/include/asm/power-domain.h
b/arch/sandbox/include/asm/power-domain.h
index
4d5e861dbce2b6434ac9bcffe5fc8f704d32e62d..3b0717f8fa06f1c0493fe6ee758e2e72ff77141e
100644
--- a/arch/sandbox/include/asm/power-domain.h
+++ b/arch/sandbox/include/asm/power-domain.h
@@ -13,6 +13,8 @@ int sandbox_power_domain_query(struct udevice *dev, unsigned
long id);
int sandbox_power_domain_test_get(struct udevice *dev);
int sandbox_power_domain_test_on(struct udevice *dev);
int sandbox_power_domain_test_off(struct udevice *dev);
+int sandbox_power_domain_test_on_ll(struct udevice *dev);
+int sandbox_power_domain_test_off_ll(struct udevice *dev);
int sandbox_power_domain_test_free(struct udevice *dev);
#endif
diff --git a/drivers/firmware/scmi/sandbox-scmi_devices.c
b/drivers/firmware/scmi/sandbox-scmi_devices.c
index
96c2922b067e2886b3fa963bcd7e396f4569a569..9f253b0fd40f703a5ec11d34c197423d27ad8b01
100644
--- a/drivers/firmware/scmi/sandbox-scmi_devices.c
+++ b/drivers/firmware/scmi/sandbox-scmi_devices.c
@@ -163,4 +163,5 @@ U_BOOT_DRIVER(sandbox_scmi_devices) = {
.priv_auto = sizeof(struct sandbox_scmi_device_priv),
.remove = sandbox_scmi_devices_remove,
.probe = sandbox_scmi_devices_probe,
+ .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF,
};
diff --git a/drivers/power/domain/power-domain-uclass.c
b/drivers/power/domain/power-domain-uclass.c
index
938bd8cbc9ffd1ba2109d702f886b6a99288d063..d9fa8ad4bd2126ea564fd5be8124035946dbd432
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;
@@ -107,22 +111,67 @@ int power_domain_free(struct power_domain *power_domain)
return ops->rfree ? ops->rfree(power_domain) : 0;
}
-int power_domain_on(struct power_domain *power_domain)
+int power_domain_on_lowlevel(struct power_domain *power_domain)
{
+ struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev);
+ struct power_domain_plat *plat = dev_get_uclass_plat(power_domain->dev);
struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev);
+ int *on_count = plat->subdomains ? &priv->on_count[power_domain->id] :
NULL;
+ int ret;
- debug("%s(power_domain=%p)\n", __func__, power_domain);
+ /* Refcounting is not enabled on all drivers by default */
+ if (on_count) {
+ debug("Enable power domain %s.%ld: %d -> %d (%s)\n",
+ power_domain->dev->name, power_domain->id, *on_count,
*on_count + 1,
+ (((*on_count + 1) > 1) ? "EALREADY" : "todo"));
- return ops->on ? ops->on(power_domain) : 0;
+ (*on_count)++;
+ if (*on_count > 1)
+ return -EALREADY;
+ }
+
+ ret = ops->on ? ops->on(power_domain) : 0;
+ if (ret) {
+ if (on_count)
+ (*on_count)--;
+ return ret;
+ }
+
+ return 0;
}
-int power_domain_off(struct power_domain *power_domain)
+int power_domain_off_lowlevel(struct power_domain *power_domain)
{
+ struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev);
+ struct power_domain_plat *plat = dev_get_uclass_plat(power_domain->dev);
struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev);
+ int *on_count = plat->subdomains ? &priv->on_count[power_domain->id] :
NULL;
+ int ret;
- debug("%s(power_domain=%p)\n", __func__, power_domain);
+ /* Refcounting is not enabled on all drivers by default */
+ if (on_count) {
+ debug("Disable power domain %s.%ld: %d -> %d (%s%s)\n",
+ power_domain->dev->name, power_domain->id, *on_count,
*on_count - 1,
+ (((*on_count) <= 0) ? "EALREADY" : ""),
+ (((*on_count - 1) > 0) ? "BUSY" : "todo"));
- return ops->off ? ops->off(power_domain) : 0;
+ if (*on_count <= 0)
+ return -EALREADY;
+
+ (*on_count)--;
+ if (*on_count > 0)
+ return -EBUSY;
+ }
+
+ ret = ops->off ? ops->off(power_domain) : 0;
+ if (ret) {
+ if (on_count)
+ (*on_count)++;
+
+ return ret;
+ }
+
+ return 0;
}
#if CONFIG_IS_ENABLED(OF_REAL)
@@ -177,7 +226,36 @@ int dev_power_domain_off(struct udevice *dev)
}
#endif /* OF_REAL */
+static int power_domain_post_probe(struct udevice *dev)
+{
+ struct power_domain_priv *priv = dev_get_uclass_priv(dev);
+ struct power_domain_plat *plat = dev_get_uclass_plat(dev);
+
+ if (plat->subdomains) {
+ priv->on_count = calloc(sizeof(int), plat->subdomains);
+ if (!priv->on_count)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int power_domain_pre_remove(struct udevice *dev)
+{
+ struct power_domain_priv *priv = dev_get_uclass_priv(dev);
+ struct power_domain_plat *plat = dev_get_uclass_plat(dev);
+
+ if (plat->subdomains)
+ free(priv->on_count);
+
+ return 0;
+}
+
UCLASS_DRIVER(power_domain) = {
.id = UCLASS_POWER_DOMAIN,
.name = "power_domain",
+ .post_probe = power_domain_post_probe,
+ .pre_remove = power_domain_pre_remove,
+ .per_device_auto = sizeof(struct power_domain_priv),
+ .per_device_plat_auto = sizeof(struct power_domain_plat),
};
diff --git a/drivers/power/domain/sandbox-power-domain-test.c
b/drivers/power/domain/sandbox-power-domain-test.c
index
08c15ef342b3dd3ce01807ee59b7e97337f7dde5..df063001f517cae92df6b04a213c81b0d5584d18
100644
--- a/drivers/power/domain/sandbox-power-domain-test.c
+++ b/drivers/power/domain/sandbox-power-domain-test.c
@@ -34,6 +34,20 @@ int sandbox_power_domain_test_off(struct udevice *dev)
return power_domain_off(&sbrt->pd);
}
+int sandbox_power_domain_test_on_ll(struct udevice *dev)
+{
+ struct sandbox_power_domain_test *sbrt = dev_get_priv(dev);
+
+ return power_domain_on_lowlevel(&sbrt->pd);
+}
+
+int sandbox_power_domain_test_off_ll(struct udevice *dev)
+{
+ struct sandbox_power_domain_test *sbrt = dev_get_priv(dev);
+
+ return power_domain_off_lowlevel(&sbrt->pd);
+}
+
int sandbox_power_domain_test_free(struct udevice *dev)
{
struct sandbox_power_domain_test *sbrt = dev_get_priv(dev);
@@ -51,4 +65,5 @@ U_BOOT_DRIVER(sandbox_power_domain_test) = {
.id = UCLASS_MISC,
.of_match = sandbox_power_domain_test_ids,
.priv_auto = sizeof(struct sandbox_power_domain_test),
+ .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF,
};
diff --git a/drivers/power/domain/sandbox-power-domain.c
b/drivers/power/domain/sandbox-power-domain.c
index
9dd490b14a3f6e502baccd94d32704e4b6bd56ed..a80316576384b27dc8159e81b5c79fc355af2860
100644
--- a/drivers/power/domain/sandbox-power-domain.c
+++ b/drivers/power/domain/sandbox-power-domain.c
@@ -64,8 +64,12 @@ static int sandbox_power_domain_bind(struct udevice *dev)
static int sandbox_power_domain_probe(struct udevice *dev)
{
+ struct power_domain_plat *plat = dev_get_uclass_plat(dev);
+
debug("%s(dev=%p)\n", __func__, dev);
+ plat->subdomains = 1;
+
return 0;
}
diff --git a/include/power-domain.h b/include/power-domain.h
index
18525073e5e3534fcbac6fae4e18462f29a4dc49..7fd2c5e365b54889a156d0f0b969fae490ac41a7
100644
--- a/include/power-domain.h
+++ b/include/power-domain.h
@@ -65,6 +65,15 @@ struct power_domain {
void *priv;
};
+/**
+ * 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.
*
@@ -147,37 +156,81 @@ 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 .
*
diff --git a/test/dm/power-domain.c b/test/dm/power-domain.c
index
896cf5b2ae9d26701150fad70e888f8b135a22b0..1002d831764b5a62b05631aee0d70c5609b8574b
100644
--- a/test/dm/power-domain.c
+++ b/test/dm/power-domain.c
@@ -27,17 +27,26 @@ static int dm_test_power_domain(struct unit_test_state *uts)
ut_assertok(uclass_get_device_by_name(UCLASS_MISC, "power-domain-test",
&dev_test));
- ut_asserteq(1, sandbox_power_domain_query(dev_power_domain,
+ ut_asserteq(0, sandbox_power_domain_query(dev_power_domain,
TEST_POWER_DOMAIN));
ut_assertok(sandbox_power_domain_test_get(dev_test));
ut_assertok(sandbox_power_domain_test_on(dev_test));
ut_asserteq(0, sandbox_power_domain_query(dev_power_domain, 0));
+ ut_asserteq(1, sandbox_power_domain_query(dev_power_domain,
+ TEST_POWER_DOMAIN));
+ ut_asserteq(-EALREADY, sandbox_power_domain_test_on_ll(dev_test));
+ ut_asserteq(1, sandbox_power_domain_query(dev_power_domain,
+ TEST_POWER_DOMAIN));
+ ut_asserteq(-EBUSY, sandbox_power_domain_test_off_ll(dev_test));
ut_asserteq(1, sandbox_power_domain_query(dev_power_domain,
TEST_POWER_DOMAIN));
ut_assertok(sandbox_power_domain_test_off(dev_test));
ut_asserteq(0, sandbox_power_domain_query(dev_power_domain, 0));
+ ut_asserteq(0, sandbox_power_domain_query(dev_power_domain,
+ TEST_POWER_DOMAIN));
+ ut_asserteq(-EALREADY, sandbox_power_domain_test_off_ll(dev_test));
ut_asserteq(0, sandbox_power_domain_query(dev_power_domain,
TEST_POWER_DOMAIN));