diff mbox

[2/2] ARM: imx6q: add cpu suspend/resume support in IRAM

Message ID 1325312624-22980-1-git-send-email-jason.chen@linaro.org
State New
Headers show

Commit Message

Jason Chen Dec. 31, 2011, 6:23 a.m. UTC
For most power saving, this patch make arm core stop and
ddr go into self-refresh mode.

It already be tested on Freescale imx6q-arm2 board.

Signed-off-by: Jason Chen <jason.chen@linaro.org>
Signed-off-by: Jason Chen <jason.chen@freescale.com>
---
 arch/arm/mach-imx/Makefile              |    2 +-
 arch/arm/mach-imx/clock-imx6q.c         |   37 +++--
 arch/arm/mach-imx/gpc.c                 |   21 ++
 arch/arm/mach-imx/pm-imx6q.c            |  128 +++++++++++++-
 arch/arm/mach-imx/suspend-imx6q.S       |  308 +++++++++++++++++++++++++++++++
 arch/arm/plat-mxc/include/mach/common.h |    2 +
 arch/arm/plat-mxc/include/mach/mx6q.h   |    6 +
 7 files changed, 487 insertions(+), 17 deletions(-)

Comments

Shawn Guo Jan. 3, 2012, 2:24 p.m. UTC | #1
On Sat, Dec 31, 2011 at 02:23:44PM +0800, Jason Chen wrote:
> For most power saving, this patch make arm core stop and

s/most/more?

Without this patch, the 'mem' state already powers arm core off.

> ddr go into self-refresh mode.
> 
> It already be tested on Freescale imx6q-arm2 board.
> 

I managed to apply the patch on v3.2-rc7 and tested it on ARM2 board,
but it does not work for me.

> Signed-off-by: Jason Chen <jason.chen@linaro.org>
> Signed-off-by: Jason Chen <jason.chen@freescale.com>
> ---
>  arch/arm/mach-imx/Makefile              |    2 +-
>  arch/arm/mach-imx/clock-imx6q.c         |   37 +++--
>  arch/arm/mach-imx/gpc.c                 |   21 ++
>  arch/arm/mach-imx/pm-imx6q.c            |  128 +++++++++++++-
>  arch/arm/mach-imx/suspend-imx6q.S       |  308 +++++++++++++++++++++++++++++++
>  arch/arm/plat-mxc/include/mach/common.h |    2 +
>  arch/arm/plat-mxc/include/mach/mx6q.h   |    6 +
>  7 files changed, 487 insertions(+), 17 deletions(-)
> 

The checkpatch.pl reports 3 warnings.

> diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
> index 88a3966..0b07eef 100644
> --- a/arch/arm/mach-imx/Makefile
> +++ b/arch/arm/mach-imx/Makefile
> @@ -72,7 +72,7 @@ AFLAGS_head-v7.o :=-Wa,-march=armv7-a
>  obj-$(CONFIG_SMP) += platsmp.o
>  obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
>  obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o
> -obj-$(CONFIG_SOC_IMX6Q) += clock-imx6q.o mach-imx6q.o pm-imx6q.o
> +obj-$(CONFIG_SOC_IMX6Q) += clock-imx6q.o mach-imx6q.o pm-imx6q.o suspend-imx6q.o
>  
>  # i.MX5 based machines
>  obj-$(CONFIG_MACH_MX51_BABBAGE) += mach-mx51_babbage.o
> diff --git a/arch/arm/mach-imx/clock-imx6q.c b/arch/arm/mach-imx/clock-imx6q.c
> index 56a7a3f..b7fecad 100644
> --- a/arch/arm/mach-imx/clock-imx6q.c
> +++ b/arch/arm/mach-imx/clock-imx6q.c
> @@ -115,6 +115,8 @@
>  #define CG14		28
>  #define CG15		30
>  
> +#define BM_CCR_RBC_EN			(0x1 << 27)
> +
>  #define BM_CCSR_PLL1_SW_SEL		(0x1 << 2)
>  #define BM_CCSR_STEP_SEL		(0x1 << 8)
>  
> @@ -1916,33 +1918,44 @@ static struct clk_lookup lookups[] = {
>  
>  int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode)
>  {
> -	u32 val = readl_relaxed(CLPCR);
> +	u32 clpcr = readl_relaxed(CLPCR);
> +	u32 ccr = readl_relaxed(CCR);
>  
> -	val &= ~BM_CLPCR_LPM;
> +	clpcr &= ~(BM_CLPCR_LPM | BM_CLPCR_VSTBY | BM_CLPCR_SBYOS
> +			| BM_CLPCR_STBY_COUNT | BM_CLPCR_WB_PER_AT_LPM);
> +	ccr &= ~(BM_CCR_RBC_EN);
>  	switch (mode) {
>  	case WAIT_CLOCKED:
>  		break;
>  	case WAIT_UNCLOCKED:
> -		val |= 0x1 << BP_CLPCR_LPM;
> +		clpcr |= 0x1 << BP_CLPCR_LPM;
>  		break;
>  	case STOP_POWER_ON:
> -		val |= 0x2 << BP_CLPCR_LPM;
> +		clpcr |= 0x2 << BP_CLPCR_LPM;
>  		break;
>  	case WAIT_UNCLOCKED_POWER_OFF:
> -		val |= 0x1 << BP_CLPCR_LPM;
> -		val &= ~BM_CLPCR_VSTBY;
> -		val &= ~BM_CLPCR_SBYOS;
> +		clpcr |= 0x1 << BP_CLPCR_LPM;
>  		break;
>  	case STOP_POWER_OFF:
> -		val |= 0x2 << BP_CLPCR_LPM;
> -		val |= 0x3 << BP_CLPCR_STBY_COUNT;
> -		val |= BM_CLPCR_VSTBY;
> -		val |= BM_CLPCR_SBYOS;
> +		clpcr |= 0x2 << BP_CLPCR_LPM;
> +		clpcr |= 0x3 << BP_CLPCR_STBY_COUNT;
> +		clpcr |= BM_CLPCR_VSTBY;
> +		clpcr |= BM_CLPCR_SBYOS;
> +		break;
> +	case ARM_POWER_OFF:

I'm unsure we need this new state.

> +		clpcr |= 0x2 << BP_CLPCR_LPM;
> +		clpcr |= 0x3 << BP_CLPCR_STBY_COUNT;
> +		clpcr |= BM_CLPCR_VSTBY;
> +		clpcr |= BM_CLPCR_SBYOS;
> +		clpcr |= BM_CLPCR_WB_PER_AT_LPM;
> +		/* assert anatop_reg_bypass signal */
> +		ccr |= BM_CCR_RBC_EN;

It seems that the difference between STOP_POWER_OFF and ARM_POWER_OFF
is all about bits BM_CLPCR_WB_PER_AT_LPM and BM_CCR_RBC_EN.  Can you
elaborate why these two bits need to be set for suspend with DDR in
self-refresh state?

>  		break;
>  	default:
>  		return -EINVAL;
>  	}
> -	writel_relaxed(val, CLPCR);
> +	writel_relaxed(clpcr, CLPCR);
> +	writel_relaxed(ccr, CCR);
>  
>  	return 0;
>  }
> diff --git a/arch/arm/mach-imx/gpc.c b/arch/arm/mach-imx/gpc.c
> index e1537f9..8fc255b 100644
> --- a/arch/arm/mach-imx/gpc.c
> +++ b/arch/arm/mach-imx/gpc.c
> @@ -17,15 +17,36 @@
>  #include <linux/of_irq.h>
>  #include <asm/hardware/gic.h>
>  
> +#define GPC_CNTR		0x000

Where is it used?

>  #define GPC_IMR1		0x008
> +#define GPC_ISR1		0x018
> +#define GPC_ISR2		0x01c
> +#define GPC_ISR3		0x020
> +#define GPC_ISR4		0x024

It seems that only definition GPC_ISR1 is used?

