arm: armv7: introduce cpu suspend and resume support

Just like linux, it supports cpu save and restore context
during enter and exit low power mode. With this patch, cpu
is able to suspend with core power off.

Workflow for trap into ATF for system suspend:
	cpu_suspend
	   -> cpu_do_suspend
	      -> arch specific fn: int (*fn)(unsigned long)
		  -> psci_system_suspend(deliver 'cpu_resume()' address to ATF)
		     -> ATF system suspend
		     <- ATF system resume
	      <- cpu_resume
	   <- cpu_do_resume
	next instruction

Notice: If needed, you should remember to save and restore GIC by yourself.

Change-Id: I5cb6fb6ac5b6a7f4ec4a975b0fc38250b000b28e
Signed-off-by: Joseph Chen <chenjh@rock-chips.com>
This commit is contained in:
Joseph Chen 2017-11-07 09:07:40 +08:00 committed by Kever Yang
parent 6f00aaa207
commit 5492555290
7 changed files with 513 additions and 0 deletions

View File

@ -12,6 +12,11 @@ obj-y += cache_v7.o cache_v7_asm.o
obj-y += cpu.o cp15.o
obj-y += syslib.o
ifeq ($(CONFIG_SPL_BUILD)$(CONFIG_TPL_BUILD),)
obj-y += suspend.o
obj-y += sleep.o
endif
ifneq ($(CONFIG_SKIP_LOWLEVEL_INIT),y)
obj-y += lowlevel_init.o
endif

185
arch/arm/cpu/armv7/sleep.S Normal file
View File

@ -0,0 +1,185 @@
/*
* (C) Copyright 2017 Rockchip Electronics Co., Ltd.
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <asm/arm32_macros.S>
#include <asm/macro.h>
#include <asm-offsets.h>
#include <asm/psci.h>
#include <config.h>
#include <linux/linkage.h>
.globl cpu_suspend
.globl cpu_do_suspend
.globl cpu_suspend_save
.globl cpu_resume
.globl cpu_do_resume
/*
* int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
* @arg will be passed to fn as argument
* return value: 0 - cpu resumed from suspended state.
* -1 - cpu not suspended.
*/
ENTRY(cpu_suspend)
push {r4 - r12, lr}
mov r5, sp
sub sp, sp, #PM_CTX_SIZE
push {r0, r1}
/* r9 is gd, save it to _suspend_gd !!! */
adr r4, _suspend_gd
str r9, [r4]
mov r1, r5
add r0, sp, #8
blx cpu_suspend_save
adr lr, aborted
/* Jump to arch specific suspend */
pop {r0, pc}
aborted:
/* cpu not suspended */
add sp, sp, #PM_CTX_SIZE
/* Return -1 to the caller */
mov r0, #(-1)
suspend_return:
pop {r4 - r12, pc}
ENDPROC(cpu_suspend)
ENTRY(cpu_do_suspend)
push {r4 - r11}
read_midr r4
ubfx r5, r4, #4, #12
ldr r4, CORTEX_A7_PART_NUM
cmp r5, r4
beq a7_suspend
ldr r4, CORTEX_A9_PART_NUM
cmp r5, r4
beq a9_suspend
/* cpu not supported */
b .
/* A9 needs PCR/DIAG */
a9_suspend:
read_pcr r4
read_diag r5
stmia r0!, {r4 - r5}
a7_suspend:
read_fcseidr r4
read_tpidruro r5
stmia r0!, {r4 - r5}
read_dacr r4
read_ttbr0 r5
read_ttbr1 r6
read_ttbcr r7
read_sctlr r8
read_actlr r9
read_cpacr r10
stmia r0!, {r4 - r10}
read_prrr r4
read_nmrr r5
read_vbar r6
mrs r7, CPSR
stmia r0, {r4 - r7}
pop {r4 - r11}
bx lr
ENDPROC(cpu_do_suspend)
ENTRY(cpu_resume)
/* Disable interrupt */
cpsid aif
/* Load gd !! */
adr r1, _suspend_gd
ldr r2, [r1]
/* Get pm_ctx */
add r2, r2, #PM_CTX_PHYS
ldr r0, [r2]
/* Need to use r0!, because cpu_do_resume needs it */
ldmia r0!, {sp, pc}
ENDPROC(cpu_resume)
/*
* void sm_do_cpu_do_resume(paddr suspend_regs) __noreturn;
* Restore the registers stored when cpu_do_suspend
* r0 points to the physical base address of the suspend_regs
* field of struct pm_ctx.
*/
ENTRY(cpu_do_resume)
read_midr r4
ubfx r5, r4, #4, #12
ldr r4, CORTEX_A7_PART_NUM
cmp r5, r4
beq a7_resume
/*
* A9 needs PCR/DIAG
*/
ldmia r0!, {r4 - r5}
write_pcr r4
write_diag r5
a7_resume:
/* v7 resume */
mov ip, #0
/* Invalidate icache to PoU */
write_iciallu
/* set reserved context */
write_contextidr ip
ldmia r0!, {r4 - r5}
write_fcseidr r4
write_tpidruro r5
ldmia r0!, {r4 - r10}
/* Invalidate entire TLB */
write_tlbiall
write_dacr r4
write_ttbr0 r5
write_ttbr1 r6
write_ttbcr r7
ldmia r0, {r4 - r7}
write_prrr r4
write_nmrr r5
write_vbar r6
write_actlr r9
write_cpacr r10
write_bpiall
isb
dsb
/* MMU will be enabled here */
write_sctlr r8
isb
/* Restore interrupt */
msr CPSR_c, r7
mov r0, #0
b suspend_return
ENDPROC(cpu_do_resume)
.align 4
_suspend_gd:
.word 0x0
CORTEX_A7_PART_NUM:
.word 0xC07
CORTEX_A9_PART_NUM:
.word 0xC09

