aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-imx
diff options
context:
space:
mode:
authorAnson Huang <b20788@freescale.com>2014-01-16 22:39:05 -0500
committerShawn Guo <shawn.guo@linaro.org>2014-03-04 21:35:10 -0500
commitdf595746fa69db2e36d89677df26ba51f9706c1b (patch)
tree802ca4cfdac8a8c048ea264d6351a5b95b9d2c05 /arch/arm/mach-imx
parent02ae8e8682112166493675c0b4aaa57737dc01e4 (diff)
ARM: imx: add suspend in ocram support for i.mx6q
When system enter suspend, we can set the DDR IO to high-Z state to save DDR IOs' power consumption, this operation can save many power(from ~26mA@1.5V to ~15mA@1.5V, measured on i.MX6Q SabreSD board, R25) of DDR IOs. To achieve that, we need to copy the suspend code to ocram and run the low level hardware related code(set DDR IOs to high-Z state) in ocram. If there is no ocram space available, then system will still do suspend in external DDR, hence no DDR IOs will be set to high-Z. The OCRAM usage layout is as below, ocram suspend region(4K currently): ======================== high address ====================== . . . ^ ^ ^ imx6_suspend code PM_INFO structure(imx6_cpu_pm_info) ======================== low address ======================= Reviewed-by: Sascha Hauer <s.hauer@pengutronix.de> Signed-off-by: Anson Huang <b20788@freescale.com> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
Diffstat (limited to 'arch/arm/mach-imx')
-rw-r--r--arch/arm/mach-imx/Makefile3
-rw-r--r--arch/arm/mach-imx/common.h6
-rw-r--r--arch/arm/mach-imx/hardware.h4
-rw-r--r--arch/arm/mach-imx/mach-imx6q.c2
-rw-r--r--arch/arm/mach-imx/mach-imx6sl.c3
-rw-r--r--arch/arm/mach-imx/pm-imx6q.c281
-rw-r--r--arch/arm/mach-imx/suspend-imx6.S289
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
102obj-$(CONFIG_SOC_IMX6Q) += clk-imx6q.o mach-imx6q.o 102obj-$(CONFIG_SOC_IMX6Q) += clk-imx6q.o mach-imx6q.o
103obj-$(CONFIG_SOC_IMX6SL) += clk-imx6sl.o mach-imx6sl.o 103obj-$(CONFIG_SOC_IMX6SL) += clk-imx6sl.o mach-imx6sl.o
104 104
105obj-$(CONFIG_SOC_IMX6Q) += pm-imx6q.o headsmp.o 105AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a
106obj-$(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
107obj-$(CONFIG_SOC_IMX6SL) += pm-imx6q.o headsmp.o 108obj-$(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);
145void imx_cpu_die(unsigned int cpu); 145void imx_cpu_die(unsigned int cpu);
146int imx_cpu_kill(unsigned int cpu); 146int imx_cpu_kill(unsigned int cpu);
147 147
148void imx6_suspend(void __iomem *ocram_vbase);
148void imx6q_pm_init(void); 149void imx6q_pm_init(void);
150void imx6dl_pm_init(void);
151void imx6sl_pm_init(void);
149void imx6q_pm_set_ccm_base(void __iomem *base); 152void imx6q_pm_set_ccm_base(void __iomem *base);
153
150#ifdef CONFIG_PM 154#ifdef CONFIG_PM
151void imx5_pm_init(void); 155void 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
65static void __init imx6sl_init_irq(void) 64static 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
61static void __iomem *ccm_base; 67static void __iomem *ccm_base;
68static void __iomem *suspend_ocram_base;
69static 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
85struct imx6_pm_base {
86 phys_addr_t pbase;
87 void __iomem *vbase;
88};
89
90struct 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
100static 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
112static 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 */
129struct 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
63void imx6q_set_int_mem_clk_lpm(void) 144void 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
178static int imx6q_suspend_finish(unsigned long val) 259static 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
221void __init imx6q_pm_init(void) 317static 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
339put_node:
340 of_node_put(node);
341out:
342 return ret;
343}
344
345static 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
456pl310_cache_map_failed:
457 iounmap(&pm_info->gpc_base.vbase);
458gpc_map_failed:
459 iounmap(&pm_info->iomuxc_base.vbase);
460iomuxc_map_failed:
461 iounmap(&pm_info->src_base.vbase);
462src_map_failed:
463 iounmap(&pm_info->mmdc_base.vbase);
464put_node:
465 of_node_put(node);
466
467 return ret;
468}
469
470static 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
499void __init imx6q_pm_init(void)
500{
501 imx6_pm_common_init(&imx6q_pm_data);
502}
503
504void __init imx6dl_pm_init(void)
505{
506 imx6_pm_common_init(NULL);
507}
508
509void __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]
811:
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
991:
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]
1142:
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
126ENTRY(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
175poll_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
185set_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
245rbc_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
267resume:
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
289ENDPROC(imx6_suspend)