From: Paul Walmsley Date: Sun, 23 Sep 2012 23:28:28 +0000 (-0600) Subject: ARM: OMAP2+: clockdomain/hwmod: add workaround for EMU clockdomain idle problems X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=b71c721;p=linux-beck.git ARM: OMAP2+: clockdomain/hwmod: add workaround for EMU clockdomain idle problems The idle status of the IP blocks and clocks inside the EMU clockdomain isn't taken into account by the PRCM hardware when deciding whether the clockdomain is idle. Add a workaround flag in the clockdomain code, CLKDM_MISSING_IDLE_REPORTING, to deal with this problem, and add the code necessary to support it. If CLKDM_MISSING_IDLE_REPORTING is set on a clockdomain, the clockdomain will be forced active whenever an IP block inside that clockdomain is in use, even if the clockdomain supports hardware-supervised idle. When the kernel indicates that the last active IP block inside the clockdomain is no longer used, the clockdomain will be forced idle, or, if that mode is not supported in the hardware, it will be placed into hardware-supervised idle. This patch is an equal collaboration with Jon Hunter . Ming Lei , Will Deacon , Madhav Vij , Kevin Hilman , Benoît Cousson , and Santosh Shilimkar all made essential contributions to the understanding of EMU clockdomain power management on OMAP. Signed-off-by: Paul Walmsley Cc: Jon Hunter Cc: Ming Lei Cc: Will Deacon Cc: Madhav Vij Cc: Kevin Hilman Cc: Benoît Cousson Cc: Santosh Shilimkar Tested-by: Jon Hunter --- diff --git a/arch/arm/mach-omap2/clockdomain.c b/arch/arm/mach-omap2/clockdomain.c index a1555627ad97..cbb879139c51 100644 --- a/arch/arm/mach-omap2/clockdomain.c +++ b/arch/arm/mach-omap2/clockdomain.c @@ -899,6 +899,23 @@ bool clkdm_in_hwsup(struct clockdomain *clkdm) return ret; } +/** + * clkdm_missing_idle_reporting - can @clkdm enter autoidle even if in use? + * @clkdm: struct clockdomain * + * + * Returns true if clockdomain @clkdm has the + * CLKDM_MISSING_IDLE_REPORTING flag set, or false if not or @clkdm is + * null. More information is available in the documentation for the + * CLKDM_MISSING_IDLE_REPORTING macro. + */ +bool clkdm_missing_idle_reporting(struct clockdomain *clkdm) +{ + if (!clkdm) + return false; + + return (clkdm->flags & CLKDM_MISSING_IDLE_REPORTING) ? true : false; +} + /* Clockdomain-to-clock/hwmod framework interface code */ static int _clkdm_clk_hwmod_enable(struct clockdomain *clkdm) diff --git a/arch/arm/mach-omap2/clockdomain.h b/arch/arm/mach-omap2/clockdomain.h index 5601dc13785e..629576be7444 100644 --- a/arch/arm/mach-omap2/clockdomain.h +++ b/arch/arm/mach-omap2/clockdomain.h @@ -1,9 +1,7 @@ /* - * arch/arm/plat-omap/include/mach/clockdomain.h - * * OMAP2/3 clockdomain framework functions * - * Copyright (C) 2008 Texas Instruments, Inc. + * Copyright (C) 2008, 2012 Texas Instruments, Inc. * Copyright (C) 2008-2011 Nokia Corporation * * Paul Walmsley @@ -34,6 +32,20 @@ * CLKDM_ACTIVE_WITH_MPU: The PRCM guarantees that this clockdomain is * active whenever the MPU is active. True for interconnects and * the WKUP clockdomains. + * CLKDM_MISSING_IDLE_REPORTING: The idle status of the IP blocks and + * clocks inside this clockdomain are not taken into account by + * the PRCM when determining whether the clockdomain is idle. + * Without this flag, if the clockdomain is set to + * hardware-supervised idle mode, the PRCM may transition the + * enclosing powerdomain to a low power state, even when devices + * inside the clockdomain and powerdomain are in use. (An example + * of such a clockdomain is the EMU clockdomain on OMAP3/4.) If + * this flag is set, and the clockdomain does not support the + * force-sleep mode, then the HW_AUTO mode will be used to put the + * clockdomain to sleep. Similarly, if the clockdomain supports + * the force-wakeup mode, then it will be used whenever a clock or + * IP block inside the clockdomain is active, rather than the + * HW_AUTO mode. */ #define CLKDM_CAN_FORCE_SLEEP (1 << 0) #define CLKDM_CAN_FORCE_WAKEUP (1 << 1) @@ -41,6 +53,7 @@ #define CLKDM_CAN_DISABLE_AUTO (1 << 3) #define CLKDM_NO_AUTODEPS (1 << 4) #define CLKDM_ACTIVE_WITH_MPU (1 << 5) +#define CLKDM_MISSING_IDLE_REPORTING (1 << 6) #define CLKDM_CAN_HWSUP (CLKDM_CAN_ENABLE_AUTO | CLKDM_CAN_DISABLE_AUTO) #define CLKDM_CAN_SWSUP (CLKDM_CAN_FORCE_SLEEP | CLKDM_CAN_FORCE_WAKEUP) @@ -187,6 +200,7 @@ int clkdm_clear_all_sleepdeps(struct clockdomain *clkdm); void clkdm_allow_idle(struct clockdomain *clkdm); void clkdm_deny_idle(struct clockdomain *clkdm); bool clkdm_in_hwsup(struct clockdomain *clkdm); +bool clkdm_missing_idle_reporting(struct clockdomain *clkdm); int clkdm_wakeup(struct clockdomain *clkdm); int clkdm_sleep(struct clockdomain *clkdm); diff --git a/arch/arm/mach-omap2/clockdomain2xxx_3xxx.c b/arch/arm/mach-omap2/clockdomain2xxx_3xxx.c index f99e65cfb862..9a7792aec673 100644 --- a/arch/arm/mach-omap2/clockdomain2xxx_3xxx.c +++ b/arch/arm/mach-omap2/clockdomain2xxx_3xxx.c @@ -162,6 +162,19 @@ static void _disable_hwsup(struct clockdomain *clkdm) clkdm->clktrctrl_mask); } +static int omap3_clkdm_sleep(struct clockdomain *clkdm) +{ + omap3xxx_cm_clkdm_force_sleep(clkdm->pwrdm.ptr->prcm_offs, + clkdm->clktrctrl_mask); + return 0; +} + +static int omap3_clkdm_wakeup(struct clockdomain *clkdm) +{ + omap3xxx_cm_clkdm_force_wakeup(clkdm->pwrdm.ptr->prcm_offs, + clkdm->clktrctrl_mask); + return 0; +} static int omap2_clkdm_clk_enable(struct clockdomain *clkdm) { @@ -170,6 +183,17 @@ static int omap2_clkdm_clk_enable(struct clockdomain *clkdm) if (!clkdm->clktrctrl_mask) return 0; + /* + * The CLKDM_MISSING_IDLE_REPORTING flag documentation has + * more details on the unpleasant problem this is working + * around + */ + if (clkdm->flags & CLKDM_MISSING_IDLE_REPORTING && + !(clkdm->flags & CLKDM_CAN_FORCE_SLEEP)) { + _enable_hwsup(clkdm); + return 0; + } + hwsup = omap2_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs, clkdm->clktrctrl_mask); @@ -193,6 +217,17 @@ static int omap2_clkdm_clk_disable(struct clockdomain *clkdm) if (!clkdm->clktrctrl_mask) return 0; + /* + * The CLKDM_MISSING_IDLE_REPORTING flag documentation has + * more details on the unpleasant problem this is working + * around + */ + if ((clkdm->flags & CLKDM_MISSING_IDLE_REPORTING) && + (clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)) { + omap3_clkdm_wakeup(clkdm); + return 0; + } + hwsup = omap2_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs, clkdm->clktrctrl_mask); @@ -209,20 +244,6 @@ static int omap2_clkdm_clk_disable(struct clockdomain *clkdm) return 0; } -static int omap3_clkdm_sleep(struct clockdomain *clkdm) -{ - omap3xxx_cm_clkdm_force_sleep(clkdm->pwrdm.ptr->prcm_offs, - clkdm->clktrctrl_mask); - return 0; -} - -static int omap3_clkdm_wakeup(struct clockdomain *clkdm) -{ - omap3xxx_cm_clkdm_force_wakeup(clkdm->pwrdm.ptr->prcm_offs, - clkdm->clktrctrl_mask); - return 0; -} - static void omap3_clkdm_allow_idle(struct clockdomain *clkdm) { if (atomic_read(&clkdm->usecount) > 0) diff --git a/arch/arm/mach-omap2/clockdomain44xx.c b/arch/arm/mach-omap2/clockdomain44xx.c index 762f2cc542ce..6fc6155625bc 100644 --- a/arch/arm/mach-omap2/clockdomain44xx.c +++ b/arch/arm/mach-omap2/clockdomain44xx.c @@ -113,6 +113,17 @@ static int omap4_clkdm_clk_disable(struct clockdomain *clkdm) if (!clkdm->prcm_partition) return 0; + /* + * The CLKDM_MISSING_IDLE_REPORTING flag documentation has + * more details on the unpleasant problem this is working + * around + */ + if (clkdm->flags & CLKDM_MISSING_IDLE_REPORTING && + !(clkdm->flags & CLKDM_CAN_FORCE_SLEEP)) { + omap4_clkdm_allow_idle(clkdm); + return 0; + } + hwsup = omap4_cminst_is_clkdm_in_hwsup(clkdm->prcm_partition, clkdm->cm_inst, clkdm->clkdm_offs); diff --git a/arch/arm/mach-omap2/clockdomains3xxx_data.c b/arch/arm/mach-omap2/clockdomains3xxx_data.c index 56089c49142a..933a35cd124a 100644 --- a/arch/arm/mach-omap2/clockdomains3xxx_data.c +++ b/arch/arm/mach-omap2/clockdomains3xxx_data.c @@ -387,14 +387,11 @@ static struct clockdomain per_am35x_clkdm = { .clktrctrl_mask = OMAP3430_CLKTRCTRL_PER_MASK, }; -/* - * Disable hw supervised mode for emu_clkdm, because emu_pwrdm is - * switched of even if sdti is in use - */ static struct clockdomain emu_clkdm = { .name = "emu_clkdm", .pwrdm = { .name = "emu_pwrdm" }, - .flags = /* CLKDM_CAN_ENABLE_AUTO | */CLKDM_CAN_SWSUP, + .flags = (CLKDM_CAN_ENABLE_AUTO | CLKDM_CAN_SWSUP | + CLKDM_MISSING_IDLE_REPORTING), .clktrctrl_mask = OMAP3430_CLKTRCTRL_EMU_MASK, }; diff --git a/arch/arm/mach-omap2/clockdomains44xx_data.c b/arch/arm/mach-omap2/clockdomains44xx_data.c index 63d60a773d3b..b56d06b48782 100644 --- a/arch/arm/mach-omap2/clockdomains44xx_data.c +++ b/arch/arm/mach-omap2/clockdomains44xx_data.c @@ -390,7 +390,8 @@ static struct clockdomain emu_sys_44xx_clkdm = { .prcm_partition = OMAP4430_PRM_PARTITION, .cm_inst = OMAP4430_PRM_EMU_CM_INST, .clkdm_offs = OMAP4430_PRM_EMU_CM_EMU_CDOFFS, - .flags = CLKDM_CAN_ENABLE_AUTO | CLKDM_CAN_FORCE_WAKEUP, + .flags = (CLKDM_CAN_ENABLE_AUTO | CLKDM_CAN_FORCE_WAKEUP | + CLKDM_MISSING_IDLE_REPORTING), }; static struct clockdomain l3_dma_44xx_clkdm = { diff --git a/arch/arm/mach-omap2/omap_hwmod.c b/arch/arm/mach-omap2/omap_hwmod.c index 21299879d2d5..6af64bbd9e1d 100644 --- a/arch/arm/mach-omap2/omap_hwmod.c +++ b/arch/arm/mach-omap2/omap_hwmod.c @@ -2007,7 +2007,8 @@ static int _enable(struct omap_hwmod *oh) * completely the module. The clockdomain can be set * in HW_AUTO only when the module become ready. */ - hwsup = clkdm_in_hwsup(oh->clkdm); + hwsup = clkdm_in_hwsup(oh->clkdm) && + !clkdm_missing_idle_reporting(oh->clkdm); r = clkdm_hwmod_enable(oh->clkdm, oh); if (r) { WARN(1, "omap_hwmod: %s: could not enable clockdomain %s: %d\n", diff --git a/arch/arm/mach-omap2/pm.c b/arch/arm/mach-omap2/pm.c index f6f05b67f86a..abefbc4d8e0b 100644 --- a/arch/arm/mach-omap2/pm.c +++ b/arch/arm/mach-omap2/pm.c @@ -80,7 +80,8 @@ static void __init omap2_init_processor_devices(void) int __init omap_pm_clkdms_setup(struct clockdomain *clkdm, void *unused) { - if (clkdm->flags & CLKDM_CAN_ENABLE_AUTO) + if ((clkdm->flags & CLKDM_CAN_ENABLE_AUTO) && + !(clkdm->flags & CLKDM_MISSING_IDLE_REPORTING)) clkdm_allow_idle(clkdm); else if (clkdm->flags & CLKDM_CAN_FORCE_SLEEP && atomic_read(&clkdm->usecount) == 0)