summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-imx/suspend-imx6.S
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-imx/suspend-imx6.S')
-rw-r--r--arch/arm/mach-imx/suspend-imx6.S289
1 files changed, 289 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/suspend-imx6.S b/arch/arm/mach-imx/suspend-imx6.S
new file mode 100644
index 000000000000..d273ee7ecc36
--- /dev/null
+++ b/arch/arm/mach-imx/suspend-imx6.S
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2014 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/hardware/cache-l2x0.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 ======================
+ * .
+ * .
+ * .
+ * ^
+ * ^
+ * ^
+ * imx6_suspend code
+ * PM_INFO structure(imx6_cpu_pm_info)
+ * ======================== low address =======================
+ */
+
+/*
+ * Below offsets are based on struct imx6_cpu_pm_info
+ * which defined in arch/arm/mach-imx/pm-imx6q.c, this
+ * structure contains necessary pm info for low level
+ * suspend related code.
+ */
+#define PM_INFO_PBASE_OFFSET 0x0
+#define PM_INFO_RESUME_ADDR_OFFSET 0x4
+#define PM_INFO_CPU_TYPE_OFFSET 0x8
+#define PM_INFO_PM_INFO_SIZE_OFFSET 0xC
+#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10
+#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14
+#define PM_INFO_MX6Q_SRC_P_OFFSET 0x18
+#define PM_INFO_MX6Q_SRC_V_OFFSET 0x1C
+#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x20
+#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x24
+#define PM_INFO_MX6Q_CCM_P_OFFSET 0x28
+#define PM_INFO_MX6Q_CCM_V_OFFSET 0x2C
+#define PM_INFO_MX6Q_GPC_P_OFFSET 0x30
+#define PM_INFO_MX6Q_GPC_V_OFFSET 0x34
+#define PM_INFO_MX6Q_L2_P_OFFSET 0x38
+#define PM_INFO_MX6Q_L2_V_OFFSET 0x3C
+#define PM_INFO_MMDC_IO_NUM_OFFSET 0x40
+#define PM_INFO_MMDC_IO_VAL_OFFSET 0x44
+
+#define MX6Q_SRC_GPR1 0x20
+#define MX6Q_SRC_GPR2 0x24
+#define MX6Q_MMDC_MAPSR 0x404
+#define MX6Q_GPC_IMR1 0x08
+#define MX6Q_GPC_IMR2 0x0c
+#define MX6Q_GPC_IMR3 0x10
+#define MX6Q_GPC_IMR4 0x14
+#define MX6Q_CCM_CCR 0x0
+
+ .align 3
+
+ .macro sync_l2_cache
+
+ /* sync L2 cache to drain L2's buffers to DRAM. */
+#ifdef CONFIG_CACHE_L2X0
+ ldr r11, [r0, #PM_INFO_MX6Q_L2_V_OFFSET]
+ mov r6, #0x0
+ str r6, [r11, #L2X0_CACHE_SYNC]
+1:
+ ldr r6, [r11, #L2X0_CACHE_SYNC]
+ ands r6, r6, #0x1
+ bne 1b
+#endif
+
+ .endm
+
+ .macro resume_mmdc
+
+ /* restore MMDC IO */
+ cmp r5, #0x0
+ ldreq r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
+ ldrne r11, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET]
+
+ ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
+ ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET
+ add r7, r7, r0
+1:
+ ldr r8, [r7], #0x4
+ ldr r9, [r7], #0x4
+ str r9, [r11, r8]
+ subs r6, r6, #0x1
+ bne 1b
+
+ cmp r5, #0x0
+ ldreq r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
+ ldrne r11, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET]
+
+ /* let DDR out of self-refresh */
+ ldr r7, [r11, #MX6Q_MMDC_MAPSR]
+ bic r7, r7, #(1 << 21)
+ str r7, [r11, #MX6Q_MMDC_MAPSR]
+2:
+ ldr r7, [r11, #MX6Q_MMDC_MAPSR]
+ ands r7, r7, #(1 << 25)
+ bne 2b
+
+ /* enable DDR auto power saving */
+ ldr r7, [r11, #MX6Q_MMDC_MAPSR]
+ bic r7, r7, #0x1
+ str r7, [r11, #MX6Q_MMDC_MAPSR]
+
+ .endm
+
+ENTRY(imx6_suspend)
+ ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
+ ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
+ ldr r3, [r0, #PM_INFO_CPU_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, =imx6_suspend
+ ldr r7, =resume
+ sub r7, r7, r6
+ add r8, r1, r4
+ add r9, r8, r7
+
+ /*
+ * make sure TLB contain the addr we want,
+ * as we will access them after MMDC IO floated.
+ */
+
+ ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
+ ldr r6, [r11, #0x0]
+ ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
+ ldr r6, [r11, #0x0]
+
+ /* use r11 to store the IO address */
+ ldr r11, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET]
+ /* store physical resume addr and pm_info address. */
+ str r9, [r11, #MX6Q_SRC_GPR1]
+ str r1, [r11, #MX6Q_SRC_GPR2]
+
+ /* need to sync L2 cache before DSM. */
+ sync_l2_cache
+
+ ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
+ /*
+ * put DDR explicitly into self-refresh and
+ * disable automatic power savings.
+ */
+ ldr r7, [r11, #MX6Q_MMDC_MAPSR]
+ orr r7, r7, #0x1
+ str r7, [r11, #MX6Q_MMDC_MAPSR]
+
+ /* make the DDR explicitly enter self-refresh. */
+ ldr r7, [r11, #MX6Q_MMDC_MAPSR]
+ orr r7, r7, #(1 << 21)
+ str r7, [r11, #MX6Q_MMDC_MAPSR]
+
+poll_dvfs_set:
+ ldr r7, [r11, #MX6Q_MMDC_MAPSR]
+ ands r7, r7, #(1 << 25)
+ beq poll_dvfs_set
+
+ ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
+ ldr r6, =0x0
+ ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
+ ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET
+ add r8, r8, r0
+set_mmdc_io_lpm:
+ ldr r9, [r8], #0x8
+ str r6, [r11, r9]
+ subs r7, r7, #0x1
+ bne set_mmdc_io_lpm
+
+ /*
+ * mask all GPC interrupts before
+ * enabling the RBC counters to
+ * avoid the counter starting too
+ * early if an interupt is already
+ * pending.
+ */
+ ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
+ ldr r6, [r11, #MX6Q_GPC_IMR1]
+ ldr r7, [r11, #MX6Q_GPC_IMR2]
+ ldr r8, [r11, #MX6Q_GPC_IMR3]
+ ldr r9, [r11, #MX6Q_GPC_IMR4]
+
+ ldr r10, =0xffffffff
+ str r10, [r11, #MX6Q_GPC_IMR1]
+ str r10, [r11, #MX6Q_GPC_IMR2]
+ str r10, [r11, #MX6Q_GPC_IMR3]
+ str r10, [r11, #MX6Q_GPC_IMR4]
+
+ /*
+ * enable the RBC bypass counter here
+ * to hold off the interrupts. RBC counter
+ * = 32 (1ms), Minimum RBC delay should be
+ * 400us for the analog LDOs to power down.
+ */
+ ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
+ ldr r10, [r11, #MX6Q_CCM_CCR]
+ bic r10, r10, #(0x3f << 21)
+ orr r10, r10, #(0x20 << 21)
+ str r10, [r11, #MX6Q_CCM_CCR]
+
+ /* enable the counter. */
+ ldr r10, [r11, #MX6Q_CCM_CCR]
+ orr r10, r10, #(0x1 << 27)
+ str r10, [r11, #MX6Q_CCM_CCR]
+
+ /* unmask all the GPC interrupts. */
+ ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
+ str r6, [r11, #MX6Q_GPC_IMR1]
+ str r7, [r11, #MX6Q_GPC_IMR2]
+ str r8, [r11, #MX6Q_GPC_IMR3]
+ str r9, [r11, #MX6Q_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 r6, =2000
+rbc_loop:
+ subs r6, r6, #0x1
+ bne rbc_loop
+
+ /* Zzz, enter stop mode */
+ wfi
+ nop
+ nop
+ nop
+ nop
+
+ /*
+ * run to here means there is pending
+ * wakeup source, system should auto
+ * resume, we need to restore MMDC IO first
+ */
+ mov r5, #0x0
+ resume_mmdc
+
+ /* 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_MX6Q_SRC_P_OFFSET]
+ mov r7, #0x0
+ str r7, [r11, #MX6Q_SRC_GPR1]
+ str r7, [r11, #MX6Q_SRC_GPR2]
+
+ mov r5, #0x1
+ resume_mmdc
+
+ mov pc, lr
+ENDPROC(imx6_suspend)