/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "hardware.h" #include "cpuidle.h" #define MX7_SUSPEND_OCRAM_OFFSET 0x4000 #define MX7_SUSPEND_OCRAM_SIZE 0x1000 #define MX7_MAX_DDRC_NUM 32 #define MX7_MAX_DDRC_PHY_NUM 16 #define MX7_SUSPEND_IRAM_ADDR_OFFSET 0 #define READ_DATA_FROM_HARDWARE 0 #define UART_UCR1 0x80 #define UART_UCR2 0x84 #define UART_UCR3 0x88 #define UART_UCR4 0x8c #define UART_UFCR 0x90 #define UART_UESC 0x9c #define UART_UTIM 0xa0 #define UART_UBIR 0xa4 #define UART_UBMR 0xa8 #define UART_UBRC 0xac #define UART_UTS 0xb4 #define MAX_IOMUXC_GPR 23 #define MAX_UART_IO 4 #define MAX_CCM_LPCG 167 #define MAX_GPT 3 #define MAX_GPIO_ROW 7 #define MAX_GPIO_COL 8 #define UART_RX_IO 0x128 #define UART_RX_PAD 0x398 #define UART_TX_IO 0x12c #define UART_TX_PAD 0x39c #define GPT_CR 0x0 #define GPT_PR 0x4 #define GPT_IR 0xc #define CCM_LPCG_START 0x4040 #define CCM_LPCG_STEP 0x10 #define CCM_EIM_LPCG 0x4160 #define CCM_PXP_LPCG 0x44c0 #define CCM_PCIE_LPCG 0x4600 #define BM_CCM_ROOT_POST_PODF 0x3f #define BM_CCM_ROOT_PRE_PODF 0x70000 #define BM_CCM_ROOT_MUX 0x7000000 #define BM_CCM_ROOT_ENABLE 0x10000000 #define BM_SYS_COUNTER_CNTCR_FCR1 0x200 #define BM_SYS_COUNTER_CNTCR_FCR0 0x100 #define PFD_A_OFFSET 0xc0 #define PFD_B_OFFSET 0xd0 #define PLL_ARM_OFFSET 0x60 #define PLL_DDR_OFFSET 0x70 #define PLL_DDR_SS_OFFSET 0x80 #define PLL_DDR_NUM_OFFSET 0x90 #define PLL_DDR_DENOM_OFFSET 0xa0 #define PLL_480_OFFSET 0xb0 #define PLL_ENET_OFFSET 0xe0 #define PLL_AUDIO_OFFSET 0xf0 #define PLL_AUDIO_SS_OFFSET 0x100 #define PLL_AUDIO_NUM_OFFSET 0x110 #define PLL_AUDIO_DENOM_OFFSET 0x120 #define PLL_VIDEO_OFFSET 0x130 #define PLL_VIDEO_SS_OFFSET 0x140 #define PLL_VIDEO_NUM_OFFSET 0x150 #define PLL_VIDEO_DENOM_OFFSET 0x160 #define REG_SET 0x4 #define REG_CLR 0x8 #define GPIO_DR 0x0 #define GPIO_GDIR 0x4 #define GPIO_ICR1 0xc #define GPIO_ICR2 0x10 #define GPIO_IMR 0x14 #define GPIO_EDGE 0x1c #define M4RCR 0x0C #define M4_SP_OFF 0x00 #define M4_PC_OFF 0x04 #define M4_RCR_HALT 0xAB #define M4_RCR_GO 0xAA #define M4_OCRAMS_RESERVED_SIZE 0xc extern unsigned long iram_tlb_base_addr; extern unsigned long iram_tlb_phys_addr; static unsigned int *ocram_saved_in_ddr; static void __iomem *ocram_base; static unsigned int ocram_size; static unsigned int *lpm_ocram_saved_in_ddr; static void __iomem *lpm_ocram_base; static unsigned int *lpm_m4tcm_saved_in_ddr; static void __iomem *lpm_m4tcm_base; static void __iomem *m4_bootrom_base; static unsigned int lpm_ocram_size; static void __iomem *ccm_base; static void __iomem *lpsr_base; static void __iomem *console_base; static void __iomem *suspend_ocram_base; static void __iomem *iomuxc_base; static void __iomem *gpt1_base; static void __iomem *system_counter_ctrl_base; static void __iomem *system_counter_cmp_base; static void __iomem *gpio1_base; static void (*imx7_suspend_in_ocram_fn)(void __iomem *ocram_vbase); struct imx7_cpu_pm_info *pm_info; static bool lpsr_enabled; static u32 iomuxc_gpr[MAX_IOMUXC_GPR]; static u32 uart1_io[MAX_UART_IO]; static u32 ccm_lpcg[MAX_CCM_LPCG]; static u32 ccm_root[][2] = { {0x8000, 0}, {0x8080, 0}, {0x8100, 0}, {0x8800, 0}, {0x8880, 0}, {0x8900, 0}, {0x8980, 0}, {0x9000, 0}, {0x9800, 0}, {0x9880, 0}, {0xa000, 0}, {0xa080, 0}, {0xa100, 0}, {0xa180, 0}, {0xa200, 0}, {0xa280, 0}, {0xa300, 0}, {0xa380, 0}, {0xa400, 0}, {0xa480, 0}, {0xa500, 0}, {0xa580, 0}, {0xa600, 0}, {0xa680, 0}, {0xa700, 0}, {0xa780, 0}, {0xa800, 0}, {0xa880, 0}, {0xa900, 0}, {0xa980, 0}, {0xaa00, 0}, {0xaa80, 0}, {0xab00, 0}, {0xab80, 0}, {0xac00, 0}, {0xac80, 0}, {0xad00, 0}, {0xad80, 0}, {0xae00, 0}, {0xae80, 0}, {0xaf00, 0}, {0xaf80, 0}, {0xb000, 0}, {0xb080, 0}, {0xb100, 0}, {0xb180, 0}, {0xb200, 0}, {0xb280, 0}, {0xb300, 0}, {0xb380, 0}, {0xb400, 0}, {0xb480, 0}, {0xb500, 0}, {0xb580, 0}, {0xb600, 0}, {0xb680, 0}, {0xb700, 0}, {0xb780, 0}, {0xb800, 0}, {0xb880, 0}, {0xb900, 0}, {0xb980, 0}, {0xba00, 0}, {0xba80, 0}, {0xbb00, 0}, {0xbb80, 0}, {0xbc00, 0}, {0xbc80, 0}, {0xbd00, 0}, {0xbd80, 0}, {0xbe00, 0}, }; static u32 pfd_a, pfd_b; static u32 pll[15]; static u32 gpt1_regs[MAX_GPT]; static u32 sys_ctrl_reg, sys_cmp_reg; static u32 gpio_reg[MAX_GPIO_ROW][MAX_GPIO_COL]; /* * suspend ocram space layout: * ======================== high address ====================== * . * . * . * ^ * ^ * ^ * imx7_suspend code * PM_INFO structure(imx7_cpu_pm_info) * ======================== low address ======================= */ struct imx7_pm_base { phys_addr_t pbase; void __iomem *vbase; }; struct imx7_pm_socdata { u32 ddr_type; const char *ddrc_compat; const char *src_compat; const char *iomuxc_compat; const char *gpc_compat; const u32 ddrc_num; const u32 (*ddrc_offset)[2]; const u32 ddrc_phy_num; const u32 (*ddrc_phy_offset)[2]; }; static const u32 imx7d_ddrc_lpddr3_setting[][2] __initconst = { { 0x0, READ_DATA_FROM_HARDWARE }, { 0x1a0, READ_DATA_FROM_HARDWARE }, { 0x1a4, READ_DATA_FROM_HARDWARE }, { 0x1a8, READ_DATA_FROM_HARDWARE }, { 0x64, READ_DATA_FROM_HARDWARE }, { 0xd0, 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 }, { 0x118, READ_DATA_FROM_HARDWARE }, { 0x120, READ_DATA_FROM_HARDWARE }, { 0x11c, READ_DATA_FROM_HARDWARE }, { 0x180, READ_DATA_FROM_HARDWARE }, { 0x184, READ_DATA_FROM_HARDWARE }, { 0x190, READ_DATA_FROM_HARDWARE }, { 0x194, READ_DATA_FROM_HARDWARE }, { 0x200, READ_DATA_FROM_HARDWARE }, { 0x204, READ_DATA_FROM_HARDWARE }, { 0x210, READ_DATA_FROM_HARDWARE }, { 0x214, READ_DATA_FROM_HARDWARE }, { 0x218, READ_DATA_FROM_HARDWARE }, { 0x240, READ_DATA_FROM_HARDWARE }, { 0x244, READ_DATA_FROM_HARDWARE }, }; static const u32 imx7d_ddrc_phy_lpddr3_setting[][2] __initconst = { { 0x0, READ_DATA_FROM_HARDWARE }, { 0x4, READ_DATA_FROM_HARDWARE }, { 0x8, READ_DATA_FROM_HARDWARE }, { 0x10, READ_DATA_FROM_HARDWARE }, { 0xb0, READ_DATA_FROM_HARDWARE }, { 0x1c, READ_DATA_FROM_HARDWARE }, { 0x9c, READ_DATA_FROM_HARDWARE }, { 0x7c, READ_DATA_FROM_HARDWARE }, { 0x80, READ_DATA_FROM_HARDWARE }, { 0x84, READ_DATA_FROM_HARDWARE }, { 0x88, READ_DATA_FROM_HARDWARE }, { 0x6c, READ_DATA_FROM_HARDWARE }, { 0x20, READ_DATA_FROM_HARDWARE }, { 0x30, READ_DATA_FROM_HARDWARE }, { 0x50, 0x01000008 }, { 0x50, 0x00000008 }, { 0xc0, 0x0e407304 }, { 0xc0, 0x0e447304 }, { 0xc0, 0x0e447306 }, { 0xc0, 0x0e4c7304 }, { 0xc0, 0x0e487306 }, }; static const u32 imx7d_ddrc_ddr3_setting[][2] __initconst = { { 0x0, READ_DATA_FROM_HARDWARE }, { 0x1a0, READ_DATA_FROM_HARDWARE }, { 0x1a4, READ_DATA_FROM_HARDWARE }, { 0x1a8, READ_DATA_FROM_HARDWARE }, { 0x64, READ_DATA_FROM_HARDWARE }, { 0x490, READ_DATA_FROM_HARDWARE }, { 0xd0, READ_DATA_FROM_HARDWARE }, { 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, READ_DATA_FROM_HARDWARE }, { 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, READ_DATA_FROM_HARDWARE }, { 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 }, { 0xb0, READ_DATA_FROM_HARDWARE }, { 0x9c, READ_DATA_FROM_HARDWARE }, { 0x7c, READ_DATA_FROM_HARDWARE }, { 0x80, READ_DATA_FROM_HARDWARE }, { 0x84, READ_DATA_FROM_HARDWARE }, { 0x88, READ_DATA_FROM_HARDWARE }, { 0x6c, READ_DATA_FROM_HARDWARE }, { 0x20, READ_DATA_FROM_HARDWARE }, { 0x30, READ_DATA_FROM_HARDWARE }, { 0x50, 0x01000010 }, { 0x50, 0x00000010 }, { 0xc0, 0x0e407304 }, { 0xc0, 0x0e447304 }, { 0xc0, 0x0e447306 }, { 0xc0, 0x0e447304 }, { 0xc0, 0x0e407306 }, }; static const struct imx7_pm_socdata imx7d_pm_data_lpddr3 __initconst = { .ddrc_compat = "fsl,imx7d-ddrc", .src_compat = "fsl,imx7d-src", .iomuxc_compat = "fsl,imx7d-iomuxc", .gpc_compat = "fsl,imx7d-gpc", .ddrc_num = ARRAY_SIZE(imx7d_ddrc_lpddr3_setting), .ddrc_offset = imx7d_ddrc_lpddr3_setting, .ddrc_phy_num = ARRAY_SIZE(imx7d_ddrc_phy_lpddr3_setting), .ddrc_phy_offset = imx7d_ddrc_phy_lpddr3_setting, }; static const struct imx7_pm_socdata imx7d_pm_data_ddr3 __initconst = { .ddrc_compat = "fsl,imx7d-ddrc", .src_compat = "fsl,imx7d-src", .iomuxc_compat = "fsl,imx7d-iomuxc", .gpc_compat = "fsl,imx7d-gpc", .ddrc_num = ARRAY_SIZE(imx7d_ddrc_ddr3_setting), .ddrc_offset = imx7d_ddrc_ddr3_setting, .ddrc_phy_num = ARRAY_SIZE(imx7d_ddrc_phy_ddr3_setting), .ddrc_phy_offset = imx7d_ddrc_phy_ddr3_setting, }; /* * This structure is for passing necessary data for low level ocram * suspend code(arch/arm/mach-imx/suspend-imx7.S), if this struct * definition is changed, the offset definition in * arch/arm/mach-imx/suspend-imx7.S must be also changed accordingly, * otherwise, the suspend to ocram function will be broken! */ struct imx7_cpu_pm_info { u32 m4_reserve0; u32 m4_reserve1; u32 m4_reserve2; phys_addr_t pbase; /* The physical address of pm_info. */ phys_addr_t resume_addr; /* The physical resume address for asm code */ u32 ddr_type; u32 pm_info_size; /* Size of pm_info. */ struct imx7_pm_base ddrc_base; struct imx7_pm_base ddrc_phy_base; struct imx7_pm_base src_base; struct imx7_pm_base iomuxc_gpr_base; struct imx7_pm_base ccm_base; struct imx7_pm_base gpc_base; struct imx7_pm_base snvs_base; struct imx7_pm_base anatop_base; struct imx7_pm_base lpsr_base; struct imx7_pm_base gic_base; u32 ttbr1; /* Store TTBR1 */ u32 ddrc_num; /* Number of DDRC which need saved/restored. */ u32 ddrc_val[MX7_MAX_DDRC_NUM][2]; /* To save offset and value */ u32 ddrc_phy_num; /* Number of DDRC which need saved/restored. */ u32 ddrc_phy_val[MX7_MAX_DDRC_NUM][2]; /* To save offset and value */ } __aligned(8); static struct map_desc imx7_pm_io_desc[] __initdata = { imx_map_entry(MX7D, AIPS1, MT_DEVICE), imx_map_entry(MX7D, AIPS2, MT_DEVICE), imx_map_entry(MX7D, AIPS3, MT_DEVICE), }; static const char * const low_power_ocram_match[] __initconst = { "fsl,lpm-sram", NULL }; static void imx7_gpio_save(void) { u32 i; for (i = 0; i < 7; i++) { gpio_reg[i][0] = readl_relaxed(gpio1_base + (i << 16) + GPIO_DR); gpio_reg[i][1] = readl_relaxed(gpio1_base + (i << 16) + GPIO_GDIR); gpio_reg[i][3] = readl_relaxed(gpio1_base + (i << 16) + GPIO_ICR1); gpio_reg[i][4] = readl_relaxed(gpio1_base + (i << 16) + GPIO_ICR2); gpio_reg[i][5] = readl_relaxed(gpio1_base + (i << 16) + GPIO_IMR); gpio_reg[i][7] = readl_relaxed(gpio1_base + (i << 16) + GPIO_EDGE); } } static void imx7_gpio_restore(void) { u32 i, val; for (i = 0; i < 7; i++) { writel_relaxed(gpio_reg[i][1], gpio1_base + (i << 16) + GPIO_GDIR); writel_relaxed(gpio_reg[i][3], gpio1_base + (i << 16) + GPIO_ICR1); writel_relaxed(gpio_reg[i][4], gpio1_base + (i << 16) + GPIO_ICR2); writel_relaxed(gpio_reg[i][5], gpio1_base + (i << 16) + GPIO_IMR); writel_relaxed(gpio_reg[i][7], gpio1_base + (i << 16) + GPIO_EDGE); /* only restore output gpio value */ val = readl_relaxed(gpio1_base + (i << 16) + GPIO_DR) | (gpio_reg[i][0] & gpio_reg[i][1]); writel_relaxed(val, gpio1_base + (i << 16) + GPIO_DR); } } static void imx7_ccm_save(void) { u32 i; for (i = 0; i < MAX_CCM_LPCG; i++) ccm_lpcg[i] = readl_relaxed(pm_info->ccm_base.vbase + i * CCM_LPCG_STEP + CCM_LPCG_START); pfd_a = readl_relaxed(pm_info->anatop_base.vbase + PFD_A_OFFSET); pfd_b = readl_relaxed(pm_info->anatop_base.vbase + PFD_B_OFFSET); pll[0] = readl_relaxed(pm_info->anatop_base.vbase + PLL_ARM_OFFSET); pll[1] = readl_relaxed(pm_info->anatop_base.vbase + PLL_DDR_OFFSET); pll[2] = readl_relaxed(pm_info->anatop_base.vbase + PLL_DDR_SS_OFFSET); pll[3] = readl_relaxed(pm_info->anatop_base.vbase + PLL_DDR_NUM_OFFSET); pll[4] = readl_relaxed(pm_info->anatop_base.vbase + PLL_DDR_DENOM_OFFSET); pll[5] = readl_relaxed(pm_info->anatop_base.vbase + PLL_480_OFFSET); pll[6] = readl_relaxed(pm_info->anatop_base.vbase + PLL_ENET_OFFSET); pll[7] = readl_relaxed(pm_info->anatop_base.vbase + PLL_AUDIO_OFFSET); pll[8] = readl_relaxed(pm_info->anatop_base.vbase + PLL_AUDIO_SS_OFFSET); pll[9] = readl_relaxed(pm_info->anatop_base.vbase + PLL_AUDIO_NUM_OFFSET); pll[10] = readl_relaxed(pm_info->anatop_base.vbase + PLL_AUDIO_DENOM_OFFSET); pll[11] = readl_relaxed(pm_info->anatop_base.vbase + PLL_VIDEO_OFFSET); pll[12] = readl_relaxed(pm_info->anatop_base.vbase + PLL_VIDEO_SS_OFFSET); pll[13] = readl_relaxed(pm_info->anatop_base.vbase + PLL_VIDEO_NUM_OFFSET); pll[14] = readl_relaxed(pm_info->anatop_base.vbase + PLL_VIDEO_DENOM_OFFSET); /* enable all PLLs/PFDs for saving CCM root */ writel_relaxed(0x1c000070, pm_info->anatop_base.vbase + PLL_480_OFFSET + 0x8); writel_relaxed(0x80808080, pm_info->anatop_base.vbase + PFD_A_OFFSET + 0x8); writel_relaxed(0x80808080, pm_info->anatop_base.vbase + PFD_B_OFFSET + 0x8); writel_relaxed(0x1fc0, pm_info->anatop_base.vbase + PLL_ENET_OFFSET + 0x4); writel_relaxed(0x12000, pm_info->anatop_base.vbase + PLL_AUDIO_OFFSET); writel_relaxed(0x12000, pm_info->anatop_base.vbase + PLL_VIDEO_OFFSET); for (i = 0; i < sizeof(ccm_root) / 8; i++) ccm_root[i][1] = readl_relaxed( pm_info->ccm_base.vbase + ccm_root[i][0]); } static void imx7_ccm_restore(void) { u32 i, val; /* enable all PLLs/PFDs for restoring CCM root */ writel_relaxed(0x1c000070, pm_info->anatop_base.vbase + PLL_480_OFFSET + REG_CLR); writel_relaxed(0x80808080, pm_info->anatop_base.vbase + PFD_A_OFFSET + REG_CLR); writel_relaxed(0x80808080, pm_info->anatop_base.vbase + PFD_B_OFFSET + REG_CLR); writel_relaxed(0x1fc0, pm_info->anatop_base.vbase + PLL_ENET_OFFSET + REG_SET); writel_relaxed(0x12000, pm_info->anatop_base.vbase + PLL_AUDIO_OFFSET); writel_relaxed(0x12000, pm_info->anatop_base.vbase + PLL_VIDEO_OFFSET); for (i = 0; i < sizeof(ccm_root) / 8; i++) { val = readl_relaxed(pm_info->ccm_base.vbase + ccm_root[i][0]); /* restore post podf */ val &= ~BM_CCM_ROOT_POST_PODF; val |= ccm_root[i][1] & BM_CCM_ROOT_POST_PODF; writel_relaxed(val, pm_info->ccm_base.vbase + ccm_root[i][0]); /* resotre pre podf */ val &= ~BM_CCM_ROOT_PRE_PODF; val |= ccm_root[i][1] & BM_CCM_ROOT_PRE_PODF; writel_relaxed(val, pm_info->ccm_base.vbase + ccm_root[i][0]); /* restore mux */ val &= ~BM_CCM_ROOT_MUX; val |= ccm_root[i][1] & BM_CCM_ROOT_MUX; writel_relaxed(val, pm_info->ccm_base.vbase + ccm_root[i][0]); /* restore enable */ val &= ~BM_CCM_ROOT_ENABLE; val |= ccm_root[i][1] & BM_CCM_ROOT_ENABLE; writel_relaxed(val, pm_info->ccm_base.vbase + ccm_root[i][0]); } /* restore PLLs */ writel_relaxed(pll[0], pm_info->anatop_base.vbase + PLL_ARM_OFFSET); writel_relaxed(pll[1], pm_info->anatop_base.vbase + PLL_DDR_OFFSET); writel_relaxed(pll[2], pm_info->anatop_base.vbase + PLL_DDR_SS_OFFSET); writel_relaxed(pll[3], pm_info->anatop_base.vbase + PLL_DDR_NUM_OFFSET); writel_relaxed(pll[4], pm_info->anatop_base.vbase + PLL_DDR_DENOM_OFFSET); writel_relaxed(pll[5], pm_info->anatop_base.vbase + PLL_480_OFFSET); writel_relaxed(pll[6], pm_info->anatop_base.vbase + PLL_ENET_OFFSET); writel_relaxed(pll[7], pm_info->anatop_base.vbase + PLL_AUDIO_OFFSET); writel_relaxed(pll[8], pm_info->anatop_base.vbase + PLL_AUDIO_SS_OFFSET); writel_relaxed(pll[9], pm_info->anatop_base.vbase + PLL_AUDIO_NUM_OFFSET); writel_relaxed(pll[10], pm_info->anatop_base.vbase + PLL_AUDIO_DENOM_OFFSET); writel_relaxed(pll[11], pm_info->anatop_base.vbase + PLL_VIDEO_OFFSET); writel_relaxed(pll[12], pm_info->anatop_base.vbase + PLL_VIDEO_SS_OFFSET); writel_relaxed(pll[13], pm_info->anatop_base.vbase + PLL_VIDEO_NUM_OFFSET); writel_relaxed(pll[14], pm_info->anatop_base.vbase + PLL_VIDEO_DENOM_OFFSET); for (i = 0; i < MAX_CCM_LPCG; i++) writel_relaxed(ccm_lpcg[i], pm_info->ccm_base.vbase + i * CCM_LPCG_STEP + CCM_LPCG_START); /* restore PFDs */ writel_relaxed(pfd_a & 0x80808080, pm_info->anatop_base.vbase + PFD_A_OFFSET + REG_SET); writel_relaxed(pfd_a, pm_info->anatop_base.vbase + PFD_A_OFFSET); writel_relaxed(pfd_b & 0x80808080, pm_info->anatop_base.vbase + PFD_B_OFFSET + REG_SET); writel_relaxed(pfd_b, pm_info->anatop_base.vbase + PFD_B_OFFSET); } static void imx7_sys_counter_save(void) { sys_ctrl_reg = readl_relaxed(system_counter_ctrl_base); sys_cmp_reg = readl_relaxed(system_counter_cmp_base); } static void imx7_sys_counter_restore(void) { writel_relaxed(sys_ctrl_reg, system_counter_ctrl_base); writel_relaxed(sys_cmp_reg, system_counter_cmp_base); } static void imx7_gpt_save(void) { gpt1_regs[0] = readl_relaxed(gpt1_base + GPT_CR); gpt1_regs[1] = readl_relaxed(gpt1_base + GPT_PR); gpt1_regs[2] = readl_relaxed(gpt1_base + GPT_IR); } static void imx7_gpt_restore(void) { writel_relaxed(gpt1_regs[0], gpt1_base + GPT_CR); writel_relaxed(gpt1_regs[1], gpt1_base + GPT_PR); writel_relaxed(gpt1_regs[2], gpt1_base + GPT_IR); } static void imx7_iomuxc_gpr_save(void) { u32 i; for (i = 0; i < MAX_IOMUXC_GPR; i++) iomuxc_gpr[i] = readl_relaxed( pm_info->iomuxc_gpr_base.vbase + i * 4); } static void imx7_iomuxc_gpr_restore(void) { u32 i; for (i = 0; i < MAX_IOMUXC_GPR; i++) writel_relaxed(iomuxc_gpr[i], pm_info->iomuxc_gpr_base.vbase + i * 4); } static void imx7_console_save(unsigned int *regs) { if (!console_base) return; regs[0] = readl_relaxed(console_base + UART_UCR1); regs[1] = readl_relaxed(console_base + UART_UCR2); regs[2] = readl_relaxed(console_base + UART_UCR3); regs[3] = readl_relaxed(console_base + UART_UCR4); regs[4] = readl_relaxed(console_base + UART_UFCR); regs[5] = readl_relaxed(console_base + UART_UESC); regs[6] = readl_relaxed(console_base + UART_UTIM); regs[7] = readl_relaxed(console_base + UART_UBIR); regs[8] = readl_relaxed(console_base + UART_UBMR); regs[9] = readl_relaxed(console_base + UART_UTS); } static void imx7_console_io_save(void) { /* save uart1 io, driver resume is too late */ uart1_io[0] = readl_relaxed(iomuxc_base + UART_RX_IO); uart1_io[1] = readl_relaxed(iomuxc_base + UART_RX_PAD); uart1_io[2] = readl_relaxed(iomuxc_base + UART_TX_IO); uart1_io[3] = readl_relaxed(iomuxc_base + UART_TX_PAD); } static void imx7_console_restore(unsigned int *regs) { if (!console_base) return; writel_relaxed(regs[4], console_base + UART_UFCR); writel_relaxed(regs[5], console_base + UART_UESC); writel_relaxed(regs[6], console_base + UART_UTIM); writel_relaxed(regs[7], console_base + UART_UBIR); writel_relaxed(regs[8], console_base + UART_UBMR); writel_relaxed(regs[9], console_base + UART_UTS); writel_relaxed(regs[0], console_base + UART_UCR1); writel_relaxed(regs[1] | 0x1, console_base + UART_UCR2); writel_relaxed(regs[2], console_base + UART_UCR3); writel_relaxed(regs[3], console_base + UART_UCR4); } static void imx7_console_io_restore(void) { /* restore uart1 io */ writel_relaxed(uart1_io[0], iomuxc_base + UART_RX_IO); writel_relaxed(uart1_io[1], iomuxc_base + UART_RX_PAD); writel_relaxed(uart1_io[2], iomuxc_base + UART_TX_IO); writel_relaxed(uart1_io[3], iomuxc_base + UART_TX_PAD); } static int imx7_suspend_finish(unsigned long val) { if (!imx7_suspend_in_ocram_fn) { cpu_do_idle(); } else { /* * call low level suspend function in ocram, * as we need to float DDR IO. */ local_flush_tlb_all(); imx7_suspend_in_ocram_fn(suspend_ocram_base); } return 0; } static void imx7_pm_set_lpsr_resume_addr(unsigned long addr) { writel_relaxed(addr, pm_info->lpsr_base.vbase); } static int imx7_pm_is_resume_from_lpsr(void) { return readl_relaxed(lpsr_base); } static int imx7_pm_enter(suspend_state_t state) { unsigned int console_saved_reg[10] = {0}; u32 val; if (!iram_tlb_base_addr) { pr_warn("No IRAM/OCRAM memory allocated for suspend/resume \ code. Please ensure device tree has an entry for \ fsl,lpm-sram.\n"); return -EINVAL; } /* * arm_arch_timer driver requires system counter to be * a clock source with CLOCK_SOURCE_SUSPEND_NONSTOP flag * set, which means hardware system counter needs to keep * running during suspend, as the base clock for system * counter is 24MHz which will be disabled in STOP mode, * so we need to switch system counter's clock to alternate * (lower) clock, it is based on 32K, from block guide, there * is no special flow needs to be followed, system counter * hardware will handle the clock transition. */ val = readl_relaxed(system_counter_ctrl_base); val &= ~BM_SYS_COUNTER_CNTCR_FCR0; val |= BM_SYS_COUNTER_CNTCR_FCR1; writel_relaxed(val, system_counter_ctrl_base); switch (state) { case PM_SUSPEND_STANDBY: imx_anatop_pre_suspend(); imx_gpcv2_pre_suspend(false); /* Zzz ... */ imx7_suspend_in_ocram_fn(suspend_ocram_base); imx_anatop_post_resume(); imx_gpcv2_post_resume(); break; case PM_SUSPEND_MEM: imx_anatop_pre_suspend(); imx_gpcv2_pre_suspend(true); if (imx_gpcv2_is_mf_mix_off()) { /* * per design requirement, EXSC for PCIe/EIM/PXP * will need clock to recover RDC setting on * resume, so enable PCIe/EIM LPCG for RDC * recovery when M/F mix off */ writel_relaxed(0x3, pm_info->ccm_base.vbase + CCM_EIM_LPCG); writel_relaxed(0x3, pm_info->ccm_base.vbase + CCM_PXP_LPCG); writel_relaxed(0x3, pm_info->ccm_base.vbase + CCM_PCIE_LPCG); /* stop m4 if mix will also be shutdown */ if (imx_src_is_m4_enabled() && imx_mu_is_m4_in_stop()) { writel(M4_RCR_HALT, pm_info->src_base.vbase + M4RCR); imx_gpcv2_enable_wakeup_for_m4(); } imx7_console_save(console_saved_reg); memcpy(ocram_saved_in_ddr, ocram_base, ocram_size); if (lpsr_enabled) { imx7_pm_set_lpsr_resume_addr(pm_info->resume_addr); imx7_console_io_save(); memcpy(lpm_ocram_saved_in_ddr, lpm_ocram_base, lpm_ocram_size); imx7_iomuxc_gpr_save(); imx7_ccm_save(); imx7_gpt_save(); imx7_sys_counter_save(); imx7_gpio_save(); } } /* Zzz ... */ cpu_suspend(0, imx7_suspend_finish); if (imx7_pm_is_resume_from_lpsr()) { imx7_console_io_restore(); memcpy(lpm_ocram_base, lpm_ocram_saved_in_ddr, lpm_ocram_size); imx7_iomuxc_gpr_restore(); imx7_ccm_restore(); imx7_gpt_restore(); imx7_sys_counter_restore(); imx7_gpio_restore(); imx7d_enable_rcosc(); } if (imx_gpcv2_is_mf_mix_off() || imx7_pm_is_resume_from_lpsr()) { writel_relaxed(0x0, pm_info->ccm_base.vbase + CCM_EIM_LPCG); writel_relaxed(0x0, pm_info->ccm_base.vbase + CCM_PXP_LPCG); writel_relaxed(0x0, pm_info->ccm_base.vbase + CCM_PCIE_LPCG); memcpy(ocram_base, ocram_saved_in_ddr, ocram_size); imx7_console_restore(console_saved_reg); if (imx_src_is_m4_enabled() && imx_mu_is_m4_in_stop()) { imx_gpcv2_disable_wakeup_for_m4(); /* restore M4 image */ memcpy(lpm_m4tcm_base, lpm_m4tcm_saved_in_ddr, SZ_32K); /* kick m4 to enable */ writel(M4_RCR_GO, pm_info->src_base.vbase + M4RCR); /* offset high bus count for m4 image */ request_bus_freq(BUS_FREQ_HIGH); /* restore M4 to run mode */ imx_mu_set_m4_run_mode(); /* gpc wakeup */ } } /* clear LPSR resume address */ imx7_pm_set_lpsr_resume_addr(0); imx_anatop_post_resume(); imx_gpcv2_post_resume(); break; default: return -EINVAL; } /* restore system counter's clock to base clock */ val = readl_relaxed(system_counter_ctrl_base); val &= ~BM_SYS_COUNTER_CNTCR_FCR1; val |= BM_SYS_COUNTER_CNTCR_FCR0; writel_relaxed(val, system_counter_ctrl_base); return 0; } static int imx7_pm_valid(suspend_state_t state) { return state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM; } static const struct platform_suspend_ops imx7_pm_ops = { .enter = imx7_pm_enter, .valid = imx7_pm_valid, }; void __init imx7_pm_set_ccm_base(void __iomem *base) { ccm_base = base; } static struct map_desc iram_tlb_io_desc __initdata = { /* .virtual and .pfn are run-time assigned */ .length = SZ_1M, .type = MT_MEMORY_RWX_NONCACHED, }; static int __init imx7_dt_find_lpsram(unsigned long node, const char *uname, int depth, void *data) { unsigned long lpram_addr; const __be32 *prop = of_get_flat_dt_prop(node, "reg", NULL); if (of_flat_dt_match(node, low_power_ocram_match)) { if (!prop) return -EINVAL; /* Add offset so we can use a full vector table for M4 */ lpram_addr = be32_to_cpup(prop) + MX7_SUSPEND_OCRAM_OFFSET; /* We need to create a 1M page table entry. */ iram_tlb_io_desc.virtual = IMX_IO_P2V(lpram_addr & 0xFFF00000); iram_tlb_io_desc.pfn = __phys_to_pfn(lpram_addr & 0xFFF00000); iram_tlb_phys_addr = lpram_addr; iram_tlb_base_addr = IMX_IO_P2V(lpram_addr); iotable_init(&iram_tlb_io_desc, 1); } return 0; } void __init imx7_pm_map_io(void) { unsigned long i, j; iotable_init(imx7_pm_io_desc, ARRAY_SIZE(imx7_pm_io_desc)); /* * Get the address of IRAM or OCRAM to be used by the low * power code from the device tree. */ WARN_ON(of_scan_flat_dt(imx7_dt_find_lpsram, NULL)); /* Return if no IRAM space is allocated for suspend/resume code. */ if (!iram_tlb_base_addr) { pr_warn("No valid ocram available for suspend/resume!\n"); return; } /* Set all entries to 0 except first 3 words reserved for M4. */ memset((void *)(iram_tlb_base_addr + M4_OCRAMS_RESERVED_SIZE), 0, MX7_IRAM_TLB_SIZE - M4_OCRAMS_RESERVED_SIZE); /* * Make sure the IRAM virtual address has a mapping in the IRAM * page table. * * Only use the top 12 bits [31-20] when storing the physical * address in the page table as only these bits are required * for 1M mapping. */ j = ((iram_tlb_base_addr >> 20) << 2) / 4; *((unsigned long *)iram_tlb_base_addr + j) = (iram_tlb_phys_addr & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; /* * Make sure the AIPS1 virtual address has a mapping in the * IRAM page table. */ for (i = 0; i < 4; i++) { j = ((IMX_IO_P2V(MX7D_AIPS1_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4; *((unsigned long *)iram_tlb_base_addr + j) = ((MX7D_AIPS1_BASE_ADDR + i * 0x100000) & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; } /* * Make sure the AIPS2 virtual address has a mapping in the * IRAM page table. */ for (i = 0; i < 4; i++) { j = ((IMX_IO_P2V(MX7D_AIPS2_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4; *((unsigned long *)iram_tlb_base_addr + j) = ((MX7D_AIPS2_BASE_ADDR + i * 0x100000) & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; } /* * Make sure the AIPS3 virtual address has a mapping * in the IRAM page table. */ for (i = 0; i < 4; i++) { j = ((IMX_IO_P2V(MX7D_AIPS3_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4; *((unsigned long *)iram_tlb_base_addr + j) = ((MX7D_AIPS3_BASE_ADDR + i * 0x100000) & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; } /* * Make sure the GIC virtual address has a mapping in the * IRAM page table. */ j = ((IMX_IO_P2V(MX7D_GIC_BASE_ADDR) >> 20) << 2) / 4; *((unsigned long *)iram_tlb_base_addr + j) = (MX7D_GIC_BASE_ADDR & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; } static int __init imx7_suspend_init(const struct imx7_pm_socdata *socdata) { struct device_node *node; int i, ret = 0; const u32 (*ddrc_offset_array)[2]; const u32 (*ddrc_phy_offset_array)[2]; unsigned long iram_paddr; suspend_set_ops(&imx7_pm_ops); if (!socdata) { pr_warn("%s: invalid argument!\n", __func__); return -EINVAL; } /* * 16KB is allocated for IRAM TLB, but only up 8k is for kernel TLB, * The lower 8K is not used, so use the lower 8K for IRAM code and * pm_info. * */ iram_paddr = iram_tlb_phys_addr + MX7_SUSPEND_IRAM_ADDR_OFFSET; /* Make sure iram_paddr is 8 byte aligned. */ if ((uintptr_t)(iram_paddr) & (FNCPY_ALIGN - 1)) iram_paddr += FNCPY_ALIGN - iram_paddr % (FNCPY_ALIGN); /* Get the virtual address of the suspend code. */ suspend_ocram_base = (void *)IMX_IO_P2V(iram_paddr); pm_info = suspend_ocram_base; /* pbase points to iram_paddr. */ pm_info->pbase = iram_paddr; pm_info->resume_addr = virt_to_phys(ca7_cpu_resume); pm_info->pm_info_size = sizeof(*pm_info); /* * ccm physical address is not used by asm code currently, * so get ccm virtual address directly, as we already have * it from ccm driver. */ pm_info->ccm_base.pbase = MX7D_CCM_BASE_ADDR; pm_info->ccm_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_CCM_BASE_ADDR); pm_info->ddrc_base.pbase = MX7D_DDRC_BASE_ADDR; pm_info->ddrc_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_DDRC_BASE_ADDR); pm_info->ddrc_phy_base.pbase = MX7D_DDRC_PHY_BASE_ADDR; pm_info->ddrc_phy_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_DDRC_PHY_BASE_ADDR); pm_info->src_base.pbase = MX7D_SRC_BASE_ADDR; pm_info->src_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_SRC_BASE_ADDR); pm_info->iomuxc_gpr_base.pbase = MX7D_IOMUXC_GPR_BASE_ADDR; pm_info->iomuxc_gpr_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR); pm_info->gpc_base.pbase = MX7D_GPC_BASE_ADDR; pm_info->gpc_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_GPC_BASE_ADDR); pm_info->anatop_base.pbase = MX7D_ANATOP_BASE_ADDR; pm_info->anatop_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR); pm_info->snvs_base.pbase = MX7D_SNVS_BASE_ADDR; pm_info->snvs_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_SNVS_BASE_ADDR); pm_info->lpsr_base.pbase = MX7D_LPSR_BASE_ADDR; lpsr_base = pm_info->lpsr_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_LPSR_BASE_ADDR); pm_info->gic_base.pbase = MX7D_GIC_BASE_ADDR; pm_info->gic_base.vbase = (void __iomem *) IMX_IO_P2V(MX7D_GIC_BASE_ADDR); pm_info->ddrc_num = socdata->ddrc_num; ddrc_offset_array = socdata->ddrc_offset; pm_info->ddrc_phy_num = socdata->ddrc_phy_num; ddrc_phy_offset_array = socdata->ddrc_phy_offset; /* initialize DDRC settings */ for (i = 0; i < pm_info->ddrc_num; i++) { pm_info->ddrc_val[i][0] = ddrc_offset_array[i][0]; if (ddrc_offset_array[i][1] == READ_DATA_FROM_HARDWARE) pm_info->ddrc_val[i][1] = readl_relaxed(pm_info->ddrc_base.vbase + ddrc_offset_array[i][0]); else pm_info->ddrc_val[i][1] = ddrc_offset_array[i][1]; if (pm_info->ddrc_val[i][0] == 0xd0) pm_info->ddrc_val[i][1] |= 0xc0000000; } /* initialize DDRC PHY settings */ for (i = 0; i < pm_info->ddrc_phy_num; i++) { pm_info->ddrc_phy_val[i][0] = ddrc_phy_offset_array[i][0]; if (ddrc_phy_offset_array[i][1] == READ_DATA_FROM_HARDWARE) pm_info->ddrc_phy_val[i][1] = readl_relaxed(pm_info->ddrc_phy_base.vbase + ddrc_phy_offset_array[i][0]); else pm_info->ddrc_phy_val[i][1] = ddrc_phy_offset_array[i][1]; } imx7_suspend_in_ocram_fn = fncpy( suspend_ocram_base + sizeof(*pm_info), &imx7_suspend, MX7_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); goto put_node; put_node: of_node_put(node); return ret; } static void __init imx7_pm_common_init(const struct imx7_pm_socdata *socdata) { int ret; struct regmap *gpr; if (IS_ENABLED(CONFIG_SUSPEND)) { ret = imx7_suspend_init(socdata); if (ret) pr_warn("%s: No DDR LPM support with suspend %d!\n", __func__, ret); } /* * Force IOMUXC irq pending, so that the interrupt to GPC can be * used to deassert dsm_request signal when the signal gets * asserted unexpectedly. */ gpr = syscon_regmap_lookup_by_compatible("fsl,imx7d-iomuxc-gpr"); if (!IS_ERR(gpr)) regmap_update_bits(gpr, IOMUXC_GPR1, IMX7D_GPR1_IRQ_MASK, IMX7D_GPR1_IRQ_MASK); } void __init imx7d_pm_init(void) { struct device_node *np; struct resource res; if (imx_src_is_m4_enabled()) { /* map the 32K of M4 TCM */ np = of_find_node_by_path( "/tcml@007f8000"); if (np) lpm_m4tcm_base = of_iomap(np, 0); WARN_ON(!lpm_m4tcm_base); /* map the m4 bootrom from dtb */ np = of_find_node_by_path( "/soc/sram@00180000"); if (np) m4_bootrom_base = of_iomap(np, 0); WARN_ON(!m4_bootrom_base); lpm_m4tcm_saved_in_ddr = kzalloc(SZ_32K, GFP_KERNEL); WARN_ON(!lpm_m4tcm_saved_in_ddr); /* save M4 Image to DDR */ memcpy(lpm_m4tcm_saved_in_ddr, lpm_m4tcm_base, SZ_32K); } np = of_find_compatible_node(NULL, NULL, "fsl,lpm-sram"); if (of_get_property(np, "fsl,enable-lpsr", NULL)) lpsr_enabled = true; if (lpsr_enabled) { pr_info("LPSR mode enabled, DSM will go into LPSR mode!\n"); lpm_ocram_base = of_iomap(np, 0); WARN_ON(!lpm_ocram_base); WARN_ON(of_address_to_resource(np, 0, &res)); lpm_ocram_size = resource_size(&res); lpm_ocram_saved_in_ddr = kzalloc(lpm_ocram_size, GFP_KERNEL); WARN_ON(!lpm_ocram_saved_in_ddr); np = of_find_node_by_path( "/soc/aips-bus@30000000/iomuxc@30330000"); if (np) iomuxc_base = of_iomap(np, 0); WARN_ON(!iomuxc_base); np = of_find_node_by_path( "/soc/aips-bus@30000000/gpt@302d0000"); if (np) gpt1_base = of_iomap(np, 0); WARN_ON(!gpt1_base); np = of_find_node_by_path( "/soc/aips-bus@30400000/system-counter-cmp@306b0000"); if (np) system_counter_cmp_base = of_iomap(np, 0); WARN_ON(!system_counter_cmp_base); np = of_find_node_by_path( "/soc/aips-bus@30000000/gpio@30200000"); if (np) gpio1_base = of_iomap(np, 0); WARN_ON(!gpio1_base); } np = of_find_node_by_path( "/soc/aips-bus@30400000/system-counter-ctrl@306c0000"); if (np) system_counter_ctrl_base = of_iomap(np, 0); WARN_ON(!system_counter_ctrl_base); if (imx_ddrc_get_ddr_type() == IMX_DDR_TYPE_LPDDR3 || imx_ddrc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2) imx7_pm_common_init(&imx7d_pm_data_lpddr3); else if (imx_ddrc_get_ddr_type() == IMX_DDR_TYPE_DDR3) imx7_pm_common_init(&imx7d_pm_data_ddr3); np = of_find_compatible_node(NULL, NULL, "fsl,mega-fast-sram"); ocram_base = of_iomap(np, 0); WARN_ON(!ocram_base); WARN_ON(of_address_to_resource(np, 0, &res)); ocram_size = resource_size(&res); ocram_saved_in_ddr = kzalloc(ocram_size, GFP_KERNEL); WARN_ON(!ocram_saved_in_ddr); np = of_find_node_by_path( "/soc/aips-bus@30800000/spba-bus@30800000/serial@30860000"); if (np) console_base = of_iomap(np, 0); /* clear LPSR resume address first */ imx7_pm_set_lpsr_resume_addr(0); }