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
.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,
};
#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;
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)
}
#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),
};
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);
.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,
};
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;
}
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.
*
#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 .
*
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));