1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * MIPS-specific support for Broadcom STB S2/S3/S5 power management |
4 | * |
5 | * Copyright (C) 2016-2017 Broadcom |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/printk.h> |
10 | #include <linux/io.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_address.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/suspend.h> |
15 | #include <asm/bmips.h> |
16 | #include <asm/tlbflush.h> |
17 | |
18 | #include "pm.h" |
19 | |
20 | #define S2_NUM_PARAMS 6 |
21 | #define MAX_NUM_MEMC 3 |
22 | |
23 | /* S3 constants */ |
24 | #define MAX_GP_REGS 16 |
25 | #define MAX_CP0_REGS 32 |
26 | #define NUM_MEMC_CLIENTS 128 |
27 | #define AON_CTRL_RAM_SIZE 128 |
28 | #define BRCMSTB_S3_MAGIC 0x5AFEB007 |
29 | |
30 | #define CLEAR_RESET_MASK 0x01 |
31 | |
32 | /* Index each CP0 register that needs to be saved */ |
33 | #define CONTEXT 0 |
34 | #define USER_LOCAL 1 |
35 | #define PGMK 2 |
36 | #define HWRENA 3 |
37 | #define COMPARE 4 |
38 | #define STATUS 5 |
39 | #define CONFIG 6 |
40 | #define MODE 7 |
41 | #define EDSP 8 |
42 | #define BOOT_VEC 9 |
43 | #define EBASE 10 |
44 | |
45 | struct brcmstb_memc { |
46 | void __iomem *ddr_phy_base; |
47 | void __iomem *arb_base; |
48 | }; |
49 | |
50 | struct brcmstb_pm_control { |
51 | void __iomem *aon_ctrl_base; |
52 | void __iomem *aon_sram_base; |
53 | void __iomem *timers_base; |
54 | struct brcmstb_memc memcs[MAX_NUM_MEMC]; |
55 | int num_memc; |
56 | }; |
57 | |
58 | struct brcm_pm_s3_context { |
59 | u32 cp0_regs[MAX_CP0_REGS]; |
60 | u32 memc0_rts[NUM_MEMC_CLIENTS]; |
61 | u32 sc_boot_vec; |
62 | }; |
63 | |
64 | struct brcmstb_mem_transfer; |
65 | |
66 | struct brcmstb_mem_transfer { |
67 | struct brcmstb_mem_transfer *next; |
68 | void *src; |
69 | void *dst; |
70 | dma_addr_t pa_src; |
71 | dma_addr_t pa_dst; |
72 | u32 len; |
73 | u8 key; |
74 | u8 mode; |
75 | u8 src_remapped; |
76 | u8 dst_remapped; |
77 | u8 src_dst_remapped; |
78 | }; |
79 | |
80 | #define AON_SAVE_SRAM(base, idx, val) \ |
81 | __raw_writel(val, base + (idx << 2)) |
82 | |
83 | /* Used for saving registers in asm */ |
84 | u32 gp_regs[MAX_GP_REGS]; |
85 | |
86 | #define BSP_CLOCK_STOP 0x00 |
87 | #define PM_INITIATE 0x01 |
88 | |
89 | static struct brcmstb_pm_control ctrl; |
90 | |
91 | static void brcm_pm_save_cp0_context(struct brcm_pm_s3_context *ctx) |
92 | { |
93 | /* Generic MIPS */ |
94 | ctx->cp0_regs[CONTEXT] = read_c0_context(); |
95 | ctx->cp0_regs[USER_LOCAL] = read_c0_userlocal(); |
96 | ctx->cp0_regs[PGMK] = read_c0_pagemask(); |
97 | ctx->cp0_regs[HWRENA] = read_c0_cache(); |
98 | ctx->cp0_regs[COMPARE] = read_c0_compare(); |
99 | ctx->cp0_regs[STATUS] = read_c0_status(); |
100 | |
101 | /* Broadcom specific */ |
102 | ctx->cp0_regs[CONFIG] = read_c0_brcm_config(); |
103 | ctx->cp0_regs[MODE] = read_c0_brcm_mode(); |
104 | ctx->cp0_regs[EDSP] = read_c0_brcm_edsp(); |
105 | ctx->cp0_regs[BOOT_VEC] = read_c0_brcm_bootvec(); |
106 | ctx->cp0_regs[EBASE] = read_c0_ebase(); |
107 | |
108 | ctx->sc_boot_vec = bmips_read_zscm_reg(0xa0); |
109 | } |
110 | |
111 | static void brcm_pm_restore_cp0_context(struct brcm_pm_s3_context *ctx) |
112 | { |
113 | /* Restore cp0 state */ |
114 | bmips_write_zscm_reg(0xa0, ctx->sc_boot_vec); |
115 | |
116 | /* Generic MIPS */ |
117 | write_c0_context(ctx->cp0_regs[CONTEXT]); |
118 | write_c0_userlocal(ctx->cp0_regs[USER_LOCAL]); |
119 | write_c0_pagemask(ctx->cp0_regs[PGMK]); |
120 | write_c0_cache(ctx->cp0_regs[HWRENA]); |
121 | write_c0_compare(ctx->cp0_regs[COMPARE]); |
122 | write_c0_status(ctx->cp0_regs[STATUS]); |
123 | |
124 | /* Broadcom specific */ |
125 | write_c0_brcm_config(ctx->cp0_regs[CONFIG]); |
126 | write_c0_brcm_mode(ctx->cp0_regs[MODE]); |
127 | write_c0_brcm_edsp(ctx->cp0_regs[EDSP]); |
128 | write_c0_brcm_bootvec(ctx->cp0_regs[BOOT_VEC]); |
129 | write_c0_ebase(ctx->cp0_regs[EBASE]); |
130 | } |
131 | |
132 | static void brcmstb_pm_handshake(void) |
133 | { |
134 | void __iomem *base = ctrl.aon_ctrl_base; |
135 | u32 tmp; |
136 | |
137 | /* BSP power handshake, v1 */ |
138 | tmp = __raw_readl(addr: base + AON_CTRL_HOST_MISC_CMDS); |
139 | tmp &= ~1UL; |
140 | __raw_writel(val: tmp, addr: base + AON_CTRL_HOST_MISC_CMDS); |
141 | (void)__raw_readl(addr: base + AON_CTRL_HOST_MISC_CMDS); |
142 | |
143 | __raw_writel(val: 0, addr: base + AON_CTRL_PM_INITIATE); |
144 | (void)__raw_readl(addr: base + AON_CTRL_PM_INITIATE); |
145 | __raw_writel(BSP_CLOCK_STOP | PM_INITIATE, |
146 | addr: base + AON_CTRL_PM_INITIATE); |
147 | /* |
148 | * HACK: BSP may have internal race on the CLOCK_STOP command. |
149 | * Avoid touching the BSP for a few milliseconds. |
150 | */ |
151 | mdelay(3); |
152 | } |
153 | |
154 | static void brcmstb_pm_s5(void) |
155 | { |
156 | void __iomem *base = ctrl.aon_ctrl_base; |
157 | |
158 | brcmstb_pm_handshake(); |
159 | |
160 | /* Clear magic s3 warm-boot value */ |
161 | AON_SAVE_SRAM(ctrl.aon_sram_base, 0, 0); |
162 | |
163 | /* Set the countdown */ |
164 | __raw_writel(val: 0x10, addr: base + AON_CTRL_PM_CPU_WAIT_COUNT); |
165 | (void)__raw_readl(addr: base + AON_CTRL_PM_CPU_WAIT_COUNT); |
166 | |
167 | /* Prepare to S5 cold boot */ |
168 | __raw_writel(PM_COLD_CONFIG, addr: base + AON_CTRL_PM_CTRL); |
169 | (void)__raw_readl(addr: base + AON_CTRL_PM_CTRL); |
170 | |
171 | __raw_writel(val: (PM_COLD_CONFIG | PM_PWR_DOWN), addr: base + |
172 | AON_CTRL_PM_CTRL); |
173 | (void)__raw_readl(addr: base + AON_CTRL_PM_CTRL); |
174 | |
175 | __asm__ __volatile__( |
176 | " wait\n" |
177 | : : : "memory" ); |
178 | } |
179 | |
180 | static int brcmstb_pm_s3(void) |
181 | { |
182 | struct brcm_pm_s3_context s3_context; |
183 | void __iomem *memc_arb_base; |
184 | unsigned long flags; |
185 | u32 tmp; |
186 | int i; |
187 | |
188 | /* Prepare for s3 */ |
189 | AON_SAVE_SRAM(ctrl.aon_sram_base, 0, BRCMSTB_S3_MAGIC); |
190 | AON_SAVE_SRAM(ctrl.aon_sram_base, 1, (u32)&s3_reentry); |
191 | AON_SAVE_SRAM(ctrl.aon_sram_base, 2, 0); |
192 | |
193 | /* Clear RESET_HISTORY */ |
194 | tmp = __raw_readl(addr: ctrl.aon_ctrl_base + AON_CTRL_RESET_CTRL); |
195 | tmp &= ~CLEAR_RESET_MASK; |
196 | __raw_writel(val: tmp, addr: ctrl.aon_ctrl_base + AON_CTRL_RESET_CTRL); |
197 | |
198 | local_irq_save(flags); |
199 | |
200 | /* Inhibit DDR_RSTb pulse for both MMCs*/ |
201 | for (i = 0; i < ctrl.num_memc; i++) { |
202 | tmp = __raw_readl(addr: ctrl.memcs[i].ddr_phy_base + |
203 | DDR40_PHY_CONTROL_REGS_0_STANDBY_CTRL); |
204 | |
205 | tmp &= ~0x0f; |
206 | __raw_writel(val: tmp, addr: ctrl.memcs[i].ddr_phy_base + |
207 | DDR40_PHY_CONTROL_REGS_0_STANDBY_CTRL); |
208 | tmp |= (0x05 | BIT(5)); |
209 | __raw_writel(val: tmp, addr: ctrl.memcs[i].ddr_phy_base + |
210 | DDR40_PHY_CONTROL_REGS_0_STANDBY_CTRL); |
211 | } |
212 | |
213 | /* Save CP0 context */ |
214 | brcm_pm_save_cp0_context(ctx: &s3_context); |
215 | |
216 | /* Save RTS(skip debug register) */ |
217 | memc_arb_base = ctrl.memcs[0].arb_base + 4; |
218 | for (i = 0; i < NUM_MEMC_CLIENTS; i++) { |
219 | s3_context.memc0_rts[i] = __raw_readl(addr: memc_arb_base); |
220 | memc_arb_base += 4; |
221 | } |
222 | |
223 | /* Save I/O context */ |
224 | local_flush_tlb_all(); |
225 | _dma_cache_wback_inv(0, ~0); |
226 | |
227 | brcm_pm_do_s3(ctrl.aon_ctrl_base, current_cpu_data.dcache.linesz); |
228 | |
229 | /* CPU reconfiguration */ |
230 | local_flush_tlb_all(); |
231 | bmips_cpu_setup(); |
232 | cpumask_clear(dstp: &bmips_booted_mask); |
233 | |
234 | /* Restore RTS (skip debug register) */ |
235 | memc_arb_base = ctrl.memcs[0].arb_base + 4; |
236 | for (i = 0; i < NUM_MEMC_CLIENTS; i++) { |
237 | __raw_writel(val: s3_context.memc0_rts[i], addr: memc_arb_base); |
238 | memc_arb_base += 4; |
239 | } |
240 | |
241 | /* restore CP0 context */ |
242 | brcm_pm_restore_cp0_context(ctx: &s3_context); |
243 | |
244 | local_irq_restore(flags); |
245 | |
246 | return 0; |
247 | } |
248 | |
249 | static int brcmstb_pm_s2(void) |
250 | { |
251 | /* |
252 | * We need to pass 6 arguments to an assembly function. Lets avoid the |
253 | * stack and pass arguments in a explicit 4 byte array. The assembly |
254 | * code assumes all arguments are 4 bytes and arguments are ordered |
255 | * like so: |
256 | * |
257 | * 0: AON_CTRl base register |
258 | * 1: DDR_PHY base register |
259 | * 2: TIMERS base resgister |
260 | * 3: I-Cache line size |
261 | * 4: Restart vector address |
262 | * 5: Restart vector size |
263 | */ |
264 | u32 s2_params[6]; |
265 | |
266 | /* Prepare s2 parameters */ |
267 | s2_params[0] = (u32)ctrl.aon_ctrl_base; |
268 | s2_params[1] = (u32)ctrl.memcs[0].ddr_phy_base; |
269 | s2_params[2] = (u32)ctrl.timers_base; |
270 | s2_params[3] = (u32)current_cpu_data.icache.linesz; |
271 | s2_params[4] = (u32)BMIPS_WARM_RESTART_VEC; |
272 | s2_params[5] = (u32)(bmips_smp_int_vec_end - |
273 | bmips_smp_int_vec); |
274 | |
275 | /* Drop to standby */ |
276 | brcm_pm_do_s2(s2_params); |
277 | |
278 | return 0; |
279 | } |
280 | |
281 | static int brcmstb_pm_standby(bool deep_standby) |
282 | { |
283 | brcmstb_pm_handshake(); |
284 | |
285 | /* Send IRQs to BMIPS_WARM_RESTART_VEC */ |
286 | clear_c0_cause(CAUSEF_IV); |
287 | irq_disable_hazard(); |
288 | set_c0_status(ST0_BEV); |
289 | irq_disable_hazard(); |
290 | |
291 | if (deep_standby) |
292 | brcmstb_pm_s3(); |
293 | else |
294 | brcmstb_pm_s2(); |
295 | |
296 | /* Send IRQs to normal runtime vectors */ |
297 | clear_c0_status(ST0_BEV); |
298 | irq_disable_hazard(); |
299 | set_c0_cause(CAUSEF_IV); |
300 | irq_disable_hazard(); |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static int brcmstb_pm_enter(suspend_state_t state) |
306 | { |
307 | int ret = -EINVAL; |
308 | |
309 | switch (state) { |
310 | case PM_SUSPEND_STANDBY: |
311 | ret = brcmstb_pm_standby(deep_standby: false); |
312 | break; |
313 | case PM_SUSPEND_MEM: |
314 | ret = brcmstb_pm_standby(deep_standby: true); |
315 | break; |
316 | } |
317 | |
318 | return ret; |
319 | } |
320 | |
321 | static int brcmstb_pm_valid(suspend_state_t state) |
322 | { |
323 | switch (state) { |
324 | case PM_SUSPEND_STANDBY: |
325 | return true; |
326 | case PM_SUSPEND_MEM: |
327 | return true; |
328 | default: |
329 | return false; |
330 | } |
331 | } |
332 | |
333 | static const struct platform_suspend_ops brcmstb_pm_ops = { |
334 | .enter = brcmstb_pm_enter, |
335 | .valid = brcmstb_pm_valid, |
336 | }; |
337 | |
338 | static const struct of_device_id aon_ctrl_dt_ids[] = { |
339 | { .compatible = "brcm,brcmstb-aon-ctrl" }, |
340 | { /* sentinel */ } |
341 | }; |
342 | |
343 | static const struct of_device_id ddr_phy_dt_ids[] = { |
344 | { .compatible = "brcm,brcmstb-ddr-phy" }, |
345 | { /* sentinel */ } |
346 | }; |
347 | |
348 | static const struct of_device_id arb_dt_ids[] = { |
349 | { .compatible = "brcm,brcmstb-memc-arb" }, |
350 | { /* sentinel */ } |
351 | }; |
352 | |
353 | static const struct of_device_id timers_ids[] = { |
354 | { .compatible = "brcm,brcmstb-timers" }, |
355 | { /* sentinel */ } |
356 | }; |
357 | |
358 | static inline void __iomem *brcmstb_ioremap_node(struct device_node *dn, |
359 | int index) |
360 | { |
361 | return of_io_request_and_map(device: dn, index, name: dn->full_name); |
362 | } |
363 | |
364 | static void __iomem *brcmstb_ioremap_match(const struct of_device_id *matches, |
365 | int index, const void **ofdata) |
366 | { |
367 | struct device_node *dn; |
368 | const struct of_device_id *match; |
369 | |
370 | dn = of_find_matching_node_and_match(NULL, matches, match: &match); |
371 | if (!dn) |
372 | return ERR_PTR(error: -EINVAL); |
373 | |
374 | if (ofdata) |
375 | *ofdata = match->data; |
376 | |
377 | return brcmstb_ioremap_node(dn, index); |
378 | } |
379 | |
380 | static int brcmstb_pm_init(void) |
381 | { |
382 | struct device_node *dn; |
383 | void __iomem *base; |
384 | int i; |
385 | |
386 | /* AON ctrl registers */ |
387 | base = brcmstb_ioremap_match(matches: aon_ctrl_dt_ids, index: 0, NULL); |
388 | if (IS_ERR(ptr: base)) { |
389 | pr_err("error mapping AON_CTRL\n" ); |
390 | goto aon_err; |
391 | } |
392 | ctrl.aon_ctrl_base = base; |
393 | |
394 | /* AON SRAM registers */ |
395 | base = brcmstb_ioremap_match(matches: aon_ctrl_dt_ids, index: 1, NULL); |
396 | if (IS_ERR(ptr: base)) { |
397 | pr_err("error mapping AON_SRAM\n" ); |
398 | goto sram_err; |
399 | } |
400 | ctrl.aon_sram_base = base; |
401 | |
402 | ctrl.num_memc = 0; |
403 | /* Map MEMC DDR PHY registers */ |
404 | for_each_matching_node(dn, ddr_phy_dt_ids) { |
405 | i = ctrl.num_memc; |
406 | if (i >= MAX_NUM_MEMC) { |
407 | pr_warn("Too many MEMCs (max %d)\n" , MAX_NUM_MEMC); |
408 | of_node_put(node: dn); |
409 | break; |
410 | } |
411 | base = brcmstb_ioremap_node(dn, index: 0); |
412 | if (IS_ERR(ptr: base)) { |
413 | of_node_put(node: dn); |
414 | goto ddr_err; |
415 | } |
416 | |
417 | ctrl.memcs[i].ddr_phy_base = base; |
418 | ctrl.num_memc++; |
419 | } |
420 | |
421 | /* MEMC ARB registers */ |
422 | base = brcmstb_ioremap_match(matches: arb_dt_ids, index: 0, NULL); |
423 | if (IS_ERR(ptr: base)) { |
424 | pr_err("error mapping MEMC ARB\n" ); |
425 | goto ddr_err; |
426 | } |
427 | ctrl.memcs[0].arb_base = base; |
428 | |
429 | /* Timer registers */ |
430 | base = brcmstb_ioremap_match(matches: timers_ids, index: 0, NULL); |
431 | if (IS_ERR(ptr: base)) { |
432 | pr_err("error mapping timers\n" ); |
433 | goto tmr_err; |
434 | } |
435 | ctrl.timers_base = base; |
436 | |
437 | /* s3 cold boot aka s5 */ |
438 | pm_power_off = brcmstb_pm_s5; |
439 | |
440 | suspend_set_ops(ops: &brcmstb_pm_ops); |
441 | |
442 | return 0; |
443 | |
444 | tmr_err: |
445 | iounmap(addr: ctrl.memcs[0].arb_base); |
446 | ddr_err: |
447 | for (i = 0; i < ctrl.num_memc; i++) |
448 | iounmap(addr: ctrl.memcs[i].ddr_phy_base); |
449 | |
450 | iounmap(addr: ctrl.aon_sram_base); |
451 | sram_err: |
452 | iounmap(addr: ctrl.aon_ctrl_base); |
453 | aon_err: |
454 | return PTR_ERR(ptr: base); |
455 | } |
456 | arch_initcall(brcmstb_pm_init); |
457 | |