MLK-11265-8 ARM: imx: add pm support for imx7d
authorOctavian Purdila <octavian.purdila@nxp.com>
Thu, 27 Oct 2016 12:15:25 +0000 (15:15 +0300)
committerNitin Garg <nitin.garg@nxp.com>
Mon, 19 Mar 2018 19:46:47 +0000 (14:46 -0500)
Add i.MX7D suspend/resume support, including standby
and mem mode support, mega/fast mix off and DDR
retention support.

Signed-off-by: Anson Huang <b20788@freescale.com>
arch/arm/mach-imx/Makefile
arch/arm/mach-imx/anatop.c
arch/arm/mach-imx/common.h
arch/arm/mach-imx/gpcv2.c [new file with mode: 0644]
arch/arm/mach-imx/mach-imx7d.c
arch/arm/mach-imx/pm-imx7.c [new file with mode: 0644]
arch/arm/mach-imx/suspend-imx7.S [new file with mode: 0644]

index d7f5586..ffcd9ef 100644 (file)
@@ -67,7 +67,7 @@ obj-$(CONFIG_MACH_VPR200) += mach-vpr200.o
 obj-$(CONFIG_MACH_IMX35_DT) += imx35-dt.o
 
 obj-$(CONFIG_HAVE_IMX_ANATOP) += anatop.o
-obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o
+obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o gpcv2.o
 obj-$(CONFIG_HAVE_IMX_MMDC) += mmdc.o
 obj-$(CONFIG_HAVE_IMX_DDRC) += ddrc.o
 obj-$(CONFIG_HAVE_IMX_SRC) += src.o
@@ -84,10 +84,13 @@ obj-$(CONFIG_SOC_IMX7D) += mach-imx7d.o
 
 ifeq ($(CONFIG_SUSPEND),y)
 AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a
+AFLAGS_suspend-imx7.o :=-Wa,-march=armv7-a
 obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o
+obj-$(CONFIG_SOC_IMX7D) += suspend-imx7.o
 obj-$(CONFIG_SOC_IMX53) += suspend-imx53.o
 endif
 obj-$(CONFIG_SOC_IMX6) += pm-imx6.o
+obj-$(CONFIG_SOC_IMX7D) += pm-imx7.o
 
 obj-$(CONFIG_SOC_IMX1) += mach-imx1.o
 obj-$(CONFIG_SOC_IMX50) += mach-imx50.o
index 649a84c..02c78ac 100644 (file)
 #define REG_SET                0x4
 #define REG_CLR                0x8
 
+#define ANADIG_ARM_PLL         0x60
+#define ANADIG_DDR_PLL         0x70
+#define ANADIG_SYS_PLL         0xb0
+#define ANADIG_ENET_PLL                0xe0
+#define ANADIG_AUDIO_PLL       0xf0
+#define ANADIG_VIDEO_PLL       0x130
+
 #define ANADIG_REG_2P5         0x130
 #define ANADIG_REG_CORE                0x140
 #define ANADIG_ANA_MISC0       0x150
@@ -74,6 +81,17 @@ static inline void imx_anatop_disconnect_high_snvs(bool enable)
 
 void imx_anatop_pre_suspend(void)
 {
+       if (cpu_is_imx7d()) {
+               /* PLL overwrite set */
+               regmap_write(anatop, ANADIG_ARM_PLL + REG_SET, 1 << 20);
+               regmap_write(anatop, ANADIG_DDR_PLL + REG_SET, 1 << 19);
+               regmap_write(anatop, ANADIG_SYS_PLL + REG_SET, 1 << 17);
+               regmap_write(anatop, ANADIG_ENET_PLL + REG_SET, 1 << 13);
+               regmap_write(anatop, ANADIG_AUDIO_PLL + REG_SET, 1 << 24);
+               regmap_write(anatop, ANADIG_VIDEO_PLL + REG_SET, 1 << 24);
+               return;
+       }
+
        if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2)
                imx_anatop_enable_2p5_pulldown(true);
        else
@@ -87,6 +105,17 @@ void imx_anatop_pre_suspend(void)
 
 void imx_anatop_post_resume(void)
 {
+       if (cpu_is_imx7d()) {
+               /* PLL overwrite set */
+               regmap_write(anatop, ANADIG_ARM_PLL + REG_CLR, 1 << 20);
+               regmap_write(anatop, ANADIG_DDR_PLL + REG_CLR, 1 << 19);
+               regmap_write(anatop, ANADIG_SYS_PLL + REG_CLR, 1 << 17);
+               regmap_write(anatop, ANADIG_ENET_PLL + REG_CLR, 1 << 13);
+               regmap_write(anatop, ANADIG_AUDIO_PLL + REG_CLR, 1 << 24);
+               regmap_write(anatop, ANADIG_VIDEO_PLL + REG_CLR, 1 << 24);
+               return;
+       }
+
        if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2)
                imx_anatop_enable_2p5_pulldown(false);
        else
index 623c645..d971663 100644 (file)
@@ -93,9 +93,15 @@ void imx_smp_prepare(void);
 static inline void imx_scu_map_io(void) {}
 static inline void imx_smp_prepare(void) {}
 #endif
+void imx7_pm_map_io(void);
 void imx_src_init(void);
 void imx_gpc_pre_suspend(bool arm_power_off);
 void imx_gpc_post_resume(void);
+void imx_gpcv2_pre_suspend(bool arm_power_off);
+void imx_gpcv2_post_resume(void);
+void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn);
+unsigned int imx_gpcv2_is_mf_mix_off(void);
+void __init imx_gpcv2_check_dt(void);
 void imx_gpc_mask_all(void);
 void imx_gpc_restore_all(void);
 void imx_gpc_hwirq_mask(unsigned int hwirq);
@@ -113,14 +119,18 @@ int imx_cpu_kill(unsigned int cpu);
 
 #ifdef CONFIG_SUSPEND
 void v7_cpu_resume(void);
+void ca7_cpu_resume(void);
 void imx53_suspend(void __iomem *ocram_vbase);
 extern const u32 imx53_suspend_sz;
 void imx6_suspend(void __iomem *ocram_vbase);
+void imx7_suspend(void __iomem *ocram_vbase);
 #else
 static inline void v7_cpu_resume(void) {}
+static inline void ca7_cpu_resume(void) {}
 static inline void imx53_suspend(void __iomem *ocram_vbase) {}
 static const u32 imx53_suspend_sz;
 static inline void imx6_suspend(void __iomem *ocram_vbase) {}
+static inline void imx7_suspend(void __iomem *ocram_vbase) {}
 #endif
 
 void imx6_pm_ccm_init(const char *ccm_compat);
