blob: 5f4e31152a69a945d122c76bacb9441c317ec130 [file] [log] [blame]
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc.
*
* 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 <asm/asm-offsets.h>
#include "hardware.h"
/*
* ==================== low level suspend ====================
*
* Better to follow below rules to use ARM registers:
* r0: pm_info structure address;
* r1 ~ r4: for saving pm_info members;
* r5 ~ r10: free registers;
* r11: io base address.
*
* suspend ocram space layout:
* ======================== high address ======================
* .
* .
* .
* ^
* ^
* ^
* imx7_suspend code
* PM_INFO structure(imx7_cpu_pm_info)
* ======================== low address =======================
*/
/*
* Below offsets are based on struct imx7_cpu_pm_info
* which defined in arch/arm/mach-imx/pm-imx7.c, this
* structure contains necessary pm info for low level
* suspend related code.
*/
#define PM_INFO_M4_RESERVE0_OFFSET 0x0
#define PM_INFO_M4_RESERVE1_OFFSET 0x4
#define PM_INFO_M4_RESERVE2_OFFSET 0x8
#define PM_INFO_PBASE_OFFSET 0xc
#define PM_INFO_RESUME_ADDR_OFFSET 0x10
#define PM_INFO_DDR_TYPE_OFFSET 0x14
#define PM_INFO_PM_INFO_SIZE_OFFSET 0x18
#define PM_INFO_MX7_DDRC_P_OFFSET 0x1c
#define PM_INFO_MX7_DDRC_V_OFFSET 0x20
#define PM_INFO_MX7_DDRC_PHY_P_OFFSET 0x24
#define PM_INFO_MX7_DDRC_PHY_V_OFFSET 0x28
#define PM_INFO_MX7_SRC_P_OFFSET 0x2c
#define PM_INFO_MX7_SRC_V_OFFSET 0x30
#define PM_INFO_MX7_IOMUXC_GPR_P_OFFSET 0x34
#define PM_INFO_MX7_IOMUXC_GPR_V_OFFSET 0x38
#define PM_INFO_MX7_CCM_P_OFFSET 0x3c
#define PM_INFO_MX7_CCM_V_OFFSET 0x40
#define PM_INFO_MX7_GPC_P_OFFSET 0x44
#define PM_INFO_MX7_GPC_V_OFFSET 0x48
#define PM_INFO_MX7_SNVS_P_OFFSET 0x4c
#define PM_INFO_MX7_SNVS_V_OFFSET 0x50
#define PM_INFO_MX7_ANATOP_P_OFFSET 0x54
#define PM_INFO_MX7_ANATOP_V_OFFSET 0x58
#define PM_INFO_MX7_LPSR_P_OFFSET 0x5c
#define PM_INFO_MX7_LPSR_V_OFFSET 0x60
#define PM_INFO_MX7_GIC_DIST_P_OFFSET 0x64
#define PM_INFO_MX7_GIC_DIST_V_OFFSET 0x68
#define PM_INFO_MX7_TTBR1_V_OFFSET 0x6c
#define PM_INFO_DDRC_REG_NUM_OFFSET 0x70
#define PM_INFO_DDRC_REG_OFFSET 0x74
#define PM_INFO_DDRC_VALUE_OFFSET 0x78
#define PM_INFO_DDRC_PHY_REG_NUM_OFFSET 0x174
#define PM_INFO_DDRC_PHY_REG_OFFSET 0x178
#define PM_INFO_DDRC_PHY_VALUE_OFFSET 0x17c
#define MX7_SRC_GPR1 0x74
#define MX7_SRC_GPR2 0x78
#define GPC_PGC_C0 0x800
#define GPC_PGC_FM 0xa00
#define ANADIG_SNVS_MISC_CTRL 0x380
#define ANADIG_SNVS_MISC_CTRL_SET 0x384
#define ANADIG_SNVS_MISC_CTRL_CLR 0x388
#define ANADIG_DIGPROG 0x800
#define DDRC_STAT 0x4
#define DDRC_PWRCTL 0x30
#define DDRC_PSTAT 0x3fc
#define DDRC_PCTRL_0 0x490
#define DDRC_DFIMISC 0x1b0
#define DDRC_SWCTL 0x320
#define DDRC_SWSTAT 0x324
#define DDRPHY_LP_CON0 0x18
#define CCM_SNVS_LPCG 0x250
#define MX7D_GPC_IMR1 0x30
#define MX7D_GPC_IMR2 0x34
#define MX7D_GPC_IMR3 0x38
#define MX7D_GPC_IMR4 0x3c
.align 3
.macro disable_l1_dcache
/*
* Flush all data from the L1 data cache before disabling
* SCTLR.C bit.
*/
push {r0 - r10, lr}
ldr r7, =v7_flush_dcache_all
mov lr, pc
mov pc, r7
pop {r0 - r10, lr}
/* disable d-cache */
mrc p15, 0, r7, c1, c0, 0
bic r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
dsb
isb
push {r0 - r10, lr}
ldr r7, =v7_flush_dcache_all
mov lr, pc
mov pc, r7
pop {r0 - r10, lr}
.endm
.macro store_ttbr1
/* Store TTBR1 to pm_info->ttbr1 */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_MX7_TTBR1_V_OFFSET]
/* Disable Branch Prediction, Z bit in SCTLR. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x800
mcr p15, 0, r6, c1, c0, 0
/* Flush the BTAC. */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
ldr r6, =iram_tlb_phys_addr
ldr r6, [r6]
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r6, c2, c0, 1
/* Read TTBCR and set PD0=1, N = 1 */
mrc p15, 0, r6, c2, c0, 2
orr r6, r6, #0x11
mcr p15, 0, r6, c2, c0, 2
dsb
isb
/* flush the TLB */
ldr r6, =0x0
mcr p15, 0, r6, c8, c3, 0
.endm
.macro restore_ttbr1
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
/* Restore TTBCR */
/* Read TTBCR and set PD0=0, N = 0 */
mrc p15, 0, r6, c2, c0, 2
bic r6, r6, #0x11
mcr p15, 0, r6, c2, c0, 2
dsb
isb
/* flush the TLB */
ldr r6, =0x0
mcr p15, 0, r6, c8, c3, 0
/* Enable Branch Prediction, Z bit in SCTLR. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x800
mcr p15, 0, r6, c1, c0, 0
/* Flush the Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* Restore TTBR1, get the origin ttbr1 from pm info */
ldr r7, [r0, #PM_INFO_MX7_TTBR1_V_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
.macro ddrc_enter_self_refresh
ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r11, #DDRC_PWRCTL]
/* wait rw port_busy clear */
ldr r6, =(0x1 << 16)
orr r6, r6, #0x1
1:
ldr r7, [r11, #DDRC_PSTAT]
ands r7, r7, r6
bne 1b
/* enter self-refresh bit 5 */
ldr r7, =(0x1 << 5)
str r7, [r11, #DDRC_PWRCTL]
/* wait until self-refresh mode entered */
2:
ldr r7, [r11, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
bne 2b
3:
ldr r7, [r11, #DDRC_STAT]
ands r7, r7, #0x20
beq 3b
/* disable dram clk */
ldr r7, [r11, #DDRC_PWRCTL]
orr r7, r7, #(1 << 3)
str r7, [r11, #DDRC_PWRCTL]
.endm
.macro ddrc_exit_self_refresh
cmp r5, #0x0
ldreq r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
ldrne r11, [r0, #PM_INFO_MX7_DDRC_P_OFFSET]
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r11, #DDRC_PWRCTL]
/* wait until self-refresh mode entered */
4:
ldr r7, [r11, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
beq 4b
/* enable auto self-refresh */
ldr r7, [r11, #DDRC_PWRCTL]
orr r7, r7, #(1 << 0)
str r7, [r11, #DDRC_PWRCTL]
.endm
.macro wait_delay
5:
subs r6, r6, #0x1
bne 5b
.endm
.macro ddr_enter_retention
ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r11, #DDRC_PCTRL_0]
/* wait rw port_busy clear */
ldr r6, =(0x1 << 16)
orr r6, r6, #0x1
6:
ldr r7, [r11, #DDRC_PSTAT]
ands r7, r7, r6
bne 6b
ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
/* enter self-refresh bit 5 */
ldr r7, =(0x1 << 5)
str r7, [r11, #DDRC_PWRCTL]
/* wait until self-refresh mode entered */
7:
ldr r7, [r11, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
bne 7b
8:
ldr r7, [r11, #DDRC_STAT]
ands r7, r7, #0x20
beq 8b
/* disable dram clk */
ldr r7, =(0x1 << 5)
orr r7, r7, #(1 << 3)
str r7, [r11, #DDRC_PWRCTL]
ldr r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET]
ldr r7, [r11, #ANADIG_DIGPROG]
and r7, r7, #0xff
cmp r7, #0x11
bne 10f
/* TO 1.1 */
ldr r11, [r0, #PM_INFO_MX7_IOMUXC_GPR_V_OFFSET]
ldr r7, =0x38000000
str r7, [r11]
/* LPSR mode need to use TO1.0 flow as IOMUX lost power */
ldr r10, [r0, #PM_INFO_MX7_LPSR_V_OFFSET]
ldr r7, [r10]
cmp r7, #0x0
beq 11f
10:
/* reset ddr_phy */
ldr r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET]
ldr r7, =0x0
str r7, [r11, #ANADIG_SNVS_MISC_CTRL]
/* delay 7 us */
ldr r6, =6000
wait_delay
ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET]
ldr r6, =0x1000
ldr r7, [r11, r6]
orr r7, r7, #0x1
str r7, [r11, r6]
11:
/* turn off ddr power */
ldr r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET]
ldr r7, =(0x1 << 29)
str r7, [r11, #ANADIG_SNVS_MISC_CTRL_SET]
ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET]
ldr r6, =0x1000
ldr r7, [r11, r6]
orr r7, r7, #0x1
str r7, [r11, r6]
.endm
.macro ddr_exit_retention
cmp r5, #0x0
ldreq r1, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET]
ldrne r1, [r0, #PM_INFO_MX7_ANATOP_P_OFFSET]
ldreq r2, [r0, #PM_INFO_MX7_SRC_V_OFFSET]
ldrne r2, [r0, #PM_INFO_MX7_SRC_P_OFFSET]
ldreq r3, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
ldrne r3, [r0, #PM_INFO_MX7_DDRC_P_OFFSET]
ldreq r4, [r0, #PM_INFO_MX7_DDRC_PHY_V_OFFSET]
ldrne r4, [r0, #PM_INFO_MX7_DDRC_PHY_P_OFFSET]
ldreq r10, [r0, #PM_INFO_MX7_CCM_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX7_CCM_P_OFFSET]
ldreq r11, [r0, #PM_INFO_MX7_IOMUXC_GPR_V_OFFSET]
ldrne r11, [r0, #PM_INFO_MX7_IOMUXC_GPR_P_OFFSET]
/* turn on ddr power */
ldr r7, =(0x1 << 29)
str r7, [r1, #ANADIG_SNVS_MISC_CTRL_CLR]
ldr r6, =50
wait_delay
/* clear ddr_phy reset */
ldr r6, =0x1000
ldr r7, [r2, r6]
orr r7, r7, #0x3
str r7, [r2, r6]
ldr r7, [r2, r6]
bic r7, r7, #0x1
str r7, [r2, r6]
13:
ldr r6, [r0, #PM_INFO_DDRC_REG_NUM_OFFSET]
ldr r7, =PM_INFO_DDRC_REG_OFFSET
add r7, r7, r0
14:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r3, r8]
subs r6, r6, #0x1
bne 14b
ldr r7, =0x20
str r7, [r3, #DDRC_PWRCTL]
ldr r7, =0x0
str r7, [r3, #DDRC_DFIMISC]
/* do PHY, clear ddr_phy reset */
ldr r6, =0x1000
ldr r7, [r2, r6]
bic r7, r7, #0x2
str r7, [r2, r6]
ldr r7, [r1, #ANADIG_DIGPROG]
and r7, r7, #0xff
cmp r7, #0x11
bne 12f
/*
* TKT262940:
* System hang when press RST for DDR PAD is
* in retention mode, fixed on TO1.1
*/
ldr r7, [r11]
bic r7, r7, #(1 << 27)
str r7, [r11]
ldr r7, [r11]
bic r7, r7, #(1 << 29)
str r7, [r11]
12:
ldr r7, =(0x1 << 30)
str r7, [r1, #ANADIG_SNVS_MISC_CTRL_SET]
/* need to delay ~5mS */
ldr r6, =0x100000
wait_delay
ldr r6, [r0, #PM_INFO_DDRC_PHY_REG_NUM_OFFSET]
ldr r7, =PM_INFO_DDRC_PHY_REG_OFFSET
add r7, r7, r0
15:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r4, r8]
subs r6, r6, #0x1
bne 15b
ldr r7, =0x0
add r9, r10, #0x4000
str r7, [r9, #0x130]
ldr r7, =0x170
orr r7, r7, #0x8
str r7, [r11, #0x20]
ldr r7, =0x2
add r9, r10, #0x4000
str r7, [r9, #0x130]
ldr r7, =0xf
str r7, [r4, #DDRPHY_LP_CON0]
/* wait until self-refresh mode entered */
16:
ldr r7, [r3, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
bne 16b
ldr r7, =0x0
str r7, [r3, #DDRC_SWCTL]
ldr r7, =0x1
str r7, [r3, #DDRC_DFIMISC]
ldr r7, =0x1
str r7, [r3, #DDRC_SWCTL]
17:
ldr r7, [r3, #DDRC_SWSTAT]
and r7, r7, #0x1
cmp r7, #0x1
bne 17b
18:
ldr r7, [r3, #DDRC_STAT]
and r7, r7, #0x20
cmp r7, #0x20
bne 18b
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r3, #DDRC_PWRCTL]
19:
ldr r7, [r3, #DDRC_STAT]
and r7, r7, #0x30
cmp r7, #0x0
bne 19b
20:
ldr r7, [r3, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x1
bne 20b
/* enable port */
ldr r7, =0x1
str r7, [r3, #DDRC_PCTRL_0]
/* enable auto self-refresh */
ldr r7, [r3, #DDRC_PWRCTL]
orr r7, r7, #(1 << 0)
str r7, [r3, #DDRC_PWRCTL]
.endm
ENTRY(imx7_suspend)
push {r4-r12}
/* make sure SNVS clk is enabled */
ldr r11, [r0, #PM_INFO_MX7_CCM_V_OFFSET]
add r11, r11, #0x4000
ldr r7, =0x3
str r7, [r11, #CCM_SNVS_LPCG]
/* check whether it is a standby mode */
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11, #GPC_PGC_C0]
cmp r7, #0
beq ddr_only_self_refresh
/*
* The value of r0 is mapped the same in origin table and IRAM table,
* thus no need to care r0 here.
*/
ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET]
ldr r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]
/*
* counting the resume address in iram
* to set it in SRC register.
*/
ldr r6, =imx7_suspend
ldr r7, =resume
sub r7, r7, r6
add r8, r1, r4
add r9, r8, r7
ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET]
/* store physical resume addr and pm_info address. */
str r9, [r11, #MX7_SRC_GPR1]
str r1, [r11, #MX7_SRC_GPR2]
disable_l1_dcache
store_ttbr1
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11, #GPC_PGC_FM]
cmp r7, #0
beq ddr_only_self_refresh
ddr_enter_retention
/* enter LPSR mode if resume addr is valid */
ldr r11, [r0, #PM_INFO_MX7_LPSR_V_OFFSET]
ldr r7, [r11]
cmp r7, #0x0
beq ddr_retention_enter_out
/* disable STOP mode before entering LPSR */
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11]
bic r7, #0xf
str r7, [r11]
/* shut down vddsoc to enter lpsr mode */
ldr r11, [r0, #PM_INFO_MX7_SNVS_V_OFFSET]
ldr r7, [r11, #0x38]
orr r7, r7, #0x60
str r7, [r11, #0x38]
wait_shutdown:
wfi
nop
nop
nop
nop
b wait_shutdown
ddr_only_self_refresh:
ddrc_enter_self_refresh
b wfi
ddr_retention_enter_out:
ldr r11, [r0, #PM_INFO_MX7_GIC_DIST_V_OFFSET]
ldr r7, =0x0
ldr r8, =0x1000
str r7, [r11, r8]
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r4, [r11, #MX7D_GPC_IMR1]
ldr r5, [r11, #MX7D_GPC_IMR2]
ldr r6, [r11, #MX7D_GPC_IMR3]
ldr r7, [r11, #MX7D_GPC_IMR4]
ldr r8, =0xffffffff
str r8, [r11, #MX7D_GPC_IMR1]
str r8, [r11, #MX7D_GPC_IMR2]
str r8, [r11, #MX7D_GPC_IMR3]
str r8, [r11, #MX7D_GPC_IMR4]
/*
* enable the RBC bypass counter here
* to hold off the interrupts. RBC counter
* = 8 (240us). With this setting, the latency
* from wakeup interrupt to ARM power up
* is ~250uS.
*/
ldr r8, [r11, #0x14]
bic r8, r8, #(0x3f << 24)
orr r8, r8, #(0x8 << 24)
str r8, [r11, #0x14]
/* enable the counter. */
ldr r8, [r11, #0x14]
orr r8, r8, #(0x1 << 30)
str r8, [r11, #0x14]
/* unmask all the GPC interrupts. */
str r4, [r11, #MX7D_GPC_IMR1]
str r5, [r11, #MX7D_GPC_IMR2]
str r6, [r11, #MX7D_GPC_IMR3]
str r7, [r11, #MX7D_GPC_IMR4]
/*
* now delay for a short while (3usec)
* ARM is at 1GHz at this point
* so a short loop should be enough.
* this delay is required to ensure that
* the RBC counter can start counting in
* case an interrupt is already pending
* or in case an interrupt arrives just
* as ARM is about to assert DSM_request.
*/
ldr r7, =2000
rbc_loop:
subs r7, r7, #0x1
bne rbc_loop
wfi:
/* Zzz, enter stop mode */
wfi
nop
nop
nop
nop
mov r5, #0x0
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11, #GPC_PGC_FM]
cmp r7, #0
beq wfi_ddr_self_refresh_out
ddr_exit_retention
b wfi_ddr_retention_out
wfi_ddr_self_refresh_out:
ddrc_exit_self_refresh
wfi_ddr_retention_out:
/* check whether it is a standby mode */
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11, #GPC_PGC_C0]
cmp r7, #0
beq standby_out
ldr r11, [r0, #PM_INFO_MX7_GIC_DIST_V_OFFSET]
ldr r7, =0x1
ldr r8, =0x1000
str r7, [r11, r8]
restore_ttbr1
standby_out:
pop {r4-r12}
/* return to suspend finish */
mov pc, lr
resume:
/* invalidate L1 I-cache first */
mov r6, #0x0
mcr p15, 0, r6, c7, c5, 0
mcr p15, 0, r6, c7, c5, 6
/* enable the Icache and branch prediction */
mov r6, #0x1800
mcr p15, 0, r6, c1, c0, 0
isb
/* get physical resume address from pm_info. */
ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
/* clear core0's entry and parameter */
ldr r11, [r0, #PM_INFO_MX7_SRC_P_OFFSET]
mov r7, #0x0
str r7, [r11, #MX7_SRC_GPR1]
str r7, [r11, #MX7_SRC_GPR2]
mov r5, #0x1
ldr r11, [r0, #PM_INFO_MX7_GPC_P_OFFSET]
ldr r7, [r11, #GPC_PGC_FM]
cmp r7, #0
beq dsm_ddr_self_refresh_out
ddr_exit_retention
b dsm_ddr_retention_out
dsm_ddr_self_refresh_out:
ddrc_exit_self_refresh
dsm_ddr_retention_out:
mov pc, lr
ENDPROC(imx7_suspend)
ENTRY(ca7_cpu_resume)
bl v7_invalidate_l1
b cpu_resume
ENDPROC(ca7_cpu_resume)