summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-omap2/clockdomain.c
diff options
context:
space:
mode:
authorPaul Walmsley <paul@pwsan.com>2010-01-26 20:13:01 -0700
committerPaul Walmsley <paul@pwsan.com>2010-01-26 20:13:01 -0700
commit369d5614457384edcf62c5f39b03dd20be6ea1df (patch)
tree4ad04d48f3049ff2d88e8ddca35fb0ad8597d12c /arch/arm/mach-omap2/clockdomain.c
parente909d62a8afda7a224a7e322cf2f387d69ca771f (diff)
downloadlinux-369d5614457384edcf62c5f39b03dd20be6ea1df.tar.bz2
OMAP clockdomains: add usecounting for wakeup and sleep dependencies
Add usecounting for wakeup and sleep dependencies. In the current situation, if several functions add dependencies on the same clockdomains, when the first dependency removal function is called, the dependency will be incorrectly removed from the hardware. Add clkdm_clear_all_wkdeps() and clkdm_clear_all_sleepdeps(), which provide a fast and usecounting-consistent way to clear all hardware clockdomain dependencies, since accesses to these registers can be quite slow. pm{2,3}4xx.c has been updated to use these new functions. The original version of this patch did not touch these files, which previously wrote directly to the wkdep registers, and thus confused the usecounting code. This problem was found by Kevin Hilman <khilman@deeprootsystems.com>. N.B.: This patch introduces one significant functional difference over the previous pm34xx.c code: sleepdeps are now cleared during clockdomain initialization, whereas previously they were left untouched. This has been tested by Kevin and confirmed to work. The original version of this patch also did not take into consideration that some clockdomains do not have sleep or wakeup dependency sources, which caused NULL pointer dereferences. This problem was debugged and fixed by Kevin Hilman <khilman@deeprootsystems.com>. Signed-off-by: Paul Walmsley <paul@pwsan.com> Signed-off-by: Kevin Hilman <khilman@deeprootsystems.com> Cc: Jouni Högander <jouni.hogander@nokia.com>
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