diff --git a/arch/arm/mach-imx/gpcv2.c b/arch/arm/mach-imx/gpcv2.c
new file mode 100644 (file)
index 0000000..e9e1a51
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip/arm-gic.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/regulator/consumer.h>
+
+#include "common.h"
+#include "hardware.h"
+
+#define IMR_NUM                        4
+#define GPC_MAX_IRQS           (IMR_NUM * 32)
+#define GPC_LPCR_A7_BSC                0x0
+#define GPC_LPCR_A7_AD         0x4
+#define GPC_LPCR_M4            0x8
+#define GPC_SLPCR              0x14
+#define GPC_MLPCR              0x20
+#define GPC_PGC_ACK_SEL_A7     0x24
+#define GPC_MISC               0x2c
+#define GPC_IMR1_CORE0         0x30
+#define GPC_IMR1_CORE1         0x40
+#define GPC_SLOT0_CFG          0xb0
+#define GPC_PGC_CPU_MAPPING    0xec
+#define GPC_CPU_PGC_SW_PUP_REQ 0xf0
+#define GPC_PU_PGC_SW_PUP_REQ  0xf8
+#define GPC_CPU_PGC_SW_PDN_REQ 0xfc
+#define GPC_PU_PGC_SW_PDN_REQ  0x104
+#define GPC_GTOR               0x124
+#define GPC_PGC_C0             0x800
+#define GPC_PGC_SCU_TIMING     0x890
+#define GPC_PGC_C1             0x840
+#define GPC_PGC_SCU            0x880
+#define GPC_PGC_FM             0xa00
+#define GPC_PGC_MIPI_PHY       0xc00
+#define GPC_PGC_PCIE_PHY       0xc40
+#define GPC_PGC_USB_OTG1_PHY   0xc80
+#define GPC_PGC_USB_OTG2_PHY   0xcc0
+#define GPC_PGC_USB_HSIC_PHY   0xd00
+
+#define BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP       0x70000000
+#define BM_LPCR_A7_BSC_CPU_CLK_ON_LPM          0x4000
+#define BM_LPCR_A7_BSC_LPM1                    0xc
+#define BM_LPCR_A7_BSC_LPM0                    0x3
+#define BP_LPCR_A7_BSC_LPM1                    2
+#define BP_LPCR_A7_BSC_LPM0                    0
+#define BM_LPCR_M4_MASK_DSM_TRIGGER            0x80000000
+#define BM_SLPCR_EN_DSM                                0x80000000
+#define BM_SLPCR_RBC_EN                                0x40000000
+#define BM_SLPCR_VSTBY                         0x4
+#define BM_SLPCR_SBYOS                         0x2
+#define BM_SLPCR_BYPASS_PMIC_READY             0x1
+#define BM_SLPCR_EN_A7_FASTWUP_WAIT_MODE       0x10000
+#define BM_LPCR_A7_AD_L2PGE                    0x10000
+#define BM_LPCR_A7_AD_EN_C1_PUP                        0x800
+#define BM_LPCR_A7_AD_EN_C1_IRQ_PUP            0x400
+#define BM_LPCR_A7_AD_EN_C0_PUP                        0x200
+#define BM_LPCR_A7_AD_EN_C0_IRQ_PUP            0x100
+#define BM_LPCR_A7_AD_EN_PLAT_PDN              0x10
+#define BM_LPCR_A7_AD_EN_C1_PDN                        0x8
+#define BM_LPCR_A7_AD_EN_C1_WFI_PDN            0x4
+#define BM_LPCR_A7_AD_EN_C0_PDN                        0x2
+#define BM_LPCR_A7_AD_EN_C0_WFI_PDN            0x1
+
+#define BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7     0x2
+
+#define BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK    0x80000000
+#define BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK    0x8000
+#define BM_GPC_MLPCR_MEMLP_CTL_DIS             0x1
+
+#define BP_LPCR_A7_BSC_IRQ_SRC                 28
+
+#define MAX_SLOT_NUMBER                                10
+#define A7_LPM_WAIT                            0x5
+#define A7_LPM_STOP                            0xa
+
+enum imx_gpc_slot {
+       CORE0_A7,
+       CORE1_A7,
+       SCU_A7,
+       FAST_MEGA_MIX,
+       MIPI_PHY,
+       PCIE_PHY,
+       USB_OTG1_PHY,
+       USB_OTG2_PHY,
+       USB_HSIC_PHY,
+       CORE0_M4,
+};
+
+static void __iomem *gpc_base;
+static u32 gpcv2_wake_irqs[IMR_NUM];
+static u32 gpcv2_saved_imrs[IMR_NUM];
+static u32 gpcv2_mf_irqs[IMR_NUM];
+static u32 gpcv2_mf_request_on[IMR_NUM];
+static DEFINE_SPINLOCK(gpcv2_lock);
+
+static int imx_gpcv2_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+       unsigned int idx = d->hwirq / 32;
+       unsigned long flags;
+       u32 mask;
+
+       mask = 1 << d->hwirq % 32;
+       spin_lock_irqsave(&gpcv2_lock, flags);
+       gpcv2_wake_irqs[idx] = on ? gpcv2_wake_irqs[idx] | mask :
+                                 gpcv2_wake_irqs[idx] & ~mask;
+       spin_unlock_irqrestore(&gpcv2_lock, flags);
+
+       return 0;
+}
+
+void imx_gpcv2_mask_all(void)
+{
+       void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0;
+       int i;
+
+       for (i = 0; i < IMR_NUM; i++) {
+               gpcv2_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
+               writel_relaxed(~0, reg_imr1 + i * 4);
+       }
+}
+
+void imx_gpcv2_restore_all(void)
+{
+       void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0;
+       int i;
+
+       for (i = 0; i < IMR_NUM; i++)
+               writel_relaxed(gpcv2_saved_imrs[i], reg_imr1 + i * 4);
+}
+
+void imx_gpcv2_hwirq_unmask(unsigned int hwirq)
+{
+       void __iomem *reg;
+       u32 val;
+
+       reg = gpc_base + GPC_IMR1_CORE0 + (hwirq / 32) * 4;
+       val = readl_relaxed(reg);
+       val &= ~(1 << hwirq % 32);
+       writel_relaxed(val, reg);
+}
+
+void imx_gpcv2_hwirq_mask(unsigned int hwirq)
+{
+       void __iomem *reg;
+       u32 val;
+
+       reg = gpc_base + GPC_IMR1_CORE0 + (hwirq / 32) * 4;
+       val = readl_relaxed(reg);
+       val |= 1 << (hwirq % 32);
+       writel_relaxed(val, reg);
+}
+
+static void imx_gpcv2_irq_unmask(struct irq_data *d)
+{
+       imx_gpcv2_hwirq_unmask(d->hwirq);
+       irq_chip_unmask_parent(d);
+}
+
+static void imx_gpcv2_irq_mask(struct irq_data *d)
+{
+       imx_gpcv2_hwirq_mask(d->hwirq);
+       irq_chip_mask_parent(d);
+}
+
+void imx_gpcv2_set_slot_ack(u32 index, enum imx_gpc_slot m_core,
+                               bool mode, bool ack)
+{
+       u32 val;
+
+       if (index >= MAX_SLOT_NUMBER)
+               pr_err("Invalid slot index!\n");
+       /* set slot */
+       writel_relaxed((mode + 1) << (m_core * 2), gpc_base +
+               GPC_SLOT0_CFG + index * 4);
+
+       if (ack) {
+               /* set ack */
+               val = readl_relaxed(gpc_base + GPC_PGC_ACK_SEL_A7);
+               /* clear dummy ack */
+               val &= ~(1 << (15 + (mode ? 16 : 0)));
+               val |= 1 << (m_core + (mode ? 16 : 0));
+               writel_relaxed(val, gpc_base + GPC_PGC_ACK_SEL_A7);
+       }
+}
+
+void imx_gpcv2_set_lpm_mode(enum mxc_cpu_pwr_mode mode)
+{
+       unsigned long flags;
+       u32 val1, val2;
+
+       spin_lock_irqsave(&gpcv2_lock, flags);
+
+       val1 = readl_relaxed(gpc_base + GPC_LPCR_A7_BSC);
+       val2 = readl_relaxed(gpc_base + GPC_SLPCR);
+
+       /* all cores' LPM settings must be same */
+       val1 &= ~(BM_LPCR_A7_BSC_LPM0 | BM_LPCR_A7_BSC_LPM1);
+
+       val1 |= BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
+
+       val2 &= ~(BM_SLPCR_EN_DSM | BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN |
+               BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY);
+       /*
+        * GPC: When improper low-power sequence is used,
+        * the SoC enters low power mode before the ARM core executes WFI.
+        *
+        * Software workaround:
+        * 1) Software should trigger IRQ #32 (IOMUX) to be always pending
+        *    by setting IOMUX_GPR1_IRQ.
+        * 2) Software should then unmask IRQ #32 in GPC before setting GPC
+        *    Low-Power mode.
+        * 3) Software should mask IRQ #32 right after GPC Low-Power mode
+        *    is set.
+        */
+       switch (mode) {
+       case WAIT_CLOCKED:
+               imx_gpcv2_hwirq_unmask(0);
+               break;
+       case WAIT_UNCLOCKED:
+               val1 |= A7_LPM_WAIT << BP_LPCR_A7_BSC_LPM0;
+               val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
+               imx_gpcv2_hwirq_mask(0);
+               break;
+       case STOP_POWER_ON:
+               val1 |= A7_LPM_STOP << BP_LPCR_A7_BSC_LPM0;
+               val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
+               val2 |= BM_SLPCR_EN_DSM;
+               val2 |= BM_SLPCR_RBC_EN;
+               val2 |= BM_SLPCR_BYPASS_PMIC_READY;
+               imx_gpcv2_hwirq_mask(0);
+               break;
+       case STOP_POWER_OFF:
+               val1 |= A7_LPM_STOP << BP_LPCR_A7_BSC_LPM0;
+               val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
+               val2 |= BM_SLPCR_EN_DSM;
+               val2 |= BM_SLPCR_RBC_EN;
+               val2 |= BM_SLPCR_SBYOS;
+               val2 |= BM_SLPCR_VSTBY;
+               val2 |= BM_SLPCR_BYPASS_PMIC_READY;
+               imx_gpcv2_hwirq_mask(0);
+               break;
+       default:
+               return;
+       }
+       writel_relaxed(val1, gpc_base + GPC_LPCR_A7_BSC);
+       writel_relaxed(val2, gpc_base + GPC_SLPCR);
+
+       spin_unlock_irqrestore(&gpcv2_lock, flags);
+}
+
+void imx_gpcv2_set_plat_power_gate_by_lpm(bool pdn)
+{
+       u32 val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD);
+
+       val &= ~(BM_LPCR_A7_AD_EN_PLAT_PDN | BM_LPCR_A7_AD_L2PGE);
+       if (pdn)
+               val |= BM_LPCR_A7_AD_EN_PLAT_PDN | BM_LPCR_A7_AD_L2PGE;
+
+       writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD);
+}
+
+void imx_gpcv2_set_m_core_pgc(bool enable, u32 offset)
+{
+       writel_relaxed(enable, gpc_base + offset);
+}
+
+void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn)
+{
+       u32 val = readl_relaxed(gpc_base + (pdn ?
+               GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ));
+
+       imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C1);
+       val |= BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7;
+       writel_relaxed(val, gpc_base + (pdn ?
+               GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ));
+
+       while ((readl_relaxed(gpc_base + (pdn ?
+               GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ)) &
+               BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7) != 0)
+               ;
+       imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C1);
+}
+
+void imx_gpcv2_set_cpu_power_gate_by_wfi(u32 cpu, bool pdn)
+{
+       unsigned long flags;
+       u32 val;
+
+       spin_lock_irqsave(&gpcv2_lock, flags);
+       val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD);
+
+       if (cpu == 0) {
+               if (pdn) {
+                       imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C0);
+                       val |= BM_LPCR_A7_AD_EN_C0_WFI_PDN |
+                               BM_LPCR_A7_AD_EN_C0_IRQ_PUP;
+               } else {
+                       imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C0);
+                       val &= ~(BM_LPCR_A7_AD_EN_C0_WFI_PDN |
+                               BM_LPCR_A7_AD_EN_C0_IRQ_PUP);
+               }
+       }
+       if (cpu == 1) {
+               if (pdn) {
+                       imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C1);
+                       val |= BM_LPCR_A7_AD_EN_C1_WFI_PDN |
+                               BM_LPCR_A7_AD_EN_C1_IRQ_PUP;
+               } else {
+                       imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C1);
+                       val &= ~(BM_LPCR_A7_AD_EN_C1_WFI_PDN |
+                               BM_LPCR_A7_AD_EN_C1_IRQ_PUP);
+               }
+       }
+       writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD);
+       spin_unlock_irqrestore(&gpcv2_lock, flags);
+}
+
+void imx_gpcv2_set_cpu_power_gate_by_lpm(u32 cpu, bool pdn)
+{
+       unsigned long flags;
+       u32 val;
+
+       spin_lock_irqsave(&gpcv2_lock, flags);
+
+       val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD);
+       if (cpu == 0) {
+               if (pdn)
+                       val |= BM_LPCR_A7_AD_EN_C0_PDN |
+                               BM_LPCR_A7_AD_EN_C0_PUP;
+               else
+                       val &= ~(BM_LPCR_A7_AD_EN_C0_PDN |
+                               BM_LPCR_A7_AD_EN_C0_PUP);
+       }
+       if (cpu == 1) {
+               if (pdn)
+                       val |= BM_LPCR_A7_AD_EN_C1_PDN |
+                               BM_LPCR_A7_AD_EN_C1_PUP;
+               else
+                       val &= ~(BM_LPCR_A7_AD_EN_C1_PDN |
+                               BM_LPCR_A7_AD_EN_C1_PUP);
+       }
+
+       writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD);
+       spin_unlock_irqrestore(&gpcv2_lock, flags);
+}
+
+void imx_gpcv2_set_cpu_power_gate_in_idle(bool pdn)
+{
+       unsigned long flags;
+       u32 cpu;
+
+       for_each_possible_cpu(cpu)
+               imx_gpcv2_set_cpu_power_gate_by_lpm(cpu, pdn);
+
+       spin_lock_irqsave(&gpcv2_lock, flags);
+
+       imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_C0);
+       if (num_online_cpus() > 1)
+               imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_C1);
+       imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_SCU);
+       imx_gpcv2_set_plat_power_gate_by_lpm(pdn);
+
+       if (pdn) {
+               imx_gpcv2_set_slot_ack(0, CORE0_A7, false, false);
+               if (num_online_cpus() > 1)
+                       imx_gpcv2_set_slot_ack(1, CORE1_A7, false, false);
+               imx_gpcv2_set_slot_ack(2, SCU_A7, false, true);
+               imx_gpcv2_set_slot_ack(6, SCU_A7, true, false);
+               imx_gpcv2_set_slot_ack(7, CORE0_A7, true, false);
+               if (num_online_cpus() > 1)
+                       imx_gpcv2_set_slot_ack(8, CORE1_A7, true, true);
+       } else {
+               writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 0 * 0x4);
+               writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 1 * 0x4);
+               writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 2 * 0x4);
+               writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 6 * 0x4);
+               writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 7 * 0x4);
+               writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 8 * 0x4);
+               writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK |
+                       BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK,
+                       gpc_base + GPC_PGC_ACK_SEL_A7);
+       }
+       spin_unlock_irqrestore(&gpcv2_lock, flags);
+}
+
+void imx_gpcv2_set_mix_phy_gate_by_lpm(u32 pdn_index, u32 pup_index)
+{
+       /* set power down slot */
+       writel_relaxed(1 << (FAST_MEGA_MIX * 2),
+               gpc_base + GPC_SLOT0_CFG + pdn_index * 4);
+
+       /* set power up slot */
+       writel_relaxed(1 << (FAST_MEGA_MIX * 2 + 1),
+               gpc_base + GPC_SLOT0_CFG + pup_index * 4);
+}
+
+unsigned int imx_gpcv2_is_mf_mix_off(void)
+{
+       return readl_relaxed(gpc_base + GPC_PGC_FM);
+}
+
+static void imx_gpcv2_mf_mix_off(void)
+{
+       int i;
+
+       for (i = 0; i < IMR_NUM; i++)
+               if (((gpcv2_wake_irqs[i] | gpcv2_mf_request_on[i]) &
+                                               gpcv2_mf_irqs[i]) != 0)
+                       return;
+
+       pr_info("Turn off Mega/Fast mix in DSM\n");
+       imx_gpcv2_set_mix_phy_gate_by_lpm(1, 5);
+       imx_gpcv2_set_m_core_pgc(true, GPC_PGC_FM);
+}
+
+int imx_gpcv2_mf_power_on(unsigned int irq, unsigned int on)
+{
+       unsigned int idx = irq / 32 - 1;
+       unsigned long flags;
+       u32 mask;
+
+       mask = 1 << (irq % 32);
+       spin_lock_irqsave(&gpcv2_lock, flags);
+       gpcv2_mf_request_on[idx] = on ? gpcv2_mf_request_on[idx] | mask :
+                                 gpcv2_mf_request_on[idx] & ~mask;
+       spin_unlock_irqrestore(&gpcv2_lock, flags);
+
+       return 0;
+}
+
+void imx_gpcv2_pre_suspend(bool arm_power_off)
+{
+       void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0;
+       int i;
+
+       if (arm_power_off) {
+               imx_gpcv2_set_lpm_mode(STOP_POWER_OFF);
+               /* enable core0 power down/up with low power mode */
+               imx_gpcv2_set_cpu_power_gate_by_lpm(0, true);
+               /* enable plat power down with low power mode */
+               imx_gpcv2_set_plat_power_gate_by_lpm(true);
+
+               /*
+                * To avoid confuse, we use slot 0~4 for power down,
+                * slot 5~9 for power up.
+                *
+                * Power down slot sequence:
+                * Slot0 -> CORE0
+                * Slot1 -> Mega/Fast MIX
+                * Slot2 -> SCU
+                *
+                * Power up slot sequence:
+                * Slot5 -> Mega/Fast MIX
+                * Slot6 -> SCU
+                * Slot7 -> CORE0
+                */
+               imx_gpcv2_set_slot_ack(0, CORE0_A7, false, false);
+               imx_gpcv2_set_slot_ack(2, SCU_A7, false, true);
+
+               imx_gpcv2_mf_mix_off();
+
+               imx_gpcv2_set_slot_ack(6, SCU_A7, true, false);
+               imx_gpcv2_set_slot_ack(7, CORE0_A7, true, true);
+
+               /* enable core0, scu */
+               imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C0);
+               imx_gpcv2_set_m_core_pgc(true, GPC_PGC_SCU);
+       } else {
+               imx_gpcv2_set_lpm_mode(STOP_POWER_ON);
+       }
+
+       for (i = 0; i < IMR_NUM; i++) {
+               gpcv2_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
+               writel_relaxed(~gpcv2_wake_irqs[i], reg_imr1 + i * 4);
+       }
+}
+
+void imx_gpcv2_post_resume(void)
+{
+       void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0;
+       int i;
+
+       for (i = 0; i < IMR_NUM; i++)
+               writel_relaxed(gpcv2_saved_imrs[i], reg_imr1 + i * 4);
+
+       imx_gpcv2_set_lpm_mode(WAIT_CLOCKED);
+       imx_gpcv2_set_cpu_power_gate_by_lpm(0, false);
+       imx_gpcv2_set_plat_power_gate_by_lpm(false);
+
+       imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C0);
+       imx_gpcv2_set_m_core_pgc(false, GPC_PGC_SCU);
+       imx_gpcv2_set_m_core_pgc(false, GPC_PGC_FM);
+       for (i = 0; i < MAX_SLOT_NUMBER; i++)
+               writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + i * 0x4);
+       writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK |
+               BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK,
+               gpc_base + GPC_PGC_ACK_SEL_A7);
+}
+
+static struct irq_chip imx_gpcv2_chip = {
+       .name                   = "GPCV2",
+       .irq_eoi                = irq_chip_eoi_parent,
+       .irq_mask               = imx_gpcv2_irq_mask,
+       .irq_unmask             = imx_gpcv2_irq_unmask,
+       .irq_retrigger          = irq_chip_retrigger_hierarchy,
+       .irq_set_wake           = imx_gpcv2_irq_set_wake,
+#ifdef CONFIG_SMP
+       .irq_set_affinity       = irq_chip_set_affinity_parent,
+#endif
+};
+
+static int imx_gpcv2_domain_xlate(struct irq_domain *domain,
+                               struct device_node *controller,
+                               const u32 *intspec,
+                               unsigned int intsize,
+                               unsigned long *out_hwirq,
+                               unsigned int *out_type)
+{
+       if (irq_domain_get_of_node(domain) != controller)
+               return -EINVAL; /* Shouldn't happen, really... */
+       if (intsize != 3)
+               return -EINVAL; /* Not GIC compliant */
+       if (intspec[0] != 0)
+               return -EINVAL; /* No PPI should point to this domain */
+
+       *out_hwirq = intspec[1];
+       *out_type = intspec[2];
+       return 0;
+}
+
+static int imx_gpcv2_domain_alloc(struct irq_domain *domain,
+                                 unsigned int irq,
+                                 unsigned int nr_irqs, void *data)
+{
+       struct of_phandle_args *args = data;
+       struct of_phandle_args parent_args;
+       irq_hw_number_t hwirq;
+       int i;
+
+       if (args->args_count != 3)
+               return -EINVAL; /* Not GIC compliant */
+       if (args->args[0] != 0)
+               return -EINVAL; /* No PPI should point to this domain */
+
+       hwirq = args->args[1];
+       if (hwirq >= GPC_MAX_IRQS)
+               return -EINVAL; /* Can't deal with this */
+
+       for (i = 0; i < nr_irqs; i++)
+               irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
+                                             &imx_gpcv2_chip, NULL);
+
+       parent_args = *args;
+       parent_args.np = irq_domain_get_of_node(domain);
+       return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs, &parent_args);
+}
+
+static struct irq_domain_ops imx_gpcv2_domain_ops = {
+       .xlate  = imx_gpcv2_domain_xlate,
+       .alloc  = imx_gpcv2_domain_alloc,
+       .free   = irq_domain_free_irqs_common,
+};
+
+static int __init imx_gpcv2_init(struct device_node *node,
+                              struct device_node *parent)
+{
+       struct irq_domain *parent_domain, *domain;
+       int i, val;
+
+       if (!parent) {
+               pr_err("%s: no parent, giving up\n", node->full_name);
+               return -ENODEV;
+       }
+
+       parent_domain = irq_find_host(parent);
+       if (!parent_domain) {
+               pr_err("%s: unable to obtain parent domain\n", node->full_name);
+               return -ENXIO;
+       }
+
+       gpc_base = of_iomap(node, 0);
+       if (WARN_ON(!gpc_base))
+               return -ENOMEM;
+
+       domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS,
+                                         node, &imx_gpcv2_domain_ops,
+                                         NULL);
+       if (!domain) {
+               iounmap(gpc_base);
+               return -ENOMEM;
+       }
+
+       /* Initially mask all interrupts */
+       for (i = 0; i < IMR_NUM; i++) {
+               writel_relaxed(~0, gpc_base + GPC_IMR1_CORE0 + i * 4);
+               writel_relaxed(~0, gpc_base + GPC_IMR1_CORE1 + i * 4);
+       }
+       /*
+        * Due to hardware design requirement, need to make sure GPR
+        * interrupt(#32) is unmasked during RUN mode to avoid entering
+        * DSM by mistake.
+        */
+       writel_relaxed(~0x1, gpc_base + GPC_IMR1_CORE0);
+
+       /* Read supported wakeup source in M/F domain */
+       if (cpu_is_imx7d()) {
+               of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 0,
+                       &gpcv2_mf_irqs[0]);
+               of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 1,
+                       &gpcv2_mf_irqs[1]);
+               of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 2,
+                       &gpcv2_mf_irqs[2]);
+               of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 3,
+                       &gpcv2_mf_irqs[3]);
+               if (!(gpcv2_mf_irqs[0] | gpcv2_mf_irqs[1] |
+                       gpcv2_mf_irqs[2] | gpcv2_mf_irqs[3]))
+                       pr_info("No wakeup source in Mega/Fast domain found!\n");
+       }
+
+       /* only external IRQs to wake up LPM and core 0/1 */
+       val = readl_relaxed(gpc_base + GPC_LPCR_A7_BSC);
+       val |= BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP;
+       writel_relaxed(val, gpc_base + GPC_LPCR_A7_BSC);
+       /* mask m4 dsm trigger */
+       writel_relaxed(readl_relaxed(gpc_base + GPC_LPCR_M4) |
+               BM_LPCR_M4_MASK_DSM_TRIGGER, gpc_base + GPC_LPCR_M4);
+       /* set mega/fast mix in A7 domain */
+       writel_relaxed(0x1, gpc_base + GPC_PGC_CPU_MAPPING);
+       /* set SCU timing */
+       writel_relaxed((0x59 << 10) | 0x5B | (0x51 << 20),
+               gpc_base + GPC_PGC_SCU_TIMING);
+       writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK |
+               BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK,
+               gpc_base + GPC_PGC_ACK_SEL_A7);
+
+       val = readl_relaxed(gpc_base + GPC_SLPCR);
+       val &= ~(BM_SLPCR_EN_DSM | BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN |
+               BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY);
+       val |= BM_SLPCR_EN_A7_FASTWUP_WAIT_MODE;
+       writel_relaxed(val, gpc_base + GPC_SLPCR);
+
+       /* disable memory low power mode */
+       val = readl_relaxed(gpc_base + GPC_MLPCR);
+       val |= BM_GPC_MLPCR_MEMLP_CTL_DIS;
+       writel_relaxed(val, gpc_base + GPC_MLPCR);
+
+       return 0;
+}
+
+/*
+ * We cannot use the IRQCHIP_DECLARE macro that lives in
+ * drivers/irqchip, so we're forced to roll our own. Not very nice.
+ */
+OF_DECLARE_2(irqchip, imx_gpcv2, "fsl,imx7d-gpc", imx_gpcv2_init);
+
+void __init imx_gpcv2_check_dt(void)
+{
+       struct device_node *np;
+
+       np = of_find_compatible_node(NULL, NULL, "fsl,imx7d-gpc");
+       if (WARN_ON(!np))
+               return;
+
+       if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) {
+               pr_warn("Outdated DT detected, suspend/resume will NOT work\n");
+
+               /* map GPC, so that at least CPUidle and WARs keep working */
+               gpc_base = of_iomap(np, 0);
+       }
+}
+
+static int imx_gpcv2_probe(struct platform_device *pdev)
+{
+       return 0;
+}
+
+static struct of_device_id imx_gpcv2_dt_ids[] = {
+       { .compatible = "fsl,imx7d-gpc" },
+       { }
+};
+
+static struct platform_driver imx_gpcv2_driver = {
+       .driver = {
+               .name = "imx-gpcv2",
+               .owner = THIS_MODULE,
+               .of_match_table = imx_gpcv2_dt_ids,
+       },
+       .probe = imx_gpcv2_probe,
+};
+
+static int __init imx_pgcv2_init(void)
+{
+       return platform_driver_register(&imx_gpcv2_driver);
+}
+subsys_initcall(imx_pgcv2_init);
index 26ca744..003ef45 100644 (file)
@@ -104,6 +104,12 @@ static void __init imx7d_init_irq(void)
        irqchip_init();
 }
 