View File

@ -0,0 +1,23 @@
/*
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <asm/suspend.h>
#include <common.h>
DECLARE_GLOBAL_DATA_PTR;
void cpu_suspend_save(struct pm_ctx *ctx, unsigned long sp)
{
gd->pm_ctx_phys = (phys_addr_t)ctx;
/* The content will be passed to cpu_do_resume as register sp */
ctx->sp = sp;
ctx->cpu_resume_addr = (phys_addr_t)cpu_do_resume;
cpu_do_suspend(ctx->suspend_regs);
flush_dcache_all();
}

View File

@ -0,0 +1,264 @@
/*
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
*
* SPDX-License-Identifier: GPL-2.0+
*/
/* Please keep them sorted based on the CRn register */
.macro read_midr reg
mrc p15, 0, \reg, c0, c0, 0
.endm
.macro read_ctr reg
mrc p15, 0, \reg, c0, c0, 1
.endm
.macro read_mpidr reg
mrc p15, 0, \reg, c0, c0, 5
.endm
.macro read_sctlr reg
mrc p15, 0, \reg, c1, c0, 0
.endm
.macro write_sctlr reg
mcr p15, 0, \reg, c1, c0, 0
.endm
.macro write_actlr reg
mcr p15, 0, \reg, c1, c0, 1
.endm
.macro read_actlr reg
mrc p15, 0, \reg, c1, c0, 1
.endm
.macro write_cpacr reg
mcr p15, 0, \reg, c1, c0, 2
.endm
.macro read_cpacr reg
mrc p15, 0, \reg, c1, c0, 2
.endm
.macro read_scr reg
mrc p15, 0, \reg, c1, c1, 0
.endm
.macro write_scr reg
mcr p15, 0, \reg, c1, c1, 0
.endm
.macro write_nsacr reg
mcr p15, 0, \reg, c1, c1, 2
.endm
.macro read_nsacr reg
mrc p15, 0, \reg, c1, c1, 2
.endm
.macro write_ttbr0 reg
mcr p15, 0, \reg, c2, c0, 0
.endm
.macro read_ttbr0 reg
mrc p15, 0, \reg, c2, c0, 0
.endm
.macro write_ttbr1 reg
mcr p15, 0, \reg, c2, c0, 1
.endm
.macro read_ttbr1 reg
mrc p15, 0, \reg, c2, c0, 1
.endm
.macro write_ttbcr reg
mcr p15, 0, \reg, c2, c0, 2
.endm
.macro read_ttbcr reg
mrc p15, 0, \reg, c2, c0, 2
.endm
.macro write_dacr reg
mcr p15, 0, \reg, c3, c0, 0
.endm
.macro read_dacr reg
mrc p15, 0, \reg, c3, c0, 0
.endm
.macro read_dfsr reg
mrc p15, 0, \reg, c5, c0, 0
.endm
.macro write_icialluis
/*
* Invalidate all instruction caches to PoU, Inner Shareable
* (register ignored)
*/
mcr p15, 0, r0, c7, c1, 0
.endm
.macro write_bpiallis
/*
* Invalidate entire branch predictor array, Inner Shareable
* (register ignored)
*/
mcr p15, 0, r0, c7, c1, 6
.endm
.macro write_iciallu
/* Invalidate all instruction caches to PoU (register ignored) */
mcr p15, 0, r0, c7, c5, 0
.endm
.macro write_icimvau reg
/* Instruction cache invalidate by MVA */
mcr p15, 0, \reg, c7, c5, 1
.endm
.macro write_bpiall
/* Invalidate entire branch predictor array (register ignored) */
mcr p15, 0, r0, c7, c5, 6
.endm
.macro write_dcimvac reg
/* Data cache invalidate by MVA */
mcr p15, 0, \reg, c7, c6, 1
.endm
.macro write_dcisw reg
/* Data cache invalidate by set/way */
mcr p15, 0, \reg, c7, c6, 2
.endm
.macro write_dccmvac reg
/* Data cache clean by MVA */
mcr p15, 0, \reg, c7, c10, 1
.endm
.macro write_dccsw reg
/* Data cache clean by set/way */
mcr p15, 0, \reg, c7, c10, 2
.endm
.macro write_dccimvac reg
/* Data cache invalidate by MVA */
mcr p15, 0, \reg, c7, c14, 1
.endm
.macro write_dccisw reg
/* Data cache clean and invalidate by set/way */
mcr p15, 0, \reg, c7, c14, 2
.endm
.macro write_tlbiall
/* Invalidate entire unified TLB (register ignored) */
mcr p15, 0, r0, c8, c7, 0
.endm
.macro write_tlbiallis
/* Invalidate entire unified TLB Inner Sharable (register ignored) */
mcr p15, 0, r0, c8, c3, 0
.endm
.macro write_tlbiasidis reg
/* Invalidate unified TLB by ASID Inner Sharable */
mcr p15, 0, \reg, c8, c3, 2
.endm
.macro write_tlbimvaais reg
/* Invalidate unified TLB by MVA all ASID Inner Sharable */
mcr p15, 0, \reg, c8, c3, 3
.endm
.macro write_prrr reg
mcr p15, 0, \reg, c10, c2, 0
.endm
.macro read_prrr reg
mrc p15, 0, \reg, c10, c2, 0
.endm
.macro write_nmrr reg
mcr p15, 0, \reg, c10, c2, 1
.endm
.macro read_nmrr reg
mrc p15, 0, \reg, c10, c2, 1
.endm
.macro read_vbar reg
mrc p15, 0, \reg, c12, c0, 0
.endm
.macro write_vbar reg
mcr p15, 0, \reg, c12, c0, 0
.endm
.macro write_mvbar reg
mcr p15, 0, \reg, c12, c0, 1
.endm
.macro read_mvbar reg
mrc p15, 0, \reg, c12, c0, 1
.endm
.macro write_fcseidr reg
mcr p15, 0, \reg, c13, c0, 0
.endm
.macro read_fcseidr reg
mrc p15, 0, \reg, c13, c0, 0
.endm
.macro write_contextidr reg
mcr p15, 0, \reg, c13, c0, 1
.endm
.macro read_contextidr reg
mrc p15, 0, \reg, c13, c0, 1
.endm
.macro write_tpidruro reg
mcr p15, 0, \reg, c13, c0, 3
.endm
.macro read_tpidruro reg
mrc p15, 0, \reg, c13, c0, 3
.endm
.macro read_clidr reg
/* Cache Level ID Register */
mrc p15, 1, \reg, c0, c0, 1
.endm
.macro read_ccsidr reg
/* Cache Size ID Registers */
mrc p15, 1, \reg, c0, c0, 0
.endm
.macro write_csselr reg
/* Cache Size Selection Register */
mcr p15, 2, \reg, c0, c0, 0
.endm
/* Cortex A9: pcr, diag registers */
.macro write_pcr reg
mcr p15, 0, \reg, c15, c0, 0
.endm
.macro read_pcr reg
mrc p15, 0, \reg, c15, c0, 0
.endm
.macro write_diag reg
mcr p15, 0, \reg, c15, c0, 1
.endm
.macro read_diag reg
mrc p15, 0, \reg, c15, c0, 1
.endm

