/* * arch/arm/mach-lpc32xx/suspend.S * * Original authors: Dmitry Chigirev, Vitaly Wool <source@mvista.com> * Modified by Kevin Wells <kevin.wells@nxp.com> * * 2005 (c) MontaVista Software, Inc. This file is licensed under * the terms of the GNU General Public License version 2. This program * is licensed "as is" without any warranty of any kind, whether express * or implied. */ #include <linux/linkage.h> #include <asm/assembler.h> #include <mach/platform.h> #include <mach/hardware.h> /* Using named register defines makes the code easier to follow */ #define WORK1_REG r0 #define WORK2_REG r1 #define SAVED_HCLK_DIV_REG r2 #define SAVED_HCLK_PLL_REG r3 #define SAVED_DRAM_CLKCTRL_REG r4 #define SAVED_PWR_CTRL_REG r5 #define CLKPWRBASE_REG r6 #define EMCBASE_REG r7 #define LPC32XX_EMC_STATUS_OFFS 0x04 #define LPC32XX_EMC_STATUS_BUSY 0x1 #define LPC32XX_EMC_STATUS_SELF_RFSH 0x4 #define LPC32XX_CLKPWR_PWR_CTRL_OFFS 0x44 #define LPC32XX_CLKPWR_HCLK_DIV_OFFS 0x40 #define LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS 0x58 #define CLKPWR_PCLK_DIV_MASK 0xFFFFFE7F .text ENTRY(lpc32xx_sys_suspend) @ Save a copy of the used registers in IRAM, r0 is corrupted adr r0, tmp_stack_end stmfd r0!, {r3 - r7, sp, lr} @ Load a few common register addresses adr WORK1_REG, reg_bases ldr CLKPWRBASE_REG, [WORK1_REG, #0] ldr EMCBASE_REG, [WORK1_REG, #4] ldr SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\ #LPC32XX_CLKPWR_PWR_CTRL_OFFS] orr WORK1_REG, SAVED_PWR_CTRL_REG, #LPC32XX_CLKPWR_SDRAM_SELF_RFSH @ Wait for SDRAM busy status to go busy and then idle @ This guarantees a small windows where DRAM isn't busy 1: ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS] and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_BUSY cmp WORK2_REG, #LPC32XX_EMC_STATUS_BUSY bne 1b @ Branch while idle 2: ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS] and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_BUSY cmp WORK2_REG, #LPC32XX_EMC_STATUS_BUSY beq 2b @ Branch until idle @ Setup self-refresh with support for manual exit of @ self-refresh mode str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] orr WORK2_REG, WORK1_REG, #LPC32XX_CLKPWR_UPD_SDRAM_SELF_RFSH str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] @ Wait for self-refresh acknowledge, clocks to the DRAM device @ will automatically stop on start of self-refresh 3: ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS] and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH cmp WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH bne 3b @ Branch until self-refresh mode starts @ Enter direct-run mode from run mode bic WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_SELECT_RUN_MODE str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] @ Safe disable of DRAM clock in EMC block, prevents DDR sync @ issues on restart ldr SAVED_HCLK_DIV_REG, [CLKPWRBASE_REG,\ #LPC32XX_CLKPWR_HCLK_DIV_OFFS] and WORK2_REG, SAVED_HCLK_DIV_REG, #CLKPWR_PCLK_DIV_MASK str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLK_DIV_OFFS] @ Save HCLK PLL state and disable HCLK PLL ldr SAVED_HCLK_PLL_REG, [CLKPWRBASE_REG,\ #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS] bic WORK2_REG, SAVED_HCLK_PLL_REG, #LPC32XX_CLKPWR_HCLKPLL_POWER_UP str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS] @ Enter stop mode until an enabled event occurs orr WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_STOP_MODE_CTRL str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] .rept 9 nop .endr @ Clear stop status bic WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_STOP_MODE_CTRL @ Restore original HCLK PLL value and wait for PLL lock str SAVED_HCLK_PLL_REG, [CLKPWRBASE_REG,\ #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS] 4: ldr WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS] and WORK2_REG, WORK2_REG, #LPC32XX_CLKPWR_HCLKPLL_PLL_STS bne 4b @ Re-enter run mode with self-refresh flag cleared, but no DRAM @ update yet. DRAM is still in self-refresh str SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\ #LPC32XX_CLKPWR_PWR_CTRL_OFFS] @ Restore original DRAM clock mode to restore DRAM clocks str SAVED_HCLK_DIV_REG, [CLKPWRBASE_REG,\ #LPC32XX_CLKPWR_HCLK_DIV_OFFS] @ Clear self-refresh mode orr WORK1_REG, SAVED_PWR_CTRL_REG,\ #LPC32XX_CLKPWR_UPD_SDRAM_SELF_RFSH str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] str SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\ #LPC32XX_CLKPWR_PWR_CTRL_OFFS] @ Wait for EMC to clear self-refresh mode 5: ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS] and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH bne 5b @ Branch until self-refresh has exited @ restore regs and return adr r0, tmp_stack ldmfd r0!, {r3 - r7, sp, pc} reg_bases: .long IO_ADDRESS(LPC32XX_CLK_PM_BASE) .long IO_ADDRESS(LPC32XX_EMC_BASE) tmp_stack: .long 0, 0, 0, 0, 0, 0, 0 tmp_stack_end: ENTRY(lpc32xx_sys_suspend_sz) .word . - lpc32xx_sys_suspend