+static void __init imx7d_map_io(void)
+{
+       debug_ll_io_init();
+       imx7_pm_map_io();
+}
+
 static const char *const imx7d_dt_compat[] __initconst = {
        "fsl,imx7d",
        "fsl,imx7s",
@@ -111,6 +117,7 @@ static const char *const imx7d_dt_compat[] __initconst = {
 };
 
 DT_MACHINE_START(IMX7D, "Freescale i.MX7 Dual (Device Tree)")
+       .map_io         = imx7d_map_io,
        .init_irq       = imx7d_init_irq,
        .init_machine   = imx7d_init_machine,
        .dt_compat      = imx7d_dt_compat,
diff --git a/arch/arm/mach-imx/pm-imx7.c b/arch/arm/mach-imx/pm-imx7.c
new file mode 100644 (file)
index 0000000..02d1678
--- /dev/null
@@ -0,0 +1,626 @@
+/*
+ * 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/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_fdt.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/genalloc.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx7-iomuxc-gpr.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_fdt.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/suspend.h>
+#include <asm/cacheflush.h>
+#include <asm/fncpy.h>
+#include <asm/mach/map.h>
+#include <asm/proc-fns.h>
+#include <asm/suspend.h>
+#include <asm/tlb.h>
+
+#include "common.h"
+#include "hardware.h"
+
+#define MX7_SUSPEND_OCRAM_SIZE         0x1000
+#define MX7_MAX_DDRC_NUM               32
+#define MX7_MAX_DDRC_PHY_NUM           16
+
+#define MX7_SUSPEND_IRAM_ADDR_OFFSET   0
+#define READ_DATA_FROM_HARDWARE                0
+
+#define UART_UCR1      0x80
+#define UART_UCR2      0x84
+#define UART_UCR3      0x88
+#define UART_UCR4      0x8c
+#define UART_UFCR      0x90
+#define UART_UESC      0x9c
+#define UART_UTIM      0xa0
+#define UART_UBIR      0xa4
+#define UART_UBMR      0xa8
+#define UART_UBRC      0xac
+#define UART_UTS       0xb4
+
+unsigned long iram_tlb_base_addr;
+unsigned long iram_tlb_phys_addr;
+
+static unsigned int *ocram_saved_in_ddr;
+static void __iomem *ocram_base;
+static unsigned int ocram_size;
+static void __iomem *ccm_base;
+static void __iomem *console_base;
+static void __iomem *suspend_ocram_base;
+static void (*imx7_suspend_in_ocram_fn)(void __iomem *ocram_vbase);
+/*
+ * suspend ocram space layout:
+ * ======================== high address ======================
+ *                              .
+ *                              .
+ *                              .
+ *                              ^
+ *                              ^
+ *                              ^
+ *                      imx7_suspend code
+ *              PM_INFO structure(imx7_cpu_pm_info)
+ * ======================== low address =======================
+ */
+
+struct imx7_pm_base {
+       phys_addr_t pbase;
+       void __iomem *vbase;
+};
+
+struct imx7_pm_socdata {
+       u32 ddr_type;
+       const char *ddrc_compat;
+       const char *src_compat;
+       const char *iomuxc_compat;
+       const char *gpc_compat;
+       const u32 ddrc_num;
+       const u32 (*ddrc_offset)[2];
+       const u32 ddrc_phy_num;
+       const u32 (*ddrc_phy_offset)[2];
+};
+
+static const u32 imx7d_ddrc_lpddr3_setting[][2] __initconst = {
+       { 0x0, READ_DATA_FROM_HARDWARE },
+       { 0x1a0, READ_DATA_FROM_HARDWARE },
+       { 0x1a4, READ_DATA_FROM_HARDWARE },
+       { 0x1a8, READ_DATA_FROM_HARDWARE },
+       { 0x64, READ_DATA_FROM_HARDWARE },
+       { 0xd0, 0xc0350001 },
+       { 0xdc, READ_DATA_FROM_HARDWARE },
+       { 0xe0, READ_DATA_FROM_HARDWARE },
+       { 0xe4, READ_DATA_FROM_HARDWARE },
+       { 0xf4, READ_DATA_FROM_HARDWARE },
+       { 0x100, READ_DATA_FROM_HARDWARE },
+       { 0x104, READ_DATA_FROM_HARDWARE },
+       { 0x108, READ_DATA_FROM_HARDWARE },
+       { 0x10c, READ_DATA_FROM_HARDWARE },
+       { 0x110, READ_DATA_FROM_HARDWARE },
+       { 0x114, READ_DATA_FROM_HARDWARE },
+       { 0x118, READ_DATA_FROM_HARDWARE },
+       { 0x11c, READ_DATA_FROM_HARDWARE },
+       { 0x180, READ_DATA_FROM_HARDWARE },
+       { 0x184, READ_DATA_FROM_HARDWARE },
+       { 0x190, READ_DATA_FROM_HARDWARE },
+       { 0x194, READ_DATA_FROM_HARDWARE },
+       { 0x200, READ_DATA_FROM_HARDWARE },
+       { 0x204, READ_DATA_FROM_HARDWARE },
+       { 0x214, READ_DATA_FROM_HARDWARE },
+       { 0x218, READ_DATA_FROM_HARDWARE },
+       { 0x240, 0x06000601 },
+       { 0x244, READ_DATA_FROM_HARDWARE },
+};
+
+static const u32 imx7d_ddrc_phy_lpddr3_setting[][2] __initconst = {
+       { 0x0, READ_DATA_FROM_HARDWARE },
+       { 0x4, READ_DATA_FROM_HARDWARE },
+       { 0x8, READ_DATA_FROM_HARDWARE },
+       { 0x10, READ_DATA_FROM_HARDWARE },
+       { 0x1c, READ_DATA_FROM_HARDWARE },
+       { 0x9c, READ_DATA_FROM_HARDWARE },
+       { 0x20, READ_DATA_FROM_HARDWARE },
+       { 0x30, READ_DATA_FROM_HARDWARE },
+       { 0x50, 0x01000008 },
+       { 0x50, 0x00000008 },
+       { 0xc0, 0x0e407304 },
+       { 0xc0, 0x0e447304 },
+       { 0xc0, 0x0e447306 },
+       { 0xc0, 0x0e4c7304 },
+       { 0xc0, 0x0e487306 },
+};
+
+static const u32 imx7d_ddrc_ddr3_setting[][2] __initconst = {
+       { 0x0, READ_DATA_FROM_HARDWARE },
+       { 0x1a0, READ_DATA_FROM_HARDWARE },
+       { 0x1a4, READ_DATA_FROM_HARDWARE },
+       { 0x1a8, READ_DATA_FROM_HARDWARE },
+       { 0x64, READ_DATA_FROM_HARDWARE },
+       { 0x490, 0x00000001 },
+       { 0xd0, 0xc0020001 },
+       { 0xd4, READ_DATA_FROM_HARDWARE },
+       { 0xdc, READ_DATA_FROM_HARDWARE },
+       { 0xe0, READ_DATA_FROM_HARDWARE },
+       { 0xe4, READ_DATA_FROM_HARDWARE },
+       { 0xf4, READ_DATA_FROM_HARDWARE },
+       { 0x100, READ_DATA_FROM_HARDWARE },
+       { 0x104, READ_DATA_FROM_HARDWARE },
+       { 0x108, READ_DATA_FROM_HARDWARE },
+       { 0x10c, READ_DATA_FROM_HARDWARE },
+       { 0x110, READ_DATA_FROM_HARDWARE },
+       { 0x114, READ_DATA_FROM_HARDWARE },
+       { 0x120, 0x03030803 },
+       { 0x180, READ_DATA_FROM_HARDWARE },
+       { 0x190, READ_DATA_FROM_HARDWARE },
+       { 0x194, READ_DATA_FROM_HARDWARE },
+       { 0x200, READ_DATA_FROM_HARDWARE },
+       { 0x204, READ_DATA_FROM_HARDWARE },
+       { 0x214, READ_DATA_FROM_HARDWARE },
+       { 0x218, READ_DATA_FROM_HARDWARE },
+       { 0x240, 0x06000601 },
+       { 0x244, READ_DATA_FROM_HARDWARE },
+};
+
+static const u32 imx7d_ddrc_phy_ddr3_setting[][2] __initconst = {
+       { 0x0, READ_DATA_FROM_HARDWARE },
+       { 0x4, READ_DATA_FROM_HARDWARE },
+       { 0x10, READ_DATA_FROM_HARDWARE },
+       { 0x9c, READ_DATA_FROM_HARDWARE },
+       { 0x20, READ_DATA_FROM_HARDWARE },
+       { 0x30, READ_DATA_FROM_HARDWARE },
+       { 0x50, 0x01000010 },
+       { 0x50, 0x00000010 },
+       { 0xc0, 0x0e407304 },
+       { 0xc0, 0x0e447304 },
+       { 0xc0, 0x0e447306 },
+       { 0xc0, 0x0e447304 },
+       { 0xc0, 0x0e407306 },
+};
+
+static const struct imx7_pm_socdata imx7d_pm_data_lpddr3 __initconst = {
+       .ddrc_compat = "fsl,imx7d-ddrc",
+       .src_compat = "fsl,imx7d-src",
+       .iomuxc_compat = "fsl,imx7d-iomuxc",
+       .gpc_compat = "fsl,imx7d-gpc",
+       .ddrc_num = ARRAY_SIZE(imx7d_ddrc_lpddr3_setting),
+       .ddrc_offset = imx7d_ddrc_lpddr3_setting,
+       .ddrc_phy_num = ARRAY_SIZE(imx7d_ddrc_phy_lpddr3_setting),
+       .ddrc_phy_offset = imx7d_ddrc_phy_lpddr3_setting,
+};
+
+static const struct imx7_pm_socdata imx7d_pm_data_ddr3 __initconst = {
+       .ddrc_compat = "fsl,imx7d-ddrc",
+       .src_compat = "fsl,imx7d-src",
+       .iomuxc_compat = "fsl,imx7d-iomuxc",
+       .gpc_compat = "fsl,imx7d-gpc",
+       .ddrc_num = ARRAY_SIZE(imx7d_ddrc_ddr3_setting),
+       .ddrc_offset = imx7d_ddrc_ddr3_setting,
+       .ddrc_phy_num = ARRAY_SIZE(imx7d_ddrc_phy_ddr3_setting),
+       .ddrc_phy_offset = imx7d_ddrc_phy_ddr3_setting,
+};
+
+/*
+ * This structure is for passing necessary data for low level ocram
+ * suspend code(arch/arm/mach-imx/suspend-imx7.S), if this struct
+ * definition is changed, the offset definition in
+ * arch/arm/mach-imx/suspend-imx7.S must be also changed accordingly,
+ * otherwise, the suspend to ocram function will be broken!
+ */
+struct imx7_cpu_pm_info {
+       u32 m4_reserve0;
+       u32 m4_reserve1;
+       u32 m4_reserve2;
+       phys_addr_t pbase; /* The physical address of pm_info. */
+       phys_addr_t resume_addr; /* The physical resume address for asm code */
+       u32 ddr_type;
+       u32 pm_info_size; /* Size of pm_info. */
+       struct imx7_pm_base ddrc_base;
+       struct imx7_pm_base ddrc_phy_base;
+       struct imx7_pm_base src_base;
+       struct imx7_pm_base iomuxc_gpr_base;
+       struct imx7_pm_base ccm_base;
+       struct imx7_pm_base gpc_base;
+       struct imx7_pm_base l2_base;
+       struct imx7_pm_base anatop_base;
+       u32 ttbr1; /* Store TTBR1 */
+       u32 ddrc_num; /* Number of DDRC which need saved/restored. */
+       u32 ddrc_val[MX7_MAX_DDRC_NUM][2]; /* To save offset and value */
+       u32 ddrc_phy_num; /* Number of DDRC which need saved/restored. */
+       u32 ddrc_phy_val[MX7_MAX_DDRC_NUM][2]; /* To save offset and value */
+} __aligned(8);
+
+static struct map_desc imx7_pm_io_desc[] __initdata = {
+       imx_map_entry(MX7D, AIPS1, MT_DEVICE),
+       imx_map_entry(MX7D, AIPS2, MT_DEVICE),
+       imx_map_entry(MX7D, AIPS3, MT_DEVICE),
+};
+
+static const char * const low_power_ocram_match[] __initconst = {
+       "fsl,lpm-sram",
+       NULL
+};
+
+static void imx7_console_save(unsigned int *regs)
+{
+       if (!console_base)
+               return;
+
+       regs[0] = readl_relaxed(console_base + UART_UCR1);
+       regs[1] = readl_relaxed(console_base + UART_UCR2);
+       regs[2] = readl_relaxed(console_base + UART_UCR3);
+       regs[3] = readl_relaxed(console_base + UART_UCR4);
+       regs[4] = readl_relaxed(console_base + UART_UFCR);
+       regs[5] = readl_relaxed(console_base + UART_UESC);
+       regs[6] = readl_relaxed(console_base + UART_UTIM);
+       regs[7] = readl_relaxed(console_base + UART_UBIR);
+       regs[8] = readl_relaxed(console_base + UART_UBMR);
+       regs[9] = readl_relaxed(console_base + UART_UTS);
+}
+
+static void imx7_console_restore(unsigned int *regs)
+{
+       if (!console_base)
+               return;
+
+       writel_relaxed(regs[4], console_base + UART_UFCR);
+       writel_relaxed(regs[5], console_base + UART_UESC);
+       writel_relaxed(regs[6], console_base + UART_UTIM);
+       writel_relaxed(regs[7], console_base + UART_UBIR);
+       writel_relaxed(regs[8], console_base + UART_UBMR);
+       writel_relaxed(regs[9], console_base + UART_UTS);
+       writel_relaxed(regs[0], console_base + UART_UCR1);
+       writel_relaxed(regs[1] | 0x1, console_base + UART_UCR2);
+       writel_relaxed(regs[2], console_base + UART_UCR3);
+       writel_relaxed(regs[3], console_base + UART_UCR4);
+}
+
+static int imx7_suspend_finish(unsigned long val)
+{
+       if (!imx7_suspend_in_ocram_fn) {
+               cpu_do_idle();
+       } else {
+               /*
+                * call low level suspend function in ocram,
+                * as we need to float DDR IO.
+                */
+               local_flush_tlb_all();
+               imx7_suspend_in_ocram_fn(suspend_ocram_base);
+       }
+
+       return 0;
+}
+
+static int imx7_pm_enter(suspend_state_t state)
+{
+       unsigned int console_saved_reg[10] = {0};
+
+       if (!iram_tlb_base_addr) {
+               pr_warn("No IRAM/OCRAM memory allocated for suspend/resume \
+                        code. Please ensure device tree has an entry for \
+                        fsl,lpm-sram.\n");
+               return -EINVAL;
+       }
+
+       switch (state) {
+       case PM_SUSPEND_STANDBY:
+               imx_anatop_pre_suspend();
+               imx_gpcv2_pre_suspend(false);
+
+               /* Zzz ... */
+               imx7_suspend_in_ocram_fn(suspend_ocram_base);
+
+               imx_anatop_post_resume();
+               imx_gpcv2_post_resume();
+               break;
+       case PM_SUSPEND_MEM:
+               imx_anatop_pre_suspend();
+               imx_gpcv2_pre_suspend(true);
+               if (imx_gpcv2_is_mf_mix_off()) {
+                       imx7_console_save(console_saved_reg);
+                       memcpy(ocram_saved_in_ddr, ocram_base, ocram_size);
+               }
+
+               /* Zzz ... */
+               cpu_suspend(0, imx7_suspend_finish);
+
+               if (imx_gpcv2_is_mf_mix_off()) {
+                       memcpy(ocram_base, ocram_saved_in_ddr, ocram_size);
+                       imx7_console_restore(console_saved_reg);
+               }
+               imx_anatop_post_resume();
+               imx_gpcv2_post_resume();
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int imx7_pm_valid(suspend_state_t state)
+{
+       return state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM;
+}
+
+static const struct platform_suspend_ops imx7_pm_ops = {
+       .enter = imx7_pm_enter,
+       .valid = imx7_pm_valid,
+};
+
+void __init imx7_pm_set_ccm_base(void __iomem *base)
+{
+       ccm_base = base;
+}
+
+static struct map_desc iram_tlb_io_desc __initdata = {
+       /* .virtual and .pfn are run-time assigned */
+       .length     = SZ_1M,
+       .type       = MT_MEMORY_RWX_NONCACHED,
+};
+
+static int __init imx7_dt_find_lpsram(unsigned long node, const char *uname,
+                                     int depth, void *data)
+{
+       unsigned long lpram_addr;
+       const __be32 *prop = of_get_flat_dt_prop(node, "reg", NULL);
+
+       if (of_flat_dt_match(node, low_power_ocram_match)) {
+               if (!prop)
+                       return -EINVAL;
+
+               lpram_addr = be32_to_cpup(prop);
+
+               /* We need to create a 1M page table entry. */
+               iram_tlb_io_desc.virtual = IMX_IO_P2V(lpram_addr & 0xFFF00000);
+               iram_tlb_io_desc.pfn = __phys_to_pfn(lpram_addr & 0xFFF00000);
+               iram_tlb_phys_addr = lpram_addr;
+               iram_tlb_base_addr = IMX_IO_P2V(lpram_addr);
+               iotable_init(&iram_tlb_io_desc, 1);
+       }
+
+       return 0;
+}
+
+void __init imx7_pm_map_io(void)
+{
+       unsigned long i, j;
+
+       iotable_init(imx7_pm_io_desc, ARRAY_SIZE(imx7_pm_io_desc));
+       /*
+        * Get the address of IRAM or OCRAM to be used by the low
+        * power code from the device tree.
+        */
+       WARN_ON(of_scan_flat_dt(imx7_dt_find_lpsram, NULL));
+
+       /* Return if no IRAM space is allocated for suspend/resume code. */
+       if (!iram_tlb_base_addr) {
+               pr_warn("No valid ocram available for suspend/resume!\n");
+               return;
+       }
+
+       /* Set all entries to 0. */
+       memset((void *)iram_tlb_base_addr, 0, MX7_IRAM_TLB_SIZE);
+
+       /*
+        * Make sure the IRAM virtual address has a mapping in the IRAM
+        * page table.
+        *
+        * Only use the top 12 bits [31-20] when storing the physical
+        * address in the page table as only these bits are required
+        * for 1M mapping.
+        */
+       j = ((iram_tlb_base_addr >> 20) << 2) / 4;
+       *((unsigned long *)iram_tlb_base_addr + j) =
+               (iram_tlb_phys_addr & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M;
+
+       /*
+        * Make sure the AIPS1 virtual address has a mapping in the
+        * IRAM page table.
+        */
+       for (i = 0; i < 4; i++) {
+               j = ((IMX_IO_P2V(MX7D_AIPS1_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4;
+               *((unsigned long *)iram_tlb_base_addr + j) =
+                       ((MX7D_AIPS1_BASE_ADDR + i * 0x100000) & 0xFFF00000) |
+                       TT_ATTRIB_NON_CACHEABLE_1M;
+       }
+
+       /*
+        * Make sure the AIPS2 virtual address has a mapping in the
+        * IRAM page table.
+        */
+       for (i = 0; i < 4; i++) {
+               j = ((IMX_IO_P2V(MX7D_AIPS2_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4;
+               *((unsigned long *)iram_tlb_base_addr + j) =
+                       ((MX7D_AIPS2_BASE_ADDR + i * 0x100000) & 0xFFF00000) |
+                       TT_ATTRIB_NON_CACHEABLE_1M;
+       }
+
+       /*
+        * Make sure the AIPS3 virtual address has a mapping
+        * in the IRAM page table.
+        */
+       for (i = 0; i < 4; i++) {
+               j = ((IMX_IO_P2V(MX7D_AIPS3_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4;
+               *((unsigned long *)iram_tlb_base_addr + j) =
+                       ((MX7D_AIPS3_BASE_ADDR + i * 0x100000) & 0xFFF00000) |
+                       TT_ATTRIB_NON_CACHEABLE_1M;
+       }
+}
+
+static int __init imx7_suspend_init(const struct imx7_pm_socdata *socdata)
+{
+       struct device_node *node;
+       struct imx7_cpu_pm_info *pm_info;
+       int i, ret = 0;
+       const u32 (*ddrc_offset_array)[2];
+       const u32 (*ddrc_phy_offset_array)[2];
+       unsigned long iram_paddr;
+
+       suspend_set_ops(&imx7_pm_ops);
+
+       if (!socdata) {
+               pr_warn("%s: invalid argument!\n", __func__);
+               return -EINVAL;
+       }
+
+       /*
+        * 16KB is allocated for IRAM TLB, but only up 8k is for kernel TLB,
+        * The lower 8K is not used, so use the lower 8K for IRAM code and
+        * pm_info.
+        *
+        */
+       iram_paddr = iram_tlb_phys_addr + MX7_SUSPEND_IRAM_ADDR_OFFSET;
+
+       /* Make sure iram_paddr is 8 byte aligned. */
+       if ((uintptr_t)(iram_paddr) & (FNCPY_ALIGN - 1))
+               iram_paddr += FNCPY_ALIGN - iram_paddr % (FNCPY_ALIGN);
+
+       /* Get the virtual address of the suspend code. */
+       suspend_ocram_base = (void *)IMX_IO_P2V(iram_paddr);
+
+       pm_info = suspend_ocram_base;
+       /* pbase points to iram_paddr. */
+       pm_info->pbase = iram_paddr;
+       pm_info->resume_addr = virt_to_phys(ca7_cpu_resume);
+       pm_info->pm_info_size = sizeof(*pm_info);
+
+       /*
+        * ccm physical address is not used by asm code currently,
+        * so get ccm virtual address directly, as we already have
+        * it from ccm driver.
+        */
+       pm_info->ccm_base.pbase = MX7D_CCM_BASE_ADDR;
+       pm_info->ccm_base.vbase = (void __iomem *)
+                                  IMX_IO_P2V(MX7D_CCM_BASE_ADDR);
+
+       pm_info->ddrc_base.pbase = MX7D_DDRC_BASE_ADDR;
+       pm_info->ddrc_base.vbase = (void __iomem *)
+                                   IMX_IO_P2V(MX7D_DDRC_BASE_ADDR);
+
+       pm_info->ddrc_phy_base.pbase = MX7D_DDRC_PHY_BASE_ADDR;
+       pm_info->ddrc_phy_base.vbase = (void __iomem *)
+                                   IMX_IO_P2V(MX7D_DDRC_PHY_BASE_ADDR);
+
+       pm_info->src_base.pbase = MX7D_SRC_BASE_ADDR;
+       pm_info->src_base.vbase = (void __iomem *)
+                                  IMX_IO_P2V(MX7D_SRC_BASE_ADDR);
+
+       pm_info->iomuxc_gpr_base.pbase = MX7D_IOMUXC_GPR_BASE_ADDR;
+       pm_info->iomuxc_gpr_base.vbase = (void __iomem *)
+                                     IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR);
+
+       pm_info->gpc_base.pbase = MX7D_GPC_BASE_ADDR;
+       pm_info->gpc_base.vbase = (void __iomem *)
+                                  IMX_IO_P2V(MX7D_GPC_BASE_ADDR);
+
+       pm_info->anatop_base.pbase = MX7D_ANATOP_BASE_ADDR;
+       pm_info->anatop_base.vbase = (void __iomem *)
+                                 IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR);
+
+       pm_info->ddrc_num = socdata->ddrc_num;
+       ddrc_offset_array = socdata->ddrc_offset;
+       pm_info->ddrc_phy_num = socdata->ddrc_phy_num;
+       ddrc_phy_offset_array = socdata->ddrc_phy_offset;
+
+       /* initialize DDRC settings */
+       for (i = 0; i < pm_info->ddrc_num; i++) {
+               pm_info->ddrc_val[i][0] = ddrc_offset_array[i][0];
+               if (ddrc_offset_array[i][1] == READ_DATA_FROM_HARDWARE)
+                       pm_info->ddrc_val[i][1] =
+                               readl_relaxed(pm_info->ddrc_base.vbase +
+                               ddrc_offset_array[i][0]);
+               else
+                       pm_info->ddrc_val[i][1] = ddrc_offset_array[i][1];
+       }
+
+       /* initialize DDRC PHY settings */
+       for (i = 0; i < pm_info->ddrc_phy_num; i++) {
+               pm_info->ddrc_phy_val[i][0] =
+                       ddrc_phy_offset_array[i][0];
+               if (ddrc_phy_offset_array[i][1] == READ_DATA_FROM_HARDWARE)
+                       pm_info->ddrc_phy_val[i][1] =
+                               readl_relaxed(pm_info->ddrc_phy_base.vbase +
+                               ddrc_phy_offset_array[i][0]);
+               else
+                       pm_info->ddrc_phy_val[i][1] =
+                               ddrc_phy_offset_array[i][1];
+       }
+
+       imx7_suspend_in_ocram_fn = fncpy(
+               suspend_ocram_base + sizeof(*pm_info),
+               &imx7_suspend,
+               MX7_SUSPEND_OCRAM_SIZE - sizeof(*pm_info));
+
+       goto put_node;
+
+put_node:
+       of_node_put(node);
+
+       return ret;
+}
+
+static void __init imx7_pm_common_init(const struct imx7_pm_socdata
+                                       *socdata)
+{
+       int ret;
+       struct regmap *gpr;
+
+       if (IS_ENABLED(CONFIG_SUSPEND)) {
+               ret = imx7_suspend_init(socdata);
+               if (ret)
+                       pr_warn("%s: No DDR LPM support with suspend %d!\n",
+                               __func__, ret);
+       }
+
+       /*
+        * Force IOMUXC irq pending, so that the interrupt to GPC can be
+        * used to deassert dsm_request signal when the signal gets
+        * asserted unexpectedly.
+        */
+       gpr = syscon_regmap_lookup_by_compatible("fsl,imx7d-iomuxc-gpr");
+       if (!IS_ERR(gpr))
+               regmap_update_bits(gpr, IOMUXC_GPR1, IMX7D_GPR1_IRQ_MASK,
+                       IMX7D_GPR1_IRQ_MASK);
+}
+
+void __init imx7d_pm_init(void)
+{
+       struct device_node *np;
+       struct resource res;
+
+       if (imx_ddrc_get_ddr_type() == IMX_DDR_TYPE_LPDDR3)
+               imx7_pm_common_init(&imx7d_pm_data_lpddr3);
+       else if (imx_ddrc_get_ddr_type() == IMX_DDR_TYPE_DDR3)
+               imx7_pm_common_init(&imx7d_pm_data_ddr3);
+
+       np = of_find_compatible_node(NULL, NULL, "fsl,mega-fast-sram");
+       ocram_base = of_iomap(np, 0);
+       WARN_ON(!ocram_base);
+       WARN_ON(of_address_to_resource(np, 0, &res));
+       ocram_size = resource_size(&res);
+       ocram_saved_in_ddr = kzalloc(ocram_size, GFP_KERNEL);
+       WARN_ON(!ocram_saved_in_ddr);
+
+       np = of_find_node_by_path(
+               "/soc/aips-bus@30800000/spba-bus@30800000/serial@30860000");
+       if (np)
+               console_base = of_iomap(np, 0);
+}
diff --git a/arch/arm/mach-imx/suspend-imx7.S b/arch/arm/mach-imx/suspend-imx7.S
new file mode 100644 (file)
index 0000000..3cc0dc5
--- /dev/null
@@ -0,0 +1,567 @@
+/*
+ * 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_L2_P_OFFSET                        0x4c
+#define PM_INFO_MX7_L2_V_OFFSET                        0x50
+#define PM_INFO_MX7_ANATOP_P_OFFSET            0x54
+#define PM_INFO_MX7_ANATOP_V_OFFSET            0x58
+#define PM_INFO_MX7_TTBR1_V_OFFSET             0x5c
+#define PM_INFO_DDRC_REG_NUM_OFFSET            0x60
+#define PM_INFO_DDRC_REG_OFFSET                        0x64
+#define PM_INFO_DDRC_VALUE_OFFSET              0x68
+#define PM_INFO_DDRC_PHY_REG_NUM_OFFSET                0x164
+#define PM_INFO_DDRC_PHY_REG_OFFSET            0x168
+#define PM_INFO_DDRC_PHY_VALUE_OFFSET          0x16c
+
+#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 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
+
+       .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]
+
+       /* 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]
+       /* turn off ddr power */
+       ldr     r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET]
+       ldr     r7, =(0x1 << 29)
+       str     r7, [r11, #ANADIG_SNVS_MISC_CTRL]
+
+       .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]
+
+       ldr     r6, =50
+       wait_delay
+
+       ldr     r7, =0x0
+       str     r7, [r1, #ANADIG_SNVS_MISC_CTRL]
+
+       /* 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]
+
+       ldr     r6, [r0, #PM_INFO_DDRC_REG_NUM_OFFSET]
+       ldr     r7, =PM_INFO_DDRC_REG_OFFSET
+       add     r7, r7, r0
+9:
+       ldr     r8, [r7], #0x4
+       ldr     r9, [r7], #0x4
+       str     r9, [r3, r8]
+       subs    r6, r6, #0x1
+       bne     9b
+       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, =(0x1 << 30)
+       str     r7, [r1, #ANADIG_SNVS_MISC_CTRL]
+
+       /* 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
+
+10:
+       ldr     r8, [r7], #0x4
+       ldr     r9, [r7], #0x4
+       str     r9, [r4, r8]
+       subs    r6, r6, #0x1
+       bne     10b
+
+       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 */
+11:
+       ldr     r7, [r3, #DDRC_STAT]
+       and     r7, r7, #0x3
+       cmp     r7, #0x3
+       bne     11b
+       ldr     r7, =0x0
+       str     r7, [r3, #DDRC_SWCTL]
+       ldr     r7, =0x1
+       str     r7, [r3, #DDRC_DFIMISC]
+       ldr     r7, =0x1
+       str     r7, [r3, #DDRC_SWCTL]
+12:
+       ldr     r7, [r3, #DDRC_SWSTAT]
+       and     r7, r7, #0x1
+       cmp     r7, #0x1
+       bne     12b
+13:
+       ldr     r7, [r3, #DDRC_STAT]
+       and     r7, r7, #0x20
+       cmp     r7, #0x20
+       bne     13b
+
+       /* let DDR out of self-refresh */
+       ldr     r7, =0x0
+       str     r7, [r3, #DDRC_PWRCTL]
+14:
+       ldr     r7, [r3, #DDRC_STAT]
+       and     r7, r7, #0x30
+       cmp     r7, #0x0
+       bne     14b
+
+15:
+       ldr     r7, [r3, #DDRC_STAT]
+       and     r7, r7, #0x3
+       cmp     r7, #0x1
+       bne     15b
+
+       /* enable port */
+       ldr     r7, =0x1
+       str     r7, [r3, #DDRC_PCTRL_0]
+
+       .endm
+
+ENTRY(imx7_suspend)
+       push    {r4-r12}
+
+       /* 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
+       b       ddr_retention_enter_out
+ddr_only_self_refresh:
+       ddrc_enter_self_refresh
+ddr_retention_enter_out:
+
+       /* 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
+
+       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)