|
@@ -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)
|