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