View File

@ -0,0 +1,25 @@
/*
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef SUSPEND_H
#define SUSPEND_H
#include <common.h>
#include <asm-generic/global_data.h>
/* suspend/resume core functions */
void cpu_suspend_save(struct pm_ctx *ptr, unsigned long sp);
void cpu_do_suspend(unsigned long *ptr);
void cpu_resume(void);
void cpu_do_resume(void);
/*
* Exported to platform suspend, arg will be passed to fn as r0
* Return value: 0 - cpu resumed from suspended state.
* -1 - cpu not suspended.
*/
int cpu_suspend(unsigned long arg, int (*fn)(unsigned long));
#endif

View File

@ -24,6 +24,13 @@
#include <membuff.h>
#include <linux/list.h>
/* Never change the sequence of members !!! */
struct pm_ctx {
unsigned long sp;
phys_addr_t cpu_resume_addr;
unsigned long suspend_regs[15];
};
typedef struct global_data {
bd_t *bd;
unsigned long flags;
@ -114,6 +121,7 @@ typedef struct global_data {
struct bootstage_data *bootstage; /* Bootstage information */
struct bootstage_data *new_bootstage; /* Relocated bootstage info */
#endif
phys_addr_t pm_ctx_phys;
} gd_t;
#endif

View File

@ -38,5 +38,8 @@ int main(void)
DEFINE(GD_START_ADDR_SP, offsetof(struct global_data, start_addr_sp));
DEFINE(PM_CTX_SIZE, sizeof(struct pm_ctx));
DEFINE(PM_CTX_PHYS, offsetof(struct global_data, pm_ctx_phys));
return 0;
}