Commit | Line | Data |
---|---|---|
0fdebc5e | 1 | // SPDX-License-Identifier: GPL-2.0-only |
8446be5d TP |
2 | /* |
3 | * Suspend/resume support. Currently supporting Armada XP only. | |
4 | * | |
5 | * Copyright (C) 2014 Marvell | |
6 | * | |
7 | * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> | |
8446be5d TP |
8 | */ |
9 | ||
10 | #include <linux/cpu_pm.h> | |
11 | #include <linux/delay.h> | |
12 | #include <linux/gpio.h> | |
13 | #include <linux/io.h> | |
14 | #include <linux/kernel.h> | |
15 | #include <linux/mbus.h> | |
16 | #include <linux/of_address.h> | |
17 | #include <linux/suspend.h> | |
18 | #include <asm/cacheflush.h> | |
19 | #include <asm/outercache.h> | |
20 | #include <asm/suspend.h> | |
21 | ||
22 | #include "coherency.h" | |
43043a55 | 23 | #include "common.h" |
8446be5d TP |
24 | #include "pmsu.h" |
25 | ||
26 | #define SDRAM_CONFIG_OFFS 0x0 | |
27 | #define SDRAM_CONFIG_SR_MODE_BIT BIT(24) | |
28 | #define SDRAM_OPERATION_OFFS 0x18 | |
29 | #define SDRAM_OPERATION_SELF_REFRESH 0x7 | |
30 | #define SDRAM_DLB_EVICTION_OFFS 0x30c | |
31 | #define SDRAM_DLB_EVICTION_THRESHOLD_MASK 0xff | |
32 | ||
33 | static void (*mvebu_board_pm_enter)(void __iomem *sdram_reg, u32 srcmd); | |
34 | static void __iomem *sdram_ctrl; | |
35 | ||
36 | static int mvebu_pm_powerdown(unsigned long data) | |
37 | { | |
38 | u32 reg, srcmd; | |
39 | ||
40 | flush_cache_all(); | |
41 | outer_flush_all(); | |
42 | ||
43 | /* | |
44 | * Issue a Data Synchronization Barrier instruction to ensure | |
45 | * that all state saving has been completed. | |
46 | */ | |
47 | dsb(); | |
48 | ||
49 | /* Flush the DLB and wait ~7 usec */ | |
50 | reg = readl(sdram_ctrl + SDRAM_DLB_EVICTION_OFFS); | |
51 | reg &= ~SDRAM_DLB_EVICTION_THRESHOLD_MASK; | |
52 | writel(reg, sdram_ctrl + SDRAM_DLB_EVICTION_OFFS); | |
53 | ||
54 | udelay(7); | |
55 | ||
56 | /* Set DRAM in battery backup mode */ | |
57 | reg = readl(sdram_ctrl + SDRAM_CONFIG_OFFS); | |
58 | reg &= ~SDRAM_CONFIG_SR_MODE_BIT; | |
59 | writel(reg, sdram_ctrl + SDRAM_CONFIG_OFFS); | |
60 | ||
61 | /* Prepare to go to self-refresh */ | |
62 | ||
63 | srcmd = readl(sdram_ctrl + SDRAM_OPERATION_OFFS); | |
64 | srcmd &= ~0x1F; | |
65 | srcmd |= SDRAM_OPERATION_SELF_REFRESH; | |
66 | ||
67 | mvebu_board_pm_enter(sdram_ctrl + SDRAM_OPERATION_OFFS, srcmd); | |
68 | ||
69 | return 0; | |
70 | } | |
71 | ||
72 | #define BOOT_INFO_ADDR 0x3000 | |
73 | #define BOOT_MAGIC_WORD 0xdeadb002 | |
74 | #define BOOT_MAGIC_LIST_END 0xffffffff | |
75 | ||
76 | /* | |
77 | * Those registers are accessed before switching the internal register | |
78 | * base, which is why we hardcode the 0xd0000000 base address, the one | |
79 | * used by the SoC out of reset. | |
80 | */ | |
81 | #define MBUS_WINDOW_12_CTRL 0xd00200b0 | |
82 | #define MBUS_INTERNAL_REG_ADDRESS 0xd0020080 | |
83 | ||
84 | #define SDRAM_WIN_BASE_REG(x) (0x20180 + (0x8*x)) | |
85 | #define SDRAM_WIN_CTRL_REG(x) (0x20184 + (0x8*x)) | |
86 | ||
87 | static phys_addr_t mvebu_internal_reg_base(void) | |
88 | { | |
89 | struct device_node *np; | |
90 | __be32 in_addr[2]; | |
91 | ||
92 | np = of_find_node_by_name(NULL, "internal-regs"); | |
93 | BUG_ON(!np); | |
94 | ||
95 | /* | |
96 | * Ask the DT what is the internal register address on this | |
97 | * platform. In the mvebu-mbus DT binding, 0xf0010000 | |
98 | * corresponds to the internal register window. | |
99 | */ | |
100 | in_addr[0] = cpu_to_be32(0xf0010000); | |
101 | in_addr[1] = 0x0; | |
102 | ||
103 | return of_translate_address(np, in_addr); | |
104 | } | |
105 | ||
88ed69f2 | 106 | static void mvebu_pm_store_armadaxp_bootinfo(u32 *store_addr) |
8446be5d | 107 | { |
8446be5d TP |
108 | phys_addr_t resume_pc; |
109 | ||
64fc2a94 | 110 | resume_pc = __pa_symbol(armada_370_xp_cpu_resume); |
8446be5d TP |
111 | |
112 | /* | |
113 | * The bootloader expects the first two words to be a magic | |
114 | * value (BOOT_MAGIC_WORD), followed by the address of the | |
115 | * resume code to jump to. Then, it expects a sequence of | |
116 | * (address, value) pairs, which can be used to restore the | |
117 | * value of certain registers. This sequence must end with the | |
118 | * BOOT_MAGIC_LIST_END magic value. | |
119 | */ | |
120 | ||
121 | writel(BOOT_MAGIC_WORD, store_addr++); | |
122 | writel(resume_pc, store_addr++); | |
123 | ||
124 | /* | |
125 | * Some platforms remap their internal register base address | |
126 | * to 0xf1000000. However, out of reset, window 12 starts at | |
127 | * 0xf0000000 and ends at 0xf7ffffff, which would overlap with | |
128 | * the internal registers. Therefore, disable window 12. | |
129 | */ | |
130 | writel(MBUS_WINDOW_12_CTRL, store_addr++); | |
131 | writel(0x0, store_addr++); | |
132 | ||
133 | /* | |
134 | * Set the internal register base address to the value | |
135 | * expected by Linux, as read from the Device Tree. | |
136 | */ | |
137 | writel(MBUS_INTERNAL_REG_ADDRESS, store_addr++); | |
138 | writel(mvebu_internal_reg_base(), store_addr++); | |
139 | ||
140 | /* | |
141 | * Ask the mvebu-mbus driver to store the SDRAM window | |
142 | * configuration, which has to be restored by the bootloader | |
143 | * before re-entering the kernel on resume. | |
144 | */ | |
145 | store_addr += mvebu_mbus_save_cpu_target(store_addr); | |
146 | ||
147 | writel(BOOT_MAGIC_LIST_END, store_addr); | |
148 | } | |
149 | ||
88ed69f2 TP |
150 | static int mvebu_pm_store_bootinfo(void) |
151 | { | |
152 | u32 *store_addr; | |
153 | ||
154 | store_addr = phys_to_virt(BOOT_INFO_ADDR); | |
155 | ||
156 | if (of_machine_is_compatible("marvell,armadaxp")) | |
157 | mvebu_pm_store_armadaxp_bootinfo(store_addr); | |
158 | else | |
159 | return -ENODEV; | |
160 | ||
161 | return 0; | |
162 | } | |
163 | ||
3cbd6a6c | 164 | static int mvebu_enter_suspend(void) |
8446be5d | 165 | { |
88ed69f2 TP |
166 | int ret; |
167 | ||
88ed69f2 TP |
168 | ret = mvebu_pm_store_bootinfo(); |
169 | if (ret) | |
170 | return ret; | |
171 | ||
8446be5d TP |
172 | cpu_pm_enter(); |
173 | ||
8446be5d TP |
174 | cpu_suspend(0, mvebu_pm_powerdown); |
175 | ||
176 | outer_resume(); | |
177 | ||
178 | mvebu_v7_pmsu_idle_exit(); | |
179 | ||
180 | set_cpu_coherent(); | |
181 | ||
182 | cpu_pm_exit(); | |
3cbd6a6c GC |
183 | return 0; |
184 | } | |
185 | ||
186 | static int mvebu_pm_enter(suspend_state_t state) | |
187 | { | |
188 | switch (state) { | |
189 | case PM_SUSPEND_STANDBY: | |
190 | cpu_do_idle(); | |
191 | break; | |
192 | case PM_SUSPEND_MEM: | |
482d638f | 193 | pr_warn("Entering suspend to RAM. Only special wake-up sources will resume the system\n"); |
3cbd6a6c GC |
194 | return mvebu_enter_suspend(); |
195 | default: | |
196 | return -EINVAL; | |
197 | } | |
198 | return 0; | |
199 | } | |
200 | ||
201 | static int mvebu_pm_valid(suspend_state_t state) | |
202 | { | |
203 | if (state == PM_SUSPEND_STANDBY) | |
204 | return 1; | |
205 | ||
206 | if (state == PM_SUSPEND_MEM && mvebu_board_pm_enter != NULL) | |
207 | return 1; | |
8446be5d TP |
208 | |
209 | return 0; | |
210 | } | |
211 | ||
212 | static const struct platform_suspend_ops mvebu_pm_ops = { | |
213 | .enter = mvebu_pm_enter, | |
3cbd6a6c | 214 | .valid = mvebu_pm_valid, |
8446be5d TP |
215 | }; |
216 | ||
3cbd6a6c GC |
217 | static int __init mvebu_pm_init(void) |
218 | { | |
219 | if (!of_machine_is_compatible("marvell,armadaxp") && | |
220 | !of_machine_is_compatible("marvell,armada370") && | |
221 | !of_machine_is_compatible("marvell,armada380") && | |
222 | !of_machine_is_compatible("marvell,armada390")) | |
223 | return -ENODEV; | |
224 | ||
225 | suspend_set_ops(&mvebu_pm_ops); | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | ||
231 | late_initcall(mvebu_pm_init); | |
232 | ||
233 | int __init mvebu_pm_suspend_init(void (*board_pm_enter)(void __iomem *sdram_reg, | |
234 | u32 srcmd)) | |
8446be5d TP |
235 | { |
236 | struct device_node *np; | |
237 | struct resource res; | |
238 | ||
8446be5d TP |
239 | np = of_find_compatible_node(NULL, NULL, |
240 | "marvell,armada-xp-sdram-controller"); | |
241 | if (!np) | |
242 | return -ENODEV; | |
243 | ||
244 | if (of_address_to_resource(np, 0, &res)) { | |
245 | of_node_put(np); | |
246 | return -ENODEV; | |
247 | } | |
248 | ||
249 | if (!request_mem_region(res.start, resource_size(&res), | |
250 | np->full_name)) { | |
251 | of_node_put(np); | |
252 | return -EBUSY; | |
253 | } | |
254 | ||
255 | sdram_ctrl = ioremap(res.start, resource_size(&res)); | |
256 | if (!sdram_ctrl) { | |
257 | release_mem_region(res.start, resource_size(&res)); | |
258 | of_node_put(np); | |
259 | return -ENOMEM; | |
260 | } | |
261 | ||
262 | of_node_put(np); | |
263 | ||
264 | mvebu_board_pm_enter = board_pm_enter; | |
265 | ||
8446be5d TP |
266 | return 0; |
267 | } |