diff options
author | Paul Walmsley <paul@pwsan.com> | 2010-01-26 20:13:01 -0700 |
---|---|---|
committer | Paul Walmsley <paul@pwsan.com> | 2010-01-26 20:13:01 -0700 |
commit | 369d5614457384edcf62c5f39b03dd20be6ea1df (patch) | |
tree | 4ad04d48f3049ff2d88e8ddca35fb0ad8597d12c /arch/arm/mach-omap2/clockdomain.c | |
parent | e909d62a8afda7a224a7e322cf2f387d69ca771f (diff) |
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.c | 216 |
1 files changed, 196 insertions, 20 deletions
diff --git a/arch/arm/mach-omap2/clockdomain.c b/arch/arm/mach-omap2/clockdomain.c index 2af9996f010..6eaa9314cd6 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 |