[PATCH v8 2/2] ARM: imx: support suspend states on imx7D
Shenwei Wang
Shenwei.Wang at freescale.com
Fri Aug 21 06:45:28 PDT 2015
Ping.
Shenwei
> -----Original Message-----
> From: linux-kernel-owner at vger.kernel.org
> [mailto:linux-kernel-owner at vger.kernel.org] On Behalf Of Shenwei Wang
> Sent: 2015年7月31日 16:34
> To: shawn.guo at linaro.org; tglx at linutronix.de; jason at lakedaemon.net
> Cc: linux-arm-kernel at lists.infradead.org; linux-kernel at vger.kernel.org; Huang
> Yongcai-B20788
> Subject: [PATCH v8 2/2] ARM: imx: support suspend states on imx7D
>
> IMX7D contains a new version of GPC IP block (GPCv2). It has two major functions:
> power management and wakeup source management.
>
> GPCv2 provides low power mode control for Cortex-A7 and Cortex-M4 domains.
> And it can support WAIT, STOP, and DSM(Deep Sleep Mode) modes.
> After configuring the GPCv2 module, the platform can enter into a selected mode
> either automatically triggered by ARM WFI instruction or manually by software.
> The system will exit the low power states by the predefined wakeup sources
> which are managed by the gpcv2 irqchip driver.
>
> This patch adds a new suspend driver to manage the power states on IMX7D.
> It currently supports "SUSPEND_STANDBY" and "SUSPEND_MEM" states.
>
> Signed-off-by: Shenwei Wang <shenwei.wang at freescale.com>
> Signed-off-by: Anson Huang <b20788 at freescale.com>
> ---
> arch/arm/mach-imx/Kconfig | 1 +
> arch/arm/mach-imx/Makefile | 2 +
> arch/arm/mach-imx/common.h | 4 +
> arch/arm/mach-imx/pm-imx7.c | 917
> +++++++++++++++++++++++++++++++++++++++
> arch/arm/mach-imx/suspend-imx7.S | 529 ++++++++++++++++++++++
> 5 files changed, 1453 insertions(+)
> create mode 100644 arch/arm/mach-imx/pm-imx7.c create mode 100644
> arch/arm/mach-imx/suspend-imx7.S
>
> diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index
> 8ceda28..54f8553 100644
> --- a/arch/arm/mach-imx/Kconfig
> +++ b/arch/arm/mach-imx/Kconfig
> @@ -562,6 +562,7 @@ config SOC_IMX7D
> select ARM_GIC
> select HAVE_IMX_ANATOP
> select HAVE_IMX_MMDC
> + select IMX_GPCV2
> help
> This enables support for Freescale i.MX7 Dual processor.
>
> diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index
> fb689d8..60f13fd 100644
> --- a/arch/arm/mach-imx/Makefile
> +++ b/arch/arm/mach-imx/Makefile
> @@ -88,6 +88,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_SOC_IMX7D) += suspend-imx7.o pm-imx7.o
> obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o
> obj-$(CONFIG_SOC_IMX53) += suspend-imx53.o endif diff --git
> a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index
> 21e4e86..c5767f4 100644
> --- a/arch/arm/mach-imx/common.h
> +++ b/arch/arm/mach-imx/common.h
> @@ -126,6 +126,10 @@ static const u32 imx53_suspend_sz; static inline void
> imx6_suspend(void __iomem *ocram_vbase) {} #endif
>
> +void ca7_cpu_resume(void);
> +void imx7_suspend(void __iomem *ocram_vbase);
> +u32 imx_gpcv2_get_wakeup_source(u32 **sources);
> +
> void imx6_pm_ccm_init(const char *ccm_compat); void imx6q_pm_init(void);
> void imx6dl_pm_init(void); diff --git a/arch/arm/mach-imx/pm-imx7.c
> b/arch/arm/mach-imx/pm-imx7.c new file mode 100644 index 0000000..052c2cb
> --- /dev/null
> +++ b/arch/arm/mach-imx/pm-imx7.c
> @@ -0,0 +1,917 @@
> +/*
> + * 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/mfd/syscon.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/regmap.h>
> +#include <linux/suspend.h>
> +#include <linux/slab.h>
> +#include <asm/suspend.h>
> +#include <asm/fncpy.h>
> +
> +#include "common.h"
> +
> +#define GPC_LPCR_A7_BSC 0x0
> +#define GPC_LPCR_A7_AD 0x4
> +#define GPC_LPCR_M4 0x8
> +
> +#define GPC_SLPCR 0x14
> +#define GPC_PGC_ACK_SEL_A7 0x24
> +
> +#define GPC_SLOTx_CFG(x) (0xb0 + 4 * (x))
> +
> +#define GPC_PGC_CPU_MAPPING 0xec
> +
> +#define GPC_PGC_C0 0x800
> +#define GPC_PGC_C1 0x840
> +#define GPC_PGC_SCU 0x880
> +#define GPC_PGC_SCU_TIMING 0x890
> +#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 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 BM_LPCR_A7_AD_L2PGE (0x1 << 16)
> +#define BM_LPCR_A7_AD_EN_C1_PUP (0x1 << 11)
> +#define BM_LPCR_A7_AD_EN_C1_IRQ_PUP (0x1 << 10)
> +#define BM_LPCR_A7_AD_EN_C0_PUP (0x1 << 9)
> +#define BM_LPCR_A7_AD_EN_C0_IRQ_PUP (0x1 << 8)
> +#define BM_LPCR_A7_AD_EN_PLAT_PDN (0x1 << 4)
> +#define BM_LPCR_A7_AD_EN_C1_PDN (0x1 << 3)
> +#define BM_LPCR_A7_AD_EN_C1_WFI_PDN (0x1 << 2)
> +#define BM_LPCR_A7_AD_EN_C0_PDN (0x1 << 1)
> +#define BM_LPCR_A7_AD_EN_C0_WFI_PDN (0x1)
> +
> +#define BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP (0x7 << 28)
> +#define BM_LPCR_A7_BSC_CPU_CLK_ON_LPM (0x1 << 14)
> +#define BM_LPCR_A7_BSC_LPM1 (0x3 << 2)
> +#define BM_LPCR_A7_BSC_LPM0 (0x3)
> +
> +#define BM_LPCR_M4_MASK_DSM_TRIGGER (0x1 << 31)
> +
> +#define BM_SLPCR_EN_DSM (0x1 << 31)
> +#define BM_SLPCR_RBC_EN (0x1 << 30)
> +#define BM_SLPCR_VSTBY (0x1 << 2)
> +#define BM_SLPCR_SBYOS (0x1 << 1)
> +#define BM_SLPCR_BYPASS_PMIC_READY (0x1)
> +
> +#define BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP (0x1 << 31)
> +#define BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN (0x1 << 15)
> +
> +#define BM_ANADIG_ARM_PLL_OVERRIDE (0x1 << 20)
> +#define BM_ANADIG_DDR_PLL_OVERRIDE (0x1 << 19)
> +#define BM_ANADIG_SYS_PLL_PFDx_OVERRIDE (0x1FF << 17)
> +#define BM_ANADIG_ENET_PLL_OVERRIDE (0x1 << 13)
> +#define BM_ANADIG_AUDIO_PLL_OVERRIDE (0x1 << 24)
> +#define BM_ANADIG_VIDEO_PLL_OVERRIDE (0x1 << 24)
> +
> +#define A7_LPM_WAIT 0x5
> +#define A7_LPM_STOP 0xa
> +#define GPC_MAX_SLOT_NUMBER 10
> +
> +#define REG_SET 0x4
> +#define REG_CLR 0x8
> +
> +#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
> +
> +enum gpcv2_mode {
> + GPC_WAIT_CLOCKED,
> + GPC_WAIT_UNCLOCKED,
> + GPC_STOP_POWER_ON,
> + GPC_STOP_POWER_OFF,
> +};
> +
> +/*
> + * GPCv2 has the following power domains, and each domain can be
> +power-up
> + * and power-down via GPC settings.
> + *
> + * Core 0 of A7 power domain
> + * Core1 of A7 power domain
> + * SCU/L2 cache RAM of A7 power domain
> + * Fastmix and megamix power domain
> + * USB OTG1 PHY power domain
> + * USB OTG2 PHY power domain
> + * PCIE PHY power domain
> + * USB HSIC PHY power domain
> + * Core 0 of M4 power domain
> + */
> +
> +enum gpcv2_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,
> +};
> +
> +struct imx_gpcv2;
> +
> +struct imx_gpcv2_suspend {
> + void (*set_mode)(struct imx_gpcv2 *, enum gpcv2_mode mode);
> + void (*lpm_cpu_power_gate)(struct imx_gpcv2 *, u32, bool);
> + void (*lpm_plat_power_gate)(struct imx_gpcv2 *, bool);
> + void (*lpm_env_setup)(struct imx_gpcv2 *);
> + void (*lpm_env_clean)(struct imx_gpcv2 *);
> +
> + void (*set_slot)(struct imx_gpcv2 *cd, u32 index,
> + enum gpcv2_slot slot, bool mode);
> + void (*set_act)(struct imx_gpcv2 *cd,
> + enum gpcv2_slot slot, bool mode);
> + void (*clear_slots)(struct imx_gpcv2 *);
> + void (*lpm_enable_core)(struct imx_gpcv2 *,
> + bool enable, u32 offset);
> +
> + void (*standby)(struct imx_gpcv2 *);
> + void (*suspend)(struct imx_gpcv2 *);
> +
> + void (*suspend_fn_in_ocram)(void __iomem *ocram_vbase);
> + void __iomem *ocram_vbase;
> +};
> +
> +struct imx_gpcv2 {
> + u32 *wakeupmix_mask;
> + u32 *lpsrmix_mask;
> + u32 *mfmix_mask;
> + spinlock_t lock;
> +
> + struct imx_gpcv2_suspend *pm;
> + struct regmap *anatop;
> + struct regmap *gpcv2;
> +
> + u32 (*get_wakeup_source)(u32 **);
> +};
> +
> +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 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 = {
> + .iomuxc_gpr_compat = "fsl,imx7d-iomuxc",
> + .ddrc_phy_compat = "fsl,imx7d-ddrc-phy",
> + .anatop_compat = "fsl,imx7d-anatop",
> + .ddrc_compat = "fsl,imx7d-ddrc",
> + .ccm_compat = "fsl,imx7d-ccm",
> + .src_compat = "fsl,imx7d-src",
> + .gpc_compat = "fsl,imx7d-gpc",
> + .ddrc_phy_num = ARRAY_SIZE(imx7d_ddrc_phy_ddr3_setting),
> + .ddrc_phy_offset = imx7d_ddrc_phy_ddr3_setting,
> + .ddrc_num = ARRAY_SIZE(imx7d_ddrc_ddr3_setting),
> + .ddrc_offset = imx7d_ddrc_ddr3_setting, };
> +
> +static struct imx_gpcv2 *gpcv2_instance;
> +
> +static void imx_gpcv2_lpm_clear_slots(struct imx_gpcv2 *gpc) {
> + int i;
> +
> + for (i = 0; i < GPC_MAX_SLOT_NUMBER; i++)
> + regmap_write(gpc->gpcv2, GPC_SLOTx_CFG(i), 0x0);
> +
> + regmap_write(gpc->gpcv2, GPC_PGC_ACK_SEL_A7,
> + BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP |
> + BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN);
> +}
> +
> +static void imx_gpcv2_lpm_enable_core(struct imx_gpcv2 *gpc,
> + bool enable, u32 offset)
> +{
> + regmap_write(gpc->gpcv2, offset, enable); }
> +
> +static void imx_gpcv2_lpm_slot_setup(struct imx_gpcv2 *gpc,
> + u32 index, enum gpcv2_slot slot, bool powerup) {
> + u32 val;
> +
> + if (index >= GPC_MAX_SLOT_NUMBER) {
> + pr_err("Invalid slot index!\n");
> + return;
> + }
> +
> + val = (powerup ? 0x2 : 0x1) << (slot * 2);
> + regmap_write(gpc->gpcv2, GPC_SLOTx_CFG(index), val); }
> +
> +static void imx_gpcv2_lpm_set_ack(struct imx_gpcv2 *gpc,
> + enum gpcv2_slot slot, bool powerup)
> +{
> + u32 val;
> +
> + regmap_read(gpc->gpcv2, GPC_PGC_ACK_SEL_A7, &val);
> +
> + /* clear dummy ack */
> + val &= ~(powerup ? BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP :
> + BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN);
> +
> + val |= 1 << (slot + (powerup ? 16 : 0));
> + regmap_write(gpc->gpcv2, GPC_PGC_ACK_SEL_A7, val); }
> +
> +static void imx_gpcv2_lpm_env_setup(struct imx_gpcv2 *gpc) {
> + /* PLL and PFDs overwrite set */
> + regmap_write(gpc->anatop, ANADIG_ARM_PLL + REG_SET,
> + BM_ANADIG_ARM_PLL_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_DDR_PLL + REG_SET,
> + BM_ANADIG_DDR_PLL_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_SYS_PLL + REG_SET,
> + BM_ANADIG_SYS_PLL_PFDx_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_ENET_PLL + REG_SET,
> + BM_ANADIG_ENET_PLL_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_AUDIO_PLL + REG_SET,
> + BM_ANADIG_AUDIO_PLL_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_VIDEO_PLL + REG_SET,
> + BM_ANADIG_VIDEO_PLL_OVERRIDE);
> +}
> +
> +static void imx_gpcv2_lpm_env_clean(struct imx_gpcv2 *gpc) {
> + /* PLL and PFDs overwrite clear */
> + regmap_write(gpc->anatop, ANADIG_ARM_PLL + REG_CLR,
> + BM_ANADIG_ARM_PLL_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_DDR_PLL + REG_CLR,
> + BM_ANADIG_DDR_PLL_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_SYS_PLL + REG_CLR,
> + BM_ANADIG_SYS_PLL_PFDx_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_ENET_PLL + REG_CLR,
> + BM_ANADIG_ENET_PLL_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_AUDIO_PLL + REG_CLR,
> + BM_ANADIG_AUDIO_PLL_OVERRIDE);
> + regmap_write(gpc->anatop, ANADIG_VIDEO_PLL + REG_CLR,
> + BM_ANADIG_VIDEO_PLL_OVERRIDE);
> +}
> +
> +static void imx_gpcv2_lpm_set_mode(struct imx_gpcv2 *gpc,
> + enum gpcv2_mode mode)
> +{
> + u32 lpcr, slpcr;
> +
> + regmap_read(gpc->gpcv2, GPC_LPCR_A7_BSC, &lpcr);
> + regmap_read(gpc->gpcv2, GPC_SLPCR, &slpcr);
> +
> + /* all cores' LPM settings must be same */
> + lpcr &= ~(BM_LPCR_A7_BSC_LPM0 | BM_LPCR_A7_BSC_LPM1);
> + lpcr |= BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
> +
> + slpcr &= ~(BM_SLPCR_EN_DSM | BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN
> |
> + BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY);
> +
> + switch (mode) {
> + case GPC_WAIT_CLOCKED:
> + break;
> + case GPC_WAIT_UNCLOCKED:
> + lpcr |= A7_LPM_WAIT;
> + lpcr &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
> + break;
> + case GPC_STOP_POWER_ON:
> + lpcr |= A7_LPM_STOP;
> + lpcr &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
> + slpcr |= (BM_SLPCR_EN_DSM | BM_SLPCR_RBC_EN |
> + BM_SLPCR_BYPASS_PMIC_READY);
> + break;
> + case GPC_STOP_POWER_OFF:
> + lpcr |= A7_LPM_STOP;
> + lpcr &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
> + slpcr |= (BM_SLPCR_EN_DSM | BM_SLPCR_RBC_EN |
> + BM_SLPCR_SBYOS | BM_SLPCR_VSTBY |
> + BM_SLPCR_BYPASS_PMIC_READY);
> + break;
> + default:
> + return;
> + }
> + regmap_write(gpc->gpcv2, GPC_LPCR_A7_BSC, lpcr);
> + regmap_write(gpc->gpcv2, GPC_SLPCR, slpcr); }
> +
> +static void imx_gpcv2_lpm_cpu_power_gate(struct imx_gpcv2 *gpc,
> + u32 cpu, bool engate)
> +{
> + 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,
> + };
> +
> + regmap_read(gpc->gpcv2, GPC_LPCR_A7_AD, &val);
> + if (engate)
> + val |= val_pdn[cpu];
> + else
> + val &= ~val_pdn[cpu];
> +
> + regmap_write(gpc->gpcv2, GPC_LPCR_A7_AD, val); }
> +
> +static void imx_lpm_plat_power_gate(struct imx_gpcv2 *gpc, bool engate)
> +{
> + u32 val;
> +
> + regmap_read(gpc->gpcv2, GPC_LPCR_A7_AD, &val);
> + val &= ~(BM_LPCR_A7_AD_EN_PLAT_PDN | BM_LPCR_A7_AD_L2PGE);
> + if (engate)
> + val |= BM_LPCR_A7_AD_EN_PLAT_PDN | BM_LPCR_A7_AD_L2PGE;
> +
> + regmap_write(gpc->gpcv2, GPC_LPCR_A7_AD, val); }
> +
> +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, GPC_STOP_POWER_OFF); */
> + pm->set_mode(gpc, GPC_WAIT_UNCLOCKED);
> +
> + /* Zzz ... */
> + cpu_do_idle();
> +
> + pm->set_mode(gpc, 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;
> + u32 *sources;
> + int i, num;
> +
> + pm->lpm_env_setup(gpc);
> + pm->set_mode(gpc, 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);
> + pm->set_slot(gpc, 2, SCU_A7, false);
> +
> + if (gpc->get_wakeup_source) {
> + pm->set_slot(gpc, 1, FAST_MEGA_MIX, false);
> + pm->set_slot(gpc, 5, FAST_MEGA_MIX, true);
> + pm->lpm_enable_core(gpc, true, GPC_PGC_FM);
> + num = gpc->get_wakeup_source(&sources);
> + for (i = 0; i < num; i++) {
> + if ((~sources[i] & gpc->mfmix_mask[i]) == 0)
> + continue;
> + pm->lpm_enable_core(gpc, false, GPC_PGC_FM);
> + break;
> + }
> + }
> +
> + pm->set_slot(gpc, 6, SCU_A7, true);
> + pm->set_slot(gpc, 7, CORE0_A7, true);
> +
> + /* Set Power down act */
> + pm->set_act(gpc, SCU_A7, false);
> +
> + /* Set Power up act */
> + pm->set_act(gpc, CORE0_A7, true);
> +
> + /* enable core0, scu */
> + pm->lpm_enable_core(gpc, true, GPC_PGC_C0);
> + pm->lpm_enable_core(gpc, true, GPC_PGC_SCU);
> +
> + cpu_suspend((unsigned long)pm, gpcv2_suspend_finish);
> +
> + pm->lpm_env_clean(gpc);
> + pm->set_mode(gpc, 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 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 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 = NULL;
> + int i, ret = 0;
> +
> + const u32 (*ddrc_phy_offset_array)[2];
> + const u32 (*ddrc_offset_array)[2];
> +
> + if (!socdata || !pm) {
> + pr_warn("%s: invalid argument!\n", __func__);
> + return -EINVAL;
> + }
> +
> + 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 const struct platform_suspend_ops imx_gpcv2_pm_ops = {
> + .enter = imx_gpcv2_pm_enter,
> + .valid = imx_gpcv2_pm_valid,
> +};
> +
> +static int __init imx_gpcv2_pm_init(void) {
> + struct imx_gpcv2_suspend *pm;
> + struct imx_gpcv2 *gpc;
> + int val, num;
> +
> + pm = kzalloc(sizeof(struct imx_gpcv2_suspend), GFP_KERNEL);
> + if (!pm) {
> + pr_warn("[GPCv2] %s init failed\r\n", __func__);
> + return -ENOMEM;
> + }
> +
> + gpc = kzalloc(sizeof(struct imx_gpcv2), GFP_KERNEL);
> + if (!gpc) {
> + pr_warn("[GPCv2] %s init failed\r\n", __func__);
> + kfree(pm);
> + return -ENOMEM;
> + }
> +
> + 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->set_act = imx_gpcv2_lpm_set_ack;
> +
> + pm->standby = imx_gpcv2_lpm_standby;
> + pm->suspend = imx_gpcv2_lpm_suspend;
> +
> + gpc->anatop = syscon_regmap_lookup_by_compatible("fsl,imx6q-anatop");
> + if (IS_ERR(gpc->anatop))
> + goto error_exit;
> +
> + gpc->gpcv2 = syscon_regmap_lookup_by_compatible("fsl,imx7d-gpc");
> + if (IS_ERR(gpc->gpcv2))
> + goto error_exit;
> +
> + /* only external IRQs to wake up LPM and core 0/1 */
> + regmap_read(gpc->gpcv2, GPC_LPCR_A7_BSC, &val);
> + val |= BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP;
> + regmap_write(gpc->gpcv2, GPC_LPCR_A7_BSC, val);
> + /* mask m4 dsm trigger */
> + regmap_read(gpc->gpcv2, GPC_LPCR_M4, &val);
> + val |= BM_LPCR_M4_MASK_DSM_TRIGGER;
> + regmap_write(gpc->gpcv2, GPC_LPCR_M4, val);
> +
> + /* set mega/fast mix in A7 domain */
> + regmap_write(gpc->gpcv2, GPC_PGC_CPU_MAPPING, 0x1);
> + /* set SCU timing */
> + val = (0x59 << 10) | 0x5B | (0x51 << 20);
> + regmap_write(gpc->gpcv2, GPC_PGC_SCU_TIMING, val);
> +
> + gpc->pm = pm;
> + gpc->get_wakeup_source = imx_gpcv2_get_wakeup_source;
> + gpcv2_instance = gpc;
> +
> + num = imx_gpcv2_get_wakeup_source(0);
> +
> + /*
> + * The IP blocks which may be the wakeup sources are allocated into
> + * several power domains. MFMIX, LPSRMX, and WAKEUPMIX are three of
> + * those power domains. If a bit is '1' in the mask, it means the IP
> + * block is inside the power domain. The mask will be used to decide
> + * if a power domain should be shutdown or not when system goes into
> + * suspend states.
> + */
> +
> + if (num)
> + gpc->wakeupmix_mask = kzalloc(sizeof(u32)*num*3, GFP_KERNEL);
> +
> + if (!gpc->wakeupmix_mask)
> + goto error_exit;
> +
> + /*
> + * Shutdown LPSRMIX and WAKEUP power domain has not been
> + * implemented in this patch yet.
> + */
> +
> + gpc->lpsrmix_mask = gpc->wakeupmix_mask + num;
> + gpc->mfmix_mask = gpc->wakeupmix_mask + num*2;
> +
> + /* Mask the wakeup sources in M/F power domain */
> + gpc->mfmix_mask[0] = 0x54010000;
> + gpc->mfmix_mask[1] = 0xC00;
> + gpc->mfmix_mask[2] = 0x0;
> + gpc->mfmix_mask[3] = 0x400010;
> +
> + suspend_set_ops(&imx_gpcv2_pm_ops);
> + return 0;
> +
> +error_exit:
> + kfree(pm);
> + kfree(gpc);
> + return -ENODEV;
> +}
> +
> +device_initcall(imx_gpcv2_pm_init);
> diff --git a/arch/arm/mach-imx/suspend-imx7.S
> b/arch/arm/mach-imx/suspend-imx7.S
> new file mode 100644
> index 0000000..8c3f516
> --- /dev/null
> +++ b/arch/arm/mach-imx/suspend-imx7.S
> @@ -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)
> --
> 2.5.0.rc2
>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
More information about the linux-arm-kernel
mailing list