suspend.S 4.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
/*
 * 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