1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
2 | /* |
3 | * arch/arm/mach-lpc32xx/suspend.S |
4 | * |
5 | * Original authors: Dmitry Chigirev, Vitaly Wool <source@mvista.com> |
6 | * Modified by Kevin Wells <kevin.wells@nxp.com> |
7 | * |
8 | * 2005 (c) MontaVista Software, Inc. |
9 | */ |
10 | #include <linux/linkage.h> |
11 | #include <asm/assembler.h> |
12 | #include "lpc32xx.h" |
13 | |
14 | /* Using named register defines makes the code easier to follow */ |
15 | #define WORK1_REG r0 |
16 | #define WORK2_REG r1 |
17 | #define SAVED_HCLK_DIV_REG r2 |
18 | #define SAVED_HCLK_PLL_REG r3 |
19 | #define SAVED_DRAM_CLKCTRL_REG r4 |
20 | #define SAVED_PWR_CTRL_REG r5 |
21 | #define CLKPWRBASE_REG r6 |
22 | #define EMCBASE_REG r7 |
23 | |
24 | #define LPC32XX_EMC_STATUS_OFFS 0x04 |
25 | #define LPC32XX_EMC_STATUS_BUSY 0x1 |
26 | #define LPC32XX_EMC_STATUS_SELF_RFSH 0x4 |
27 | |
28 | #define LPC32XX_CLKPWR_PWR_CTRL_OFFS 0x44 |
29 | #define LPC32XX_CLKPWR_HCLK_DIV_OFFS 0x40 |
30 | #define LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS 0x58 |
31 | |
32 | #define CLKPWR_PCLK_DIV_MASK 0xFFFFFE7F |
33 | |
34 | .text |
35 | |
36 | ENTRY(lpc32xx_sys_suspend) |
37 | @ Save a copy of the used registers in IRAM, r0 is corrupted |
38 | adr r0, tmp_stack_end |
39 | stmfd r0!, {r3 - r7, sp, lr} |
40 | |
41 | @ Load a few common register addresses |
42 | adr WORK1_REG, reg_bases |
43 | ldr CLKPWRBASE_REG, [WORK1_REG, #0] |
44 | ldr EMCBASE_REG, [WORK1_REG, #4] |
45 | |
46 | ldr SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\ |
47 | #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
48 | orr WORK1_REG, SAVED_PWR_CTRL_REG, #LPC32XX_CLKPWR_SDRAM_SELF_RFSH |
49 | |
50 | @ Wait for SDRAM busy status to go busy and then idle |
51 | @ This guarantees a small windows where DRAM isn't busy |
52 | 1: |
53 | ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS] |
54 | and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_BUSY |
55 | cmp WORK2_REG, #LPC32XX_EMC_STATUS_BUSY |
56 | bne 1b @ Branch while idle |
57 | 2: |
58 | ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS] |
59 | and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_BUSY |
60 | cmp WORK2_REG, #LPC32XX_EMC_STATUS_BUSY |
61 | beq 2b @ Branch until idle |
62 | |
63 | @ Setup self-refresh with support for manual exit of |
64 | @ self-refresh mode |
65 | str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
66 | orr WORK2_REG, WORK1_REG, #LPC32XX_CLKPWR_UPD_SDRAM_SELF_RFSH |
67 | str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
68 | str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
69 | |
70 | @ Wait for self-refresh acknowledge, clocks to the DRAM device |
71 | @ will automatically stop on start of self-refresh |
72 | 3: |
73 | ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS] |
74 | and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH |
75 | cmp WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH |
76 | bne 3b @ Branch until self-refresh mode starts |
77 | |
78 | @ Enter direct-run mode from run mode |
79 | bic WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_SELECT_RUN_MODE |
80 | str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
81 | |
82 | @ Safe disable of DRAM clock in EMC block, prevents DDR sync |
83 | @ issues on restart |
84 | ldr SAVED_HCLK_DIV_REG, [CLKPWRBASE_REG,\ |
85 | #LPC32XX_CLKPWR_HCLK_DIV_OFFS] |
86 | and WORK2_REG, SAVED_HCLK_DIV_REG, #CLKPWR_PCLK_DIV_MASK |
87 | str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLK_DIV_OFFS] |
88 | |
89 | @ Save HCLK PLL state and disable HCLK PLL |
90 | ldr SAVED_HCLK_PLL_REG, [CLKPWRBASE_REG,\ |
91 | #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS] |
92 | bic WORK2_REG, SAVED_HCLK_PLL_REG, #LPC32XX_CLKPWR_HCLKPLL_POWER_UP |
93 | str WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS] |
94 | |
95 | @ Enter stop mode until an enabled event occurs |
96 | orr WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_STOP_MODE_CTRL |
97 | str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
98 | .rept 9 |
99 | nop |
100 | .endr |
101 | |
102 | @ Clear stop status |
103 | bic WORK1_REG, WORK1_REG, #LPC32XX_CLKPWR_STOP_MODE_CTRL |
104 | |
105 | @ Restore original HCLK PLL value and wait for PLL lock |
106 | str SAVED_HCLK_PLL_REG, [CLKPWRBASE_REG,\ |
107 | #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS] |
108 | 4: |
109 | ldr WORK2_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_HCLKPLL_CTRL_OFFS] |
110 | and WORK2_REG, WORK2_REG, #LPC32XX_CLKPWR_HCLKPLL_PLL_STS |
111 | bne 4b |
112 | |
113 | @ Re-enter run mode with self-refresh flag cleared, but no DRAM |
114 | @ update yet. DRAM is still in self-refresh |
115 | str SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\ |
116 | #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
117 | |
118 | @ Restore original DRAM clock mode to restore DRAM clocks |
119 | str SAVED_HCLK_DIV_REG, [CLKPWRBASE_REG,\ |
120 | #LPC32XX_CLKPWR_HCLK_DIV_OFFS] |
121 | |
122 | @ Clear self-refresh mode |
123 | orr WORK1_REG, SAVED_PWR_CTRL_REG,\ |
124 | #LPC32XX_CLKPWR_UPD_SDRAM_SELF_RFSH |
125 | str WORK1_REG, [CLKPWRBASE_REG, #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
126 | str SAVED_PWR_CTRL_REG, [CLKPWRBASE_REG,\ |
127 | #LPC32XX_CLKPWR_PWR_CTRL_OFFS] |
128 | |
129 | @ Wait for EMC to clear self-refresh mode |
130 | 5: |
131 | ldr WORK2_REG, [EMCBASE_REG, #LPC32XX_EMC_STATUS_OFFS] |
132 | and WORK2_REG, WORK2_REG, #LPC32XX_EMC_STATUS_SELF_RFSH |
133 | bne 5b @ Branch until self-refresh has exited |
134 | |
135 | @ restore regs and return |
136 | adr r0, tmp_stack |
137 | ldmfd r0!, {r3 - r7, sp, pc} |
138 | |
139 | reg_bases: |
140 | .long IO_ADDRESS(LPC32XX_CLK_PM_BASE) |
141 | .long IO_ADDRESS(LPC32XX_EMC_BASE) |
142 | |
143 | tmp_stack: |
144 | .long 0, 0, 0, 0, 0, 0, 0 |
145 | tmp_stack_end: |
146 | |
147 | ENTRY(lpc32xx_sys_suspend_sz) |
148 | .word . - lpc32xx_sys_suspend |
149 | |