summaryrefslogtreecommitdiffstats
path: root/kernel/arch/arm/mach-lpc32xx/suspend.S
blob: 374f9f07fe480ead6bac9c1c79378c91f61f1b84 (plain)
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