summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/suspend.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-tegra/suspend.c')
-rw-r--r--arch/arm/mach-tegra/suspend.c192
1 files changed, 171 insertions, 21 deletions
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);