diff options
-rw-r--r-- | arch/sandbox/include/asm/power-domain.h | 2 | ||||
-rw-r--r-- | drivers/firmware/scmi/sandbox-scmi_devices.c | 1 | ||||
-rw-r--r-- | drivers/power/domain/power-domain-uclass.c | 90 | ||||
-rw-r--r-- | drivers/power/domain/sandbox-power-domain-test.c | 15 | ||||
-rw-r--r-- | drivers/power/domain/sandbox-power-domain.c | 4 | ||||
-rw-r--r-- | include/power-domain.h | 69 | ||||
-rw-r--r-- | 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 4d5e861dbce..3b0717f8fa0 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 96c2922b067..9f253b0fd40 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 938bd8cbc9f..d9fa8ad4bd2 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")); + + (*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 ops->on ? ops->on(power_domain) : 0; + 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")); + + 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 ops->off ? ops->off(power_domain) : 0; + 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 08c15ef342b..df063001f51 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 9dd490b14a3..a8031657638 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 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. diff --git a/test/dm/power-domain.c b/test/dm/power-domain.c index 896cf5b2ae9..1002d831764 100644 --- a/test/dm/power-domain.c +++ b/test/dm/power-domain.c @@ -27,7 +27,7 @@ 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)); @@ -35,11 +35,20 @@ static int dm_test_power_domain(struct unit_test_state *uts) 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)); ut_assertok(sandbox_power_domain_test_free(dev_test)); |