[PATCH 12/21] ARM: tegra: Add suspend and hotplug support

Russell King - ARM Linux linux at arm.linux.org.uk
Sun Dec 5 19:01:41 EST 2010


On Sun, Dec 05, 2010 at 03:08:59PM -0800, Colin Cross wrote:
>  obj-$(CONFIG_ARCH_TEGRA_2x_SOC)		+= pinmux-t2-tables.o
> -obj-$(CONFIG_SMP)                       += platsmp.o localtimer.o headsmp.o
> -obj-$(CONFIG_HOTPLUG_CPU)               += hotplug.o
> +obj-$(CONFIG_SMP)                       += localtimer.o

This wants to be CONFIG_LOCAL_TIMERS, not CONFIG_SMP.  Local timer support
is dependent on CONFIG_LOCAL_TIMERS being set.

> +#ifdef CONFIG_VFPv3
> +	orr	r2, r3, #0xF00000
> +	mcr	p15, 0, r2, c1, c0, 2	@ enable access to FPU
> +	VFPFMRX	r2, FPEXC
> +	str	r2, [r8, #CTX_FPEXC]
> +	mov	r1, #0x40000000		@ enable access to FPU
> +	VFPFMXR	FPEXC, r1
> +	VFPFMRX	r1, FPSCR
> +	str	r1, [r8, #CTX_FPSCR]
> +	isb
> +	add	r9, r8, #CTX_VFP_REGS
> +
> +	VFPFSTMIA r9, r12	@ save out (16 or 32)*8B of FPU registers
> +	VFPFMXR	FPEXC, r2
> +	mrc	p15, 0, r3, c1, c0, 2	@ restore original FPEXC/CPACR
> +#endif

There's already functions provided for saving/restoring VFP state.  Please
use them rather than inventing a different method.

> +	cps	0x1f			@ SYS mode
> +	add	r9, r8, #CTX_SYS_SP
> +	stmia	r9, {sp,lr}
> +
> +	cps	0x17			@ Abort mode
> +	mrs	r12, spsr
> +	add	r9, r8, #CTX_ABT_SPSR
> +	stmia	r9, {r12,sp,lr}
> +
> +	cps	0x12			@ IRQ mode
> +	mrs	r12, spsr
> +	add	r9, r8, #CTX_IRQ_SPSR
> +	stmia	r9, {r12,sp,lr}
> +
> +	cps	0x1b			@ Undefined mode
> +	mrs	r12, spsr
> +	add	r9, r8, #CTX_UND_SPSR
> +	stmia	r9, {r12,sp,lr}
> +
> +	mov	r0, r8
> +	add	r1, r8, #CTX_FIQ_SPSR
> +	cps	0x11			@ FIQ mode
> +	mrs	r7, spsr
> +	stmia	r1, {r7-r12,sp,lr}

Same old mistakes...  Take a look at what we put in these registers,
and then go and look at cpu_init(), and ask whether you really need
to save all this.

> +	add	r9, r8, #CTS_CP14_BKPT_0
> +	mrc	p14, 0, r2, c0, c0, 4
> +	mrc	p14, 0, r3, c0, c0, 5
> +	stmia	r9!, {r2-r3}     	@ BRKPT_0
> +	mrc	p14, 0, r2, c0, c1, 4
> +	mrc	p14, 0, r3, c0, c1, 5
> +	stmia	r9!, {r2-r3}     	@ BRKPT_0
> +	mrc	p14, 0, r2, c0, c2, 4
> +	mrc	p14, 0, r3, c0, c2, 5
> +	stmia	r9!, {r2-r3}     	@ BRKPT_0
> +	mrc	p14, 0, r2, c0, c3, 4
> +	mrc	p14, 0, r3, c0, c3, 5
> +	stmia	r9!, {r2-r3}     	@ BRKPT_0
> +	mrc	p14, 0, r2, c0, c4, 4
> +	mrc	p14, 0, r3, c0, c4, 5
> +	stmia	r9!, {r2-r3}     	@ BRKPT_0
> +	mrc	p14, 0, r2, c0, c5, 4
> +	mrc	p14, 0, r3, c0, c5, 5
> +	stmia	r9!, {r2-r3}     	@ BRKPT_0
> +
> +	add	r9, r8, #CTS_CP14_WPT_0
> +	mrc	p14, 0, r2, c0, c0, 6
> +	mrc	p14, 0, r3, c0, c0, 7
> +	stmia	r9!, {r2-r3}     	@ WPT_0
> +	mrc	p14, 0, r2, c0, c1, 6
> +	mrc	p14, 0, r3, c0, c1, 7
> +	stmia	r9!, {r2-r3}     	@ WPT_0
> +	mrc	p14, 0, r2, c0, c2, 6
> +	mrc	p14, 0, r3, c0, c2, 7
> +	stmia	r9!, {r2-r3}     	@ WPT_0
> +	mrc	p14, 0, r2, c0, c3, 6
> +	mrc	p14, 0, r3, c0, c3, 7
> +	stmia	r9!, {r2-r3}     	@ WPT_0

Breakpoint and watchdog registers should be handled by the perf code.
If not, it needs to be added there, rather than inventing your own here.

> +#ifdef CONFIG_CACHE_L2X0
> +	cpu_id	r4
> +	cmp	r4, #0
> +	bne	__cortex_a9_save_clean_cache
> +	mov32	r4, (TEGRA_ARM_PL310_BASE-IO_CPU_PHYS+IO_CPU_VIRT)
> +	add	r9, r8, #CTX_L2_CTRL
> +	ldr	r0, [r4, #L2X0_CTRL]
> +	ldr	r1, [r4, #L2X0_AUX_CTRL]
> +	ldr	r2, [r4, #L2X0_TAG_LATENCY_CTRL]
> +	ldr	r3, [r4, #L2X0_DATA_LATENCY_CTRL]
> +	ldr	r4, [r4, #L2X0_PREFETCH_CTRL]
> +	stmia	r9, {r0-r4}
> +#endif

PM support needs to be added to the L2x0 support code.

> +#ifdef CONFIG_HOTPLUG_CPU
> +static DEFINE_PER_CPU(struct completion, cpu_killed);
> +extern void tegra_hotplug_startup(void);
> +#endif

You don't need a per-CPU cpu_killed completion.  Only one CPU can be
taken offline at a time - it's serialized by the cpu_add_remove_lock
mutex.

In any case, as a result of my cleanups, this is now in core code.

> +
> +static DECLARE_BITMAP(cpu_init_bits, CONFIG_NR_CPUS) __read_mostly;
> +const struct cpumask *const cpu_init_mask = to_cpumask(cpu_init_bits);
> +#define cpu_init_map (*(cpumask_t *)cpu_init_mask)
> +
>  #define EVP_CPU_RESET_VECTOR \
>  	(IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE) + 0x100)
>  #define CLK_RST_CONTROLLER_CLK_CPU_CMPLX \
>  	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x4c)
> +#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET \
> +	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x340)
>  #define CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR \
>  	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x344)
>  
>  void __cpuinit platform_secondary_init(unsigned int cpu)
>  {
>  	trace_hardirqs_off();

This has also been moved to core code.

> -
> -	/*
> -	 * if any interrupts are already enabled for the primary
> -	 * core (e.g. timer irq), then they will not have been enabled
> -	 * for us: do so
> -	 */
>  	gic_cpu_init(0, IO_ADDRESS(TEGRA_ARM_PERIF_BASE) + 0x100);
> -
>  	/*
>  	 * Synchronise with the boot thread.
>  	 */
>  	spin_lock(&boot_lock);
> +#ifdef CONFIG_HOTPLUG_CPU
> +	cpu_set(cpu, cpu_init_map);
> +	INIT_COMPLETION(per_cpu(cpu_killed, cpu));
> +#endif
>  	spin_unlock(&boot_lock);
>  }
>  
> @@ -70,27 +89,30 @@ int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle)
>  	 */
>  	spin_lock(&boot_lock);
>  
> -
>  	/* set the reset vector to point to the secondary_startup routine */
> +#ifdef CONFIG_HOTPLUG_CPU
> +	if (cpumask_test_cpu(cpu, cpu_init_mask))
> +		boot_vector = virt_to_phys(tegra_hotplug_startup);
> +	else
> +#endif
> +		boot_vector = virt_to_phys(tegra_secondary_startup);
> +
> +	smp_wmb();
>  
> -	boot_vector = virt_to_phys(tegra_secondary_startup);
>  	old_boot_vector = readl(EVP_CPU_RESET_VECTOR);
>  	writel(boot_vector, EVP_CPU_RESET_VECTOR);
>  
> -	/* enable cpu clock on cpu1 */
> +	/* enable cpu clock on cpu */
>  	reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
> -	writel(reg & ~(1<<9), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
> +	writel(reg & ~(1<<(8+cpu)), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
>  
> -	reg = (1<<13) | (1<<9) | (1<<5) | (1<<1);
> +	reg = 0x1111<<cpu;
>  	writel(reg, CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
>  
> -	smp_wmb();
> -	flush_cache_all();
> -
>  	/* unhalt the cpu */
> -	writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x14);
> +	writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x14 + 0x8*(cpu-1));
>  
> -	timeout = jiffies + (1 * HZ);
> +	timeout = jiffies + HZ;
>  	while (time_before(jiffies, timeout)) {
>  		if (readl(EVP_CPU_RESET_VECTOR) != boot_vector)
>  			break;
> @@ -142,6 +164,12 @@ void __init smp_prepare_cpus(unsigned int max_cpus)
>  	for (i = 0; i < max_cpus; i++)
>  		set_cpu_present(i, true);
>  
> +#ifdef CONFIG_HOTPLUG_CPU
> +	for_each_present_cpu(i) {
> +		init_completion(&per_cpu(cpu_killed, i));
> +	}
> +#endif
> +
>  	/*
>  	 * Initialise the SCU if there are more than one CPU and let
>  	 * them know where to start. Note that, on modern versions of
> @@ -154,3 +182,71 @@ void __init smp_prepare_cpus(unsigned int max_cpus)
>  		scu_enable(scu_base);
>  	}
>  }
> +
> +#ifdef CONFIG_HOTPLUG_CPU
> +
> +extern void vfp_sync_state(struct thread_info *thread);

Looks unused, and in any case is not necessary.  With SMP, we always
save the VFP state when switching away from a task, and that includes
when we switch to the idle task.  We can only go offline from the idle
task, so we've already saved the VFP state - specific to the last running
thread which used VFP - safely away.

> +
> +void __cpuinit secondary_start_kernel(void);
> +
> +int platform_cpu_kill(unsigned int cpu)
> +{
> +	unsigned int reg;
> +	int e;
> +
> +	e = wait_for_completion_timeout(&per_cpu(cpu_killed, cpu), 100);
> +	printk(KERN_NOTICE "CPU%u: %s shutdown\n", cpu, (e) ? "clean":"forced");
> +
> +	if (e) {
> +		do {
> +			reg = readl(CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET);
> +			cpu_relax();
> +		} while (!(reg & (1<<cpu)));
> +	} else {
> +		writel(0x1111<<cpu, CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET);
> +		/* put flow controller in WAIT_EVENT mode */
> +		writel(2<<29, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE)+0x14 + 0x8*(cpu-1));
> +	}
> +	spin_lock(&boot_lock);
> +	reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
> +	writel(reg | (1<<(8+cpu)), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
> +	spin_unlock(&boot_lock);
> +	return e;
> +}
> +
> +void platform_cpu_die(unsigned int cpu)
> +{
> +#ifdef DEBUG
> +	unsigned int this_cpu = hard_smp_processor_id();
> +
> +	if (cpu != this_cpu) {
> +		printk(KERN_CRIT "Eek! platform_cpu_die running on %u, should be %u\n",
> +			   this_cpu, cpu);
> +		BUG();
> +	}
> +#endif
> +
> +	gic_cpu_exit(0);
> +	barrier();
> +	complete(&per_cpu(cpu_killed, cpu));
> +	flush_cache_all();
> +	barrier();
> +	__cortex_a9_save(0);
> +
> +	/* return happens from __cortex_a9_restore */
> +	barrier();
> +	writel(smp_processor_id(), EVP_CPU_RESET_VECTOR);

Hopefully it doesn't return here - if you can take the core offline
properly, you should restore it by effectively rebooting it.

> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +
> +#include <asm/io.h>

linux/io.h (and any other places in this patch set which also have asm/io.h
includes.)

> +
> +#include <mach/gpio.h>

linux/gpio.h (and any other places which have mach/gpio.h)

> +	unsigned int flags = PMD_TYPE_SECT | PMD_SECT_AP_WRITE |
> +		PMD_SECT_WBWA | PMD_SECT_S;
> +
> +	tegra_pgd = pgd_alloc(&init_mm);
> +	if (!tegra_pgd)
> +		return -ENOMEM;
> +
> +	for (i=0; i<ARRAY_SIZE(addr_p); i++) {
> +		unsigned long v = addr_v[i];
> +		pmd = pmd_offset(tegra_pgd + pgd_index(v), v);
> +		*pmd = __pmd((addr_p[i] & PGDIR_MASK) | flags);
> +		flush_pmd_entry(pmd);
> +		outer_clean_range(__pa(pmd), __pa(pmd + 1));
> +	}
> +
> +	tegra_pgd_phys = virt_to_phys(tegra_pgd);
> +	__cpuc_flush_dcache_area(&tegra_pgd_phys,
> +		sizeof(tegra_pgd_phys));
> +	outer_clean_range(__pa(&tegra_pgd_phys),
> +		__pa(&tegra_pgd_phys+1));
> +
> +	__cpuc_flush_dcache_area(&tegra_context_area,
> +		sizeof(tegra_context_area));
> +	outer_clean_range(__pa(&tegra_context_area),
> +		__pa(&tegra_context_area+1));

Eww, no.  We should have this as a separate API.  It's also not
checkpatch-clean.

> +
> +	return 0;
> +}
> +
> +
> +
> +/*
> + * suspend_cpu_complex
> + *
> + *   disable periodic IRQs used for DVFS to prevent suspend wakeups
> + *   disable coresight debug interface
> + *
> + *
> + */
> +static noinline void restore_cpu_complex(void)

Comment doesn't match function.

> +{
> +	unsigned int reg;
> +
> +	/* restore original burst policy setting; PLLX state restored
> +	 * by CPU boot-up code - wait for PLL stabilization if PLLX
> +	 * was enabled, or if explicitly requested by caller */
> +
> +	BUG_ON(readl(clk_rst + CLK_RESET_PLLX_BASE) != tegra_sctx.pllx_base);
> +
> +	if (tegra_sctx.pllx_base & (1<<30)) {
> +		while (readl(tmrus)-tegra_sctx.pll_timeout >= 0x80000000UL)
> +			cpu_relax();
> +	}
> +	writel(tegra_sctx.cclk_divider, clk_rst + CLK_RESET_CCLK_DIVIDER);
> +	writel(tegra_sctx.cpu_burst, clk_rst + CLK_RESET_CCLK_BURST);
> +	writel(tegra_sctx.clk_csite_src, clk_rst + CLK_RESET_SOURCE_CSITE);
> +
> +	/* do not power-gate the CPU when flow controlled */
> +	reg = readl(flow_ctrl + FLOW_CTRL_CPU_CSR);
> +	reg &= ~((1<<5) | (1<<4) | 1); /* clear WFE bitmask */
> +	reg |= (1<<14); /* write-1-clear event flag */
> +	writel(reg, flow_ctrl + FLOW_CTRL_CPU_CSR);
> +	wmb();
> +
> +#ifdef CONFIG_HAVE_ARM_TWD
> +	writel(tegra_sctx.twd_ctrl, twd_base + 0x8);
> +	writel(tegra_sctx.twd_load, twd_base + 0);
> +#endif
> +
> +	gic_dist_restore(0);
> +	get_irq_chip(IRQ_LOCALTIMER)->unmask(IRQ_LOCALTIMER);
> +
> +	enable_irq(INT_SYS_STATS_MON);
> +}
> +
> +static noinline void suspend_cpu_complex(void)
> +{
> +	unsigned int reg;
> +	int i;
> +
> +	disable_irq(INT_SYS_STATS_MON);
> +
> +	/* switch coresite to clk_m, save off original source */
> +	tegra_sctx.clk_csite_src = readl(clk_rst + CLK_RESET_SOURCE_CSITE);
> +	writel(3<<30, clk_rst + CLK_RESET_SOURCE_CSITE);
> +
> +	tegra_sctx.cpu_burst = readl(clk_rst + CLK_RESET_CCLK_BURST);
> +	tegra_sctx.pllx_base = readl(clk_rst + CLK_RESET_PLLX_BASE);
> +	tegra_sctx.pllx_misc = readl(clk_rst + CLK_RESET_PLLX_MISC);
> +	tegra_sctx.pllp_base = readl(clk_rst + CLK_RESET_PLLP_BASE);
> +	tegra_sctx.pllp_outa = readl(clk_rst + CLK_RESET_PLLP_OUTA);
> +	tegra_sctx.pllp_outb = readl(clk_rst + CLK_RESET_PLLP_OUTB);
> +	tegra_sctx.pllp_misc = readl(clk_rst + CLK_RESET_PLLP_MISC);
> +	tegra_sctx.cclk_divider = readl(clk_rst + CLK_RESET_CCLK_DIVIDER);
> +
> +#ifdef CONFIG_HAVE_ARM_TWD
> +	tegra_sctx.twd_ctrl = readl(twd_base + 0x8);
> +	tegra_sctx.twd_load = readl(twd_base + 0);
> +	local_timer_stop();
> +#endif
> +
> +	reg = readl(flow_ctrl + FLOW_CTRL_CPU_CSR);
> +	/* clear any pending events, set the WFE bitmap to specify just
> +	 * CPU0, and clear any pending events for this CPU */
> +	reg &= ~(1<<5); /* clear CPU1 WFE */
> +	reg |= (1<<14) | (1<<4) | 1; /* enable CPU0 WFE */
> +	writel(reg, flow_ctrl + FLOW_CTRL_CPU_CSR);
> +	wmb();
> +
> +	for (i=1; i<num_present_cpus(); i++) {
> +		unsigned int offs = FLOW_CTRL_CPU1_CSR + (i-1)*8;
> +		reg = readl(flow_ctrl + offs);
> +		writel(reg | (1<<14), flow_ctrl + offs);
> +		wmb();
> +	}
> +
> +	gic_cpu_exit(0);
> +	gic_dist_save(0);
> +}
> +
> +unsigned int tegra_suspend_lp2(unsigned int us)
> +{
> +	unsigned int mode;
> +	unsigned long orig, reg;
> +	unsigned int remain;
> +
> +	reg = readl(pmc + PMC_CTRL);
> +	mode = (reg >> TEGRA_POWER_PMC_SHIFT) & TEGRA_POWER_PMC_MASK;
> +	mode |= TEGRA_POWER_CPU_PWRREQ_OE;
> +	if (pdata->separate_req)
> +		mode |= TEGRA_POWER_PWRREQ_OE;
> +	else
> +		mode &= ~TEGRA_POWER_PWRREQ_OE;
> +	mode &= ~TEGRA_POWER_EFFECT_LP0;
> +
> +	orig = readl(evp_reset);
> +	writel(virt_to_phys(tegra_lp2_startup), evp_reset);
> +
> +	set_power_timers(pdata->cpu_timer, pdata->cpu_off_timer,
> +			 clk_get_rate(tegra_pclk));
> +
> +	if (us)
> +		tegra_lp2_set_trigger(us);
> +
> +	suspend_cpu_complex();
> +	flush_cache_all();
> +	/* structure is written by reset code, so the L2 lines
> +	 * must be invalidated */
> +	outer_flush_range(__pa(&tegra_sctx),__pa(&tegra_sctx+1));
> +	barrier();
> +
> +	__cortex_a9_save(mode);
> +	/* return from __cortex_a9_restore */
> +	barrier();
> +	restore_cpu_complex();
> +
> +	remain = tegra_lp2_timer_remain();
> +	if (us)
> +		tegra_lp2_set_trigger(0);
> +
> +	writel(orig, evp_reset);
> +
> +	return remain;
> +}
> +
> +#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);
> +	udelay(130);
> +}
> +
> +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 do_lp0)
> +{
> +	unsigned int mode = TEGRA_POWER_SDRAM_SELFREFRESH;
> +	unsigned long orig, reg;
> +
> +	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);
> +
> +	set_power_timers(pdata->cpu_timer, pdata->cpu_off_timer, 32768);
> +
> +	reg = readl(pmc + PMC_CTRL);
> +	mode |= ((reg >> TEGRA_POWER_PMC_SHIFT) & TEGRA_POWER_PMC_MASK);
> +
> +	if (!do_lp0) {
> +		writel(TEGRA_IRAM_CODE_AREA, evp_reset);
> +
> +		mode |= TEGRA_POWER_CPU_PWRREQ_OE;
> +		if (pdata->separate_req)
> +			mode |= TEGRA_POWER_PWRREQ_OE;
> +		else
> +			mode &= ~TEGRA_POWER_PWRREQ_OE;
> +		mode &= ~TEGRA_POWER_EFFECT_LP0;
> +
> +		tegra_legacy_irq_set_lp1_wake_mask();
> +	} else {
> +		u32 boot_flag = readl(pmc + PMC_SCRATCH0);
> +		pmc_32kwritel(boot_flag | 1, PMC_SCRATCH0);
> +		pmc_32kwritel(wb0_restore, PMC_SCRATCH1);
> +		writel(0x0, pmc + PMC_SCRATCH39);
> +		mode |= TEGRA_POWER_CPU_PWRREQ_OE;
> +		mode |= TEGRA_POWER_PWRREQ_OE;
> +		mode |= TEGRA_POWER_EFFECT_LP0;
> +
> +		/* for platforms where the core & CPU power requests are
> +		 * combined as a single request to the PMU, transition to
> +		 * LP0 state by temporarily enabling both requests
> +		 */
> +		if (!pdata->separate_req) {
> +			reg |= ((mode & TEGRA_POWER_PMC_MASK) <<
> +				TEGRA_POWER_PMC_SHIFT);
> +			pmc_32kwritel(reg, PMC_CTRL);
> +			mode &= ~TEGRA_POWER_CPU_PWRREQ_OE;
> +		}
> +
> +		tegra_set_lp0_wake_pads(pdata->wake_enb, pdata->wake_high,
> +			pdata->wake_any);
> +	}
> +
> +	suspend_cpu_complex();
> +	flush_cache_all();
> +	outer_flush_all();
> +	outer_disable();
> +
> +	__cortex_a9_save(mode);
> +	restore_cpu_complex();
> +
> +	writel(orig, evp_reset);
> +	tegra_init_cache();
> +
> +	if (!do_lp0) {
> +		memcpy(iram_code, iram_save, iram_save_size);
> +		tegra_legacy_irq_restore_mask();
> +	} else {
> +		/* for platforms where the core & CPU power requests are
> +		 * combined as a single request to the PMU, transition out
> +		 * of LP0 state by temporarily enabling both requests
> +		 */
> +		if (!pdata->separate_req) {
> +			reg = readl(pmc + PMC_CTRL);
> +			reg |= (TEGRA_POWER_CPU_PWRREQ_OE << TEGRA_POWER_PMC_SHIFT);
> +			pmc_32kwritel(reg, PMC_CTRL);
> +			reg &= ~(TEGRA_POWER_PWRREQ_OE << TEGRA_POWER_PMC_SHIFT);
> +			writel(reg, pmc + PMC_CTRL);
> +		}
> +	}
> +
> +	wmb();
> +}
> +
> +static int tegra_suspend_prepare_late(void)
> +{
> +	disable_irq(INT_SYS_STATS_MON);
> +	return 0;
> +}
> +
> +static void tegra_suspend_wake(void)
> +{
> +	enable_irq(INT_SYS_STATS_MON);
> +}
> +
> +static u8 uart_state[5];
> +
> +static int tegra_debug_uart_suspend(void)
> +{
> +	void __iomem *uart;
> +	u32 lcr;
> +
> +	if (TEGRA_DEBUG_UART_BASE == 0)
> +		return 0;
> +
> +	uart = IO_ADDRESS(TEGRA_DEBUG_UART_BASE);
> +
> +	lcr = readb(uart + UART_LCR * 4);
> +
> +	uart_state[0] = lcr;
> +	uart_state[1] = readb(uart + UART_MCR * 4);
> +
> +	/* DLAB = 0 */
> +	writeb(lcr & ~UART_LCR_DLAB, uart + UART_LCR * 4);
> +
> +	uart_state[2] = readb(uart + UART_IER * 4);
> +
> +	/* DLAB = 1 */
> +	writeb(lcr | UART_LCR_DLAB, uart + UART_LCR * 4);
> +
> +	uart_state[3] = readb(uart + UART_DLL * 4);
> +	uart_state[4] = readb(uart + UART_DLM * 4);
> +
> +	writeb(lcr, uart + UART_LCR * 4);
> +
> +	return 0;
> +}
> +
> +static void tegra_debug_uart_resume(void)
> +{
> +	void __iomem *uart;
> +	u32 lcr;
> +
> +	if (TEGRA_DEBUG_UART_BASE == 0)
> +		return;
> +
> +	uart = IO_ADDRESS(TEGRA_DEBUG_UART_BASE);
> +
> +	lcr = uart_state[0];
> +
> +	writeb(uart_state[1], uart + UART_MCR * 4);
> +
> +	/* DLAB = 0 */
> +	writeb(lcr & ~UART_LCR_DLAB, uart + UART_LCR * 4);
> +
> +	writeb(uart_state[2], uart + UART_IER * 4);
> +
> +	/* DLAB = 1 */
> +	writeb(lcr | UART_LCR_DLAB, uart + UART_LCR * 4);
> +
> +	writeb(uart_state[3], uart + UART_DLL * 4);
> +	writeb(uart_state[4], uart + UART_DLM * 4);
> +
> +	writeb(lcr, uart + UART_LCR * 4);
> +}
> +
> +#define MC_SECURITY_START	0x6c
> +#define MC_SECURITY_SIZE	0x70
> +
> +static int tegra_suspend_enter(suspend_state_t state)
> +{
> +	struct irq_desc *desc;
> +	void __iomem *mc = IO_ADDRESS(TEGRA_MC_BASE);
> +	unsigned long flags;
> +	u32 mc_data[2];
> +	int irq;
> +	bool do_lp0 = (current_suspend_mode == TEGRA_SUSPEND_LP0);
> +	bool do_lp2 = (current_suspend_mode == TEGRA_SUSPEND_LP2);
> +	int lp_state;
> +	u64 rtc_before;
> +	u64 rtc_after;
> +	u64 secs;
> +	u32 ms;
> +
> +	if (do_lp2)
> +		lp_state = 2;
> +	else if (do_lp0)
> +		lp_state = 0;
> +	else
> +		lp_state = 1;
> +
> +	local_irq_save(flags);
> +	local_fiq_disable();
> +
> +	pr_info("Entering suspend state LP%d\n", lp_state);
> +	if (do_lp0) {
> +		tegra_irq_suspend();
> +		tegra_dma_suspend();
> +		tegra_debug_uart_suspend();
> +		tegra_pinmux_suspend();
> +		tegra_timer_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) &&
> +		    (desc->status & IRQ_SUSPENDED)) {
> +			get_irq_chip(irq)->unmask(irq);
> +		}
> +	}
> +
> +	rtc_before = tegra_rtc_read_ms();
> +
> +	if (do_lp2)
> +		tegra_suspend_lp2(0);
> +	else
> +		tegra_suspend_dram(do_lp0);
> +
> +	rtc_after = tegra_rtc_read_ms();
> +
> +	for_each_irq_desc(irq, desc) {
> +		if ((desc->status & IRQ_WAKEUP) &&
> +		    (desc->status & IRQ_SUSPENDED)) {
> +			get_irq_chip(irq)->mask(irq);
> +		}
> +	}
> +
> +	/* Clear DPD sample */
> +	writel(0x0, pmc + PMC_DPD_SAMPLE);
> +
> +	if (do_lp0) {
> +		writel(mc_data[0], mc + MC_SECURITY_START);
> +		writel(mc_data[1], mc + MC_SECURITY_SIZE);
> +
> +		tegra_clk_resume();
> +		tegra_gpio_resume();
> +		tegra_timer_resume();
> +		tegra_pinmux_resume();
> +		tegra_debug_uart_resume();
> +		tegra_dma_resume();
> +		tegra_irq_resume();
> +	}
> +
> +	secs = rtc_after - rtc_before;
> +	ms = do_div(secs, 1000);
> +	pr_info("Suspended for %llu.%03u seconds\n", secs, ms);
> +
> +	tegra_time_in_suspend[time_to_bin(secs)]++;
> +
> +	local_fiq_enable();
> +	local_irq_restore(flags);
> +
> +	return 0;
> +}
> +
> +static struct platform_suspend_ops tegra_suspend_ops = {
> +	.valid		= suspend_valid_only_mem,
> +	.prepare_late	= tegra_suspend_prepare_late,
> +	.wake		= tegra_suspend_wake,
> +	.enter		= tegra_suspend_enter,
> +};
> +#endif
> +
> +static unsigned long lp0_vec_orig_start = 0;
> +static unsigned long lp0_vec_orig_size = 0;
> +
> +static int __init tegra_lp0_vec_arg(char *options)
> +{
> +	char *p = options;
> +
> +	lp0_vec_orig_size = memparse(p, &p);
> +	if (*p == '@')
> +		lp0_vec_orig_start = memparse(p+1, &p);
> +
> +	return 0;
> +}
> +__setup("lp0_vec=", tegra_lp0_vec_arg);
> +
> +void __init tegra_init_suspend(struct tegra_suspend_platform_data *plat)
> +{
> +	u32 reg, mode;
> +
> +	tegra_pclk = clk_get_sys(NULL, "pclk");
> +	BUG_ON(!tegra_pclk);
> +	pdata = plat;
> +	(void)reg;
> +	(void)mode;
> +
> +	if (plat->suspend_mode == TEGRA_SUSPEND_LP0 &&
> +			lp0_vec_orig_size && lp0_vec_orig_start) {
> +		unsigned char *reloc_lp0;
> +		unsigned long tmp;
> +		void __iomem *orig;
> +		reloc_lp0 = kmalloc(lp0_vec_orig_size+L1_CACHE_BYTES-1,
> +				    GFP_KERNEL);
> +		WARN_ON(!reloc_lp0);
> +		if (!reloc_lp0)
> +			goto out;
> +
> +		orig = ioremap(lp0_vec_orig_start, lp0_vec_orig_size);
> +		WARN_ON(!orig);
> +		if (!orig) {
> +			kfree(reloc_lp0);
> +			goto out;
> +		}
> +		tmp = (unsigned long) reloc_lp0;
> +		tmp = (tmp + L1_CACHE_BYTES - 1) & ~(L1_CACHE_BYTES-1);
> +		reloc_lp0 = (unsigned char *)tmp;
> +		memcpy(reloc_lp0, orig, lp0_vec_orig_size);
> +		iounmap(orig);
> +		wb0_restore = virt_to_phys(reloc_lp0);
> +	}
> +out:
> +	if (plat->suspend_mode == TEGRA_SUSPEND_LP0 && !wb0_restore) {
> +		pr_warning("Suspend mode LP0 requested, but missing lp0_vec\n");
> +		pr_warning("Disabling LP0\n");
> +		plat->suspend_mode = TEGRA_SUSPEND_LP1;
> +	}
> +
> +	tegra_context_area = kzalloc(CONTEXT_SIZE_BYTES * NR_CPUS, GFP_KERNEL);
> +
> +	if (tegra_context_area && create_suspend_pgtable()) {
> +		kfree(tegra_context_area);
> +		tegra_context_area = NULL;
> +	}
> +
> +#ifdef CONFIG_PM
> +	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__);
> +		plat->suspend_mode = TEGRA_SUSPEND_LP2;
> +	}
> +	/* CPU reset vector for LP0 and LP1 */
> +	writel(virt_to_phys(tegra_lp2_startup), pmc + PMC_SCRATCH41);
> +
> +	/* Always enable CPU power request; just normal polarity is supported */
> +	reg = readl(pmc + PMC_CTRL);
> +	BUG_ON(reg & (TEGRA_POWER_CPU_PWRREQ_POLARITY << TEGRA_POWER_PMC_SHIFT));
> +	reg |= (TEGRA_POWER_CPU_PWRREQ_OE << TEGRA_POWER_PMC_SHIFT);
> +	pmc_32kwritel(reg, PMC_CTRL);
> +
> +	/* Configure core power request and system clock control if LP0
> +	   is supported */
> +	writel(pdata->core_timer, pmc + PMC_COREPWRGOOD_TIMER);
> +	writel(pdata->core_off_timer, pmc + PMC_COREPWROFF_TIMER);
> +	reg = readl(pmc + PMC_CTRL);
> +	mode = (reg >> TEGRA_POWER_PMC_SHIFT) & TEGRA_POWER_PMC_MASK;
> +
> +	mode &= ~TEGRA_POWER_SYSCLK_POLARITY;
> +	mode &= ~TEGRA_POWER_PWRREQ_POLARITY;
> +
> +	if (!pdata->sysclkreq_high)
> +		mode |= TEGRA_POWER_SYSCLK_POLARITY;
> +	if (!pdata->corereq_high)
> +		mode |= TEGRA_POWER_PWRREQ_POLARITY;
> +
> +	/* configure output inverters while the request is tristated */
> +	reg |= (mode << TEGRA_POWER_PMC_SHIFT);
> +	pmc_32kwritel(reg, PMC_CTRL);
> +
> +	/* now enable requests */
> +	reg |= (TEGRA_POWER_SYSCLK_OE << TEGRA_POWER_PMC_SHIFT);
> +	if (pdata->separate_req)
> +		reg |= (TEGRA_POWER_PWRREQ_OE << TEGRA_POWER_PMC_SHIFT);
> +	writel(reg, pmc + PMC_CTRL);
> +
> +	if (pdata->suspend_mode == TEGRA_SUSPEND_LP0)
> +		lp0_suspend_init();
> +
> +	suspend_set_ops(&tegra_suspend_ops);
> +#endif
> +
> +	current_suspend_mode = plat->suspend_mode;
> +}
> +
> +#ifdef CONFIG_DEBUG_FS
> +static const char *tegra_suspend_name[TEGRA_MAX_SUSPEND_MODE] = {
> +	[TEGRA_SUSPEND_NONE]	= "none",
> +	[TEGRA_SUSPEND_LP2]	= "lp2",
> +	[TEGRA_SUSPEND_LP1]	= "lp1",
> +	[TEGRA_SUSPEND_LP0]	= "lp0",
> +};
> +
> +static int tegra_suspend_debug_show(struct seq_file *s, void *data)
> +{
> +	seq_printf(s, "%s\n", tegra_suspend_name[*(int *)s->private]);
> +	return 0;
> +}
> +
> +static int tegra_suspend_debug_open(struct inode *inode, struct file *file)
> +{
> +	return single_open(file, tegra_suspend_debug_show, inode->i_private);
> +}
> +
> +static int tegra_suspend_debug_write(struct file *file,
> +	const char __user *user_buf, size_t count, loff_t *ppos)
> +{
> +	char buf[32];
> +	int buf_size;
> +	int i;
> +	struct seq_file *s = file->private_data;
> +	enum tegra_suspend_mode *val = s->private;
> +
> +	memset(buf, 0x00, sizeof(buf));
> +	buf_size = min(count, (sizeof(buf)-1));
> +	if (copy_from_user(buf, user_buf, buf_size))
> +		return -EFAULT;
> +
> +	for (i = 0; i < TEGRA_MAX_SUSPEND_MODE; i++) {
> +		if (!strnicmp(buf, tegra_suspend_name[i],
> +		    strlen(tegra_suspend_name[i]))) {
> +			if (i > pdata->suspend_mode)
> +				return -EINVAL;
> +			*val = i;
> +			return count;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static const struct file_operations tegra_suspend_debug_fops = {
> +	.open		= tegra_suspend_debug_open,
> +	.write		= tegra_suspend_debug_write,
> +	.read		= seq_read,
> +	.llseek		= seq_lseek,
> +	.release	= single_release,
> +};
> +
> +static int tegra_suspend_time_debug_show(struct seq_file *s, void *data)
> +{
> +	int bin;
> +	seq_printf(s, "time (secs)  count\n");
> +	seq_printf(s, "------------------\n");
> +	for (bin = 0; bin < 32; bin++) {
> +		if (tegra_time_in_suspend[bin] == 0)
> +			continue;
> +		seq_printf(s, "%4d - %4d %4u\n",
> +			bin ? 1 << (bin - 1) : 0, 1 << bin,
> +			tegra_time_in_suspend[bin]);
> +	}
> +	return 0;
> +}
> +
> +static int tegra_suspend_time_debug_open(struct inode *inode, struct file *file)
> +{
> +	return single_open(file, tegra_suspend_time_debug_show, NULL);
> +}
> +
> +static const struct file_operations tegra_suspend_time_debug_fops = {
> +	.open		= tegra_suspend_time_debug_open,
> +	.read		= seq_read,
> +	.llseek		= seq_lseek,
> +	.release	= single_release,
> +};
> +
> +static int __init tegra_suspend_debug_init(void)
> +{
> +	struct dentry *d;
> +
> +	d = debugfs_create_file("suspend_mode", 0755, NULL,
> +		(void *)&current_suspend_mode, &tegra_suspend_debug_fops);
> +	if (!d) {
> +		pr_info("Failed to create suspend_mode debug file\n");
> +		return -ENOMEM;
> +	}
> +
> +	d = debugfs_create_file("suspend_time", 0755, NULL, NULL,
> +		&tegra_suspend_time_debug_fops);
> +	if (!d) {
> +		pr_info("Failed to create suspend_time debug file\n");
> +		return -ENOMEM;
> +	}
> +
> +	return 0;
> +}
> +
> +late_initcall(tegra_suspend_debug_init);
> +#endif
> diff --git a/arch/arm/mach-tegra/tegra2_save.S b/arch/arm/mach-tegra/tegra2_save.S
> new file mode 100644
> index 0000000..91f2ba0
> --- /dev/null
> +++ b/arch/arm/mach-tegra/tegra2_save.S
> @@ -0,0 +1,413 @@
> +/*
> + * arch/arm/mach-tegra/tegra2_save.S
> + *
> + * CPU state save & restore routines for CPU hotplug
> + *
> + * Copyright (c) 2010, NVIDIA Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write to the Free Software Foundation, Inc.,
> + * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
> + */
> +
> +#include <linux/linkage.h>
> +#include <linux/init.h>
> +
> +#include <asm/assembler.h>
> +#include <asm/domain.h>
> +#include <asm/ptrace.h>
> +#include <asm/cache.h>
> +#include <asm/vfpmacros.h>
> +#include <asm/memory.h>
> +#include <asm/hardware/cache-l2x0.h>
> +
> +#include <mach/iomap.h>
> +#include <mach/io.h>
> +
> +#include "power.h"
> +
> +/*	.section ".cpuinit.text", "ax"*/
> +
> +#define TTB_FLAGS 0x6A	@ IRGN_WBWA, OC_RGN_WBWA, S, NOS
> +
> +#define EMC_CFG				0xc
> +#define EMC_ADR_CFG			0x10
> +#define EMC_REFRESH			0x70
> +#define EMC_NOP				0xdc
> +#define EMC_SELF_REF			0xe0
> +#define EMC_REQ_CTRL			0x2b0
> +#define EMC_EMC_STATUS			0x2b4
> +
> +#define PMC_CTRL			0x0
> +#define PMC_CTRL_BFI_SHIFT		8
> +#define PMC_CTRL_BFI_WIDTH		9
> +#define PMC_SCRATCH38			0x134
> +#define PMC_SCRATCH41			0x140
> +
> +#define CLK_RESET_CCLK_BURST		0x20
> +#define CLK_RESET_CCLK_DIVIDER		0x24
> +#define CLK_RESET_SCLK_BURST		0x28
> +#define CLK_RESET_SCLK_DIVIDER		0x2c
> +
> +#define CLK_RESET_PLLC_BASE		0x80
> +#define CLK_RESET_PLLM_BASE		0x90
> +#define CLK_RESET_PLLP_BASE		0xa0
> +
> +#define FLOW_CTRL_HALT_CPU_EVENTS	0x0
> +
> +#include "power-macros.S"
> +
> +.macro emc_device_mask, rd, base
> +	ldr	\rd, [\base, #EMC_ADR_CFG]
> +	tst	\rd, #(0x3<<24)
> +	moveq	\rd, #(0x1<<8)		@ just 1 device
> +	movne	\rd, #(0x3<<8)		@ 2 devices
> +.endm
> +
> +/*
> + *
> + *	__tear_down_master( r8 = context_pa, sp = power state )
> + *
> + *	  Set the clock burst policy to the selected wakeup source
> + *	  Enable CPU power-request mode in the PMC
> + *	  Put the CPU in wait-for-event mode on the flow controller
> + *	  Trigger the PMC state machine to put the CPU in reset
> + */
> +ENTRY(__tear_down_master)
> +__tear_down_master:
> +#ifdef CONFIG_CACHE_L2X0
> +	/* clean out the dirtied L2 lines, since all power transitions
> +	 * cause the cache state to get invalidated (although LP1 & LP2
> +	 * preserve the data in the L2, the control words (L2X0_CTRL,
> +	 * L2X0_AUX_CTRL, etc.) need to be cleaned to L3 so that they
> +	 * will be visible on reboot.  skip this for LP0, since the L2 cache
> +	 * will be shutdown before we reach this point */
> +	tst	sp, #TEGRA_POWER_EFFECT_LP0
> +	bne	__l2_clean_done
> +	mov32	r0, (TEGRA_ARM_PL310_BASE-IO_CPU_PHYS+IO_CPU_VIRT)
> +	add	r3, r8, #(CONTEXT_SIZE_BYTES)
> +	bic	r8, r8, #0x1f
> +	add	r3, r3, #0x1f
> +11:	str	r8, [r0, #L2X0_CLEAN_LINE_PA]
> +	add	r8, r8, #32
> +	cmp	r8, r3
> +	blo	11b
> +12:	ldr	r1, [r0, #L2X0_CLEAN_LINE_PA]
> +	tst	r1, #1
> +	bne	12b
> +	mov	r1, #0
> +	str	r1, [r0, #L2X0_CACHE_SYNC]
> +13:	ldr	r1, [r0, #L2X0_CACHE_SYNC]
> +	tst	r1, #1
> +	bne	13b
> +__l2_clean_done:
> +#endif
> +
> +	tst	sp, #TEGRA_POWER_SDRAM_SELFREFRESH
> +
> +	/* preload all the address literals that are needed for the
> +	 * CPU power-gating process, to avoid loads from SDRAM (which are
> +	 * not supported once SDRAM is put into self-refresh.
> +	 * LP0 / LP1 use physical address, since the MMU needs to be
> +	 * disabled before putting SDRAM into self-refresh to avoid
> +	 * memory access due to page table walks */
> +	mov32	r0, (IO_APB_VIRT-IO_APB_PHYS)
> +	mov32	r4, TEGRA_PMC_BASE
> +	mov32	r0, (IO_PPSB_VIRT-IO_PPSB_PHYS)
> +	mov32	r5, TEGRA_CLK_RESET_BASE
> +	mov32	r6, TEGRA_FLOW_CTRL_BASE
> +	mov32	r7, TEGRA_TMRUS_BASE
> +
> +	/* change page table pointer to tegra_pgd_phys, so that IRAM
> +	 * and MMU shut-off will be mapped virtual == physical */
> +	adr	r3, __tear_down_master_data
> +	ldr	r3, [r3]		@ &tegra_pgd_phys
> +	ldr	r3, [r3]
> +	orr	r3, r3, #TTB_FLAGS
> +	mov	r2, #0
> +	mcr	p15, 0, r2, c13, c0, 1	@ reserved context
> +	isb
> +	mcr	p15, 0, r3, c2, c0, 0	@ TTB 0
> +	isb
> +
> +	/* Obtain LP1 information.
> +	 * R10 = LP1 branch target */
> +	mov32	r2, __tegra_lp1_reset
> +	mov32	r3, __tear_down_master_sdram
> +	sub	r2, r3, r2
> +	mov32	r3, (TEGRA_IRAM_CODE_AREA)
> +	add	r10, r2, r3
> +
> +	mov32	r3, __shut_off_mmu
> +
> +	/* R9 = LP2 branch target */
> +	mov32	r9, __tear_down_master_pll_cpu
> +
> +	/* Convert the branch targets
> +	 * to physical addresses */
> +	sub	r3, r3, #(PAGE_OFFSET - PHYS_OFFSET)
> +	sub	r9, r9, #(PAGE_OFFSET - PHYS_OFFSET)
> +	movne	r9, r10
> +	bx	r3
> +ENDPROC(__tear_down_master)
> +	.type	__tear_down_master_data, %object
> +__tear_down_master_data:
> +	.long	tegra_pgd_phys
> +	.size	__tear_down_master_data, . - __tear_down_master_data
> +
> +/*  START OF ROUTINES COPIED TO IRAM  */
> +/*
> + *	__tegra_lp1_reset
> + *
> + *	  reset vector for LP1 restore; copied into IRAM during suspend.
> + *	  brings the system back up to a safe starting point (SDRAM out of
> + *	  self-refresh, PLLC, PLLM and PLLP reenabled, CPU running on PLLP,
> + *	  system clock running on the same PLL that it suspended at), and
> + *	  jumps to tegra_lp2_startup to restore PLLX and virtual addressing.
> + *	  physical address of tegra_lp2_startup expected to be stored in
> + *	  PMC_SCRATCH41
> + */
> +	.align L1_CACHE_SHIFT
> +ENTRY(__tegra_lp1_reset)
> +__tegra_lp1_reset:
> +	/* the CPU and system bus are running at 32KHz and executing from
> +	 * IRAM when this code is executed; immediately switch to CLKM and
> +	 * enable PLLP. */
> +	mov32	r0, TEGRA_CLK_RESET_BASE
> +	mov	r1, #(1<<28)
> +	str	r1, [r0, #CLK_RESET_SCLK_BURST]
> +	str	r1, [r0, #CLK_RESET_CCLK_BURST]
> +	mov	r1, #0
> +	str	r1, [r0, #CLK_RESET_SCLK_DIVIDER]
> +	str	r1, [r0, #CLK_RESET_CCLK_DIVIDER]
> +
> +	ldr	r1, [r0, #CLK_RESET_PLLM_BASE]
> +	tst	r1, #(1<<30)
> +	orreq	r1, r1, #(1<<30)
> +	streq	r1, [r0, #CLK_RESET_PLLM_BASE]
> +	ldr	r1, [r0, #CLK_RESET_PLLP_BASE]
> +	tst	r1, #(1<<30)
> +	orreq	r1, r1, #(1<<30)
> +	streq	r1, [r0, #CLK_RESET_PLLP_BASE]
> +	ldr	r1, [r0, #CLK_RESET_PLLC_BASE]
> +	tst	r1, #(1<<30)
> +	orreq	r1, r1, #(1<<30)
> +	streq	r1, [r0, #CLK_RESET_PLLC_BASE]
> +	mov32	r7, TEGRA_TMRUS_BASE
> +	ldr	r1, [r7]
> +
> +	/* since the optimized settings are still in SDRAM, there is
> +	 * no need to store them back into the IRAM-local __lp1_pad_area */
> +	add	r2, pc, #__lp1_pad_area-(.+8)
> +padload:ldmia	r2!, {r3-r4}
> +	cmp	r3, #0
> +	beq	padload_done
> +	str	r4, [r3]
> +	b	padload
> +padload_done:
> +	ldr	r2, [r7]
> +	add	r2, r2, #0x4	@ 4uS delay for DRAM pad restoration
> +	wait_until r2, r7, r3
> +	add	r1, r1, #0xff	@ 255uS delay for PLL stabilization
> +	wait_until r1, r7, r3
> +
> +	str	r4, [r0, #CLK_RESET_SCLK_BURST]
> +	mov32	r4, ((1<<28) | (4))	@ burst policy is PLLP
> +	str	r4, [r0, #CLK_RESET_CCLK_BURST]
> +
> +	mov32	r0, TEGRA_EMC_BASE
> +	ldr	r1, [r0, #EMC_CFG]
> +	bic	r1, r1, #(1<<31)	@ disable DRAM_CLK_STOP
> +	str	r1, [r0, #EMC_CFG]
> +
> +	mov	r1, #0
> +	str	r1, [r0, #EMC_SELF_REF]	@ take DRAM out of self refresh
> +	mov	r1, #1
> +	str	r1, [r0, #EMC_NOP]
> +	str	r1, [r0, #EMC_NOP]
> +	str	r1, [r0, #EMC_REFRESH]
> +
> +	emc_device_mask r1, r0
> +
> +exit_selfrefresh_loop:
> +	ldr	r2, [r0, #EMC_EMC_STATUS]
> +	ands	r2, r2, r1
> +	bne	exit_selfrefresh_loop
> +
> +	mov	r1, #0
> +	str	r1, [r0, #EMC_REQ_CTRL]
> +
> +	mov32	r0, TEGRA_PMC_BASE
> +	ldr	r0, [r0, #PMC_SCRATCH41]
> +	mov	pc, r0
> +ENDPROC(__tegra_lp1_reset)
> +
> +/*
> + *	__tear_down_master_sdram
> + *
> + *	  disables MMU, data cache, and puts SDRAM into self-refresh.
> + *	  must execute from IRAM.
> + */
> +	.align L1_CACHE_SHIFT
> +__tear_down_master_sdram:
> +	mov32	r1, TEGRA_EMC_BASE
> +	mov	r2, #3
> +	str	r2, [r1, #EMC_REQ_CTRL]		@ stall incoming DRAM requests
> +
> +emcidle:ldr	r2, [r1, #EMC_EMC_STATUS]
> +	tst	r2, #4
> +	beq	emcidle
> +
> +	mov	r2, #1
> +	str	r2, [r1, #EMC_SELF_REF]
> +
> +	emc_device_mask r2, r1
> +
> +emcself:ldr	r3, [r1, #EMC_EMC_STATUS]
> +	and	r3, r3, r2
> +	cmp	r3, r2
> +	bne	emcself				@ loop until DDR in self-refresh
> +
> +	add	r2, pc, #__lp1_pad_area-(.+8)
> +
> +padsave:ldm	r2, {r0-r1}
> +	cmp	r0, #0
> +	beq	padsave_done
> +	ldr	r3, [r0]
> +	str	r1, [r0]
> +	str	r3, [r2, #4]
> +	add	r2, r2, #8
> +	b	padsave
> +padsave_done:
> +
> +	ldr	r0, [r5, #CLK_RESET_SCLK_BURST]
> +	str	r0, [r2, #4]
> +	dsb
> +	b	__tear_down_master_pll_cpu
> +ENDPROC(__tear_down_master_sdram)
> +
> +	.align	L1_CACHE_SHIFT
> +	.type	__lp1_pad_area, %object
> +__lp1_pad_area:
> +	.word	TEGRA_APB_MISC_BASE + 0x8c8 /* XM2CFGCPADCTRL */
> +	.word	0x8
> +	.word	TEGRA_APB_MISC_BASE + 0x8cc /* XM2CFGDPADCTRL */
> +	.word	0x8
> +	.word	TEGRA_APB_MISC_BASE + 0x8d0 /* XM2CLKCFGPADCTRL */
> +	.word	0x0
> +	.word	TEGRA_APB_MISC_BASE + 0x8d4 /* XM2COMPPADCTRL */
> +	.word	0x8
> +	.word	TEGRA_APB_MISC_BASE + 0x8d8 /* XM2VTTGENPADCTRL */
> +	.word	0x5500
> +	.word	TEGRA_APB_MISC_BASE + 0x8e4 /* XM2CFGCPADCTRL2 */
> +	.word	0x08080040
> +	.word	TEGRA_APB_MISC_BASE + 0x8e8 /* XM2CFGDPADCTRL2 */
> +	.word	0x0
> +	.word	0x0	/* end of list */
> +	.word	0x0	/* sclk_burst_policy */
> +	.size	__lp1_pad_area, . - __lp1_pad_area
> +
> +	.align L1_CACHE_SHIFT
> +__tear_down_master_pll_cpu:
> +	ldr	r0, [r4, #PMC_CTRL]
> +	bfi	r0, sp, #PMC_CTRL_BFI_SHIFT, #PMC_CTRL_BFI_WIDTH
> +	str	r0, [r4, #PMC_CTRL]
> +	tst	sp, #TEGRA_POWER_SDRAM_SELFREFRESH
> +
> +	/* in LP2 idle (SDRAM active), set the CPU burst policy to PLLP */
> +	moveq	r0, #(2<<28)    /* burst policy = run mode */
> +	orreq	r0, r0, #(4<<4) /* use PLLP in run mode burst */
> +	streq	r0, [r5, #CLK_RESET_CCLK_BURST]
> +	moveq	r0, #0
> +	streq	r0, [r5, #CLK_RESET_CCLK_DIVIDER]
> +	beq	__cclk_burst_set
> +
> +	/* in other modes, set system & CPU burst policies to 32KHz.
> +	 * start by jumping to CLKM to safely disable PLLs, then jump
> +	 * to CLKS */
> +	mov	r0, #(1<<28)
> +	str	r0, [r5, #CLK_RESET_SCLK_BURST]
> +	str	r0, [r5, #CLK_RESET_CCLK_BURST]
> +	mov	r0, #0
> +	str	r0, [r5, #CLK_RESET_CCLK_DIVIDER]
> +	str	r0, [r5, #CLK_RESET_SCLK_DIVIDER]
> +
> +	/* 2 us delay between changing sclk and disabling PLLs */
> +	wait_for_us r1, r7, r9
> +	add	r1, r1, #2
> +	wait_until r1, r7, r9
> +
> +	/* switch to CLKS */
> +	mov	r0, #0	/* burst policy = 32KHz */
> +	str	r0, [r5, #CLK_RESET_SCLK_BURST]
> +
> +	/* disable PLLP, PLLM, PLLC in LP0 and LP1 states */
> +	ldr	r0, [r5, #CLK_RESET_PLLM_BASE]
> +	bic	r0, r0, #(1<<30)
> +	str	r0, [r5, #CLK_RESET_PLLM_BASE]
> +	ldr	r0, [r5, #CLK_RESET_PLLP_BASE]
> +	bic	r0, r0, #(1<<30)
> +	str	r0, [r5, #CLK_RESET_PLLP_BASE]
> +	ldr	r0, [r5, #CLK_RESET_PLLC_BASE]
> +	bic	r0, r0, #(1<<30)
> +	str	r0, [r5, #CLK_RESET_PLLC_BASE]
> +
> +__cclk_burst_set:
> +	mov	r0, #(4<<29)			/* STOP_UNTIL_IRQ */
> +	orr	r0, r0, #(1<<10) | (1<<8)	/* IRQ_0, FIQ_0	*/
> +	ldr	r1, [r7]
> +	str	r1, [r4, #PMC_SCRATCH38]
> +	dsb
> +	str	r0, [r6, #FLOW_CTRL_HALT_CPU_EVENTS]
> +	dsb
> +	ldr	r0, [r6, #FLOW_CTRL_HALT_CPU_EVENTS] /* memory barrier */
> +
> +halted:	dsb
> +	wfe	/* CPU should be power gated here */
> +	isb
> +	b	halted
> +ENDPROC(__tear_down_master_pll_cpu)
> +
> +/*
> + *	__put_cpu_in_reset(cpu_nr)
> + *
> + *	 puts the specified CPU in wait-for-event mode on the flow controller
> + *	 and puts the CPU in reset
> + */
> +ENTRY(__put_cpu_in_reset)
> +__put_cpu_in_reset:
> +	cmp	r0, #0
> +	subne	r1, r0, #1
> +	movne	r1, r1, lsl #3
> +	addne	r1, r1, #0x14
> +	moveq	r1, #0			@ r1 = CPUx_HALT_EVENTS register offset
> +	mov32	r7, (TEGRA_FLOW_CTRL_BASE-IO_PPSB_PHYS+IO_PPSB_VIRT)
> +	mov	r2, #(0x2<<29)
> +	str	r2, [r7, r1]		@ put flow controller in wait event mode
> +	isb
> +	dsb
> +	movw	r1, 0x1011
> +	mov	r1, r1, lsl r0
> +	mov32	r7, (TEGRA_CLK_RESET_BASE-IO_PPSB_PHYS+IO_PPSB_VIRT)
> +	str	r1, [r7, #0x340]	@ put slave CPU in reset
> +	isb
> +	dsb
> +	b	.
> +ENDPROC(__put_cpu_in_reset)
> +
> +/* dummy symbol for end of IRAM */
> +	.align L1_CACHE_SHIFT
> +ENTRY(__tegra_iram_end)
> +__tegra_iram_end:
> +	b	.
> +ENDPROC(__tegra_iram_end)
> -- 
> 1.7.3.1
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel



More information about the linux-arm-kernel mailing list