summaryrefslogtreecommitdiff
path: root/arch/arm/mach-omap2/clockdomain.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-omap2/clockdomain.c')
-rw-r--r--arch/arm/mach-omap2/clockdomain.c216
1 files changed, 196 insertions, 20 deletions
diff --git a/arch/arm/mach-omap2/clockdomain.c b/arch/arm/mach-omap2/clockdomain.c
index 2af9996f010b..6eaa9314cd64 100644
--- a/arch/arm/mach-omap2/clockdomain.c
+++ b/arch/arm/mach-omap2/clockdomain.c
@@ -113,7 +113,6 @@ static struct clkdm_dep *_clkdm_deps_lookup(struct clockdomain *clkdm,
return ERR_PTR(-EINVAL);
for (cd = deps; cd->clkdm_name; cd++) {
-
if (!omap_chip_is(cd->omap_chip))
continue;
@@ -122,7 +121,6 @@ static struct clkdm_dep *_clkdm_deps_lookup(struct clockdomain *clkdm,
if (cd->clkdm == clkdm)
break;
-
}
if (!cd->clkdm_name)
@@ -254,6 +252,96 @@ static void _omap2_clkdm_set_hwsup(struct clockdomain *clkdm, int enable)
}
+/**
+ * _init_wkdep_usecount - initialize wkdep usecounts to match hardware
+ * @clkdm: clockdomain to initialize wkdep usecounts
+ *
+ * Initialize the wakeup dependency usecount variables for clockdomain @clkdm.
+ * If a wakeup dependency is present in the hardware, the usecount will be
+ * set to 1; otherwise, it will be set to 0. Software should clear all
+ * software wakeup dependencies prior to calling this function if it wishes
+ * to ensure that all usecounts start at 0. No return value.
+ */
+static void _init_wkdep_usecount(struct clockdomain *clkdm)
+{
+ u32 v;
+ struct clkdm_dep *cd;
+
+ if (!clkdm->wkdep_srcs)
+ return;
+
+ for (cd = clkdm->wkdep_srcs; cd->clkdm_name; cd++) {
+ if (!omap_chip_is(cd->omap_chip))
+ continue;
+
+ if (!cd->clkdm && cd->clkdm_name)
+ cd->clkdm = _clkdm_lookup(cd->clkdm_name);
+
+ if (!cd->clkdm) {
+ WARN(!cd->clkdm, "clockdomain: %s: wkdep clkdm %s not "
+ "found\n", clkdm->name, cd->clkdm_name);
+ continue;
+ }
+
+ v = prm_read_mod_bits_shift(clkdm->pwrdm.ptr->prcm_offs,
+ PM_WKDEP,
+ (1 << cd->clkdm->dep_bit));
+
+ if (v)
+ pr_debug("clockdomain: %s: wakeup dependency already "
+ "set to wake up when %s wakes\n",
+ clkdm->name, cd->clkdm->name);
+
+ atomic_set(&cd->wkdep_usecount, (v) ? 1 : 0);
+ }
+}
+
+/**
+ * _init_sleepdep_usecount - initialize sleepdep usecounts to match hardware
+ * @clkdm: clockdomain to initialize sleepdep usecounts
+ *
+ * Initialize the sleep dependency usecount variables for clockdomain @clkdm.
+ * If a sleep dependency is present in the hardware, the usecount will be
+ * set to 1; otherwise, it will be set to 0. Software should clear all
+ * software sleep dependencies prior to calling this function if it wishes
+ * to ensure that all usecounts start at 0. No return value.
+ */
+static void _init_sleepdep_usecount(struct clockdomain *clkdm)
+{
+ u32 v;
+ struct clkdm_dep *cd;
+
+ if (!cpu_is_omap34xx())
+ return;
+
+ if (!clkdm->sleepdep_srcs)
+ return;
+
+ for (cd = clkdm->sleepdep_srcs; cd->clkdm_name; cd++) {
+ if (!omap_chip_is(cd->omap_chip))
+ continue;
+
+ if (!cd->clkdm && cd->clkdm_name)
+ cd->clkdm = _clkdm_lookup(cd->clkdm_name);
+
+ if (!cd->clkdm) {
+ WARN(!cd->clkdm, "clockdomain: %s: sleepdep clkdm %s "
+ "not found\n", clkdm->name, cd->clkdm_name);
+ continue;
+ }
+
+ v = prm_read_mod_bits_shift(clkdm->pwrdm.ptr->prcm_offs,
+ OMAP3430_CM_SLEEPDEP,
+ (1 << cd->clkdm->dep_bit));
+
+ if (v)
+ pr_debug("clockdomain: %s: sleep dependency already "
+ "set to prevent from idling until %s "
+ "idles\n", clkdm->name, cd->clkdm->name);
+
+ atomic_set(&cd->sleepdep_usecount, (v) ? 1 : 0);
+ }
+};
/* Public functions */
@@ -272,6 +360,7 @@ void clkdm_init(struct clockdomain **clkdms,
struct clkdm_autodep *init_autodeps)
{
struct clockdomain **c = NULL;
+ struct clockdomain *clkdm;
struct clkdm_autodep *autodep = NULL;
if (clkdms)
@@ -282,6 +371,15 @@ void clkdm_init(struct clockdomain **clkdms,
if (autodeps)
for (autodep = autodeps; autodep->clkdm.ptr; autodep++)
_autodep_lookup(autodep);
+
+ /*
+ * Ensure that the *dep_usecount registers reflect the current
+ * state of the PRCM.
+ */
+ list_for_each_entry(clkdm, &clkdm_list, node) {
+ _init_wkdep_usecount(clkdm);
+ _init_sleepdep_usecount(clkdm);
+ }
}
/**
@@ -387,11 +485,13 @@ int clkdm_add_wkdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
return PTR_ERR(cd);
}
- pr_debug("clockdomain: hardware will wake up %s when %s wakes up\n",
- clkdm1->name, clkdm2->name);
+ if (atomic_inc_return(&cd->wkdep_usecount) == 1) {
+ pr_debug("clockdomain: hardware will wake up %s when %s wakes "
+ "up\n", clkdm1->name, clkdm2->name);
- prm_set_mod_reg_bits((1 << clkdm2->dep_bit),
- clkdm1->pwrdm.ptr->prcm_offs, PM_WKDEP);
+ prm_set_mod_reg_bits((1 << clkdm2->dep_bit),
+ clkdm1->pwrdm.ptr->prcm_offs, PM_WKDEP);
+ }
return 0;
}
@@ -420,11 +520,13 @@ int clkdm_del_wkdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
return PTR_ERR(cd);
}
- pr_debug("clockdomain: hardware will no longer wake up %s after %s "
- "wakes up\n", clkdm1->name, clkdm2->name);
+ if (atomic_dec_return(&cd->wkdep_usecount) == 0) {
+ pr_debug("clockdomain: hardware will no longer wake up %s "
+ "after %s wakes up\n", clkdm1->name, clkdm2->name);
- prm_clear_mod_reg_bits((1 << clkdm2->dep_bit),
- clkdm1->pwrdm.ptr->prcm_offs, PM_WKDEP);
+ prm_clear_mod_reg_bits((1 << clkdm2->dep_bit),
+ clkdm1->pwrdm.ptr->prcm_offs, PM_WKDEP);
+ }
return 0;
}
@@ -457,11 +559,44 @@ int clkdm_read_wkdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
return PTR_ERR(cd);
}
+ /* XXX It's faster to return the atomic wkdep_usecount */
return prm_read_mod_bits_shift(clkdm1->pwrdm.ptr->prcm_offs, PM_WKDEP,
(1 << clkdm2->dep_bit));
}
/**
+ * clkdm_clear_all_wkdeps - remove all wakeup dependencies from target clkdm
+ * @clkdm: struct clockdomain * to remove all wakeup dependencies from
+ *
+ * Remove all inter-clockdomain wakeup dependencies that could cause
+ * @clkdm to wake. 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_wkdeps(struct clockdomain *clkdm)
+{
+ struct clkdm_dep *cd;
+ u32 mask = 0;
+
+ if (!clkdm)
+ return -EINVAL;
+
+ for (cd = clkdm->wkdep_srcs; cd && cd->clkdm_name; cd++) {
+ if (!omap_chip_is(cd->omap_chip))
+ continue;
+
+ /* PRM accesses are slow, so minimize them */
+ mask |= 1 << cd->clkdm->dep_bit;
+ atomic_set(&cd->wkdep_usecount, 0);
+ }
+
+ prm_clear_mod_reg_bits(mask, clkdm->pwrdm.ptr->prcm_offs, PM_WKDEP);
+
+ return 0;
+}
+
+/**
* 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)
@@ -491,12 +626,14 @@ int clkdm_add_sleepdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
return PTR_ERR(cd);
}
- pr_debug("clockdomain: will prevent %s from sleeping if %s is active\n",
- clkdm1->name, clkdm2->name);
+ 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);
- cm_set_mod_reg_bits((1 << clkdm2->dep_bit),
- clkdm1->pwrdm.ptr->prcm_offs,
- OMAP3430_CM_SLEEPDEP);
+ cm_set_mod_reg_bits((1 << clkdm2->dep_bit),
+ clkdm1->pwrdm.ptr->prcm_offs,
+ OMAP3430_CM_SLEEPDEP);
+ }
return 0;
}
@@ -531,12 +668,15 @@ int clkdm_del_sleepdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
return PTR_ERR(cd);
}
- pr_debug("clockdomain: will no longer prevent %s from sleeping if "
- "%s is active\n", clkdm1->name, clkdm2->name);
+ 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);
- cm_clear_mod_reg_bits((1 << clkdm2->dep_bit),
- clkdm1->pwrdm.ptr->prcm_offs,
- OMAP3430_CM_SLEEPDEP);
+ cm_clear_mod_reg_bits((1 << clkdm2->dep_bit),
+ clkdm1->pwrdm.ptr->prcm_offs,
+ OMAP3430_CM_SLEEPDEP);
+ }
return 0;
}
@@ -575,11 +715,47 @@ int clkdm_read_sleepdep(struct clockdomain *clkdm1, struct clockdomain *clkdm2)
return PTR_ERR(cd);
}
+ /* XXX It's faster to return the atomic sleepdep_usecount */
return prm_read_mod_bits_shift(clkdm1->pwrdm.ptr->prcm_offs,
OMAP3430_CM_SLEEPDEP,
(1 << clkdm2->dep_bit));
}
+/**
+ * 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)
+{
+ struct clkdm_dep *cd;
+ u32 mask = 0;
+
+ if (!cpu_is_omap34xx())
+ return -EINVAL;
+
+ if (!clkdm)
+ return -EINVAL;
+
+ for (cd = clkdm->sleepdep_srcs; cd && cd->clkdm_name; cd++) {
+ if (!omap_chip_is(cd->omap_chip))
+ continue;
+
+ /* PRM accesses are slow, so minimize them */
+ mask |= 1 << cd->clkdm->dep_bit;
+ atomic_set(&cd->sleepdep_usecount, 0);
+ }
+
+ prm_clear_mod_reg_bits(mask, clkdm->pwrdm.ptr->prcm_offs,
+ OMAP3430_CM_SLEEPDEP);
+
+ return 0;
+}
/**
* omap2_clkdm_clktrctrl_read - read the clkdm's current state transition mode