1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
2 | /* |
3 | * Copyright 2014 Freescale Semiconductor, Inc. |
4 | */ |
5 | |
6 | #include <linux/linkage.h> |
7 | #include <asm/assembler.h> |
8 | #include <asm/asm-offsets.h> |
9 | #include <asm/hardware/cache-l2x0.h> |
10 | #include "hardware.h" |
11 | |
12 | .arch armv7-a |
13 | |
14 | /* |
15 | * ==================== low level suspend ==================== |
16 | * |
17 | * Better to follow below rules to use ARM registers: |
18 | * r0: pm_info structure address; |
19 | * r1 ~ r4: for saving pm_info members; |
20 | * r5 ~ r10: free registers; |
21 | * r11: io base address. |
22 | * |
23 | * suspend ocram space layout: |
24 | * ======================== high address ====================== |
25 | * . |
26 | * . |
27 | * . |
28 | * ^ |
29 | * ^ |
30 | * ^ |
31 | * imx6_suspend code |
32 | * PM_INFO structure(imx6_cpu_pm_info) |
33 | * ======================== low address ======================= |
34 | */ |
35 | |
36 | /* |
37 | * Below offsets are based on struct imx6_cpu_pm_info |
38 | * which defined in arch/arm/mach-imx/pm-imx6q.c, this |
39 | * structure contains necessary pm info for low level |
40 | * suspend related code. |
41 | */ |
42 | #define PM_INFO_PBASE_OFFSET 0x0 |
43 | #define PM_INFO_RESUME_ADDR_OFFSET 0x4 |
44 | #define PM_INFO_DDR_TYPE_OFFSET 0x8 |
45 | #define PM_INFO_PM_INFO_SIZE_OFFSET 0xC |
46 | #define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 |
47 | #define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 |
48 | #define PM_INFO_MX6Q_SRC_P_OFFSET 0x18 |
49 | #define PM_INFO_MX6Q_SRC_V_OFFSET 0x1C |
50 | #define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x20 |
51 | #define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x24 |
52 | #define PM_INFO_MX6Q_CCM_P_OFFSET 0x28 |
53 | #define PM_INFO_MX6Q_CCM_V_OFFSET 0x2C |
54 | #define PM_INFO_MX6Q_GPC_P_OFFSET 0x30 |
55 | #define PM_INFO_MX6Q_GPC_V_OFFSET 0x34 |
56 | #define PM_INFO_MX6Q_L2_P_OFFSET 0x38 |
57 | #define PM_INFO_MX6Q_L2_V_OFFSET 0x3C |
58 | #define PM_INFO_MMDC_IO_NUM_OFFSET 0x40 |
59 | #define PM_INFO_MMDC_IO_VAL_OFFSET 0x44 |
60 | |
61 | #define MX6Q_SRC_GPR1 0x20 |
62 | #define MX6Q_SRC_GPR2 0x24 |
63 | #define MX6Q_MMDC_MAPSR 0x404 |
64 | #define MX6Q_MMDC_MPDGCTRL0 0x83c |
65 | #define MX6Q_GPC_IMR1 0x08 |
66 | #define MX6Q_GPC_IMR2 0x0c |
67 | #define MX6Q_GPC_IMR3 0x10 |
68 | #define MX6Q_GPC_IMR4 0x14 |
69 | #define MX6Q_CCM_CCR 0x0 |
70 | |
71 | .align 3 |
72 | .arm |
73 | |
74 | .macro sync_l2_cache |
75 | |
76 | /* sync L2 cache to drain L2's buffers to DRAM. */ |
77 | #ifdef CONFIG_CACHE_L2X0 |
78 | ldr r11, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] |
79 | teq r11, #0 |
80 | beq 6f |
81 | mov r6, #0x0 |
82 | str r6, [r11, #L2X0_CACHE_SYNC] |
83 | 1: |
84 | ldr r6, [r11, #L2X0_CACHE_SYNC] |
85 | ands r6, r6, #0x1 |
86 | bne 1b |
87 | 6: |
88 | #endif |
89 | |
90 | .endm |
91 | |
92 | .macro resume_mmdc |
93 | |
94 | /* restore MMDC IO */ |
95 | cmp r5, #0x0 |
96 | ldreq r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] |
97 | ldrne r11, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] |
98 | |
99 | ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] |
100 | ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET |
101 | add r7, r7, r0 |
102 | 1: |
103 | ldr r8, [r7], #0x4 |
104 | ldr r9, [r7], #0x4 |
105 | str r9, [r11, r8] |
106 | subs r6, r6, #0x1 |
107 | bne 1b |
108 | |
109 | cmp r5, #0x0 |
110 | ldreq r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] |
111 | ldrne r11, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] |
112 | |
113 | cmp r3, #IMX_DDR_TYPE_LPDDR2 |
114 | bne 4f |
115 | |
116 | /* reset read FIFO, RST_RD_FIFO */ |
117 | ldr r7, =MX6Q_MMDC_MPDGCTRL0 |
118 | ldr r6, [r11, r7] |
119 | orr r6, r6, #(1 << 31) |
120 | str r6, [r11, r7] |
121 | 2: |
122 | ldr r6, [r11, r7] |
123 | ands r6, r6, #(1 << 31) |
124 | bne 2b |
125 | |
126 | /* reset FIFO a second time */ |
127 | ldr r6, [r11, r7] |
128 | orr r6, r6, #(1 << 31) |
129 | str r6, [r11, r7] |
130 | 3: |
131 | ldr r6, [r11, r7] |
132 | ands r6, r6, #(1 << 31) |
133 | bne 3b |
134 | 4: |
135 | /* let DDR out of self-refresh */ |
136 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] |
137 | bic r7, r7, #(1 << 21) |
138 | str r7, [r11, #MX6Q_MMDC_MAPSR] |
139 | 5: |
140 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] |
141 | ands r7, r7, #(1 << 25) |
142 | bne 5b |
143 | |
144 | /* enable DDR auto power saving */ |
145 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] |
146 | bic r7, r7, #0x1 |
147 | str r7, [r11, #MX6Q_MMDC_MAPSR] |
148 | |
149 | .endm |
150 | |
151 | ENTRY(imx6_suspend) |
152 | ldr r1, [r0, #PM_INFO_PBASE_OFFSET] |
153 | ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET] |
154 | ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] |
155 | ldr r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] |
156 | |
157 | /* |
158 | * counting the resume address in iram |
159 | * to set it in SRC register. |
160 | */ |
161 | ldr r6, =imx6_suspend |
162 | ldr r7, =resume |
163 | sub r7, r7, r6 |
164 | add r8, r1, r4 |
165 | add r9, r8, r7 |
166 | |
167 | /* |
168 | * make sure TLB contain the addr we want, |
169 | * as we will access them after MMDC IO floated. |
170 | */ |
171 | |
172 | ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] |
173 | ldr r6, [r11, #0x0] |
174 | ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] |
175 | ldr r6, [r11, #0x0] |
176 | ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] |
177 | ldr r6, [r11, #0x0] |
178 | |
179 | /* use r11 to store the IO address */ |
180 | ldr r11, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] |
181 | /* store physical resume addr and pm_info address. */ |
182 | str r9, [r11, #MX6Q_SRC_GPR1] |
183 | str r1, [r11, #MX6Q_SRC_GPR2] |
184 | |
185 | /* need to sync L2 cache before DSM. */ |
186 | sync_l2_cache |
187 | |
188 | ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] |
189 | /* |
190 | * put DDR explicitly into self-refresh and |
191 | * disable automatic power savings. |
192 | */ |
193 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] |
194 | orr r7, r7, #0x1 |
195 | str r7, [r11, #MX6Q_MMDC_MAPSR] |
196 | |
197 | /* make the DDR explicitly enter self-refresh. */ |
198 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] |
199 | orr r7, r7, #(1 << 21) |
200 | str r7, [r11, #MX6Q_MMDC_MAPSR] |
201 | |
202 | poll_dvfs_set: |
203 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] |
204 | ands r7, r7, #(1 << 25) |
205 | beq poll_dvfs_set |
206 | |
207 | ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] |
208 | ldr r6, =0x0 |
209 | ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] |
210 | ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET |
211 | add r8, r8, r0 |
212 | /* LPDDR2's last 3 IOs need special setting */ |
213 | cmp r3, #IMX_DDR_TYPE_LPDDR2 |
214 | subeq r7, r7, #0x3 |
215 | set_mmdc_io_lpm: |
216 | ldr r9, [r8], #0x8 |
217 | str r6, [r11, r9] |
218 | subs r7, r7, #0x1 |
219 | bne set_mmdc_io_lpm |
220 | |
221 | cmp r3, #IMX_DDR_TYPE_LPDDR2 |
222 | bne set_mmdc_io_lpm_done |
223 | ldr r6, =0x1000 |
224 | ldr r9, [r8], #0x8 |
225 | str r6, [r11, r9] |
226 | ldr r9, [r8], #0x8 |
227 | str r6, [r11, r9] |
228 | ldr r6, =0x80000 |
229 | ldr r9, [r8] |
230 | str r6, [r11, r9] |
231 | set_mmdc_io_lpm_done: |
232 | |
233 | /* |
234 | * mask all GPC interrupts before |
235 | * enabling the RBC counters to |
236 | * avoid the counter starting too |
237 | * early if an interupt is already |
238 | * pending. |
239 | */ |
240 | ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] |
241 | ldr r6, [r11, #MX6Q_GPC_IMR1] |
242 | ldr r7, [r11, #MX6Q_GPC_IMR2] |
243 | ldr r8, [r11, #MX6Q_GPC_IMR3] |
244 | ldr r9, [r11, #MX6Q_GPC_IMR4] |
245 | |
246 | ldr r10, =0xffffffff |
247 | str r10, [r11, #MX6Q_GPC_IMR1] |
248 | str r10, [r11, #MX6Q_GPC_IMR2] |
249 | str r10, [r11, #MX6Q_GPC_IMR3] |
250 | str r10, [r11, #MX6Q_GPC_IMR4] |
251 | |
252 | /* |
253 | * enable the RBC bypass counter here |
254 | * to hold off the interrupts. RBC counter |
255 | * = 32 (1ms), Minimum RBC delay should be |
256 | * 400us for the analog LDOs to power down. |
257 | */ |
258 | ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] |
259 | ldr r10, [r11, #MX6Q_CCM_CCR] |
260 | bic r10, r10, #(0x3f << 21) |
261 | orr r10, r10, #(0x20 << 21) |
262 | str r10, [r11, #MX6Q_CCM_CCR] |
263 | |
264 | /* enable the counter. */ |
265 | ldr r10, [r11, #MX6Q_CCM_CCR] |
266 | orr r10, r10, #(0x1 << 27) |
267 | str r10, [r11, #MX6Q_CCM_CCR] |
268 | |
269 | /* unmask all the GPC interrupts. */ |
270 | ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] |
271 | str r6, [r11, #MX6Q_GPC_IMR1] |
272 | str r7, [r11, #MX6Q_GPC_IMR2] |
273 | str r8, [r11, #MX6Q_GPC_IMR3] |
274 | str r9, [r11, #MX6Q_GPC_IMR4] |
275 | |
276 | /* |
277 | * now delay for a short while (3usec) |
278 | * ARM is at 1GHz at this point |
279 | * so a short loop should be enough. |
280 | * this delay is required to ensure that |
281 | * the RBC counter can start counting in |
282 | * case an interrupt is already pending |
283 | * or in case an interrupt arrives just |
284 | * as ARM is about to assert DSM_request. |
285 | */ |
286 | ldr r6, =2000 |
287 | rbc_loop: |
288 | subs r6, r6, #0x1 |
289 | bne rbc_loop |
290 | |
291 | /* Zzz, enter stop mode */ |
292 | wfi |
293 | nop |
294 | nop |
295 | nop |
296 | nop |
297 | |
298 | /* |
299 | * run to here means there is pending |
300 | * wakeup source, system should auto |
301 | * resume, we need to restore MMDC IO first |
302 | */ |
303 | mov r5, #0x0 |
304 | resume_mmdc |
305 | |
306 | /* return to suspend finish */ |
307 | ret lr |
308 | |
309 | resume: |
310 | /* invalidate L1 I-cache first */ |
311 | mov r6, #0x0 |
312 | mcr p15, 0, r6, c7, c5, 0 |
313 | mcr p15, 0, r6, c7, c5, 6 |
314 | /* enable the Icache and branch prediction */ |
315 | mov r6, #0x1800 |
316 | mcr p15, 0, r6, c1, c0, 0 |
317 | isb |
318 | |
319 | /* get physical resume address from pm_info. */ |
320 | ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] |
321 | /* clear core0's entry and parameter */ |
322 | ldr r11, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] |
323 | mov r7, #0x0 |
324 | str r7, [r11, #MX6Q_SRC_GPR1] |
325 | str r7, [r11, #MX6Q_SRC_GPR2] |
326 | |
327 | ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] |
328 | mov r5, #0x1 |
329 | resume_mmdc |
330 | |
331 | ret lr |
332 | ENDPROC(imx6_suspend) |
333 | |