@@ -552,6 +552,7 @@ config SOC_IMX7D
bool "i.MX7 Dual support"
select PINCTRL_IMX7D
select ARM_GIC
+ select IMX_GPCV2
select HAVE_IMX_ANATOP
select HAVE_IMX_MMDC
help
@@ -87,6 +87,8 @@ 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_IMX_GPCV2) += suspend-imx7.o pm-imx7.o
obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o
obj-$(CONFIG_SOC_IMX53) += suspend-imx53.o
endif
new file mode 100644
@@ -0,0 +1,765 @@
+
+/*
+ * Copyright (C) 2015 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <linux/suspend.h>
+#include <asm/suspend.h>
+#include <asm/fncpy.h>
+
+#include <soc/imx/gpcv2.h>
+
+extern struct imx_gpcv2_irq *gpcv2_irq_instance;
+static struct imx_gpcv2 *gpcv2_instance;
+
+static void imx_gpcv2_lpm_clear_slots(struct imx_gpcv2 *gpc)
+{
+ struct imx_gpcv2_irq *cd = gpc->irqchip;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&cd->lock, flags);
+ for (i = 0; i < MAX_SLOT_NUMBER; i++)
+ writel_relaxed(0x0, cd->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,
+ cd->gpc_base + GPC_PGC_ACK_SEL_A7);
+
+ spin_unlock_irqrestore(&cd->lock, flags);
+}
+
+static void imx_gpcv2_lpm_enable_core(struct imx_gpcv2 *gpc,
+ bool enable, u32 offset)
+{
+ struct imx_gpcv2_irq *cd = gpc->irqchip;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cd->lock, flags);
+ writel_relaxed(enable, cd->gpc_base + offset);
+ spin_unlock_irqrestore(&cd->lock, flags);
+}
+
+static void imx_gpcv2_lpm_slot_setup(struct imx_gpcv2 *gpc,
+ u32 index, enum gpcv2_slot m_core, bool mode, bool ack)
+{
+ struct imx_gpcv2_irq *cd = gpc->irqchip;
+ unsigned long flags;
+ u32 val;
+
+ if (index >= MAX_SLOT_NUMBER)
+ pr_err("Invalid slot index!\n");
+
+ spin_lock_irqsave(&cd->lock, flags);
+ /* set slot */
+ writel_relaxed((mode + 1) << (m_core * 2), cd->gpc_base +
+ GPC_SLOT0_CFG + index * 4);
+
+ if (ack) {
+ /* set ack */
+ val = readl_relaxed(cd->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, cd->gpc_base + GPC_PGC_ACK_SEL_A7);
+ }
+
+ spin_unlock_irqrestore(&cd->lock, flags);
+}
+
+static void imx_gpcv2_lpm_env_setup(struct imx_gpcv2 *gpc)
+{
+ struct imx_gpcv2_suspend *pm = gpc->pm;
+
+ /* PLL and PFDs overwrite set */
+ regmap_write(pm->anatop, ANADIG_ARM_PLL + REG_SET, 1 << 20);
+ regmap_write(pm->anatop, ANADIG_DDR_PLL + REG_SET, 1 << 19);
+ regmap_write(pm->anatop, ANADIG_SYS_PLL + REG_SET, 0x1ff << 17);
+ regmap_write(pm->anatop, ANADIG_ENET_PLL + REG_SET, 1 << 13);
+ regmap_write(pm->anatop, ANADIG_AUDIO_PLL + REG_SET, 1 << 24);
+ regmap_write(pm->anatop, ANADIG_VIDEO_PLL + REG_SET, 1 << 24);
+}
+
+static void imx_gpcv2_lpm_env_clean(struct imx_gpcv2 *gpc)
+{
+ struct imx_gpcv2_suspend *pm = gpc->pm;
+
+ /* PLL and PFDs overwrite clear */
+ regmap_write(pm->anatop, ANADIG_ARM_PLL + REG_CLR, 1 << 20);
+ regmap_write(pm->anatop, ANADIG_DDR_PLL + REG_CLR, 1 << 19);
+ regmap_write(pm->anatop, ANADIG_SYS_PLL + REG_CLR, 0x1ff << 17);
+ regmap_write(pm->anatop, ANADIG_ENET_PLL + REG_CLR, 1 << 13);
+ regmap_write(pm->anatop, ANADIG_AUDIO_PLL + REG_CLR, 1 << 24);
+ regmap_write(pm->anatop, ANADIG_VIDEO_PLL + REG_CLR, 1 << 24);
+}
+
+static void imx_gpcv2_lpm_set_mode(struct imx_gpcv2 *gpc,
+ enum gpcv2_mode mode)
+{
+ struct imx_gpcv2_irq *cd = gpc->irqchip;
+ unsigned long flags;
+ u32 val1, val2;
+
+ spin_lock_irqsave(&cd->lock, flags);
+
+ val1 = readl_relaxed(cd->gpc_base + GPC_LPCR_A7_BSC);
+ val2 = readl_relaxed(cd->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);
+ /*
+ * GPCv2: 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:
+ break;
+ case WAIT_UNCLOCKED:
+ val1 |= A7_LPM_WAIT << BP_LPCR_A7_BSC_LPM0;
+ val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
+ 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;
+ 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;
+ break;
+ default:
+ return;
+ }
+ writel_relaxed(val1, cd->gpc_base + GPC_LPCR_A7_BSC);
+ writel_relaxed(val2, cd->gpc_base + GPC_SLPCR);
+
+ spin_unlock_irqrestore(&cd->lock, flags);
+}
+
+
+static void imx_gpcv2_lpm_cpu_power_gate(struct imx_gpcv2 *gpc,
+ u32 cpu, bool pdn)
+{
+ struct imx_gpcv2_irq *cd = gpc->irqchip;
+ unsigned long flags;
+ u32 val;
+
+ const u32 val_pdn[2] = {
+ BM_LPCR_A7_AD_EN_C0_PDN | BM_LPCR_A7_AD_EN_C0_PUP,
+ BM_LPCR_A7_AD_EN_C1_PDN | BM_LPCR_A7_AD_EN_C1_PUP,
+ };
+
+ spin_lock_irqsave(&cd->lock, flags);
+
+ val = readl_relaxed(cd->gpc_base + GPC_LPCR_A7_AD);
+ if (pdn)
+ val |= val_pdn[cpu];
+ else
+ val &= ~val_pdn[cpu];
+
+ writel_relaxed(val, cd->gpc_base + GPC_LPCR_A7_AD);
+ spin_unlock_irqrestore(&cd->lock, flags);
+}
+
+static void imx_lpm_plat_power_gate(struct imx_gpcv2 *gpc, bool pdn)
+{
+ struct imx_gpcv2_irq *cd = gpc->irqchip;
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&cd->lock, flags);
+ val = readl_relaxed(cd->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, cd->gpc_base + GPC_LPCR_A7_AD);
+ spin_unlock_irqrestore(&cd->lock, flags);
+}
+
+static void imx_gpcv2_lpm_standby(struct imx_gpcv2 *gpc)
+{
+ struct imx_gpcv2_suspend *pm = gpc->pm;
+
+ pm->lpm_env_setup(gpc);
+ /* pm->set_mode(gpc, STOP_POWER_OFF); */
+ pm->set_mode(gpc, WAIT_UNCLOCKED);
+
+ pr_debug("[GPCv2] %s %d\r\n", __func__, __LINE__);
+ /* Zzz ... */
+ cpu_do_idle();
+
+ pm->set_mode(gpc, WAIT_CLOCKED);
+ pm->lpm_env_clean(gpc);
+}
+
+
+
+static int gpcv2_suspend_finish(unsigned long val)
+{
+ struct imx_gpcv2_suspend *pm = (struct imx_gpcv2_suspend *)val;
+
+ if (!pm->suspend_fn_in_ocram) {
+ cpu_do_idle();
+ } else {
+ /*
+ * call low level suspend function in ocram,
+ * as we need to float DDR IO.
+ */
+ local_flush_tlb_all();
+ pm->suspend_fn_in_ocram(pm->ocram_vbase);
+ }
+
+ return 0;
+}
+static void imx_gpcv2_lpm_suspend(struct imx_gpcv2 *gpc)
+{
+ struct imx_gpcv2_suspend *pm = gpc->pm;
+ struct imx_gpcv2_irq *cd = gpc->irqchip;
+ int i = 0;
+
+ pm->lpm_env_setup(gpc);
+ pm->set_mode(gpc, STOP_POWER_OFF);
+
+ /* enable core0 power down/up with low power mode */
+ pm->lpm_cpu_power_gate(gpc, 0, true);
+
+ /* enable plat power down with low power mode */
+ pm->lpm_plat_power_gate(gpc, 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
+ */
+ pm->set_slot(gpc, 0, CORE0_A7, false, false);
+ pm->set_slot(gpc, 2, SCU_A7, false, true);
+
+ for (i = 0; i < IMR_NUM; i++) {
+ if ((~cd->wakeup_sources[i] & pm->mfmix_mask[i]) != 0)
+ break;
+
+ pm->set_slot(gpc, 1, FAST_MEGA_MIX, false, false);
+ pm->set_slot(gpc, 5, FAST_MEGA_MIX, true, false);
+ pm->lpm_enable_core(gpc, true, GPC_PGC_FM);
+ break;
+ }
+
+ pm->set_slot(gpc, 6, SCU_A7, true, false);
+ pm->set_slot(gpc, 7, CORE0_A7, true, true);
+
+ /* enable core0, scu */
+ pm->lpm_enable_core(gpc, true, GPC_PGC_C0);
+ pm->lpm_enable_core(gpc, true, GPC_PGC_SCU);
+
+ /* Suspend to MEM has not been implemented yet */
+ cpu_suspend((unsigned long)pm, gpcv2_suspend_finish);
+
+ pm->lpm_env_clean(gpc);
+ pm->set_mode(gpc, WAIT_CLOCKED);
+ pm->lpm_cpu_power_gate(gpc, 0, false);
+ pm->lpm_plat_power_gate(gpc, false);
+
+ pm->lpm_enable_core(gpc, false, GPC_PGC_C0);
+ pm->lpm_enable_core(gpc, false, GPC_PGC_SCU);
+ pm->lpm_enable_core(gpc, false, GPC_PGC_FM);
+ pm->clear_slots(gpc);
+}
+
+static int imx_gpcv2_pm_enter(suspend_state_t state)
+{
+ struct imx_gpcv2_suspend *pm;
+
+ BUG_ON(!gpcv2_instance);
+ pm = gpcv2_instance->pm;
+
+ switch (state) {
+ case PM_SUSPEND_STANDBY:
+ pm->standby(gpcv2_instance);
+ break;
+
+ case PM_SUSPEND_MEM:
+ pm->suspend(gpcv2_instance);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int imx_gpcv2_pm_valid(suspend_state_t state)
+{
+ return state == PM_SUSPEND_MEM || state == PM_SUSPEND_STANDBY;
+}
+
+static const struct platform_suspend_ops imx_gpcv2_pm_ops = {
+ .enter = imx_gpcv2_pm_enter,
+ .valid = imx_gpcv2_pm_valid,
+};
+
+
+#define MX7_MAX_DDRC_NUM 32
+#define MX7_MAX_DDRC_PHY_NUM 16
+
+#define READ_DATA_FROM_HARDWARE 0
+#define MX7_SUSPEND_OCRAM_SIZE 0x1000
+
+struct imx7_pm_base {
+ phys_addr_t pbase;
+ void __iomem *vbase;
+};
+
+struct imx7_pm_socdata {
+ u32 ddr_type;
+ const char *ddrc_compat;
+ const char *ddrc_phy_compat;
+ const char *src_compat;
+ const char *iomuxc_gpr_compat;
+ const char *ccm_compat;
+ const char *gpc_compat;
+ const char *anatop_compat;
+ const u32 ddrc_num;
+ const u32 (*ddrc_offset)[2];
+ const u32 ddrc_phy_num;
+ const u32 (*ddrc_phy_offset)[2];
+};
+
+/*
+ * 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;
+
+ /* The physical address of pm_info. */
+ phys_addr_t pbase;
+
+ /* The physical resume address for asm code */
+ phys_addr_t resume_addr;
+ u32 ddr_type;
+
+ u32 pm_info_size;
+ 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;
+
+ /* Number of DDRC which need saved/restored. */
+ u32 ddrc_num;
+
+ /* To save offset and value */
+ u32 ddrc_val[MX7_MAX_DDRC_NUM][2];
+
+ /* Number of DDRC which need saved/restored. */
+ u32 ddrc_phy_num;
+
+ /* To save offset and value */
+ u32 ddrc_phy_val[MX7_MAX_DDRC_NUM][2];
+} __aligned(8);
+
+
+static int __init imx_get_base_from_node(struct device_node *node,
+ struct imx7_pm_base *base)
+{
+ struct resource res;
+ int ret = 0;
+
+ ret = of_address_to_resource(node, 0, &res);
+ if (ret)
+ goto put_node;
+
+ base->pbase = res.start;
+ base->vbase = ioremap(res.start, resource_size(&res));
+ if (!base->vbase) {
+ iounmap(base->vbase);
+ ret = -ENOMEM;
+ }
+put_node:
+ of_node_put(node);
+
+ return ret;
+}
+
+static int __init imx_get_base_from_dt(struct imx7_pm_base *base,
+ const char *compat)
+{
+ struct device_node *node;
+ struct resource res;
+ int ret = 0;
+
+ node = of_find_compatible_node(NULL, NULL, compat);
+ if (!node) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ ret = of_address_to_resource(node, 0, &res);
+ if (ret)
+ goto put_node;
+
+ base->pbase = res.start;
+ base->vbase = ioremap(res.start, resource_size(&res));
+ if (!base->vbase)
+ ret = -ENOMEM;
+
+put_node:
+ of_node_put(node);
+out:
+ return ret;
+}
+
+static int __init imx_get_exec_base_from_dt(struct imx7_pm_base *base,
+ const char *compat)
+{
+ struct device_node *node;
+ struct resource res;
+ int ret = 0;
+
+ node = of_find_compatible_node(NULL, NULL, compat);
+ if (!node) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ ret = of_address_to_resource(node, 0, &res);
+ if (ret)
+ goto put_node;
+
+ base->pbase = res.start;
+ base->vbase = __arm_ioremap_exec(res.start, resource_size(&res), false);
+
+ if (!base->vbase)
+ ret = -ENOMEM;
+
+put_node:
+ of_node_put(node);
+out:
+ return ret;
+}
+
+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_ddr3 __initconst = {
+ .ddrc_compat = "fsl,imx7d-ddrc",
+ .ddrc_phy_compat = "fsl,imx7d-ddrc-phy",
+ .ccm_compat = "fsl,imx7d-ccm",
+ .src_compat = "fsl,imx7d-src",
+ .iomuxc_gpr_compat = "fsl,imx7d-iomuxc",
+ .gpc_compat = "fsl,imx7d-gpc",
+ .anatop_compat = "fsl,imx7d-anatop",
+ .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,
+};
+
+
+static int __init imx_gpcv2_suspend_init(struct imx_gpcv2_suspend *pm,
+ const struct imx7_pm_socdata *socdata)
+{
+ struct imx7_pm_base aips_base[3] = { {0, 0}, {0, 0}, {0, 0} };
+ struct imx7_pm_base sram_base = {0, 0};
+ struct imx7_cpu_pm_info *pm_info;
+ struct device_node *node;
+ int i, ret = 0;
+
+ const u32 (*ddrc_offset_array)[2];
+ const u32 (*ddrc_phy_offset_array)[2];
+
+ if (!socdata || !pm) {
+ pr_warn("%s: invalid argument!\n", __func__);
+ return -EINVAL;
+ }
+
+ node = NULL;
+ for (i = 0; i < 3; i++) {
+ node = of_find_compatible_node(node, NULL, "fsl,aips-bus");
+ if (!node) {
+ pr_warn("%s: failed to find aips %d node!\n",
+ __func__, i);
+ break;
+ }
+ ret = imx_get_base_from_node(node, &aips_base[i]);
+ if (ret) {
+ pr_warn("%s: failed to get aips[%d] base %d!\n",
+ __func__, i, ret);
+ }
+ }
+
+ ret = imx_get_exec_base_from_dt(&sram_base, "fsl,lpm-sram");
+ if (ret) {
+ pr_warn("%s: failed to get lpm-sram base %d!\n",
+ __func__, ret);
+ goto lpm_sram_map_failed;
+ }
+
+ pm_info = sram_base.vbase;
+ pm_info->pbase = sram_base.pbase;
+ pm_info->resume_addr = virt_to_phys(ca7_cpu_resume);
+ pm_info->pm_info_size = sizeof(*pm_info);
+
+ ret = imx_get_base_from_dt(&pm_info->ccm_base, socdata->ccm_compat);
+ if (ret) {
+ pr_warn("%s: failed to get ccm base %d!\n", __func__, ret);
+ goto ccm_map_failed;
+ }
+
+
+ ret = imx_get_base_from_dt(&pm_info->ddrc_base, socdata->ddrc_compat);
+ if (ret) {
+ pr_warn("%s: failed to get ddrc base %d!\n", __func__, ret);
+ goto ddrc_map_failed;
+ }
+
+ ret = imx_get_base_from_dt(&pm_info->ddrc_phy_base,
+ socdata->ddrc_phy_compat);
+ if (ret) {
+ pr_warn("%s: failed to get ddrc_phy base %d!\n", __func__, ret);
+ goto ddrc_phy_map_failed;
+ }
+
+
+ ret = imx_get_base_from_dt(&pm_info->src_base, socdata->src_compat);
+ if (ret) {
+ pr_warn("%s: failed to get src base %d!\n", __func__, ret);
+ goto src_map_failed;
+ }
+
+ ret = imx_get_base_from_dt(&pm_info->iomuxc_gpr_base,
+ socdata->iomuxc_gpr_compat);
+ if (ret) {
+ pr_warn("%s: failed to get iomuxc_gpr base %d!\n",
+ __func__, ret);
+ goto iomuxc_gpr_map_failed;
+ }
+
+ ret = imx_get_base_from_dt(&pm_info->gpc_base, socdata->gpc_compat);
+ if (ret) {
+ pr_warn("%s: failed to get gpc base %d!\n", __func__, ret);
+ goto gpc_map_failed;
+ }
+
+ ret = imx_get_base_from_dt(&pm_info->anatop_base,
+ socdata->anatop_compat);
+ if (ret) {
+ pr_warn("%s: failed to get anatop base %d!\n", __func__, ret);
+ goto anatop_map_failed;
+ }
+
+ 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];
+ }
+
+ pm->suspend_fn_in_ocram = fncpy(
+ sram_base.vbase + sizeof(*pm_info),
+ &imx7_suspend,
+ MX7_SUSPEND_OCRAM_SIZE - sizeof(*pm_info));
+ pm->ocram_vbase = sram_base.vbase;
+
+ goto put_node;
+
+anatop_map_failed:
+ iounmap(pm_info->anatop_base.vbase);
+gpc_map_failed:
+ iounmap(pm_info->gpc_base.vbase);
+iomuxc_gpr_map_failed:
+ iounmap(pm_info->iomuxc_gpr_base.vbase);
+src_map_failed:
+ iounmap(pm_info->src_base.vbase);
+ddrc_phy_map_failed:
+ iounmap(pm_info->ddrc_phy_base.vbase);
+ddrc_map_failed:
+ iounmap(pm_info->ddrc_base.vbase);
+ccm_map_failed:
+ iounmap(pm_info->ccm_base.vbase);
+lpm_sram_map_failed:
+ iounmap(sram_base.vbase);
+put_node:
+ of_node_put(node);
+
+ return ret;
+}
+
+static int __init imx_gpcv2_pm_init(void)
+{
+ struct imx_gpcv2_suspend *pm;
+ struct imx_gpcv2_irq *cd;
+ struct imx_gpcv2 *gpc;
+
+ pm = kzalloc(sizeof(struct imx_gpcv2_suspend), GFP_KERNEL);
+ gpc = kzalloc(sizeof(struct imx_gpcv2), GFP_KERNEL);
+ cd = gpcv2_irq_instance;
+
+ if (!cd || !pm || !gpc) {
+ pr_debug("[GPCv2] %s init failed\r\n", __func__);
+ return 0;
+ }
+
+
+ imx_gpcv2_suspend_init(pm, &imx7d_pm_data_ddr3);
+
+ pm->lpm_env_setup = imx_gpcv2_lpm_env_setup;
+ pm->lpm_env_clean = imx_gpcv2_lpm_env_clean;
+
+ pm->lpm_cpu_power_gate = imx_gpcv2_lpm_cpu_power_gate;
+ pm->lpm_plat_power_gate = imx_lpm_plat_power_gate;
+ pm->lpm_enable_core = imx_gpcv2_lpm_enable_core;
+ pm->set_mode = imx_gpcv2_lpm_set_mode;
+
+ pm->clear_slots = imx_gpcv2_lpm_clear_slots;
+ pm->set_slot = imx_gpcv2_lpm_slot_setup;
+
+ pm->standby = imx_gpcv2_lpm_standby;
+ pm->suspend = imx_gpcv2_lpm_suspend;
+
+ pr_debug("[GPCv2] %s \r\n", __func__);
+ pm->anatop = syscon_regmap_lookup_by_compatible("fsl,imx6q-anatop");
+ WARN_ON(!pm->anatop);
+ pm->imx_src = syscon_regmap_lookup_by_compatible("fsl,imx7d-src");
+ WARN_ON(!pm->imx_src);
+
+ /*
+ * Due to hardware design failure, need to make sure GPR
+ * interrupt(#32) is unmasked during RUN mode to avoid entering
+ * DSM by mistake.
+ */
+ writel_relaxed(~0x1, cd->gpc_base + cd->cpu2wakeup);
+
+ /* Mask the wakeup sources in M/F power domain */
+ pm->mfmix_mask[0] = 0x54010000;
+ pm->mfmix_mask[1] = 0xc00;
+ pm->mfmix_mask[2] = 0x0;
+ pm->mfmix_mask[3] = 0x400010;
+
+ gpc->pm = pm;
+ gpc->irqchip = cd;
+ gpcv2_instance = gpc;
+
+ suspend_set_ops(&imx_gpcv2_pm_ops);
+
+ return 0;
+}
+
+device_initcall(imx_gpcv2_pm_init);
+
new file mode 100644
@@ -0,0 +1,529 @@
+/*
+ * 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_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 enable_l1_dcache
+
+ /* Enable L1 data cache. */
+ mrc p15, 0, r6, c1, c0, 0
+ orr r6, r6, #0x4
+ mcr p15, 0, r6, c1, c0, 0
+
+ dsb
+ isb
+
+ .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]
+
+ cmp r5, #0x0
+ beq 101f
+ ldr r7, =0x170
+ orr r7, r7, #0x8
+ str r7, [r11, #0x20]
+
+101:
+ 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}
+
+ /*
+ * 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
+
+ /*
+ * make sure TLB contain the addr we want,
+ * as we will access them after DDR is in
+ * self-refresh mode.
+ */
+ ldr r6, [r0, #PM_INFO_MX7_IOMUXC_GPR_V_OFFSET]
+ ldr r7, [r0, #0x0]
+ ldr r6, [r0, #PM_INFO_MX7_CCM_V_OFFSET]
+ add r6, #0x4000
+ ldr r7, [r6]
+ ldr r6, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET]
+ ldr r7, [r6, #0x0]
+ ldr r6, [r0, #PM_INFO_MX7_SRC_V_OFFSET]
+ ldr r7, [r6, #0x0]
+ add r6, #0x1000
+ ldr r7, [r6]
+ ldr r6, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
+ ldr r7, [r6, #0x0]
+ ldr r7, [r6, #0x490]
+ ldr r6, [r0, #PM_INFO_MX7_DDRC_PHY_V_OFFSET]
+ ldr r7, [r6, #0x0]
+
+ 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:
+
+ ldr r11, [r0, #PM_INFO_MX7_IOMUXC_GPR_V_OFFSET]
+ ldr r7, =0x170
+ orr r7, r7, #0x8
+ str r7, [r11, #0x20]
+
+ /* clear core0's entry and parameter */
+ ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET]
+ mov r7, #0x0
+ str r7, [r11, #MX7_SRC_GPR1]
+ str r7, [r11, #MX7_SRC_GPR2]
+
+ enable_l1_dcache
+
+ 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)