diff options
-rw-r--r-- | arch/arm/mach-imx/Makefile | 3 | ||||
-rw-r--r-- | arch/arm/mach-imx/common.h | 6 | ||||
-rw-r--r-- | arch/arm/mach-imx/hardware.h | 4 | ||||
-rw-r--r-- | arch/arm/mach-imx/mach-imx6q.c | 2 | ||||
-rw-r--r-- | arch/arm/mach-imx/mach-imx6sl.c | 3 | ||||
-rw-r--r-- | arch/arm/mach-imx/pm-imx6q.c | 281 | ||||
-rw-r--r-- | arch/arm/mach-imx/suspend-imx6.S | 289 |
7 files changed, 577 insertions, 11 deletions
diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 21114a9a2ae7..48b47aa6224a 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile | |||
@@ -102,7 +102,8 @@ obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o | |||
102 | obj-$(CONFIG_SOC_IMX6Q) += clk-imx6q.o mach-imx6q.o | 102 | obj-$(CONFIG_SOC_IMX6Q) += clk-imx6q.o mach-imx6q.o |
103 | obj-$(CONFIG_SOC_IMX6SL) += clk-imx6sl.o mach-imx6sl.o | 103 | obj-$(CONFIG_SOC_IMX6SL) += clk-imx6sl.o mach-imx6sl.o |
104 | 104 | ||
105 | obj-$(CONFIG_SOC_IMX6Q) += pm-imx6q.o headsmp.o | 105 | AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a |
106 | obj-$(CONFIG_SOC_IMX6Q) += pm-imx6q.o headsmp.o suspend-imx6.o | ||
106 | # i.MX6SL reuses i.MX6Q code | 107 | # i.MX6SL reuses i.MX6Q code |
107 | obj-$(CONFIG_SOC_IMX6SL) += pm-imx6q.o headsmp.o | 108 | obj-$(CONFIG_SOC_IMX6SL) += pm-imx6q.o headsmp.o |
108 | 109 | ||
diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index b909b689619b..91d69ccf7c76 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright 2004-2013 Freescale Semiconductor, Inc. All Rights Reserved. | 2 | * Copyright 2004-2014 Freescale Semiconductor, Inc. All Rights Reserved. |
3 | */ | 3 | */ |
4 | 4 | ||
5 | /* | 5 | /* |
@@ -145,8 +145,12 @@ void imx6sl_set_wait_clk(bool enter); | |||
145 | void imx_cpu_die(unsigned int cpu); | 145 | void imx_cpu_die(unsigned int cpu); |
146 | int imx_cpu_kill(unsigned int cpu); | 146 | int imx_cpu_kill(unsigned int cpu); |
147 | 147 | ||
148 | void imx6_suspend(void __iomem *ocram_vbase); | ||
148 | void imx6q_pm_init(void); | 149 | void imx6q_pm_init(void); |
150 | void imx6dl_pm_init(void); | ||
151 | void imx6sl_pm_init(void); | ||
149 | void imx6q_pm_set_ccm_base(void __iomem *base); | 152 | void imx6q_pm_set_ccm_base(void __iomem *base); |
153 | |||
150 | #ifdef CONFIG_PM | 154 | #ifdef CONFIG_PM |
151 | void imx5_pm_init(void); | 155 | void imx5_pm_init(void); |
152 | #else | 156 | #else |
diff --git a/arch/arm/mach-imx/hardware.h b/arch/arm/mach-imx/hardware.h index a3b0b04b45c9..abf43bb47eca 100644 --- a/arch/arm/mach-imx/hardware.h +++ b/arch/arm/mach-imx/hardware.h | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. | 2 | * Copyright 2004-2007, 2014 Freescale Semiconductor, Inc. All Rights Reserved. |
3 | * Copyright 2008 Juergen Beisert, kernel@pengutronix.de | 3 | * Copyright 2008 Juergen Beisert, kernel@pengutronix.de |
4 | * | 4 | * |
5 | * This program is free software; you can redistribute it and/or | 5 | * This program is free software; you can redistribute it and/or |
@@ -20,7 +20,9 @@ | |||
20 | #ifndef __ASM_ARCH_MXC_HARDWARE_H__ | 20 | #ifndef __ASM_ARCH_MXC_HARDWARE_H__ |
21 | #define __ASM_ARCH_MXC_HARDWARE_H__ | 21 | #define __ASM_ARCH_MXC_HARDWARE_H__ |
22 | 22 | ||
23 | #ifndef __ASSEMBLY__ | ||
23 | #include <asm/io.h> | 24 | #include <asm/io.h> |
25 | #endif | ||
24 | #include <asm/sizes.h> | 26 | #include <asm/sizes.h> |
25 | 27 | ||
26 | #define addr_in_module(addr, mod) \ | 28 | #define addr_in_module(addr, mod) \ |
diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c index 76e5db4fce35..f9cbbf9e5e31 100644 --- a/arch/arm/mach-imx/mach-imx6q.c +++ b/arch/arm/mach-imx/mach-imx6q.c | |||
@@ -212,7 +212,7 @@ static void __init imx6q_init_machine(void) | |||
212 | of_platform_populate(NULL, of_default_bus_match_table, NULL, parent); | 212 | of_platform_populate(NULL, of_default_bus_match_table, NULL, parent); |
213 | 213 | ||
214 | imx_anatop_init(); | 214 | imx_anatop_init(); |
215 | imx6q_pm_init(); | 215 | cpu_is_imx6q() ? imx6q_pm_init() : imx6dl_pm_init(); |
216 | imx6q_1588_init(); | 216 | imx6q_1588_init(); |
217 | } | 217 | } |
218 | 218 | ||
diff --git a/arch/arm/mach-imx/mach-imx6sl.c b/arch/arm/mach-imx/mach-imx6sl.c index a26fdb2b1a6f..ad323385115c 100644 --- a/arch/arm/mach-imx/mach-imx6sl.c +++ b/arch/arm/mach-imx/mach-imx6sl.c | |||
@@ -58,8 +58,7 @@ static void __init imx6sl_init_machine(void) | |||
58 | 58 | ||
59 | imx6sl_fec_init(); | 59 | imx6sl_fec_init(); |
60 | imx_anatop_init(); | 60 | imx_anatop_init(); |
61 | /* Reuse imx6q pm code */ | 61 | imx6sl_pm_init(); |
62 | imx6q_pm_init(); | ||
63 | } | 62 | } |
64 | 63 | ||
65 | static void __init imx6sl_init_irq(void) | 64 | static void __init imx6sl_init_irq(void) |
diff --git a/arch/arm/mach-imx/pm-imx6q.c b/arch/arm/mach-imx/pm-imx6q.c index 30af3c0e1bf3..a7b1e58a3ac1 100644 --- a/arch/arm/mach-imx/pm-imx6q.c +++ b/arch/arm/mach-imx/pm-imx6q.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright 2011-2013 Freescale Semiconductor, Inc. | 2 | * Copyright 2011-2014 Freescale Semiconductor, Inc. |
3 | * Copyright 2011 Linaro Ltd. | 3 | * Copyright 2011 Linaro Ltd. |
4 | * | 4 | * |
5 | * The code contained herein is licensed under the GNU General Public | 5 | * The code contained herein is licensed under the GNU General Public |
@@ -14,16 +14,19 @@ | |||
14 | #include <linux/init.h> | 14 | #include <linux/init.h> |
15 | #include <linux/io.h> | 15 | #include <linux/io.h> |
16 | #include <linux/irq.h> | 16 | #include <linux/irq.h> |
17 | #include <linux/genalloc.h> | ||
17 | #include <linux/mfd/syscon.h> | 18 | #include <linux/mfd/syscon.h> |
18 | #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> | 19 | #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> |
19 | #include <linux/of.h> | 20 | #include <linux/of.h> |
20 | #include <linux/of_address.h> | 21 | #include <linux/of_address.h> |
22 | #include <linux/of_platform.h> | ||
21 | #include <linux/regmap.h> | 23 | #include <linux/regmap.h> |
22 | #include <linux/suspend.h> | 24 | #include <linux/suspend.h> |
23 | #include <asm/cacheflush.h> | 25 | #include <asm/cacheflush.h> |
26 | #include <asm/fncpy.h> | ||
24 | #include <asm/proc-fns.h> | 27 | #include <asm/proc-fns.h> |
25 | #include <asm/suspend.h> | 28 | #include <asm/suspend.h> |
26 | #include <asm/hardware/cache-l2x0.h> | 29 | #include <asm/tlb.h> |
27 | 30 | ||
28 | #include "common.h" | 31 | #include "common.h" |
29 | #include "hardware.h" | 32 | #include "hardware.h" |
@@ -58,7 +61,85 @@ | |||
58 | #define CGPR 0x64 | 61 | #define CGPR 0x64 |
59 | #define BM_CGPR_INT_MEM_CLK_LPM (0x1 << 17) | 62 | #define BM_CGPR_INT_MEM_CLK_LPM (0x1 << 17) |
60 | 63 | ||
64 | #define MX6Q_SUSPEND_OCRAM_SIZE 0x1000 | ||
65 | #define MX6_MAX_MMDC_IO_NUM 33 | ||
66 | |||
61 | static void __iomem *ccm_base; | 67 | static void __iomem *ccm_base; |
68 | static void __iomem *suspend_ocram_base; | ||
69 | static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase); | ||
70 | |||
71 | /* | ||
72 | * suspend ocram space layout: | ||
73 | * ======================== high address ====================== | ||
74 | * . | ||
75 | * . | ||
76 | * . | ||
77 | * ^ | ||
78 | * ^ | ||
79 | * ^ | ||
80 | * imx6_suspend code | ||
81 | * PM_INFO structure(imx6_cpu_pm_info) | ||
82 | * ======================== low address ======================= | ||
83 | */ | ||
84 | |||
85 | struct imx6_pm_base { | ||
86 | phys_addr_t pbase; | ||
87 | void __iomem *vbase; | ||
88 | }; | ||
89 | |||
90 | struct imx6_pm_socdata { | ||
91 | u32 cpu_type; | ||
92 | const char *mmdc_compat; | ||
93 | const char *src_compat; | ||
94 | const char *iomuxc_compat; | ||
95 | const char *gpc_compat; | ||
96 | const u32 mmdc_io_num; | ||
97 | const u32 *mmdc_io_offset; | ||
98 | }; | ||
99 | |||
100 | static const u32 imx6q_mmdc_io_offset[] __initconst = { | ||
101 | 0x5ac, 0x5b4, 0x528, 0x520, /* DQM0 ~ DQM3 */ | ||
102 | 0x514, 0x510, 0x5bc, 0x5c4, /* DQM4 ~ DQM7 */ | ||
103 | 0x56c, 0x578, 0x588, 0x594, /* CAS, RAS, SDCLK_0, SDCLK_1 */ | ||
104 | 0x5a8, 0x5b0, 0x524, 0x51c, /* SDQS0 ~ SDQS3 */ | ||
105 | 0x518, 0x50c, 0x5b8, 0x5c0, /* SDQS4 ~ SDQS7 */ | ||
106 | 0x784, 0x788, 0x794, 0x79c, /* GPR_B0DS ~ GPR_B3DS */ | ||
107 | 0x7a0, 0x7a4, 0x7a8, 0x748, /* GPR_B4DS ~ GPR_B7DS */ | ||
108 | 0x59c, 0x5a0, 0x750, 0x774, /* SODT0, SODT1, MODE_CTL, MODE */ | ||
109 | 0x74c, /* GPR_ADDS */ | ||
110 | }; | ||
111 | |||
112 | static const struct imx6_pm_socdata imx6q_pm_data __initconst = { | ||
113 | .cpu_type = MXC_CPU_IMX6Q, | ||
114 | .mmdc_compat = "fsl,imx6q-mmdc", | ||
115 | .src_compat = "fsl,imx6q-src", | ||
116 | .iomuxc_compat = "fsl,imx6q-iomuxc", | ||
117 | .gpc_compat = "fsl,imx6q-gpc", | ||
118 | .mmdc_io_num = ARRAY_SIZE(imx6q_mmdc_io_offset), | ||
119 | .mmdc_io_offset = imx6q_mmdc_io_offset, | ||
120 | }; | ||
121 | |||
122 | /* | ||
123 | * This structure is for passing necessary data for low level ocram | ||
124 | * suspend code(arch/arm/mach-imx/suspend-imx6.S), if this struct | ||
125 | * definition is changed, the offset definition in | ||
126 | * arch/arm/mach-imx/suspend-imx6.S must be also changed accordingly, | ||
127 | * otherwise, the suspend to ocram function will be broken! | ||
128 | */ | ||
129 | struct imx6_cpu_pm_info { | ||
130 | phys_addr_t pbase; /* The physical address of pm_info. */ | ||
131 | phys_addr_t resume_addr; /* The physical resume address for asm code */ | ||
132 | u32 cpu_type; | ||
133 | u32 pm_info_size; /* Size of pm_info. */ | ||
134 | struct imx6_pm_base mmdc_base; | ||
135 | struct imx6_pm_base src_base; | ||
136 | struct imx6_pm_base iomuxc_base; | ||
137 | struct imx6_pm_base ccm_base; | ||
138 | struct imx6_pm_base gpc_base; | ||
139 | struct imx6_pm_base l2_base; | ||
140 | u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */ | ||
141 | u32 mmdc_io_val[MX6_MAX_MMDC_IO_NUM][2]; /* To save offset and value */ | ||
142 | } __aligned(8); | ||
62 | 143 | ||
63 | void imx6q_set_int_mem_clk_lpm(void) | 144 | void imx6q_set_int_mem_clk_lpm(void) |
64 | { | 145 | { |
@@ -177,7 +258,17 @@ int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode) | |||
177 | 258 | ||
178 | static int imx6q_suspend_finish(unsigned long val) | 259 | static int imx6q_suspend_finish(unsigned long val) |
179 | { | 260 | { |
180 | cpu_do_idle(); | 261 | if (!imx6_suspend_in_ocram_fn) { |
262 | cpu_do_idle(); | ||
263 | } else { | ||
264 | /* | ||
265 | * call low level suspend function in ocram, | ||
266 | * as we need to float DDR IO. | ||
267 | */ | ||
268 | local_flush_tlb_all(); | ||
269 | imx6_suspend_in_ocram_fn(suspend_ocram_base); | ||
270 | } | ||
271 | |||
181 | return 0; | 272 | return 0; |
182 | } | 273 | } |
183 | 274 | ||
@@ -187,7 +278,12 @@ static int imx6q_pm_enter(suspend_state_t state) | |||
187 | case PM_SUSPEND_MEM: | 278 | case PM_SUSPEND_MEM: |
188 | imx6q_set_lpm(STOP_POWER_OFF); | 279 | imx6q_set_lpm(STOP_POWER_OFF); |
189 | imx6q_enable_wb(true); | 280 | imx6q_enable_wb(true); |
190 | imx6q_enable_rbc(true); | 281 | /* |
282 | * For suspend into ocram, asm code already take care of | ||
283 | * RBC setting, so we do NOT need to do that here. | ||
284 | */ | ||
285 | if (!imx6_suspend_in_ocram_fn) | ||
286 | imx6q_enable_rbc(true); | ||
191 | imx_gpc_pre_suspend(); | 287 | imx_gpc_pre_suspend(); |
192 | imx_anatop_pre_suspend(); | 288 | imx_anatop_pre_suspend(); |
193 | imx_set_cpu_jump(0, v7_cpu_resume); | 289 | imx_set_cpu_jump(0, v7_cpu_resume); |
@@ -218,12 +314,172 @@ void __init imx6q_pm_set_ccm_base(void __iomem *base) | |||
218 | ccm_base = base; | 314 | ccm_base = base; |
219 | } | 315 | } |
220 | 316 | ||
221 | void __init imx6q_pm_init(void) | 317 | static int __init imx6_pm_get_base(struct imx6_pm_base *base, |
318 | const char *compat) | ||
319 | { | ||
320 | struct device_node *node; | ||
321 | struct resource res; | ||
322 | int ret = 0; | ||
323 | |||
324 | node = of_find_compatible_node(NULL, NULL, compat); | ||
325 | if (!node) { | ||
326 | ret = -ENODEV; | ||
327 | goto out; | ||
328 | } | ||
329 | |||
330 | ret = of_address_to_resource(node, 0, &res); | ||
331 | if (ret) | ||
332 | goto put_node; | ||
333 | |||
334 | base->pbase = res.start; | ||
335 | base->vbase = ioremap(res.start, resource_size(&res)); | ||
336 | if (!base->vbase) | ||
337 | ret = -ENOMEM; | ||
338 | |||
339 | put_node: | ||
340 | of_node_put(node); | ||
341 | out: | ||
342 | return ret; | ||
343 | } | ||
344 | |||
345 | static int __init imx6q_ocram_suspend_init(const struct imx6_pm_socdata | ||
346 | *socdata) | ||
347 | { | ||
348 | phys_addr_t ocram_pbase; | ||
349 | struct device_node *node; | ||
350 | struct platform_device *pdev; | ||
351 | struct imx6_cpu_pm_info *pm_info; | ||
352 | struct gen_pool *ocram_pool; | ||
353 | unsigned long ocram_base; | ||
354 | int i, ret = 0; | ||
355 | const u32 *mmdc_offset_array; | ||
356 | |||
357 | if (!socdata) { | ||
358 | pr_warn("%s: invalid argument!\n", __func__); | ||
359 | return -EINVAL; | ||
360 | } | ||
361 | |||
362 | node = of_find_compatible_node(NULL, NULL, "mmio-sram"); | ||
363 | if (!node) { | ||
364 | pr_warn("%s: failed to find ocram node!\n", __func__); | ||
365 | return -ENODEV; | ||
366 | } | ||
367 | |||
368 | pdev = of_find_device_by_node(node); | ||
369 | if (!pdev) { | ||
370 | pr_warn("%s: failed to find ocram device!\n", __func__); | ||
371 | ret = -ENODEV; | ||
372 | goto put_node; | ||
373 | } | ||
374 | |||
375 | ocram_pool = dev_get_gen_pool(&pdev->dev); | ||
376 | if (!ocram_pool) { | ||
377 | pr_warn("%s: ocram pool unavailable!\n", __func__); | ||
378 | ret = -ENODEV; | ||
379 | goto put_node; | ||
380 | } | ||
381 | |||
382 | ocram_base = gen_pool_alloc(ocram_pool, MX6Q_SUSPEND_OCRAM_SIZE); | ||
383 | if (!ocram_base) { | ||
384 | pr_warn("%s: unable to alloc ocram!\n", __func__); | ||
385 | ret = -ENOMEM; | ||
386 | goto put_node; | ||
387 | } | ||
388 | |||
389 | ocram_pbase = gen_pool_virt_to_phys(ocram_pool, ocram_base); | ||
390 | |||
391 | suspend_ocram_base = __arm_ioremap_exec(ocram_pbase, | ||
392 | MX6Q_SUSPEND_OCRAM_SIZE, false); | ||
393 | |||
394 | pm_info = suspend_ocram_base; | ||
395 | pm_info->pbase = ocram_pbase; | ||
396 | pm_info->resume_addr = virt_to_phys(v7_cpu_resume); | ||
397 | pm_info->pm_info_size = sizeof(*pm_info); | ||
398 | |||
399 | /* | ||
400 | * ccm physical address is not used by asm code currently, | ||
401 | * so get ccm virtual address directly, as we already have | ||
402 | * it from ccm driver. | ||
403 | */ | ||
404 | pm_info->ccm_base.vbase = ccm_base; | ||
405 | |||
406 | ret = imx6_pm_get_base(&pm_info->mmdc_base, socdata->mmdc_compat); | ||
407 | if (ret) { | ||
408 | pr_warn("%s: failed to get mmdc base %d!\n", __func__, ret); | ||
409 | goto put_node; | ||
410 | } | ||
411 | |||
412 | ret = imx6_pm_get_base(&pm_info->src_base, socdata->src_compat); | ||
413 | if (ret) { | ||
414 | pr_warn("%s: failed to get src base %d!\n", __func__, ret); | ||
415 | goto src_map_failed; | ||
416 | } | ||
417 | |||
418 | ret = imx6_pm_get_base(&pm_info->iomuxc_base, socdata->iomuxc_compat); | ||
419 | if (ret) { | ||
420 | pr_warn("%s: failed to get iomuxc base %d!\n", __func__, ret); | ||
421 | goto iomuxc_map_failed; | ||
422 | } | ||
423 | |||
424 | ret = imx6_pm_get_base(&pm_info->gpc_base, socdata->gpc_compat); | ||
425 | if (ret) { | ||
426 | pr_warn("%s: failed to get gpc base %d!\n", __func__, ret); | ||
427 | goto gpc_map_failed; | ||
428 | } | ||
429 | |||
430 | ret = imx6_pm_get_base(&pm_info->l2_base, "arm,pl310-cache"); | ||
431 | if (ret) { | ||
432 | pr_warn("%s: failed to get pl310-cache base %d!\n", | ||
433 | __func__, ret); | ||
434 | goto pl310_cache_map_failed; | ||
435 | } | ||
436 | |||
437 | pm_info->cpu_type = socdata->cpu_type; | ||
438 | pm_info->mmdc_io_num = socdata->mmdc_io_num; | ||
439 | mmdc_offset_array = socdata->mmdc_io_offset; | ||
440 | |||
441 | for (i = 0; i < pm_info->mmdc_io_num; i++) { | ||
442 | pm_info->mmdc_io_val[i][0] = | ||
443 | mmdc_offset_array[i]; | ||
444 | pm_info->mmdc_io_val[i][1] = | ||
445 | readl_relaxed(pm_info->iomuxc_base.vbase + | ||
446 | mmdc_offset_array[i]); | ||
447 | } | ||
448 | |||
449 | imx6_suspend_in_ocram_fn = fncpy( | ||
450 | suspend_ocram_base + sizeof(*pm_info), | ||
451 | &imx6_suspend, | ||
452 | MX6Q_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); | ||
453 | |||
454 | goto put_node; | ||
455 | |||
456 | pl310_cache_map_failed: | ||
457 | iounmap(&pm_info->gpc_base.vbase); | ||
458 | gpc_map_failed: | ||
459 | iounmap(&pm_info->iomuxc_base.vbase); | ||
460 | iomuxc_map_failed: | ||
461 | iounmap(&pm_info->src_base.vbase); | ||
462 | src_map_failed: | ||
463 | iounmap(&pm_info->mmdc_base.vbase); | ||
464 | put_node: | ||
465 | of_node_put(node); | ||
466 | |||
467 | return ret; | ||
468 | } | ||
469 | |||
470 | static void __init imx6_pm_common_init(const struct imx6_pm_socdata | ||
471 | *socdata) | ||
222 | { | 472 | { |
223 | struct regmap *gpr; | 473 | struct regmap *gpr; |
474 | int ret; | ||
224 | 475 | ||
225 | WARN_ON(!ccm_base); | 476 | WARN_ON(!ccm_base); |
226 | 477 | ||
478 | ret = imx6q_ocram_suspend_init(socdata); | ||
479 | if (ret) | ||
480 | pr_warn("%s: failed to initialize ocram suspend %d!\n", | ||
481 | __func__, ret); | ||
482 | |||
227 | /* | 483 | /* |
228 | * This is for SW workaround step #1 of ERR007265, see comments | 484 | * This is for SW workaround step #1 of ERR007265, see comments |
229 | * in imx6q_set_lpm for details of this errata. | 485 | * in imx6q_set_lpm for details of this errata. |
@@ -239,3 +495,18 @@ void __init imx6q_pm_init(void) | |||
239 | 495 | ||
240 | suspend_set_ops(&imx6q_pm_ops); | 496 | suspend_set_ops(&imx6q_pm_ops); |
241 | } | 497 | } |
498 | |||
499 | void __init imx6q_pm_init(void) | ||
500 | { | ||
501 | imx6_pm_common_init(&imx6q_pm_data); | ||
502 | } | ||
503 | |||
504 | void __init imx6dl_pm_init(void) | ||
505 | { | ||
506 | imx6_pm_common_init(NULL); | ||
507 | } | ||
508 | |||
509 | void __init imx6sl_pm_init(void) | ||
510 | { | ||
511 | imx6_pm_common_init(NULL); | ||
512 | } | ||
diff --git a/arch/arm/mach-imx/suspend-imx6.S b/arch/arm/mach-imx/suspend-imx6.S new file mode 100644 index 000000000000..d273ee7ecc36 --- /dev/null +++ b/arch/arm/mach-imx/suspend-imx6.S | |||
@@ -0,0 +1,289 @@ | |||
1 | /* | ||
2 | * Copyright 2014 Freescale Semiconductor, Inc. | ||
3 | * | ||
4 | * The code contained herein is licensed under the GNU General Public | ||
5 | * License. You may obtain a copy of the GNU General Public License | ||
6 | * Version 2 or later at the following locations: | ||
7 | * | ||
8 | * http://www.opensource.org/licenses/gpl-license.html | ||
9 | * http://www.gnu.org/copyleft/gpl.html | ||
10 | */ | ||
11 | |||
12 | #include <linux/linkage.h> | ||
13 | #include <asm/hardware/cache-l2x0.h> | ||
14 | #include "hardware.h" | ||
15 | |||
16 | /* | ||
17 | * ==================== low level suspend ==================== | ||
18 | * | ||
19 | * Better to follow below rules to use ARM registers: | ||
20 | * r0: pm_info structure address; | ||
21 | * r1 ~ r4: for saving pm_info members; | ||
22 | * r5 ~ r10: free registers; | ||
23 | * r11: io base address. | ||
24 | * | ||
25 | * suspend ocram space layout: | ||
26 | * ======================== high address ====================== | ||
27 | * . | ||
28 | * . | ||
29 | * . | ||
30 | * ^ | ||
31 | * ^ | ||
32 | * ^ | ||
33 | * imx6_suspend code | ||
34 | * PM_INFO structure(imx6_cpu_pm_info) | ||
35 | * ======================== low address ======================= | ||
36 | */ | ||
37 | |||
38 | /* | ||
39 | * Below offsets are based on struct imx6_cpu_pm_info | ||
40 | * which defined in arch/arm/mach-imx/pm-imx6q.c, this | ||
41 | * structure contains necessary pm info for low level | ||
42 | * suspend related code. | ||
43 | */ | ||
44 | #define PM_INFO_PBASE_OFFSET 0x0 | ||
45 | #define PM_INFO_RESUME_ADDR_OFFSET 0x4 | ||
46 | #define PM_INFO_CPU_TYPE_OFFSET 0x8 | ||
47 | #define PM_INFO_PM_INFO_SIZE_OFFSET 0xC | ||
48 | #define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 | ||
49 | #define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 | ||
50 | #define PM_INFO_MX6Q_SRC_P_OFFSET 0x18 | ||
51 | #define PM_INFO_MX6Q_SRC_V_OFFSET 0x1C | ||
52 | #define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x20 | ||
53 | #define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x24 | ||
54 | #define PM_INFO_MX6Q_CCM_P_OFFSET 0x28 | ||
55 | #define PM_INFO_MX6Q_CCM_V_OFFSET 0x2C | ||
56 | #define PM_INFO_MX6Q_GPC_P_OFFSET 0x30 | ||
57 | #define PM_INFO_MX6Q_GPC_V_OFFSET 0x34 | ||
58 | #define PM_INFO_MX6Q_L2_P_OFFSET 0x38 | ||
59 | #define PM_INFO_MX6Q_L2_V_OFFSET 0x3C | ||
60 | #define PM_INFO_MMDC_IO_NUM_OFFSET 0x40 | ||
61 | #define PM_INFO_MMDC_IO_VAL_OFFSET 0x44 | ||
62 | |||
63 | #define MX6Q_SRC_GPR1 0x20 | ||
64 | #define MX6Q_SRC_GPR2 0x24 | ||
65 | #define MX6Q_MMDC_MAPSR 0x404 | ||
66 | #define MX6Q_GPC_IMR1 0x08 | ||
67 | #define MX6Q_GPC_IMR2 0x0c | ||
68 | #define MX6Q_GPC_IMR3 0x10 | ||
69 | #define MX6Q_GPC_IMR4 0x14 | ||
70 | #define MX6Q_CCM_CCR 0x0 | ||
71 | |||
72 | .align 3 | ||
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 | mov r6, #0x0 | ||
80 | str r6, [r11, #L2X0_CACHE_SYNC] | ||
81 | 1: | ||
82 | ldr r6, [r11, #L2X0_CACHE_SYNC] | ||
83 | ands r6, r6, #0x1 | ||
84 | bne 1b | ||
85 | #endif | ||
86 | |||
87 | .endm | ||
88 | |||
89 | .macro resume_mmdc | ||
90 | |||
91 | /* restore MMDC IO */ | ||
92 | cmp r5, #0x0 | ||
93 | ldreq r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] | ||
94 | ldrne r11, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] | ||
95 | |||
96 | ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] | ||
97 | ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET | ||
98 | add r7, r7, r0 | ||
99 | 1: | ||
100 | ldr r8, [r7], #0x4 | ||
101 | ldr r9, [r7], #0x4 | ||
102 | str r9, [r11, r8] | ||
103 | subs r6, r6, #0x1 | ||
104 | bne 1b | ||
105 | |||
106 | cmp r5, #0x0 | ||
107 | ldreq r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] | ||
108 | ldrne r11, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] | ||
109 | |||
110 | /* let DDR out of self-refresh */ | ||
111 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] | ||
112 | bic r7, r7, #(1 << 21) | ||
113 | str r7, [r11, #MX6Q_MMDC_MAPSR] | ||
114 | 2: | ||
115 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] | ||
116 | ands r7, r7, #(1 << 25) | ||
117 | bne 2b | ||
118 | |||
119 | /* enable DDR auto power saving */ | ||
120 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] | ||
121 | bic r7, r7, #0x1 | ||
122 | str r7, [r11, #MX6Q_MMDC_MAPSR] | ||
123 | |||
124 | .endm | ||
125 | |||
126 | ENTRY(imx6_suspend) | ||
127 | ldr r1, [r0, #PM_INFO_PBASE_OFFSET] | ||
128 | ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET] | ||
129 | ldr r3, [r0, #PM_INFO_CPU_TYPE_OFFSET] | ||
130 | ldr r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] | ||
131 | |||
132 | /* | ||
133 | * counting the resume address in iram | ||
134 | * to set it in SRC register. | ||
135 | */ | ||
136 | ldr r6, =imx6_suspend | ||
137 | ldr r7, =resume | ||
138 | sub r7, r7, r6 | ||
139 | add r8, r1, r4 | ||
140 | add r9, r8, r7 | ||
141 | |||
142 | /* | ||
143 | * make sure TLB contain the addr we want, | ||
144 | * as we will access them after MMDC IO floated. | ||
145 | */ | ||
146 | |||
147 | ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] | ||
148 | ldr r6, [r11, #0x0] | ||
149 | ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] | ||
150 | ldr r6, [r11, #0x0] | ||
151 | |||
152 | /* use r11 to store the IO address */ | ||
153 | ldr r11, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] | ||
154 | /* store physical resume addr and pm_info address. */ | ||
155 | str r9, [r11, #MX6Q_SRC_GPR1] | ||
156 | str r1, [r11, #MX6Q_SRC_GPR2] | ||
157 | |||
158 | /* need to sync L2 cache before DSM. */ | ||
159 | sync_l2_cache | ||
160 | |||
161 | ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] | ||
162 | /* | ||
163 | * put DDR explicitly into self-refresh and | ||
164 | * disable automatic power savings. | ||
165 | */ | ||
166 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] | ||
167 | orr r7, r7, #0x1 | ||
168 | str r7, [r11, #MX6Q_MMDC_MAPSR] | ||
169 | |||
170 | /* make the DDR explicitly enter self-refresh. */ | ||
171 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] | ||
172 | orr r7, r7, #(1 << 21) | ||
173 | str r7, [r11, #MX6Q_MMDC_MAPSR] | ||
174 | |||
175 | poll_dvfs_set: | ||
176 | ldr r7, [r11, #MX6Q_MMDC_MAPSR] | ||
177 | ands r7, r7, #(1 << 25) | ||
178 | beq poll_dvfs_set | ||
179 | |||
180 | ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] | ||
181 | ldr r6, =0x0 | ||
182 | ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] | ||
183 | ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET | ||
184 | add r8, r8, r0 | ||
185 | set_mmdc_io_lpm: | ||
186 | ldr r9, [r8], #0x8 | ||
187 | str r6, [r11, r9] | ||
188 | subs r7, r7, #0x1 | ||
189 | bne set_mmdc_io_lpm | ||
190 | |||
191 | /* | ||
192 | * mask all GPC interrupts before | ||
193 | * enabling the RBC counters to | ||
194 | * avoid the counter starting too | ||
195 | * early if an interupt is already | ||
196 | * pending. | ||
197 | */ | ||
198 | ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] | ||
199 | ldr r6, [r11, #MX6Q_GPC_IMR1] | ||
200 | ldr r7, [r11, #MX6Q_GPC_IMR2] | ||
201 | ldr r8, [r11, #MX6Q_GPC_IMR3] | ||
202 | ldr r9, [r11, #MX6Q_GPC_IMR4] | ||
203 | |||
204 | ldr r10, =0xffffffff | ||
205 | str r10, [r11, #MX6Q_GPC_IMR1] | ||
206 | str r10, [r11, #MX6Q_GPC_IMR2] | ||
207 | str r10, [r11, #MX6Q_GPC_IMR3] | ||
208 | str r10, [r11, #MX6Q_GPC_IMR4] | ||
209 | |||
210 | /* | ||
211 | * enable the RBC bypass counter here | ||
212 | * to hold off the interrupts. RBC counter | ||
213 | * = 32 (1ms), Minimum RBC delay should be | ||
214 | * 400us for the analog LDOs to power down. | ||
215 | */ | ||
216 | ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] | ||
217 | ldr r10, [r11, #MX6Q_CCM_CCR] | ||
218 | bic r10, r10, #(0x3f << 21) | ||
219 | orr r10, r10, #(0x20 << 21) | ||
220 | str r10, [r11, #MX6Q_CCM_CCR] | ||
221 | |||
222 | /* enable the counter. */ | ||
223 | ldr r10, [r11, #MX6Q_CCM_CCR] | ||
224 | orr r10, r10, #(0x1 << 27) | ||
225 | str r10, [r11, #MX6Q_CCM_CCR] | ||
226 | |||
227 | /* unmask all the GPC interrupts. */ | ||
228 | ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] | ||
229 | str r6, [r11, #MX6Q_GPC_IMR1] | ||
230 | str r7, [r11, #MX6Q_GPC_IMR2] | ||
231 | str r8, [r11, #MX6Q_GPC_IMR3] | ||
232 | str r9, [r11, #MX6Q_GPC_IMR4] | ||
233 | |||
234 | /* | ||
235 | * now delay for a short while (3usec) | ||
236 | * ARM is at 1GHz at this point | ||
237 | * so a short loop should be enough. | ||
238 | * this delay is required to ensure that | ||
239 | * the RBC counter can start counting in | ||
240 | * case an interrupt is already pending | ||
241 | * or in case an interrupt arrives just | ||
242 | * as ARM is about to assert DSM_request. | ||
243 | */ | ||
244 | ldr r6, =2000 | ||
245 | rbc_loop: | ||
246 | subs r6, r6, #0x1 | ||
247 | bne rbc_loop | ||
248 | |||
249 | /* Zzz, enter stop mode */ | ||
250 | wfi | ||
251 | nop | ||
252 | nop | ||
253 | nop | ||
254 | nop | ||
255 | |||
256 | /* | ||
257 | * run to here means there is pending | ||
258 | * wakeup source, system should auto | ||
259 | * resume, we need to restore MMDC IO first | ||
260 | */ | ||
261 | mov r5, #0x0 | ||
262 | resume_mmdc | ||
263 | |||
264 | /* return to suspend finish */ | ||
265 | mov pc, lr | ||
266 | |||
267 | resume: | ||
268 | /* invalidate L1 I-cache first */ | ||
269 | mov r6, #0x0 | ||
270 | mcr p15, 0, r6, c7, c5, 0 | ||
271 | mcr p15, 0, r6, c7, c5, 6 | ||
272 | /* enable the Icache and branch prediction */ | ||
273 | mov r6, #0x1800 | ||
274 | mcr p15, 0, r6, c1, c0, 0 | ||
275 | isb | ||
276 | |||
277 | /* get physical resume address from pm_info. */ | ||
278 | ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] | ||
279 | /* clear core0's entry and parameter */ | ||
280 | ldr r11, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] | ||
281 | mov r7, #0x0 | ||
282 | str r7, [r11, #MX6Q_SRC_GPR1] | ||
283 | str r7, [r11, #MX6Q_SRC_GPR2] | ||
284 | |||
285 | mov r5, #0x1 | ||
286 | resume_mmdc | ||
287 | |||
288 | mov pc, lr | ||
289 | ENDPROC(imx6_suspend) | ||