From 119e28566f08bf1bb775fe6e8139de7a9413d432 Mon Sep 17 00:00:00 2001 From: Gary King Date: Thu, 27 May 2010 22:26:49 -0700 Subject: [ARM/tegra] suspend: add support for LP0 and LP1 suspend modes in both LP0 and LP1, SDRAM is placed into self-refresh. in order to safely perform this transition, the final shutdown procedure responsible for * turning off the MMU and L1 data cache * putting memory into self-refresh * setting the DDR pads to the lowest power state * and turning off PLLs is copied into IRAM (at the address TEGRA_IRAM_BASE + SZ_4K) at the start of the suspend process. in LP1 mode (like LP2), the CPU is reset and executes the code specified at the EVP reset vector. since SDRAM is in self-refresh, this code must also be located in IRAM, and it must re-enable DRAM before restoring the full context. in this implementation, it enables the CPU on PLLP, enables PLLC and PLLM, restores the SCLK burst policy, and jumps to the LP2 reset vector to restore the rest of the system (MMU, PLLX, coresite, etc.). the LP2 reset vector is expected to be found in PMC_SCRATCH1, and is initialized during system-bootup in LP0 mode, the core voltage domain is also shutoff. as a result, all of the volatile state in the core voltage domain (e.g., pinmux registers, clock registers, etc.) must be saved to memory so that it can be restored after the system resumes. a limited set of wakeups are available from LP0, and the correct levels for the wakeups must be programmed into the PMC wakepad configuration register prior to system shutdown. on resume, the system resets into the boot ROM, and the boot ROM restores SDRAM and other system state using values saved during kernel initialization in the PMC scratch registers for simplicity, the outer cache is shutdown for both LP0 and LP1; it is possible to optimize the LP1 routine to bypass outer cache shutdown and restart v2 fixes from Vik Kasivajhula: * restore PLLC during LP1 resume * fix typo which set the CPU clock burst policy to PLLM, rather than PLLP Change-Id: Icb1d2cbcbac8503369a10d16fd5c8b561af5a35a Reviewed-on: http://git-master/r/1773 Reviewed-by: Gary King Tested-by: Gary King --- arch/arm/mach-tegra/board-nvodm.c | 18 ++- arch/arm/mach-tegra/cortex_a9_save.S | 206 ++++++++++++++++++++++++++++++++--- arch/arm/mach-tegra/power.h | 9 ++ arch/arm/mach-tegra/suspend.c | 192 ++++++++++++++++++++++++++++---- 4 files changed, 385 insertions(+), 40 deletions(-) diff --git a/arch/arm/mach-tegra/board-nvodm.c b/arch/arm/mach-tegra/board-nvodm.c index 19416f7da720..8d6a1b6b5b36 100644 --- a/arch/arm/mach-tegra/board-nvodm.c +++ b/arch/arm/mach-tegra/board-nvodm.c @@ -1320,10 +1320,26 @@ static void __init tegra_setup_suspend(void) if (!w || !nr_wake) goto do_register; + plat->wake_enb = 0; + plat->wake_low = 0; + plat->wake_high = 0; + plat->wake_any = 0; + while (nr_wake--) { unsigned int pad = w->WakeupPadNumber; if (pad < ARRAY_SIZE(wakepad_irq) && w->enable) - enable_irq_wake(wakepad_irq[w->WakeupPadNumber]); + enable_irq_wake(wakepad_irq[pad]); + + if (w->enable) { + plat->wake_enb |= (1 << pad); + + if (w->Polarity == NvOdmWakeupPadPolarity_Low) + plat->wake_low |= (1 << pad); + else if (w->Polarity == NvOdmWakeupPadPolarity_High) + plat->wake_high |= (1 << pad); + else if (w->Polarity == NvOdmWakeupPadPolarity_AnyEdge) + plat->wake_any |= (1 << pad); + } w++; } diff --git a/arch/arm/mach-tegra/cortex_a9_save.S b/arch/arm/mach-tegra/cortex_a9_save.S index 4456f68c67d8..6c80e8e28b79 100644 --- a/arch/arm/mach-tegra/cortex_a9_save.S +++ b/arch/arm/mach-tegra/cortex_a9_save.S @@ -42,9 +42,18 @@ #define CONTEXT_SIZE_WORDS_SHIFT 7 #define CONTEXT_SIZE_WORDS (1< + #define TEGRA_POWER_SDRAM_SELFREFRESH 0x400 /* SDRAM is in self-refresh */ #define TEGRA_POWER_PWRREQ_POLARITY 0x1 /* core power request polarity */ @@ -36,6 +38,9 @@ #define TEGRA_POWER_PMC_SHIFT 8 #define TEGRA_POWER_PMC_MASK 0x1ff +/* layout of IRAM used for LP1 save & restore */ +#define TEGRA_IRAM_CODE_AREA TEGRA_IRAM_BASE + SZ_4K +#define TEGRA_IRAM_CODE_SIZE SZ_4K #ifndef __ASSEMBLY__ void tegra_lp2_set_trigger(unsigned long cycles); @@ -47,6 +52,10 @@ struct tegra_suspend_platform_data { unsigned long cpu_off_timer; /* CPU power off time us, LP2/LP1 */ unsigned long core_timer; /* core power good time in ticks, LP0 */ unsigned long core_off_timer; /* core power off time ticks, LP0 */ + unsigned long wake_enb; /* mask of enabled wake pads */ + unsigned long wake_high; /* high-level-triggered wake pads */ + unsigned long wake_low; /* low-level-triggered wake pads */ + unsigned long wake_any; /* any-edge-triggered wake pads */ bool dram_suspend; /* platform supports DRAM self-refresh */ bool core_off; /* platform supports core voltage off */ bool corereq_high; /* Core power request active-high */ diff --git a/arch/arm/mach-tegra/suspend.c b/arch/arm/mach-tegra/suspend.c index 9cdd805709d8..e99dda3a3b65 100644 --- a/arch/arm/mach-tegra/suspend.c +++ b/arch/arm/mach-tegra/suspend.c @@ -71,6 +71,11 @@ static void __iomem *evp_reset = IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE)+0x100; static void __iomem *tmrus = IO_ADDRESS(TEGRA_TMRUS_BASE); #define PMC_CTRL 0x0 +#define PMC_CTRL_LATCH_WAKEUPS (1 << 5) +#define PMC_WAKE_MASK 0xc +#define PMC_WAKE_LEVEL 0x10 + +#define PMC_SW_WAKE_STATUS 0x18 #define PMC_COREPWRGOOD_TIMER 0x3c #define PMC_SCRATCH1 0x54 #define PMC_CPUPWRGOOD_TIMER 0xc8 @@ -257,6 +262,131 @@ unsigned int tegra_suspend_lp2(unsigned int us) } #ifdef CONFIG_PM + +/* ensures that sufficient time is passed for a register write to + * serialize into the 32KHz domain */ +static void pmc_32kwritel(u32 val, unsigned long offs) +{ + writel(val, pmc + offs); + wmb(); + udelay(130); +} + +static void tegra_setup_wakepads(bool lp0_ok) +{ + u32 temp, status, lvl; + + /* wakeup by interrupt, nothing to do here */ + if (!lp0_ok) + return; + + pmc_32kwritel(0, PMC_SW_WAKE_STATUS); + temp = readl(pmc + PMC_CTRL); + temp |= PMC_CTRL_LATCH_WAKEUPS; + pmc_32kwritel(0, PMC_CTRL); + temp &= ~PMC_CTRL_LATCH_WAKEUPS; + pmc_32kwritel(0, PMC_CTRL); + + status = readl(pmc + PMC_SW_WAKE_STATUS); + lvl = readl(pmc + PMC_WAKE_LEVEL); + + /* flip the wakeup trigger for any-edge triggered pads + * which are currently asserting as wakeups */ + status &= pdata->wake_any; + lvl &= ~pdata->wake_low; + lvl |= pdata->wake_high; + lvl ^= status; + + writel(lvl, PMC_WAKE_LEVEL); + writel(pdata->wake_enb, PMC_WAKE_MASK); +} + +extern void __tegra_lp1_reset(void); +extern void __tegra_iram_end(void); + +static u8 *iram_save = NULL; +static unsigned int iram_save_size = 0; +static void __iomem *iram_code = IO_ADDRESS(TEGRA_IRAM_CODE_AREA); + +static void tegra_suspend_dram(bool lp0_ok) +{ + static unsigned long cpu_timer_32k = 0; + unsigned int lp2_timer; + unsigned int mode = TEGRA_POWER_SDRAM_SELFREFRESH; + unsigned long orig; + + orig = readl(evp_reset); + /* copy the reset vector and SDRAM shutdown code into IRAM */ + memcpy(iram_save, iram_code, iram_save_size); + memcpy(iram_code, (void *)__tegra_lp1_reset, iram_save_size); + + if (!cpu_timer_32k) { + unsigned long long temp = 32768ull*pdata->cpu_timer + 999999; + do_div(temp, 1000000ul); + cpu_timer_32k = temp; + } + + if (!lp0_ok) { + writel(TEGRA_IRAM_CODE_AREA, evp_reset); + NvRmPrivPowerSetState(s_hRmGlobal, NvRmPowerState_LP1); + lp2_timer = readl(pmc + PMC_CPUPWRGOOD_TIMER); + writel(cpu_timer_32k, pmc + PMC_CPUPWRGOOD_TIMER); + mode |= TEGRA_POWER_CPU_PWRREQ_OE; + if (pdata->core_off) + mode |= TEGRA_POWER_SYSCLK_OE; + } else { + NvRmPrivPowerSetState(s_hRmGlobal, NvRmPowerState_LP0); + set_power_timers(pdata->cpu_timer, pdata->cpu_off_timer); + mode |= TEGRA_POWER_SYSCLK_OE; + mode |= TEGRA_POWER_PWRREQ_OE; + mode |= TEGRA_POWER_EFFECT_LP0; + } + + if (!pdata->corereq_high) + mode |= TEGRA_POWER_PWRREQ_POLARITY; + if (!pdata->sysclkreq_high) + mode |= TEGRA_POWER_SYSCLK_POLARITY; + + /* for platforms where the core & CPU power requests are combined as + * a single request to the PMU, transition from the current state to + * the desired state by temporarily enabling both requests (if the + * previous state does not match the new state) + */ + + if (!pdata->separate_req) { + u32 reg = readl(pmc + PMC_CTRL); + reg |= ((mode & TEGRA_POWER_PMC_MASK) << TEGRA_POWER_PMC_SHIFT); + pmc_32kwritel(reg, PMC_CTRL); + } + + tegra_setup_wakepads(lp0_ok); + suspend_cpu_complex(); + flush_cache_all(); + outer_shutdown(); + + __cortex_a9_save(mode); + restore_cpu_complex(); + + writel(orig, evp_reset); + outer_restart(); + + /* restore to CPU power request, assuming that the next transition + * will be into LP2 */ + if (lp0_ok && !pdata->separate_req) { + u32 reg = readl(pmc + PMC_CTRL); + mode &= TEGRA_POWER_PMC_MASK; + mode |= TEGRA_POWER_CPU_PWRREQ_OE; + reg |= (mode << TEGRA_POWER_PMC_SHIFT); + pmc_32kwritel(reg, PMC_CTRL); + reg &= ~(TEGRA_POWER_PWRREQ_OE << TEGRA_POWER_PMC_SHIFT); + writel(reg, pmc + PMC_CTRL); + } else if (!lp0_ok) + writel(lp2_timer, pmc + PMC_CPUPWRGOOD_TIMER); + + memcpy(iram_code, iram_save, iram_save_size); + wmb(); +} + static int tegra_suspend_prepare_late(void) { #ifdef CONFIG_TEGRA_NVRM @@ -397,14 +527,17 @@ static int tegra_suspend_enter(suspend_state_t state) int irq; local_irq_save(flags); - tegra_irq_suspend(); - tegra_dma_suspend(); - tegra_pinmux_suspend(); - tegra_gpio_suspend(); - tegra_clk_suspend(); - mc_data[0] = readl(mc + MC_SECURITY_START); - mc_data[1] = readl(mc + MC_SECURITY_SIZE); + if (pdata->core_off) { + tegra_irq_suspend(); + tegra_dma_suspend(); + tegra_pinmux_suspend(); + tegra_gpio_suspend(); + tegra_clk_suspend(); + + mc_data[0] = readl(mc + MC_SECURITY_START); + mc_data[1] = readl(mc + MC_SECURITY_SIZE); + } for_each_irq_desc(irq, desc) { if ((desc->status & IRQ_WAKEUP) && @@ -413,9 +546,12 @@ static int tegra_suspend_enter(suspend_state_t state) } } - /* lie about the power state so that the RM restarts DVFS */ - NvRmPrivPowerSetState(s_hRmGlobal, NvRmPowerState_LP1); - tegra_suspend_lp2(0); + if (!pdata->dram_suspend || !iram_save) { + /* lie about the power state so that the RM restarts DVFS */ + NvRmPrivPowerSetState(s_hRmGlobal, NvRmPowerState_LP1); + tegra_suspend_lp2(0); + } else + tegra_suspend_dram(pdata->core_off); for_each_irq_desc(irq, desc) { if ((desc->status & IRQ_WAKEUP) && @@ -424,14 +560,16 @@ static int tegra_suspend_enter(suspend_state_t state) } } - writel(mc_data[0], mc + MC_SECURITY_START); - writel(mc_data[1], mc + MC_SECURITY_SIZE); - - tegra_clk_resume(); - tegra_gpio_resume(); - tegra_pinmux_resume(); - tegra_dma_resume(); - tegra_irq_resume(); + if (pdata->core_off) { + writel(mc_data[0], mc + MC_SECURITY_START); + writel(mc_data[1], mc + MC_SECURITY_SIZE); + + tegra_clk_resume(); + tegra_gpio_resume(); + tegra_pinmux_resume(); + tegra_dma_resume(); + tegra_irq_resume(); + } local_irq_restore(flags); @@ -446,12 +584,24 @@ static struct platform_suspend_ops tegra_suspend_ops = { }; #endif +extern void __init lp0_suspend_init(void); + void __init tegra_init_suspend(struct tegra_suspend_platform_data *plat) { tegra_pclk = clk_get_sys(NULL, "pclk"); BUG_ON(!tegra_pclk); pdata = plat; + iram_save_size = (unsigned long)__tegra_iram_end; + iram_save_size -= (unsigned long)__tegra_lp1_reset; + + iram_save = kmalloc(iram_save_size, GFP_KERNEL); + if (!iram_save) { + pr_err("%s: unable to allocate memory for SDRAM self-refresh " + "LP0/LP1 unavailable\n", __func__); + } + writel(virt_to_phys(tegra_lp2_startup), pmc + PMC_SCRATCH1); + if (pdata->core_off) { u32 reg = 0, mode; @@ -470,11 +620,11 @@ void __init tegra_init_suspend(struct tegra_suspend_platform_data *plat) /* configure output inverters while the request is tristated */ reg |= (mode << TEGRA_POWER_PMC_SHIFT); - writel(reg, pmc + PMC_CTRL); - wmb(); - udelay(2000); /* 32KHz domain delay */ + pmc_32kwritel(reg, PMC_CTRL); reg |= (TEGRA_POWER_SYSCLK_OE << TEGRA_POWER_PMC_SHIFT); writel(reg, pmc + PMC_CTRL); + + lp0_suspend_init(); } #ifdef CONFIG_PM suspend_set_ops(&tegra_suspend_ops); -- cgit v1.2.3