Subject: [PATCH 08/15] ARM: OMAP5: PM: Add CPU power off mode support

From: [email-masked] (Nishanth Menon)
Date: Fri, 1 Mar 2013 15:36:01 -0600

On 17:40-20130301, Santosh Shilimkar wrote:
> Add power management code to handle the CPU off mode. Separate
> suspend finisher is used for OMAP5(Cortex-A15) because it doesn't
> use SCU power status register and external PL310 L2 cache which makes
> code flow bit different.
> 
> Signed-off-by: Santosh Shilimkar <[email-masked]>
> ---
>  arch/arm/mach-omap2/omap-mpuss-lowpower.c |   31 +++++++----
>  arch/arm/mach-omap2/omap-secure.h         |    1 +
>  arch/arm/mach-omap2/omap4-sar-layout.h    |    2 +
>  arch/arm/mach-omap2/sleep_omap4plus.S     |   80 +++++++++++++++++++++++++++++
>  4 files changed, 104 insertions(+), 10 deletions(-)
> 
> diff --git a/arch/arm/mach-omap2/omap-mpuss-lowpower.c b/arch/arm/mach-omap2/omap-mpuss-lowpower.c
> index 9fda96b..275f9a4 100644
> --- a/arch/arm/mach-omap2/omap-mpuss-lowpower.c
> +++ b/arch/arm/mach-omap2/omap-mpuss-lowpower.c
> @@ -76,10 +76,12 @@ struct cpu_pm_ops {
>  	int (*finish_suspend)(unsigned long cpu_state);
>  	void (*resume)(void);
>  	void (*scu_prepare)(unsigned int cpu_id, unsigned int cpu_state);
> +	void (*hotplug_restart)(void);
>  };
>  
>  extern int omap4_finish_suspend(unsigned long cpu_state);
>  extern void omap4_cpu_resume(void);
> +extern int omap5_finish_suspend(unsigned long cpu_state);
>  
>  static DEFINE_PER_CPU(struct omap4_cpu_pm_info, omap4_pm_info);
>  static struct powerdomain *mpuss_pd;
> @@ -102,6 +104,7 @@ struct cpu_pm_ops omap_pm_ops = {
>  	.finish_suspend		= default_finish_suspend,
>  	.resume			= dummy_cpu_resume,
>  	.scu_prepare		= dummy_scu_prepare,
> +	.hotplug_restart	= dummy_cpu_resume,
>  };
>  
>  /*
> @@ -334,7 +337,6 @@ int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state)
>  int __cpuinit omap4_hotplug_cpu(unsigned int cpu, unsigned int power_state)
>  {
>  	unsigned int cpu_state = 0;
> -	struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu);
>  
>  	if (omap_rev() == OMAP4430_REV_ES1_0)
>  		return -ENXIO;
> @@ -344,7 +346,7 @@ int __cpuinit omap4_hotplug_cpu(unsigned int cpu, unsigned int power_state)
>  
>  	clear_cpu_prev_pwrst(cpu);
>  	set_cpu_next_pwrst(cpu, power_state);
> -	set_cpu_wakeup_addr(cpu, virt_to_phys(pm_info->secondary_startup));
> +	set_cpu_wakeup_addr(cpu, virt_to_phys(omap_pm_ops.hotplug_restart));
>  	omap_pm_ops.scu_prepare(cpu, power_state);
>  
>  	/*
> @@ -379,6 +381,7 @@ static void enable_mercury_retention_mode(void)
>  int __init omap4_mpuss_init(void)
>  {
>  	struct omap4_cpu_pm_info *pm_info;
> +	u32 cpu_wakeup_addr = 0;
>  
>  	if (omap_rev() == OMAP4430_REV_ES1_0) {
>  		WARN(1, "Power Management not supported on OMAP4430 ES1.0\n");
> @@ -388,9 +391,13 @@ int __init omap4_mpuss_init(void)
>  	sar_base = omap4_get_sar_ram_base();
>  
>  	/* Initilaise per CPU PM information */
> +	if (cpu_is_omap44xx())
> +		cpu_wakeup_addr = CPU0_WAKEUP_NS_PA_ADDR_OFFSET;
> +	else if (soc_is_omap54xx())
> +		cpu_wakeup_addr = OMAP5_CPU0_WAKEUP_NS_PA_ADDR_OFFSET;
>  	pm_info = &per_cpu(omap4_pm_info, 0x0);
>  	pm_info->scu_sar_addr = sar_base + SCU_OFFSET0;
> -	pm_info->wkup_sar_addr = sar_base + CPU0_WAKEUP_NS_PA_ADDR_OFFSET;
> +	pm_info->wkup_sar_addr = sar_base + cpu_wakeup_addr;
>  	pm_info->l2x0_sar_addr = sar_base + L2X0_SAVE_OFFSET0;
>  	pm_info->pwrdm = pwrdm_lookup("cpu0_pwrdm");
>  	if (!pm_info->pwrdm) {
> @@ -405,14 +412,14 @@ int __init omap4_mpuss_init(void)
>  	/* Initialise CPU0 power domain state to ON */
>  	pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON);
>  
> +	if (cpu_is_omap44xx())
> +		cpu_wakeup_addr = CPU1_WAKEUP_NS_PA_ADDR_OFFSET;
> +	else if (soc_is_omap54xx())
> +		cpu_wakeup_addr = OMAP5_CPU1_WAKEUP_NS_PA_ADDR_OFFSET;
>  	pm_info = &per_cpu(omap4_pm_info, 0x1);
>  	pm_info->scu_sar_addr = sar_base + SCU_OFFSET1;
> -	pm_info->wkup_sar_addr = sar_base + CPU1_WAKEUP_NS_PA_ADDR_OFFSET;
> +	pm_info->wkup_sar_addr = sar_base + cpu_wakeup_addr;
>  	pm_info->l2x0_sar_addr = sar_base + L2X0_SAVE_OFFSET1;
> -	if (cpu_is_omap446x())
> -		pm_info->secondary_startup = omap_secondary_startup_4460;
> -	else
> -		pm_info->secondary_startup = omap_secondary_startup;
>  
>  	pm_info->pwrdm = pwrdm_lookup("cpu1_pwrdm");
>  	if (!pm_info->pwrdm) {
> @@ -445,15 +452,19 @@ int __init omap4_mpuss_init(void)
>  
>  	if (cpu_is_omap44xx()) {
>  		omap_pm_ops.finish_suspend = omap4_finish_suspend;
> +		omap_pm_ops.hotplug_restart = omap_secondary_startup;
could we handle omap_pm_ops.hotplug_restart = omap_secondary_startup_4460
here as well with the interest of keeping all function inits
in consecutive source location?
>  		omap_pm_ops.resume = omap4_cpu_resume;
>  		omap_pm_ops.scu_prepare = scu_pwrst_prepare;
>  		cpu_context_offset = OMAP4_RM_CPU0_CPU0_CONTEXT_OFFSET;
>  	} else if (soc_is_omap54xx()) {
> +		omap_pm_ops.finish_suspend = omap5_finish_suspend;
> +		omap_pm_ops.hotplug_restart = omap5_secondary_startup;
>  		cpu_context_offset = OMAP54XX_RM_CPU0_CPU0_CONTEXT_OFFSET;
> +		enable_mercury_retention_mode();
>  	}
>  
> -	if (soc_is_omap54xx())
> -		enable_mercury_retention_mode();
> +	if (cpu_is_omap446x())
> +		omap_pm_ops.hotplug_restart = omap_secondary_startup_4460;
>  
>  	return 0;
>  }
> diff --git a/arch/arm/mach-omap2/omap-secure.h b/arch/arm/mach-omap2/omap-secure.h
> index 82b3c4c..6f4dbee 100644
> --- a/arch/arm/mach-omap2/omap-secure.h
> +++ b/arch/arm/mach-omap2/omap-secure.h
> @@ -41,6 +41,7 @@
>  #define OMAP4_MON_L2X0_CTRL_INDEX	0x102
>  #define OMAP4_MON_L2X0_AUXCTRL_INDEX	0x109
>  #define OMAP4_MON_L2X0_PREFETCH_INDEX	0x113
> +#define OMAP5_MON_CACHES_CLEAN_INDEX	0x103
>  
>  #define OMAP5_MON_AMBA_IF_INDEX		0x108
>  
> diff --git a/arch/arm/mach-omap2/omap4-sar-layout.h b/arch/arm/mach-omap2/omap4-sar-layout.h
> index 6822d0a..ee8215b 100644
> --- a/arch/arm/mach-omap2/omap4-sar-layout.h
> +++ b/arch/arm/mach-omap2/omap4-sar-layout.h
> @@ -31,6 +31,8 @@
>  /* CPUx Wakeup Non-Secure Physical Address offsets in SAR_BANK3 */
>  #define CPU0_WAKEUP_NS_PA_ADDR_OFFSET		0xa04
>  #define CPU1_WAKEUP_NS_PA_ADDR_OFFSET		0xa08
> +#define OMAP5_CPU0_WAKEUP_NS_PA_ADDR_OFFSET	0xe00
> +#define OMAP5_CPU1_WAKEUP_NS_PA_ADDR_OFFSET	0xe04
>  
>  #define SAR_BACKUP_STATUS_OFFSET		(SAR_BANK3_OFFSET + 0x500)
>  #define SAR_SECURE_RAM_SIZE_OFFSET		(SAR_BANK3_OFFSET + 0x504)
> diff --git a/arch/arm/mach-omap2/sleep_omap4plus.S b/arch/arm/mach-omap2/sleep_omap4plus.S
> index 88ff83a..3322fc8 100644
> --- a/arch/arm/mach-omap2/sleep_omap4plus.S
> +++ b/arch/arm/mach-omap2/sleep_omap4plus.S
> @@ -326,6 +326,86 @@ skip_l2en:
>  
>  	b	cpu_resume			@ Jump to generic resume
>  ENDPROC(omap4_cpu_resume)
> +
> +/*
> + * ================================
> + * == OMAP5 CPU suspend finisher ==
> + * ================================
> + *
> + * OMAP5 MPUSS states for the context save:
> + * save_state =
> + *	0 - Nothing lost and no need to save: MPUSS INA/CSWR
> + *	1 - CPUx L1 and logic lost: CPU OFF, MPUSS INA/CSWR
> + *	2 - CPUx L1 and logic lost + GIC lost: MPUSS OSWR
> + *	3 - CPUx L1 and logic lost + GIC + L2 lost: DEVICE OFF
> + */
> +ENTRY(omap5_finish_suspend)
> +	stmfd	sp!, {r4-r12, lr}
> +	cmp	r0, #0x0
> +	beq	do_wfi				@ No lowpower state, jump to WFI
> +
> +	/*
> +	 * Flush all data from the L1 data cache before disabling
> +	 * SCTLR.C bit.
> +	 */
> +	bl	omap4_get_sar_ram_base
> +	ldr	r9, [r0, #OMAP_TYPE_OFFSET]
> +	cmp	r9, #0x1			@ Check for HS device
> +	bne	skip_secure_l1_clean_op
> +	mov	r0, #0				@ Clean secure L1
> +	stmfd   r13!, {r4-r12, r14}
> +	ldr	r12, =OMAP5_MON_CACHES_CLEAN_INDEX
> +	DO_SMC
> +	ldmfd   r13!, {r4-r12, r14}
> +skip_secure_l1_clean_op:
> +	bl	v7_flush_dcache_louis
> +
> +	/*
> +	 * Clear the SCTLR.C bit to prevent further data cache
> +	 * allocation. Clearing SCTLR.C would make all the data accesses
> +	 * strongly ordered and would not hit the cache.
> +	 */
> +	mrc	p15, 0, r0, c1, c0, 0
> +	bic	r0, r0, #(1 << 2)		@ Disable the C bit
> +	mcr	p15, 0, r0, c1, c0, 0
> +	isb
> +
> +	/* Clean and Invalidate L1 data cache. */
> +	bl	v7_flush_dcache_louis
Curious question - once we have flushed and invalidated L1 on
skip_secure_l1_clean_op:, we disable SCTLR.C to make all accesses SO,
what is the need to go through clean and invalidate again? is'nt it an
un-necessary cycle consuming NOP? What kind of data are we cleaning out
here?
> +
> +	/*
> +	 * Take CPU out of Symmetric Multiprocessing (SMP) mode and thus
> +	 * preventing the CPU from receiving cache, TLB, or BTB
> +	 * maintenance operations broadcast by other CPUs in the cluster.
> +	 */
> +	mrc	p15, 0, r0, c1, c1, 2		@ Read NSACR data
> +	tst	r0, #(1 << 18)
> +	mrcne	p15, 0, r0, c1, c0, 1
> +	bicne	r0, r0, #(1 << 6)		@ Disable SMP bit
> +	mcrne	p15, 0, r0, c1, c0, 1
> +	isb
> +	dsb
> +
when save_state=3, as per the function comment:
 3 - CPUx L1 and logic lost + GIC + L2 lost: DEVICE OFF
But we dont flush L2 nonsecure OR secure at this point. results wont be
pretty.
If we dont want to deal with "3", then should'nt we add an adequate
handler on entry and remove the code comment claiming we support
it?
is'nt it better to squash in "[PATCH 11/15] ARM: OMAP5: PM: Add L2
memory power down support" here?
> +do_wfi:
> +	bl	omap_do_wfi
> +
> +	/*
> +	 * CPU is here when it failed to enter OFF/DORMANT or
> +	 * no low power state was attempted.
This might need a bit more clarity IMHO. successful WFI (which is
arguably an power state if we consider ARM internal clock gating takes
place), will reach here as well. So would OMAP CPU INA state. The
comment is probably valid for the defined save_states in code comments
for CPU OFF which is un-successful.
> +	 */
> +	mrc	p15, 0, r0, c1, c0, 0
> +	tst	r0, #(1 << 2)			@ Check C bit enabled?
> +	orreq	r0, r0, #(1 << 2)		@ Enable the C bit
> +	mcreq	p15, 0, r0, c1, c0, 0
> +	isb
> +	mrc	p15, 0, r0, c1, c0, 1
> +	tst	r0, #(1 << 6)			@ Check SMP bit enabled?
> +	orreq	r0, r0, #(1 << 6)
> +	mcreq	p15, 0, r0, c1, c0, 1
> +	isb
> +	dsb
> +	ldmfd	sp!, {r4-r12, pc}
> +ENDPROC(omap5_finish_suspend)
>  #endif
>  
>  #ifndef CONFIG_OMAP4_ERRATA_I688

I was curious at this point -> given that we added documentation that
we will be able to go to CPU OFF here, but I did not see an resume
handler registered. Untill I looked down in the series to see:
[PATCH 10/15] ARM: OMAP5: PM: Add MPU Open Switch Retention support
Might be better to squash things  up? or maybe remove the
"documentation"? for example:
patch 10 introduces OSWR support, but part of the required support for
it is suspend code - which is introduced here! There is no point IMHO
introducing code to enter OFF mode in one patch and exit off mode in
another patch - just my 2 cents. if we wanted to organize it as per
feature, then maybe:
patch 9 supports just WFI. patch 10 brings in additional suspend and
resume handler for OSWR, patch 11 for device OFF,
in each stage adding documentation for save_state - so that it is
appropriate and in context.

-- 
Regards,
Nishanth Menon