|
@@ -581,3 +581,158 @@ int clkdm_clear_all_wkdeps(struct clockdomain *clkdm)
|
|
|
|
|
|
if (!arch_clkdm || !arch_clkdm->clkdm_clear_all_wkdeps)
|
|
|
return -EINVAL;
|
|
|
+
|
|
|
+ return arch_clkdm->clkdm_clear_all_wkdeps(clkdm);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * clkdm_add_sleepdep - add a sleep dependency from clkdm2 to clkdm1
|
|
|
+ * @clkdm1: prevent this struct clockdomain * from sleeping (dependent)
|
|
|
+ * @clkdm2: when this struct clockdomain * is active (source)
|
|
|
+ *
|
|
|
+ * Prevent @clkdm1 from automatically going inactive (and then to
|
|
|
+ * retention or off) if @clkdm2 is active. Returns -EINVAL if
|
|
|
+ * presented with invalid clockdomain pointers or called on a machine
|
|
|
+ * that does not support software-configurable hardware sleep
|
|
|
+ * dependencies, -ENOENT if the specified dependency cannot be set in
|
|
|
+ * hardware, or 0 upon success.
|
|
|
+ */
|
|
|
+int clkdm_add_sleepdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
|
|
|
+{
|
|
|
+ struct clkdm_dep *cd;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (!clkdm1 || !clkdm2)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ cd = _clkdm_deps_lookup(clkdm2, clkdm1->sleepdep_srcs);
|
|
|
+ if (IS_ERR(cd))
|
|
|
+ ret = PTR_ERR(cd);
|
|
|
+
|
|
|
+ if (!arch_clkdm || !arch_clkdm->clkdm_add_sleepdep)
|
|
|
+ ret = -EINVAL;
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ pr_debug("clockdomain: hardware cannot set/clear sleep dependency affecting %s from %s\n",
|
|
|
+ clkdm1->name, clkdm2->name);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (atomic_inc_return(&cd->sleepdep_usecount) == 1) {
|
|
|
+ pr_debug("clockdomain: will prevent %s from sleeping if %s is active\n",
|
|
|
+ clkdm1->name, clkdm2->name);
|
|
|
+
|
|
|
+ ret = arch_clkdm->clkdm_add_sleepdep(clkdm1, clkdm2);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * clkdm_del_sleepdep - remove a sleep dependency from clkdm2 to clkdm1
|
|
|
+ * @clkdm1: prevent this struct clockdomain * from sleeping (dependent)
|
|
|
+ * @clkdm2: when this struct clockdomain * is active (source)
|
|
|
+ *
|
|
|
+ * Allow @clkdm1 to automatically go inactive (and then to retention or
|
|
|
+ * off), independent of the activity state of @clkdm2. Returns -EINVAL
|
|
|
+ * if presented with invalid clockdomain pointers or called on a machine
|
|
|
+ * that does not support software-configurable hardware sleep dependencies,
|
|
|
+ * -ENOENT if the specified dependency cannot be cleared in hardware, or
|
|
|
+ * 0 upon success.
|
|
|
+ */
|
|
|
+int clkdm_del_sleepdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
|
|
|
+{
|
|
|
+ struct clkdm_dep *cd;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (!clkdm1 || !clkdm2)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ cd = _clkdm_deps_lookup(clkdm2, clkdm1->sleepdep_srcs);
|
|
|
+ if (IS_ERR(cd))
|
|
|
+ ret = PTR_ERR(cd);
|
|
|
+
|
|
|
+ if (!arch_clkdm || !arch_clkdm->clkdm_del_sleepdep)
|
|
|
+ ret = -EINVAL;
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ pr_debug("clockdomain: hardware cannot set/clear sleep dependency affecting %s from %s\n",
|
|
|
+ clkdm1->name, clkdm2->name);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (atomic_dec_return(&cd->sleepdep_usecount) == 0) {
|
|
|
+ pr_debug("clockdomain: will no longer prevent %s from sleeping if %s is active\n",
|
|
|
+ clkdm1->name, clkdm2->name);
|
|
|
+
|
|
|
+ ret = arch_clkdm->clkdm_del_sleepdep(clkdm1, clkdm2);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * clkdm_read_sleepdep - read sleep dependency state from clkdm2 to clkdm1
|
|
|
+ * @clkdm1: prevent this struct clockdomain * from sleeping (dependent)
|
|
|
+ * @clkdm2: when this struct clockdomain * is active (source)
|
|
|
+ *
|
|
|
+ * Return 1 if a hardware sleep dependency exists wherein @clkdm1 will
|
|
|
+ * not be allowed to automatically go inactive if @clkdm2 is active;
|
|
|
+ * 0 if @clkdm1's automatic power state inactivity transition is independent
|
|
|
+ * of @clkdm2's; -EINVAL if either clockdomain pointer is invalid or called
|
|
|
+ * on a machine that does not support software-configurable hardware sleep
|
|
|
+ * dependencies; or -ENOENT if the hardware is incapable.
|
|
|
+ *
|
|
|
+ * REVISIT: Currently this function only represents software-controllable
|
|
|
+ * sleep dependencies. Sleep dependencies fixed in hardware are not
|
|
|
+ * yet handled here.
|
|
|
+ */
|
|
|
+int clkdm_read_sleepdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
|
|
|
+{
|
|
|
+ struct clkdm_dep *cd;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (!clkdm1 || !clkdm2)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ cd = _clkdm_deps_lookup(clkdm2, clkdm1->sleepdep_srcs);
|
|
|
+ if (IS_ERR(cd))
|
|
|
+ ret = PTR_ERR(cd);
|
|
|
+
|
|
|
+ if (!arch_clkdm || !arch_clkdm->clkdm_read_sleepdep)
|
|
|
+ ret = -EINVAL;
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ pr_debug("clockdomain: hardware cannot set/clear sleep dependency affecting %s from %s\n",
|
|
|
+ clkdm1->name, clkdm2->name);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* XXX It's faster to return the atomic sleepdep_usecount */
|
|
|
+ return arch_clkdm->clkdm_read_sleepdep(clkdm1, clkdm2);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * clkdm_clear_all_sleepdeps - remove all sleep dependencies from target clkdm
|
|
|
+ * @clkdm: struct clockdomain * to remove all sleep dependencies from
|
|
|
+ *
|
|
|
+ * Remove all inter-clockdomain sleep dependencies that could prevent
|
|
|
+ * @clkdm from idling. Intended to be used during boot to initialize the
|
|
|
+ * PRCM to a known state, after all clockdomains are put into swsup idle
|
|
|
+ * and woken up. Returns -EINVAL if @clkdm pointer is invalid, or
|
|
|
+ * 0 upon success.
|
|
|
+ */
|
|
|
+int clkdm_clear_all_sleepdeps(struct clockdomain *clkdm)
|
|
|
+{
|
|
|
+ if (!clkdm)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!arch_clkdm || !arch_clkdm->clkdm_clear_all_sleepdeps)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ return arch_clkdm->clkdm_clear_all_sleepdeps(clkdm);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * clkdm_sleep - force clockdomain sleep transition
|
|
|
+ * @clkdm: struct clockdomain *
|