From 891552fd40cac7239309db30b1de7cf267b726e1 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Tue, 8 Nov 2016 00:50:08 +0800 Subject: [PATCH] MLK-13441-10 ARM: imx: add suspend support for i.mx7ulp Add suspend/resume support for i.MX7ULP, standby mode will enter VLPS mode, can be waked up by any interrupt which is enabled in GIC, and mem will enter VLLS mode, can only be waked up by NMI currently. Signed-off-by: Anson Huang --- arch/arm/mach-imx/Makefile | 2 + arch/arm/mach-imx/common.h | 5 + arch/arm/mach-imx/mach-imx7ulp.c | 3 +- arch/arm/mach-imx/mx7ulp.h | 2 + arch/arm/mach-imx/pm-imx7ulp.c | 485 +++++++++++++++++++++++++++- arch/arm/mach-imx/suspend-imx7ulp.S | 454 ++++++++++++++++++++++++++ 6 files changed, 939 insertions(+), 12 deletions(-) create mode 100644 arch/arm/mach-imx/suspend-imx7ulp.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index cc5caee5d8a4..3b9d92cfd494 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -110,9 +110,11 @@ obj-$(CONFIG_SOC_IMX7ULP) += mach-imx7ulp.o pm-imx7ulp.o ifeq ($(CONFIG_SUSPEND),y) AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a AFLAGS_suspend-imx7.o :=-Wa,-march=armv7-a +AFLAGS_suspend-imx7ulp.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 +obj-$(CONFIG_SOC_IMX7ULP) += suspend-imx7ulp.o endif obj-$(CONFIG_SOC_IMX6) += pm-imx6.o AFLAGS_smp_wfe.o :=-Wa,-march=armv7-a diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 14e054dc7556..2f392df07d1f 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -195,15 +195,19 @@ void v7_cpu_resume(void); void ca7_cpu_resume(void); void imx53_suspend(void __iomem *ocram_vbase); extern const u32 imx53_suspend_sz; +void imx7ulp_cpu_resume(void); void imx6_suspend(void __iomem *ocram_vbase); void imx7_suspend(void __iomem *ocram_vbase); +void imx7ulp_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 imx7ulp_cpu_resume(void) {} static inline void imx6_suspend(void __iomem *ocram_vbase) {} static inline void imx7_suspend(void __iomem *ocram_vbase) {} +static inline void imx7ulp_suspend(void __iomem *ocram_vbase) {} #endif void imx6_pm_ccm_init(const char *ccm_compat); @@ -214,6 +218,7 @@ void imx6sx_pm_init(void); void imx6ul_pm_init(void); void imx6ull_pm_init(void); void imx7ulp_pm_init(void); +void imx7ulp_enable_nmi(void); void imx6q_pm_set_ccm_base(void __iomem *base); #ifdef CONFIG_PM diff --git a/arch/arm/mach-imx/mach-imx7ulp.c b/arch/arm/mach-imx/mach-imx7ulp.c index f54e9a824e17..205ea02b3471 100644 --- a/arch/arm/mach-imx/mach-imx7ulp.c +++ b/arch/arm/mach-imx/mach-imx7ulp.c @@ -28,6 +28,7 @@ static void __init imx7ulp_init_machine(void) static void __init imx7ulp_init_irq(void) { + /* TBD */ mxc_set_cpu_type(MXC_CPU_IMX7ULP); irqchip_init(); @@ -41,7 +42,7 @@ static void __init imx7ulp_map_io(void) static void __init imx7ulp_init_late(void) { - + imx7ulp_enable_nmi(); } static const char *const imx7ulp_dt_compat[] __initconst = { diff --git a/arch/arm/mach-imx/mx7ulp.h b/arch/arm/mach-imx/mx7ulp.h index 5b912531d799..ecefde5f7d12 100644 --- a/arch/arm/mach-imx/mx7ulp.h +++ b/arch/arm/mach-imx/mx7ulp.h @@ -24,6 +24,8 @@ #define MX7ULP_SCG1_SIZE 0x1000 #define MX7ULP_SIM_BASE_ADDR 0x410a3000 #define MX7ULP_SIM_SIZE 0x1000 +#define MX7ULP_PMC1_BASE_ADDR 0x40400000 +#define MX7ULP_PMC1_SIZE 0x1000 #define MX7ULP_MMDC_BASE_ADDR 0x40ab0000 #define MX7ULP_MMDC_SIZE 0x1000 #define MX7ULP_MMDC_IO_BASE_ADDR 0x40ad0000 diff --git a/arch/arm/mach-imx/pm-imx7ulp.c b/arch/arm/mach-imx/pm-imx7ulp.c index d3a0b6748191..a492c0ea013e 100644 --- a/arch/arm/mach-imx/pm-imx7ulp.c +++ b/arch/arm/mach-imx/pm-imx7ulp.c @@ -34,6 +34,8 @@ #include "common.h" #include "hardware.h" +#define MU_SR 0x60 + #define PMPROT 0x8 #define PMCTRL 0x10 #define PMSTAT 0x18 @@ -77,12 +79,284 @@ #define BP_PMCTRL_STOPM 0 #define BP_PMCTRL_PSTOPO 16 +#define MX7ULP_MAX_MMDC_IO_NUM 36 +#define MX7ULP_MAX_MMDC_NUM 50 + +#define TPM_SC 0x10 +#define TPM_MOD 0x18 +#define TPM_C0SC 0x20 +#define TPM_C0V 0x24 + +#define PCC2_ENABLE_PCS_FIRC ((1 << 30) | (3 << 24)) +#define PCC2_ENABLE (1 << 30) + +#define LPUART_BAUD 0x10 +#define LPUART_CTRL 0x18 +#define LPUART_FIFO 0x28 +#define LPUART_WATER 0x2c + +#define PTC2_LPUART4_TX_OFFSET 0x8 +#define PTC3_LPUART4_RX_OFFSET 0xc +#define PTC2_LPUART4_TX_INPUT_OFFSET 0x248 +#define PTC3_LPUART4_RX_INPUT_OFFSET 0x24c +#define LPUART4_MUX_VALUE (4 << 8) +#define LPUART4_INPUT_VALUE (1) + +#define MU_B_SR_NMIC (1 << 3) + +#define DGO_GPR3 0x60 +#define DGO_GPR4 0x64 + static void __iomem *smc1_base; static void __iomem *pmc0_base; +static void __iomem *pmc1_base; +static void __iomem *tpm5_base; +static void __iomem *lpuart4_base; +static void __iomem *iomuxc1_base; +static void __iomem *pcc2_base; +static void __iomem *pcc3_base; +static void __iomem *mu_base; +static void __iomem *scg1_base; +static void __iomem *wdog1_base; +static void __iomem *wdog2_base; +static void __iomem *suspend_ocram_base; +static void (*imx7ulp_suspend_in_ocram_fn)(void __iomem *sram_vbase); + +static u32 tpm5_regs[4]; +static u32 lpuart4_regs[4]; +static u32 pcc2_regs[25][2] = { + {0x20, 0}, {0x3c, 0}, {0x40, 0}, {0x6c, 0}, + {0x84, 0}, {0x8c, 0}, {0x90, 0}, {0x94, 0}, + {0x98, 0}, {0x9c, 0}, {0xa4, 0}, {0xa8, 0}, + {0xac, 0}, {0xb0, 0}, {0xb4, 0}, {0xb8, 0}, + {0xc4, 0}, {0xcc, 0}, {0xd0, 0}, {0xd4, 0}, + {0xd8, 0}, {0xdc, 0}, {0xe0, 0}, {0xf4, 0}, + {0x10c, 0}, +}; + +static u32 pcc3_regs[16][2] = { + {0x84, 0}, {0x88, 0}, {0x90, 0}, {0x94, 0}, + {0x98, 0}, {0x9c, 0}, {0xa0, 0}, {0xa4, 0}, + {0xa8, 0}, {0xac, 0}, {0xb8, 0}, {0xbc, 0}, + {0xc0, 0}, {0xc4, 0}, {0x140, 0}, {0x144, 0}, +}; + +static u32 scg1_offset[16] = { + 0x14, 0x30, 0x40, 0x304, + 0x500, 0x504, 0x508, 0x50c, + 0x510, 0x514, 0x600, 0x604, + 0x608, 0x60c, 0x610, 0x614, +}; extern unsigned long iram_tlb_base_addr; extern unsigned long iram_tlb_phys_addr; +/* + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx7ulp_suspend code + * PM_INFO structure(imx7ulp_cpu_pm_info) + * ======================== low address ======================= + */ + +struct imx7ulp_pm_base { + phys_addr_t pbase; + void __iomem *vbase; +}; + +struct imx7ulp_pm_socdata { + u32 ddr_type; + const char *mmdc_compat; + const u32 mmdc_io_num; + const u32 *mmdc_io_offset; + const u32 mmdc_num; + const u32 *mmdc_offset; +}; + +static const u32 imx7ulp_mmdc_io_lpddr3_offset[] __initconst = { + 0x128, 0xf8, 0xd8, 0x108, + 0x104, 0x124, 0x80, 0x84, + 0x88, 0x8c, 0x120, 0x10c, + 0x110, 0x114, 0x118, 0x90, + 0x94, 0x98, 0x9c, 0xe0, + 0xe4, +}; + +static const u32 imx7ulp_mmdc_lpddr3_offset[] __initconst = { + 0x01c, 0x800, 0x85c, 0x890, + 0x848, 0x850, 0x81c, 0x820, + 0x824, 0x828, 0x82c, 0x830, + 0x834, 0x838, 0x8c0, 0x8b8, + 0x004, 0x00c, 0x010, 0x038, + 0x014, 0x018, 0x02c, 0x030, + 0x040, 0x000, 0x83c, 0x01c, + 0x01c, 0x01c, 0x01c, 0x01c, + 0x01c, 0x01c, 0x01c, 0x01c, + 0x01c, 0x020, 0x800, 0x004, + 0x404, 0x01c, +}; + +static const u32 imx7ulp_lpddr3_script[] __initconst = { + 0x00008000, 0xA1390003, 0x0D3900A0, 0x00400000, + 0x40404040, 0x40404040, 0x33333333, 0x33333333, + 0x33333333, 0x33333333, 0xf3333333, 0xf3333333, + 0xf3333333, 0xf3333333, 0x24922492, 0x00000800, + 0x00020052, 0x292C42F3, 0x00100A22, 0x00120556, + 0x00C700DB, 0x00211718, 0x0F9F26D2, 0x009F0E10, + 0x0000003F, 0xC3190000, 0x20000000, 0x003F8030, + 0x003F8038, 0xFF0A8030, 0xFF0A8038, 0x04028030, + 0x04028038, 0x83018030, 0x83018038, 0x01038030, + 0x01038038, 0x00001800, 0xA1310000, 0x00020052, + 0x00011006, 0x00000000, +}; + +static const struct imx7ulp_pm_socdata imx7ulp_lpddr3_pm_data __initconst = { + .mmdc_compat = "fsl,imx7ulp-mmdc", + .mmdc_io_num = ARRAY_SIZE(imx7ulp_mmdc_io_lpddr3_offset), + .mmdc_io_offset = imx7ulp_mmdc_io_lpddr3_offset, + .mmdc_num = ARRAY_SIZE(imx7ulp_mmdc_lpddr3_offset), + .mmdc_offset = imx7ulp_mmdc_lpddr3_offset, +}; + +/* + * This structure is for passing necessary data for low level ocram + * suspend code(arch/arm/mach-imx/suspend-imx7ulp.S), if this struct + * definition is changed, the offset definition in + * arch/arm/mach-imx/suspend-imx7ulp.S must be also changed accordingly, + * otherwise, the suspend to sram function will be broken! + */ +struct imx7ulp_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 pm_info_size; /* Size of pm_info. */ + struct imx7ulp_pm_base sim_base; + struct imx7ulp_pm_base mmdc_base; + struct imx7ulp_pm_base mmdc_io_base; + u32 scg1[16]; + u32 ttbr1; /* Store TTBR1 */ + u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */ + u32 mmdc_io_val[MX7ULP_MAX_MMDC_IO_NUM][2]; /* To save offset and value */ + u32 mmdc_num; /* Number of MMDC registers which need saved/restored. */ + u32 mmdc_val[MX7ULP_MAX_MMDC_NUM][2]; +} __aligned(8); + +static struct imx7ulp_cpu_pm_info *pm_info; + +static const char * const low_power_ocram_match[] __initconst = { + "fsl,lpm-sram", + NULL +}; + +static struct map_desc imx7ulp_pm_io_desc[] __initdata = { + imx_map_entry(MX7ULP, AIPS1, MT_DEVICE), + imx_map_entry(MX7ULP, AIPS2, MT_DEVICE), +}; + +static void imx7ulp_scg1_save(void) +{ + int i; + + for (i = 0; i < 16; i++) + pm_info->scg1[i] = readl_relaxed(scg1_base + scg1_offset[i]); +} + +static void imx7ulp_pcc3_save(void) +{ + int i; + + for (i = 0; i < 16; i++) + pcc3_regs[i][1] = readl_relaxed(pcc3_base + pcc3_regs[i][0]); +} + +static void imx7ulp_pcc3_restore(void) +{ + int i; + + for (i = 0; i < 16; i++) + writel_relaxed(pcc3_regs[i][1], pcc3_base + pcc3_regs[i][0]); +} + +static void imx7ulp_pcc2_save(void) +{ + int i; + + for (i = 0; i < 25; i++) + pcc2_regs[i][1] = readl_relaxed(pcc2_base + pcc2_regs[i][0]); +} + +static void imx7ulp_pcc2_restore(void) +{ + int i; + + for (i = 0; i < 25; i++) + writel_relaxed(pcc2_regs[i][1], pcc2_base + pcc2_regs[i][0]); +} + +static void imx7ulp_lpuart_save(void) +{ + lpuart4_regs[0] = readl_relaxed(lpuart4_base + LPUART_BAUD); + lpuart4_regs[1] = readl_relaxed(lpuart4_base + LPUART_FIFO); + lpuart4_regs[2] = readl_relaxed(lpuart4_base + LPUART_WATER); + lpuart4_regs[3] = readl_relaxed(lpuart4_base + LPUART_CTRL); +} + +static void imx7ulp_lpuart_restore(void) +{ + writel_relaxed(0x10101, scg1_base + 0x104); + writel_relaxed(LPUART4_MUX_VALUE, + iomuxc1_base + PTC2_LPUART4_TX_OFFSET); + writel_relaxed(LPUART4_MUX_VALUE, + iomuxc1_base + PTC3_LPUART4_RX_OFFSET); + writel_relaxed(LPUART4_INPUT_VALUE, + iomuxc1_base + PTC2_LPUART4_TX_INPUT_OFFSET); + writel_relaxed(LPUART4_INPUT_VALUE, + iomuxc1_base + PTC3_LPUART4_RX_INPUT_OFFSET); + + writel_relaxed(lpuart4_regs[0], lpuart4_base + LPUART_BAUD); + writel_relaxed(lpuart4_regs[1], lpuart4_base + LPUART_FIFO); + writel_relaxed(lpuart4_regs[2], lpuart4_base + LPUART_WATER); + writel_relaxed(lpuart4_regs[3], lpuart4_base + LPUART_CTRL); +} + +static void imx7ulp_tpm_save(void) +{ + tpm5_regs[0] = readl_relaxed(tpm5_base + TPM_SC); + tpm5_regs[1] = readl_relaxed(tpm5_base + TPM_MOD); + tpm5_regs[2] = readl_relaxed(tpm5_base + TPM_C0SC); + tpm5_regs[3] = readl_relaxed(tpm5_base + TPM_C0V); +} + +static void imx7ulp_tpm_restore(void) +{ + writel_relaxed(tpm5_regs[0], tpm5_base + TPM_SC); + writel_relaxed(tpm5_regs[1], tpm5_base + TPM_MOD); + writel_relaxed(tpm5_regs[2], tpm5_base + TPM_C0SC); + writel_relaxed(tpm5_regs[3], tpm5_base + TPM_C0V); +} + +static void imx7ulp_disable_wdog(void) +{ + writel_relaxed(0x0, wdog1_base); + writel_relaxed(0x0, wdog2_base); + writel_relaxed(0x4, wdog1_base + 0x8); + writel_relaxed(0x4, wdog2_base + 0x8); +} + +static void imx7ulp_set_dgo(u32 val) +{ + writel_relaxed(val, pm_info->sim_base.vbase + DGO_GPR3); + writel_relaxed(val, pm_info->sim_base.vbase + DGO_GPR4); +} + int imx7ulp_set_lpm(enum imx7ulp_cpu_pwr_mode mode) { u32 val1 = BM_PMPROT_AVLP | BM_PMPROT_AVLLS; @@ -123,22 +397,75 @@ int imx7ulp_set_lpm(enum imx7ulp_cpu_pwr_mode mode) return 0; } +static int imx7ulp_suspend_finish(unsigned long val) +{ + imx7ulp_suspend_in_ocram_fn(suspend_ocram_base); + + return 0; +} + +static int imx7ulp_pm_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_STANDBY: + imx7ulp_set_lpm(VLPS); + + /* Zzz ... */ + cpu_suspend(0, imx7ulp_suspend_finish); + + imx7ulp_set_lpm(RUN); + break; + case PM_SUSPEND_MEM: + imx7ulp_scg1_save(); + imx7ulp_pcc2_save(); + imx7ulp_pcc3_save(); + imx7ulp_tpm_save(); + imx7ulp_lpuart_save(); + imx7ulp_set_lpm(VLLS); + + /* Zzz ... */ + cpu_suspend(0, imx7ulp_suspend_finish); + + imx7ulp_pcc2_restore(); + imx7ulp_pcc3_restore(); + imx7ulp_lpuart_restore(); + imx7ulp_disable_wdog(); + imx7ulp_set_dgo(0); + imx7ulp_tpm_restore(); + imx7ulp_set_lpm(RUN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int imx7ulp_pm_valid(suspend_state_t state) +{ + return (state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM); +} + +static const struct platform_suspend_ops imx7ulp_pm_ops = { + .enter = imx7ulp_pm_enter, + .valid = imx7ulp_pm_valid, +}; + +static int __init imx7ulp_suspend_init(void) +{ + int ret = 0; + + suspend_set_ops(&imx7ulp_pm_ops); + + return ret; +} + 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 const char * const low_power_ocram_match[] __initconst = { - "fsl,lpm-sram", - NULL -}; - -static struct map_desc imx7ulp_pm_io_desc[] __initdata = { - imx_map_entry(MX7ULP, AIPS1, MT_DEVICE), - imx_map_entry(MX7ULP, AIPS2, MT_DEVICE), -}; - static int __init imx7ulp_dt_find_lpsram(unsigned long node, const char *uname, int depth, void *data) { @@ -216,9 +543,14 @@ void __init imx7ulp_pm_map_io(void) } } -void __init imx7ulp_pm_init(void) +void __init imx7ulp_pm_common_init(const struct imx7ulp_pm_socdata + *socdata) { struct device_node *np; + unsigned long sram_paddr; + const u32 *mmdc_offset_array; + const u32 *mmdc_io_offset_array; + int i, ret; np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-smc1"); smc1_base = of_iomap(np, 0); @@ -228,5 +560,136 @@ void __init imx7ulp_pm_init(void) pmc0_base = of_iomap(np, 0); WARN_ON(!pmc0_base); + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-pmc1"); + pmc1_base = of_iomap(np, 0); + WARN_ON(!pmc1_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-tpm"); + tpm5_base = of_iomap(np, 0); + WARN_ON(!tpm5_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-lpuart"); + lpuart4_base = of_iomap(np, 0); + WARN_ON(!lpuart4_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-pcc2"); + pcc2_base = of_iomap(np, 0); + WARN_ON(!pcc2_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-pcc3"); + pcc3_base = of_iomap(np, 0); + WARN_ON(!pcc3_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-iomuxc-1"); + iomuxc1_base = of_iomap(np, 0); + WARN_ON(!iomuxc1_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-scg1"); + scg1_base = of_iomap(np, 0); + WARN_ON(!scg1_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-wdt"); + wdog1_base = of_iomap(np, 0); + WARN_ON(!wdog1_base); + + np = of_find_compatible_node(np, NULL, "fsl,imx7ulp-wdt"); + wdog2_base = of_iomap(np, 0); + WARN_ON(!wdog2_base); + + /* + * 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. + * + */ + sram_paddr = iram_tlb_phys_addr; + + /* Make sure sram_paddr is 8 byte aligned. */ + if ((uintptr_t)(sram_paddr) & (FNCPY_ALIGN - 1)) + sram_paddr += FNCPY_ALIGN - sram_paddr % (FNCPY_ALIGN); + + /* Get the virtual address of the suspend code. */ + suspend_ocram_base = (void *)IMX_IO_P2V(sram_paddr); + + pm_info = suspend_ocram_base; + pm_info->pbase = sram_paddr; + pm_info->resume_addr = virt_to_phys(imx7ulp_cpu_resume); + pm_info->pm_info_size = sizeof(*pm_info); + + pm_info->sim_base.pbase = MX7ULP_SIM_BASE_ADDR; + pm_info->sim_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7ULP_SIM_BASE_ADDR); + + pm_info->mmdc_base.pbase = MX7ULP_MMDC_BASE_ADDR; + pm_info->mmdc_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7ULP_MMDC_BASE_ADDR); + + pm_info->mmdc_io_base.pbase = MX7ULP_MMDC_IO_BASE_ADDR; + pm_info->mmdc_io_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7ULP_MMDC_IO_BASE_ADDR); + + pm_info->mmdc_io_num = socdata->mmdc_io_num; + mmdc_io_offset_array = socdata->mmdc_io_offset; + pm_info->mmdc_num = socdata->mmdc_num; + mmdc_offset_array = socdata->mmdc_offset; + + for (i = 0; i < pm_info->mmdc_io_num; i++) { + pm_info->mmdc_io_val[i][0] = + mmdc_io_offset_array[i]; + pm_info->mmdc_io_val[i][1] = + readl_relaxed(pm_info->mmdc_io_base.vbase + + mmdc_io_offset_array[i]); + } + + /* initialize MMDC settings */ + for (i = 0; i < pm_info->mmdc_num; i++) + pm_info->mmdc_val[i][0] = + mmdc_offset_array[i]; + + for (i = 0; i < pm_info->mmdc_num; i++) + pm_info->mmdc_val[i][1] = imx7ulp_lpddr3_script[i]; + + imx7ulp_suspend_in_ocram_fn = fncpy( + suspend_ocram_base + sizeof(*pm_info), + &imx7ulp_suspend, + MX7ULP_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); + + if (IS_ENABLED(CONFIG_SUSPEND)) { + ret = imx7ulp_suspend_init(); + if (ret) + pr_warn("%s: No DDR LPM support with suspend %d!\n", + __func__, ret); + } +} + +void __init imx7ulp_pm_init(void) +{ + imx7ulp_pm_common_init(&imx7ulp_lpddr3_pm_data); imx7ulp_set_lpm(RUN); } + +static irqreturn_t imx7ulp_nmi_isr(int irq, void *param) +{ + writel_relaxed(readl_relaxed(mu_base + MU_SR) | MU_B_SR_NMIC, + mu_base + MU_SR); + + return IRQ_HANDLED; +} + +void imx7ulp_enable_nmi(void) +{ + struct device_node *np; + int irq, ret; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-nmi"); + mu_base = of_iomap(np, 0); + WARN_ON(!mu_base); + irq = of_irq_get(np, 0); + ret = request_irq(irq, imx7ulp_nmi_isr, + IRQF_NO_SUSPEND, "imx7ulp-nmi", NULL); + if (ret) { + pr_err("%s: register interrupt %d failed, rc %d\n", + __func__, irq, ret); + return; + } +} diff --git a/arch/arm/mach-imx/suspend-imx7ulp.S b/arch/arm/mach-imx/suspend-imx7ulp.S new file mode 100644 index 000000000000..b6ecea0d16c5 --- /dev/null +++ b/arch/arm/mach-imx/suspend-imx7ulp.S @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2016 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 +#include +#include "hardware.h" + +/* + * ==================== low level suspend ==================== + * + * Better to follow below rules to use ARM registers: + * r0: pm_info structure address; + * + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx7ulp_suspend code + * PM_INFO structure(imx7ulp_cpu_pm_info) + * ======================== low address ======================= + */ + +/* + * Below offsets are based on struct imx7ulp_cpu_pm_info + * which defined in arch/arm/mach-imx/pm-imx7ulp.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_PM_INFO_SIZE_OFFSET 0x14 +#define PM_INFO_PM_INFO_SIM_PBASE_OFFSET 0x18 +#define PM_INFO_PM_INFO_SIM_VBASE_OFFSET 0x1c +#define PM_INFO_PM_INFO_MMDC_PBASE_OFFSET 0x20 +#define PM_INFO_PM_INFO_MMDC_VBASE_OFFSET 0x24 +#define PM_INFO_PM_INFO_MMDC_IO_PBASE_OFFSET 0x28 +#define PM_INFO_PM_INFO_MMDC_IO_VBASE_OFFSET 0x2c +#define PM_INFO_PM_INFO_SCG1_VAL_OFFSET 0x30 +#define PM_INFO_MX7ULP_TTBR1_V_OFFSET 0x70 +#define PM_INFO_MMDC_IO_NUM_OFFSET 0x74 +#define PM_INFO_MMDC_IO_VAL_OFFSET 0x78 +/* below offsets depends on MX7ULP_MAX_MMDC_IO_NUM(36) definition */ +#define PM_INFO_MMDC_NUM_OFFSET 0x198 +#define PM_INFO_MMDC_VAL_OFFSET 0x19c + +#define DGO_GPR3 0x60 +#define DGO_GPR4 0x64 + +#define MX7ULP_MMDC_MISC 0x18 +#define MX7ULP_MMDC_MAPSR 0x404 +#define MX7ULP_MMDC_MPDGCTRL0 0x83c + +#define SCG_RCCR 0x14 +#define SCG_DDRCCR 0x30 +#define SCG_NICCCR 0x40 +#define SCG_FIRCDIV 0x304 +#define SCG_APLLCSR 0x500 +#define SCG_APLLDIV 0x504 +#define SCG_APLLCFG 0x508 +#define SCG_APLLPFD 0x50c +#define SCG_APLLNUM 0x510 +#define SCG_APLLDENOM 0x514 +#define SCG_SPLLCSR 0x600 +#define SCG_SPLLDIV 0x604 +#define SCG_SPLLCFG 0x608 +#define SCG_SPLLPFD 0x60c +#define SCG_SPLLNUM 0x610 +#define SCG_SPLLDENOM 0x614 +#define SCG_SOSCDIV 0x104 + +#define PMC1_CTRL 0x24 + + .align 3 + + .macro store_ttbr1 + + /* Store TTBR1 to pm_info->ttbr1 */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_MX7ULP_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_MX7ULP_TTBR1_V_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + + .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 restore_mmdc_settings + + ldr r10, [r0, #PM_INFO_PM_INFO_MMDC_IO_PBASE_OFFSET] + ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_PBASE_OFFSET] + + /* resume mmdc iomuxc settings */ + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +11: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 11b + + /* restore MMDC settings */ + ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_VAL_OFFSET + add r7, r7, r0 +1: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r11, r8] + subs r6, r6, #0x1 + bne 1b + + /* let DDR enter self-refresh */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + orr r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] +2: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + beq 2b + + /* let DDR out of self-refresh */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] +3: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + bne 3b + + /* kick off MMDC */ + ldr r4, =0x0 + str r4, [r11, #0x1c] + + /* let DDR out of self-refresh */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] +4: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + bne 4b + + /* enable DDR auto power saving */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r11, #MX7ULP_MMDC_MAPSR] + + .endm + +ENTRY(imx7ulp_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_PM_INFO_SIZE_OFFSET] + + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r6, =imx7ulp_suspend + ldr r7, =resume + sub r7, r7, r6 + add r8, r1, r3 + add r9, r8, r7 + + ldr r11, [r0, #PM_INFO_PM_INFO_SIM_VBASE_OFFSET] + /* store physical resume addr and pm_info address. */ + str r9, [r11, #DGO_GPR3] + str r1, [r11, #DGO_GPR4] + + disable_l1_dcache + + store_ttbr1 + + ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_VBASE_OFFSET] + + /* + * put DDR explicitly into self-refresh and + * disable automatic power savings. + */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r11, #MX7ULP_MMDC_MAPSR] + + /* make the DDR explicitly enter self-refresh. */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + orr r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] + +poll_dvfs_set: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + beq poll_dvfs_set + + /* Zzz, enter stop mode */ + wfi + nop + nop + nop + nop + + /* clear core0's entry and parameter */ + ldr r10, [r0, #PM_INFO_PM_INFO_SIM_VBASE_OFFSET] + mov r7, #0x0 + str r7, [r10, #DGO_GPR3] + str r7, [r10, #DGO_GPR4] + + ldr r7, =100 +loop: + subs r7, r7, #0x1 + bne loop + + /* let DDR out of self-refresh */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] +poll_dvfs_clear: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + bne poll_dvfs_clear + + /* enable DDR auto power saving */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r11, #MX7ULP_MMDC_MAPSR] + + restore_ttbr1 + 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 + + ldr r6, =MX7ULP_PMC1_BASE_ADDR + ldr r7, [r6, #PMC1_CTRL] + orr r7, r7, #(1 << 14) + str r7, [r6, #PMC1_CTRL] + + ldr r0, =0x20008000 + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + + ldr r11, =MX7ULP_SCG1_BASE_ADDR + /* enable spll and pfd0 */ + ldr r5, =PM_INFO_PM_INFO_SCG1_VAL_OFFSET + add r6, r5, #48 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLCFG] + + add r6, r5, #56 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLNUM] + + add r6, r5, #60 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLDENOM] + + add r6, r5, #40 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLCSR] +5: + ldr r7, [r11, #SCG_SPLLCSR] + ands r7, r7, #0x1000000 + beq 5b + + add r6, r5, #44 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLDIV] + + add r6, r5, #52 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLPFD] + + add r6, r5, #0 + ldr r7, [r0, r6] + str r7, [r11, #SCG_RCCR] + + /* enable apll and pfd0 */ + add r6, r5, #24 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLCFG] + + add r6, r5, #32 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLNUM] + + add r6, r5, #36 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLDENOM] + + add r6, r5, #16 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLCSR] +6: + ldr r7, [r11, #SCG_APLLCSR] + ands r7, r7, #0x1000000 + beq 6b + + add r6, r5, #20 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLDIV] + + add r6, r5, #28 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLPFD] + + /* set ddr ccr */ + add r6, r5, #4 + ldr r7, [r0, r6] + str r7, [r11, #SCG_DDRCCR] + + /* set nic sel */ + add r6, r5, #8 + ldr r7, [r0, r6] + str r7, [r11, #SCG_NICCCR] + + /* set firc div2 to get 48MHz */ + add r6, r5, #12 + ldr r7, [r0, r6] + str r7, [r11, #SCG_FIRCDIV] + + /* enable mmdc clock in pcc3 */ + ldr r11, =MX7ULP_PCC3_BASE_ADDR + ldr r7, [r11, #0xac] + orr r7, r7, #0x40000000 + str r7, [r11, #0xac] + + restore_mmdc_settings + + mov pc, lr +ENDPROC(imx7ulp_suspend) + +ENTRY(imx7ulp_cpu_resume) + bl v7_invalidate_l1 + b cpu_resume +ENDPROC(imx7ulp_cpu_resume) -- 2.17.1