>  #define GPC_PGC_CPU_PDN		0x2a0
>  
>  #define IMR_NUM			4
> +#define ISR_NUM			4
>  
>  static void __iomem *gpc_base;
>  static u32 gpc_wake_irqs[IMR_NUM];
>  static u32 gpc_saved_imrs[IMR_NUM];
>  
> +bool imx_gpc_wake_irq_pending(void)
> +{
> +	void __iomem *reg_isr1 = gpc_base + GPC_ISR1;
> +	int i;
> +	u32 val;
> +
> +	for (i = 0; i < ISR_NUM; i++) {
> +		val = readl_relaxed(reg_isr1 + i * 4);
> +		if (val & gpc_wake_irqs[i])
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
>  void imx_gpc_pre_suspend(void)
>  {
>  	void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
> diff --git a/arch/arm/mach-imx/pm-imx6q.c b/arch/arm/mach-imx/pm-imx6q.c
> index f20f191..6e45245 100644
> --- a/arch/arm/mach-imx/pm-imx6q.c
> +++ b/arch/arm/mach-imx/pm-imx6q.c
> @@ -13,31 +13,73 @@
>  #include <linux/init.h>
>  #include <linux/io.h>
>  #include <linux/of.h>
> +#include <linux/of_address.h>
>  #include <linux/suspend.h>
>  #include <asm/cacheflush.h>
>  #include <asm/proc-fns.h>
>  #include <asm/suspend.h>
> +#include <asm/mach/map.h>
>  #include <asm/hardware/cache-l2x0.h>
> +#include <mach/iram.h>
>  #include <mach/common.h>
>  #include <mach/hardware.h>
>  
> +struct imx_iram_pm {
> +	void *iram_cpaddr;
> +	unsigned long suspend_iram_paddr;
> +	unsigned long suspend_iram_size;
> +	void *suspend_iram_vaddr;
> +	void *reg_ptr[4];
> +} imx6q_iram_pm;
> +
>  extern unsigned long phys_l2x0_saved_regs;
> +extern void imx6q_suspend(void);
> +static void (*suspend_in_iram)(unsigned long iram_paddr,
> +				unsigned long iram_vaddr,
> +				unsigned long iram_size);
>  
>  static int imx6q_suspend_finish(unsigned long val)
>  {
> -	cpu_do_idle();
> +	if ((val == PM_SUSPEND_MEM) && suspend_in_iram) {
> +		suspend_in_iram((unsigned long)imx6q_iram_pm.suspend_iram_paddr,
> +				(unsigned long)imx6q_iram_pm.suspend_iram_vaddr,
> +				(unsigned long)imx6q_iram_pm.suspend_iram_size);
> +	} else

The brace pair {} is not needed.

> +		cpu_do_idle();
> +
>  	return 0;
>  }
>  
> +static void imx6q_prepare_suspend_iram(void)
> +{
> +	unsigned long *iram_stack = imx6q_iram_pm.suspend_iram_vaddr
> +					+ imx6q_iram_pm.suspend_iram_size;
> +
> +	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[3];
> +	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[2];
> +	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[1];
> +	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[0];
> +}
> +

I'm not sure we need to do this.  I feel we have done too much in
suspend_in_iram().  See comments on suspend_in_iram below.

>  static int imx6q_pm_enter(suspend_state_t state)
>  {
>  	switch (state) {
> +	case PM_SUSPEND_STANDBY:
>  	case PM_SUSPEND_MEM:
> -		imx6q_set_lpm(STOP_POWER_OFF);
> +		if (imx_gpc_wake_irq_pending())
> +			return 0;
> +
> +		if (state == PM_SUSPEND_STANDBY)
> +			imx6q_set_lpm(STOP_POWER_OFF);
> +		else
> +			imx6q_set_lpm(ARM_POWER_OFF);
> +

I do not think we need to distinguish these two states, STOP_POWER_OFF
and ARM_POWER_OFF.  When I added STOP_POWER_OFF support, the DDR self
refresh support was missed there.  When this feature is available, we
can just add it on top of STOP_POWER_OFF rather than inventing a new
state.

>  		imx_gpc_pre_suspend();
>  		imx_set_cpu_jump(0, v7_cpu_resume);
> +		if (state == PM_SUSPEND_MEM)
> +			imx6q_prepare_suspend_iram();
>  		/* Zzz ... */
> -		cpu_suspend(0, imx6q_suspend_finish);
> +		cpu_suspend(state, imx6q_suspend_finish);
>  		imx_smp_prepare();
>  		imx_gpc_post_resume();
>  		break;
> @@ -48,11 +90,55 @@ static int imx6q_pm_enter(suspend_state_t state)
>  	return 0;
>  }
>  
> +static int imx6q_pm_valid(suspend_state_t state)
> +{
> +	return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX);
> +}
> +
>  static const struct platform_suspend_ops imx6q_pm_ops = {
>  	.enter = imx6q_pm_enter,
> -	.valid = suspend_valid_only_mem,
> +	.valid = imx6q_pm_valid,
>  };
>  
> +static int __init imx6q_pm_iram_of_init(void)
> +{
> +	struct device_node *np;
> +
> +	/*
> +	 * these register may already ioremaped, need figure out
> +	 * one way to save vmalloc space.
> +	 */
> +	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-src");
> +	imx6q_iram_pm.reg_ptr[0] = of_iomap(np, 0);
> +	if (!imx6q_iram_pm.reg_ptr[0])
> +		goto err0;
> +
> +	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc");
> +	imx6q_iram_pm.reg_ptr[1] = of_iomap(np, 0);
> +	if (!imx6q_iram_pm.reg_ptr[1])
> +		goto err1;
> +
> +	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc");
> +	imx6q_iram_pm.reg_ptr[2] = of_iomap(np, 0);
> +	if (!imx6q_iram_pm.reg_ptr[2])
> +		goto err2;
> +
> +	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop");
> +	imx6q_iram_pm.reg_ptr[3] = of_iomap(np, 0);
> +	if (!imx6q_iram_pm.reg_ptr[3])
> +		goto err3;

This is not really nice and necessary, if we can limit the work done
in suspend_in_iram().

> +
> +	return 0;
> +err3:
> +	iounmap(imx6q_iram_pm.reg_ptr[2]);
> +err2:
> +	iounmap(imx6q_iram_pm.reg_ptr[1]);
> +err1:
> +	iounmap(imx6q_iram_pm.reg_ptr[0]);
> +err0:
> +	return -EINVAL;
> +}
> +
>  void __init imx6q_pm_init(void)
>  {
>  	/*
> @@ -67,4 +153,38 @@ void __init imx6q_pm_init(void)
>  	phys_l2x0_saved_regs = __pa(&l2x0_saved_regs);
>  
>  	suspend_set_ops(&imx6q_pm_ops);
> +
> +	/* Move suspend routine into iRAM */
> +	imx6q_iram_pm.suspend_iram_size = SZ_4K;
> +	imx6q_iram_pm.iram_cpaddr = iram_alloc(imx6q_iram_pm.suspend_iram_size,
> +			&imx6q_iram_pm.suspend_iram_paddr);
> +	if (imx6q_iram_pm.iram_cpaddr) {
> +		if (imx6q_pm_iram_of_init() < 0) {
> +			iram_free(imx6q_iram_pm.suspend_iram_paddr,
> +					imx6q_iram_pm.suspend_iram_size);
> +			return;
> +		}
> +		/*
> +		 * Need to remap the area here since we want the memory region
> +		 * to be noncached & executable.
> +		 */
> +		imx6q_iram_pm.suspend_iram_vaddr =
> +			__arm_ioremap(imx6q_iram_pm.suspend_iram_paddr,
> +					imx6q_iram_pm.suspend_iram_size,
> +					MT_MEMORY_NONCACHED);
> +		pr_info("cpaddr = %p suspend_iram_base=%p\n",

pr_debug?

> +				imx6q_iram_pm.iram_cpaddr,
> +				imx6q_iram_pm.suspend_iram_vaddr);
> +
> +		/*
> +		 * Need to run the suspend code from IRAM as the DDR needs
> +		 * to be put into low power mode manually.
> +		 */
> +		memcpy(imx6q_iram_pm.iram_cpaddr, imx6q_suspend,
> +					imx6q_iram_pm.suspend_iram_size);

Please try to use fncpy (arch/arm/include/asm/fncpy.h)

> +
> +		suspend_in_iram = (void *)imx6q_iram_pm.suspend_iram_vaddr;
> +
> +	} else
> +		suspend_in_iram = NULL;

The suspend_in_iram is already initialized as NULL, I guess.

>  }
> diff --git a/arch/arm/mach-imx/suspend-imx6q.S b/arch/arm/mach-imx/suspend-imx6q.S
> new file mode 100644
> index 0000000..f803e2b
> --- /dev/null
> +++ b/arch/arm/mach-imx/suspend-imx6q.S
> @@ -0,0 +1,308 @@
> +/*
> + * Copyright 2011 Freescale Semiconductor, Inc.
> + * Copyright 2011 Linaro Ltd.
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + */
> +
> +#include <linux/linkage.h>
> +#include <mach/hardware.h>
> +#include <asm/hardware/cache-l2x0.h>
> +
> +#define SRC_GPR1_OFFSET		0x020
> +#define SRC_GPR2_OFFSET		0x024
> +#define MMDC_MAPSR_OFFSET 	0x404
> +#define MMDC_MAPSR_PSS 		(1 << 4)
> +#define MMDC_MAPSR_PSD 		(1 << 0)
> +#define ANATOP_REG_2P5		0x130
> +
> +.macro	ddr_io_save
> +	ldr	r4, [r9, #0x5ac] /* DRAM_DQM0 */

If we have macro defined for DRAM_DQM0, we can save the magic number
and the comment.  It applies to the same cases all over the ddr_io_xxx
functions.

> +	ldr	r5, [r9, #0x5b4] /* DRAM_DQM1 */
> +	ldr	r6, [r9, #0x528] /* DRAM_DQM2 */
> +	ldr	r7, [r9, #0x520] /* DRAM_DQM3 */
> +	stmfd	sp!, {r4-r7}
> +
> +	ldr	r4, [r9, #0x514] /* DRAM_DQM4 */
> +	ldr	r5, [r9, #0x510] /* DRAM_DQM5 */
> +	ldr	r6, [r9, #0x5bc] /* DRAM_DQM6 */
> +	ldr	r7, [r9, #0x5c4] /* DRAM_DQM7 */
> +	stmfd	sp!, {r4-r7}
> +
> +	ldr	r4, [r9, #0x56c] /* DRAM_CAS */
> +	ldr	r5, [r9, #0x578] /* DRAM_RAS */
> +	ldr	r6, [r9, #0x588] /* DRAM_SDCLK_0 */
> +	ldr	r7, [r9, #0x594] /* DRAM_SDCLK_1 */
> +	stmfd	sp!, {r4-r7}
> +
> +	ldr	r5, [r9, #0x750] /* DDRMODE_CTL */
> +	ldr	r6, [r9, #0x774] /* DDRMODE */
> +	stmfd	sp!, {r5-r6}
> +
> +	ldr	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
> +	ldr	r5, [r9, #0x5b0] /* DRAM_SDQS1 */
> +	ldr	r6, [r9, #0x524] /* DRAM_SDQS2 */
> +	ldr	r7, [r9, #0x51c] /* DRAM_SDQS3 */
> +	stmfd	sp!, {r4-r7}
> +
> +	ldr	r4, [r9, #0x518] /* DRAM_SDQS4 */
> +	ldr	r5, [r9, #0x50c] /* DRAM_SDQS5 */
> +	ldr	r6, [r9, #0x5b8] /* DRAM_SDQS6 */
> +	ldr	r7, [r9, #0x5c0] /* DRAM_SDQS7 */
> +	stmfd	sp!, {r4-r7}
> +
> +	ldr	r4, [r9, #0x784] /* GPR_B0DS */
> +	ldr	r5, [r9, #0x788] /* GPR_B1DS */
> +	ldr	r6, [r9, #0x794] /* GPR_B2DS */
> +	ldr	r7, [r9, #0x79c] /* GPR_B3DS */
> +	stmfd	sp!, {r4-r7}
> +
> +	ldr	r4, [r9, #0x7a0] /* GPR_B4DS */
> +	ldr	r5, [r9, #0x7a4] /* GPR_B5DS */
> +	ldr	r6, [r9, #0x7a8] /* GPR_B6DS */
> +	ldr	r7, [r9, #0x748] /* GPR_B7DS */
> +	stmfd	sp!, {r4-r7}
> +
> +	ldr	r5, [r9, #0x74c] /* GPR_ADDS*/
> +	ldr	r6, [r9, #0x59c] /* DRAM_SODT0*/
> +	ldr	r7, [r9, #0x5a0] /* DRAM_SODT1*/
> +	stmfd	sp!, {r5-r7}
> +.endm
> +
> +.macro	ddr_io_restore
> +	ldmfd	sp!, {r5-r7}
> +	str	r5, [r9, #0x74c] /* GPR_ADDS*/
> +	str	r6, [r9, #0x59c] /* DRAM_SODT0*/
> +	str	r7, [r9, #0x5a0] /* DRAM_SODT1*/
> +
> +	ldmfd	sp!, {r4-r7}
> +	str	r4, [r9, #0x7a0] /* GPR_B4DS */
> +	str	r5, [r9, #0x7a4] /* GPR_B5DS */
> +	str	r6, [r9, #0x7a8] /* GPR_B6DS */
> +	str	r7, [r9, #0x748] /* GPR_B7DS */
> +
> +	ldmfd	sp!, {r4-r7}
> +	str	r4, [r9, #0x784] /* GPR_B0DS */
> +	str	r5, [r9, #0x788] /* GPR_B1DS */
> +	str	r6, [r9, #0x794] /* GPR_B2DS */
> +	str	r7, [r9, #0x79c] /* GPR_B3DS */
> +
> +	ldmfd	sp!, {r4-r7}
> +	str	r4, [r9, #0x518] /* DRAM_SDQS4 */
> +	str	r5, [r9, #0x50c] /* DRAM_SDQS5 */
> +	str	r6, [r9, #0x5b8] /* DRAM_SDQS6 */
> +	str	r7, [r9, #0x5c0] /* DRAM_SDQS7 */
> +
> +	ldmfd	sp!, {r4-r7}
> +	str	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
> +	str	r5, [r9, #0x5b0] /* DRAM_SDQS1 */
> +	str	r6, [r9, #0x524] /* DRAM_SDQS2 */
> +	str	r7, [r9, #0x51c] /* DRAM_SDQS3 */
> +
> +	ldmfd	sp!, {r5-r6}
> +	str	r5, [r9, #0x750] /* DDRMODE_CTL */
> +	str	r6, [r9, #0x774] /* DDRMODE */
> +
> +	ldmfd	sp!, {r4-r7}
> +	str	r4, [r9, #0x56c] /* DRAM_CAS */
> +	str	r5, [r9, #0x578] /* DRAM_RAS */
> +	str	r6, [r9, #0x588] /* DRAM_SDCLK_0 */
> +	str	r7, [r9, #0x594] /* DRAM_SDCLK_1 */
> +
> +	ldmfd	sp!, {r4-r7}
> +	str	r4, [r9, #0x514] /* DRAM_DQM4 */
> +	str	r5, [r9, #0x510] /* DRAM_DQM5 */
> +	str	r6, [r9, #0x5bc] /* DRAM_DQM6 */
> +	str	r7, [r9, #0x5c4] /* DRAM_DQM7 */
> +
> +	ldmfd	sp!, {r4-r7}
> +	str	r4, [r9, #0x5ac] /* DRAM_DQM0 */
> +	str	r5, [r9, #0x5b4] /* DRAM_DQM1 */
> +	str	r6, [r9, #0x528] /* DRAM_DQM2 */
> +	str	r7, [r9, #0x520] /* DRAM_DQM3 */
> +.endm
> +
> +.macro	ddr_io_set_lpm
> +	mov	r4, #0
> +	str	r4, [r9, #0x5ac] /* DRAM_DQM0 */
> +	str	r4, [r9, #0x5b4] /* DRAM_DQM1 */
> +	str	r4, [r9, #0x528] /* DRAM_DQM2 */
> +	str	r4, [r9, #0x520] /* DRAM_DQM3 */
> +
> +	str	r4, [r9, #0x514] /* DRAM_DQM4 */
> +	str	r4, [r9, #0x510] /* DRAM_DQM5 */
> +	str	r4, [r9, #0x5bc] /* DRAM_DQM6 */
> +	str	r4, [r9, #0x5c4] /* DRAM_DQM7 */
> +
> +	str	r4, [r9, #0x56c] /* DRAM_CAS */
> +	str	r4, [r9, #0x578] /* DRAM_RAS */
> +	str	r4, [r9, #0x588] /* DRAM_SDCLK_0 */
> +	str	r4, [r9, #0x594] /* DRAM_SDCLK_1 */
> +
> +	str	r4, [r9, #0x750] /* DDRMODE_CTL */
> +	str	r4, [r9, #0x774] /* DDRMODE */
> +
> +	str	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
> +	str	r4, [r9, #0x5b0] /* DRAM_SDQS1 */
> +	str	r4, [r9, #0x524] /* DRAM_SDQS2 */
> +	str	r4, [r9, #0x51c] /* DRAM_SDQS3 */
> +
> +	str	r4, [r9, #0x518] /* DRAM_SDQS4 */
> +	str	r4, [r9, #0x50c] /* DRAM_SDQS5 */
> +	str	r4, [r9, #0x5b8] /* DRAM_SDQS6 */
> +	str	r4, [r9, #0x5c0] /* DRAM_SDQS7 */
> +
> +	str	r4, [r9, #0x784] /* GPR_B0DS */
> +	str	r4, [r9, #0x788] /* GPR_B1DS */
> +	str	r4, [r9, #0x794] /* GPR_B2DS */
> +	str	r4, [r9, #0x79c] /* GPR_B3DS */
> +
> +	str	r4, [r9, #0x7a0] /* GPR_B4DS */
> +	str	r4, [r9, #0x7a4] /* GPR_B5DS */
> +	str	r4, [r9, #0x7a8] /* GPR_B6DS */
> +	str	r4, [r9, #0x748] /* GPR_B7DS */
> +
> +	str	r4, [r9, #0x74c] /* GPR_ADDS*/
> +	str	r4, [r9, #0x59c] /* DRAM_SODT0*/
> +	str	r4, [r9, #0x5a0] /* DRAM_SODT1*/
> +.endm
> +
> +/*
> + * imx6q_suspend:
> + *
> + * Suspend the processor (eg, wait for interrupt).
> + * Set the DDR into Self Refresh
> + * IRQs are already disabled.
> + *
> + * Registers usage in the imx6q_suspend:
> + *
> + * r0: suspend_iram_paddr
> + * r1: suspend_iram_vaddr
> + * r2: suspend_iram_size
> + *
> + * r8: src_base pointer
> + * r9: iomux_base pointer
> + * r10: mmdc_base pointer
> + * r11: anatop_base pointer
> + * sp: iram stack
> + *
> + * Corrupted registers: r0-r3
> + */
> +
> +ENTRY(imx6q_suspend)
> +	mov	r3, sp				@ save sp
> +	add	sp, r1, r2			@ set sp to top iram stack
> +	sub	sp, sp, #16	 		@ 4 regs ptr
> +	stmfd	sp!, {r4 - r12, lr}		@ save registers
> +
> +	add	r4, r1, r2
> +	ldr	r8, [r4, #-16]			@ r8 = src_base pointer
> +	ldr	r9, [r4, #-12]			@ r9 = iomux_base pointer
> +	ldr	r10, [r4, #-8]			@ r10 = mmdc_base pointer
> +	ldr	r11, [r4, #-4]			@ r11 = anatop_base pointer
> +
> +	/* should not access sp in ddr until resume with cache MMU on */
> +	stmfd	sp!, {r3}			@ save old sp
> +
> +	ldr	r4, [r8, #SRC_GPR1_OFFSET]	@ r8 = src_base pointer
> +	stmfd	sp!, {r4}			@ save old resume func
> +
> +	/* Enable weak 2P5 linear regulator */
> +	ldr	r4, [r11, #ANATOP_REG_2P5]	@ r11 = anatop_base pointer
> +	orr	r4, r4, #0x40000
> +	str	r4, [r11, #ANATOP_REG_2P5]
> +	mov	r6, #1
> +wait:	ldr	r4, [r11, #ANATOP_REG_2P5]
> +	and	r4, r4, r6, lsl #17			@ output ok?
> +	cmp	r4, #0
> +	beq	wait
> +
> +	/* save ddr iomux regs */
> +	ddr_io_save
> +

Do we really need to have all above codes running in iram?  My
understanding is only the portion of the codes that actually puts
DDR into self-refresh state and the codes after that need to run
in iram.

> +	/* set ddr to low power mode */
> +	ldr	r4, [r10, #MMDC_MAPSR_OFFSET]	@ r10 = mmdc_base pointer
> +	bic	r4, #MMDC_MAPSR_PSD
> +	str	r4, [r10, #MMDC_MAPSR_OFFSET]
> +refresh:
> +	ldr	r4, [r10, #MMDC_MAPSR_OFFSET]
> +	and	r4, r4, #MMDC_MAPSR_PSS
> +	cmp	r4, #0
> +	beq	refresh
> +
> +	ddr_io_set_lpm
> +
> +	/* save resume pointer into SRC_GPR1, sp pointer into SRC_GPR2 */
> +	ldr	r4, =imx6q_suspend
> +	ldr	r5, =imx6q_resume
> +	sub	r5, r5, r4			@ r5 = resmue offset
> +	add	r5, r0, r5			@ r0 = suspend_iram_paddr, r5 = resmue phy addr
> +	str	r5, [r8, #SRC_GPR1_OFFSET]	@ r8 = src_base pointer
> +	sub	r5, sp, r1			@ r1 = suspend_iram_vaddr, r5 = sp offset
> +	add	r5, r0, r5			@ r0 = suspend_iram_paddr, r5 = sp phy addr
> +	str	r5, [r8, #SRC_GPR2_OFFSET]	@ r8 = src_base pointer
> +
> +	/* execute a wfi instruction to let SOC go into stop mode */
> +	dsb
> +	wfi
> +
> +	nop
> +	nop
> +	nop
> +	nop
> +
> +	/*
> +	 * if go here, means there is a wakeup irq pending, we should resume
> +	 * system immediately.
> +	 */

I'm wondering how this will happen exactly.  Is this a real case in
practical world?  If yes, do you have the steps to reproduce it?  I'm
actually asking if this resume path has been tested yet.

> +	ddr_io_restore
> +
> +	/* Disable weak 2P5 linear regulator */
> +	ldr	r4, [r11, #ANATOP_REG_2P5]	@ r11 = anatop_base pointer
> +	bic	r4, #0x40000
> +	str	r4, [r11, #ANATOP_REG_2P5]
> +
> +	ldmfd	sp!, {r4}			@ drop old resmue func ptr
> +	ldmfd	sp!, {r3}
> +	ldmfd	sp!, {r4 - r12, lr}
> +	mov	sp, r3
> +	mov	pc, lr
> +
> +/*
> + * when SOC exit stop mode, arm core restart from here, currently
> + * are running with MMU off.
> + */
> +imx6q_resume:
> +	ldr	r0, =MX6Q_SRC_BASE_ADDR
> +	mov	r1, #0x0
> +	str	r1, [r0, #SRC_GPR1_OFFSET] 	@ clear SRC_GPR1
> +	ldr	sp, [r0, #SRC_GPR2_OFFSET]
> +	str	r1, [r0, #SRC_GPR2_OFFSET]	@ clear SRC_GPR2
> +
> +	ldr	r9, =MX6Q_IOMUXC_BASE_ADDR
> +	ddr_io_restore
> +
> +	ldr	r0, =MX6Q_MMDC0_BASE_ADDR
> +	ldr	r1, [r0, #MMDC_MAPSR_OFFSET]
> +	orr	r1, #MMDC_MAPSR_PSD
> +	str	r1, [r0, #MMDC_MAPSR_OFFSET]
> +
> +	/* Disable weak 2P5 linear regulator */
> +	ldr	r0, =MX6Q_ANATOP_BASE_ADDR
> +	ldr	r1, [r0, #ANATOP_REG_2P5]
> +	bic	r1, #0x40000
> +	str	r1, [r0, #ANATOP_REG_2P5]
> +
> +	ldmfd	sp!, {r2}			@ resmue func ptr
> +	ldmfd	sp!, {r3}
> +	ldmfd	sp!, {r4 - r12, lr}
> +	mov	sp, r3
> +
> +	/* return back */
> +	mov	pc, r2
> +ENDPROC(imx6q_suspend)
> diff --git a/arch/arm/plat-mxc/include/mach/common.h b/arch/arm/plat-mxc/include/mach/common.h
> index 83cca9b..0e1e652 100644
> --- a/arch/arm/plat-mxc/include/mach/common.h
> +++ b/arch/arm/plat-mxc/include/mach/common.h
> @@ -82,6 +82,7 @@ enum mxc_cpu_pwr_mode {
>  	WAIT_UNCLOCKED_POWER_OFF,	/* WAIT + SRPG */
>  	STOP_POWER_ON,		/* just STOP */
>  	STOP_POWER_OFF,		/* STOP + SRPG */
> +	ARM_POWER_OFF,          /* STOP + SRPG + ARM power off */

I'm not sure that we need this new mode at all.  From the comment,
the 'ARM power off' is the difference between ARM_POWER_OFF and
STOP_POWER_OFF.  But it's not really the case.

Regards,
Shawn

>  };
>  
>  extern void mx5_cpu_lp_set(enum mxc_cpu_pwr_mode mode);
> @@ -123,6 +124,7 @@ extern void imx_set_cpu_jump(int cpu, void *jump_addr);
>  extern void imx_src_init(void);
>  extern void imx_src_prepare_restart(void);
>  extern void imx_gpc_init(void);
> +extern bool imx_gpc_wake_irq_pending(void);
>  extern void imx_gpc_pre_suspend(void);
>  extern void imx_gpc_post_resume(void);
>  extern void imx51_babbage_common_init(void);
> diff --git a/arch/arm/plat-mxc/include/mach/mx6q.h b/arch/arm/plat-mxc/include/mach/mx6q.h
> index 254a561..eea2968 100644
> --- a/arch/arm/plat-mxc/include/mach/mx6q.h
> +++ b/arch/arm/plat-mxc/include/mach/mx6q.h
> @@ -27,6 +27,12 @@
>  #define MX6Q_CCM_SIZE			0x4000
>  #define MX6Q_ANATOP_BASE_ADDR		0x020c8000
>  #define MX6Q_ANATOP_SIZE		0x1000
> +#define MX6Q_SRC_BASE_ADDR		0x020d8000
> +#define MX6Q_SRC_SIZE			0x4000
> +#define MX6Q_IOMUXC_BASE_ADDR		0x020e0000
> +#define MX6Q_IOMUXC_SIZE		0x4000
> +#define MX6Q_MMDC0_BASE_ADDR 		0x021b0000
> +#define MX6Q_MMDC0_SIZE 		0x4000
>  #define MX6Q_UART4_BASE_ADDR		0x021f0000
>  #define MX6Q_UART4_SIZE			0x4000
>  
> -- 
> 1.7.4.1
jason.chen@freescale.com Jan. 4, 2012, 7:24 a.m. UTC | #2
hi, Shawn,

I'll separate this patches and update with your comments.

On Tue, Jan 03, 2012 at 10:24:51PM +0800, Shawn Guo wrote:
> On Sat, Dec 31, 2011 at 02:23:44PM +0800, Jason Chen wrote:
> > For most power saving, this patch make arm core stop and
> 
> s/most/more?
> 
> Without this patch, the 'mem' state already powers arm core off.
> 
> > ddr go into self-refresh mode.
> > 
> > It already be tested on Freescale imx6q-arm2 board.
> > 
> 
> I managed to apply the patch on v3.2-rc7 and tested it on ARM2 board,
> but it does not work for me.
> 
> > Signed-off-by: Jason Chen <jason.chen@linaro.org>
> > Signed-off-by: Jason Chen <jason.chen@freescale.com>
> > ---
> >  arch/arm/mach-imx/Makefile              |    2 +-
> >  arch/arm/mach-imx/clock-imx6q.c         |   37 +++--
> >  arch/arm/mach-imx/gpc.c                 |   21 ++
> >  arch/arm/mach-imx/pm-imx6q.c            |  128 +++++++++++++-
> >  arch/arm/mach-imx/suspend-imx6q.S       |  308 +++++++++++++++++++++++++++++++
> >  arch/arm/plat-mxc/include/mach/common.h |    2 +
> >  arch/arm/plat-mxc/include/mach/mx6q.h   |    6 +
> >  7 files changed, 487 insertions(+), 17 deletions(-)
> > 
> 
> The checkpatch.pl reports 3 warnings.
> 
> > diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
> > index 88a3966..0b07eef 100644
> > --- a/arch/arm/mach-imx/Makefile
> > +++ b/arch/arm/mach-imx/Makefile
> > @@ -72,7 +72,7 @@ AFLAGS_head-v7.o :=-Wa,-march=armv7-a
> >  obj-$(CONFIG_SMP) += platsmp.o
> >  obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
> >  obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o
> > -obj-$(CONFIG_SOC_IMX6Q) += clock-imx6q.o mach-imx6q.o pm-imx6q.o
> > +obj-$(CONFIG_SOC_IMX6Q) += clock-imx6q.o mach-imx6q.o pm-imx6q.o suspend-imx6q.o
> >  
> >  # i.MX5 based machines
> >  obj-$(CONFIG_MACH_MX51_BABBAGE) += mach-mx51_babbage.o
> > diff --git a/arch/arm/mach-imx/clock-imx6q.c b/arch/arm/mach-imx/clock-imx6q.c
> > index 56a7a3f..b7fecad 100644
> > --- a/arch/arm/mach-imx/clock-imx6q.c
> > +++ b/arch/arm/mach-imx/clock-imx6q.c
> > @@ -115,6 +115,8 @@
> >  #define CG14		28
> >  #define CG15		30
> >  
> > +#define BM_CCR_RBC_EN			(0x1 << 27)
> > +
> >  #define BM_CCSR_PLL1_SW_SEL		(0x1 << 2)
> >  #define BM_CCSR_STEP_SEL		(0x1 << 8)
> >  
> > @@ -1916,33 +1918,44 @@ static struct clk_lookup lookups[] = {
> >  
> >  int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode)
> >  {
> > -	u32 val = readl_relaxed(CLPCR);
> > +	u32 clpcr = readl_relaxed(CLPCR);
> > +	u32 ccr = readl_relaxed(CCR);
> >  
> > -	val &= ~BM_CLPCR_LPM;
> > +	clpcr &= ~(BM_CLPCR_LPM | BM_CLPCR_VSTBY | BM_CLPCR_SBYOS
> > +			| BM_CLPCR_STBY_COUNT | BM_CLPCR_WB_PER_AT_LPM);
> > +	ccr &= ~(BM_CCR_RBC_EN);
> >  	switch (mode) {
> >  	case WAIT_CLOCKED:
> >  		break;
> >  	case WAIT_UNCLOCKED:
> > -		val |= 0x1 << BP_CLPCR_LPM;
> > +		clpcr |= 0x1 << BP_CLPCR_LPM;
> >  		break;
> >  	case STOP_POWER_ON:
> > -		val |= 0x2 << BP_CLPCR_LPM;
> > +		clpcr |= 0x2 << BP_CLPCR_LPM;
> >  		break;
> >  	case WAIT_UNCLOCKED_POWER_OFF:
> > -		val |= 0x1 << BP_CLPCR_LPM;
> > -		val &= ~BM_CLPCR_VSTBY;
> > -		val &= ~BM_CLPCR_SBYOS;
> > +		clpcr |= 0x1 << BP_CLPCR_LPM;
> >  		break;
> >  	case STOP_POWER_OFF:
> > -		val |= 0x2 << BP_CLPCR_LPM;
> > -		val |= 0x3 << BP_CLPCR_STBY_COUNT;
> > -		val |= BM_CLPCR_VSTBY;
> > -		val |= BM_CLPCR_SBYOS;
> > +		clpcr |= 0x2 << BP_CLPCR_LPM;
> > +		clpcr |= 0x3 << BP_CLPCR_STBY_COUNT;
> > +		clpcr |= BM_CLPCR_VSTBY;
> > +		clpcr |= BM_CLPCR_SBYOS;
> > +		break;
> > +	case ARM_POWER_OFF:
> 
> I'm unsure we need this new state.
> 
> > +		clpcr |= 0x2 << BP_CLPCR_LPM;
> > +		clpcr |= 0x3 << BP_CLPCR_STBY_COUNT;
> > +		clpcr |= BM_CLPCR_VSTBY;
> > +		clpcr |= BM_CLPCR_SBYOS;
> > +		clpcr |= BM_CLPCR_WB_PER_AT_LPM;
> > +		/* assert anatop_reg_bypass signal */
> > +		ccr |= BM_CCR_RBC_EN;
> 
> It seems that the difference between STOP_POWER_OFF and ARM_POWER_OFF
> is all about bits BM_CLPCR_WB_PER_AT_LPM and BM_CCR_RBC_EN.  Can you
> elaborate why these two bits need to be set for suspend with DDR in
> self-refresh state?
> 
> >  		break;
> >  	default:
> >  		return -EINVAL;
> >  	}
> > -	writel_relaxed(val, CLPCR);
> > +	writel_relaxed(clpcr, CLPCR);
> > +	writel_relaxed(ccr, CCR);
> >  
> >  	return 0;
> >  }
> > diff --git a/arch/arm/mach-imx/gpc.c b/arch/arm/mach-imx/gpc.c
> > index e1537f9..8fc255b 100644
> > --- a/arch/arm/mach-imx/gpc.c
> > +++ b/arch/arm/mach-imx/gpc.c
> > @@ -17,15 +17,36 @@
> >  #include <linux/of_irq.h>
> >  #include <asm/hardware/gic.h>
> >  
> > +#define GPC_CNTR		0x000
> 
> Where is it used?
> 
> >  #define GPC_IMR1		0x008
> > +#define GPC_ISR1		0x018
> > +#define GPC_ISR2		0x01c
> > +#define GPC_ISR3		0x020
> > +#define GPC_ISR4		0x024
> 
> It seems that only definition GPC_ISR1 is used?
> 
> >  #define GPC_PGC_CPU_PDN		0x2a0
> >  
> >  #define IMR_NUM			4
> > +#define ISR_NUM			4
> >  
> >  static void __iomem *gpc_base;
> >  static u32 gpc_wake_irqs[IMR_NUM];
> >  static u32 gpc_saved_imrs[IMR_NUM];
> >  
> > +bool imx_gpc_wake_irq_pending(void)
> > +{
> > +	void __iomem *reg_isr1 = gpc_base + GPC_ISR1;
> > +	int i;
> > +	u32 val;
> > +
> > +	for (i = 0; i < ISR_NUM; i++) {
> > +		val = readl_relaxed(reg_isr1 + i * 4);
> > +		if (val & gpc_wake_irqs[i])
> > +			return true;
> > +	}
> > +
> > +	return false;
> > +}
> > +
> >  void imx_gpc_pre_suspend(void)
> >  {
> >  	void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
> > diff --git a/arch/arm/mach-imx/pm-imx6q.c b/arch/arm/mach-imx/pm-imx6q.c
> > index f20f191..6e45245 100644
> > --- a/arch/arm/mach-imx/pm-imx6q.c
> > +++ b/arch/arm/mach-imx/pm-imx6q.c
> > @@ -13,31 +13,73 @@
> >  #include <linux/init.h>
> >  #include <linux/io.h>
> >  #include <linux/of.h>
> > +#include <linux/of_address.h>
> >  #include <linux/suspend.h>
> >  #include <asm/cacheflush.h>
> >  #include <asm/proc-fns.h>
> >  #include <asm/suspend.h>
> > +#include <asm/mach/map.h>
> >  #include <asm/hardware/cache-l2x0.h>
> > +#include <mach/iram.h>
> >  #include <mach/common.h>
> >  #include <mach/hardware.h>
> >  
> > +struct imx_iram_pm {
> > +	void *iram_cpaddr;
> > +	unsigned long suspend_iram_paddr;
> > +	unsigned long suspend_iram_size;
> > +	void *suspend_iram_vaddr;
> > +	void *reg_ptr[4];
> > +} imx6q_iram_pm;
> > +
> >  extern unsigned long phys_l2x0_saved_regs;
> > +extern void imx6q_suspend(void);
> > +static void (*suspend_in_iram)(unsigned long iram_paddr,
> > +				unsigned long iram_vaddr,
> > +				unsigned long iram_size);
> >  
> >  static int imx6q_suspend_finish(unsigned long val)
> >  {
> > -	cpu_do_idle();
> > +	if ((val == PM_SUSPEND_MEM) && suspend_in_iram) {
> > +		suspend_in_iram((unsigned long)imx6q_iram_pm.suspend_iram_paddr,
> > +				(unsigned long)imx6q_iram_pm.suspend_iram_vaddr,
> > +				(unsigned long)imx6q_iram_pm.suspend_iram_size);
> > +	} else
> 
> The brace pair {} is not needed.
> 
> > +		cpu_do_idle();
> > +
> >  	return 0;
> >  }
> >  
> > +static void imx6q_prepare_suspend_iram(void)
> > +{
> > +	unsigned long *iram_stack = imx6q_iram_pm.suspend_iram_vaddr
> > +					+ imx6q_iram_pm.suspend_iram_size;
> > +
> > +	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[3];
> > +	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[2];
> > +	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[1];
> > +	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[0];
> > +}
> > +
> 
> I'm not sure we need to do this.  I feel we have done too much in
> suspend_in_iram().  See comments on suspend_in_iram below.
> 
> >  static int imx6q_pm_enter(suspend_state_t state)
> >  {
> >  	switch (state) {
> > +	case PM_SUSPEND_STANDBY:
> >  	case PM_SUSPEND_MEM:
> > -		imx6q_set_lpm(STOP_POWER_OFF);
> > +		if (imx_gpc_wake_irq_pending())
> > +			return 0;
> > +
> > +		if (state == PM_SUSPEND_STANDBY)
> > +			imx6q_set_lpm(STOP_POWER_OFF);
> > +		else
> > +			imx6q_set_lpm(ARM_POWER_OFF);
> > +
> 
> I do not think we need to distinguish these two states, STOP_POWER_OFF
> and ARM_POWER_OFF.  When I added STOP_POWER_OFF support, the DDR self
> refresh support was missed there.  When this feature is available, we
> can just add it on top of STOP_POWER_OFF rather than inventing a new
> state.
> 
> >  		imx_gpc_pre_suspend();
> >  		imx_set_cpu_jump(0, v7_cpu_resume);
> > +		if (state == PM_SUSPEND_MEM)
> > +			imx6q_prepare_suspend_iram();
> >  		/* Zzz ... */
> > -		cpu_suspend(0, imx6q_suspend_finish);
> > +		cpu_suspend(state, imx6q_suspend_finish);
> >  		imx_smp_prepare();
> >  		imx_gpc_post_resume();
> >  		break;
> > @@ -48,11 +90,55 @@ static int imx6q_pm_enter(suspend_state_t state)
> >  	return 0;
> >  }
> >  
> > +static int imx6q_pm_valid(suspend_state_t state)
> > +{
> > +	return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX);
> > +}
> > +
> >  static const struct platform_suspend_ops imx6q_pm_ops = {
> >  	.enter = imx6q_pm_enter,
> > -	.valid = suspend_valid_only_mem,
> > +	.valid = imx6q_pm_valid,
> >  };
> >  
> > +static int __init imx6q_pm_iram_of_init(void)
> > +{
> > +	struct device_node *np;
> > +
> > +	/*
> > +	 * these register may already ioremaped, need figure out
> > +	 * one way to save vmalloc space.
> > +	 */
> > +	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-src");
> > +	imx6q_iram_pm.reg_ptr[0] = of_iomap(np, 0);
> > +	if (!imx6q_iram_pm.reg_ptr[0])
> > +		goto err0;
> > +
> > +	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc");
> > +	imx6q_iram_pm.reg_ptr[1] = of_iomap(np, 0);
> > +	if (!imx6q_iram_pm.reg_ptr[1])
> > +		goto err1;
> > +
> > +	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc");
> > +	imx6q_iram_pm.reg_ptr[2] = of_iomap(np, 0);
> > +	if (!imx6q_iram_pm.reg_ptr[2])
> > +		goto err2;
> > +
> > +	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop");
> > +	imx6q_iram_pm.reg_ptr[3] = of_iomap(np, 0);
> > +	if (!imx6q_iram_pm.reg_ptr[3])
> > +		goto err3;
> 
> This is not really nice and necessary, if we can limit the work done
> in suspend_in_iram().
> 
> > +
> > +	return 0;
> > +err3:
> > +	iounmap(imx6q_iram_pm.reg_ptr[2]);
> > +err2:
> > +	iounmap(imx6q_iram_pm.reg_ptr[1]);
> > +err1:
> > +	iounmap(imx6q_iram_pm.reg_ptr[0]);
> > +err0:
> > +	return -EINVAL;
> > +}
> > +
> >  void __init imx6q_pm_init(void)
> >  {
> >  	/*
> > @@ -67,4 +153,38 @@ void __init imx6q_pm_init(void)
> >  	phys_l2x0_saved_regs = __pa(&l2x0_saved_regs);
> >  
> >  	suspend_set_ops(&imx6q_pm_ops);
> > +
> > +	/* Move suspend routine into iRAM */
> > +	imx6q_iram_pm.suspend_iram_size = SZ_4K;
> > +	imx6q_iram_pm.iram_cpaddr = iram_alloc(imx6q_iram_pm.suspend_iram_size,
> > +			&imx6q_iram_pm.suspend_iram_paddr);
> > +	if (imx6q_iram_pm.iram_cpaddr) {
> > +		if (imx6q_pm_iram_of_init() < 0) {
> > +			iram_free(imx6q_iram_pm.suspend_iram_paddr,
> > +					imx6q_iram_pm.suspend_iram_size);
> > +			return;
> > +		}
> > +		/*
> > +		 * Need to remap the area here since we want the memory region
> > +		 * to be noncached & executable.
> > +		 */
> > +		imx6q_iram_pm.suspend_iram_vaddr =
> > +			__arm_ioremap(imx6q_iram_pm.suspend_iram_paddr,
> > +					imx6q_iram_pm.suspend_iram_size,
> > +					MT_MEMORY_NONCACHED);
> > +		pr_info("cpaddr = %p suspend_iram_base=%p\n",
> 
> pr_debug?
> 
> > +				imx6q_iram_pm.iram_cpaddr,
> > +				imx6q_iram_pm.suspend_iram_vaddr);
> > +
> > +		/*
> > +		 * Need to run the suspend code from IRAM as the DDR needs
> > +		 * to be put into low power mode manually.
> > +		 */
> > +		memcpy(imx6q_iram_pm.iram_cpaddr, imx6q_suspend,
> > +					imx6q_iram_pm.suspend_iram_size);
> 
> Please try to use fncpy (arch/arm/include/asm/fncpy.h)
> 
> > +
> > +		suspend_in_iram = (void *)imx6q_iram_pm.suspend_iram_vaddr;
> > +
> > +	} else
> > +		suspend_in_iram = NULL;
> 
> The suspend_in_iram is already initialized as NULL, I guess.
> 
> >  }
> > diff --git a/arch/arm/mach-imx/suspend-imx6q.S b/arch/arm/mach-imx/suspend-imx6q.S
> > new file mode 100644
> > index 0000000..f803e2b
> > --- /dev/null
> > +++ b/arch/arm/mach-imx/suspend-imx6q.S
> > @@ -0,0 +1,308 @@
> > +/*
> > + * Copyright 2011 Freescale Semiconductor, Inc.
> > + * Copyright 2011 Linaro Ltd.
> > + *
> > + * The code contained herein is licensed under the GNU General Public
> > + * License. You may obtain a copy of the GNU General Public License
> > + * Version 2 or later at the following locations:
> > + *
> > + * http://www.opensource.org/licenses/gpl-license.html
> > + * http://www.gnu.org/copyleft/gpl.html
> > + */
> > +
> > +#include <linux/linkage.h>
> > +#include <mach/hardware.h>
> > +#include <asm/hardware/cache-l2x0.h>
> > +
> > +#define SRC_GPR1_OFFSET		0x020
> > +#define SRC_GPR2_OFFSET		0x024
> > +#define MMDC_MAPSR_OFFSET 	0x404
> > +#define MMDC_MAPSR_PSS 		(1 << 4)
> > +#define MMDC_MAPSR_PSD 		(1 << 0)
> > +#define ANATOP_REG_2P5		0x130
> > +
> > +.macro	ddr_io_save
> > +	ldr	r4, [r9, #0x5ac] /* DRAM_DQM0 */
> 
> If we have macro defined for DRAM_DQM0, we can save the magic number
> and the comment.  It applies to the same cases all over the ddr_io_xxx
> functions.
> 
> > +	ldr	r5, [r9, #0x5b4] /* DRAM_DQM1 */
> > +	ldr	r6, [r9, #0x528] /* DRAM_DQM2 */
> > +	ldr	r7, [r9, #0x520] /* DRAM_DQM3 */
> > +	stmfd	sp!, {r4-r7}
> > +
> > +	ldr	r4, [r9, #0x514] /* DRAM_DQM4 */
> > +	ldr	r5, [r9, #0x510] /* DRAM_DQM5 */
> > +	ldr	r6, [r9, #0x5bc] /* DRAM_DQM6 */
> > +	ldr	r7, [r9, #0x5c4] /* DRAM_DQM7 */
> > +	stmfd	sp!, {r4-r7}
> > +
> > +	ldr	r4, [r9, #0x56c] /* DRAM_CAS */
> > +	ldr	r5, [r9, #0x578] /* DRAM_RAS */
> > +	ldr	r6, [r9, #0x588] /* DRAM_SDCLK_0 */
> > +	ldr	r7, [r9, #0x594] /* DRAM_SDCLK_1 */
> > +	stmfd	sp!, {r4-r7}
> > +
> > +	ldr	r5, [r9, #0x750] /* DDRMODE_CTL */
> > +	ldr	r6, [r9, #0x774] /* DDRMODE */
> > +	stmfd	sp!, {r5-r6}
> > +
> > +	ldr	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
> > +	ldr	r5, [r9, #0x5b0] /* DRAM_SDQS1 */
> > +	ldr	r6, [r9, #0x524] /* DRAM_SDQS2 */
> > +	ldr	r7, [r9, #0x51c] /* DRAM_SDQS3 */
> > +	stmfd	sp!, {r4-r7}
> > +
> > +	ldr	r4, [r9, #0x518] /* DRAM_SDQS4 */
> > +	ldr	r5, [r9, #0x50c] /* DRAM_SDQS5 */
> > +	ldr	r6, [r9, #0x5b8] /* DRAM_SDQS6 */
> > +	ldr	r7, [r9, #0x5c0] /* DRAM_SDQS7 */
> > +	stmfd	sp!, {r4-r7}
> > +
> > +	ldr	r4, [r9, #0x784] /* GPR_B0DS */
> > +	ldr	r5, [r9, #0x788] /* GPR_B1DS */
> > +	ldr	r6, [r9, #0x794] /* GPR_B2DS */
> > +	ldr	r7, [r9, #0x79c] /* GPR_B3DS */
> > +	stmfd	sp!, {r4-r7}
> > +
> > +	ldr	r4, [r9, #0x7a0] /* GPR_B4DS */
> > +	ldr	r5, [r9, #0x7a4] /* GPR_B5DS */
> > +	ldr	r6, [r9, #0x7a8] /* GPR_B6DS */
> > +	ldr	r7, [r9, #0x748] /* GPR_B7DS */
> > +	stmfd	sp!, {r4-r7}
> > +
> > +	ldr	r5, [r9, #0x74c] /* GPR_ADDS*/
> > +	ldr	r6, [r9, #0x59c] /* DRAM_SODT0*/
> > +	ldr	r7, [r9, #0x5a0] /* DRAM_SODT1*/
> > +	stmfd	sp!, {r5-r7}
> > +.endm
> > +
> > +.macro	ddr_io_restore
> > +	ldmfd	sp!, {r5-r7}
> > +	str	r5, [r9, #0x74c] /* GPR_ADDS*/
> > +	str	r6, [r9, #0x59c] /* DRAM_SODT0*/
> > +	str	r7, [r9, #0x5a0] /* DRAM_SODT1*/
> > +
> > +	ldmfd	sp!, {r4-r7}
> > +	str	r4, [r9, #0x7a0] /* GPR_B4DS */
> > +	str	r5, [r9, #0x7a4] /* GPR_B5DS */
> > +	str	r6, [r9, #0x7a8] /* GPR_B6DS */
> > +	str	r7, [r9, #0x748] /* GPR_B7DS */
> > +
> > +	ldmfd	sp!, {r4-r7}
> > +	str	r4, [r9, #0x784] /* GPR_B0DS */
> > +	str	r5, [r9, #0x788] /* GPR_B1DS */
> > +	str	r6, [r9, #0x794] /* GPR_B2DS */
> > +	str	r7, [r9, #0x79c] /* GPR_B3DS */
> > +
> > +	ldmfd	sp!, {r4-r7}
> > +	str	r4, [r9, #0x518] /* DRAM_SDQS4 */
> > +	str	r5, [r9, #0x50c] /* DRAM_SDQS5 */
> > +	str	r6, [r9, #0x5b8] /* DRAM_SDQS6 */
> > +	str	r7, [r9, #0x5c0] /* DRAM_SDQS7 */
> > +
> > +	ldmfd	sp!, {r4-r7}
> > +	str	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
> > +	str	r5, [r9, #0x5b0] /* DRAM_SDQS1 */
> > +	str	r6, [r9, #0x524] /* DRAM_SDQS2 */
> > +	str	r7, [r9, #0x51c] /* DRAM_SDQS3 */
> > +
> > +	ldmfd	sp!, {r5-r6}
> > +	str	r5, [r9, #0x750] /* DDRMODE_CTL */
> > +	str	r6, [r9, #0x774] /* DDRMODE */
> > +
> > +	ldmfd	sp!, {r4-r7}
> > +	str	r4, [r9, #0x56c] /* DRAM_CAS */
> > +	str	r5, [r9, #0x578] /* DRAM_RAS */
> > +	str	r6, [r9, #0x588] /* DRAM_SDCLK_0 */
> > +	str	r7, [r9, #0x594] /* DRAM_SDCLK_1 */
> > +
> > +	ldmfd	sp!, {r4-r7}
> > +	str	r4, [r9, #0x514] /* DRAM_DQM4 */
> > +	str	r5, [r9, #0x510] /* DRAM_DQM5 */
> > +	str	r6, [r9, #0x5bc] /* DRAM_DQM6 */
> > +	str	r7, [r9, #0x5c4] /* DRAM_DQM7 */
> > +
> > +	ldmfd	sp!, {r4-r7}
> > +	str	r4, [r9, #0x5ac] /* DRAM_DQM0 */
> > +	str	r5, [r9, #0x5b4] /* DRAM_DQM1 */
> > +	str	r6, [r9, #0x528] /* DRAM_DQM2 */
> > +	str	r7, [r9, #0x520] /* DRAM_DQM3 */
> > +.endm
> > +
> > +.macro	ddr_io_set_lpm
> > +	mov	r4, #0
> > +	str	r4, [r9, #0x5ac] /* DRAM_DQM0 */
> > +	str	r4, [r9, #0x5b4] /* DRAM_DQM1 */
> > +	str	r4, [r9, #0x528] /* DRAM_DQM2 */
> > +	str	r4, [r9, #0x520] /* DRAM_DQM3 */
> > +
> > +	str	r4, [r9, #0x514] /* DRAM_DQM4 */
> > +	str	r4, [r9, #0x510] /* DRAM_DQM5 */
> > +	str	r4, [r9, #0x5bc] /* DRAM_DQM6 */
> > +	str	r4, [r9, #0x5c4] /* DRAM_DQM7 */
> > +
> > +	str	r4, [r9, #0x56c] /* DRAM_CAS */
> > +	str	r4, [r9, #0x578] /* DRAM_RAS */
> > +	str	r4, [r9, #0x588] /* DRAM_SDCLK_0 */
> > +	str	r4, [r9, #0x594] /* DRAM_SDCLK_1 */
> > +
> > +	str	r4, [r9, #0x750] /* DDRMODE_CTL */
> > +	str	r4, [r9, #0x774] /* DDRMODE */
> > +
> > +	str	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
> > +	str	r4, [r9, #0x5b0] /* DRAM_SDQS1 */
> > +	str	r4, [r9, #0x524] /* DRAM_SDQS2 */
> > +	str	r4, [r9, #0x51c] /* DRAM_SDQS3 */
> > +
> > +	str	r4, [r9, #0x518] /* DRAM_SDQS4 */
> > +	str	r4, [r9, #0x50c] /* DRAM_SDQS5 */
> > +	str	r4, [r9, #0x5b8] /* DRAM_SDQS6 */
> > +	str	r4, [r9, #0x5c0] /* DRAM_SDQS7 */
> > +
> > +	str	r4, [r9, #0x784] /* GPR_B0DS */
> > +	str	r4, [r9, #0x788] /* GPR_B1DS */
> > +	str	r4, [r9, #0x794] /* GPR_B2DS */
> > +	str	r4, [r9, #0x79c] /* GPR_B3DS */
> > +
> > +	str	r4, [r9, #0x7a0] /* GPR_B4DS */
> > +	str	r4, [r9, #0x7a4] /* GPR_B5DS */
> > +	str	r4, [r9, #0x7a8] /* GPR_B6DS */
> > +	str	r4, [r9, #0x748] /* GPR_B7DS */
> > +
> > +	str	r4, [r9, #0x74c] /* GPR_ADDS*/
> > +	str	r4, [r9, #0x59c] /* DRAM_SODT0*/
> > +	str	r4, [r9, #0x5a0] /* DRAM_SODT1*/
> > +.endm
> > +
> > +/*
> > + * imx6q_suspend:
> > + *
> > + * Suspend the processor (eg, wait for interrupt).
> > + * Set the DDR into Self Refresh
> > + * IRQs are already disabled.
> > + *
> > + * Registers usage in the imx6q_suspend:
> > + *
> > + * r0: suspend_iram_paddr
> > + * r1: suspend_iram_vaddr
> > + * r2: suspend_iram_size
> > + *
> > + * r8: src_base pointer
> > + * r9: iomux_base pointer
> > + * r10: mmdc_base pointer
> > + * r11: anatop_base pointer
> > + * sp: iram stack
> > + *
> > + * Corrupted registers: r0-r3
> > + */
> > +
> > +ENTRY(imx6q_suspend)
> > +	mov	r3, sp				@ save sp
> > +	add	sp, r1, r2			@ set sp to top iram stack
> > +	sub	sp, sp, #16	 		@ 4 regs ptr
> > +	stmfd	sp!, {r4 - r12, lr}		@ save registers
> > +
> > +	add	r4, r1, r2
> > +	ldr	r8, [r4, #-16]			@ r8 = src_base pointer
> > +	ldr	r9, [r4, #-12]			@ r9 = iomux_base pointer
> > +	ldr	r10, [r4, #-8]			@ r10 = mmdc_base pointer
> > +	ldr	r11, [r4, #-4]			@ r11 = anatop_base pointer
> > +
> > +	/* should not access sp in ddr until resume with cache MMU on */
> > +	stmfd	sp!, {r3}			@ save old sp
> > +
> > +	ldr	r4, [r8, #SRC_GPR1_OFFSET]	@ r8 = src_base pointer
> > +	stmfd	sp!, {r4}			@ save old resume func
> > +
> > +	/* Enable weak 2P5 linear regulator */
> > +	ldr	r4, [r11, #ANATOP_REG_2P5]	@ r11 = anatop_base pointer
> > +	orr	r4, r4, #0x40000
> > +	str	r4, [r11, #ANATOP_REG_2P5]
> > +	mov	r6, #1
> > +wait:	ldr	r4, [r11, #ANATOP_REG_2P5]
> > +	and	r4, r4, r6, lsl #17			@ output ok?
> > +	cmp	r4, #0
> > +	beq	wait
> > +
> > +	/* save ddr iomux regs */
> > +	ddr_io_save
> > +
> 
> Do we really need to have all above codes running in iram?  My
> understanding is only the portion of the codes that actually puts
> DDR into self-refresh state and the codes after that need to run
> in iram.
> 
> > +	/* set ddr to low power mode */
> > +	ldr	r4, [r10, #MMDC_MAPSR_OFFSET]	@ r10 = mmdc_base pointer
> > +	bic	r4, #MMDC_MAPSR_PSD
> > +	str	r4, [r10, #MMDC_MAPSR_OFFSET]
> > +refresh:
> > +	ldr	r4, [r10, #MMDC_MAPSR_OFFSET]
> > +	and	r4, r4, #MMDC_MAPSR_PSS
> > +	cmp	r4, #0
> > +	beq	refresh
> > +
> > +	ddr_io_set_lpm
> > +
> > +	/* save resume pointer into SRC_GPR1, sp pointer into SRC_GPR2 */
> > +	ldr	r4, =imx6q_suspend
> > +	ldr	r5, =imx6q_resume
> > +	sub	r5, r5, r4			@ r5 = resmue offset
> > +	add	r5, r0, r5			@ r0 = suspend_iram_paddr, r5 = resmue phy addr
> > +	str	r5, [r8, #SRC_GPR1_OFFSET]	@ r8 = src_base pointer
> > +	sub	r5, sp, r1			@ r1 = suspend_iram_vaddr, r5 = sp offset
> > +	add	r5, r0, r5			@ r0 = suspend_iram_paddr, r5 = sp phy addr
> > +	str	r5, [r8, #SRC_GPR2_OFFSET]	@ r8 = src_base pointer
> > +
> > +	/* execute a wfi instruction to let SOC go into stop mode */
> > +	dsb
> > +	wfi
> > +
> > +	nop
> > +	nop
> > +	nop
> > +	nop
> > +
> > +	/*
> > +	 * if go here, means there is a wakeup irq pending, we should resume
> > +	 * system immediately.
> > +	 */
> 
> I'm wondering how this will happen exactly.  Is this a real case in
> practical world?  If yes, do you have the steps to reproduce it?  I'm
> actually asking if this resume path has been tested yet.
> 
> > +	ddr_io_restore
> > +
> > +	/* Disable weak 2P5 linear regulator */
> > +	ldr	r4, [r11, #ANATOP_REG_2P5]	@ r11 = anatop_base pointer
> > +	bic	r4, #0x40000
> > +	str	r4, [r11, #ANATOP_REG_2P5]
> > +
> > +	ldmfd	sp!, {r4}			@ drop old resmue func ptr
> > +	ldmfd	sp!, {r3}
> > +	ldmfd	sp!, {r4 - r12, lr}
> > +	mov	sp, r3
> > +	mov	pc, lr
> > +
> > +/*
> > + * when SOC exit stop mode, arm core restart from here, currently
> > + * are running with MMU off.
> > + */
> > +imx6q_resume:
> > +	ldr	r0, =MX6Q_SRC_BASE_ADDR
> > +	mov	r1, #0x0
> > +	str	r1, [r0, #SRC_GPR1_OFFSET] 	@ clear SRC_GPR1
> > +	ldr	sp, [r0, #SRC_GPR2_OFFSET]
> > +	str	r1, [r0, #SRC_GPR2_OFFSET]	@ clear SRC_GPR2
> > +
> > +	ldr	r9, =MX6Q_IOMUXC_BASE_ADDR
> > +	ddr_io_restore
> > +
> > +	ldr	r0, =MX6Q_MMDC0_BASE_ADDR
> > +	ldr	r1, [r0, #MMDC_MAPSR_OFFSET]
> > +	orr	r1, #MMDC_MAPSR_PSD
> > +	str	r1, [r0, #MMDC_MAPSR_OFFSET]
> > +
> > +	/* Disable weak 2P5 linear regulator */
> > +	ldr	r0, =MX6Q_ANATOP_BASE_ADDR
> > +	ldr	r1, [r0, #ANATOP_REG_2P5]
> > +	bic	r1, #0x40000
> > +	str	r1, [r0, #ANATOP_REG_2P5]
> > +
> > +	ldmfd	sp!, {r2}			@ resmue func ptr
> > +	ldmfd	sp!, {r3}
> > +	ldmfd	sp!, {r4 - r12, lr}
> > +	mov	sp, r3
> > +
> > +	/* return back */
> > +	mov	pc, r2
> > +ENDPROC(imx6q_suspend)
> > diff --git a/arch/arm/plat-mxc/include/mach/common.h b/arch/arm/plat-mxc/include/mach/common.h
> > index 83cca9b..0e1e652 100644
> > --- a/arch/arm/plat-mxc/include/mach/common.h
> > +++ b/arch/arm/plat-mxc/include/mach/common.h
> > @@ -82,6 +82,7 @@ enum mxc_cpu_pwr_mode {
> >  	WAIT_UNCLOCKED_POWER_OFF,	/* WAIT + SRPG */
> >  	STOP_POWER_ON,		/* just STOP */
> >  	STOP_POWER_OFF,		/* STOP + SRPG */
> > +	ARM_POWER_OFF,          /* STOP + SRPG + ARM power off */
> 
> I'm not sure that we need this new mode at all.  From the comment,
> the 'ARM power off' is the difference between ARM_POWER_OFF and
> STOP_POWER_OFF.  But it's not really the case.
> 
> Regards,
> Shawn
> 
> >  };
> >  
> >  extern void mx5_cpu_lp_set(enum mxc_cpu_pwr_mode mode);
> > @@ -123,6 +124,7 @@ extern void imx_set_cpu_jump(int cpu, void *jump_addr);
> >  extern void imx_src_init(void);
> >  extern void imx_src_prepare_restart(void);
> >  extern void imx_gpc_init(void);
> > +extern bool imx_gpc_wake_irq_pending(void);
> >  extern void imx_gpc_pre_suspend(void);
> >  extern void imx_gpc_post_resume(void);
> >  extern void imx51_babbage_common_init(void);
> > diff --git a/arch/arm/plat-mxc/include/mach/mx6q.h b/arch/arm/plat-mxc/include/mach/mx6q.h
> > index 254a561..eea2968 100644
> > --- a/arch/arm/plat-mxc/include/mach/mx6q.h
> > +++ b/arch/arm/plat-mxc/include/mach/mx6q.h
> > @@ -27,6 +27,12 @@
> >  #define MX6Q_CCM_SIZE			0x4000
> >  #define MX6Q_ANATOP_BASE_ADDR		0x020c8000
> >  #define MX6Q_ANATOP_SIZE		0x1000
> > +#define MX6Q_SRC_BASE_ADDR		0x020d8000
> > +#define MX6Q_SRC_SIZE			0x4000
> > +#define MX6Q_IOMUXC_BASE_ADDR		0x020e0000
> > +#define MX6Q_IOMUXC_SIZE		0x4000
> > +#define MX6Q_MMDC0_BASE_ADDR 		0x021b0000
> > +#define MX6Q_MMDC0_SIZE 		0x4000
> >  #define MX6Q_UART4_BASE_ADDR		0x021f0000
> >  #define MX6Q_UART4_SIZE			0x4000
> >  
> > -- 
> > 1.7.4.1
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
Russell King - ARM Linux Jan. 4, 2012, 9:13 a.m. UTC | #3
On Sat, Dec 31, 2011 at 02:23:44PM +0800, Jason Chen wrote:
> +		imx6q_iram_pm.suspend_iram_vaddr =
> +			__arm_ioremap(imx6q_iram_pm.suspend_iram_paddr,
> +					imx6q_iram_pm.suspend_iram_size,
> +					MT_MEMORY_NONCACHED);

Stop going beneath the API covers.
Linus Walleij Jan. 4, 2012, 11:02 a.m. UTC | #4
On Sat, Dec 31, 2011 at 7:23 AM, Jason Chen <jason.chen@linaro.org> wrote:

>  void __init imx6q_pm_init(void)
>  {
>        /*
> @@ -67,4 +153,38 @@ void __init imx6q_pm_init(void)
>        phys_l2x0_saved_regs = __pa(&l2x0_saved_regs);
>
>        suspend_set_ops(&imx6q_pm_ops);
> +
> +       /* Move suspend routine into iRAM */
(...)
> diff --git a/arch/arm/mach-imx/suspend-imx6q.S b/arch/arm/mach-imx/suspend-imx6q.S
(...)
> +.macro ddr_io_save
> +       ldr     r4, [r9, #0x5ac] /* DRAM_DQM0 */
> +       ldr     r5, [r9, #0x5b4] /* DRAM_DQM1 */
> +       ldr     r6, [r9, #0x528] /* DRAM_DQM2 */
> +       ldr     r7, [r9, #0x520] /* DRAM_DQM3 */
> +       stmfd   sp!, {r4-r7}
(etc)

This is not an elegant solution. I know other platforms have
done similar things but it's still not elegant.

What we need it real handling of the on-chip memory pool
and possibility to compile and link directly into the
on-chip memory areas.

Compare to what we have for TCM:
Documentation/arm/tcm.txt
Implemented in:
arch/arm/kernel/tcm.c
Linker trickery in:
arch/arm/kernel/vmlinux.lds.S

An approach similar to this would make it possible to:

- Manage the on-chip heap in a structured way

- Write the above assembler code in C and compile it directly
  into on-chip RAM

- Share the same code with other archs and possibly merge
  it with some of the TCM handling code top create common
  onchip RAM handling

- Optionally free up the .TEXT memory pages used by this
  code after copying it to on-chip RAM (if that RAM is
  persistent, as it seems to be)

- Make it possible to define MT_MEMORY_ONCHIP (if
  MT_MEMORY_ITCM isn't good enough) in
  arch/arm/include/asm/mach/map.h so everybody can
  use this.

It is also a bit more work and patch iteration involved.
But the end solution is very appealing IMO. Also,
multi-platform binaries will complicate the above a bit
(especially if, say, two platforms have their on-chip
RAM in the same location...) but I am sure it can be
solved with proper cleverness and step-by-step measures.

Yours,
Linus Walleij
Shawn Guo Jan. 5, 2012, 7:32 a.m. UTC | #5
On Wed, Jan 04, 2012 at 09:13:55AM +0000, Russell King - ARM Linux wrote:
> On Sat, Dec 31, 2011 at 02:23:44PM +0800, Jason Chen wrote:
> > +		imx6q_iram_pm.suspend_iram_vaddr =
> > +			__arm_ioremap(imx6q_iram_pm.suspend_iram_paddr,
> > +					imx6q_iram_pm.suspend_iram_size,
> > +					MT_MEMORY_NONCACHED);
> 
> Stop going beneath the API covers.
> 
Assume that you are suggesting __arch_ioremap() should be used instead,
correct?
jason.chen@freescale.com Jan. 5, 2012, 7:53 a.m. UTC | #6
On Thu, Jan 05, 2012 at 03:58:03PM +0800, Shawn Guo wrote:
> On Wed, Jan 04, 2012 at 12:02:51PM +0100, Linus Walleij wrote:
> > On Sat, Dec 31, 2011 at 7:23 AM, Jason Chen <jason.chen@linaro.org> wrote:
> > 
> > > ?void __init imx6q_pm_init(void)
> > > ?{
> > > ? ? ? ?/*
> > > @@ -67,4 +153,38 @@ void __init imx6q_pm_init(void)
> > > ? ? ? ?phys_l2x0_saved_regs = __pa(&l2x0_saved_regs);
> > >
> > > ? ? ? ?suspend_set_ops(&imx6q_pm_ops);
> > > +
> > > + ? ? ? /* Move suspend routine into iRAM */
> > (...)
> > > diff --git a/arch/arm/mach-imx/suspend-imx6q.S b/arch/arm/mach-imx/suspend-imx6q.S
> > (...)
> > > +.macro ddr_io_save
> > > + ? ? ? ldr ? ? r4, [r9, #0x5ac] /* DRAM_DQM0 */
> > > + ? ? ? ldr ? ? r5, [r9, #0x5b4] /* DRAM_DQM1 */
> > > + ? ? ? ldr ? ? r6, [r9, #0x528] /* DRAM_DQM2 */
> > > + ? ? ? ldr ? ? r7, [r9, #0x520] /* DRAM_DQM3 */
> > > + ? ? ? stmfd ? sp!, {r4-r7}
> > (etc)
> > 
> > This is not an elegant solution. I know other platforms have
> > done similar things but it's still not elegant.
> > 
> > What we need it real handling of the on-chip memory pool
> > and possibility to compile and link directly into the
> > on-chip memory areas.
> > 
> > Compare to what we have for TCM:
> > Documentation/arm/tcm.txt
> > Implemented in:
> > arch/arm/kernel/tcm.c
> > Linker trickery in:
> > arch/arm/kernel/vmlinux.lds.S
> > 
> > An approach similar to this would make it possible to:
> > 
> > - Manage the on-chip heap in a structured way
> > 
> > - Write the above assembler code in C and compile it directly
> >   into on-chip RAM
> > 
> > - Share the same code with other archs and possibly merge
> >   it with some of the TCM handling code top create common
> >   onchip RAM handling
> > 
> > - Optionally free up the .TEXT memory pages used by this
> >   code after copying it to on-chip RAM (if that RAM is
> >   persistent, as it seems to be)
> > 
> > - Make it possible to define MT_MEMORY_ONCHIP (if
> >   MT_MEMORY_ITCM isn't good enough) in
> >   arch/arm/include/asm/mach/map.h so everybody can
> >   use this.
> > 
> > It is also a bit more work and patch iteration involved.
> > But the end solution is very appealing IMO. Also,
> > multi-platform binaries will complicate the above a bit
> > (especially if, say, two platforms have their on-chip
> > RAM in the same location...) but I am sure it can be
> > solved with proper cleverness and step-by-step measures.
> > 
> It looks really interesting and appealing.  We absolutely should
> take a deep look into the suggestion.  Thanks, Linus.
> 
Agree, thanks, Linus.
> -- 
> Regards,
> Shawn
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
Shawn Guo Jan. 5, 2012, 7:58 a.m. UTC | #7
On Wed, Jan 04, 2012 at 12:02:51PM +0100, Linus Walleij wrote:
> On Sat, Dec 31, 2011 at 7:23 AM, Jason Chen <jason.chen@linaro.org> wrote:
> 
> >  void __init imx6q_pm_init(void)
> >  {
> >        /*
> > @@ -67,4 +153,38 @@ void __init imx6q_pm_init(void)
> >        phys_l2x0_saved_regs = __pa(&l2x0_saved_regs);
> >
> >        suspend_set_ops(&imx6q_pm_ops);
> > +
> > +       /* Move suspend routine into iRAM */
> (...)
> > diff --git a/arch/arm/mach-imx/suspend-imx6q.S b/arch/arm/mach-imx/suspend-imx6q.S
> (...)
> > +.macro ddr_io_save
> > +       ldr     r4, [r9, #0x5ac] /* DRAM_DQM0 */
> > +       ldr     r5, [r9, #0x5b4] /* DRAM_DQM1 */
> > +       ldr     r6, [r9, #0x528] /* DRAM_DQM2 */
> > +       ldr     r7, [r9, #0x520] /* DRAM_DQM3 */
> > +       stmfd   sp!, {r4-r7}
> (etc)
> 
> This is not an elegant solution. I know other platforms have
> done similar things but it's still not elegant.
> 
> What we need it real handling of the on-chip memory pool
> and possibility to compile and link directly into the
> on-chip memory areas.
> 
> Compare to what we have for TCM:
> Documentation/arm/tcm.txt
> Implemented in:
> arch/arm/kernel/tcm.c
> Linker trickery in:
> arch/arm/kernel/vmlinux.lds.S
> 
> An approach similar to this would make it possible to:
> 
> - Manage the on-chip heap in a structured way
> 
> - Write the above assembler code in C and compile it directly
>   into on-chip RAM
> 
> - Share the same code with other archs and possibly merge
>   it with some of the TCM handling code top create common
>   onchip RAM handling
> 
> - Optionally free up the .TEXT memory pages used by this
>   code after copying it to on-chip RAM (if that RAM is
>   persistent, as it seems to be)
> 
> - Make it possible to define MT_MEMORY_ONCHIP (if
>   MT_MEMORY_ITCM isn't good enough) in
>   arch/arm/include/asm/mach/map.h so everybody can
>   use this.
> 
> It is also a bit more work and patch iteration involved.
> But the end solution is very appealing IMO. Also,
> multi-platform binaries will complicate the above a bit
> (especially if, say, two platforms have their on-chip
> RAM in the same location...) but I am sure it can be
> solved with proper cleverness and step-by-step measures.
> 
It looks really interesting and appealing.  We absolutely should
take a deep look into the suggestion.  Thanks, Linus.
diff mbox

Patch

diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
index 88a3966..0b07eef 100644
--- a/arch/arm/mach-imx/Makefile
+++ b/arch/arm/mach-imx/Makefile
@@ -72,7 +72,7 @@  AFLAGS_head-v7.o :=-Wa,-march=armv7-a
 obj-$(CONFIG_SMP) += platsmp.o
 obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
 obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o
-obj-$(CONFIG_SOC_IMX6Q) += clock-imx6q.o mach-imx6q.o pm-imx6q.o
+obj-$(CONFIG_SOC_IMX6Q) += clock-imx6q.o mach-imx6q.o pm-imx6q.o suspend-imx6q.o
 
 # i.MX5 based machines
 obj-$(CONFIG_MACH_MX51_BABBAGE) += mach-mx51_babbage.o
diff --git a/arch/arm/mach-imx/clock-imx6q.c b/arch/arm/mach-imx/clock-imx6q.c
index 56a7a3f..b7fecad 100644
--- a/arch/arm/mach-imx/clock-imx6q.c
+++ b/arch/arm/mach-imx/clock-imx6q.c
@@ -115,6 +115,8 @@ 
 #define CG14		28
 #define CG15		30
 
+#define BM_CCR_RBC_EN			(0x1 << 27)
+
 #define BM_CCSR_PLL1_SW_SEL		(0x1 << 2)
 #define BM_CCSR_STEP_SEL		(0x1 << 8)
 
@@ -1916,33 +1918,44 @@  static struct clk_lookup lookups[] = {
 
 int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode)
 {
-	u32 val = readl_relaxed(CLPCR);
+	u32 clpcr = readl_relaxed(CLPCR);
+	u32 ccr = readl_relaxed(CCR);
 
-	val &= ~BM_CLPCR_LPM;
+	clpcr &= ~(BM_CLPCR_LPM | BM_CLPCR_VSTBY | BM_CLPCR_SBYOS
+			| BM_CLPCR_STBY_COUNT | BM_CLPCR_WB_PER_AT_LPM);
+	ccr &= ~(BM_CCR_RBC_EN);
 	switch (mode) {
 	case WAIT_CLOCKED:
 		break;
 	case WAIT_UNCLOCKED:
-		val |= 0x1 << BP_CLPCR_LPM;
+		clpcr |= 0x1 << BP_CLPCR_LPM;
 		break;
 	case STOP_POWER_ON:
-		val |= 0x2 << BP_CLPCR_LPM;
+		clpcr |= 0x2 << BP_CLPCR_LPM;
 		break;
 	case WAIT_UNCLOCKED_POWER_OFF:
-		val |= 0x1 << BP_CLPCR_LPM;
-		val &= ~BM_CLPCR_VSTBY;
-		val &= ~BM_CLPCR_SBYOS;
+		clpcr |= 0x1 << BP_CLPCR_LPM;
 		break;
 	case STOP_POWER_OFF:
-		val |= 0x2 << BP_CLPCR_LPM;
-		val |= 0x3 << BP_CLPCR_STBY_COUNT;
-		val |= BM_CLPCR_VSTBY;
-		val |= BM_CLPCR_SBYOS;
+		clpcr |= 0x2 << BP_CLPCR_LPM;
+		clpcr |= 0x3 << BP_CLPCR_STBY_COUNT;
+		clpcr |= BM_CLPCR_VSTBY;
+		clpcr |= BM_CLPCR_SBYOS;
+		break;
+	case ARM_POWER_OFF:
+		clpcr |= 0x2 << BP_CLPCR_LPM;
+		clpcr |= 0x3 << BP_CLPCR_STBY_COUNT;
+		clpcr |= BM_CLPCR_VSTBY;
+		clpcr |= BM_CLPCR_SBYOS;
+		clpcr |= BM_CLPCR_WB_PER_AT_LPM;
+		/* assert anatop_reg_bypass signal */
+		ccr |= BM_CCR_RBC_EN;
 		break;
 	default:
 		return -EINVAL;
 	}
-	writel_relaxed(val, CLPCR);
+	writel_relaxed(clpcr, CLPCR);
+	writel_relaxed(ccr, CCR);
 
 	return 0;
 }
diff --git a/arch/arm/mach-imx/gpc.c b/arch/arm/mach-imx/gpc.c
index e1537f9..8fc255b 100644
--- a/arch/arm/mach-imx/gpc.c
+++ b/arch/arm/mach-imx/gpc.c
@@ -17,15 +17,36 @@ 
 #include <linux/of_irq.h>
 #include <asm/hardware/gic.h>
 
+#define GPC_CNTR		0x000
 #define GPC_IMR1		0x008
+#define GPC_ISR1		0x018
+#define GPC_ISR2		0x01c
+#define GPC_ISR3		0x020
+#define GPC_ISR4		0x024
 #define GPC_PGC_CPU_PDN		0x2a0
 
 #define IMR_NUM			4
+#define ISR_NUM			4
 
 static void __iomem *gpc_base;
 static u32 gpc_wake_irqs[IMR_NUM];
 static u32 gpc_saved_imrs[IMR_NUM];
 
+bool imx_gpc_wake_irq_pending(void)
+{
+	void __iomem *reg_isr1 = gpc_base + GPC_ISR1;
+	int i;
+	u32 val;
+
+	for (i = 0; i < ISR_NUM; i++) {
+		val = readl_relaxed(reg_isr1 + i * 4);
+		if (val & gpc_wake_irqs[i])
+			return true;
+	}
+
+	return false;
+}
+
 void imx_gpc_pre_suspend(void)
 {
 	void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
diff --git a/arch/arm/mach-imx/pm-imx6q.c b/arch/arm/mach-imx/pm-imx6q.c
index f20f191..6e45245 100644
--- a/arch/arm/mach-imx/pm-imx6q.c
+++ b/arch/arm/mach-imx/pm-imx6q.c
@@ -13,31 +13,73 @@ 
 #include <linux/init.h>
 #include <linux/io.h>
 #include <linux/of.h>
+#include <linux/of_address.h>
 #include <linux/suspend.h>
 #include <asm/cacheflush.h>
 #include <asm/proc-fns.h>
 #include <asm/suspend.h>
+#include <asm/mach/map.h>
 #include <asm/hardware/cache-l2x0.h>
+#include <mach/iram.h>
 #include <mach/common.h>
 #include <mach/hardware.h>
 
+struct imx_iram_pm {
+	void *iram_cpaddr;
+	unsigned long suspend_iram_paddr;
+	unsigned long suspend_iram_size;
+	void *suspend_iram_vaddr;
+	void *reg_ptr[4];
+} imx6q_iram_pm;
+
 extern unsigned long phys_l2x0_saved_regs;
+extern void imx6q_suspend(void);
+static void (*suspend_in_iram)(unsigned long iram_paddr,
+				unsigned long iram_vaddr,
+				unsigned long iram_size);
 
 static int imx6q_suspend_finish(unsigned long val)
 {
-	cpu_do_idle();
+	if ((val == PM_SUSPEND_MEM) && suspend_in_iram) {
+		suspend_in_iram((unsigned long)imx6q_iram_pm.suspend_iram_paddr,
+				(unsigned long)imx6q_iram_pm.suspend_iram_vaddr,
+				(unsigned long)imx6q_iram_pm.suspend_iram_size);
+	} else
+		cpu_do_idle();
+
 	return 0;
 }
 
+static void imx6q_prepare_suspend_iram(void)
+{
+	unsigned long *iram_stack = imx6q_iram_pm.suspend_iram_vaddr
+					+ imx6q_iram_pm.suspend_iram_size;
+
+	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[3];
+	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[2];
+	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[1];
+	*(--iram_stack) = (unsigned long)imx6q_iram_pm.reg_ptr[0];
+}
+
 static int imx6q_pm_enter(suspend_state_t state)
 {
 	switch (state) {
+	case PM_SUSPEND_STANDBY:
 	case PM_SUSPEND_MEM:
-		imx6q_set_lpm(STOP_POWER_OFF);
+		if (imx_gpc_wake_irq_pending())
+			return 0;
+
+		if (state == PM_SUSPEND_STANDBY)
+			imx6q_set_lpm(STOP_POWER_OFF);
+		else
+			imx6q_set_lpm(ARM_POWER_OFF);
+
 		imx_gpc_pre_suspend();
 		imx_set_cpu_jump(0, v7_cpu_resume);
+		if (state == PM_SUSPEND_MEM)
+			imx6q_prepare_suspend_iram();
 		/* Zzz ... */
-		cpu_suspend(0, imx6q_suspend_finish);
+		cpu_suspend(state, imx6q_suspend_finish);
 		imx_smp_prepare();
 		imx_gpc_post_resume();
 		break;
@@ -48,11 +90,55 @@  static int imx6q_pm_enter(suspend_state_t state)
 	return 0;
 }
 
+static int imx6q_pm_valid(suspend_state_t state)
+{
+	return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX);
+}
+
 static const struct platform_suspend_ops imx6q_pm_ops = {
 	.enter = imx6q_pm_enter,
-	.valid = suspend_valid_only_mem,
+	.valid = imx6q_pm_valid,
 };
 
+static int __init imx6q_pm_iram_of_init(void)
+{
+	struct device_node *np;
+
+	/*
+	 * these register may already ioremaped, need figure out
+	 * one way to save vmalloc space.
+	 */
+	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-src");
+	imx6q_iram_pm.reg_ptr[0] = of_iomap(np, 0);
+	if (!imx6q_iram_pm.reg_ptr[0])
+		goto err0;
+
+	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc");
+	imx6q_iram_pm.reg_ptr[1] = of_iomap(np, 0);
+	if (!imx6q_iram_pm.reg_ptr[1])
+		goto err1;
+
+	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc");
+	imx6q_iram_pm.reg_ptr[2] = of_iomap(np, 0);
+	if (!imx6q_iram_pm.reg_ptr[2])
+		goto err2;
+
+	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop");
+	imx6q_iram_pm.reg_ptr[3] = of_iomap(np, 0);
+	if (!imx6q_iram_pm.reg_ptr[3])
+		goto err3;
+
+	return 0;
+err3:
+	iounmap(imx6q_iram_pm.reg_ptr[2]);
+err2:
+	iounmap(imx6q_iram_pm.reg_ptr[1]);
+err1:
+	iounmap(imx6q_iram_pm.reg_ptr[0]);
+err0:
+	return -EINVAL;
+}
+
 void __init imx6q_pm_init(void)
 {
 	/*
@@ -67,4 +153,38 @@  void __init imx6q_pm_init(void)
 	phys_l2x0_saved_regs = __pa(&l2x0_saved_regs);
 
 	suspend_set_ops(&imx6q_pm_ops);
+
+	/* Move suspend routine into iRAM */
+	imx6q_iram_pm.suspend_iram_size = SZ_4K;
+	imx6q_iram_pm.iram_cpaddr = iram_alloc(imx6q_iram_pm.suspend_iram_size,
+			&imx6q_iram_pm.suspend_iram_paddr);
+	if (imx6q_iram_pm.iram_cpaddr) {
+		if (imx6q_pm_iram_of_init() < 0) {
+			iram_free(imx6q_iram_pm.suspend_iram_paddr,
+					imx6q_iram_pm.suspend_iram_size);
+			return;
+		}
+		/*
+		 * Need to remap the area here since we want the memory region
+		 * to be noncached & executable.
+		 */
+		imx6q_iram_pm.suspend_iram_vaddr =
+			__arm_ioremap(imx6q_iram_pm.suspend_iram_paddr,
+					imx6q_iram_pm.suspend_iram_size,
+					MT_MEMORY_NONCACHED);
+		pr_info("cpaddr = %p suspend_iram_base=%p\n",
+				imx6q_iram_pm.iram_cpaddr,
+				imx6q_iram_pm.suspend_iram_vaddr);
+
+		/*
+		 * Need to run the suspend code from IRAM as the DDR needs
+		 * to be put into low power mode manually.
+		 */
+		memcpy(imx6q_iram_pm.iram_cpaddr, imx6q_suspend,
+					imx6q_iram_pm.suspend_iram_size);
+
+		suspend_in_iram = (void *)imx6q_iram_pm.suspend_iram_vaddr;
+
+	} else
+		suspend_in_iram = NULL;
 }
diff --git a/arch/arm/mach-imx/suspend-imx6q.S b/arch/arm/mach-imx/suspend-imx6q.S
new file mode 100644
index 0000000..f803e2b
--- /dev/null
+++ b/arch/arm/mach-imx/suspend-imx6q.S
@@ -0,0 +1,308 @@ 
+/*
+ * Copyright 2011 Freescale Semiconductor, Inc.
+ * Copyright 2011 Linaro Ltd.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/linkage.h>
+#include <mach/hardware.h>
+#include <asm/hardware/cache-l2x0.h>
+
+#define SRC_GPR1_OFFSET		0x020
+#define SRC_GPR2_OFFSET		0x024
+#define MMDC_MAPSR_OFFSET 	0x404
+#define MMDC_MAPSR_PSS 		(1 << 4)
+#define MMDC_MAPSR_PSD 		(1 << 0)
+#define ANATOP_REG_2P5		0x130
+
+.macro	ddr_io_save
+	ldr	r4, [r9, #0x5ac] /* DRAM_DQM0 */
+	ldr	r5, [r9, #0x5b4] /* DRAM_DQM1 */
+	ldr	r6, [r9, #0x528] /* DRAM_DQM2 */
+	ldr	r7, [r9, #0x520] /* DRAM_DQM3 */
+	stmfd	sp!, {r4-r7}
+
+	ldr	r4, [r9, #0x514] /* DRAM_DQM4 */
+	ldr	r5, [r9, #0x510] /* DRAM_DQM5 */
+	ldr	r6, [r9, #0x5bc] /* DRAM_DQM6 */
+	ldr	r7, [r9, #0x5c4] /* DRAM_DQM7 */
+	stmfd	sp!, {r4-r7}
+
+	ldr	r4, [r9, #0x56c] /* DRAM_CAS */
+	ldr	r5, [r9, #0x578] /* DRAM_RAS */
+	ldr	r6, [r9, #0x588] /* DRAM_SDCLK_0 */
+	ldr	r7, [r9, #0x594] /* DRAM_SDCLK_1 */
+	stmfd	sp!, {r4-r7}
+
+	ldr	r5, [r9, #0x750] /* DDRMODE_CTL */
+	ldr	r6, [r9, #0x774] /* DDRMODE */
+	stmfd	sp!, {r5-r6}
+
+	ldr	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
+	ldr	r5, [r9, #0x5b0] /* DRAM_SDQS1 */
+	ldr	r6, [r9, #0x524] /* DRAM_SDQS2 */
+	ldr	r7, [r9, #0x51c] /* DRAM_SDQS3 */
+	stmfd	sp!, {r4-r7}
+
+	ldr	r4, [r9, #0x518] /* DRAM_SDQS4 */
+	ldr	r5, [r9, #0x50c] /* DRAM_SDQS5 */
+	ldr	r6, [r9, #0x5b8] /* DRAM_SDQS6 */
+	ldr	r7, [r9, #0x5c0] /* DRAM_SDQS7 */
+	stmfd	sp!, {r4-r7}
+
+	ldr	r4, [r9, #0x784] /* GPR_B0DS */
+	ldr	r5, [r9, #0x788] /* GPR_B1DS */
+	ldr	r6, [r9, #0x794] /* GPR_B2DS */
+	ldr	r7, [r9, #0x79c] /* GPR_B3DS */
+	stmfd	sp!, {r4-r7}
+
+	ldr	r4, [r9, #0x7a0] /* GPR_B4DS */
+	ldr	r5, [r9, #0x7a4] /* GPR_B5DS */
+	ldr	r6, [r9, #0x7a8] /* GPR_B6DS */
+	ldr	r7, [r9, #0x748] /* GPR_B7DS */
+	stmfd	sp!, {r4-r7}
+
+	ldr	r5, [r9, #0x74c] /* GPR_ADDS*/
+	ldr	r6, [r9, #0x59c] /* DRAM_SODT0*/
+	ldr	r7, [r9, #0x5a0] /* DRAM_SODT1*/
+	stmfd	sp!, {r5-r7}
+.endm
+
+.macro	ddr_io_restore
+	ldmfd	sp!, {r5-r7}
+	str	r5, [r9, #0x74c] /* GPR_ADDS*/
+	str	r6, [r9, #0x59c] /* DRAM_SODT0*/
+	str	r7, [r9, #0x5a0] /* DRAM_SODT1*/
+
+	ldmfd	sp!, {r4-r7}
+	str	r4, [r9, #0x7a0] /* GPR_B4DS */
+	str	r5, [r9, #0x7a4] /* GPR_B5DS */
+	str	r6, [r9, #0x7a8] /* GPR_B6DS */
+	str	r7, [r9, #0x748] /* GPR_B7DS */
+
+	ldmfd	sp!, {r4-r7}
+	str	r4, [r9, #0x784] /* GPR_B0DS */
+	str	r5, [r9, #0x788] /* GPR_B1DS */
+	str	r6, [r9, #0x794] /* GPR_B2DS */
+	str	r7, [r9, #0x79c] /* GPR_B3DS */
+
+	ldmfd	sp!, {r4-r7}
+	str	r4, [r9, #0x518] /* DRAM_SDQS4 */
+	str	r5, [r9, #0x50c] /* DRAM_SDQS5 */
+	str	r6, [r9, #0x5b8] /* DRAM_SDQS6 */
+	str	r7, [r9, #0x5c0] /* DRAM_SDQS7 */
+
+	ldmfd	sp!, {r4-r7}
+	str	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
+	str	r5, [r9, #0x5b0] /* DRAM_SDQS1 */
+	str	r6, [r9, #0x524] /* DRAM_SDQS2 */
+	str	r7, [r9, #0x51c] /* DRAM_SDQS3 */
+
+	ldmfd	sp!, {r5-r6}
+	str	r5, [r9, #0x750] /* DDRMODE_CTL */
+	str	r6, [r9, #0x774] /* DDRMODE */
+
+	ldmfd	sp!, {r4-r7}
+	str	r4, [r9, #0x56c] /* DRAM_CAS */
+	str	r5, [r9, #0x578] /* DRAM_RAS */
+	str	r6, [r9, #0x588] /* DRAM_SDCLK_0 */
+	str	r7, [r9, #0x594] /* DRAM_SDCLK_1 */
+
+	ldmfd	sp!, {r4-r7}
+	str	r4, [r9, #0x514] /* DRAM_DQM4 */
+	str	r5, [r9, #0x510] /* DRAM_DQM5 */
+	str	r6, [r9, #0x5bc] /* DRAM_DQM6 */
+	str	r7, [r9, #0x5c4] /* DRAM_DQM7 */
+
+	ldmfd	sp!, {r4-r7}
+	str	r4, [r9, #0x5ac] /* DRAM_DQM0 */
+	str	r5, [r9, #0x5b4] /* DRAM_DQM1 */
+	str	r6, [r9, #0x528] /* DRAM_DQM2 */
+	str	r7, [r9, #0x520] /* DRAM_DQM3 */
+.endm
+
+.macro	ddr_io_set_lpm
+	mov	r4, #0
+	str	r4, [r9, #0x5ac] /* DRAM_DQM0 */
+	str	r4, [r9, #0x5b4] /* DRAM_DQM1 */
+	str	r4, [r9, #0x528] /* DRAM_DQM2 */
+	str	r4, [r9, #0x520] /* DRAM_DQM3 */
+
+	str	r4, [r9, #0x514] /* DRAM_DQM4 */
+	str	r4, [r9, #0x510] /* DRAM_DQM5 */
+	str	r4, [r9, #0x5bc] /* DRAM_DQM6 */
+	str	r4, [r9, #0x5c4] /* DRAM_DQM7 */
+
+	str	r4, [r9, #0x56c] /* DRAM_CAS */
+	str	r4, [r9, #0x578] /* DRAM_RAS */
+	str	r4, [r9, #0x588] /* DRAM_SDCLK_0 */
+	str	r4, [r9, #0x594] /* DRAM_SDCLK_1 */
+
+	str	r4, [r9, #0x750] /* DDRMODE_CTL */
+	str	r4, [r9, #0x774] /* DDRMODE */
+
+	str	r4, [r9, #0x5a8] /* DRAM_SDQS0 */
+	str	r4, [r9, #0x5b0] /* DRAM_SDQS1 */
+	str	r4, [r9, #0x524] /* DRAM_SDQS2 */
+	str	r4, [r9, #0x51c] /* DRAM_SDQS3 */
+
+	str	r4, [r9, #0x518] /* DRAM_SDQS4 */
+	str	r4, [r9, #0x50c] /* DRAM_SDQS5 */
+	str	r4, [r9, #0x5b8] /* DRAM_SDQS6 */
+	str	r4, [r9, #0x5c0] /* DRAM_SDQS7 */
+
+	str	r4, [r9, #0x784] /* GPR_B0DS */
+	str	r4, [r9, #0x788] /* GPR_B1DS */
+	str	r4, [r9, #0x794] /* GPR_B2DS */
+	str	r4, [r9, #0x79c] /* GPR_B3DS */
+
+	str	r4, [r9, #0x7a0] /* GPR_B4DS */
+	str	r4, [r9, #0x7a4] /* GPR_B5DS */
+	str	r4, [r9, #0x7a8] /* GPR_B6DS */
+	str	r4, [r9, #0x748] /* GPR_B7DS */
+
+	str	r4, [r9, #0x74c] /* GPR_ADDS*/
+	str	r4, [r9, #0x59c] /* DRAM_SODT0*/
+	str	r4, [r9, #0x5a0] /* DRAM_SODT1*/
+.endm
+
+/*
+ * imx6q_suspend:
+ *
+ * Suspend the processor (eg, wait for interrupt).
+ * Set the DDR into Self Refresh
+ * IRQs are already disabled.
+ *
+ * Registers usage in the imx6q_suspend:
+ *
+ * r0: suspend_iram_paddr
+ * r1: suspend_iram_vaddr
+ * r2: suspend_iram_size
+ *
+ * r8: src_base pointer
+ * r9: iomux_base pointer
+ * r10: mmdc_base pointer
+ * r11: anatop_base pointer
+ * sp: iram stack
+ *
+ * Corrupted registers: r0-r3
+ */
+
+ENTRY(imx6q_suspend)
+	mov	r3, sp				@ save sp
+	add	sp, r1, r2			@ set sp to top iram stack
+	sub	sp, sp, #16	 		@ 4 regs ptr
+	stmfd	sp!, {r4 - r12, lr}		@ save registers
+
+	add	r4, r1, r2
+	ldr	r8, [r4, #-16]			@ r8 = src_base pointer
+	ldr	r9, [r4, #-12]			@ r9 = iomux_base pointer
+	ldr	r10, [r4, #-8]			@ r10 = mmdc_base pointer
+	ldr	r11, [r4, #-4]			@ r11 = anatop_base pointer
+
+	/* should not access sp in ddr until resume with cache MMU on */
+	stmfd	sp!, {r3}			@ save old sp
+
+	ldr	r4, [r8, #SRC_GPR1_OFFSET]	@ r8 = src_base pointer
+	stmfd	sp!, {r4}			@ save old resume func
+
+	/* Enable weak 2P5 linear regulator */
+	ldr	r4, [r11, #ANATOP_REG_2P5]	@ r11 = anatop_base pointer
+	orr	r4, r4, #0x40000
+	str	r4, [r11, #ANATOP_REG_2P5]
+	mov	r6, #1
+wait:	ldr	r4, [r11, #ANATOP_REG_2P5]
+	and	r4, r4, r6, lsl #17			@ output ok?
+	cmp	r4, #0
+	beq	wait
+
+	/* save ddr iomux regs */
+	ddr_io_save
+
+	/* set ddr to low power mode */
+	ldr	r4, [r10, #MMDC_MAPSR_OFFSET]	@ r10 = mmdc_base pointer
+	bic	r4, #MMDC_MAPSR_PSD
+	str	r4, [r10, #MMDC_MAPSR_OFFSET]
+refresh:
+	ldr	r4, [r10, #MMDC_MAPSR_OFFSET]
+	and	r4, r4, #MMDC_MAPSR_PSS
+	cmp	r4, #0
+	beq	refresh
+
+	ddr_io_set_lpm
+
+	/* save resume pointer into SRC_GPR1, sp pointer into SRC_GPR2 */
+	ldr	r4, =imx6q_suspend
+	ldr	r5, =imx6q_resume
+	sub	r5, r5, r4			@ r5 = resmue offset
+	add	r5, r0, r5			@ r0 = suspend_iram_paddr, r5 = resmue phy addr
+	str	r5, [r8, #SRC_GPR1_OFFSET]	@ r8 = src_base pointer
+	sub	r5, sp, r1			@ r1 = suspend_iram_vaddr, r5 = sp offset
+	add	r5, r0, r5			@ r0 = suspend_iram_paddr, r5 = sp phy addr
+	str	r5, [r8, #SRC_GPR2_OFFSET]	@ r8 = src_base pointer
+
+	/* execute a wfi instruction to let SOC go into stop mode */
+	dsb
+	wfi
+
+	nop
+	nop
+	nop
+	nop
+
+	/*
+	 * if go here, means there is a wakeup irq pending, we should resume
+	 * system immediately.
+	 */
+	ddr_io_restore
+
+	/* Disable weak 2P5 linear regulator */
+	ldr	r4, [r11, #ANATOP_REG_2P5]	@ r11 = anatop_base pointer
+	bic	r4, #0x40000
+	str	r4, [r11, #ANATOP_REG_2P5]
+
+	ldmfd	sp!, {r4}			@ drop old resmue func ptr
+	ldmfd	sp!, {r3}
+	ldmfd	sp!, {r4 - r12, lr}
+	mov	sp, r3
+	mov	pc, lr
+
+/*
+ * when SOC exit stop mode, arm core restart from here, currently
+ * are running with MMU off.
+ */
+imx6q_resume:
+	ldr	r0, =MX6Q_SRC_BASE_ADDR
+	mov	r1, #0x0
+	str	r1, [r0, #SRC_GPR1_OFFSET] 	@ clear SRC_GPR1
+	ldr	sp, [r0, #SRC_GPR2_OFFSET]
+	str	r1, [r0, #SRC_GPR2_OFFSET]	@ clear SRC_GPR2
+
+	ldr	r9, =MX6Q_IOMUXC_BASE_ADDR
+	ddr_io_restore
+
+	ldr	r0, =MX6Q_MMDC0_BASE_ADDR
+	ldr	r1, [r0, #MMDC_MAPSR_OFFSET]
+	orr	r1, #MMDC_MAPSR_PSD
+	str	r1, [r0, #MMDC_MAPSR_OFFSET]
+
+	/* Disable weak 2P5 linear regulator */
+	ldr	r0, =MX6Q_ANATOP_BASE_ADDR
+	ldr	r1, [r0, #ANATOP_REG_2P5]
+	bic	r1, #0x40000
+	str	r1, [r0, #ANATOP_REG_2P5]
+
+	ldmfd	sp!, {r2}			@ resmue func ptr
+	ldmfd	sp!, {r3}
+	ldmfd	sp!, {r4 - r12, lr}
+	mov	sp, r3
+
+	/* return back */
+	mov	pc, r2
+ENDPROC(imx6q_suspend)
diff --git a/arch/arm/plat-mxc/include/mach/common.h b/arch/arm/plat-mxc/include/mach/common.h
index 83cca9b..0e1e652 100644
--- a/arch/arm/plat-mxc/include/mach/common.h
+++ b/arch/arm/plat-mxc/include/mach/common.h
@@ -82,6 +82,7 @@  enum mxc_cpu_pwr_mode {
 	WAIT_UNCLOCKED_POWER_OFF,	/* WAIT + SRPG */
 	STOP_POWER_ON,		/* just STOP */
 	STOP_POWER_OFF,		/* STOP + SRPG */
+	ARM_POWER_OFF,          /* STOP + SRPG + ARM power off */
 };
 
 extern void mx5_cpu_lp_set(enum mxc_cpu_pwr_mode mode);
@@ -123,6 +124,7 @@  extern void imx_set_cpu_jump(int cpu, void *jump_addr);
 extern void imx_src_init(void);
 extern void imx_src_prepare_restart(void);
 extern void imx_gpc_init(void);
+extern bool imx_gpc_wake_irq_pending(void);
 extern void imx_gpc_pre_suspend(void);
 extern void imx_gpc_post_resume(void);
 extern void imx51_babbage_common_init(void);
diff --git a/arch/arm/plat-mxc/include/mach/mx6q.h b/arch/arm/plat-mxc/include/mach/mx6q.h
index 254a561..eea2968 100644
--- a/arch/arm/plat-mxc/include/mach/mx6q.h
+++ b/arch/arm/plat-mxc/include/mach/mx6q.h
@@ -27,6 +27,12 @@ 
 #define MX6Q_CCM_SIZE			0x4000
 #define MX6Q_ANATOP_BASE_ADDR		0x020c8000
 #define MX6Q_ANATOP_SIZE		0x1000
+#define MX6Q_SRC_BASE_ADDR		0x020d8000
+#define MX6Q_SRC_SIZE			0x4000
+#define MX6Q_IOMUXC_BASE_ADDR		0x020e0000
+#define MX6Q_IOMUXC_SIZE		0x4000
+#define MX6Q_MMDC0_BASE_ADDR 		0x021b0000
+#define MX6Q_MMDC0_SIZE 		0x4000
 #define MX6Q_UART4_BASE_ADDR		0x021f0000
 #define MX6Q_UART4_SIZE			0x4000