1
0
Fork 0

Merge branch 'arch/next' into next

* arch/next: (81 commits)
  MLK-21599-1 arm64: Kconfig: Make FORCE_MAX_ZONEORDER configurable
  LF-171 ARM: imx: Add cpu type check for imx6ulz in msl code
  LF-176 ARM: imx: mach-imx6q: Revert "ARM: imx: correct the enet_clk_ref clock string"
  drivers: soc: fsl: add qixis driver
  Add APIs to setup HugeTLB mappings for USDPAA
  ...
5.4-rM2-2.2.x-imx-squashed
Dong Aisheng 2019-12-02 18:02:06 +08:00
commit 96aa3bc2ff
120 changed files with 25515 additions and 522 deletions

View File

@ -85,6 +85,22 @@ extern void __bad_udelay(void);
__const_udelay((n) * UDELAY_MULT)) : \
__udelay(n))
#define spin_event_timeout(condition, timeout, delay) \
({ \
typeof(condition) __ret; \
int i = 0; \
while (!(__ret = (condition)) && (i++ < timeout)) { \
if (delay) \
udelay(delay); \
else \
cpu_relax(); \
udelay(1); \
} \
if (!__ret) \
__ret = (condition); \
__ret; \
})
/* Loop-based definitions for assembly code. */
extern void __loop_delay(unsigned long loops);
extern void __loop_udelay(unsigned long usecs);

View File

@ -123,6 +123,7 @@ static inline u32 __raw_readl(const volatile void __iomem *addr)
#define MT_DEVICE_NONSHARED 1
#define MT_DEVICE_CACHED 2
#define MT_DEVICE_WC 3
#define MT_MEMORY_RW_NS 4
/*
* types 4 onwards can be found in asm/mach/map.h and are undefined
* for ioremap
@ -224,6 +225,34 @@ void __iomem *pci_remap_cfgspace(resource_size_t res_cookie, size_t size);
#endif
#endif
/* access ports */
#define setbits32(_addr, _v) iowrite32be(ioread32be(_addr) | (_v), (_addr))
#define clrbits32(_addr, _v) iowrite32be(ioread32be(_addr) & ~(_v), (_addr))
#define setbits16(_addr, _v) iowrite16be(ioread16be(_addr) | (_v), (_addr))
#define clrbits16(_addr, _v) iowrite16be(ioread16be(_addr) & ~(_v), (_addr))
#define setbits8(_addr, _v) iowrite8(ioread8(_addr) | (_v), (_addr))
#define clrbits8(_addr, _v) iowrite8(ioread8(_addr) & ~(_v), (_addr))
/* Clear and set bits in one shot. These macros can be used to clear and
* set multiple bits in a register using a single read-modify-write. These
* macros can also be used to set a multiple-bit bit pattern using a mask,
* by specifying the mask in the 'clear' parameter and the new bit pattern
* in the 'set' parameter.
*/
#define clrsetbits_be32(addr, clear, set) \
iowrite32be((ioread32be(addr) & ~(clear)) | (set), (addr))
#define clrsetbits_le32(addr, clear, set) \
iowrite32le((ioread32le(addr) & ~(clear)) | (set), (addr))
#define clrsetbits_be16(addr, clear, set) \
iowrite16be((ioread16be(addr) & ~(clear)) | (set), (addr))
#define clrsetbits_le16(addr, clear, set) \
iowrite16le((ioread16le(addr) & ~(clear)) | (set), (addr))
#define clrsetbits_8(addr, clear, set) \
iowrite8((ioread8(addr) & ~(clear)) | (set), (addr))
/*
* IO port access primitives
* -------------------------
@ -410,6 +439,8 @@ void __iomem *ioremap_wc(resource_size_t res_cookie, size_t size);
#define ioremap_wc ioremap_wc
#define ioremap_wt ioremap_wc
void __iomem *ioremap_cache_ns(resource_size_t res_cookie, size_t size);
void iounmap(volatile void __iomem *iomem_cookie);
#define iounmap iounmap

View File

@ -18,9 +18,9 @@ struct map_desc {
unsigned int type;
};
/* types 0-3 are defined in asm/io.h */
/* types 0-4 are defined in asm/io.h */
enum {
MT_UNCACHED = 4,
MT_UNCACHED = 5,
MT_CACHECLEAN,
MT_MINICLEAN,
MT_LOW_VECTORS,

View File

@ -116,6 +116,13 @@ extern pgprot_t pgprot_s2_device;
#define pgprot_noncached(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED)
#define pgprot_cached(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_DEV_CACHED)
#define pgprot_cached_ns(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_DEV_CACHED | \
L_PTE_MT_DEV_NONSHARED)
#define pgprot_writecombine(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE)

View File

@ -9,6 +9,7 @@
* reading the RTC at bootup, etc...
*/
#include <linux/clk-provider.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/errno.h>
#include <linux/export.h>
@ -107,5 +108,7 @@ void __init time_init(void)
of_clk_init(NULL);
#endif
timer_probe();
tick_setup_hrtimer_broadcast();
}
}

View File

@ -40,9 +40,29 @@ config HAVE_IMX_GPC
bool
select PM_GENERIC_DOMAINS if PM
config HAVE_IMX_GPCV2
bool
select PM_GENERIC_DOMAINS if PM
config HAVE_IMX_MMDC
bool
config HAVE_IMX_AMP
bool
config HAVE_IMX_DDRC
bool
select HAVE_IMX_BUSFREQ
config HAVE_IMX_BUSFREQ
bool
config HAVE_IMX_MU
bool
config HAVE_IMX_RPMSG
bool
config HAVE_IMX_SRC
def_bool y if SMP
select ARCH_HAS_RESET_CONTROLLER
@ -511,7 +531,12 @@ config SOC_IMX6SLL
config SOC_IMX6SX
bool "i.MX6 SoloX support"
select PINCTRL_IMX6SX
select HAVE_IMX_AMP
select SOC_IMX6
select HAVE_IMX_MU
select HAVE_IMX_RPMSG
select IMX_SEMA4
select KEYBOARD_SNVS_PWRKEY
help
This enables support for Freescale i.MX6 SoloX processor.
@ -546,6 +571,11 @@ config SOC_IMX7D_CA7
select HAVE_IMX_MMDC
select HAVE_IMX_SRC
select IMX_GPCV2
select HAVE_IMX_DDRC
select HAVE_IMX_MU
select HAVE_IMX_RPMSG
select HAVE_IMX_GPCV2
select KEYBOARD_SNVS_PWRKEY
config SOC_IMX7D_CM4
bool

View File

@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-y := cpu.o system.o irq-common.o
obj-y := cpu.o system.o irq-common.o common.o
obj-$(CONFIG_SOC_IMX21) += mm-imx21.o
@ -25,11 +25,18 @@ obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o
ifeq ($(CONFIG_CPU_IDLE),y)
obj-$(CONFIG_SOC_IMX5) += cpuidle-imx5.o
obj-$(CONFIG_SOC_IMX6Q) += cpuidle-imx6q.o
obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o
obj-$(CONFIG_SOC_IMX6SLL) += cpuidle-imx6sx.o
obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o
obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6sx.o
obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o
AFLAGS_imx6sl_low_power_idle.o :=-Wa,-march=armv7-a
obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o imx6sl_low_power_idle.o
AFLAGS_imx6sll_low_power_idle.o :=-Wa,-march=armv7-a
obj-$(CONFIG_SOC_IMX6SLL) += cpuidle-imx6sll.o imx6sll_low_power_idle.o
obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o imx6sx_low_power_idle.o
AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a
AFLAGS_imx6ul_low_power_idle.o :=-Wa,-march=armv7-a
AFLAGS_imx6ull_low_power_idle.o :=-Wa,-march=armv7-a
obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6ul.o imx6ul_low_power_idle.o imx6ull_low_power_idle.o
obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o pm-rpmsg.o
AFLAGS_imx7d_low_power_idle.o :=-Wa,-march=armv7-a
obj-$(CONFIG_SOC_IMX7D_CA7) += cpuidle-imx7d.o imx7d_low_power_idle.o
endif
ifdef CONFIG_SND_SOC_IMX_PCM_FIQ
@ -70,26 +77,46 @@ obj-$(CONFIG_MACH_IMX35_DT) += imx35-dt.o
obj-$(CONFIG_HAVE_IMX_ANATOP) += anatop.o
obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o
obj-$(CONFIG_HAVE_IMX_GPCV2) += gpcv2.o
obj-$(CONFIG_HAVE_IMX_MMDC) += mmdc.o
obj-$(CONFIG_HAVE_IMX_SRC) += src.o
obj-$(CONFIG_HAVE_IMX_DDRC) += ddrc.o
obj-$(CONFIG_HAVE_IMX_MU) += mu.o
ifneq ($(CONFIG_SOC_IMX6)$(CONFIG_SOC_LS1021A),)
AFLAGS_headsmp.o :=-Wa,-march=armv7-a
obj-$(CONFIG_SMP) += headsmp.o platsmp.o
obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
endif
obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o
obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o
obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o
obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o
obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o ddr3_freq_imx6.o smp_wfe_imx6.o \
lpddr2_freq_imx6q.o
obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o lpddr2_freq_imx6.o
obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o lpddr2_freq_imx6sll.o
obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o ddr3_freq_imx6sx.o smp_wfe_imx6.o lpddr2_freq_imx6sx.o
obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o
obj-$(CONFIG_SOC_IMX7D_CA7) += mach-imx7d.o
obj-$(CONFIG_SOC_IMX7D_CA7) += mach-imx7d.o pm-imx7.o ddr3_freq_imx7d.o smp_wfe.o \
lpddr3_freq_imx.o suspend-imx7.o
obj-$(CONFIG_SOC_IMX7D_CM4) += mach-imx7d-cm4.o
obj-$(CONFIG_SOC_IMX7ULP) += mach-imx7ulp.o pm-imx7ulp.o
obj-y += busfreq-imx.o busfreq_ddr3.o busfreq_lpddr2.o
AFLAGS_smp_wfe.o :=-Wa,-march=armv7-a
AFLAGS_smp_wfe_imx6.o :=-Wa,-march=armv7-a
AFLAGS_ddr3_freq_imx7d.o :=-Wa,-march=armv7-a
AFLAGS_lpddr3_freq_imx.o :=-Wa,-march=armv7-a
AFLAGS_ddr3_freq_imx6.o :=-Wa,-march=armv7-a
AFLAGS_lpddr2_freq_imx6.o :=-Wa,-march=armv7-a
AFLAGS_lpddr2_freq_imx6q.o :=-Wa,-march=armv7-a
AFLAGS_lpddr2_freq_imx6sx.o :=-Wa,-march=armv7-a
AFLAGS_lpddr2_freq_imx6sll.o :=-Wa,-march=armv7-a
AFLAGS_ddr3_freq_imx6sx.o :=-Wa,-march=armv7-a
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_IMX53) += suspend-imx53.o
obj-$(CONFIG_SOC_IMX7ULP) += suspend-imx7ulp.o
endif
obj-$(CONFIG_SOC_IMX6) += pm-imx6.o

View File

@ -4,6 +4,7 @@
* Copyright 2017-2018 NXP.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
@ -16,38 +17,63 @@
#define REG_SET 0x4
#define REG_CLR 0x8
#define ANADIG_ARM_PLL 0x60
#define ANADIG_DDR_PLL 0x70
#define ANADIG_SYS_PLL 0xb0
#define ANADIG_ENET_PLL 0xe0
#define ANADIG_AUDIO_PLL 0xf0
#define ANADIG_VIDEO_PLL 0x130
#define ANADIG_REG_2P5 0x130
#define ANADIG_REG_CORE 0x140
#define ANADIG_ANA_MISC0 0x150
#define ANADIG_USB1_CHRG_DETECT 0x1b0
#define ANADIG_USB2_CHRG_DETECT 0x210
#define ANADIG_ANA_MISC2 0x170
#define ANADIG_DIGPROG 0x260
#define ANADIG_DIGPROG_IMX6SL 0x280
#define ANADIG_DIGPROG_IMX7D 0x800
#define SRC_SBMR2 0x1c
#define BM_ANADIG_REG_2P5_ENABLE_WEAK_LINREG 0x40000
#define BM_ANADIG_REG_2P5_ENABLE_PULLDOWN 0x8
#define BM_ANADIG_REG_CORE_FET_ODRIVE 0x20000000
#define BM_ANADIG_REG_CORE_REG1 (0x1f << 9)
#define BM_ANADIG_REG_CORE_REG2 (0x1f << 18)
#define BP_ANADIG_REG_CORE_REG2 (18)
#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG 0x1000
#define BM_ANADIG_ANA_MISC0_V2_STOP_MODE_CONFIG 0x800
#define BM_ANADIG_ANA_MISC0_V3_STOP_MODE_CONFIG 0xc00
#define BM_ANADIG_ANA_MISC2_REG1_STEP_TIME (0x3 << 26)
#define BP_ANADIG_ANA_MISC2_REG1_STEP_TIME (26)
/* Below MISC0_DISCON_HIGH_SNVS is only for i.MX6SL */
#define BM_ANADIG_ANA_MISC0_DISCON_HIGH_SNVS 0x2000
/* Since i.MX6SX, DISCON_HIGH_SNVS is changed to bit 12 */
#define BM_ANADIG_ANA_MISC0_V2_DISCON_HIGH_SNVS 0x1000
#define BM_ANADIG_USB_CHRG_DETECT_CHK_CHRG_B 0x80000
#define BM_ANADIG_USB_CHRG_DETECT_EN_B 0x100000
#define LDO_RAMP_UP_UNIT_IN_CYCLES 64 /* 64 cycles per step */
#define LDO_RAMP_UP_FREQ_IN_MHZ 24 /* cycle based on 24M OSC */
static struct regmap *anatop;
static void imx_anatop_enable_weak2p5(bool enable)
{
u32 reg, val;
u32 reg, val, mask;
regmap_read(anatop, ANADIG_ANA_MISC0, &val);
if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() ||
cpu_is_imx6ulz() || cpu_is_imx6sll())
mask = BM_ANADIG_ANA_MISC0_V3_STOP_MODE_CONFIG;
else if (cpu_is_imx6sl())
mask = BM_ANADIG_ANA_MISC0_V2_STOP_MODE_CONFIG;
else
mask = BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG;
/* can only be enabled when stop_mode_config is clear. */
reg = ANADIG_REG_2P5;
reg += (enable && (val & BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG) == 0) ?
REG_SET : REG_CLR;
reg += (enable && (val & mask) == 0) ? REG_SET : REG_CLR;
regmap_write(anatop, reg, BM_ANADIG_REG_2P5_ENABLE_WEAK_LINREG);
}
@ -65,35 +91,89 @@ static inline void imx_anatop_enable_2p5_pulldown(bool enable)
static inline void imx_anatop_disconnect_high_snvs(bool enable)
{
regmap_write(anatop, ANADIG_ANA_MISC0 + (enable ? REG_SET : REG_CLR),
BM_ANADIG_ANA_MISC0_DISCON_HIGH_SNVS);
if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() ||
cpu_is_imx6ulz() || cpu_is_imx6sll())
regmap_write(anatop, ANADIG_ANA_MISC0 +
(enable ? REG_SET : REG_CLR),
BM_ANADIG_ANA_MISC0_V2_DISCON_HIGH_SNVS);
else
regmap_write(anatop, ANADIG_ANA_MISC0 +
(enable ? REG_SET : REG_CLR),
BM_ANADIG_ANA_MISC0_DISCON_HIGH_SNVS);
}
static void imx_anatop_disable_pu(bool off)
{
u32 val, soc, delay;
if (off) {
regmap_read(anatop, ANADIG_REG_CORE, &val);
val &= ~BM_ANADIG_REG_CORE_REG1;
regmap_write(anatop, ANADIG_REG_CORE, val);
} else {
/* track vddpu with vddsoc */
regmap_read(anatop, ANADIG_REG_CORE, &val);
soc = val & BM_ANADIG_REG_CORE_REG2;
val &= ~BM_ANADIG_REG_CORE_REG1;
val |= soc >> 9;
regmap_write(anatop, ANADIG_REG_CORE, val);
/* wait PU LDO ramp */
regmap_read(anatop, ANADIG_ANA_MISC2, &val);
val &= BM_ANADIG_ANA_MISC2_REG1_STEP_TIME;
val >>= BP_ANADIG_ANA_MISC2_REG1_STEP_TIME;
delay = (soc >> BP_ANADIG_REG_CORE_REG2) *
(LDO_RAMP_UP_UNIT_IN_CYCLES << val) /
LDO_RAMP_UP_FREQ_IN_MHZ + 1;
udelay(delay);
}
}
void imx_anatop_pre_suspend(void)
{
if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2)
imx_anatop_enable_2p5_pulldown(true);
else
imx_anatop_enable_weak2p5(true);
if (cpu_is_imx7d()) {
/* PLL and PFDs overwrite set */
regmap_write(anatop, ANADIG_ARM_PLL + REG_SET, 1 << 20);
regmap_write(anatop, ANADIG_DDR_PLL + REG_SET, 1 << 19);
regmap_write(anatop, ANADIG_SYS_PLL + REG_SET, 0x1ff << 17);
regmap_write(anatop, ANADIG_ENET_PLL + REG_SET, 1 << 13);
regmap_write(anatop, ANADIG_AUDIO_PLL + REG_SET, 1 << 24);
regmap_write(anatop, ANADIG_VIDEO_PLL + REG_SET, 1 << 24);
return;
}
if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0)
imx_anatop_disable_pu(true);
imx_anatop_enable_weak2p5(true);
imx_anatop_enable_fet_odrive(true);
if (cpu_is_imx6sl())
if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() ||
cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll())
imx_anatop_disconnect_high_snvs(true);
}
void imx_anatop_post_resume(void)
{
if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2)
imx_anatop_enable_2p5_pulldown(false);
else
imx_anatop_enable_weak2p5(false);
if (cpu_is_imx7d()) {
/* PLL and PFDs overwrite clear */
regmap_write(anatop, ANADIG_ARM_PLL + REG_CLR, 1 << 20);
regmap_write(anatop, ANADIG_DDR_PLL + REG_CLR, 1 << 19);
regmap_write(anatop, ANADIG_SYS_PLL + REG_CLR, 0x1ff << 17);
regmap_write(anatop, ANADIG_ENET_PLL + REG_CLR, 1 << 13);
regmap_write(anatop, ANADIG_AUDIO_PLL + REG_CLR, 1 << 24);
regmap_write(anatop, ANADIG_VIDEO_PLL + REG_CLR, 1 << 24);
return;
}
if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0)
imx_anatop_disable_pu(false);
imx_anatop_enable_weak2p5(false);
imx_anatop_enable_fet_odrive(false);
if (cpu_is_imx6sl())
if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() ||
cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll())
imx_anatop_disconnect_high_snvs(false);
}
static void imx_anatop_usb_chrg_detect_disable(void)
@ -110,10 +190,11 @@ void __init imx_init_revision_from_anatop(void)
{
struct device_node *np;
void __iomem *anatop_base;
void __iomem *src_base;
unsigned int revision;
u32 digprog;
u32 digprog, sbmr2 = 0;
u16 offset = ANADIG_DIGPROG;
u8 major_part, minor_part;
u16 major_part, minor_part;
np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop");
anatop_base = of_iomap(np, 0);
@ -125,6 +206,20 @@ void __init imx_init_revision_from_anatop(void)
digprog = readl_relaxed(anatop_base + offset);
iounmap(anatop_base);
if ((digprog >> 16) == MXC_CPU_IMX6ULL) {
np = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-src");
if (np) {
src_base = of_iomap(np, 0);
WARN_ON(!src_base);
sbmr2 = readl_relaxed(src_base + 0x1c);
iounmap(src_base);
}
if (sbmr2 & (1 << 6)) {
digprog &= ~(0xff << 16);
digprog |= (MXC_CPU_IMX6ULZ << 16);
}
}
/*
* On i.MX7D digprog value match linux version format, so
* it needn't map again and we can use register value directly.
@ -144,24 +239,6 @@ void __init imx_init_revision_from_anatop(void)
major_part = (digprog >> 8) & 0xf;
minor_part = digprog & 0xf;
revision = ((major_part + 1) << 4) | minor_part;
if ((digprog >> 16) == MXC_CPU_IMX6ULL) {
void __iomem *src_base;
u32 sbmr2;
np = of_find_compatible_node(NULL, NULL,
"fsl,imx6ul-src");
src_base = of_iomap(np, 0);
WARN_ON(!src_base);
sbmr2 = readl_relaxed(src_base + SRC_SBMR2);
iounmap(src_base);
/* src_sbmr2 bit 6 is to identify if it is i.MX6ULZ */
if (sbmr2 & (1 << 6)) {
digprog &= ~(0xff << 16);
digprog |= (MXC_CPU_IMX6ULZ << 16);
}
}
}
mxc_set_cpu_type(digprog >> 16 & 0xff);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,772 @@
/*
* Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
* 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
*/
/*!
* @file busfreq_ddr3.c
*
* @brief iMX6 DDR3 frequency change specific file.
*
* @ingroup PM
*/
#include <asm/cacheflush.h>
#include <asm/fncpy.h>
#include <asm/io.h>
#include <asm/mach/map.h>
#include <asm/mach-types.h>
#include <asm/tlb.h>
#include <linux/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include "hardware.h"
#include "common.h"
#define SMP_WFE_CODE_SIZE 0x400
#define MIN_DLL_ON_FREQ 333000000
#define MAX_DLL_OFF_FREQ 125000000
#define MMDC0_MPMUR0 0x8b8
#define MMDC0_MPMUR0_OFFSET 16
#define MMDC0_MPMUR0_MASK 0x3ff
/*
* This structure is for passing necessary data for low level ocram
* busfreq code(arch/arm/mach-imx/ddr3_freq_imx6.S), if this struct
* definition is changed, the offset definition in
* arch/arm/mach-imx/ddr3_freq_imx6.S must be also changed accordingly,
* otherwise, the busfreq change function will be broken!
*
* This structure will be placed in front of the asm code on ocram.
*/
struct imx6_busfreq_info {
u32 freq;
void *ddr_settings;
u32 dll_off;
void *iomux_offsets;
u32 mu_delay_val;
} __aligned(8);
static struct imx6_busfreq_info *imx6_busfreq_info;
/* DDR settings */
static unsigned long (*iram_ddr_settings)[2];
static unsigned long (*normal_mmdc_settings)[2];
static unsigned long (*iram_iomux_settings)[2];
static void __iomem *mmdc_base;
static void __iomem *iomux_base;
static void __iomem *gic_dist_base;
static int ddr_settings_size;
static int iomux_settings_size;
static int curr_ddr_rate;
void (*imx6_up_change_ddr_freq)(struct imx6_busfreq_info *busfreq_info);
extern void imx6_up_ddr3_freq_change(struct imx6_busfreq_info *busfreq_info);
void (*imx7d_change_ddr_freq)(u32 freq) = NULL;
extern void imx7d_ddr3_freq_change(u32 freq);
extern void imx_lpddr3_freq_change(u32 freq);
void (*mx6_change_ddr_freq)(u32 freq, void *ddr_settings,
bool dll_mode, void *iomux_offsets) = NULL;
extern unsigned int ddr_normal_rate;
extern int low_bus_freq_mode;
extern int audio_bus_freq_mode;
extern void mx6_ddr3_freq_change(u32 freq, void *ddr_settings,
bool dll_mode, void *iomux_offsets);
extern unsigned long save_ttbr1(void);
extern void restore_ttbr1(unsigned long ttbr1);
extern unsigned long ddr_freq_change_iram_base;
extern unsigned long ddr_freq_change_total_size;
extern unsigned long iram_tlb_phys_addr;
extern unsigned long mx6_ddr3_freq_change_start asm("mx6_ddr3_freq_change_start");
extern unsigned long mx6_ddr3_freq_change_end asm("mx6_ddr3_freq_change_end");
extern unsigned long imx6_up_ddr3_freq_change_start asm("imx6_up_ddr3_freq_change_start");
extern unsigned long imx6_up_ddr3_freq_change_end asm("imx6_up_ddr3_freq_change_end");
#ifdef CONFIG_SMP
static unsigned long wfe_freq_change_iram_base;
volatile u32 *wait_for_ddr_freq_update;
static unsigned int online_cpus;
static u32 *irqs_used;
void (*wfe_change_ddr_freq)(u32 cpuid, u32 *ddr_freq_change_done);
void (*imx7_wfe_change_ddr_freq)(u32 cpuid, u32 ocram_base);
extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done);
extern void imx7_smp_wfe(u32 cpuid, u32 ocram_base);
extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start");
extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end");
extern void __iomem *scu_base;
#endif
unsigned long ddr3_dll_mx6sx[][2] = {
{0x0c, 0x0},
{0x10, 0x0},
{0x1C, 0x04008032},
{0x1C, 0x00048031},
{0x1C, 0x05208030},
{0x1C, 0x04008040},
{0x818, 0x0},
{0x18, 0x0},
};
unsigned long ddr3_calibration_mx6sx[][2] = {
{0x83c, 0x0},
{0x840, 0x0},
{0x848, 0x0},
{0x850, 0x0},
};
unsigned long iomux_offsets_mx6sx[][2] = {
{0x330, 0x0},
{0x334, 0x0},
{0x338, 0x0},
{0x33c, 0x0},
};
unsigned long iomux_offsets_mx6ul[][2] = {
{0x280, 0x0},
{0x284, 0x0},
};
unsigned long ddr3_dll_mx6q[][2] = {
{0x0c, 0x0},
{0x10, 0x0},
{0x1C, 0x04088032},
{0x1C, 0x0408803a},
{0x1C, 0x08408030},
{0x1C, 0x08408038},
{0x818, 0x0},
{0x18, 0x0},
};
unsigned long ddr3_calibration[][2] = {
{0x83c, 0x0},
{0x840, 0x0},
{0x483c, 0x0},
{0x4840, 0x0},
{0x848, 0x0},
{0x4848, 0x0},
{0x850, 0x0},
{0x4850, 0x0},
};
unsigned long iomux_offsets_mx6q[][2] = {
{0x5A8, 0x0},
{0x5B0, 0x0},
{0x524, 0x0},
{0x51C, 0x0},
{0x518, 0x0},
{0x50C, 0x0},
{0x5B8, 0x0},
{0x5C0, 0x0},
};
unsigned long ddr3_dll_mx6dl[][2] = {
{0x0c, 0x0},
{0x10, 0x0},
{0x1C, 0x04008032},
{0x1C, 0x0400803a},
{0x1C, 0x07208030},
{0x1C, 0x07208038},
{0x818, 0x0},
{0x18, 0x0},
};
unsigned long iomux_offsets_mx6dl[][2] = {
{0x4BC, 0x0},
{0x4C0, 0x0},
{0x4C4, 0x0},
{0x4C8, 0x0},
{0x4CC, 0x0},
{0x4D0, 0x0},
{0x4D4, 0x0},
{0x4D8, 0x0},
};
int can_change_ddr_freq(void)
{
return 1;
}
#ifdef CONFIG_SMP
/*
* each active core apart from the one changing
* the DDR frequency will execute this function.
* the rest of the cores have to remain in WFE
* state until the frequency is changed.
*/
static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id)
{
u32 me;
me = smp_processor_id();
#ifdef CONFIG_LOCAL_TIMERS
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER,
&me);
#endif
if (cpu_is_imx7d())
imx7_wfe_change_ddr_freq(0x8 * me,
(u32)ddr_freq_change_iram_base);
else
wfe_change_ddr_freq(0xff << (me * 8),
(u32 *)&iram_iomux_settings[0][1]);
#ifdef CONFIG_LOCAL_TIMERS
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT,
&me);
#endif
return IRQ_HANDLED;
}
#endif
/* change the DDR frequency. */
int update_ddr_freq_imx_smp(int ddr_rate)
{
int me = 0;
unsigned long ttbr1;
bool dll_off = false;
int i;
#ifdef CONFIG_SMP
unsigned int reg = 0;
int cpu = 0;
#endif
int mode = get_bus_freq_mode();
if (!can_change_ddr_freq())
return -1;
if (ddr_rate == curr_ddr_rate)
return 0;
printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate);
if (cpu_is_imx6()) {
if ((mode == BUS_FREQ_LOW) || (mode == BUS_FREQ_AUDIO))
dll_off = true;
iram_ddr_settings[0][0] = ddr_settings_size;
iram_iomux_settings[0][0] = iomux_settings_size;
if (ddr_rate == ddr_normal_rate) {
for (i = 0; i < iram_ddr_settings[0][0]; i++) {
iram_ddr_settings[i + 1][0] =
normal_mmdc_settings[i][0];
iram_ddr_settings[i + 1][1] =
normal_mmdc_settings[i][1];
}
}
}
/* ensure that all Cores are in WFE. */
local_irq_disable();
#ifdef CONFIG_SMP
me = smp_processor_id();
/* Make sure all the online cores are active */
while (1) {
bool not_exited_busfreq = false;
u32 reg = 0;
for_each_online_cpu(cpu) {
if (cpu_is_imx7d())
reg = *(wait_for_ddr_freq_update + 1);
else if (cpu_is_imx6())
reg = __raw_readl(scu_base + 0x08);
if (reg & (0x02 << (cpu * 8)))
not_exited_busfreq = true;
}
if (!not_exited_busfreq)
break;
}
wmb();
*wait_for_ddr_freq_update = 1;
dsb();
if (cpu_is_imx7d())
online_cpus = *(wait_for_ddr_freq_update + 1);
else if (cpu_is_imx6())
online_cpus = readl_relaxed(scu_base + 0x08);
for_each_online_cpu(cpu) {
*((char *)(&online_cpus) + (u8)cpu) = 0x02;
if (cpu != me) {
/* set the interrupt to be pending in the GIC. */
reg = 1 << (irqs_used[cpu] % 32);
writel_relaxed(reg, gic_dist_base + GIC_DIST_PENDING_SET
+ (irqs_used[cpu] / 32) * 4);
}
}
/* Wait for the other active CPUs to idle */
while (1) {
u32 reg = 0;
if (cpu_is_imx7d())
reg = *(wait_for_ddr_freq_update + 1);
else if (cpu_is_imx6())
reg = readl_relaxed(scu_base + 0x08);
reg |= (0x02 << (me * 8));
if (reg == online_cpus)
break;
}
#endif
/* Ensure iram_tlb_phys_addr is flushed to DDR. */
__cpuc_flush_dcache_area(&iram_tlb_phys_addr,
sizeof(iram_tlb_phys_addr));
if (cpu_is_imx6())
outer_clean_range(__pa(&iram_tlb_phys_addr),
__pa(&iram_tlb_phys_addr + 1));
ttbr1 = save_ttbr1();
/* Now we can change the DDR frequency. */
if (cpu_is_imx7d())
imx7d_change_ddr_freq(ddr_rate);
else if (cpu_is_imx6())
mx6_change_ddr_freq(ddr_rate, iram_ddr_settings,
dll_off, iram_iomux_settings);
restore_ttbr1(ttbr1);
curr_ddr_rate = ddr_rate;
#ifdef CONFIG_SMP
wmb();
/* DDR frequency change is done . */
*wait_for_ddr_freq_update = 0;
dsb();
/* wake up all the cores. */
sev();
#endif
local_irq_enable();
printk(KERN_DEBUG "Bus freq set to %d done! cpu=%d\n", ddr_rate, me);
return 0;
}
/* Used by i.MX6SX/i.MX6UL for updating the ddr frequency */
int update_ddr_freq_imx6_up(int ddr_rate)
{
int i;
bool dll_off = false;
unsigned long ttbr1;
int mode = get_bus_freq_mode();
if (ddr_rate == curr_ddr_rate)
return 0;
printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate);
if ((mode == BUS_FREQ_LOW) || (mode == BUS_FREQ_AUDIO))
dll_off = true;
imx6_busfreq_info->dll_off = dll_off;
iram_ddr_settings[0][0] = ddr_settings_size;
iram_iomux_settings[0][0] = iomux_settings_size;
for (i = 0; i < iram_ddr_settings[0][0]; i++) {
iram_ddr_settings[i + 1][0] =
normal_mmdc_settings[i][0];
iram_ddr_settings[i + 1][1] =
normal_mmdc_settings[i][1];
}
local_irq_disable();
ttbr1 = save_ttbr1();
imx6_busfreq_info->freq = ddr_rate;
imx6_busfreq_info->ddr_settings = iram_ddr_settings;
imx6_busfreq_info->iomux_offsets = iram_iomux_settings;
imx6_busfreq_info->mu_delay_val = ((readl_relaxed(mmdc_base + MMDC0_MPMUR0)
>> MMDC0_MPMUR0_OFFSET) & MMDC0_MPMUR0_MASK);
imx6_up_change_ddr_freq(imx6_busfreq_info);
restore_ttbr1(ttbr1);
curr_ddr_rate = ddr_rate;
local_irq_enable();
printk(KERN_DEBUG "Bus freq set to %d done!\n", ddr_rate);
return 0;
}
int init_ddrc_ddr_settings(struct platform_device *busfreq_pdev)
{
int ddr_type = imx_ddrc_get_ddr_type();
#ifdef CONFIG_SMP
struct device_node *node;
u32 cpu;
struct device *dev = &busfreq_pdev->dev;
int err;
struct irq_data *d;
node = of_find_compatible_node(NULL, NULL, "arm,cortex-a7-gic");
if (!node) {
printk(KERN_ERR "failed to find imx7d-a7-gic device tree data!\n");
return -EINVAL;
}
gic_dist_base = of_iomap(node, 0);
WARN(!gic_dist_base, "unable to map gic dist registers\n");
irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(),
GFP_KERNEL);
for_each_online_cpu(cpu) {
int irq;
/*
* set up a reserved interrupt to get all
* the active cores into a WFE state
* before changing the DDR frequency.
*/
irq = platform_get_irq(busfreq_pdev, cpu);
err = request_irq(irq, wait_in_wfe_irq,
IRQF_PERCPU, "ddrc", NULL);
if (err) {
dev_err(dev,
"Busfreq:request_irq failed %d, err = %d\n",
irq, err);
return err;
}
err = irq_set_affinity(irq, cpumask_of(cpu));
if (err) {
dev_err(dev,
"Busfreq: Cannot set irq affinity irq=%d\n",
irq);
return err;
}
d = irq_get_irq_data(irq);
irqs_used[cpu] = d->hwirq + 32;
}
/* Store the variable used to communicate between cores */
wait_for_ddr_freq_update = (u32 *)ddr_freq_change_iram_base;
imx7_wfe_change_ddr_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base + 0x8,
&imx7_smp_wfe, SMP_WFE_CODE_SIZE - 0x8);
#endif
if (ddr_type == IMX_DDR_TYPE_DDR3)
imx7d_change_ddr_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base + SMP_WFE_CODE_SIZE,
&imx7d_ddr3_freq_change,
MX7_BUSFREQ_OCRAM_SIZE - SMP_WFE_CODE_SIZE);
else if (ddr_type == IMX_DDR_TYPE_LPDDR3
|| ddr_type == IMX_DDR_TYPE_LPDDR2)
imx7d_change_ddr_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base +
SMP_WFE_CODE_SIZE,
&imx_lpddr3_freq_change,
MX7_BUSFREQ_OCRAM_SIZE - SMP_WFE_CODE_SIZE);
curr_ddr_rate = ddr_normal_rate;
return 0;
}
/* Used by i.MX6SX/i.MX6UL for mmdc setting init. */
int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev)
{
int i;
struct device_node *node;
unsigned long ddr_code_size;
node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc");
if (!node) {
printk(KERN_ERR "failed to find mmdc device tree data!\n");
return -EINVAL;
}
mmdc_base = of_iomap(node, 0);
WARN(!mmdc_base, "unable to map mmdc registers\n");
if (cpu_is_imx6sx())
node = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-iomuxc");
else
node = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-iomuxc");
if (!node) {
printk(KERN_ERR "failed to find iomuxc device tree data!\n");
return -EINVAL;
}
iomux_base = of_iomap(node, 0);
WARN(!iomux_base, "unable to map iomux registers\n");
ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6sx) +
ARRAY_SIZE(ddr3_calibration_mx6sx);
normal_mmdc_settings = kmalloc((ddr_settings_size * 8), GFP_KERNEL);
memcpy(normal_mmdc_settings, ddr3_dll_mx6sx,
sizeof(ddr3_dll_mx6sx));
memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6sx)),
ddr3_calibration_mx6sx, sizeof(ddr3_calibration_mx6sx));
/* store the original DDR settings at boot. */
for (i = 0; i < ddr_settings_size; i++) {
/*
* writes via command mode register cannot be read back.
* hence hardcode them in the initial static array.
* this may require modification on a per customer basis.
*/
if (normal_mmdc_settings[i][0] != 0x1C)
normal_mmdc_settings[i][1] =
readl_relaxed(mmdc_base
+ normal_mmdc_settings[i][0]);
}
if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz())
iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6ul);
else
iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6sx);
ddr_code_size = (&imx6_up_ddr3_freq_change_end -&imx6_up_ddr3_freq_change_start) *4 +
sizeof(*imx6_busfreq_info);
imx6_busfreq_info = (struct imx6_busfreq_info *)ddr_freq_change_iram_base;
imx6_up_change_ddr_freq = (void *)fncpy((void *)ddr_freq_change_iram_base + sizeof(*imx6_busfreq_info),
&imx6_up_ddr3_freq_change, ddr_code_size - sizeof(*imx6_busfreq_info));
/*
* Store the size of the array in iRAM also,
* increase the size by 8 bytes.
*/
iram_iomux_settings = (void *)(ddr_freq_change_iram_base + ddr_code_size);
iram_ddr_settings = iram_iomux_settings + (iomux_settings_size * 8) + 8;
if ((ddr_code_size + (iomux_settings_size + ddr_settings_size) * 8 + 16)
> ddr_freq_change_total_size) {
printk(KERN_ERR "Not enough memory allocated for DDR Frequency change code.\n");
return EINVAL;
}
for (i = 0; i < iomux_settings_size; i++) {
if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz()) {
iomux_offsets_mx6ul[i][1] =
readl_relaxed(iomux_base +
iomux_offsets_mx6ul[i][0]);
iram_iomux_settings[i + 1][0] =
iomux_offsets_mx6ul[i][0];
iram_iomux_settings[i + 1][1] =
iomux_offsets_mx6ul[i][1];
} else {
iomux_offsets_mx6sx[i][1] =
readl_relaxed(iomux_base +
iomux_offsets_mx6sx[i][0]);
iram_iomux_settings[i + 1][0] =
iomux_offsets_mx6sx[i][0];
iram_iomux_settings[i + 1][1] =
iomux_offsets_mx6sx[i][1];
}
}
curr_ddr_rate = ddr_normal_rate;
return 0;
}
int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *busfreq_pdev)
{
int i;
struct device_node *node;
unsigned long ddr_code_size;
unsigned long wfe_code_size = 0;
#ifdef CONFIG_SMP
u32 cpu;
struct device *dev = &busfreq_pdev->dev;
int err;
struct irq_data *d;
#endif
node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc-combine");
if (!node) {
printk(KERN_ERR "failed to find imx6q-mmdc device tree data!\n");
return -EINVAL;
}
mmdc_base = of_iomap(node, 0);
WARN(!mmdc_base, "unable to map mmdc registers\n");
node = NULL;
if (cpu_is_imx6q())
node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc");
if (cpu_is_imx6dl())
node = of_find_compatible_node(NULL, NULL,
"fsl,imx6dl-iomuxc");
if (!node) {
printk(KERN_ERR "failed to find imx6q-iomux device tree data!\n");
return -EINVAL;
}
iomux_base = of_iomap(node, 0);
WARN(!iomux_base, "unable to map iomux registers\n");
node = NULL;
node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-gic");
if (!node) {
printk(KERN_ERR "failed to find imx6q-a9-gic device tree data!\n");
return -EINVAL;
}
gic_dist_base = of_iomap(node, 0);
WARN(!gic_dist_base, "unable to map gic dist registers\n");
if (cpu_is_imx6q())
ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6q) +
ARRAY_SIZE(ddr3_calibration);
if (cpu_is_imx6dl())
ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6dl) +
ARRAY_SIZE(ddr3_calibration);
normal_mmdc_settings = kmalloc((ddr_settings_size * 8), GFP_KERNEL);
if (cpu_is_imx6q()) {
memcpy(normal_mmdc_settings, ddr3_dll_mx6q,
sizeof(ddr3_dll_mx6q));
memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6q)),
ddr3_calibration, sizeof(ddr3_calibration));
}
if (cpu_is_imx6dl()) {
memcpy(normal_mmdc_settings, ddr3_dll_mx6dl,
sizeof(ddr3_dll_mx6dl));
memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6dl)),
ddr3_calibration, sizeof(ddr3_calibration));
}
/* store the original DDR settings at boot. */
for (i = 0; i < ddr_settings_size; i++) {
/*
* writes via command mode register cannot be read back.
* hence hardcode them in the initial static array.
* this may require modification on a per customer basis.
*/
if (normal_mmdc_settings[i][0] != 0x1C)
normal_mmdc_settings[i][1] =
readl_relaxed(mmdc_base
+ normal_mmdc_settings[i][0]);
}
#ifdef CONFIG_SMP
irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(),
GFP_KERNEL);
for_each_online_cpu(cpu) {
int irq;
/*
* set up a reserved interrupt to get all
* the active cores into a WFE state
* before changing the DDR frequency.
*/
irq = platform_get_irq(busfreq_pdev, cpu);
err = request_irq(irq, wait_in_wfe_irq,
IRQF_PERCPU, "mmdc_1", NULL);
if (err) {
dev_err(dev,
"Busfreq:request_irq failed %d, err = %d\n",
irq, err);
return err;
}
err = irq_set_affinity(irq, cpumask_of(cpu));
if (err) {
dev_err(dev,
"Busfreq: Cannot set irq affinity irq=%d,\n",
irq);
return err;
}
d = irq_get_irq_data(irq);
irqs_used[cpu] = d->hwirq + 32;
}
#endif
iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6q);
ddr_code_size = (&mx6_ddr3_freq_change_end -
&mx6_ddr3_freq_change_start) * 4;
mx6_change_ddr_freq = (void *)fncpy((void *)ddr_freq_change_iram_base,
&mx6_ddr3_freq_change, ddr_code_size);
/*
* Store the size of the array in iRAM also,
* increase the size by 8 bytes.
*/
iram_iomux_settings = (void *)(ddr_freq_change_iram_base +
ddr_code_size);
iram_ddr_settings = iram_iomux_settings + (iomux_settings_size * 8) + 8;
#ifdef CONFIG_SMP
wfe_freq_change_iram_base = (unsigned long)((u32 *)iram_ddr_settings +
(ddr_settings_size * 8) + 8);
if (wfe_freq_change_iram_base & (FNCPY_ALIGN - 1))
wfe_freq_change_iram_base += FNCPY_ALIGN -
((uintptr_t)wfe_freq_change_iram_base % (FNCPY_ALIGN));
wfe_code_size = (&wfe_smp_freq_change_end -
&wfe_smp_freq_change_start) *4;
wfe_change_ddr_freq = (void *)fncpy((void *)wfe_freq_change_iram_base,
&wfe_smp_freq_change, wfe_code_size);
/*
* Store the variable used to communicate
* between cores in a non-cacheable IRAM area
*/
wait_for_ddr_freq_update = (u32 *)&iram_iomux_settings[0][1];
#endif
if ((ddr_code_size + wfe_code_size + (iomux_settings_size +
ddr_settings_size) * 8 + 16)
> ddr_freq_change_total_size) {
printk(KERN_ERR "Not enough memory for DDR Freq scale.\n");
return EINVAL;
}
if (cpu_is_imx6q()) {
/* store the IOMUX settings at boot. */
for (i = 0; i < iomux_settings_size; i++) {
iomux_offsets_mx6q[i][1] =
readl_relaxed(iomux_base +
iomux_offsets_mx6q[i][0]);
iram_iomux_settings[i + 1][0] =
iomux_offsets_mx6q[i][0];
iram_iomux_settings[i + 1][1] =
iomux_offsets_mx6q[i][1];
}
}
if (cpu_is_imx6dl()) {
for (i = 0; i < iomux_settings_size; i++) {
iomux_offsets_mx6dl[i][1] =
readl_relaxed(iomux_base +
iomux_offsets_mx6dl[i][0]);
iram_iomux_settings[i + 1][0] =
iomux_offsets_mx6dl[i][0];
iram_iomux_settings[i + 1][1] =
iomux_offsets_mx6dl[i][1];
}
}
curr_ddr_rate = ddr_normal_rate;
return 0;
}

View File

@ -0,0 +1,374 @@
/*
* Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved.
* Copyright 2017 NXP.
*/
/*
* 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
*/
/*!
* @file busfreq_lpddr2.c
*
* @brief iMX6 LPDDR2 frequency change specific file.
*
* @ingroup PM
*/
#include <asm/cacheflush.h>
#include <asm/fncpy.h>
#include <asm/io.h>
#include <asm/mach/map.h>
#include <asm/mach-types.h>
#include <asm/tlb.h>
#include <linux/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/slab.h>
#include "common.h"
#include "hardware.h"
static struct device *busfreq_dev;
static int curr_ddr_rate;
static DEFINE_SPINLOCK(freq_lock);
void (*mx6_change_lpddr2_freq)(u32 ddr_freq, int bus_freq_mode) = NULL;
extern unsigned int ddr_normal_rate;
extern void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode);
extern void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode);
extern void imx6sll_lpddr2_freq_change(u32 freq, int bus_freq_mode);
extern unsigned long save_ttbr1(void);
extern void restore_ttbr1(unsigned long ttbr1);
extern void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings);
extern unsigned long ddr_freq_change_iram_base;
extern unsigned long imx6_lpddr2_freq_change_start asm("imx6_lpddr2_freq_change_start");
extern unsigned long imx6_lpddr2_freq_change_end asm("imx6_lpddr2_freq_change_end");
extern unsigned long mx6q_lpddr2_freq_change_start asm("mx6q_lpddr2_freq_change_start");
extern unsigned long mx6q_lpddr2_freq_change_end asm("mx6q_lpddr2_freq_change_end");
extern unsigned long iram_tlb_phys_addr;
struct mmdc_settings_info {
u32 size;
void *settings;
int freq;
} __aligned(8);
static struct mmdc_settings_info *mmdc_settings_info;
void (*mx6_change_lpddr2_freq_smp)(u32 ddr_freq, struct mmdc_settings_info
*mmdc_settings_info) = NULL;
static int mmdc_settings_size;
static unsigned long (*mmdc_settings)[2];
static unsigned long (*iram_mmdc_settings)[2];
static unsigned long *iram_settings_size;
static unsigned long *iram_ddr_freq_chage;
unsigned long mmdc_timing_settings[][2] = {
{0x0C, 0x0}, /* mmdc_mdcfg0 */
{0x10, 0x0}, /* mmdc_mdcfg1 */
{0x14, 0x0}, /* mmdc_mdcfg2 */
{0x18, 0x0}, /* mmdc_mdmisc */
{0x38, 0x0}, /* mmdc_mdcfg3lp */
};
#ifdef CONFIG_SMP
volatile u32 *wait_for_lpddr2_freq_update;
static unsigned int online_cpus;
static u32 *irqs_used;
void (*wfe_change_lpddr2_freq)(u32 cpuid, u32 *ddr_freq_change_done);
extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done);
extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start");
extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end");
extern void __iomem *scu_base;
static void __iomem *gic_dist_base;
#endif
#ifdef CONFIG_SMP
static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id)
{
u32 me;
me = smp_processor_id();
#ifdef CONFIG_LOCAL_TIMERS
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &me);
#endif
wfe_change_lpddr2_freq(0xff << (me * 8),
(u32 *)ddr_freq_change_iram_base);
#ifdef CONFIG_LOCAL_TIMERS
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &me);
#endif
return IRQ_HANDLED;
}
#endif
/* change the DDR frequency. */
int update_lpddr2_freq(int ddr_rate)
{
unsigned long ttbr1, flags;
int mode = get_bus_freq_mode();
if (ddr_rate == curr_ddr_rate)
return 0;
printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate);
spin_lock_irqsave(&freq_lock, flags);
/*
* Flush the TLB, to ensure no TLB maintenance occurs
* when DDR is in self-refresh.
*/
ttbr1 = save_ttbr1();
/* Now change DDR frequency. */
if (cpu_is_imx6sl())
mx6_change_lpddr2_freq(ddr_rate,
(mode == BUS_FREQ_LOW || mode == BUS_FREQ_ULTRA_LOW) ? 1 : 0);
else
mx6_change_lpddr2_freq(ddr_rate,
(mode == BUS_FREQ_LOW || mode == BUS_FREQ_AUDIO) ? 1 : 0);
restore_ttbr1(ttbr1);
curr_ddr_rate = ddr_rate;
spin_unlock_irqrestore(&freq_lock, flags);
printk(KERN_DEBUG "\nBus freq set to %d done...\n", ddr_rate);
return 0;
}
int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev)
{
unsigned long ddr_code_size;
busfreq_dev = &busfreq_pdev->dev;
ddr_code_size = SZ_4K;
if (cpu_is_imx6sl())
mx6_change_lpddr2_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base,
&mx6_lpddr2_freq_change, ddr_code_size);
if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() ||
cpu_is_imx6ulz())
mx6_change_lpddr2_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base,
&imx6_up_lpddr2_freq_change, ddr_code_size);
if (cpu_is_imx6sll())
mx6_change_lpddr2_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base,
&imx6sll_lpddr2_freq_change, ddr_code_size);
curr_ddr_rate = ddr_normal_rate;
return 0;
}
int update_lpddr2_freq_smp(int ddr_rate)
{
unsigned long ttbr1;
int i, me = 0;
#ifdef CONFIG_SMP
int cpu = 0;
u32 reg = 0;
#endif
if (ddr_rate == curr_ddr_rate)
return 0;
printk(KERN_DEBUG "Bus freq set to %d start...\n", ddr_rate);
for (i=0; i < mmdc_settings_size; i++) {
iram_mmdc_settings[i][0] = mmdc_settings[i][0];
iram_mmdc_settings[i][1] = mmdc_settings[i][1];
}
mmdc_settings_info->size = mmdc_settings_size;
mmdc_settings_info->settings = iram_mmdc_settings;
mmdc_settings_info->freq = curr_ddr_rate;
/* ensure that all Cores are in WFE. */
local_irq_disable();
#ifdef CONFIG_SMP
me = smp_processor_id();
/* Make sure all the online cores are active */
while (1) {
bool not_exited_busfreq = false;
for_each_online_cpu(cpu) {
reg = __raw_readl(scu_base + 0x08);
if (reg & (0x02 << (cpu * 8)))
not_exited_busfreq = true;
}
if (!not_exited_busfreq)
break;
}
wmb();
*wait_for_lpddr2_freq_update = 1;
dsb();
online_cpus = readl_relaxed(scu_base + 0x08);
for_each_online_cpu(cpu) {
*((char *)(&online_cpus) + (u8)cpu) = 0x02;
if (cpu != me) {
reg = 1 << (irqs_used[cpu] % 32);
writel_relaxed(reg, gic_dist_base + GIC_DIST_PENDING_SET
+ (irqs_used[cpu] / 32) * 4);
}
}
/* Wait for the other active CPUs to idle */
while (1) {
reg = 0;
reg = readl_relaxed(scu_base + 0x08);
reg |= (0x02 << (me * 8));
if (reg == online_cpus)
break;
}
#endif
/* Ensure iram_tlb_phys_addr is flushed to DDR. */
__cpuc_flush_dcache_area(&iram_tlb_phys_addr,
sizeof(iram_tlb_phys_addr));
outer_clean_range(__pa(&iram_tlb_phys_addr),
__pa(&iram_tlb_phys_addr + 1));
/*
* Flush the TLB, to ensure no TLB maintenance occurs
* when DDR is in self-refresh.
*/
ttbr1 = save_ttbr1();
curr_ddr_rate = ddr_rate;
/* Now change DDR frequency. */
mx6_change_lpddr2_freq_smp(ddr_rate, mmdc_settings_info);
restore_ttbr1(ttbr1);
#ifdef CONFIG_SMP
wmb();
/* DDR frequency change is done . */
*wait_for_lpddr2_freq_update = 0;
dsb();
/* wake up all the cores. */
sev();
#endif
local_irq_enable();
printk(KERN_DEBUG "Bus freq set to %d done! cpu=%d\n", ddr_rate, me);
return 0;
}
int init_mmdc_lpddr2_settings_mx6q(struct platform_device *busfreq_pdev)
{
struct device *dev = &busfreq_pdev->dev;
unsigned long ddr_code_size = 0;
unsigned long wfe_code_size = 0;
struct device_node *node;
void __iomem *mmdc_base;
int i;
#ifdef CONFIG_SMP
struct irq_data *d;
u32 cpu;
int err;
#endif
node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc");
if (!node) {
printk(KERN_ERR "failed to find mmdc device tree data!\n");
return -EINVAL;
}
mmdc_base = of_iomap(node, 0);
if (!mmdc_base) {
dev_err(dev, "unable to map mmdc registers\n");
return -EINVAL;
}
mmdc_settings_size = ARRAY_SIZE(mmdc_timing_settings);
mmdc_settings = kmalloc((mmdc_settings_size * 8), GFP_KERNEL);
memcpy(mmdc_settings, mmdc_timing_settings,
sizeof(mmdc_timing_settings));
#ifdef CONFIG_SMP
node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-gic");
if (!node) {
printk(KERN_ERR "failed to find imx6q-a9-gic device tree data!\n");
return -EINVAL;
}
gic_dist_base = of_iomap(node, 0);
WARN(!gic_dist_base, "unable to map gic dist registers\n");
irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(),
GFP_KERNEL);
for_each_online_cpu(cpu) {
int irq = platform_get_irq(busfreq_pdev, cpu);
err = request_irq(irq, wait_in_wfe_irq, IRQF_PERCPU,
"mmdc_1", NULL);
if (err) {
dev_err(dev,
"Busfreq:request_irq failed %d, err = %d\n",
irq, err);
return err;
}
err = irq_set_affinity(irq, cpumask_of(cpu));
if (err) {
dev_err(dev,
"Busfreq: Cannot set irq affinity irq=%d,\n",
irq);
return err;
}
d = irq_get_irq_data(irq);
irqs_used[cpu] = d->hwirq + 32;
}
/* Stoange_iram_basee the variable used to communicate between cores in
* a non-cacheable IRAM area */
wait_for_lpddr2_freq_update = (u32 *)ddr_freq_change_iram_base;
wfe_code_size = (&wfe_smp_freq_change_end - &wfe_smp_freq_change_start) *4;
wfe_change_lpddr2_freq = (void *)fncpy((void *)ddr_freq_change_iram_base + 0x8,
&wfe_smp_freq_change, wfe_code_size);
#endif
iram_settings_size = (void *)ddr_freq_change_iram_base + wfe_code_size + 0x8;
iram_mmdc_settings = (void *)iram_settings_size + sizeof(*mmdc_settings_info);
iram_ddr_freq_chage = (void *)iram_mmdc_settings + (mmdc_settings_size * 8) + 0x8;
mmdc_settings_info = (struct mmdc_settings_info *)iram_settings_size;
ddr_code_size = (&mx6q_lpddr2_freq_change_end -&mx6q_lpddr2_freq_change_start) *4;
mx6_change_lpddr2_freq_smp = (void *)fncpy(iram_ddr_freq_chage,
&mx6q_lpddr2_freq_change, ddr_code_size);
/* save initial mmdc boot timing settings */
for (i=0; i < mmdc_settings_size; i++)
mmdc_settings[i][1] = readl_relaxed(mmdc_base +
mmdc_settings[i][0]);
curr_ddr_rate = ddr_normal_rate;
return 0;
}

View File

@ -0,0 +1,161 @@
/*
* Copyright (C) 2015-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 <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_net.h>
#include <linux/slab.h>
#include "hardware.h"
unsigned long iram_tlb_base_addr;
unsigned long iram_tlb_phys_addr;
unsigned long save_ttbr1(void)
{
unsigned long lttbr1;
asm volatile(
".align 4\n"
"mrc p15, 0, %0, c2, c0, 1\n"
: "=r" (lttbr1)
);
return lttbr1;
}
void restore_ttbr1(unsigned long ttbr1)
{
asm volatile(
".align 4\n"
"mcr p15, 0, %0, c2, c0, 1\n"
: : "r" (ttbr1)
);
}
#define OCOTP_MAC_OFF (cpu_is_imx7d() ? 0x640 : 0x620)
#define OCOTP_MACn(n) (OCOTP_MAC_OFF + (n) * 0x10)
void __init imx6_enet_mac_init(const char *enet_compat, const char *ocotp_compat)
{
struct device_node *ocotp_np, *enet_np, *from = NULL;
void __iomem *base;
struct property *newmac;
u32 macaddr_low;
u32 macaddr_high = 0;
u32 macaddr1_high = 0;
u8 *macaddr;
int i, id;
for (i = 0; i < 2; i++) {
enet_np = of_find_compatible_node(from, NULL, enet_compat);
if (!enet_np)
return;
from = enet_np;
if (of_get_mac_address(enet_np))
goto put_enet_node;
id = of_alias_get_id(enet_np, "ethernet");
if (id < 0)
id = i;
ocotp_np = of_find_compatible_node(NULL, NULL, ocotp_compat);
if (!ocotp_np) {
pr_warn("failed to find ocotp node\n");
goto put_enet_node;
}
base = of_iomap(ocotp_np, 0);
if (!base) {
pr_warn("failed to map ocotp\n");
goto put_ocotp_node;
}
macaddr_low = readl_relaxed(base + OCOTP_MACn(1));
if (id)
macaddr1_high = readl_relaxed(base + OCOTP_MACn(2));
else
macaddr_high = readl_relaxed(base + OCOTP_MACn(0));
newmac = kzalloc(sizeof(*newmac) + 6, GFP_KERNEL);
if (!newmac)
goto put_ocotp_node;
newmac->value = newmac + 1;
newmac->length = 6;
newmac->name = kstrdup("local-mac-address", GFP_KERNEL);
if (!newmac->name) {
kfree(newmac);
goto put_ocotp_node;
}
macaddr = newmac->value;
if (id) {
macaddr[5] = (macaddr_low >> 16) & 0xff;
macaddr[4] = (macaddr_low >> 24) & 0xff;
macaddr[3] = macaddr1_high & 0xff;
macaddr[2] = (macaddr1_high >> 8) & 0xff;
macaddr[1] = (macaddr1_high >> 16) & 0xff;
macaddr[0] = (macaddr1_high >> 24) & 0xff;
} else {
macaddr[5] = macaddr_high & 0xff;
macaddr[4] = (macaddr_high >> 8) & 0xff;
macaddr[3] = (macaddr_high >> 16) & 0xff;
macaddr[2] = (macaddr_high >> 24) & 0xff;
macaddr[1] = macaddr_low & 0xff;
macaddr[0] = (macaddr_low >> 8) & 0xff;
}
of_update_property(enet_np, newmac);
put_ocotp_node:
of_node_put(ocotp_np);
put_enet_node:
of_node_put(enet_np);
}
}
#ifndef CONFIG_HAVE_IMX_GPC
int imx_gpc_mf_request_on(unsigned int irq, unsigned int on) { return 0; }
EXPORT_SYMBOL_GPL(imx_gpc_mf_request_on);
#endif
#if !defined(CONFIG_SOC_IMX6SL)
u32 imx6_lpddr2_freq_change_start, imx6_lpddr2_freq_change_end;
void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode) {}
#endif
#if !defined(CONFIG_SOC_IMX6SLL)
void imx6sll_lpddr2_freq_change(u32 freq, int bus_freq_mode) {}
#endif
#if !defined(CONFIG_SOC_IMX6SX) && !defined(CONFIG_SOC_IMX6UL)
u32 imx6_up_ddr3_freq_change_start, imx6_up_ddr3_freq_change_end;
struct imx6_busfreq_info {
} __aligned(8);
void imx6_up_ddr3_freq_change(struct imx6_busfreq_info *busfreq_info) {}
void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode) {}
#endif
#if !defined(CONFIG_SOC_IMX6Q)
u32 mx6_ddr3_freq_change_start, mx6_ddr3_freq_change_end;
u32 mx6q_lpddr2_freq_change_start, mx6q_lpddr2_freq_change_end;
u32 wfe_smp_freq_change_start, wfe_smp_freq_change_end;
void mx6_ddr3_freq_change(u32 freq, void *ddr_settings,
bool dll_mode, void *iomux_offsets) {}
void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings) {}
void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done) {}
#endif
#if !defined(CONFIG_SOC_IMX7D)
void imx7_smp_wfe(u32 cpuid, u32 ocram_base) {}
void imx7d_ddr3_freq_change(u32 freq) {}
#endif

View File

@ -8,6 +8,7 @@
#define __ASM_ARCH_MXC_COMMON_H__
#include <linux/reboot.h>
#include <soc/imx/src.h>
struct irq_data;
struct platform_device;
@ -56,9 +57,19 @@ void imx_gpc_set_arm_power_in_lpm(bool power_off);
void imx_gpc_set_l2_mem_power_in_lpm(bool power_off);
void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw);
void imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw);
void imx_gpcv2_pre_suspend(bool arm_power_off);
void imx_gpcv2_post_resume(void);
unsigned int imx_gpcv2_is_mf_mix_off(void);
void imx_gpcv2_enable_wakeup_for_m4(void);
void imx_gpcv2_disable_wakeup_for_m4(void);
void imx25_pm_init(void);
void imx27_pm_init(void);
void imx5_pmu_init(void);
#ifdef CONFIG_HAVE_IMX_MU
int imx_mu_lpm_ready(bool ready);
#else
static inline int imx_mu_lpm_ready(bool ready) { return 0; }
#endif
enum mxc_cpu_pwr_mode {
WAIT_CLOCKED, /* wfi only */
@ -89,35 +100,97 @@ void imx_smp_prepare(void);
static inline void imx_scu_map_io(void) {}
static inline void imx_smp_prepare(void) {}
#endif
void imx6sx_set_m4_highfreq(bool high_freq);
void imx_mu_enable_m4_irqs_in_gic(bool enable);
#ifdef CONFIG_HAVE_IMX_GPC
void imx_gpc_add_m4_wake_up_irq(u32 irq, bool enable);
unsigned int imx_gpc_is_m4_sleeping(void);
#else
static inline void imx_gpc_add_m4_wake_up_irq(u32 irq, bool enable) {}
static inline unsigned int imx_gpc_is_m4_sleeping(void) { return 0; }
#endif
#ifdef CONFIG_HAVE_IMX_GPCV2
int imx_gpcv2_mf_power_on(unsigned int irq, unsigned int on);
void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn);
void imx_gpcv2_add_m4_wake_up_irq(u32 hwirq, bool enable);
#else
static inline int imx_gpcv2_mf_power_on(unsigned int irq, unsigned int on) { return 0; }
static inline void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn) {}
static inline void imx_gpcv2_add_m4_wake_up_irq(u32 hwirq, bool enable) {}
#endif
void imx_gpc_hold_m4_in_sleep(void);
void imx_gpc_release_m4_in_sleep(void);
void __init imx_gpcv2_check_dt(void);
void imx_gpcv2_set_lpm_mode(enum mxc_cpu_pwr_mode mode);
void imx_gpcv2_set_cpu_power_gate_in_idle(bool pdn);
void imx_gpcv2_enable_rbc(bool enable);
bool imx_mu_is_m4_in_low_freq(void);
bool imx_mu_is_m4_in_stop(void);
void imx_mu_set_m4_run_mode(void);
void imx_src_init(void);
void imx_gpc_pre_suspend(bool arm_power_off);
void imx_gpc_post_resume(void);
void imx_gpc_switch_pupscr_clk(bool flag);
void imx_gpc_mask_all(void);
void imx_gpc_restore_all(void);
void imx_gpc_hwirq_mask(unsigned int hwirq);
void imx_gpc_hwirq_unmask(unsigned int hwirq);
unsigned int imx_gpc_is_mf_mix_off(void);
void imx_anatop_init(void);
void imx_anatop_pre_suspend(void);
void imx_anatop_post_resume(void);
int imx6_set_lpm(enum mxc_cpu_pwr_mode mode);
void imx6_set_int_mem_clk_lpm(bool enable);
void imx6sl_set_wait_clk(bool enter);
void imx6_enet_mac_init(const char *enet_compat, const char *ocotp_compat);
void imx6sl_low_power_idle(void);
void imx6sll_low_power_idle(void);
void imx6sx_low_power_idle(void);
void imx6ul_low_power_idle(void);
void imx6ull_low_power_idle(void);
void imx7d_low_power_idle(void);
#ifdef CONFIG_HAVE_IMX_MMDC
int imx_mmdc_get_ddr_type(void);
int imx_mmdc_get_lpddr2_2ch_mode(void);
#else
static inline int imx_mmdc_get_ddr_type(void) { return 0; }
static inline int imx_mmdc_get_lpddr2_2ch_mode(void) { return 0; }
#endif
int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode);
void imx_busfreq_map_io(void);
void imx7_pm_map_io(void);
void imx6_pm_map_io(void);
void imx7ulp_pm_map_io(void);
void imx7ulp_enable_nmi(void);
void imx7ulp_poweroff(void);
void imx_cpu_die(unsigned int cpu);
int imx_cpu_kill(unsigned int cpu);
#ifdef CONFIG_SUSPEND
void v7_cpu_resume(void);
void ca7_cpu_resume(void);
void imx53_suspend(void __iomem *ocram_vbase);
extern const u32 imx53_suspend_sz;
void imx6_suspend(void __iomem *ocram_vbase);
void imx7_suspend(void __iomem *ocram_vbase);
void imx7ulp_cpu_resume(void);
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 imx6_suspend(void __iomem *ocram_vbase) {}
static inline void imx7_suspend(void __iomem *ocram_vbase) {}
static inline void imx7ulp_cpu_resume(void) {}
static inline void imx7ulp_suspend(void __iomem *ocram_vbase) {}
#endif
#ifdef CONFIG_HAVE_IMX_DDRC
int imx_ddrc_get_ddr_type(void);
#else
static inline int imx_ddrc_get_ddr_type(void) { return 0; }
#endif
void imx6_pm_ccm_init(const char *ccm_compat);
@ -126,6 +199,7 @@ void imx6dl_pm_init(void);
void imx6sl_pm_init(void);
void imx6sx_pm_init(void);
void imx6ul_pm_init(void);
void imx7d_pm_init(void);
void imx7ulp_pm_init(void);
#ifdef CONFIG_PM
@ -151,4 +225,5 @@ static inline void imx_init_l2cache(void) {}
extern const struct smp_operations imx_smp_ops;
extern const struct smp_operations ls1021a_smp_ops;
extern bool uart_from_osc;
#endif

View File

@ -1,26 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2014 Freescale Semiconductor, Inc.
* Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
* Copyright 2017-2018 NXP.
*/
#include <linux/busfreq-imx.h>
#include <linux/cpuidle.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/platform_device.h>
#include <linux/psci.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <asm/cpuidle.h>
#include <asm/fncpy.h>
#include <asm/proc-fns.h>
#include <uapi/linux/psci.h>
#include "common.h"
#include "cpuidle.h"
#include "hardware.h"
#define MAX_MMDC_IO_NUM 19
static void __iomem *wfi_iram_base;
extern unsigned long iram_tlb_base_addr;
#ifdef CONFIG_CPU_FREQ
extern unsigned long mx6sl_lpm_wfi_start asm("mx6sl_lpm_wfi_start");
extern unsigned long mx6sl_lpm_wfi_end asm("mx6sl_lpm_wfi_end");
#endif
struct imx6_cpuidle_pm_info {
u32 pm_info_size; /* Size of pm_info */
u32 ttbr;
void __iomem *mmdc_base;
void __iomem *iomuxc_base;
void __iomem *ccm_base;
void __iomem *l2_base;
void __iomem *anatop_base;
u32 mmdc_io_num; /*Number of MMDC IOs which need saved/restored. */
u32 mmdc_io_val[MAX_MMDC_IO_NUM][2]; /* To save offset and value */
} __aligned(8);
static const u32 imx6sl_mmdc_io_offset[] __initconst = {
0x30c, 0x310, 0x314, 0x318, /* DQM0 ~ DQM3 */
0x5c4, 0x5cc, 0x5d4, 0x5d8, /* GPR_B0DS ~ GPR_B3DS */
0x300, 0x31c, 0x338, 0x5ac, /*CAS, RAS, SDCLK_0, GPR_ADDS */
0x33c, 0x340, 0x5b0, 0x5c0, /*SODT0, SODT1, ,MODE_CTL, MODE */
0x330, 0x334, 0x320, /*SDCKE0, SDCK1, RESET */
};
static struct regulator *vbus_ldo;
static struct regulator_dev *ldo2p5_dummy_regulator_rdev;
static struct regulator_init_data ldo2p5_dummy_initdata = {
.constraints = {
.valid_ops_mask = REGULATOR_CHANGE_STATUS,
},
};
static int ldo2p5_dummy_enable;
static void (*imx6sl_wfi_in_iram_fn)(void __iomem *iram_vbase,
int audio_mode, bool vbus_ldo);
#define MX6SL_POWERDWN_IDLE_PARAM \
((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
static int imx6sl_enter_wait(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
int mode = get_bus_freq_mode();
imx6_set_lpm(WAIT_UNCLOCKED);
/*
* Software workaround for ERR005311, see function
* description for details.
*/
imx6sl_set_wait_clk(true);
cpu_do_idle();
imx6sl_set_wait_clk(false);
if ((mode == BUS_FREQ_AUDIO) || (mode == BUS_FREQ_ULTRA_LOW)) {
/*
* bit 2 used for low power mode;
* bit 1 used for the ldo2p5_dummmy enable
*/
if (psci_ops.cpu_suspend) {
psci_ops.cpu_suspend((MX6SL_POWERDWN_IDLE_PARAM | ((mode == BUS_FREQ_AUDIO ? 1 : 0) << 2) |
(ldo2p5_dummy_enable ? 1 : 0) << 1), __pa(cpu_resume));
} else {
imx6sl_wfi_in_iram_fn(wfi_iram_base, (mode == BUS_FREQ_AUDIO) ? 1 : 0,
ldo2p5_dummy_enable);
}
} else {
/*
* Software workaround for ERR005311, see function
* description for details.
*/
imx6sl_set_wait_clk(true);
cpu_do_idle();
imx6sl_set_wait_clk(false);
}
imx6_set_lpm(WAIT_CLOCKED);
return index;
@ -48,5 +126,109 @@ static struct cpuidle_driver imx6sl_cpuidle_driver = {
int __init imx6sl_cpuidle_init(void)
{
#ifdef CONFIG_CPU_FREQ
struct imx6_cpuidle_pm_info *pm_info;
int i;
const u32 *mmdc_offset_array;
u32 wfi_code_size;
vbus_ldo = regulator_get(NULL, "ldo2p5-dummy");
if (IS_ERR(vbus_ldo))
vbus_ldo = NULL;
wfi_iram_base = (void *)(iram_tlb_base_addr + MX6_CPUIDLE_IRAM_ADDR_OFFSET);
/* Make sure wif_iram_base is 8 byte aligned. */
if ((uintptr_t)(wfi_iram_base) & (FNCPY_ALIGN - 1))
wfi_iram_base += FNCPY_ALIGN - ((uintptr_t)wfi_iram_base % (FNCPY_ALIGN));
pm_info = wfi_iram_base;
pm_info->pm_info_size = sizeof(*pm_info);
pm_info->mmdc_io_num = ARRAY_SIZE(imx6sl_mmdc_io_offset);
mmdc_offset_array = imx6sl_mmdc_io_offset;
pm_info->mmdc_base = (void __iomem *)IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR);
pm_info->ccm_base = (void __iomem *)IMX_IO_P2V(MX6Q_CCM_BASE_ADDR);
pm_info->anatop_base = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR);
pm_info->iomuxc_base = (void __iomem *)IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR);
pm_info->l2_base = (void __iomem *)IMX_IO_P2V(MX6Q_L2_BASE_ADDR);
/* Only save mmdc io offset, settings will be saved in asm code */
for (i = 0; i < pm_info->mmdc_io_num; i++)
pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i];
/* calculate the wfi code size */
wfi_code_size = (&mx6sl_lpm_wfi_end -&mx6sl_lpm_wfi_start) *4;
imx6sl_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*pm_info),
&imx6sl_low_power_idle, wfi_code_size);
#endif
return cpuidle_register(&imx6sl_cpuidle_driver, NULL);
}
static int imx_ldo2p5_dummy_enable(struct regulator_dev *rdev)
{
ldo2p5_dummy_enable = 1;
return 0;
}
static int imx_ldo2p5_dummy_disable(struct regulator_dev *rdev)
{
ldo2p5_dummy_enable = 0;
return 0;
}
static int imx_ldo2p5_dummy_is_enable(struct regulator_dev *rdev)
{
return ldo2p5_dummy_enable;
}
static struct regulator_ops ldo2p5_dummy_ops = {
.enable = imx_ldo2p5_dummy_enable,
.disable = imx_ldo2p5_dummy_disable,
.is_enabled = imx_ldo2p5_dummy_is_enable,
};
static struct regulator_desc ldo2p5_dummy_desc = {
.name = "ldo2p5-dummy",
.id = -1,
.type = REGULATOR_VOLTAGE,
.owner = THIS_MODULE,
.ops = &ldo2p5_dummy_ops,
};
static int ldo2p5_dummy_probe(struct platform_device *pdev)
{
struct regulator_config config = { };
int ret;
config.dev = &pdev->dev;
config.init_data = &ldo2p5_dummy_initdata;
config.of_node = pdev->dev.of_node;
ldo2p5_dummy_regulator_rdev = regulator_register(&ldo2p5_dummy_desc, &config);
if (IS_ERR(ldo2p5_dummy_regulator_rdev)) {
ret = PTR_ERR(ldo2p5_dummy_regulator_rdev);
dev_err(&pdev->dev, "Failed to register dummy ldo2p5 regulator: %d\n", ret);
return ret;
}
return 0;
}
static const struct of_device_id imx_ldo2p5_dummy_ids[] = {
{ .compatible = "fsl,imx6-dummy-ldo2p5", },
{ },
};
MODULE_DEVICE_TABLE(ofm, imx_ldo2p5_dummy_ids);
static struct platform_driver ldo2p5_dummy_driver = {
.probe = ldo2p5_dummy_probe,
.driver = {
.name = "ldo2p5-dummy",
.owner = THIS_MODULE,
.of_match_table = imx_ldo2p5_dummy_ids,
},
};
module_platform_driver(ldo2p5_dummy_driver);

View File

@ -0,0 +1,277 @@
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
* Copyright 2017 NXP.
*
* 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/busfreq-imx.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/psci.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <asm/cpuidle.h>
#include <asm/fncpy.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <uapi/linux/psci.h>
#include "common.h"
#include "cpuidle.h"
#include "hardware.h"
#define MAX_MMDC_IO_NUM 14
#define PMU_LOW_PWR_CTRL 0x270
#define XTALOSC24M_OSC_CONFIG0 0x2a0
#define XTALOSC24M_OSC_CONFIG1 0x2b0
#define XTALOSC24M_OSC_CONFIG2 0x2c0
#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT 24
#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK 0xf
#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT 16
#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK 0xf
#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT 12
#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT 4
#define XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT 1
#define XTALOSC24M_OSC_CONFIG0_START_SHIFT 0
#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT 20
#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT 0
#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK 0xfff
#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT 0
extern unsigned long iram_tlb_phys_addr;
static void __iomem *wfi_iram_base;
#ifdef CONFIG_CPU_FREQ
static void __iomem *wfi_iram_base_phys;
extern unsigned long mx6sll_lpm_wfi_start asm("mx6sll_lpm_wfi_start");
extern unsigned long mx6sll_lpm_wfi_end asm("mx6sll_lpm_wfi_end");
#endif
struct imx6_pm_base {
phys_addr_t pbase;
void __iomem *vbase;
};
struct imx6_cpuidle_pm_info {
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. */
u32 ttbr;
struct imx6_pm_base mmdc_base;
struct imx6_pm_base iomuxc_base;
struct imx6_pm_base ccm_base;
struct imx6_pm_base gpc_base;
struct imx6_pm_base anatop_base;
struct imx6_pm_base src_base;
struct imx6_pm_base l2_base;
u32 saved_diagnostic; /* To save disagnostic register */
u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */
u32 mmdc_io_val[MAX_MMDC_IO_NUM][2]; /* To save offset and value */
} __aligned(8);
static const u32 imx6sll_mmdc_io_offset[] __initconst = {
0x294, 0x298, 0x29c, 0x2a0, /* DQM0, DQM1, RAS, CAS */
0x544, 0x54c, 0x554, 0x558, /* GPR_B0DS ~ GPR_B3DS */
0x530, 0x540, 0x2ac, 0x52c, /* MODE_CTL, MODE, SDCLK0, GPR_ADDS */
0x2a4, 0x2a8, /* SDCKE0, SDCKE1 */
};
static void (*imx6sll_wfi_in_iram_fn)(void __iomem *iram_vbase);
#define MX6SLL_POWERDWN_IDLE_PARAM \
((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
static int imx6sll_idle_finish(unsigned long val)
{
if (psci_ops.cpu_suspend)
psci_ops.cpu_suspend(MX6SLL_POWERDWN_IDLE_PARAM,
__pa(cpu_resume));
else
imx6sll_wfi_in_iram_fn(wfi_iram_base);
return 0;
}
static int imx6sll_enter_wait(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
int mode = get_bus_freq_mode();
imx6_set_lpm(WAIT_UNCLOCKED);
if ((index == 1) || ((mode != BUS_FREQ_LOW) && index == 2)) {
index = 1;
cpu_do_idle();
} else {
imx_gpc_switch_pupscr_clk(true);
/* Need to notify there is a cpu pm operation. */
cpu_pm_enter();
cpu_cluster_pm_enter();
cpu_suspend(0, imx6sll_idle_finish);
cpu_cluster_pm_exit();
cpu_pm_exit();
imx6_enable_rbc(false);
imx_gpc_switch_pupscr_clk(false);
}
imx6_set_lpm(WAIT_CLOCKED);
return index;
}
static struct cpuidle_driver imx6sll_cpuidle_driver = {
.name = "imx6sll_cpuidle",
.owner = THIS_MODULE,
.states = {
/* WFI */
ARM_CPUIDLE_WFI_STATE,
/* WAIT */
{
.exit_latency = 50,
.target_residency = 75,
.enter = imx6sll_enter_wait,
.name = "WAIT",
.desc = "Clock off",
},
/* LOW POWER IDLE */
{
/*
* RBC 130us + ARM gating 43us + RBC clear 65us
* + PLL2 relock 450us and some margin, here set
* it to 700us.
*/
.exit_latency = 700,
.target_residency = 1000,
.enter = imx6sll_enter_wait,
.name = "LOW-POWER-IDLE",
.desc = "ARM power off",
}
},
.state_count = 3,
.safe_state_index = 0,
};
int __init imx6sll_cpuidle_init(void)
{
void __iomem *anatop_base = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR);
u32 val;
#ifdef CONFIG_CPU_FREQ
struct imx6_cpuidle_pm_info *cpuidle_pm_info;
int i;
const u32 *mmdc_offset_array;
u32 wfi_code_size;
wfi_iram_base_phys = (void *)(iram_tlb_phys_addr + MX6_CPUIDLE_IRAM_ADDR_OFFSET);
/* Make sure wfi_iram_base is 8 byte aligned. */
if ((uintptr_t)(wfi_iram_base_phys) & (FNCPY_ALIGN - 1))
wfi_iram_base_phys += FNCPY_ALIGN - ((uintptr_t)wfi_iram_base_phys % (FNCPY_ALIGN));
wfi_iram_base = (void *)IMX_IO_P2V((unsigned long) wfi_iram_base_phys);
cpuidle_pm_info = wfi_iram_base;
cpuidle_pm_info->pbase = (phys_addr_t) wfi_iram_base_phys;
cpuidle_pm_info->pm_info_size = sizeof(*cpuidle_pm_info);
cpuidle_pm_info->resume_addr = virt_to_phys(v7_cpu_resume);
cpuidle_pm_info->mmdc_io_num = ARRAY_SIZE(imx6sll_mmdc_io_offset);
mmdc_offset_array = imx6sll_mmdc_io_offset;
cpuidle_pm_info->mmdc_base.pbase = MX6Q_MMDC_P0_BASE_ADDR;
cpuidle_pm_info->mmdc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR);
cpuidle_pm_info->ccm_base.pbase = MX6Q_CCM_BASE_ADDR;
cpuidle_pm_info->ccm_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_CCM_BASE_ADDR);
cpuidle_pm_info->anatop_base.pbase = MX6Q_ANATOP_BASE_ADDR;
cpuidle_pm_info->anatop_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR);
cpuidle_pm_info->gpc_base.pbase = MX6Q_GPC_BASE_ADDR;
cpuidle_pm_info->gpc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_GPC_BASE_ADDR);
cpuidle_pm_info->iomuxc_base.pbase = MX6Q_IOMUXC_BASE_ADDR;
cpuidle_pm_info->iomuxc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR);
cpuidle_pm_info->src_base.pbase = MX6Q_SRC_BASE_ADDR;
cpuidle_pm_info->src_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_SRC_BASE_ADDR);
cpuidle_pm_info->l2_base.pbase = MX6Q_L2_BASE_ADDR;
cpuidle_pm_info->l2_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_L2_BASE_ADDR);
/* Only save mmdc io offset, settings will be saved in asm code */
for (i = 0; i < cpuidle_pm_info->mmdc_io_num; i++)
cpuidle_pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i];
wfi_code_size = (&mx6sll_lpm_wfi_end -&mx6sll_lpm_wfi_start) *4;
imx6sll_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info),
&imx6sll_low_power_idle, wfi_code_size);
#endif
imx6_set_int_mem_clk_lpm(true);
/*
* enable RC-OSC here, as it needs at least 4ms for RC-OSC to
* be stable, low power idle flow can NOT endure this big
* latency, so we make RC-OSC self-tuning enabled here.
*/
val = readl_relaxed(anatop_base + PMU_LOW_PWR_CTRL);
val |= 0x1;
writel_relaxed(val, anatop_base + PMU_LOW_PWR_CTRL);
/*
* config RC-OSC freq
* tune_enable = 1;tune_start = 1;hyst_plus = 0;hyst_minus = 0;
* osc_prog = 0xa7;
*/
writel_relaxed(
0x4 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT |
0xa7 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT |
0x1 << XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT |
0x1 << XTALOSC24M_OSC_CONFIG0_START_SHIFT,
anatop_base + XTALOSC24M_OSC_CONFIG0);
/* set count_trg = 0x2dc */
writel_relaxed(
0x40 << XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT |
0x2dc << XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT,
anatop_base + XTALOSC24M_OSC_CONFIG1);
/* wait 4ms according to hardware design */
msleep(4);
/*
* now add some hysteresis, hyst_plus=3, hyst_minus=3
* (the minimum hysteresis that looks good is 2)
*/
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG0);
val &= ~((XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK <<
XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) |
(XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK <<
XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT));
val |= (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) |
(0x3 << XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT);
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG0);
/* set the count_1m_trg = 0x2d7 */
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG2);
val &= ~(XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK <<
XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT);
val |= 0x2d7 << XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT;
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG2);
/*
* hardware design require to write XTALOSC24M_OSC_CONFIG0 or
* XTALOSC24M_OSC_CONFIG1 to
* make XTALOSC24M_OSC_CONFIG2 write work
*/
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG1);
return cpuidle_register(&imx6sll_cpuidle_driver, NULL);
}

View File

@ -1,20 +1,97 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2014 Freescale Semiconductor, Inc.
* Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
*/
#include <linux/busfreq-imx.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/delay.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/psci.h>
#include <asm/cacheflush.h>
#include <asm/cpuidle.h>
#include <asm/fncpy.h>
#include <asm/mach/map.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/tlb.h>
#include <uapi/linux/psci.h>
#include "common.h"
#include "cpuidle.h"
#include "hardware.h"
static int imx6sx_idle_finish(unsigned long val)
#define MX6_MAX_MMDC_IO_NUM 19
#define PMU_LOW_PWR_CTRL 0x270
#define XTALOSC24M_OSC_CONFIG0 0x2a0
#define XTALOSC24M_OSC_CONFIG1 0x2b0
#define XTALOSC24M_OSC_CONFIG2 0x2c0
#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT 24
#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK 0xf
#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT 16
#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK 0xf
#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT 12
#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT 4
#define XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT 1
#define XTALOSC24M_OSC_CONFIG0_START_SHIFT 0
#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT 20
#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT 0
#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK 0xfff
#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT 0
extern unsigned long iram_tlb_phys_addr;
static void __iomem *wfi_iram_base;
#ifdef CONFIG_CPU_FREQ
static void __iomem *wfi_iram_base_phys;
extern unsigned long mx6sx_lpm_wfi_start asm("mx6sx_lpm_wfi_start");
extern unsigned long mx6sx_lpm_wfi_end asm("mx6sx_lpm_wfi_end");
#endif
struct imx6_pm_base {
phys_addr_t pbase;
void __iomem *vbase;
};
static const u32 imx6sx_mmdc_io_offset[] __initconst = {
0x2ec, 0x2f0, 0x2f4, 0x2f8, /* DQM0 ~ DQM3 */
0x330, 0x334, 0x338, 0x33c, /* SDQS0 ~ SDQS3 */
0x60c, 0x610, 0x61c, 0x620, /* B0DS ~ B3DS */
0x5f8, 0x608, 0x310, 0x314, /* CTL, MODE, SODT0, SODT1 */
0x300, 0x2fc, 0x32c, /* CAS, RAS, SDCLK_0 */
};
struct imx6_cpuidle_pm_info {
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. */
u32 ttbr;
struct imx6_pm_base mmdc_base;
struct imx6_pm_base iomuxc_base;
struct imx6_pm_base ccm_base;
struct imx6_pm_base gpc_base;
struct imx6_pm_base l2_base;
struct imx6_pm_base anatop_base;
struct imx6_pm_base src_base;
struct imx6_pm_base sema4_base;
u32 saved_diagnostic; /* To save disagnostic register */
u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */
u32 mmdc_io_val[MX6_MAX_MMDC_IO_NUM][2]; /* To save offset and value */
} __aligned(8);
static void (*imx6sx_wfi_in_iram_fn)(void __iomem *iram_vbase);
#define MX6SX_POWERDWN_IDLE_PARAM \
((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
static int imx6_idle_finish(unsigned long val)
{
/*
* for Cortex-A7 which has an internal L2
@ -25,7 +102,11 @@ static int imx6sx_idle_finish(unsigned long val)
* just call flush_cache_all() is fine.
*/
flush_cache_all();
cpu_do_idle();
if (psci_ops.cpu_suspend)
psci_ops.cpu_suspend(MX6SX_POWERDWN_IDLE_PARAM,
__pa(cpu_resume));
else
imx6sx_wfi_in_iram_fn(wfi_iram_base);
return 0;
}
@ -33,29 +114,22 @@ static int imx6sx_idle_finish(unsigned long val)
static int imx6sx_enter_wait(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
int mode = get_bus_freq_mode();
imx6_set_lpm(WAIT_UNCLOCKED);
switch (index) {
case 1:
if ((index == 1) || ((mode != BUS_FREQ_LOW) && index == 2)) {
index = 1;
cpu_do_idle();
break;
case 2:
imx6_enable_rbc(true);
imx_gpc_set_arm_power_in_lpm(true);
imx_set_cpu_jump(0, v7_cpu_resume);
/* Need to notify there is a cpu pm operation. */
cpu_pm_enter();
cpu_cluster_pm_enter();
} else {
/* Need to notify there is a cpu pm operation. */
cpu_pm_enter();
cpu_cluster_pm_enter();
cpu_suspend(0, imx6sx_idle_finish);
cpu_suspend(0, imx6_idle_finish);
cpu_cluster_pm_exit();
cpu_pm_exit();
imx_gpc_set_arm_power_in_lpm(false);
imx6_enable_rbc(false);
break;
default:
break;
cpu_cluster_pm_exit();
cpu_pm_exit();
imx6_enable_rbc(false);
}
imx6_set_lpm(WAIT_CLOCKED);
@ -69,25 +143,23 @@ static struct cpuidle_driver imx6sx_cpuidle_driver = {
.states = {
/* WFI */
ARM_CPUIDLE_WFI_STATE,
/* WAIT */
/* WAIT MODE */
{
.exit_latency = 50,
.target_residency = 75,
.flags = CPUIDLE_FLAG_TIMER_STOP,
.enter = imx6sx_enter_wait,
.name = "WAIT",
.desc = "Clock off",
},
/* WAIT + ARM power off */
/* LOW POWER IDLE */
{
/*
* ARM gating 31us * 5 + RBC clear 65us
* and some margin for SW execution, here set it
* to 300us.
* RBC 130us + ARM gating 93us + RBC clear 65us
* + PLL2 relock 450us and some margin, here set
* it to 800us.
*/
.exit_latency = 300,
.target_residency = 500,
.flags = CPUIDLE_FLAG_TIMER_STOP,
.exit_latency = 800,
.target_residency = 1000,
.enter = imx6sx_enter_wait,
.name = "LOW-POWER-IDLE",
.desc = "ARM power off",
@ -99,17 +171,119 @@ static struct cpuidle_driver imx6sx_cpuidle_driver = {
int __init imx6sx_cpuidle_init(void)
{
void __iomem *anatop_base = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR);
u32 val;
#ifdef CONFIG_CPU_FREQ
struct imx6_cpuidle_pm_info *cpuidle_pm_info;
int i;
const u32 *mmdc_offset_array;
u32 wfi_code_size;
wfi_iram_base_phys = (void *)(iram_tlb_phys_addr + MX6_CPUIDLE_IRAM_ADDR_OFFSET);
/* Make sure wfi_iram_base is 8 byte aligned. */
if ((uintptr_t)(wfi_iram_base_phys) & (FNCPY_ALIGN - 1))
wfi_iram_base_phys += FNCPY_ALIGN - ((uintptr_t)wfi_iram_base_phys % (FNCPY_ALIGN));
wfi_iram_base = (void *)IMX_IO_P2V((unsigned long) wfi_iram_base_phys);
cpuidle_pm_info = wfi_iram_base;
cpuidle_pm_info->pbase = (phys_addr_t) wfi_iram_base_phys;
cpuidle_pm_info->pm_info_size = sizeof(*cpuidle_pm_info);
cpuidle_pm_info->resume_addr = virt_to_phys(v7_cpu_resume);
cpuidle_pm_info->mmdc_io_num = ARRAY_SIZE(imx6sx_mmdc_io_offset);
mmdc_offset_array = imx6sx_mmdc_io_offset;
cpuidle_pm_info->mmdc_base.pbase = MX6Q_MMDC_P0_BASE_ADDR;
cpuidle_pm_info->mmdc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR);
cpuidle_pm_info->ccm_base.pbase = MX6Q_CCM_BASE_ADDR;
cpuidle_pm_info->ccm_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_CCM_BASE_ADDR);
cpuidle_pm_info->anatop_base.pbase = MX6Q_ANATOP_BASE_ADDR;
cpuidle_pm_info->anatop_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR);
cpuidle_pm_info->gpc_base.pbase = MX6Q_GPC_BASE_ADDR;
cpuidle_pm_info->gpc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_GPC_BASE_ADDR);
cpuidle_pm_info->iomuxc_base.pbase = MX6Q_IOMUXC_BASE_ADDR;
cpuidle_pm_info->iomuxc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR);
cpuidle_pm_info->l2_base.pbase = MX6Q_L2_BASE_ADDR;
cpuidle_pm_info->l2_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_L2_BASE_ADDR);
cpuidle_pm_info->src_base.pbase = MX6Q_SRC_BASE_ADDR;
cpuidle_pm_info->src_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_SRC_BASE_ADDR);
cpuidle_pm_info->sema4_base.pbase = MX6Q_SEMA4_BASE_ADDR;
cpuidle_pm_info->sema4_base.vbase =
(void __iomem *)IMX_IO_P2V(MX6Q_SEMA4_BASE_ADDR);
/* only save mmdc io offset, settings will be saved in asm code */
for (i = 0; i < cpuidle_pm_info->mmdc_io_num; i++)
cpuidle_pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i];
/* code size should include cpuidle_pm_info size */
wfi_code_size = (&mx6sx_lpm_wfi_end -&mx6sx_lpm_wfi_start) *4 + sizeof(*cpuidle_pm_info);
imx6sx_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info),
&imx6sx_low_power_idle, wfi_code_size);
#endif
imx6_set_int_mem_clk_lpm(true);
imx6_enable_rbc(false);
imx_gpc_set_l2_mem_power_in_lpm(false);
/*
* set ARM power up/down timing to the fastest,
* sw2iso and sw can be set to one 32K cycle = 31us
* except for power up sw2iso which need to be
* larger than LDO ramp up time.
*/
imx_gpc_set_arm_power_up_timing(cpu_is_imx6sx() ? 0xf : 0x2, 1);
imx_gpc_set_arm_power_down_timing(1, 1);
if (imx_get_soc_revision() >= IMX_CHIP_REVISION_1_2) {
/*
* enable RC-OSC here, as it needs at least 4ms for RC-OSC to
* be stable, low power idle flow can NOT endure this big
* latency, so we make RC-OSC self-tuning enabled here.
*/
val = readl_relaxed(anatop_base + PMU_LOW_PWR_CTRL);
val |= 0x1;
writel_relaxed(val, anatop_base + PMU_LOW_PWR_CTRL);
/*
* config RC-OSC freq
* tune_enable = 1;tune_start = 1;hyst_plus = 0;hyst_minus = 0;
* osc_prog = 0xa7;
*/
writel_relaxed(
0x4 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT |
0xa7 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT |
0x1 << XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT |
0x1 << XTALOSC24M_OSC_CONFIG0_START_SHIFT,
anatop_base + XTALOSC24M_OSC_CONFIG0);
/* set count_trg = 0x2dc */
writel_relaxed(
0x40 << XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT |
0x2dc << XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT,
anatop_base + XTALOSC24M_OSC_CONFIG1);
/* wait 4ms according to hardware design */
msleep(4);
/*
* now add some hysteresis, hyst_plus=3, hyst_minus=3
* (the minimum hysteresis that looks good is 2)
*/
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG0);
val &= ~((XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK <<
XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) |
(XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK <<
XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT));
val |= (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) |
(0x3 << XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT);
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG0);
/* set the count_1m_trg = 0x2d7 */
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG2);
val &= ~(XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK <<
XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT);
val |= 0x2d7 << XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT;
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG2);
/*
* hardware design require to write XTALOSC24M_OSC_CONFIG0 or
* XTALOSC24M_OSC_CONFIG1 to
* make XTALOSC24M_OSC_CONFIG2 write work
*/
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG1);
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG1);
}
return cpuidle_register(&imx6sx_cpuidle_driver, NULL);
}

View File

@ -0,0 +1,327 @@
/*
* Copyright (C) 2015-2016 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/busfreq-imx.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/psci.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <asm/cpuidle.h>
#include <asm/fncpy.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <uapi/linux/psci.h>
#include "common.h"
#include "cpuidle.h"
#include "hardware.h"
#define MAX_MMDC_IO_NUM 14
#define PMU_LOW_PWR_CTRL 0x270
#define XTALOSC24M_OSC_CONFIG0 0x2a0
#define XTALOSC24M_OSC_CONFIG1 0x2b0
#define XTALOSC24M_OSC_CONFIG2 0x2c0
#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT 24
#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK 0xf
#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT 16
#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK 0xf
#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT 12
#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT 4
#define XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT 1
#define XTALOSC24M_OSC_CONFIG0_START_SHIFT 0
#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT 20
#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT 0
#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK 0xfff
#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT 0
extern unsigned long iram_tlb_phys_addr;
static void __iomem *wfi_iram_base;
#ifdef CONFIG_CPU_FREQ
static void __iomem *wfi_iram_base_phys;
extern unsigned long mx6ul_lpm_wfi_start asm("mx6ul_lpm_wfi_start");
extern unsigned long mx6ul_lpm_wfi_end asm("mx6ul_lpm_wfi_end");
extern unsigned long mx6ull_lpm_wfi_start asm("mx6ull_lpm_wfi_start");
extern unsigned long mx6ull_lpm_wfi_end asm("mx6ull_lpm_wfi_end");
#endif
struct imx6_pm_base {
phys_addr_t pbase;
void __iomem *vbase;
};
struct imx6_cpuidle_pm_info {
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. */
u32 ttbr;
struct imx6_pm_base mmdc_base;
struct imx6_pm_base iomuxc_base;
struct imx6_pm_base ccm_base;
struct imx6_pm_base gpc_base;
struct imx6_pm_base anatop_base;
struct imx6_pm_base src_base;
u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */
u32 mmdc_io_val[MAX_MMDC_IO_NUM][2]; /* To save offset and value */
} __aligned(8);
static const u32 imx6ul_mmdc_io_offset[] __initconst = {
0x244, 0x248, 0x24c, 0x250, /* DQM0, DQM1, RAS, CAS */
0x27c, 0x498, 0x4a4, 0x490, /* SDCLK0, GPR_B0DS-B1DS, GPR_ADDS */
0x280, 0x284, 0x260, 0x264, /* SDQS0~1, SODT0, SODT1 */
0x494, 0x4b0, /* MODE_CTL, MODE, */
};
static void (*imx6ul_wfi_in_iram_fn)(void __iomem *iram_vbase);
#define MX6UL_POWERDWN_IDLE_PARAM \
((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
static int imx6ul_idle_finish(unsigned long val)
{
if (psci_ops.cpu_suspend)
psci_ops.cpu_suspend(MX6UL_POWERDWN_IDLE_PARAM,
__pa(cpu_resume));
else
imx6ul_wfi_in_iram_fn(wfi_iram_base);
return 0;
}
static int imx6ul_enter_wait(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
int mode = get_bus_freq_mode();
imx6_set_lpm(WAIT_UNCLOCKED);
if ((index == 1) || ((mode != BUS_FREQ_LOW) && index == 2)) {
cpu_do_idle();
index = 1;
} else {
/*
* i.MX6UL TO1.0 ARM power up uses IPG/2048 as clock source,
* from TO1.1, PGC_CPU_PUPSCR bit [5] is re-defined to switch
* clock to IPG/32, enable this bit to speed up the ARM power
* up process in low power idle case.
*/
if (cpu_is_imx6ul() && imx_get_soc_revision() >
IMX_CHIP_REVISION_1_0)
imx_gpc_switch_pupscr_clk(true);
/* Need to notify there is a cpu pm operation. */
cpu_pm_enter();
cpu_cluster_pm_enter();
cpu_suspend(0, imx6ul_idle_finish);
cpu_cluster_pm_exit();
cpu_pm_exit();
imx6_enable_rbc(false);
if (cpu_is_imx6ul() && imx_get_soc_revision() >
IMX_CHIP_REVISION_1_0)
imx_gpc_switch_pupscr_clk(false);
}
imx6_set_lpm(WAIT_CLOCKED);
return index;
}
static struct cpuidle_driver imx6ul_cpuidle_driver_v2 = {
.name = "imx6ul_cpuidle",
.owner = THIS_MODULE,
.states = {
/* WFI */
ARM_CPUIDLE_WFI_STATE,
/* WAIT */
{
.exit_latency = 50,
.target_residency = 75,
.enter = imx6ul_enter_wait,
.name = "WAIT",
.desc = "Clock off",
},
/* LOW POWER IDLE */
{
/*
* RBC 130us + ARM gating 43us + RBC clear 65us
* + PLL2 relock 450us and some margin, here set
* it to 700us.
*/
.exit_latency = 700,
.target_residency = 1000,
.enter = imx6ul_enter_wait,
.name = "LOW-POWER-IDLE",
.desc = "ARM power off",
}
},
.state_count = 3,
.safe_state_index = 0,
};
static struct cpuidle_driver imx6ul_cpuidle_driver = {
.name = "imx6ul_cpuidle",
.owner = THIS_MODULE,
.states = {
/* WFI */
ARM_CPUIDLE_WFI_STATE,
/* WAIT */
{
.exit_latency = 50,
.target_residency = 75,
.enter = imx6ul_enter_wait,
.name = "WAIT",
.desc = "Clock off",
},
/* LOW POWER IDLE */
{
/*
* RBC 130us + ARM gating 1370us + RBC clear 65us
* + PLL2 relock 450us and some margin, here set
* it to 2100us.
*/
.exit_latency = 2100,
.target_residency = 2500,
.enter = imx6ul_enter_wait,
.name = "LOW-POWER-IDLE",
.desc = "ARM power off",
}
},
.state_count = 3,
.safe_state_index = 0,
};
int __init imx6ul_cpuidle_init(void)
{
void __iomem *anatop_base = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR);
u32 val;
#ifdef CONFIG_CPU_FREQ
struct imx6_cpuidle_pm_info *cpuidle_pm_info;
int i;
const u32 *mmdc_offset_array;
u32 wfi_code_size;
wfi_iram_base_phys = (void *)(iram_tlb_phys_addr + MX6_CPUIDLE_IRAM_ADDR_OFFSET);
/* Make sure wfi_iram_base is 8 byte aligned. */
if ((uintptr_t)(wfi_iram_base_phys) & (FNCPY_ALIGN - 1))
wfi_iram_base_phys += FNCPY_ALIGN - ((uintptr_t)wfi_iram_base_phys % (FNCPY_ALIGN));
wfi_iram_base = (void *)IMX_IO_P2V((unsigned long) wfi_iram_base_phys);
cpuidle_pm_info = wfi_iram_base;
cpuidle_pm_info->pbase = (phys_addr_t) wfi_iram_base_phys;
cpuidle_pm_info->pm_info_size = sizeof(*cpuidle_pm_info);
cpuidle_pm_info->resume_addr = virt_to_phys(v7_cpu_resume);
cpuidle_pm_info->mmdc_io_num = ARRAY_SIZE(imx6ul_mmdc_io_offset);
mmdc_offset_array = imx6ul_mmdc_io_offset;
cpuidle_pm_info->mmdc_base.pbase = MX6Q_MMDC_P0_BASE_ADDR;
cpuidle_pm_info->mmdc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR);
cpuidle_pm_info->ccm_base.pbase = MX6Q_CCM_BASE_ADDR;
cpuidle_pm_info->ccm_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_CCM_BASE_ADDR);
cpuidle_pm_info->anatop_base.pbase = MX6Q_ANATOP_BASE_ADDR;
cpuidle_pm_info->anatop_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR);
cpuidle_pm_info->gpc_base.pbase = MX6Q_GPC_BASE_ADDR;
cpuidle_pm_info->gpc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_GPC_BASE_ADDR);
cpuidle_pm_info->iomuxc_base.pbase = MX6Q_IOMUXC_BASE_ADDR;
cpuidle_pm_info->iomuxc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR);
cpuidle_pm_info->src_base.pbase = MX6Q_SRC_BASE_ADDR;
cpuidle_pm_info->src_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_SRC_BASE_ADDR);
/* Only save mmdc io offset, settings will be saved in asm code */
for (i = 0; i < cpuidle_pm_info->mmdc_io_num; i++)
cpuidle_pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i];
/* calculate the wfi code size */
if (cpu_is_imx6ul()) {
wfi_code_size = (&mx6ul_lpm_wfi_end -&mx6ul_lpm_wfi_start) *4;
imx6ul_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info),
&imx6ul_low_power_idle, wfi_code_size);
} else {
wfi_code_size = (&mx6ull_lpm_wfi_end -&mx6ull_lpm_wfi_start) *4;
imx6ul_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info),
&imx6ull_low_power_idle, wfi_code_size);
}
#endif
imx6_set_int_mem_clk_lpm(true);
/*
* enable RC-OSC here, as it needs at least 4ms for RC-OSC to
* be stable, low power idle flow can NOT endure this big
* latency, so we make RC-OSC self-tuning enabled here.
*/
val = readl_relaxed(anatop_base + PMU_LOW_PWR_CTRL);
val |= 0x1;
writel_relaxed(val, anatop_base + PMU_LOW_PWR_CTRL);
/*
* config RC-OSC freq
* tune_enable = 1;tune_start = 1;hyst_plus = 0;hyst_minus = 0;
* osc_prog = 0xa7;
*/
writel_relaxed(
0x4 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT |
0xa7 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT |
0x1 << XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT |
0x1 << XTALOSC24M_OSC_CONFIG0_START_SHIFT,
anatop_base + XTALOSC24M_OSC_CONFIG0);
/* set count_trg = 0x2dc */
writel_relaxed(
0x40 << XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT |
0x2dc << XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT,
anatop_base + XTALOSC24M_OSC_CONFIG1);
/* wait 4ms according to hardware design */
msleep(4);
/*
* now add some hysteresis, hyst_plus=3, hyst_minus=3
* (the minimum hysteresis that looks good is 2)
*/
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG0);
val &= ~((XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK <<
XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) |
(XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK <<
XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT));
val |= (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) |
(0x3 << XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT);
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG0);
/* set the count_1m_trg = 0x2d7 */
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG2);
val &= ~(XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK <<
XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT);
val |= 0x2d7 << XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT;
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG2);
/*
* hardware design require to write XTALOSC24M_OSC_CONFIG0 or
* XTALOSC24M_OSC_CONFIG1 to
* make XTALOSC24M_OSC_CONFIG2 write work
*/
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG1);
/* ARM power up time is reduced since TO1.1 */
if (imx_get_soc_revision() > IMX_CHIP_REVISION_1_0)
return cpuidle_register(&imx6ul_cpuidle_driver_v2, NULL);
else
return cpuidle_register(&imx6ul_cpuidle_driver, NULL);
}

View File

@ -0,0 +1,390 @@
/*
* 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/busfreq-imx.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/delay.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/psci.h>
#include <asm/cp15.h>
#include <asm/cpuidle.h>
#include <asm/fncpy.h>
#include <asm/mach/map.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/tlb.h>
#include <uapi/linux/psci.h>
#include "common.h"
#include "cpuidle.h"
#include "hardware.h"
#define XTALOSC24M_OSC_CONFIG0 0x10
#define XTALOSC24M_OSC_CONFIG1 0x20
#define XTALOSC24M_OSC_CONFIG2 0x30
#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT 24
#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK 0xf
#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT 16
#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK 0xf
#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT 12
#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT 4
#define XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT 1
#define XTALOSC24M_OSC_CONFIG0_START_SHIFT 0
#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT 20
#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT 0
#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK 0xfff
#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT 0
#define XTALOSC_CTRL_24M 0x0
#define XTALOSC_CTRL_24M_RC_OSC_EN_SHIFT 13
#define REG_SET 0x4
static void __iomem *wfi_iram_base;
static void __iomem *wfi_iram_base_phys;
extern unsigned long iram_tlb_phys_addr;
struct imx7_pm_base {
phys_addr_t pbase;
void __iomem *vbase;
};
struct imx7_cpuidle_pm_info {
phys_addr_t vbase; /* The virtual address of pm_info. */
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;
u32 ttbr;
u32 num_online_cpus;
u32 num_lpi_cpus;
atomic_t val;
atomic_t flag0;
atomic_t flag1;
struct imx7_pm_base ddrc_base;
struct imx7_pm_base ccm_base;
struct imx7_pm_base anatop_base;
struct imx7_pm_base src_base;
struct imx7_pm_base iomuxc_gpr_base;
struct imx7_pm_base gpc_base;
struct imx7_pm_base gic_dist_base;
} __aligned(8);
static atomic_t master_lpi = ATOMIC_INIT(0);
static atomic_t master_wait = ATOMIC_INIT(0);
static void (*imx7d_wfi_in_iram_fn)(void __iomem *iram_vbase);
static struct imx7_cpuidle_pm_info *cpuidle_pm_info;
#define MX7D_POWERDWN_IDLE_PARAM \
((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
#define MX7D_STANDBY_IDLE_PARAM \
((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_STANDBY << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
/* Mapped for the kernel, unlike cpuidle_pm_info->gic_dist_base.vbase */
static void __iomem *imx7d_cpuidle_gic_base;
static void imx_pen_lock(int cpu)
{
if (cpu == 0) {
atomic_set(&cpuidle_pm_info->flag0, 1);
dsb();
atomic_set(&cpuidle_pm_info->val, cpu);
do {
dsb();
} while (atomic_read(&cpuidle_pm_info->flag1) == 1
&& atomic_read(&cpuidle_pm_info->val) == cpu)
;
} else {
atomic_set(&cpuidle_pm_info->flag1, 1);
dsb();
atomic_set(&cpuidle_pm_info->val, cpu);
do {
dsb();
} while (atomic_read(&cpuidle_pm_info->flag0) == 1
&& atomic_read(&cpuidle_pm_info->val) == cpu)
;
}
}
static void imx_pen_unlock(int cpu)
{
dsb();
if (cpu == 0)
atomic_set(&cpuidle_pm_info->flag0, 0);
else
atomic_set(&cpuidle_pm_info->flag1, 0);
}
static int imx7d_idle_finish(unsigned long val)
{
if (psci_ops.cpu_suspend)
psci_ops.cpu_suspend(MX7D_POWERDWN_IDLE_PARAM, __pa(cpu_resume));
else
imx7d_wfi_in_iram_fn(wfi_iram_base);
return 0;
}
static bool imx7d_gic_sgis_pending(void)
{
void __iomem *sgip_base = imx7d_cpuidle_gic_base + 0x1f20;
return (readl_relaxed(sgip_base + 0x0) |
readl_relaxed(sgip_base + 0x4) |
readl_relaxed(sgip_base + 0x8) |
readl_relaxed(sgip_base + 0xc));
}
static DEFINE_SPINLOCK(psci_lock);
static int imx7d_enter_low_power_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
int mode = get_bus_freq_mode();
if ((index == 1) || ((mode != BUS_FREQ_LOW) && index == 2)) {
index = 1;
if (atomic_inc_return(&master_wait) == num_online_cpus())
imx_gpcv2_set_lpm_mode(WAIT_UNCLOCKED);
cpu_do_idle();
atomic_dec(&master_wait);
imx_gpcv2_set_lpm_mode(WAIT_CLOCKED);
} else {
if (psci_ops.cpu_suspend) {
cpu_pm_enter();
spin_lock(&psci_lock);
if (atomic_inc_return(&master_lpi) == num_online_cpus()) {
if (imx7d_gic_sgis_pending()) {
atomic_dec(&master_lpi);
index = -1;
goto psci_skip_lpi_flow;
}
imx_gpcv2_set_lpm_mode(WAIT_UNCLOCKED);
imx_gpcv2_set_cpu_power_gate_in_idle(true);
cpu_cluster_pm_enter();
}
spin_unlock(&psci_lock);
cpu_suspend(0, imx7d_idle_finish);
spin_lock(&psci_lock);
if (atomic_read(&master_lpi) == num_online_cpus()) {
cpu_cluster_pm_exit();
imx_gpcv2_set_cpu_power_gate_in_idle(false);
imx_gpcv2_set_lpm_mode(WAIT_CLOCKED);
}
atomic_dec(&master_lpi);
psci_skip_lpi_flow:
spin_unlock(&psci_lock);
cpu_pm_exit();
} else {
imx_pen_lock(dev->cpu);
cpuidle_pm_info->num_online_cpus = num_online_cpus();
++cpuidle_pm_info->num_lpi_cpus;
cpu_pm_enter();
if (cpuidle_pm_info->num_lpi_cpus ==
cpuidle_pm_info->num_online_cpus) {
/*
* GPC will not wake on SGIs so check for them
* manually here. At this point we know the other cpu
* is in wfi or waiting for the lock and can't send
* any additional IPIs.
*/
if (imx7d_gic_sgis_pending()) {
index = -1;
goto skip_lpi_flow;
}
imx_gpcv2_set_lpm_mode(WAIT_UNCLOCKED);
imx_gpcv2_set_cpu_power_gate_in_idle(true);
cpu_cluster_pm_enter();
} else {
imx_set_cpu_jump(dev->cpu, ca7_cpu_resume);
}
cpu_suspend(0, imx7d_idle_finish);
if (cpuidle_pm_info->num_lpi_cpus ==
cpuidle_pm_info->num_online_cpus) {
cpu_cluster_pm_exit();
imx_gpcv2_set_cpu_power_gate_in_idle(false);
imx_gpcv2_set_lpm_mode(WAIT_CLOCKED);
}
skip_lpi_flow:
cpu_pm_exit();
--cpuidle_pm_info->num_lpi_cpus;
imx_pen_unlock(dev->cpu);
}
}
return index;
}
static struct cpuidle_driver imx7d_cpuidle_driver = {
.name = "imx7d_cpuidle",
.owner = THIS_MODULE,
.states = {
/* WFI */
ARM_CPUIDLE_WFI_STATE,
/* WAIT MODE */
{
.exit_latency = 50,
.target_residency = 75,
.flags = CPUIDLE_FLAG_TIMER_STOP,
.enter = imx7d_enter_low_power_idle,
.name = "WAIT",
.desc = "Clock off",
},
/* LOW POWER IDLE */
{
.exit_latency = 10000,
.target_residency = 20000,
.flags = CPUIDLE_FLAG_TIMER_STOP,
.enter = imx7d_enter_low_power_idle,
.name = "LOW-POWER-IDLE",
.desc = "ARM power off",
},
},
.state_count = 3,
.safe_state_index = 0,
};
int imx7d_enable_rcosc(void)
{
void __iomem *anatop_base =
(void __iomem *)IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR);
u32 val;
imx_gpcv2_set_lpm_mode(WAIT_CLOCKED);
/* set RC-OSC freq and turn it on */
writel_relaxed(0x1 << XTALOSC_CTRL_24M_RC_OSC_EN_SHIFT,
anatop_base + XTALOSC_CTRL_24M + REG_SET);
/*
* config RC-OSC freq
* tune_enable = 1;tune_start = 1;hyst_plus = 0;hyst_minus = 0;
* osc_prog = 0xa7;
*/
writel_relaxed(
0x4 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT |
0xa7 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT |
0x1 << XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT |
0x1 << XTALOSC24M_OSC_CONFIG0_START_SHIFT,
anatop_base + XTALOSC24M_OSC_CONFIG0);
/* set count_trg = 0x2dc */
writel_relaxed(
0x40 << XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT |
0x2dc << XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT,
anatop_base + XTALOSC24M_OSC_CONFIG1);
/* wait at least 4ms according to hardware design */
mdelay(6);
/*
* now add some hysteresis, hyst_plus=3, hyst_minus=3
* (the minimum hysteresis that looks good is 2)
*/
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG0);
val &= ~((XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK <<
XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) |
(XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK <<
XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT));
val |= (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) |
(0x3 << XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT);
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG0);
/* set the count_1m_trg = 0x2d7 */
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG2);
val &= ~(XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK <<
XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT);
val |= 0x2d7 << XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT;
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG2);
/*
* hardware design require to write XTALOSC24M_OSC_CONFIG0 or
* XTALOSC24M_OSC_CONFIG1 to
* make XTALOSC24M_OSC_CONFIG2 write work
*/
val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG1);
writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG1);
return 0;
}
int __init imx7d_cpuidle_init(void)
{
wfi_iram_base_phys = (void *)(iram_tlb_phys_addr +
MX7_CPUIDLE_OCRAM_ADDR_OFFSET);
/* Make sure wfi_iram_base is 8 byte aligned. */
if ((uintptr_t)(wfi_iram_base_phys) & (FNCPY_ALIGN - 1))
wfi_iram_base_phys += FNCPY_ALIGN -
((uintptr_t)wfi_iram_base_phys % (FNCPY_ALIGN));
wfi_iram_base = (void *)IMX_IO_P2V((unsigned long) wfi_iram_base_phys);
cpuidle_pm_info = wfi_iram_base;
cpuidle_pm_info->vbase = (phys_addr_t) wfi_iram_base;
cpuidle_pm_info->pbase = (phys_addr_t) wfi_iram_base_phys;
cpuidle_pm_info->pm_info_size = sizeof(*cpuidle_pm_info);
cpuidle_pm_info->resume_addr = virt_to_phys(ca7_cpu_resume);
cpuidle_pm_info->num_online_cpus = num_online_cpus();
cpuidle_pm_info->ddrc_base.pbase = MX7D_DDRC_BASE_ADDR;
cpuidle_pm_info->ddrc_base.vbase =
(void __iomem *)IMX_IO_P2V(MX7D_DDRC_BASE_ADDR);
cpuidle_pm_info->ccm_base.pbase = MX7D_CCM_BASE_ADDR;
cpuidle_pm_info->ccm_base.vbase =
(void __iomem *)IMX_IO_P2V(MX7D_CCM_BASE_ADDR);
cpuidle_pm_info->anatop_base.pbase = MX7D_ANATOP_BASE_ADDR;
cpuidle_pm_info->anatop_base.vbase =
(void __iomem *)IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR);
cpuidle_pm_info->src_base.pbase = MX7D_SRC_BASE_ADDR;
cpuidle_pm_info->src_base.vbase =
(void __iomem *)IMX_IO_P2V(MX7D_SRC_BASE_ADDR);
cpuidle_pm_info->iomuxc_gpr_base.pbase = MX7D_IOMUXC_GPR_BASE_ADDR;
cpuidle_pm_info->iomuxc_gpr_base.vbase =
(void __iomem *)IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR);
cpuidle_pm_info->gpc_base.pbase = MX7D_GPC_BASE_ADDR;
cpuidle_pm_info->gpc_base.vbase =
(void __iomem *)IMX_IO_P2V(MX7D_GPC_BASE_ADDR);
cpuidle_pm_info->gic_dist_base.pbase = MX7D_GIC_BASE_ADDR;
cpuidle_pm_info->gic_dist_base.vbase =
(void __iomem *)IMX_IO_P2V(MX7D_GIC_BASE_ADDR);
imx7d_cpuidle_gic_base = ioremap(MX7D_GIC_BASE_ADDR, MX7D_GIC_SIZE);
imx7d_enable_rcosc();
/* code size should include cpuidle_pm_info size */
if (!psci_ops.cpu_suspend) {
imx7d_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base +
sizeof(*cpuidle_pm_info),
&imx7d_low_power_idle,
MX7_CPUIDLE_OCRAM_SIZE - sizeof(*cpuidle_pm_info));
}
return cpuidle_register(&imx7d_cpuidle_driver, NULL);
}

View File

@ -8,7 +8,11 @@
extern int imx5_cpuidle_init(void);
extern int imx6q_cpuidle_init(void);
extern int imx6sl_cpuidle_init(void);
extern int imx6sll_cpuidle_init(void);
extern int imx6sx_cpuidle_init(void);
extern int imx6ul_cpuidle_init(void);
extern int imx7d_cpuidle_init(void);
extern int imx7d_enable_rcosc(void);
extern int imx7ulp_cpuidle_init(void);
#else
static inline int imx5_cpuidle_init(void)
@ -23,10 +27,26 @@ static inline int imx6sl_cpuidle_init(void)
{
return 0;
}
static inline int imx6sll_cpuidle_init(void)
{
return 0;
}
static inline int imx6sx_cpuidle_init(void)
{
return 0;
}
static inline int imx6ul_cpuidle_init(void)
{
return 0;
}
static inline int imx7d_cpuidle_init(void)
{
return 0;
}
static inline int imx7d_enable_rcosc(void)
{
return 0;
}
static inline int imx7ulp_cpuidle_init(void)
{
return 0;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,764 @@
/*
* Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include "hardware.h"
.globl imx6_up_ddr3_freq_change_start
.globl imx6_up_ddr3_freq_change_end
#define MMDC0_MDPDC 0x4
#define MMDC0_MDCF0 0xc
#define MMDC0_MDCF1 0x10
#define MMDC0_MDMISC 0x18
#define MMDC0_MDSCR 0x1c
#define MMDC0_MAPSR 0x404
#define MMDC0_MADPCR0 0x410
#define MMDC0_MPZQHWCTRL 0x800
#define MMDC0_MPODTCTRL 0x818
#define MMDC0_MPDGCTRL0 0x83c
#define MMDC0_MPMUR0 0x8b8
#define CCM_CBCDR 0x14
#define CCM_CBCMR 0x18
#define CCM_CSCMR1 0x1c
#define CCM_CDHIPR 0x48
#define L2_CACHE_SYNC 0x730
#define PL310_AUX_CTRL 0x104
#define PL310_DCACHE_LOCKDOWN_BASE 0x900
#define PL310_AUX_16WAY_BIT 0x10000
#define PL310_LOCKDOWN_NBREGS 8
#define PL310_LOCKDOWN_SZREG 4
#define PL310_8WAYS_MASK 0x00FF
#define PL310_16WAYS_UPPERMASK 0xFF00
#define BUSFREQ_INFO_FREQ_OFFSET 0x0
#define BUSFREQ_INFO_DDR_SETTINGS_OFFSET 0x4
#define BUSFREQ_INFO_DLL_OFF_OFFSET 0x8
#define BUSFREQ_INFO_IOMUX_OFFSETS_OFFSET 0xc
#define BUSFREQ_INFO_MU_DELAY_OFFSET 0x10
.extern iram_tlb_phys_addr
.align 3
/* Check if the cpu is cortex-a7 */
.macro is_ca7
/* Read the primary cpu number is MPIDR */
mrc p15, 0, r7, c0, c0, 0
ldr r8, =0xfff0
and r7, r7, r8
ldr r8, =0xc070
cmp r7, r8
.endm
.macro do_delay
1:
ldr r9, =0
2:
ldr r10, [r4, r9]
add r9, r9, #4
cmp r9, #16
bne 2b
sub r8, r8, #1
cmp r8, #0
bgt 1b
.endm
.macro wait_for_ccm_handshake
3:
ldr r8, [r5, #CCM_CDHIPR]
cmp r8, #0
bne 3b
.endm
.macro switch_to_400MHz
/* check whether periph2_clk is already from top path */
ldr r8, [r5, #CCM_CBCDR]
ands r8, #(1 << 26)
beq skip_periph2_clk2_switch_400m
/* now switch periph2_clk back. */
ldr r8, [r5, #CCM_CBCDR]
bic r8, r8, #(1 << 26)
str r8, [r5, #CCM_CBCDR]
wait_for_ccm_handshake
/*
* on i.MX6SX, pre_periph2_clk will be always from
* pll2_pfd2, so no need to set pre_periph2_clk
* parent, just set the mmdc divider directly.
*/
skip_periph2_clk2_switch_400m:
/* fabric_mmdc_podf to 0 */
ldr r8, [r5, #CCM_CBCDR]
bic r8, r8, #(0x7 << 3)
str r8, [r5, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro switch_to_50MHz
/* check whether periph2_clk is already from top path */
ldr r8, [r5, #CCM_CBCDR]
ands r8, #(1 << 26)
beq skip_periph2_clk2_switch_50m
/* now switch periph2_clk back. */
ldr r8, [r5, #CCM_CBCDR]
bic r8, r8, #(1 << 26)
str r8, [r5, #CCM_CBCDR]
wait_for_ccm_handshake
/*
* on i.MX6SX, pre_periph2_clk will be always from
* pll2_pfd2, so no need to set pre_periph2_clk
* parent, just set the mmdc divider directly.
*/
skip_periph2_clk2_switch_50m:
/* fabric_mmdc_podf to 7 so that mmdc is 400 / 8 = 50MHz */
ldr r8, [r5, #CCM_CBCDR]
orr r8, r8, #(0x7 << 3)
str r8, [r5, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro switch_to_24MHz
/* periph2_clk2 sel to OSC_CLK */
ldr r8, [r5, #CCM_CBCMR]
orr r8, r8, #(1 << 20)
str r8, [r5, #CCM_CBCMR]
/* periph2_clk2_podf to 0 */
ldr r8, [r5, #CCM_CBCDR]
bic r8, r8, #0x7
str r8, [r5, #CCM_CBCDR]
/* periph2_clk sel to periph2_clk2 */
ldr r8, [r5, #CCM_CBCDR]
orr r8, r8, #(0x1 << 26)
str r8, [r5, #CCM_CBCDR]
wait_for_ccm_handshake
/* fabric_mmdc_podf to 0 */
ldr r8, [r5, #CCM_CBCDR]
bic r8, r8, #(0x7 << 3)
str r8, [r5, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
/*
* imx6_up_ddr3_freq_change
* Below code can be used by i.MX6SX and i.MX6UL.
*
* idle the processor (eg, wait for interrupt).
* make sure DDR is in self-refresh.
* IRQs are already disabled.
*/
ENTRY(imx6_up_ddr3_freq_change)
imx6_up_ddr3_freq_change_start:
stmfd sp!, {r4 - r11}
ldr r1, [r0, #BUSFREQ_INFO_DDR_SETTINGS_OFFSET]
ldr r2, [r0, #BUSFREQ_INFO_DLL_OFF_OFFSET]
ldr r3, [r0, #BUSFREQ_INFO_IOMUX_OFFSETS_OFFSET]
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* 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 Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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
dsb
isb
/* Disable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
ldr r4, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
ldr r5, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
ldr r6, =IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR)
is_ca7
beq skip_disable_l2
#ifdef CONFIG_CACHE_L2X0
/*
* make sure the L2 buffers are drained,
* sync operation on L2 drains the buffers.
*/
ldr r8, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
/* Wait for background operations to complete. */
wait_for_l2_to_idle:
ldr r7, [r8, #0x730]
cmp r7, #0x0
bne wait_for_l2_to_idle
mov r7, #0x0
str r7, [r8, #L2_CACHE_SYNC]
/* Lock L2. */
ldr r9, [r8, #PL310_AUX_CTRL]
tst r9, #PL310_AUX_16WAY_BIT
mov r9, #PL310_8WAYS_MASK
orrne r9, #PL310_16WAYS_UPPERMASK
mov r10, #PL310_LOCKDOWN_NBREGS
add r11, r8, #PL310_DCACHE_LOCKDOWN_BASE
1: /* lock Dcache and Icache */
str r9, [r11], #PL310_LOCKDOWN_SZREG
str r9, [r11], #PL310_LOCKDOWN_SZREG
subs r10, r10, #1
bne 1b
/*
* The second dsb might be needed to keep cache sync (device write)
* ordering with the memory accesses before it.
*/
dsb
isb
#endif
skip_disable_l2:
/* disable automatic power saving. */
ldr r8, [r4, #MMDC0_MAPSR]
orr r8, r8, #0x1
str r8, [r4, #MMDC0_MAPSR]
/* disable MMDC power down timer. */
ldr r8, [r4, #MMDC0_MDPDC]
bic r8, r8, #(0xff << 8)
str r8, [r4, #MMDC0_MDPDC]
/* delay for a while */
ldr r8, =4
do_delay
/* set CON_REG */
ldr r8, =0x8000
str r8, [r4, #MMDC0_MDSCR]
poll_conreq_set_1:
ldr r8, [r4, #MMDC0_MDSCR]
and r8, r8, #(0x4 << 12)
cmp r8, #(0x4 << 12)
bne poll_conreq_set_1
/*
* if requested frequency is greater than
* 300MHz go to DLL on mode.
*/
ldr r8, [r0, #BUSFREQ_INFO_FREQ_OFFSET]
ldr r9, =300000000
cmp r8, r9
bge dll_on_mode
dll_off_mode:
/* if DLL is currently on, turn it off. */
cmp r2, #1
beq continue_dll_off_1
ldr r8, =0x00018031
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x00018039
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =10
do_delay
continue_dll_off_1:
/* set DVFS - enter self refresh mode */
ldr r8, [r4, #MMDC0_MAPSR]
orr r8, r8, #(1 << 21)
str r8, [r4, #MMDC0_MAPSR]
/* de-assert con_req */
mov r8, #0x0
str r8, [r4, #MMDC0_MDSCR]
poll_dvfs_set_1:
ldr r8, [r4, #MMDC0_MAPSR]
and r8, r8, #(1 << 25)
cmp r8, #(1 << 25)
bne poll_dvfs_set_1
ldr r8, [r0, #BUSFREQ_INFO_FREQ_OFFSET]
ldr r9, =24000000
cmp r8, r9
beq switch_freq_24
switch_to_50MHz
b continue_dll_off_2
switch_freq_24:
switch_to_24MHz
continue_dll_off_2:
/* set SBS - block ddr accesses */
ldr r8, [r4, #MMDC0_MADPCR0]
orr r8, r8, #(1 << 8)
str r8, [r4, #MMDC0_MADPCR0]
/* clear DVFS - exit from self refresh mode */
ldr r8, [r4, #MMDC0_MAPSR]
bic r8, r8, #(1 << 21)
str r8, [r4, #MMDC0_MAPSR]
poll_dvfs_clear_1:
ldr r8, [r4, #MMDC0_MAPSR]
and r8, r8, #(1 << 25)
cmp r8, #(1 << 25)
beq poll_dvfs_clear_1
/* if DLL was previously on, continue DLL off routine. */
cmp r2, #1
beq continue_dll_off_3
ldr r8, =0x00018031
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x00018039
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x04208030
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x04208038
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x00088032
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x0008803A
str r8, [r4, #MMDC0_MDSCR]
/* delay for a while. */
ldr r8, =4
do_delay
ldr r8, [r4, #MMDC0_MDCF0]
bic r8, r8, #0xf
orr r8, r8, #0x3
str r8, [r4, #MMDC0_MDCF0]
ldr r8, [r4, #MMDC0_MDCF1]
bic r8, r8, #0x7
orr r8, r8, #0x4
str r8, [r4, #MMDC0_MDCF1]
ldr r8, [r4, #MMDC0_MDMISC]
bic r8, r8, #(0x3 << 16) /* walat = 0x1 */
orr r8, r8, #(0x1 << 16)
bic r8, r8, #(0x7 << 6) /* ralat = 0x2 */
orr r8, r8, #(0x2 << 6)
str r8, [r4, #MMDC0_MDMISC]
/* enable dqs pull down in the IOMUX. */
ldr r8, [r3]
add r3, r3, #8
ldr r9, =0x3028
update_iomux:
ldr r10, [r3]
ldr r11, [r6, r10]
bic r11, r11, r9
orr r11, r11, #(0x3 << 12)
orr r11, r11, #0x28
str r11, [r6, r10]
add r3, r3, #8
sub r8, r8, #1
cmp r8, #0
bgt update_iomux
/* ODT disabled. */
ldr r8, =0x0
str r8, [r4, #MMDC0_MPODTCTRL]
/* DQS gating disabled. */
ldr r8, [r4, #MMDC0_MPDGCTRL0]
orr r8, r8, #(1 << 29)
str r8, [r4, #MMDC0_MPDGCTRL0]
/* Add workaround for ERR005778.*/
/* double the original MU_UNIT_DEL_NUM. */
ldr r8, [r0, #BUSFREQ_INFO_MU_DELAY_OFFSET]
lsl r8, r8, #1
/* Bypass the automatic MU by setting the mu_byp_en */
ldr r10, [r4, #MMDC0_MPMUR0]
orr r10, r10, #0x400
/* Set the MU_BYP_VAL */
orr r10, r10, r8
str r10, [r4, #MMDC0_MPMUR0]
/* Now perform a force measure */
ldr r8, [r4, #MMDC0_MPMUR0]
orr r8, r8, #0x800
str r8, [r4, #MMDC0_MPMUR0]
/* Wait for FRC_MSR to clear. */
1:
ldr r8, [r4, #MMDC0_MPMUR0]
and r8, r8, #0x800
cmp r8, #0x0
bne 1b
continue_dll_off_3:
/* clear SBS - unblock accesses to DDR. */
ldr r8, [r4, #MMDC0_MADPCR0]
bic r8, r8, #(0x1 << 8)
str r8, [r4, #MMDC0_MADPCR0]
mov r8, #0x0
str r8, [r4, #MMDC0_MDSCR]
poll_conreq_clear_1:
ldr r8, [r4, #MMDC0_MDSCR]
and r8, r8, #(0x4 << 12)
cmp r8, #(0x4 << 12)
beq poll_conreq_clear_1
b done
dll_on_mode:
/* assert DVFS - enter self refresh mode. */
ldr r8, [r4, #MMDC0_MAPSR]
orr r8, r8, #(1 << 21)
str r8, [r4, #MMDC0_MAPSR]
/* de-assert CON_REQ. */
mov r8, #0x0
str r8, [r4, #MMDC0_MDSCR]
/* poll DVFS ack. */
poll_dvfs_set_2:
ldr r8, [r4, #MMDC0_MAPSR]
and r8, r8, #(1 << 25)
cmp r8, #(1 << 25)
bne poll_dvfs_set_2
switch_to_400MHz
/* set SBS step-by-step mode. */
ldr r8, [r4, #MMDC0_MADPCR0]
orr r8, r8, #(1 << 8)
str r8, [r4, #MMDC0_MADPCR0]
/* clear DVFS - exit self refresh mode. */
ldr r8, [r4, #MMDC0_MAPSR]
bic r8, r8, #(1 << 21)
str r8, [r4, #MMDC0_MAPSR]
poll_dvfs_clear_2:
ldr r8, [r4, #MMDC0_MAPSR]
ands r8, r8, #(1 << 25)
bne poll_dvfs_clear_2
/* if DLL is currently off, turn it back on. */
cmp r2, #0
beq update_calibration_only
/* issue zq calibration command */
ldr r8, [r4, #MMDC0_MPZQHWCTRL]
orr r8, r8, #0x3
str r8, [r4, #MMDC0_MPZQHWCTRL]
/* enable DQS gating. */
ldr r10, =MMDC0_MPDGCTRL0
ldr r8, [r4, r10]
bic r8, r8, #(1 << 29)
str r8, [r4, r10]
/* Now perform a force measure */
ldr r8, =0x00000800
str r8, [r4, #MMDC0_MPMUR0]
/* Wait for FRC_MSR to clear. */
1:
ldr r8, [r4, #MMDC0_MPMUR0]
and r8, r8, #0x800
cmp r8, #0x0
bne 1b
/* disable dqs pull down in the IOMUX. */
ldr r8, [r3]
add r3, r3, #8
update_iomux1:
ldr r10, [r3, #0x0]
ldr r11, [r3, #0x4]
str r11, [r6, r10]
add r3, r3, #8
sub r8, r8, #1
cmp r8, #0
bgt update_iomux1
/* config MMDC timings to 400MHz. */
ldr r1, [r0, #BUSFREQ_INFO_DDR_SETTINGS_OFFSET]
ldr r7, [r1]
add r1, r1, #8
ldr r10, [r1, #0x0]
ldr r11, [r1, #0x4]
str r11, [r4, r10]
add r1, r1, #8
ldr r10, [r1, #0x0]
ldr r11, [r1, #0x4]
str r11, [r4, r10]
add r1, r1, #8
/* configure ddr devices to dll on, odt. */
ldr r8, =0x00028031
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x00028039
str r8, [r4, #MMDC0_MDSCR]
/* delay for while. */
ldr r8, =4
do_delay
/* reset dll. */
ldr r8, =0x09208030
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x09208038
str r8, [r4, #MMDC0_MDSCR]
/* delay for while. */
ldr r8, =100
do_delay
ldr r10, [r1, #0x0]
ldr r11, [r1, #0x4]
str r11, [r4, r10]
add r1, r1, #8
ldr r10, [r1, #0x0]
ldr r11, [r1, #0x4]
str r11, [r4, r10]
add r1, r1, #8
ldr r8, =0x00428031
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x00428039
str r8, [r4, #MMDC0_MDSCR]
ldr r10, [r1, #0x0]
ldr r11, [r1, #0x4]
str r11, [r4, r10]
add r1, r1, #8
ldr r10, [r1, #0x0]
ldr r11, [r1, #0x4]
str r11, [r4, r10]
add r1, r1, #8
/* issue a zq command. */
ldr r8, =0x04008040
str r8, [r4, #MMDC0_MDSCR]
ldr r8, =0x04008048
str r8, [r4, #MMDC0_MDSCR]
/* MMDC ODT enable. */
ldr r10, [r1, #0x0]
ldr r11, [r1, #0x4]
str r11, [r4, r10]
add r1, r1, #8
/* delay for while. */
ldr r8, =40
do_delay
/* enable MMDC power down timer. */
ldr r8, [r4, #MMDC0_MDPDC]
orr r8, r8, #(0x55 << 8)
str r8, [r4, #MMDC0_MDPDC]
b update_calibration
update_calibration_only:
ldr r8, [r1]
sub r8, r8, #7
add r1, r1, #64
b update_calib
update_calibration:
/* write the new calibration values. */
mov r8, r7
sub r8, r8, #7
update_calib:
ldr r10, [r1, #0x0]
ldr r11, [r1, #0x4]
str r11, [r4, r10]
add r1, r1, #8
sub r8, r8, #1
cmp r8, #0
bgt update_calib
/* perform a force measurement. */
ldr r8, =0x800
str r8, [r4, #MMDC0_MPMUR0]
/* Wait for FRC_MSR to clear. */
1:
ldr r8, [r4, #MMDC0_MPMUR0]
and r8, r8, #0x800
cmp r8, #0x0
bne 1b
/* clear SBS - unblock DDR accesses. */
ldr r8, [r4, #MMDC0_MADPCR0]
bic r8, r8, #(1 << 8)
str r8, [r4, #MMDC0_MADPCR0]
mov r8, #0x0
str r8, [r4, #MMDC0_MDSCR]
poll_conreq_clear_2:
ldr r8, [r4, #MMDC0_MDSCR]
and r8, r8, #(0x4 << 12)
cmp r8, #(0x4 << 12)
beq poll_conreq_clear_2
done:
/* MMDC0_MAPSR adopt power down enable. */
ldr r8, [r4, #MMDC0_MAPSR]
bic r8, r8, #0x01
str r8, [r4, #MMDC0_MAPSR]
is_ca7
beq skip_enable_l2
#ifdef CONFIG_CACHE_L2X0
/* Unlock L2. */
ldr r8, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
ldr r9, [r8, #PL310_AUX_CTRL]
tst r9, #PL310_AUX_16WAY_BIT
mov r10, #PL310_LOCKDOWN_NBREGS
mov r9, #0x00 /* 8 ways mask */
orrne r9, #0x0000 /* 16 ways mask */
add r11, r8, #PL310_DCACHE_LOCKDOWN_BASE
1: /* lock Dcache and Icache */
str r9, [r11], #PL310_LOCKDOWN_SZREG
str r9, [r11], #PL310_LOCKDOWN_SZREG
subs r10, r10, #1
bne 1b
#endif
skip_enable_l2:
/* Enable L1 data cache. */
mrc p15, 0, r7, c1, c0, 0
orr r7, r7, #0x4
mcr p15, 0, r7, c1, c0, 0
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* Enable Branch Prediction, Z bit in SCTLR. */
mrc p15, 0, r7, c1, c0, 0
orr r7, r7, #0x800
mcr p15, 0, r7, c1, c0, 0
/* Flush the Branch Target Address Cache (BTAC) */
ldr r7, =0x0
mcr p15, 0, r7, c7, c1, 6
/* restore registers */
ldmfd sp!, {r4 - r11}
mov pc, lr
/*
* Add ltorg here to ensure that all
* literals are stored here and are
* within the text space.
*/
.ltorg
imx6_up_ddr3_freq_change_end:
ENDPROC(imx6_up_ddr3_freq_change)

View File

@ -0,0 +1,586 @@
/*
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include "hardware.h"
#define DDRC_MSTR 0x0
#define DDRC_STAT 0x4
#define DDRC_MRCTRL0 0x10
#define DDRC_MRCTRL1 0x14
#define DDRC_MRSTAT 0x18
#define DDRC_PWRCTL 0x30
#define DDRC_RFSHCTL3 0x60
#define DDRC_RFSHTMG 0x64
#define DDRC_DBG1 0x304
#define DDRC_SWCTL 0x320
#define DDRC_SWSTAT 0x324
#define DDRC_PSTAT 0x3fc
#define DDRC_PCTRL_0 0x490
#define DDRC_ZQCTL0 0x180
#define DDRC_DFIMISC 0x1b0
#define DDRC_DBGCAM 0x308
#define DDRPHY_LP_CON0 0x18
#define IOMUXC_GPR8 0x20
#define DDRPHY_MDLL_CON0 0xb0
#define DDRPHY_MDLL_CON1 0xb4
#define DDRPHY_OFFSETD_CON0 0x50
#define DDRPHY_OFFSETR_CON0 0x20
#define DDRPHY_OFFSETR_CON1 0x24
#define DDRPHY_OFFSETR_CON2 0x28
#define DDRPHY_OFFSETW_CON0 0x30
#define DDRPHY_OFFSETW_CON1 0x34
#define DDRPHY_OFFSETW_CON2 0x38
#define DDRPHY_CA_WLDSKEW_CON0 0x6c
#define DDRPHY_CA_DSKEW_CON0 0x7c
#define DDRPHY_CA_DSKEW_CON1 0x80
#define DDRPHY_CA_DSKEW_CON2 0x84
#define ANADIG_DIGPROG 0x800
.align 3
.macro switch_to_below_100m
ldr r7, =0x2
str r7, [r4, #DDRC_DBG1]
ldr r6, =0x36000000
1:
ldr r7, [r4, #DDRC_DBGCAM]
and r7, r7, r6
cmp r7, r6
bne 1b
ldr r6, =0x1
2:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 2b
ldr r7, =0x10f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x0
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800010f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r6, =0x1
3:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 3b
ldr r7, =0x20f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x8
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800020f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r6, =0x1
4:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 4b
ldr r7, =0x10f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x1
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800010f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x20
str r7, [r4, #DDRC_PWRCTL]
ldr r6, =0x23
5:
ldr r7, [r4, #DDRC_STAT]
and r7, r7, r6
cmp r7, r6
bne 5b
ldr r7, =0x0
str r7, [r4, #DDRC_SWCTL]
ldr r7, =0x03048001
str r7, [r4, #DDRC_MSTR]
ldr r7, =0x1
str r7, [r4, #DDRC_SWCTL]
ldr r6, =0x1
6:
ldr r7, [r4, #DDRC_SWSTAT]
and r7, r7, r6
cmp r7, r6
bne 6b
ldr r7, =0x10010100
str r7, [r5, #0x4]
ldr r6, =24000000
cmp r0, r6
beq 25f
ldr r7, =0x000B000D
str r7,[r4, #DDRC_RFSHTMG]
b 7f
25:
ldr r7, =0x00030004
str r7,[r4, #DDRC_RFSHTMG]
/* dram alt sel set to OSC */
ldr r7, =0x10000000
ldr r8, =0xa080
str r7, [r2, r8]
/* dram root set to from dram alt, div by 1 */
ldr r7, =0x11000000
ldr r8, =0x9880
str r7, [r2, r8]
b 8f
7:
/* dram alt sel set to pfd0_392m */
ldr r7, =0x15000000
ldr r8, =0xa080
str r7, [r2, r8]
/* dram root set to from dram alt, div by 4 */
ldr r7, =0x11000003
ldr r8, =0x9880
str r7, [r2, r8]
8:
ldr r7, =0x202ffd0
str r7, [r5, #DDRPHY_MDLL_CON0]
ldr r7, =0x1000007f
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x7f7f7f7f
str r7, [r5, #DDRPHY_OFFSETR_CON0]
str r7, [r5, #DDRPHY_OFFSETR_CON1]
ldr r7, =0x7f
str r7, [r5, #DDRPHY_OFFSETR_CON2]
ldr r7, =0x7f7f7f7f
str r7, [r5, #DDRPHY_OFFSETW_CON0]
str r7, [r5, #DDRPHY_OFFSETW_CON1]
ldr r7, =0x7f
str r7, [r5, #DDRPHY_OFFSETW_CON2]
ldr r7, [r9, #ANADIG_DIGPROG]
and r7, r7, #0x11
cmp r7, #0x11
bne 20f
ldr r7, =0x0
str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0]
ldr r7, =0x60606060
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
ldr r7, =0x00006060
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
b 21f
20:
ldr r7, =0x0
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
21:
ldr r7, =0x1100007f
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x1000007f
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x0
str r7, [r4, #DDRC_PWRCTL]
ldr r6, =0x1
9:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 9b
ldr r7, =0xf0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x820
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800000f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r6, =0x1
10:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 10b
ldr r7, =0x800020
str r7, [r4, #DDRC_ZQCTL0]
ldr r7, =0x0
str r7, [r4, #DDRC_DBG1]
/* enable auto self-refresh */
ldr r7, [r4, #DDRC_PWRCTL]
orr r7, r7, #(1 << 0)
str r7, [r4, #DDRC_PWRCTL]
.endm
.macro switch_to_533m
ldr r7, =0x2
str r7, [r4, #DDRC_DBG1]
ldr r7, =0x78
str r7, [r3, #IOMUXC_GPR8]
orr r7, r7, #0x100
str r7, [r3, #IOMUXC_GPR8]
ldr r6, =0x30000000
11:
ldr r7, [r4, #DDRC_DBGCAM]
and r7, r7, r6
cmp r7, r6
bne 11b
ldr r6, =0x1
12:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 12b
ldr r7, =0x10f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x1
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800010f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x20
str r7, [r4, #DDRC_PWRCTL]
ldr r6, =0x23
13:
ldr r7, [r4, #DDRC_STAT]
and r7, r7, r6
cmp r7, r6
bne 13b
ldr r7, =0x03040001
str r7, [r4, #DDRC_MSTR]
ldr r7, =0x40800020
str r7, [r4, #DDRC_ZQCTL0]
ldr r7, =0x10210100
str r7, [r5, #0x4]
ldr r7, =0x00040046
str r7, [r4, #DDRC_RFSHTMG]
/* dram root set to from dram main, div by 2 */
ldr r7, =0x10000001
ldr r8, =0x9880
str r7, [r2, r8]
ldr r7, =0x1010007e
str r7, [r5, #DDRPHY_MDLL_CON0]
ldr r7, =0x10000008
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x08080808
str r7, [r5, #DDRPHY_OFFSETR_CON0]
str r7, [r5, #DDRPHY_OFFSETR_CON1]
ldr r7, =0x8
str r7, [r5, #DDRPHY_OFFSETR_CON2]
ldr r7, =0x08080808
str r7, [r5, #DDRPHY_OFFSETW_CON0]
str r7, [r5, #DDRPHY_OFFSETW_CON1]
ldr r7, =0x8
str r7, [r5, #DDRPHY_OFFSETW_CON2]
ldr r7, [r9, #ANADIG_DIGPROG]
and r7, r7, #0x11
cmp r7, #0x11
bne 22f
ldr r7, =0x40404040
str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0]
ldr r7, =0x18181818
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
ldr r7, =0x40401818
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
b 23f
22:
ldr r7, =0x0
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
23:
ldr r7, =0x11000008
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x10000008
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r6, =0x4
14:
ldr r7, [r5, #DDRPHY_MDLL_CON1]
and r7, r7, r6
cmp r7, r6
bne 14b
ldr r7, =0x1
str r7, [r4, #DDRC_RFSHCTL3]
ldr r7, =0x3
str r7, [r4, #DDRC_RFSHCTL3]
ldr r7, =0x0
str r7, [r4, #DDRC_PWRCTL]
ldr r6, =0x1
15:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 15b
ldr r7, =0x10f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x0
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800010f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r6, =0x1
16:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 16b
ldr r7, =0xf0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x930
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800000f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x0
str r7, [r4, #DDRC_RFSHCTL3]
ldr r7, =0x2
str r7, [r4, #DDRC_RFSHCTL3]
ldr r6, =0x1
17:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 17b
ldr r7, =0xf0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x930
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800000f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r6, =0x1
18:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 18b
ldr r7, =0x20f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x408
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800020f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r6, =0x1
19:
ldr r7, [r4, #DDRC_MRSTAT]
and r7, r7, r6
cmp r7, r6
beq 19b
ldr r7, =0x10f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x4
str r7, [r4, #DDRC_MRCTRL1]
ldr r7, =0x800010f0
str r7, [r4, #DDRC_MRCTRL0]
ldr r7, =0x0
str r7, [r4, #DDRC_DBG1]
/* enable auto self-refresh */
ldr r7, [r4, #DDRC_PWRCTL]
orr r7, r7, #(1 << 0)
str r7, [r4, #DDRC_PWRCTL]
.endm
ENTRY(imx7d_ddr3_freq_change)
push {r2 - r9}
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =0x0
mcr p15, 0, r6, c8, c3, 0
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* 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 Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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
dsb
isb
ldr r2, =IMX_IO_P2V(MX7D_CCM_BASE_ADDR)
ldr r3, =IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR)
ldr r4, =IMX_IO_P2V(MX7D_DDRC_BASE_ADDR)
ldr r5, =IMX_IO_P2V(MX7D_DDRC_PHY_BASE_ADDR)
ldr r9, =IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR)
ldr r6, =100000000
cmp r0, r6
bgt set_to_533m
set_to_below_100m:
switch_to_below_100m
b done
set_to_533m:
switch_to_533m
b done
done:
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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
dsb
isb
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
/* Restore registers */
pop {r2 - r9}
mov pc, lr
.ltorg
ENDPROC(imx7d_ddr3_freq_change)

View File

@ -0,0 +1,86 @@
/*
* Copyright 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/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include "hardware.h"
#define DDRC_MSTR 0x0
#define BM_DDRC_MSTR_DDR3 0x1
#define BM_DDRC_MSTR_LPDDR2 0x4
#define BM_DDRC_MSTR_LPDDR3 0x8
static int ddr_type;
static int imx_ddrc_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
void __iomem *ddrc_base, *reg;
u32 val;
ddrc_base = of_iomap(np, 0);
WARN_ON(!ddrc_base);
reg = ddrc_base + DDRC_MSTR;
/* Get ddr type */
val = readl_relaxed(reg);
val &= (BM_DDRC_MSTR_DDR3 | BM_DDRC_MSTR_LPDDR2
| BM_DDRC_MSTR_LPDDR3);
switch (val) {
case BM_DDRC_MSTR_DDR3:
pr_info("DDR type is DDR3!\n");
ddr_type = IMX_DDR_TYPE_DDR3;
break;
case BM_DDRC_MSTR_LPDDR2:
pr_info("DDR type is LPDDR2!\n");
ddr_type = IMX_DDR_TYPE_LPDDR2;
break;
case BM_DDRC_MSTR_LPDDR3:
pr_info("DDR type is LPDDR3!\n");
ddr_type = IMX_DDR_TYPE_LPDDR3;
break;
default:
break;
}
return 0;
}
int imx_ddrc_get_ddr_type(void)
{
return ddr_type;
}
static struct of_device_id imx_ddrc_dt_ids[] = {
{ .compatible = "fsl,imx7-ddrc", },
{ /* sentinel */ }
};
static struct platform_driver imx_ddrc_driver = {
.driver = {
.name = "imx-ddrc",
.owner = THIS_MODULE,
.of_match_table = imx_ddrc_dt_ids,
},
.probe = imx_ddrc_probe,
};
static int __init imx_ddrc_init(void)
{
return platform_driver_register(&imx_ddrc_driver);
}
postcore_initcall(imx_ddrc_init);

View File

@ -14,22 +14,153 @@
#include "common.h"
#include "hardware.h"
#define GPC_CNTR 0x0
#define GPC_CNTR 0x000
#define GPC_CNTR_L2_PGE 22
#define GPC_IMR1 0x008
#define GPC_PGC_MF_PDN 0x220
#define GPC_PGC_CPU_PDN 0x2a0
#define GPC_PGC_CPU_PUPSCR 0x2a4
#define GPC_PGC_CPU_PDNSCR 0x2a8
#define GPC_PGC_SW2ISO_SHIFT 0x8
#define GPC_PGC_SW_SHIFT 0x0
#define GPC_M4_LPSR 0x2c
#define GPC_M4_LPSR_M4_SLEEPING_SHIFT 4
#define GPC_M4_LPSR_M4_SLEEPING_MASK 0x1
#define GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_MASK 0x1
#define GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_SHIFT 0
#define GPC_M4_LPSR_M4_SLEEP_HOLD_ACK_MASK 0x1
#define GPC_M4_LPSR_M4_SLEEP_HOLD_ACK_SHIFT 1
#define GPC_CNTR_L2_PGE_SHIFT 22
#define GPC_PGC_CPU_SW_SHIFT 0
#define GPC_PGC_CPU_SW_MASK 0x3f
#define GPC_PGC_CPU_SW2ISO_SHIFT 8
#define GPC_PGC_CPU_SW2ISO_MASK 0x3f
#define IMR_NUM 4
#define GPC_MAX_IRQS (IMR_NUM * 32)
/* for irq #74 and #75 */
#define GPC_USB_VBUS_WAKEUP_IRQ_MASK 0xc00
/* for irq #150 and #151 */
#define GPC_ENET_WAKEUP_IRQ_MASK 0xC00000
static void __iomem *gpc_base;
static u32 gpc_wake_irqs[IMR_NUM];
static u32 gpc_saved_imrs[IMR_NUM];
static u32 gpc_mf_irqs[IMR_NUM];
static u32 gpc_mf_request_on[IMR_NUM];
static DEFINE_SPINLOCK(gpc_lock);
void imx_gpc_add_m4_wake_up_irq(u32 hwirq, bool enable)
{
unsigned int idx = hwirq / 32;
unsigned long flags;
u32 mask;
/* Sanity check for SPI irq */
if (hwirq < 32)
return;
mask = 1 << hwirq % 32;
spin_lock_irqsave(&gpc_lock, flags);
gpc_wake_irqs[idx] = enable ? gpc_wake_irqs[idx] | mask :
gpc_wake_irqs[idx] & ~mask;
spin_unlock_irqrestore(&gpc_lock, flags);
}
void imx_gpc_hold_m4_in_sleep(void)
{
int val;
unsigned long timeout = jiffies + msecs_to_jiffies(500);
/* wait M4 in wfi before asserting hold request */
while (!imx_gpc_is_m4_sleeping())
if (time_after(jiffies, timeout))
pr_err("M4 is NOT in expected sleep!\n");
val = readl_relaxed(gpc_base + GPC_M4_LPSR);
val &= ~(GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_MASK <<
GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_SHIFT);
writel_relaxed(val, gpc_base + GPC_M4_LPSR);
timeout = jiffies + msecs_to_jiffies(500);
while (readl_relaxed(gpc_base + GPC_M4_LPSR)
& (GPC_M4_LPSR_M4_SLEEP_HOLD_ACK_MASK <<
GPC_M4_LPSR_M4_SLEEP_HOLD_ACK_SHIFT))
if (time_after(jiffies, timeout))
pr_err("Wait M4 hold ack timeout!\n");
}
void imx_gpc_release_m4_in_sleep(void)
{
int val;
val = readl_relaxed(gpc_base + GPC_M4_LPSR);
val |= GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_MASK <<
GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_SHIFT;
writel_relaxed(val, gpc_base + GPC_M4_LPSR);
}
unsigned int imx_gpc_is_m4_sleeping(void)
{
if (readl_relaxed(gpc_base + GPC_M4_LPSR) &
(GPC_M4_LPSR_M4_SLEEPING_MASK <<
GPC_M4_LPSR_M4_SLEEPING_SHIFT))
return 1;
return 0;
}
bool imx_gpc_usb_wakeup_enabled(void)
{
if (!(cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() ||
cpu_is_imx6ulz() || cpu_is_imx6sll()))
return false;
/*
* for SoC later than i.MX6SX, USB vbus wakeup
* only needs weak 2P5 on, stop_mode_config is
* NOT needed, so we check if is USB vbus wakeup
* is enabled(assume irq #74 and #75) to decide
* if to keep weak 2P5 on.
*/
if (gpc_wake_irqs[1] & GPC_USB_VBUS_WAKEUP_IRQ_MASK)
return true;
return false;
}
bool imx_gpc_enet_wakeup_enabled(void)
{
if (!cpu_is_imx6q())
return false;
if (gpc_wake_irqs[3] & GPC_ENET_WAKEUP_IRQ_MASK)
return true;
return false;
}
unsigned int imx_gpc_is_mf_mix_off(void)
{
return readl_relaxed(gpc_base + GPC_PGC_MF_PDN);
}
static void imx_gpc_mf_mix_off(void)
{
int i;
for (i = 0; i < IMR_NUM; i++)
if (((gpc_wake_irqs[i] | gpc_mf_request_on[i]) &
gpc_mf_irqs[i]) != 0)
return;
pr_info("Turn off M/F mix!\n");
/* turn off mega/fast mix */
writel_relaxed(0x1, gpc_base + GPC_PGC_MF_PDN);
}
void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw)
{
@ -53,9 +184,9 @@ void imx_gpc_set_l2_mem_power_in_lpm(bool power_off)
u32 val;
val = readl_relaxed(gpc_base + GPC_CNTR);
val &= ~(1 << GPC_CNTR_L2_PGE_SHIFT);
val &= ~(1 << GPC_CNTR_L2_PGE);
if (power_off)
val |= 1 << GPC_CNTR_L2_PGE_SHIFT;
val |= 1 << GPC_CNTR_L2_PGE;
writel_relaxed(val, gpc_base + GPC_CNTR);
}
@ -64,6 +195,11 @@ void imx_gpc_pre_suspend(bool arm_power_off)
void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
int i;
/* power down the mega-fast power domain */
if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() ||
cpu_is_imx6ulz() || cpu_is_imx6sll()) && arm_power_off)
imx_gpc_mf_mix_off();
/* Tell GPC to power off ARM core when suspend */
if (arm_power_off)
imx_gpc_set_arm_power_in_lpm(arm_power_off);
@ -81,6 +217,10 @@ void imx_gpc_post_resume(void)
/* Keep ARM core powered on for other low-power modes */
imx_gpc_set_arm_power_in_lpm(false);
/* Keep M/F mix powered on for other low-power modes */
if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() ||
cpu_is_imx6ulz() || cpu_is_imx6sll())
writel_relaxed(0x0, gpc_base + GPC_PGC_MF_PDN);
for (i = 0; i < IMR_NUM; i++)
writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4);
@ -89,11 +229,14 @@ void imx_gpc_post_resume(void)
static int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on)
{
unsigned int idx = d->hwirq / 32;
unsigned long flags;
u32 mask;
mask = 1 << d->hwirq % 32;
spin_lock_irqsave(&gpc_lock, flags);
gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask :
gpc_wake_irqs[idx] & ~mask;
spin_unlock_irqrestore(&gpc_lock, flags);
/*
* Do *not* call into the parent, as the GIC doesn't have any
@ -225,11 +368,78 @@ static const struct irq_domain_ops imx_gpc_domain_ops = {
.free = irq_domain_free_irqs_common,
};
int imx_gpc_mf_power_on(unsigned int irq, unsigned int on)
{
struct irq_desc *d = irq_to_desc(irq);
unsigned int idx = d->irq_data.hwirq / 32;
unsigned long flags;
u32 mask;
mask = 1 << (d->irq_data.hwirq % 32);
spin_lock_irqsave(&gpc_lock, flags);
gpc_mf_request_on[idx] = on ? gpc_mf_request_on[idx] | mask :
gpc_mf_request_on[idx] & ~mask;
spin_unlock_irqrestore(&gpc_lock, flags);
return 0;
}
int imx_gpc_mf_request_on(unsigned int irq, unsigned int on)
{
if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() ||
cpu_is_imx6ulz() || cpu_is_imx6sll())
return imx_gpc_mf_power_on(irq, on);
else if (cpu_is_imx7d())
return imx_gpcv2_mf_power_on(irq, on);
else
return 0;
}
EXPORT_SYMBOL_GPL(imx_gpc_mf_request_on);
void imx_gpc_switch_pupscr_clk(bool flag)
{
static u32 pupscr_sw2iso, pupscr_sw;
u32 ratio, pupscr = readl_relaxed(gpc_base + GPC_PGC_CPU_PUPSCR);
if (flag) {
/* save the init clock setting IPG/2048 for IPG@66Mhz */
pupscr_sw2iso = (pupscr >> GPC_PGC_CPU_SW2ISO_SHIFT) &
GPC_PGC_CPU_SW2ISO_MASK;
pupscr_sw = (pupscr >> GPC_PGC_CPU_SW_SHIFT) &
GPC_PGC_CPU_SW_MASK;
/*
* i.MX6UL TO1.0 ARM power up uses IPG/2048 as clock source,
* from TO1.1, PGC_CPU_PUPSCR bit [5] is re-defined to switch
* clock to IPG/32, enable this bit to speed up the ARM power
* up process in low power idle case(IPG@1.5Mhz). So the sw and
* sw2iso need to be adjusted as below:
* sw_new(sw2iso_new) = (2048 * 1.5 / 66 * 32) * sw(sw2iso)
*/
ratio = 3072 / (66 * 32);
pupscr &= ~(GPC_PGC_CPU_SW_MASK << GPC_PGC_CPU_SW_SHIFT |
GPC_PGC_CPU_SW2ISO_MASK << GPC_PGC_CPU_SW2ISO_SHIFT);
pupscr |= (ratio * pupscr_sw + 1) << GPC_PGC_CPU_SW_SHIFT |
1 << 5 | (ratio * pupscr_sw2iso + 1) <<
GPC_PGC_CPU_SW2ISO_SHIFT;
writel_relaxed(pupscr, gpc_base + GPC_PGC_CPU_PUPSCR);
} else {
/* restore back after exit from low power idle */
pupscr &= ~(GPC_PGC_CPU_SW_MASK << GPC_PGC_CPU_SW_SHIFT |
GPC_PGC_CPU_SW2ISO_MASK << GPC_PGC_CPU_SW2ISO_SHIFT);
pupscr |= pupscr_sw << GPC_PGC_CPU_SW_SHIFT |
pupscr_sw2iso << GPC_PGC_CPU_SW2ISO_SHIFT;
writel_relaxed(pupscr, gpc_base + GPC_PGC_CPU_PUPSCR);
}
}
static int __init imx_gpc_init(struct device_node *node,
struct device_node *parent)
{
struct irq_domain *parent_domain, *domain;
int i;
u32 val;
u32 cpu_pupscr_sw2iso, cpu_pupscr_sw;
u32 cpu_pdnscr_iso2sw, cpu_pdnscr_iso;
if (!parent) {
pr_err("%pOF: no parent, giving up\n", node);
@ -258,12 +468,70 @@ static int __init imx_gpc_init(struct device_node *node,
for (i = 0; i < IMR_NUM; i++)
writel_relaxed(~0, gpc_base + GPC_IMR1 + i * 4);
/* Read supported wakeup source in M/F domain */
if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() ||
cpu_is_imx6ulz() || cpu_is_imx6sll()) {
of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 0,
&gpc_mf_irqs[0]);
of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 1,
&gpc_mf_irqs[1]);
of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 2,
&gpc_mf_irqs[2]);
of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 3,
&gpc_mf_irqs[3]);
if (!(gpc_mf_irqs[0] | gpc_mf_irqs[1] |
gpc_mf_irqs[2] | gpc_mf_irqs[3]))
pr_info("No wakeup source in Mega/Fast domain found!\n");
}
/* clear the L2_PGE bit on i.MX6SLL */
if (cpu_is_imx6sll()) {
val = readl_relaxed(gpc_base + GPC_CNTR);
val &= ~(1 << GPC_CNTR_L2_PGE);
writel_relaxed(val, gpc_base + GPC_CNTR);
}
/*
* Clear the OF_POPULATED flag set in of_irq_init so that
* later the GPC power domain driver will not be skipped.
*/
of_node_clear_flag(node, OF_POPULATED);
/*
* If there are CPU isolation timing settings in dts,
* update them according to dts, otherwise, keep them
* with default value in registers.
*/
cpu_pupscr_sw2iso = cpu_pupscr_sw =
cpu_pdnscr_iso2sw = cpu_pdnscr_iso = 0;
/* Read CPU isolation setting for GPC */
of_property_read_u32(node, "fsl,cpu_pupscr_sw2iso", &cpu_pupscr_sw2iso);
of_property_read_u32(node, "fsl,cpu_pupscr_sw", &cpu_pupscr_sw);
of_property_read_u32(node, "fsl,cpu_pdnscr_iso2sw", &cpu_pdnscr_iso2sw);
of_property_read_u32(node, "fsl,cpu_pdnscr_iso", &cpu_pdnscr_iso);
/* Return if no property found in dtb */
if ((cpu_pupscr_sw2iso | cpu_pupscr_sw
| cpu_pdnscr_iso2sw | cpu_pdnscr_iso) == 0)
return 0;
/* Update CPU PUPSCR timing if it is defined in dts */
val = readl_relaxed(gpc_base + GPC_PGC_CPU_PUPSCR);
val &= ~(GPC_PGC_CPU_SW2ISO_MASK << GPC_PGC_CPU_SW2ISO_SHIFT);
val &= ~(GPC_PGC_CPU_SW_MASK << GPC_PGC_CPU_SW_SHIFT);
val |= cpu_pupscr_sw2iso << GPC_PGC_CPU_SW2ISO_SHIFT;
val |= cpu_pupscr_sw << GPC_PGC_CPU_SW_SHIFT;
writel_relaxed(val, gpc_base + GPC_PGC_CPU_PUPSCR);
/* Update CPU PDNSCR timing if it is defined in dts */
val = readl_relaxed(gpc_base + GPC_PGC_CPU_PDNSCR);
val &= ~(GPC_PGC_CPU_SW2ISO_MASK << GPC_PGC_CPU_SW2ISO_SHIFT);
val &= ~(GPC_PGC_CPU_SW_MASK << GPC_PGC_CPU_SW_SHIFT);
val |= cpu_pdnscr_iso2sw << GPC_PGC_CPU_SW2ISO_SHIFT;
val |= cpu_pdnscr_iso << GPC_PGC_CPU_SW_SHIFT;
writel_relaxed(val, gpc_base + GPC_PGC_CPU_PDNSCR);
return 0;
}
IRQCHIP_DECLARE(imx_gpc, "fsl,imx6q-gpc", imx_gpc_init);

View File

@ -0,0 +1,851 @@
/*
* Copyright (C) 2015-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 <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/regulator/consumer.h>
#include "common.h"
#include "hardware.h"
#define IMR_NUM 4
#define GPC_MAX_IRQS (IMR_NUM * 32)
#define GPC_LPCR_A7_BSC 0x0
#define GPC_LPCR_A7_AD 0x4
#define GPC_LPCR_M4 0x8
#define GPC_SLPCR 0x14
#define GPC_MLPCR 0x20
#define GPC_PGC_ACK_SEL_A7 0x24
#define GPC_MISC 0x2c
#define GPC_IMR1_CORE0 0x30
#define GPC_IMR1_CORE1 0x40
#define GPC_IMR1_M4 0x50
#define GPC_SLOT0_CFG 0xb0
#define GPC_PGC_CPU_MAPPING 0xec
#define GPC_CPU_PGC_SW_PUP_REQ 0xf0
#define GPC_PU_PGC_SW_PUP_REQ 0xf8
#define GPC_CPU_PGC_SW_PDN_REQ 0xfc
#define GPC_PU_PGC_SW_PDN_REQ 0x104
#define GPC_GTOR 0x124
#define GPC_PGC_C0 0x800
#define GPC_PGC_C0_PUPSCR 0x804
#define GPC_PGC_SCU_TIMING 0x890
#define GPC_PGC_C1 0x840
#define GPC_PGC_C1_PUPSCR 0x844
#define GPC_PGC_SCU 0x880
#define GPC_PGC_FM 0xa00
#define BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP 0x70000000
#define BM_LPCR_A7_BSC_CPU_CLK_ON_LPM 0x4000
#define BM_LPCR_A7_BSC_LPM1 0xc
#define BM_LPCR_A7_BSC_LPM0 0x3
#define BP_LPCR_A7_BSC_LPM1 2
#define BP_LPCR_A7_BSC_LPM0 0
#define BM_LPCR_M4_MASK_DSM_TRIGGER 0x80000000
#define BM_SLPCR_EN_DSM 0x80000000
#define BM_SLPCR_RBC_EN 0x40000000
#define BM_SLPCR_REG_BYPASS_COUNT 0x3f000000
#define BM_SLPCR_VSTBY 0x4
#define BM_SLPCR_SBYOS 0x2
#define BM_SLPCR_BYPASS_PMIC_READY 0x1
#define BM_SLPCR_EN_A7_FASTWUP_WAIT_MODE 0x10000
#define BM_LPCR_A7_AD_L2PGE 0x10000
#define BM_LPCR_A7_AD_EN_C1_PUP 0x800
#define BM_LPCR_A7_AD_EN_C1_IRQ_PUP 0x400
#define BM_LPCR_A7_AD_EN_C0_PUP 0x200
#define BM_LPCR_A7_AD_EN_C0_IRQ_PUP 0x100
#define BM_LPCR_A7_AD_EN_PLAT_PDN 0x10
#define BM_LPCR_A7_AD_EN_C1_PDN 0x8
#define BM_LPCR_A7_AD_EN_C1_WFI_PDN 0x4
#define BM_LPCR_A7_AD_EN_C0_PDN 0x2
#define BM_LPCR_A7_AD_EN_C0_WFI_PDN 0x1
#define BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7 0x2
#define BM_GPC_PGC_PCG 0x1
#define BM_GPC_PGC_CORE_PUPSCR 0x7fff80
#define BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK 0x80000000
#define BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK 0x8000
#define BM_GPC_MLPCR_MEMLP_CTL_DIS 0x1
#define BP_LPCR_A7_BSC_IRQ_SRC 28
#define MAX_SLOT_NUMBER 10
#define A7_LPM_WAIT 0x5
#define A7_LPM_STOP 0xa
enum imx_gpc_slot {
CORE0_A7,
CORE1_A7,
SCU_A7,
FAST_MEGA_MIX,
MIPI_PHY,
PCIE_PHY,
USB_OTG1_PHY,
USB_OTG2_PHY,
USB_HSIC_PHY,
CORE0_M4,
};
static void __iomem *gpc_base;
static u32 gpcv2_wake_irqs[IMR_NUM];
static u32 gpcv2_saved_imrs[IMR_NUM];
static u32 gpcv2_saved_imrs_m4[IMR_NUM];
static u32 gpcv2_mf_irqs[IMR_NUM];
static u32 gpcv2_mf_request_on[IMR_NUM];
static DEFINE_SPINLOCK(gpcv2_lock);
void imx_gpcv2_add_m4_wake_up_irq(u32 hwirq, bool enable)
{
unsigned int idx = hwirq / 32;
unsigned long flags;
u32 mask;
/* Sanity check for SPI irq */
if (hwirq < 32)
return;
mask = 1 << hwirq % 32;
spin_lock_irqsave(&gpcv2_lock, flags);
gpcv2_wake_irqs[idx] = enable ? gpcv2_wake_irqs[idx] | mask :
gpcv2_wake_irqs[idx] & ~mask;
spin_unlock_irqrestore(&gpcv2_lock, flags);
}
static int imx_gpcv2_irq_set_wake(struct irq_data *d, unsigned int on)
{
unsigned int idx = d->hwirq / 32;
unsigned long flags;
u32 mask;
BUG_ON(idx >= IMR_NUM);
mask = 1 << d->hwirq % 32;
spin_lock_irqsave(&gpcv2_lock, flags);
gpcv2_wake_irqs[idx] = on ? gpcv2_wake_irqs[idx] | mask :
gpcv2_wake_irqs[idx] & ~mask;
spin_unlock_irqrestore(&gpcv2_lock, flags);
return 0;
}
void imx_gpcv2_mask_all(void)
{
void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0;
int i;
for (i = 0; i < IMR_NUM; i++) {
gpcv2_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
writel_relaxed(~0, reg_imr1 + i * 4);
}
}
void imx_gpcv2_restore_all(void)
{
void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0;
int i;
for (i = 0; i < IMR_NUM; i++)
writel_relaxed(gpcv2_saved_imrs[i], reg_imr1 + i * 4);
}
void imx_gpcv2_hwirq_unmask(unsigned int hwirq)
{
void __iomem *reg;
u32 val;
reg = gpc_base + GPC_IMR1_CORE0 + (hwirq / 32) * 4;
val = readl_relaxed(reg);
val &= ~(1 << hwirq % 32);
writel_relaxed(val, reg);
}
void imx_gpcv2_hwirq_mask(unsigned int hwirq)
{
void __iomem *reg;
u32 val;
reg = gpc_base + GPC_IMR1_CORE0 + (hwirq / 32) * 4;
val = readl_relaxed(reg);
val |= 1 << (hwirq % 32);
writel_relaxed(val, reg);
}
static void imx_gpcv2_irq_unmask(struct irq_data *d)
{
imx_gpcv2_hwirq_unmask(d->hwirq);
irq_chip_unmask_parent(d);
}
static void imx_gpcv2_irq_mask(struct irq_data *d)
{
imx_gpcv2_hwirq_mask(d->hwirq);
irq_chip_mask_parent(d);
}
void imx_gpcv2_set_slot_ack(u32 index, enum imx_gpc_slot m_core,
bool mode, bool ack)
{
u32 val;
if (index >= MAX_SLOT_NUMBER)
pr_err("Invalid slot index!\n");
/* set slot */
writel_relaxed(readl_relaxed(gpc_base + GPC_SLOT0_CFG + index * 4) |
((mode + 1) << (m_core * 2)),
gpc_base + GPC_SLOT0_CFG + index * 4);
if (ack) {
/* set ack */
val = readl_relaxed(gpc_base + GPC_PGC_ACK_SEL_A7);
/* clear dummy ack */
val &= ~(1 << (15 + (mode ? 16 : 0)));
val |= 1 << (m_core + (mode ? 16 : 0));
writel_relaxed(val, gpc_base + GPC_PGC_ACK_SEL_A7);
}
}
void imx_gpcv2_set_lpm_mode(enum mxc_cpu_pwr_mode mode)
{
unsigned long flags;
u32 val1, val2;
spin_lock_irqsave(&gpcv2_lock, flags);
val1 = readl_relaxed(gpc_base + GPC_LPCR_A7_BSC);
val2 = readl_relaxed(gpc_base + GPC_SLPCR);
/* all cores' LPM settings must be same */
val1 &= ~(BM_LPCR_A7_BSC_LPM0 | BM_LPCR_A7_BSC_LPM1);
val1 |= BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
val2 &= ~(BM_SLPCR_EN_DSM | BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN |
BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY);
/*
* GPC: When improper low-power sequence is used,
* the SoC enters low power mode before the ARM core executes WFI.
*
* Software workaround:
* 1) Software should trigger IRQ #32 (IOMUX) to be always pending
* by setting IOMUX_GPR1_IRQ.
* 2) Software should then unmask IRQ #32 in GPC before setting GPC
* Low-Power mode.
* 3) Software should mask IRQ #32 right after GPC Low-Power mode
* is set.
*/
switch (mode) {
case WAIT_CLOCKED:
imx_gpcv2_hwirq_unmask(0);
break;
case WAIT_UNCLOCKED:
val1 |= A7_LPM_WAIT << BP_LPCR_A7_BSC_LPM0;
val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
imx_gpcv2_hwirq_mask(0);
break;
case STOP_POWER_ON:
val1 |= A7_LPM_STOP << BP_LPCR_A7_BSC_LPM0;
val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
val2 |= BM_SLPCR_EN_DSM;
val2 |= BM_SLPCR_RBC_EN;
val2 |= BM_SLPCR_BYPASS_PMIC_READY;
imx_gpcv2_hwirq_mask(0);
break;
case STOP_POWER_OFF:
val1 |= A7_LPM_STOP << BP_LPCR_A7_BSC_LPM0;
val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM;
val2 |= BM_SLPCR_EN_DSM;
val2 |= BM_SLPCR_RBC_EN;
val2 |= BM_SLPCR_SBYOS;
val2 |= BM_SLPCR_VSTBY;
val2 |= BM_SLPCR_BYPASS_PMIC_READY;
imx_gpcv2_hwirq_mask(0);
break;
default:
return;
}
writel_relaxed(val1, gpc_base + GPC_LPCR_A7_BSC);
writel_relaxed(val2, gpc_base + GPC_SLPCR);
spin_unlock_irqrestore(&gpcv2_lock, flags);
}
void imx_gpcv2_set_plat_power_gate_by_lpm(bool pdn)
{
u32 val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD);
val &= ~(BM_LPCR_A7_AD_EN_PLAT_PDN | BM_LPCR_A7_AD_L2PGE);
if (pdn)
val |= BM_LPCR_A7_AD_EN_PLAT_PDN | BM_LPCR_A7_AD_L2PGE;
writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD);
}
void imx_gpcv2_set_m_core_pgc(bool enable, u32 offset)
{
u32 val = readl_relaxed(gpc_base + offset) & (~BM_GPC_PGC_PCG);
if (enable)
val |= BM_GPC_PGC_PCG;
writel_relaxed(val, gpc_base + offset);
}
void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn)
{
u32 val = readl_relaxed(gpc_base + (pdn ?
GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ));
imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C1);
val |= BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7;
writel_relaxed(val, gpc_base + (pdn ?
GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ));
while ((readl_relaxed(gpc_base + (pdn ?
GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ)) &
BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7) != 0)
;
imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C1);
}
void imx_gpcv2_set_cpu_power_gate_by_wfi(u32 cpu, bool pdn)
{
unsigned long flags;
u32 val;
spin_lock_irqsave(&gpcv2_lock, flags);
val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD);
if (cpu == 0) {
if (pdn) {
imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C0);
val |= BM_LPCR_A7_AD_EN_C0_WFI_PDN |
BM_LPCR_A7_AD_EN_C0_IRQ_PUP;
} else {
imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C0);
val &= ~(BM_LPCR_A7_AD_EN_C0_WFI_PDN |
BM_LPCR_A7_AD_EN_C0_IRQ_PUP);
}
}
if (cpu == 1) {
if (pdn) {
imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C1);
val |= BM_LPCR_A7_AD_EN_C1_WFI_PDN |
BM_LPCR_A7_AD_EN_C1_IRQ_PUP;
} else {
imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C1);
val &= ~(BM_LPCR_A7_AD_EN_C1_WFI_PDN |
BM_LPCR_A7_AD_EN_C1_IRQ_PUP);
}
}
writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD);
spin_unlock_irqrestore(&gpcv2_lock, flags);
}
void imx_gpcv2_set_cpu_power_gate_by_lpm(u32 cpu, bool pdn)
{
unsigned long flags;
u32 val;
spin_lock_irqsave(&gpcv2_lock, flags);
val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD);
if (cpu == 0) {
if (pdn)
val |= BM_LPCR_A7_AD_EN_C0_PDN |
BM_LPCR_A7_AD_EN_C0_PUP;
else
val &= ~(BM_LPCR_A7_AD_EN_C0_PDN |
BM_LPCR_A7_AD_EN_C0_PUP);
}
if (cpu == 1) {
if (pdn)
val |= BM_LPCR_A7_AD_EN_C1_PDN |
BM_LPCR_A7_AD_EN_C1_PUP;
else
val &= ~(BM_LPCR_A7_AD_EN_C1_PDN |
BM_LPCR_A7_AD_EN_C1_PUP);
}
writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD);
spin_unlock_irqrestore(&gpcv2_lock, flags);
}
void imx_gpcv2_set_cpu_power_gate_in_idle(bool pdn)
{
unsigned long flags;
u32 cpu;
for_each_possible_cpu(cpu)
imx_gpcv2_set_cpu_power_gate_by_lpm(cpu, pdn);
spin_lock_irqsave(&gpcv2_lock, flags);
imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_C0);
if (num_online_cpus() > 1)
imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_C1);
imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_SCU);
imx_gpcv2_set_plat_power_gate_by_lpm(pdn);
if (pdn) {
imx_gpcv2_set_slot_ack(0, CORE0_A7, false, false);
if (num_online_cpus() > 1)
imx_gpcv2_set_slot_ack(2, CORE1_A7, false, false);
imx_gpcv2_set_slot_ack(3, SCU_A7, false, true);
imx_gpcv2_set_slot_ack(6, SCU_A7, true, false);
if (num_online_cpus() > 1)
imx_gpcv2_set_slot_ack(6, CORE1_A7, true, false);
imx_gpcv2_set_slot_ack(6, CORE0_A7, true, true);
} else {
writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 0 * 0x4);
writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 2 * 0x4);
writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 3 * 0x4);
writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 6 * 0x4);
writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 7 * 0x4);
writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 8 * 0x4);
writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK |
BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK,
gpc_base + GPC_PGC_ACK_SEL_A7);
imx_gpcv2_enable_rbc(false);
}
spin_unlock_irqrestore(&gpcv2_lock, flags);
}
void imx_gpcv2_set_mix_phy_gate_by_lpm(u32 pdn_index, u32 pup_index)
{
/* set power down slot */
writel_relaxed(1 << (FAST_MEGA_MIX * 2),
gpc_base + GPC_SLOT0_CFG + pdn_index * 4);
/* set power up slot */
writel_relaxed(1 << (FAST_MEGA_MIX * 2 + 1),
gpc_base + GPC_SLOT0_CFG + pup_index * 4);
}
unsigned int imx_gpcv2_is_mf_mix_off(void)
{
return readl_relaxed(gpc_base + GPC_PGC_FM);
}
static void imx_gpcv2_mf_mix_off(void)
{
int i;
for (i = 0; i < IMR_NUM; i++)
if (((gpcv2_wake_irqs[i] | gpcv2_mf_request_on[i]) &
gpcv2_mf_irqs[i]) != 0)
return;
pr_info("Turn off Mega/Fast mix in DSM\n");
imx_gpcv2_set_slot_ack(1, FAST_MEGA_MIX, false, false);
imx_gpcv2_set_slot_ack(5, FAST_MEGA_MIX, true, false);
imx_gpcv2_set_m_core_pgc(true, GPC_PGC_FM);
}
int imx_gpcv2_mf_power_on(unsigned int irq, unsigned int on)
{
struct irq_desc *desc = irq_to_desc(irq);
unsigned long hwirq = desc->irq_data.hwirq;
unsigned int idx = hwirq / 32;
unsigned long flags;
u32 mask = 1 << (hwirq % 32);
BUG_ON(idx >= IMR_NUM);
spin_lock_irqsave(&gpcv2_lock, flags);
gpcv2_mf_request_on[idx] = on ? gpcv2_mf_request_on[idx] | mask :
gpcv2_mf_request_on[idx] & ~mask;
spin_unlock_irqrestore(&gpcv2_lock, flags);
return 0;
}
void imx_gpcv2_enable_rbc(bool enable)
{
u32 val;
/*
* need to mask all interrupts in GPC before
* operating RBC configurations
*/
imx_gpcv2_mask_all();
/* configure RBC enable bit */
val = readl_relaxed(gpc_base + GPC_SLPCR);
val &= ~BM_SLPCR_RBC_EN;
val |= enable ? BM_SLPCR_RBC_EN : 0;
writel_relaxed(val, gpc_base + GPC_SLPCR);
/* configure RBC count */
val = readl_relaxed(gpc_base + GPC_SLPCR);
val &= ~BM_SLPCR_REG_BYPASS_COUNT;
val |= enable ? BM_SLPCR_REG_BYPASS_COUNT : 0;
writel(val, gpc_base + GPC_SLPCR);
/*
* need to delay at least 2 cycles of CKIL(32K)
* due to hardware design requirement, which is
* ~61us, here we use 65us for safe
*/
udelay(65);
/* restore GPC interrupt mask settings */
imx_gpcv2_restore_all();
}
void imx_gpcv2_pre_suspend(bool arm_power_off)
{
void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0;
int i;
if (arm_power_off) {
imx_gpcv2_set_lpm_mode(STOP_POWER_OFF);
/* enable core0 power down/up with low power mode */
imx_gpcv2_set_cpu_power_gate_by_lpm(0, true);
/* enable plat power down with low power mode */
imx_gpcv2_set_plat_power_gate_by_lpm(true);
/*
* To avoid confuse, we use slot 0~4 for power down,
* slot 5~9 for power up.
*
* Power down slot sequence:
* Slot0 -> CORE0
* Slot1 -> Mega/Fast MIX
* Slot2 -> SCU
*
* Power up slot sequence:
* Slot5 -> Mega/Fast MIX
* Slot6 -> SCU
* Slot7 -> CORE0
*/
imx_gpcv2_set_slot_ack(0, CORE0_A7, false, false);
imx_gpcv2_set_slot_ack(2, SCU_A7, false, true);
if ((!imx_src_is_m4_enabled()) ||
(imx_src_is_m4_enabled() && imx_mu_is_m4_in_stop()))
imx_gpcv2_mf_mix_off();;
imx_gpcv2_set_slot_ack(6, SCU_A7, true, false);
imx_gpcv2_set_slot_ack(6, CORE0_A7, true, true);
/* enable core0, scu */
imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C0);
imx_gpcv2_set_m_core_pgc(true, GPC_PGC_SCU);
} else {
imx_gpcv2_set_lpm_mode(STOP_POWER_ON);
}
for (i = 0; i < IMR_NUM; i++) {
gpcv2_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
writel_relaxed(~gpcv2_wake_irqs[i], reg_imr1 + i * 4);
}
}
void imx_gpcv2_enable_wakeup_for_m4(void)
{
void __iomem *reg_imr2 = gpc_base + GPC_IMR1_M4;
u32 i;
for (i = 0; i < IMR_NUM; i++) {
gpcv2_saved_imrs_m4[i] = readl_relaxed(reg_imr2 + i * 4);
writel_relaxed(~gpcv2_wake_irqs[i], reg_imr2 + i * 4);
}
}
void imx_gpcv2_disable_wakeup_for_m4(void)
{
void __iomem *reg_imr2 = gpc_base + GPC_IMR1_M4;
u32 i;
for (i = 0; i < IMR_NUM; i++)
writel_relaxed(gpcv2_saved_imrs_m4[i], reg_imr2 + i * 4);
}
void imx_gpcv2_post_resume(void)
{
void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0;
int i, val;
/* only external IRQs to wake up LPM and core 0/1 */
val = readl_relaxed(gpc_base + GPC_LPCR_A7_BSC);
val |= BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP;
writel_relaxed(val, gpc_base + GPC_LPCR_A7_BSC);
/* mask m4 dsm trigger if M4 NOT enabled */
if (!imx_src_is_m4_enabled())
writel_relaxed(readl_relaxed(gpc_base + GPC_LPCR_M4) |
BM_LPCR_M4_MASK_DSM_TRIGGER, gpc_base + GPC_LPCR_M4);
/* set mega/fast mix in A7 domain */
writel_relaxed(0x1, gpc_base + GPC_PGC_CPU_MAPPING);
/* set SCU timing */
writel_relaxed((0x59 << 10) | 0x5B | (0x2 << 20),
gpc_base + GPC_PGC_SCU_TIMING);
/* set C0/C1 power up timming per design requirement */
val = readl_relaxed(gpc_base + GPC_PGC_C0_PUPSCR);
val &= ~BM_GPC_PGC_CORE_PUPSCR;
val |= (0x1A << 7);
writel_relaxed(val, gpc_base + GPC_PGC_C0_PUPSCR);
val = readl_relaxed(gpc_base + GPC_PGC_C1_PUPSCR);
val &= ~BM_GPC_PGC_CORE_PUPSCR;
val |= (0x1A << 7);
writel_relaxed(val, gpc_base + GPC_PGC_C1_PUPSCR);
val = readl_relaxed(gpc_base + GPC_SLPCR);
val &= ~(BM_SLPCR_EN_DSM);
if (!imx_src_is_m4_enabled())
val &= ~(BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN |
BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY);
val |= BM_SLPCR_EN_A7_FASTWUP_WAIT_MODE;
writel_relaxed(val, gpc_base + GPC_SLPCR);
if (imx_get_soc_revision() == IMX_CHIP_REVISION_1_0) {
/* disable memory low power mode */
val = readl_relaxed(gpc_base + GPC_MLPCR);
val |= BM_GPC_MLPCR_MEMLP_CTL_DIS;
writel_relaxed(val, gpc_base + GPC_MLPCR);
}
for (i = 0; i < IMR_NUM; i++)
writel_relaxed(gpcv2_saved_imrs[i], reg_imr1 + i * 4);
imx_gpcv2_set_lpm_mode(WAIT_CLOCKED);
imx_gpcv2_set_cpu_power_gate_by_lpm(0, false);
imx_gpcv2_set_plat_power_gate_by_lpm(false);
imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C0);
imx_gpcv2_set_m_core_pgc(false, GPC_PGC_SCU);
imx_gpcv2_set_m_core_pgc(false, GPC_PGC_FM);
for (i = 0; i < MAX_SLOT_NUMBER; i++){
if (i == 1 || i == 5) /* skip slts m4 uses */
continue;
writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + i * 0x4);
}
writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK |
BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK,
gpc_base + GPC_PGC_ACK_SEL_A7);
/* disable RBC */
imx_gpcv2_enable_rbc(false);
}
static struct irq_chip imx_gpcv2_chip = {
.name = "GPCV2",
.irq_eoi = irq_chip_eoi_parent,
.irq_mask = imx_gpcv2_irq_mask,
.irq_unmask = imx_gpcv2_irq_unmask,
.irq_retrigger = irq_chip_retrigger_hierarchy,
.irq_set_wake = imx_gpcv2_irq_set_wake,
#ifdef CONFIG_SMP
.irq_set_affinity = irq_chip_set_affinity_parent,
#endif
};
static int imx_gpcv2_domain_xlate(struct irq_domain *domain,
struct device_node *controller,
const u32 *intspec,
unsigned int intsize,
unsigned long *out_hwirq,
unsigned int *out_type)
{
if (irq_domain_get_of_node(domain) != controller)
return -EINVAL; /* Shouldn't happen, really... */
if (intsize != 3)
return -EINVAL; /* Not GIC compliant */
if (intspec[0] != 0)
return -EINVAL; /* No PPI should point to this domain */
*out_hwirq = intspec[1];
*out_type = intspec[2];
return 0;
}
static int imx_gpcv2_domain_alloc(struct irq_domain *domain,
unsigned int irq,
unsigned int nr_irqs, void *data)
{
struct irq_fwspec *fwspec = data;
struct irq_fwspec parent_fwspec;
irq_hw_number_t hwirq;
int i;
if (fwspec->param_count != 3)
return -EINVAL; /* Not GIC compliant */
if (fwspec->param[0] != 0)
return -EINVAL; /* No PPI should point to this domain */
hwirq = fwspec->param[1];
if (hwirq >= GPC_MAX_IRQS)
return -EINVAL; /* Can't deal with this */
for (i = 0; i < nr_irqs; i++)
irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
&imx_gpcv2_chip, NULL);
parent_fwspec.fwnode = domain->parent->fwnode;
parent_fwspec.param_count = 3;
parent_fwspec.param[0] = 0;
parent_fwspec.param[1] = hwirq;
parent_fwspec.param[2] = fwspec->param[2];
return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs,
&parent_fwspec);
}
static struct irq_domain_ops imx_gpcv2_domain_ops = {
.xlate = imx_gpcv2_domain_xlate,
.alloc = imx_gpcv2_domain_alloc,
.free = irq_domain_free_irqs_common,
};
static int __init imx_gpcv2_init(struct device_node *node,
struct device_node *parent)
{
struct irq_domain *parent_domain, *domain;
int i, val;
if (!parent) {
pr_err("%s: no parent, giving up\n", node->full_name);
return -ENODEV;
}
parent_domain = irq_find_host(parent);
if (!parent_domain) {
pr_err("%s: unable to obtain parent domain\n", node->full_name);
return -ENXIO;
}
gpc_base = of_iomap(node, 0);
if (WARN_ON(!gpc_base))
return -ENOMEM;
domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS,
node, &imx_gpcv2_domain_ops,
NULL);
if (!domain) {
iounmap(gpc_base);
return -ENOMEM;
}
/* Initially mask all interrupts */
for (i = 0; i < IMR_NUM; i++) {
writel_relaxed(~0, gpc_base + GPC_IMR1_CORE0 + i * 4);
writel_relaxed(~0, gpc_base + GPC_IMR1_CORE1 + i * 4);
}
/*
* Due to hardware design requirement, need to make sure GPR
* interrupt(#32) is unmasked during RUN mode to avoid entering
* DSM by mistake.
*/
writel_relaxed(~0x1, gpc_base + GPC_IMR1_CORE0);
/* Read supported wakeup source in M/F domain */
if (cpu_is_imx7d()) {
of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 0,
&gpcv2_mf_irqs[0]);
of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 1,
&gpcv2_mf_irqs[1]);
of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 2,
&gpcv2_mf_irqs[2]);
of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 3,
&gpcv2_mf_irqs[3]);
if (!(gpcv2_mf_irqs[0] | gpcv2_mf_irqs[1] |
gpcv2_mf_irqs[2] | gpcv2_mf_irqs[3]))
pr_info("No wakeup source in Mega/Fast domain found!\n");
}
/* only external IRQs to wake up LPM and core 0/1 */
val = readl_relaxed(gpc_base + GPC_LPCR_A7_BSC);
val |= BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP;
writel_relaxed(val, gpc_base + GPC_LPCR_A7_BSC);
/* mask m4 dsm trigger if M4 NOT enabled */
if (!imx_src_is_m4_enabled())
writel_relaxed(readl_relaxed(gpc_base + GPC_LPCR_M4) |
BM_LPCR_M4_MASK_DSM_TRIGGER, gpc_base + GPC_LPCR_M4);
/* set mega/fast mix in A7 domain */
writel_relaxed(0x1, gpc_base + GPC_PGC_CPU_MAPPING);
/* set SCU timing */
writel_relaxed((0x59 << 10) | 0x5B | (0x2 << 20),
gpc_base + GPC_PGC_SCU_TIMING);
/* set C0/C1 power up timming per design requirement */
val = readl_relaxed(gpc_base + GPC_PGC_C0_PUPSCR);
val &= ~BM_GPC_PGC_CORE_PUPSCR;
val |= (0x1A << 7);
writel_relaxed(val, gpc_base + GPC_PGC_C0_PUPSCR);
val = readl_relaxed(gpc_base + GPC_PGC_C1_PUPSCR);
val &= ~BM_GPC_PGC_CORE_PUPSCR;
val |= (0x1A << 7);
writel_relaxed(val, gpc_base + GPC_PGC_C1_PUPSCR);
writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK |
BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK,
gpc_base + GPC_PGC_ACK_SEL_A7);
val = readl_relaxed(gpc_base + GPC_SLPCR);
val &= ~(BM_SLPCR_EN_DSM);
if (!imx_src_is_m4_enabled())
val &= ~(BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN |
BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY);
val |= BM_SLPCR_EN_A7_FASTWUP_WAIT_MODE;
writel_relaxed(val, gpc_base + GPC_SLPCR);
if (imx_get_soc_revision() == IMX_CHIP_REVISION_1_0) {
/* disable memory low power mode */
val = readl_relaxed(gpc_base + GPC_MLPCR);
val |= BM_GPC_MLPCR_MEMLP_CTL_DIS;
writel_relaxed(val, gpc_base + GPC_MLPCR);
}
/* disable RBC */
imx_gpcv2_enable_rbc(false);
/*
* Clear the OF_POPULATED flag set in of_irq_init so that
* later the GPC power domain driver will not be skipped.
*/
of_node_clear_flag(node, OF_POPULATED);
return 0;
}
/*
* We cannot use the IRQCHIP_DECLARE macro that lives in
* drivers/irqchip, so we're forced to roll our own. Not very nice.
*/
OF_DECLARE_2(irqchip, imx_gpcv2, "fsl,imx7d-gpc", imx_gpcv2_init);
void __init imx_gpcv2_check_dt(void)
{
struct device_node *np;
np = of_find_compatible_node(NULL, NULL, "fsl,imx7d-gpc");
if (WARN_ON(!np))
return;
if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) {
pr_warn("Outdated DT detected, suspend/resume will NOT work\n");
/* map GPC, so that at least CPUidle and WARs keep working */
gpc_base = of_iomap(np, 0);
}
}

View File

@ -81,13 +81,16 @@
* CCM 0x020c4000+0x004000 -> 0xf42c4000+0x004000
* ANATOP 0x020c8000+0x004000 -> 0xf42c8000+0x004000
* UART4 0x021f0000+0x004000 -> 0xf42f0000+0x004000
* mx7d:
* CCM 0x30380000+0x010000 -> 0xf5380000+0x010000
* ANATOP 0x30360000+0x010000 -> 0xf5360000+0x010000
* UART1 0x30860000+0x010000 -> 0xf5860000+0x010000
*/
#define IMX_IO_P2V(x) ( \
(((x) & 0x80000000) >> 7) | \
(0xf4000000 + \
(((x) & 0x50000000) >> 6) + \
(((x) & 0x0b000000) >> 4) + \
(((x) & 0x000fffff))))
(((x) & 0x50000000) >> 4) + \
(((x) & 0x0a000000) >> 4) + \
(((x) & 0x00ffffff))))
#define IMX_IO_ADDRESS(x) IOMEM(IMX_IO_P2V(x))
@ -99,6 +102,9 @@
#include "mx2x.h"
#include "mx21.h"
#include "mx27.h"
#include "mx6.h"
#include "mx7.h"
#include "mx7ulp.h"
#define imx_map_entry(soc, name, _type) { \
.virtual = soc ## _IO_P2V(soc ## _ ## name ## _BASE_ADDR), \

View File

@ -21,6 +21,17 @@ diag_reg_offset:
ENTRY(v7_secondary_startup)
ARM_BE8(setend be) @ go BE8 if entered LE
mrc p15, 0, r0, c0, c0, 0
ldr r1, =0xf00
orr r1, r1, #0xff
mov r0, r0, lsr #4
and r0, r0, r1
/* 0xc07 is cortex A7's ID */
ldr r1, =0xc00
orr r1, r1, #0x7
cmp r0, r1
beq secondary_startup
set_diag_reg
b secondary_startup
ENDPROC(v7_secondary_startup)

View File

@ -0,0 +1,776 @@
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the license, or
* (at your option) any later version.
*
* This program is distributed in teh hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#define PM_INFO_PM_INFO_SIZE_OFFSET 0x0
#define PM_INFO_TTBR_OFFSET 0x4
#define PM_INFO_MMDC_V_OFFSET 0x8
#define PM_INFO_IOMUXC_V_OFFSET 0xc
#define PM_INFO_CCM_V_OFFSET 0x10
#define PM_INFO_L2_V_OFFSET 0x14
#define PM_INFO_ANATOP_V_OFFSET 0x18
#define PM_INFO_IO_NUM_OFFSET 0x1c
#define PM_INFO_IO_VAL_OFFSET 0x20
#define MX6Q_MMDC_MAPSR 0x404
#define MX6Q_MMDC_MPDGCTRL0 0x83c
.global mx6sl_lpm_wfi_start
.global mx6sl_lpm_wfi_end
.macro pll_do_wait_lock
1:
ldr r7, [r10, r8]
ands r7, #0x80000000
beq 1b
.endm
.macro ccm_do_wait
2:
ldr r7, [r10, #0x48]
cmp r7, #0x0
bne 2b
.endm
.macro ccm_enter_idle
ldr r10, [r0, #PM_INFO_CCM_V_OFFSET]
/*
* if in audio_bus_freq_mode, skip to
* audio_mode low power setting.
*/
cmp r1, #0x1
beq audio_mode
/*
* Now set DDR rate to 1MHz.
* DDR is from bypassed PLL2 on periph2_clk2 path.
* Set the periph2_clk2_podf to divide by 8.
*/
ldr r6, [r10, #0x14]
orr r6, r6, #0x07
str r6, [r10, #0x14]
/* Now set MMDC PODF to divide by 3. */
ldr r6, [r10, #0x14]
bic r6, r6, #0x38
orr r6, r6, #0x10
str r6, [r10, #0x14]
ccm_do_wait
/* Set the AHB to 3MHz. AXI to 3MHz. */
ldr r6, [r10, #0x14]
/*r12 stores the origin AHB podf value */
mov r12, r6
orr r6, r6, #0x1c00
orr r6, r6, #0x70000
str r6, [r10, #0x14]
ccm_do_wait
/* Now set ARM to 24MHz.
* Move ARM to be sourced from step_clk
* after setting step_clk to 24MHz.
*/
ldr r6, [r10, #0x0c]
bic r6, r6, #0x100
str r6, [r10, #0xc]
/*Now pll1_sw_clk to step_clk */
ldr r6, [r10, #0x0c]
orr r6, r6, #0x4
str r6, [r10, #0x0c]
/* Bypass PLL1 and power it down */
ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET]
ldr r6, =(1 << 16)
orr r6, r6, #0x1000
str r6, [r10, #0x04]
/*
* Set the ARM PODF to divide by 8.
* IPG is at 1.5MHz here, we need ARM to
* run at the 12:5 ratio (WAIT mode issue).
*/
ldr r10, [r0, #PM_INFO_CCM_V_OFFSET]
ldr r11, [r10, #0x10]
ldr r6, =0x07
str r6, [r10, #0x10]
ccm_do_wait
b ccm_idle_done
audio_mode:
/*
* MMDC is sourced from pll2_200M.
* Set the mmdc_podf to div by 8
*/
ldr r10, [r0, #PM_INFO_CCM_V_OFFSET]
ldr r6, [r10, #0x14]
orr r6, r6, #0x38
str r6, [r10, #0x14]
ccm_do_wait
/*
* ARM is sourced from pll2_pfd2_400M here.
* switch ARM to bypassed PLL1
*/
ldr r10, [r0, #PM_INFO_CCM_V_OFFSET]
ldr r6, [r10, #0x0c]
bic r6, r6, #0x4
str r6, [r10, #0xc]
/*
* set the arm_podf to divide by 3
* as IPG is at 4MHz, we cannot run
* arm clk above 9.6MHz when system
* enter WAIT mode
*/
ldr r11, [r10, #0x10]
ldr r6, =0x2
str r6, [r10, #0x10]
ccm_do_wait
ccm_idle_done:
.endm
.macro ccm_exit_idle
/*
* If in audio_bus_freq_mode, skip to
* audio_mode ccm restore.
*/
cmp r1, #0x1
beq audio_ccm_restore
ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET]
/* Power up PLL1 and un-bypass it. */
ldr r6, =(1 << 12)
str r6, [r10, #0x08]
/* Wait for PLL1 to relock */
ldr r8, =0x0
pll_do_wait_lock
ldr r6, =(1 << 16)
str r6, [r10, #0x08]
ldr r10, [r0, #PM_INFO_CCM_V_OFFSET]
/* Set PLL1_sw_clk back to PLL1 */
ldr r6, [r10, #0x0c]
bic r6, r6, #0x4
str r6, [r10, #0x0c]
/* Restore AHB/AXI back */
str r12, [r10, #0x14]
ccm_do_wait
/* restore mmdc back to 24MHz*/
ldr r6, [r10, #0x14]
bic r6, r6, #0x3f
str r6, [r10, #0x14]
ccm_do_wait
b ccm_exit_done
audio_ccm_restore:
/* move arm clk back to pll2_pfd2_400M */
ldr r6, [r10, #0xc]
orr r6, r6, #0x4
str r6, [r10, #0xc]
/* restore mmdc podf */
ldr r10, [r0, #PM_INFO_CCM_V_OFFSET]
ldr r6, [r10, #0x14]
bic r6, r6, #0x38
orr r6, #0x8
str r6, [r10, #0x14]
ccm_do_wait
ccm_exit_done:
.endm
.macro check_pll_state
ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET]
/*
* Check whether any PLL is enabled, as only when
* there is no PLLs enabled, 2p5 can be off and
* only enable the weak one. PLL1 will be powered
* down late, so no need to check PLL1 state.
*/
/* sys PLL2 */
ldr r6, [r10, #0x30]
ands r6, r6, #(1 << 31)
bne 1f
/* usb PLL3 */
ldr r6, [r10, #0x10]
ands r6, r6, #(1 << 31)
bne 1f
/* audio PLL4 */
ldr r6, [r10, #0x70]
ands r6, r6, #(1 << 31)
bne 1f
/* video PLL5 */
ldr r6, [r10, #0xa0]
ands r6, r6, #(1 << 31)
bne 1f
/* enet PLL6 */
ldr r6, [r10, #0xe0]
ands r6, r6, #(1 << 31)
bne 1f
/* usb host PLL7 */
ldr r6, [r10, #0x20]
ands r6, r6, #(1 << 31)
bne 1f
ldr r4, =0x1
b check_done
1:
ldr r4, =0x0
check_done:
.endm
.macro anatop_enter_idle
ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET]
cmp r4, #0x0
beq anatop_enter_done
/* Disable 1p1 brown out. */
ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET]
ldr r6, [r10, #0x110]
bic r6, r6, #0x2
str r6, [r10, #0x110]
/*
* Set the OSC bias current to -37.5%
* to drop the power on VDDHIGH.
*/
ldr r6, [r10, #0x150]
orr r6, r6, #0xc000
str r6, [r10, #0x150]
/*
* if the usb VBUS wakeup is enabled, skip
* disable main 2p5.
*/
cmp r2, #0x1
beq anatop_enter_done
/* Enable the week 2p5 */
ldr r6, [r10, #0x130]
orr r6, r6, #0x40000
str r6, [r10, #0x130]
/* Disable main 2p5. */
ldr r6, [r10, #0x130]
bic r6, r6, #0x1
str r6, [r10, #0x130]
/*
* Cannot diable regular bandgap
* in LDO-enable mode. The bandgap
* is required for ARM-LDO to regulate
* the voltage.
*/
ldr r6, [r10, #0x140]
and r6, r6, #0x1f
cmp r6, #0x1f
bne anatop_enter_done
/* Enable low power bandgap */
ldr r6, [r10, #0x260]
orr r6, r6, #0x20
str r6, [r10, #0x260]
/*
* Turn off the bias current
* from the regular bandgap.
*/
ldr r6, [r10, #0x260]
orr r6, r6, #0x80
str r6, [r10, #0x260]
/*
* Clear the REFTTOP+SELFBIASOFF,
* self_bais circuit of the band gap.
* Per RM, should be cleared when
* band gap is powered down.
*/
ldr r6, [r10, #0x150]
bic r6, r6, #0x8
str r6, [r10, #0x150]
/* Power down the regular bandgap */
ldr r6, [r10, #0x150]
orr r6, r6, #0x1
str r6, [r10, #0x150]
anatop_enter_done:
.endm
.macro anatop_exit_idle
ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET]
cmp r4, #0x0
beq skip_anatop_restore
cmp r2, #0x1
beq ldo2p5_not_disabled
/*
* Regular bandgap will not be disabled
* in LDO-enabled mode as it is required
* for ARM-LDO to reguulate the voltage.
*/
ldr r6, [r10, #0x140]
and r6, r6, #0x1f
cmp r6, #0x1f
bne skip_bandgap_restore
/* Power up the regular bandgap */
ldr r6, [r10, #0x150]
bic r6, r6, #0x1
str r6, [r10, #0x150]
/* wait for bandgap stable */
3:
ldr r6, [r10, #0x150]
and r6, r6, #0x80
cmp r6, #0x80
bne 3b
/* now disable bandgap self-bias circuit */
ldr r6, [r10, #0x150]
orr r6, r6, #0x8
str r6, [r10, #0x150]
/* Turn on the bias current
* from the regular bandgap.
*/
ldr r6, [r10, #0x260]
bic r6, r6, #0x80
str r6, [r10, #0x260]
/* Disable the low power bandgap */
ldr r6, [r10, #0x260]
bic r6, r6, #0x20
str r6, [r10, #0x260]
skip_bandgap_restore:
/* Enable main 2p5. */
ldr r6, [r10, #0x130]
orr r6, r6, #0x1
str r6, [r10, #0x130]
/* Ensure the 2p5 is up */
5:
ldr r6, [r10, #0x130]
and r6, r6, #0x20000
cmp r6, #0x20000
bne 5b
/* Disable the weak 2p5 */
ldr r6, [r10, #0x130]
bic r6, r6, #0x40000
str r6, [r10, #0x130]
ldo2p5_not_disabled:
/*
* Set the OSC bias current to max
* value for normal operation.
*/
ldr r6, [r10, #0x150]
bic r6, r6, #0xc000
str r6, [r10, #0x150]
/* Enable 1p1 brown out, */
ldr r6, [r10, #0x110]
orr r6, r6, #0x2
str r6, [r10, #0x110]
skip_anatop_restore:
.endm
.macro disable_l1_dcache
/* disable d-cache */
mrc p15, 0, r7, c1, c0, 0
bic r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
dsb
isb
.endm
.macro mmdc_enter_dvfs_mode
/* disable automatic power saving. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
/* disable power down timer */
ldr r7, [r10, #0x04]
bic r7, r7, #0xff00
str r7, [r10, #0x04]
/* Make the DDR explicitly enter self-refresh. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
poll_dvfs_set:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
beq poll_dvfs_set
/* set SBS step-by step mode */
ldr r7, [r10, #0x410]
orr r7, r7, #0x100
str r7, [r10, #0x410]
.endm
.macro resume_mmdc
/* restore MMDC IO */
ldr r10, [r0, #PM_INFO_IOMUXC_V_OFFSET]
ldr r6, [r0, #PM_INFO_IO_NUM_OFFSET]
ldr r7, =PM_INFO_IO_VAL_OFFSET
add r7, r7, r0
6:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r10, r8]
subs r6, r6, #0x1
bne 6b
/*
* Need to reset the FIFO to avoid MMDC lockup
* caused because of floating/changing the
* configuration of many DDR IO pads.
*/
ldr r10, [r0, #PM_INFO_MMDC_V_OFFSET]
/* reset read FIFO, RST_RD_FIFO */
ldr r7, =MX6Q_MMDC_MPDGCTRL0
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
7:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 7b
/* reset FIFO a second time */
ldr r7, =MX6Q_MMDC_MPDGCTRL0
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
8:
ldr r6, [r10, r7]
ands r6, r6, #(1 <<31)
bne 8b
ldr r10, [r0, #PM_INFO_MMDC_V_OFFSET]
/* Let DDR out of self-refresh */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
9:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
bne 9b
/* enable power down timer */
ldr r7, [r10, #0x04]
orr r7, r7, #0x5500
str r7, [r10, #0x04]
/* enable DDR auto power saving */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
/* Clear SBS - unblock DDR accesses */
ldr r7, [r10, #0x410]
bic r7, r7, #0x100
str r7, [r10, #0x410]
.endm
.macro tlb_set_to_ocram
/* save ttbr */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_TTBR_OFFSET]
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* we need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to the IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is transslated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* 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
dsb
isb
/* store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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 tlb_back_to_ddr
/* Restore the TTBCR */
dsb
isb
/* 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
/* Flush the TLB */
ldr r6, =0x0
mcr p15, 0, r6, c8, c3, 0
dsb
isb
/* 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 ttbr */
ldr r7, [r0, #PM_INFO_TTBR_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
.extern iram_tlb_phys_addr
/*
* imx6sl_low_power_wfi code
* r0: wfi code base address
* r1: audio_bus_freq mode stat
* r2: vbus_ldo status
* r4: used for store the PLLs state
* r11: used for saving the ARM_PODF origin value
* r12: used for saving AHB_PODF origin value
*/
.align 3
ENTRY(imx6sl_low_power_idle)
mx6sl_lpm_wfi_start:
push {r4-r12}
tlb_set_to_ocram
disable_l1_dcache
#ifdef CONFIG_CACHE_L2X0
/* sync L2 */
ldr r10, [r0, #PM_INFO_L2_V_OFFSET]
/* Wait for background operations to complete. */
wait_for_l2_idle:
ldr r6, [r10, #0x730]
cmp r6, #0x0
bne wait_for_l2_idle
mov r6, #0x0
str r6, [r10, #0x730]
/* disable L2 */
str r6, [r10, #0x100]
dsb
isb
#endif
/* make sure MMDC in self-refresh */
ldr r10, [r0, #PM_INFO_MMDC_V_OFFSET]
mmdc_enter_dvfs_mode
/* save DDR IO settings and set to LPM mode*/
ldr r10, [r0, #PM_INFO_IOMUXC_V_OFFSET]
ldr r6, =0x0
ldr r7, [r0, #PM_INFO_IO_NUM_OFFSET]
ldr r8, =PM_INFO_IO_VAL_OFFSET
add r8, r8, r0
/* imx6sl's last 3 IOs need special setting */
sub r7, r7, #0x3
save_and_set_mmdc_io_lpm:
ldr r9, [r8], #0x4
ldr r5, [r10, r9]
str r6, [r10, r9]
str r5, [r8], #0x4
subs r7, r7, #0x1
bne save_and_set_mmdc_io_lpm
ldr r6, =0x1000
ldr r9, [r8], #0x4
ldr r5, [r10, r9]
str r5, [r8], #0x4
str r6, [r10, r9]
ldr r9, [r8], #0x4
ldr r5, [r10, r9]
str r6, [r10, r9]
str r5, [r8], #0x4
ldr r6, =0x80000
ldr r9, [r8], #0x4
ldr r5, [r10, r9]
str r6, [r10, r9]
str r5, [r8], #0x4
/* check the PLLs lock state */
check_pll_state
ccm_enter_idle
/* if in audio low power mode, no
* need to do anatop setting.
*/
cmp r1, #0x1
beq do_wfi
anatop_enter_idle
do_wfi:
wfi
/*
* Add these nops so that the
* prefetcher will not try to get
* any instrutions from DDR.
* The prefetch depth is about 23
* on A9, so adding 25 nops.
*/
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
/*
* restore the ARM PODF first to speed
* up the restore procedure
*/
ldr r10, [r0, #PM_INFO_CCM_V_OFFSET]
/* Restore arm_clk_podf */
str r11, [r10, #0x10]
ccm_do_wait
/*
* if in audio low power mode, skip
* restore the anatop setting.
*/
cmp r1, #0x1
beq skip_analog_restore
anatop_exit_idle
skip_analog_restore:
ccm_exit_idle
resume_mmdc
/* enable d-cache */
mrc p15, 0, r7, c1, c0, 0
orr r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
#ifdef CONFIG_CACHE_L2X0
ldr r10, [r0, #PM_INFO_L2_V_OFFSET]
mov r7, #0x1
/* enable L2 */
str r7, [r10, #0x100]
#endif
tlb_back_to_ddr
/* Restore register */
pop {r4 - r12}
mov pc, lr
/*
* Add ltorg here to ensure that all
* literals are stored here and are
* within the text space.
*/
.ltorg
mx6sl_lpm_wfi_end:

View File

@ -0,0 +1,780 @@
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#define PM_INFO_PBASE_OFFSET 0x0
#define PM_INFO_RESUME_ADDR_OFFSET 0x4
#define PM_INFO_PM_INFO_SIZE_OFFSET 0x8
#define PM_INFO_PM_INFO_TTBR_OFFSET 0xc
#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10
#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14
#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18
#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c
#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20
#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24
#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28
#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c
#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x30
#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x34
#define PM_INFO_MX6Q_SRC_P_OFFSET 0x38
#define PM_INFO_MX6Q_SRC_V_OFFSET 0x3c
#define PM_INFO_MX6Q_L2_P_OFFSET 0x40
#define PM_INFO_MX6Q_L2_V_OFFSET 0x44
#define PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET 0x48
#define PM_INFO_MMDC_IO_NUM_OFFSET 0x4c
#define PM_INFO_MMDC_IO_VAL_OFFSET 0x50
#define MX6Q_MMDC_MAPSR 0x404
#define MX6Q_MMDC_MPDGCTRL0 0x83c
#define MX6Q_SRC_GPR1 0x20
#define MX6Q_SRC_GPR2 0x24
#define MX6Q_GPC_IMR1 0x08
#define MX6Q_GPC_IMR2 0x0c
#define MX6Q_GPC_IMR3 0x10
#define MX6Q_GPC_IMR4 0x14
#define MX6Q_CCM_CCR 0x0
.globl mx6sll_lpm_wfi_start
.globl mx6sll_lpm_wfi_end
.macro pll_do_wait_lock
1:
ldr r7, [r10, r8]
ands r7, #0x80000000
beq 1b
.endm
.macro ccm_do_wait
2:
ldr r7, [r10, #0x48]
cmp r7, #0x0
bne 2b
.endm
.macro ccm_enter_idle
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
/* set ahb to 3MHz */
ldr r7, [r10, #0x14]
orr r7, r7, #0x1c00
str r7, [r10, #0x14]
/* set perclk to 6MHz */
ldr r7, [r10, #0x1c]
bic r7, r7, #0x3f
orr r7, r7, #0x3
str r7, [r10, #0x1c]
/* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */
ldr r7, [r10, #0x14]
orr r7, r7, #0x2
orr r7, r7, #(0x7 << 3)
str r7, [r10, #0x14]
ccm_do_wait
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/*
* disable pll2, suppose when system enter low
* power idle mode, only 396MHz pfd needs pll2,
* now we switch arm clock to OSC, we can disable
* pll2 now, gate pll2_pfd2 first.
*/
ldr r7, [r10, #0x100]
orr r7, #0x800000
str r7, [r10, #0x100]
ldr r7, [r10, #0x30]
orr r7, r7, #0x1000
bic r7, r7, #0x2000
str r7, [r10, #0x30]
.endm
.macro ccm_exit_idle
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* enable pll2 and pll2_pfd2 */
ldr r7, [r10, #0x30]
bic r7, r7, #0x1000
orr r7, r7, #0x2000
str r7, [r10, #0x30]
ldr r8, =0x30
pll_do_wait_lock
ldr r7, [r10, #0x100]
bic r7, #0x800000
str r7, [r10, #0x100]
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET]
/* set perclk back to 24MHz */
ldr r7, [r10, #0x1c]
bic r7, r7, #0x3f
str r7, [r10, #0x1c]
/* set mmdc back to 24MHz */
ldr r7, [r10, #0x14]
bic r7, r7, #0x7
bic r7, r7, #(0x7 << 3)
str r7, [r10, #0x14]
/* set ahb div back to 24MHz */
ldr r7, [r10, #0x14]
bic r7, r7, #0x1c00
str r7, [r10, #0x14]
ccm_do_wait
.endm
.macro anatop_enter_idle
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/*
* check whether any PLL is enabled, as only when
* there is no PLLs enabled, 2P5 and 1P1 can be
* off and only enable weak ones.
*/
/* arm pll1 */
ldr r7, [r10, #0]
ands r7, r7, #(1 << 31)
bne 10f
/* sys pll2 */
ldr r7, [r10, #0x30]
ands r7, r7, #(1 << 31)
bne 10f
/* usb pll3 */
ldr r7, [r10, #0x10]
ands r7, r7, #(1 << 31)
bne 10f
/* audio pll4 */
ldr r7, [r10, #0x70]
ands r7, r7, #(1 << 31)
bne 10f
/* vidio pll5 */
ldr r7, [r10, #0xa0]
ands r7, r7, #(1 << 31)
bne 10f
/* enet pll6 */
ldr r7, [r10, #0xe0]
ands r7, r7, #(1 << 31)
bne 10f
/* usb host pll7 */
ldr r7, [r10, #0x20]
ands r7, r7, #(1 << 31)
bne 10f
/* enable weak 2P5 and turn off regular 2P5 */
ldr r7, [r10, #0x130]
orr r7, r7, #0x40000
str r7, [r10, #0x130]
bic r7, r7, #0x1
str r7, [r10, #0x130]
/* enable weak 1p1 and turn off regular 1P1 */
ldr r7, [r10, #0x110]
orr r7, r7, #0x40000
str r7, [r10, #0x110]
bic r7, r7, #0x1
str r7, [r10, #0x110]
/* low power band gap enable */
ldr r7, [r10, #0x270]
orr r7, r7, #0x20
str r7, [r10, #0x270]
/* turn off the bias current from the regular bandgap */
ldr r7, [r10, #0x270]
orr r7, r7, #0x80
str r7, [r10, #0x270]
/*
* clear the REFTOP_SELFBIASOFF,
* self-bias circuit of the band gap.
* Per RM, should be cleared when
* band gap is powered down.
*/
ldr r7, [r10, #0x150]
bic r7, r7, #0x8
str r7, [r10, #0x150]
/* turn off regular bandgap */
ldr r7, [r10, #0x150]
orr r7, r7, #0x1
str r7, [r10, #0x150]
10:
/* switch to RC-OSC */
ldr r7, [r10, #0x270]
orr r7, r7, #0x10
str r7, [r10, #0x270]
/* turn off XTAL-OSC */
ldr r7, [r10, #0x150]
orr r7, r7, #0x40000000
str r7, [r10, #0x150]
/* lower OSC current by 37.5% */
ldr r7, [r10, #0x150]
orr r7, r7, #0x6000
str r7, [r10, #0x150]
/* disconnect vdd_high_in and vdd_snvs_in */
ldr r7, [r10, #0x150]
orr r7, r7, #0x1000
str r7, [r10, #0x150]
.endm
.macro anatop_exit_idle
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* increase OSC current to normal */
ldr r7, [r10, #0x150]
bic r7, r7, #0x6000
str r7, [r10, #0x150]
/* turn on XTAL-OSC and detector */
ldr r7, [r10, #0x150]
bic r7, r7, #0x40000000
orr r7, r7, #0x10000
str r7, [r10, #0x150]
/* wait for XTAL stable */
14:
ldr r7, [r10, #0x150]
ands r7, r7, #0x8000
beq 14b
/* switch to XTAL-OSC */
ldr r7, [r10, #0x270]
bic r7, r7, #0x10
str r7, [r10, #0x270]
/* turn off XTAL-OSC detector */
ldr r7, [r10, #0x150]
bic r7, r7, #0x10000
str r7, [r10, #0x150]
15:
/* check whether we need to enable 2P5/1P1 */
ldr r7, [r10, #0x110]
ands r7, r7, #0x40000
beq 11f
/* turn on regular bandgap and wait for stable */
ldr r7, [r10, #0x150]
bic r7, r7, #0x1
str r7, [r10, #0x150]
13:
ldr r7, [r10, #0x150]
ands r7, #0x80
beq 13b
/*
* set the REFTOP_SELFBIASOFF,
* self-bias circuit of the band gap.
*/
ldr r7, [r10, #0x150]
orr r7, r7, #0x8
str r7, [r10, #0x150]
/* turn on the bias current from the regular bandgap */
ldr r7, [r10, #0x270]
bic r7, r7, #0x80
str r7, [r10, #0x270]
/* low power band gap disable */
ldr r7, [r10, #0x270]
bic r7, r7, #0x20
str r7, [r10, #0x270]
12:
/* enable regular 2P5 and turn off weak 2P5 */
ldr r7, [r10, #0x130]
orr r7, r7, #0x1
str r7, [r10, #0x130]
/* Ensure the 2P5 is up. */
3:
ldr r7, [r10, #0x130]
ands r7, r7, #0x20000
beq 3b
ldr r7, [r10, #0x130]
bic r7, r7, #0x40000
str r7, [r10, #0x130]
/* enable regular 1p1 and turn off weak 1P1 */
ldr r7, [r10, #0x110]
orr r7, r7, #0x1
str r7, [r10, #0x110]
4:
ldr r7, [r10, #0x110]
ands r7, r7, #0x20000
beq 4b
ldr r7, [r10, #0x110]
bic r7, r7, #0x40000
str r7, [r10, #0x110]
11:
.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 mmdc_enter_dvfs_mode
/* disable automatic power savings. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
/* make the DDR explicitly enter self-refresh. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
5:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
beq 5b
.endm
.macro resume_mmdc
/* restore MMDC IO */
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET]
ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET
add r7, r7, r0
6:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r10, r8]
subs r6, r6, #0x1
bne 6b
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET]
/* reset read FIFO, RST_RD_FIFO */
ldr r7, =MX6Q_MMDC_MPDGCTRL0
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
7:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 7b
/* reset FIFO a second time */
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
8:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 8b
/* let DDR out of self-refresh */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
9:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
bne 9b
/* enable DDR auto power saving */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
.endm
.macro tlb_set_to_ocram
/* save ttbr */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the BTAC. */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* 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
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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 tlb_back_to_ddr
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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 ttbr */
ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
.extern iram_tlb_phys_addr
/* imx6sx_low_power_idle */
.align 3
ENTRY(imx6sll_low_power_idle)
mx6sll_lpm_wfi_start:
push {r4 - r10}
/* get necessary info from pm_info */
ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]
/*
* counting the resume address in iram
* to set it in SRC register.
*/
ldr r5, =imx6sll_low_power_idle
ldr r6, =wakeup
sub r6, r6, r5
add r8, r1, r2
add r3, r8, r6
/* store physical resume addr and pm_info address. */
ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET]
str r3, [r10, #0x20]
str r1, [r10, #0x24]
/* save disagnostic register */
mrc p15, 0, r7, c15, c0, 1
str r7, [r0, #PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET]
/* set ARM power to be gated */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r7, =0x1
str r7, [r10, #0x2a0]
disable_l1_dcache
#ifdef CONFIG_CACHE_L2X0
/* sync L2 */
ldr r10, [r0, #PM_INFO_MX6Q_L2_V_OFFSET]
/* Wait for background operations to complete. */
wait_for_l2_to_idle:
ldr r7, [r10, #0x730]
cmp r7, #0x0
bne wait_for_l2_to_idle
mov r7, #0x0
str r7, [r10, #0x730]
/* disable L2 */
str r7, [r10, #0x100]
dsb
isb
#endif
tlb_set_to_ocram
/* make sure MMDC in self-refresh */
ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
mmdc_enter_dvfs_mode
/* save DDR IO settings */
ldr r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldr r6, =0x0
ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET
add r8, r8, r0
save_and_set_mmdc_io_lpm:
ldr r9, [r8], #0x4
ldr r5, [r10, r9]
str r6, [r10, r9]
str r5, [r8], #0x4
subs r7, r7, #0x1
bne save_and_set_mmdc_io_lpm
mov r5, #0x0
ccm_enter_idle
anatop_enter_idle
/*
* mask all GPC interrupts before
* enabling the RBC counters to
* avoid the counter starting too
* early if an interupt is already
* pending.
*/
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r4, [r10, #MX6Q_GPC_IMR1]
ldr r5, [r10, #MX6Q_GPC_IMR2]
ldr r6, [r10, #MX6Q_GPC_IMR3]
ldr r7, [r10, #MX6Q_GPC_IMR4]
ldr r3, =0xffffffff
str r3, [r10, #MX6Q_GPC_IMR1]
str r3, [r10, #MX6Q_GPC_IMR2]
str r3, [r10, #MX6Q_GPC_IMR3]
str r3, [r10, #MX6Q_GPC_IMR4]
/*
* enable the RBC bypass counter here
* to hold off the interrupts. RBC counter
* = 4 (120us). With this setting, the latency
* from wakeup interrupt to ARM power up
* is ~130uS.
*/
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
ldr r3, [r10, #MX6Q_CCM_CCR]
bic r3, r3, #(0x3f << 21)
orr r3, r3, #(0x4 << 21)
str r3, [r10, #MX6Q_CCM_CCR]
/* enable the counter. */
ldr r3, [r10, #MX6Q_CCM_CCR]
orr r3, r3, #(0x1 << 27)
str r3, [r10, #MX6Q_CCM_CCR]
/* unmask all the GPC interrupts. */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
str r4, [r10, #MX6Q_GPC_IMR1]
str r5, [r10, #MX6Q_GPC_IMR2]
str r6, [r10, #MX6Q_GPC_IMR3]
str r7, [r10, #MX6Q_GPC_IMR4]
/*
* now delay for a short while (3usec)
* ARM is at 24MHz at this point
* so a short loop should be enough.
* this delay is required to ensure that
* the RBC counter can start counting in
* case an interrupt is already pending
* or in case an interrupt arrives just
* as ARM is about to assert DSM_request.
*/
ldr r4, =50
rbc_loop:
subs r4, r4, #0x1
bne rbc_loop
wfi
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
mov r5, #0x0
anatop_exit_idle
ccm_exit_idle
resume_mmdc
/* clear ARM power gate setting */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r7, =0x0
str r7, [r10, #0x2a0]
/* enable d-cache */
mrc p15, 0, r7, c1, c0, 0
orr r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
#ifdef CONFIG_CACHE_L2X0
ldr r10, [r0, #PM_INFO_MX6Q_L2_V_OFFSET]
mov r7, #0x1
/* enable L2 */
str r7, [r10, #0x100]
#endif
tlb_back_to_ddr
/* Restore registers */
pop {r4 - r10}
mov pc, lr
wakeup:
/* invalidate L1 I-cache first */
mov r1, #0x0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 6
/* enable the Icache and branch prediction */
mov r1, #0x1800
mcr p15, 0, r1, c1, c0, 0
isb
/* restore disagnostic register */
ldr r7, [r0, #PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET]
mcr p15, 0, r7, c15, c0, 1
/* get physical resume address from pm_info. */
ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
/* clear core0's entry and parameter */
ldr r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET]
mov r7, #0x0
str r7, [r10, #MX6Q_SRC_GPR1]
str r7, [r10, #MX6Q_SRC_GPR2]
/* clear ARM power gate setting */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET]
ldr r7, =0x0
str r7, [r10, #0x2a0]
mov r5, #0x1
anatop_exit_idle
ccm_exit_idle
resume_mmdc
/* Restore registers */
mov pc, lr
.ltorg
mx6sll_lpm_wfi_end:

View File

@ -0,0 +1,887 @@
/*
* Copyright (C) 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#define PM_INFO_PBASE_OFFSET 0x0
#define PM_INFO_RESUME_ADDR_OFFSET 0x4
#define PM_INFO_PM_INFO_SIZE_OFFSET 0x8
#define PM_INFO_PM_INFO_TTBR_OFFSET 0xc
#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10
#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14
#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18
#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c
#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20
#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24
#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28
#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c
#define PM_INFO_MX6Q_L2_P_OFFSET 0x30
#define PM_INFO_MX6Q_L2_V_OFFSET 0x34
#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x38
#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x3c
#define PM_INFO_MX6Q_SRC_P_OFFSET 0x40
#define PM_INFO_MX6Q_SRC_V_OFFSET 0x44
#define PM_INFO_MX6Q_SEMA4_P_OFFSET 0x48
#define PM_INFO_MX6Q_SEMA4_V_OFFSET 0x4c
#define PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET 0x50
#define PM_INFO_MMDC_IO_NUM_OFFSET 0x54
#define PM_INFO_MMDC_IO_VAL_OFFSET 0x58
#define MX6Q_MMDC_MAPSR 0x404
#define MX6Q_MMDC_MPDGCTRL0 0x83c
#define MX6Q_SRC_GPR1 0x20
#define MX6Q_SRC_GPR2 0x24
#define MX6Q_GPC_IMR1 0x08
#define MX6Q_GPC_IMR2 0x0c
#define MX6Q_GPC_IMR3 0x10
#define MX6Q_GPC_IMR4 0x14
#define MX6Q_CCM_CCR 0x0
.globl mx6sx_lpm_wfi_start
.globl mx6sx_lpm_wfi_end
.macro pll_do_wait_lock
1:
ldr r7, [r10, r8]
ands r7, #0x80000000
beq 1b
.endm
.macro ccm_do_wait
2:
ldr r7, [r10, #0x48]
cmp r7, #0x0
bne 2b
.endm
.macro ccm_enter_idle
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
/* set ahb to 3MHz */
ldr r7, [r10, #0x14]
orr r7, r7, #0x1c00
str r7, [r10, #0x14]
/* set perclk to 6MHz */
ldr r7, [r10, #0x1c]
bic r7, r7, #0x3f
orr r7, r7, #0x3
str r7, [r10, #0x1c]
/* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */
ldr r7, [r10, #0x14]
orr r7, r7, #0x2
orr r7, r7, #(0x7 << 3)
str r7, [r10, #0x14]
ccm_do_wait
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
/* set pll1_sw to from pll1 main */
ldr r7, [r10, #0xc]
bic r7, r7, #0x4
str r7, [r10, #0xc]
/* set step from osc */
ldr r7, [r10, #0xc]
bic r7, r7, #0x100
str r7, [r10, #0xc]
/* set pll1_sw to from step */
ldr r7, [r10, #0xc]
orr r7, r7, #0x4
str r7, [r10, #0xc]
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/* Disable PLL1 bypass output */
ldr r7, [r10]
bic r7, r7, #0x12000
str r7, [r10]
/*
* disable pll2, suppose when system enter low
* power idle mode, only 396MHz pfd needs pll2,
* now we switch arm clock to OSC, we can disable
* pll2 now, gate pll2_pfd2 first.
*/
ldr r7, [r10, #0x100]
orr r7, #0x800000
str r7, [r10, #0x100]
ldr r7, [r10, #0x30]
orr r7, r7, #0x1000
bic r7, r7, #0x2000
str r7, [r10, #0x30]
.endm
.macro ccm_exit_idle
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* enable pll2 and pll2_pfd2 */
ldr r7, [r10, #0x30]
bic r7, r7, #0x1000
orr r7, r7, #0x2000
str r7, [r10, #0x30]
ldr r8, =0x30
pll_do_wait_lock
ldr r7, [r10, #0x100]
bic r7, #0x800000
str r7, [r10, #0x100]
/* enable PLL1 bypass output */
ldr r7, [r10]
orr r7, r7, #0x12000
str r7, [r10]
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET]
/* set perclk back to 24MHz */
ldr r7, [r10, #0x1c]
bic r7, r7, #0x3f
str r7, [r10, #0x1c]
/* set mmdc back to 24MHz */
ldr r7, [r10, #0x14]
bic r7, r7, #0x7
bic r7, r7, #(0x7 << 3)
str r7, [r10, #0x14]
/* set ahb div back to 24MHz */
ldr r7, [r10, #0x14]
bic r7, r7, #0x1c00
str r7, [r10, #0x14]
ccm_do_wait
/* set pll1_sw to from pll1 main */
ldr r7, [r10, #0xc]
bic r7, r7, #0x4
str r7, [r10, #0xc]
/* set step from pll2_pfd2 */
ldr r7, [r10, #0xc]
orr r7, r7, #0x100
str r7, [r10, #0xc]
/* set pll1_sw to from step */
ldr r7, [r10, #0xc]
orr r7, r7, #0x4
str r7, [r10, #0xc]
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
.endm
.macro anatop_enter_idle
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/*
* check whether any PLL is enabled, as only when
* there is no PLLs enabled, 2P5 and 1P1 can be
* off and only enable weak ones.
*/
/* arm pll1 */
ldr r7, [r10, #0]
ands r7, r7, #(1 << 31)
bne 10f
/* sys pll2 */
ldr r7, [r10, #0x30]
ands r7, r7, #(1 << 31)
bne 10f
/* usb pll3 */
ldr r7, [r10, #0x10]
ands r7, r7, #(1 << 31)
bne 10f
/* audio pll4 */
ldr r7, [r10, #0x70]
ands r7, r7, #(1 << 31)
bne 10f
/* vidio pll5 */
ldr r7, [r10, #0xa0]
ands r7, r7, #(1 << 31)
bne 10f
/* enet pll6 */
ldr r7, [r10, #0xe0]
ands r7, r7, #(1 << 31)
bne 10f
/* usb host pll7 */
ldr r7, [r10, #0x20]
ands r7, r7, #(1 << 31)
bne 10f
/* enable weak 2P5 and turn off regular 2P5 */
ldr r7, [r10, #0x130]
orr r7, r7, #0x40000
str r7, [r10, #0x130]
bic r7, r7, #0x1
str r7, [r10, #0x130]
/* enable weak 1p1 and turn off regular 1P1 */
ldr r7, [r10, #0x110]
orr r7, r7, #0x40000
str r7, [r10, #0x110]
bic r7, r7, #0x1
str r7, [r10, #0x110]
/* check whether ARM LDO is bypassed */
ldr r7, [r10, #0x140]
and r7, r7, #0x1f
cmp r7, #0x1f
bne 10f
/* low power band gap enable */
ldr r7, [r10, #0x270]
orr r7, r7, #0x20
str r7, [r10, #0x270]
/* turn off the bias current from the regular bandgap */
ldr r7, [r10, #0x270]
orr r7, r7, #0x80
str r7, [r10, #0x270]
/*
* clear the REFTOP_SELFBIASOFF,
* self-bias circuit of the band gap.
* Per RM, should be cleared when
* band gap is powered down.
*/
ldr r7, [r10, #0x150]
bic r7, r7, #0x8
str r7, [r10, #0x150]
/* turn off regular bandgap */
ldr r7, [r10, #0x150]
orr r7, r7, #0x1
str r7, [r10, #0x150]
/* only switch to RC-OSC clk after TO1.2 */
ldr r7, [r10, #0x260]
and r7, r7, #0x3
cmp r7, #0x2
blt 10f
/* switch to RC-OSC */
ldr r7, [r10, #0x270]
orr r7, r7, #0x10
str r7, [r10, #0x270]
/* turn off XTAL-OSC */
ldr r7, [r10, #0x150]
orr r7, r7, #0x40000000
str r7, [r10, #0x150]
10:
/* lower OSC current by 37.5% */
ldr r7, [r10, #0x150]
orr r7, r7, #0x6000
str r7, [r10, #0x150]
.endm
.macro anatop_exit_idle
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* increase OSC current to normal */
ldr r7, [r10, #0x150]
bic r7, r7, #0x6000
str r7, [r10, #0x150]
/* only switch to RC-OSC after TO1.2 */
ldr r7, [r10, #0x260]
and r7, r7, #0x3
cmp r7, #0x2
blt 15f
/* turn on XTAL-OSC and detector */
ldr r7, [r10, #0x150]
bic r7, r7, #0x40000000
orr r7, r7, #0x10000
str r7, [r10, #0x150]
/* wait for XTAL stable */
14:
ldr r7, [r10, #0x150]
ands r7, r7, #0x8000
beq 14b
/* switch to XTAL-OSC */
ldr r7, [r10, #0x270]
bic r7, r7, #0x10
str r7, [r10, #0x270]
/* turn off XTAL-OSC detector */
ldr r7, [r10, #0x150]
bic r7, r7, #0x10000
str r7, [r10, #0x150]
15:
/* check whether we need to enable 2P5/1P1 */
ldr r7, [r10, #0x110]
ands r7, r7, #0x40000
beq 11f
/* check whether ARM LDO is bypassed */
ldr r7, [r10, #0x140]
and r7, r7, #0x1f
cmp r7, #0x1f
bne 12f
/* turn on regular bandgap and wait for stable */
ldr r7, [r10, #0x150]
bic r7, r7, #0x1
str r7, [r10, #0x150]
13:
ldr r7, [r10, #0x150]
ands r7, #0x80
beq 13b
/*
* set the REFTOP_SELFBIASOFF,
* self-bias circuit of the band gap.
*/
ldr r7, [r10, #0x150]
orr r7, r7, #0x8
str r7, [r10, #0x150]
/* turn on the bias current from the regular bandgap */
ldr r7, [r10, #0x270]
bic r7, r7, #0x80
str r7, [r10, #0x270]
/* low power band gap disable */
ldr r7, [r10, #0x270]
bic r7, r7, #0x20
str r7, [r10, #0x270]
12:
/* enable regular 2P5 and turn off weak 2P5 */
ldr r7, [r10, #0x130]
orr r7, r7, #0x1
str r7, [r10, #0x130]
/* Ensure the 2P5 is up. */
3:
ldr r7, [r10, #0x130]
ands r7, r7, #0x20000
beq 3b
ldr r7, [r10, #0x130]
bic r7, r7, #0x40000
str r7, [r10, #0x130]
/* enable regular 1p1 and turn off weak 1P1 */
ldr r7, [r10, #0x110]
orr r7, r7, #0x1
str r7, [r10, #0x110]
4:
ldr r7, [r10, #0x110]
ands r7, r7, #0x20000
beq 4b
ldr r7, [r10, #0x110]
bic r7, r7, #0x40000
str r7, [r10, #0x110]
11:
.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 mmdc_enter_dvfs_mode
/* disable automatic power savings. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
/* disable power down timer */
ldr r7, [r10, #0x4]
bic r7, r7, #0xff00
str r7, [r10, #0x4]
/* make the DDR explicitly enter self-refresh. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
5:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
beq 5b
.endm
.macro resume_mmdc
/* restore MMDC IO */
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET]
ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET
add r7, r7, r0
6:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r10, r8]
subs r6, r6, #0x1
bne 6b
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET]
/* reset read FIFO, RST_RD_FIFO */
ldr r7, =MX6Q_MMDC_MPDGCTRL0
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
7:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 7b
/* reset FIFO a second time */
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
8:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 8b
/* let DDR out of self-refresh */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
9:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
bne 9b
/* enable power down timer */
ldr r7, [r10, #0x4]
orr r7, r7, #0x5500
str r7, [r10, #0x4]
/* enable DDR auto power saving */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
.endm
.macro sema4_lock
/* lock share memory sema4 */
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_SEMA4_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_SEMA4_P_OFFSET]
ldrb r6, =0x1
16:
ldrb r7, [r10, #0x6]
cmp r7, #0x0
bne 16b
strb r6, [r10, #0x6]
.endm
.macro sema4_unlock
/* unlock share memory sema4 */
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_SEMA4_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_SEMA4_P_OFFSET]
ldrb r6, =0x0
strb r6, [r10, #0x6]
.endm
.macro tlb_set_to_ocram
/* save ttbr */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the BTAC. */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* 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
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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 tlb_back_to_ddr
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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 ttbr */
ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
.extern iram_tlb_phys_addr
/* imx6sx_low_power_idle */
.align 3
ENTRY(imx6sx_low_power_idle)
mx6sx_lpm_wfi_start:
push {r4 - r10}
/* get necessary info from pm_info */
ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]
/*
* counting the resume address in iram
* to set it in SRC register.
*/
ldr r5, =imx6sx_low_power_idle
ldr r6, =wakeup
sub r6, r6, r5
add r8, r1, r2
add r3, r8, r6
/* store physical resume addr and pm_info address. */
ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET]
str r3, [r10, #0x20]
str r1, [r10, #0x24]
/* save disagnostic register */
mrc p15, 0, r7, c15, c0, 1
str r7, [r0, #PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET]
/* set ARM power to be gated */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r7, =0x1
str r7, [r10, #0x2a0]
disable_l1_dcache
#ifdef CONFIG_CACHE_L2X0
/* sync L2 */
ldr r10, [r0, #PM_INFO_MX6Q_L2_V_OFFSET]
/* Wait for background operations to complete. */
wait_for_l2_to_idle:
ldr r7, [r10, #0x730]
cmp r7, #0x0
bne wait_for_l2_to_idle
mov r7, #0x0
str r7, [r10, #0x730]
/* disable L2 */
str r7, [r10, #0x100]
dsb
isb
#endif
tlb_set_to_ocram
/* make sure MMDC in self-refresh */
ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
mmdc_enter_dvfs_mode
/* save DDR IO settings */
ldr r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldr r6, =0x0
ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET
add r8, r8, r0
save_and_set_mmdc_io_lpm:
ldr r9, [r8], #0x4
ldr r5, [r10, r9]
str r6, [r10, r9]
str r5, [r8], #0x4
subs r7, r7, #0x1
bne save_and_set_mmdc_io_lpm
mov r5, #0x0
sema4_lock
ccm_enter_idle
anatop_enter_idle
sema4_unlock
/*
* mask all GPC interrupts before
* enabling the RBC counters to
* avoid the counter starting too
* early if an interupt is already
* pending.
*/
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r4, [r10, #MX6Q_GPC_IMR1]
ldr r5, [r10, #MX6Q_GPC_IMR2]
ldr r6, [r10, #MX6Q_GPC_IMR3]
ldr r7, [r10, #MX6Q_GPC_IMR4]
ldr r3, =0xffffffff
str r3, [r10, #MX6Q_GPC_IMR1]
str r3, [r10, #MX6Q_GPC_IMR2]
str r3, [r10, #MX6Q_GPC_IMR3]
str r3, [r10, #MX6Q_GPC_IMR4]
/*
* enable the RBC bypass counter here
* to hold off the interrupts. RBC counter
* = 4 (120us). With this setting, the latency
* from wakeup interrupt to ARM power up
* is ~130uS.
*/
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
ldr r3, [r10, #MX6Q_CCM_CCR]
bic r3, r3, #(0x3f << 21)
orr r3, r3, #(0x4 << 21)
str r3, [r10, #MX6Q_CCM_CCR]
/* enable the counter. */
ldr r3, [r10, #MX6Q_CCM_CCR]
orr r3, r3, #(0x1 << 27)
str r3, [r10, #MX6Q_CCM_CCR]
/* unmask all the GPC interrupts. */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
str r4, [r10, #MX6Q_GPC_IMR1]
str r5, [r10, #MX6Q_GPC_IMR2]
str r6, [r10, #MX6Q_GPC_IMR3]
str r7, [r10, #MX6Q_GPC_IMR4]
/*
* now delay for a short while (3usec)
* ARM is at 24MHz at this point
* so a short loop should be enough.
* this delay is required to ensure that
* the RBC counter can start counting in
* case an interrupt is already pending
* or in case an interrupt arrives just
* as ARM is about to assert DSM_request.
*/
ldr r4, =50
rbc_loop:
subs r4, r4, #0x1
bne rbc_loop
wfi
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
mov r5, #0x0
sema4_lock
anatop_exit_idle
ccm_exit_idle
sema4_unlock
resume_mmdc
/* clear ARM power gate setting */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r7, =0x0
str r7, [r10, #0x2a0]
/* enable d-cache */
mrc p15, 0, r7, c1, c0, 0
orr r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
#ifdef CONFIG_CACHE_L2X0
ldr r10, [r0, #PM_INFO_MX6Q_L2_V_OFFSET]
mov r7, #0x1
/* enable L2 */
str r7, [r10, #0x100]
#endif
tlb_back_to_ddr
/* Restore registers */
pop {r4 - r10}
mov pc, lr
wakeup:
/* invalidate L1 I-cache first */
mov r1, #0x0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 6
/* enable the Icache and branch prediction */
mov r1, #0x1800
mcr p15, 0, r1, c1, c0, 0
isb
/* restore disagnostic register */
ldr r7, [r0, #PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET]
mcr p15, 0, r7, c15, c0, 1
/* get physical resume address from pm_info. */
ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
/* clear core0's entry and parameter */
ldr r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET]
mov r7, #0x0
str r7, [r10, #MX6Q_SRC_GPR1]
str r7, [r10, #MX6Q_SRC_GPR2]
/* clear ARM power gate setting */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET]
ldr r7, =0x0
str r7, [r10, #0x2a0]
mov r5, #0x1
sema4_lock
anatop_exit_idle
ccm_exit_idle
sema4_unlock
resume_mmdc
/* Restore registers */
mov pc, lr
.ltorg
mx6sx_lpm_wfi_end:

View File

@ -0,0 +1,821 @@
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#define PM_INFO_PBASE_OFFSET 0x0
#define PM_INFO_RESUME_ADDR_OFFSET 0x4
#define PM_INFO_PM_INFO_SIZE_OFFSET 0x8
#define PM_INFO_PM_INFO_TTBR_OFFSET 0xc
#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10
#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14
#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18
#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c
#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20
#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24
#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28
#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c
#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x30
#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x34
#define PM_INFO_MX6Q_SRC_P_OFFSET 0x38
#define PM_INFO_MX6Q_SRC_V_OFFSET 0x3c
#define PM_INFO_MMDC_IO_NUM_OFFSET 0x40
#define PM_INFO_MMDC_IO_VAL_OFFSET 0x44
#define MX6Q_MMDC_MAPSR 0x404
#define MX6Q_MMDC_MPDGCTRL0 0x83c
#define MX6Q_SRC_GPR1 0x20
#define MX6Q_SRC_GPR2 0x24
#define MX6Q_GPC_IMR1 0x08
#define MX6Q_GPC_IMR2 0x0c
#define MX6Q_GPC_IMR3 0x10
#define MX6Q_GPC_IMR4 0x14
#define MX6Q_CCM_CCR 0x0
.globl mx6ul_lpm_wfi_start
.globl mx6ul_lpm_wfi_end
.macro pll_do_wait_lock
1:
ldr r7, [r10, r8]
ands r7, #0x80000000
beq 1b
.endm
.macro ccm_do_wait
2:
ldr r7, [r10, #0x48]
cmp r7, #0x0
bne 2b
.endm
.macro ccm_enter_idle
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
/* set ahb to 3MHz */
ldr r7, [r10, #0x14]
orr r7, r7, #0x1c00
str r7, [r10, #0x14]
/* set perclk to 6MHz */
ldr r7, [r10, #0x1c]
bic r7, r7, #0x3f
orr r7, r7, #0x3
str r7, [r10, #0x1c]
/* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */
ldr r7, [r10, #0x14]
orr r7, r7, #0x2
orr r7, r7, #(0x7 << 3)
str r7, [r10, #0x14]
ccm_do_wait
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/* bypass PLL1 output to OSC */
ldr r7, [r10]
orr r7, r7, #(0x1 << 16)
str r7, [r10]
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
/* set pll1_sw to from pll1 main */
ldr r7, [r10, #0xc]
bic r7, r7, #0x4
str r7, [r10, #0xc]
/* set step from osc */
ldr r7, [r10, #0xc]
bic r7, r7, #0x100
str r7, [r10, #0xc]
/* set pll1_sw to from step */
ldr r7, [r10, #0xc]
orr r7, r7, #0x4
str r7, [r10, #0xc]
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/* Disable PLL1 bypass output */
ldr r7, [r10]
bic r7, r7, #0x12000
str r7, [r10]
/*
* disable pll2, suppose when system enter low
* power idle mode, only 396MHz pfd needs pll2,
* now we switch arm clock to OSC, we can disable
* pll2 now, gate pll2_pfd2 first.
*/
ldr r7, [r10, #0x100]
orr r7, #0x800000
str r7, [r10, #0x100]
ldr r7, [r10, #0x30]
orr r7, r7, #0x1000
bic r7, r7, #0x2000
str r7, [r10, #0x30]
.endm
.macro ccm_exit_idle
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* enable pll2 and pll2_pfd2 */
ldr r7, [r10, #0x30]
bic r7, r7, #0x1000
orr r7, r7, #0x2000
str r7, [r10, #0x30]
ldr r8, =0x30
pll_do_wait_lock
ldr r7, [r10, #0x100]
bic r7, #0x800000
str r7, [r10, #0x100]
/* enable PLL1 bypass output */
ldr r7, [r10]
orr r7, r7, #0x12000
str r7, [r10]
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET]
/* set perclk back to 24MHz */
ldr r7, [r10, #0x1c]
bic r7, r7, #0x3f
str r7, [r10, #0x1c]
/* set mmdc back to 24MHz */
ldr r7, [r10, #0x14]
bic r7, r7, #0x7
bic r7, r7, #(0x7 << 3)
str r7, [r10, #0x14]
/* set ahb div back to 24MHz */
ldr r7, [r10, #0x14]
bic r7, r7, #0x1c00
str r7, [r10, #0x14]
ccm_do_wait
/* set pll1_sw to from pll1 main */
ldr r7, [r10, #0xc]
bic r7, r7, #0x4
str r7, [r10, #0xc]
/* set step from pll2_pfd2 */
ldr r7, [r10, #0xc]
orr r7, r7, #0x100
str r7, [r10, #0xc]
/* set pll1_sw to from step */
ldr r7, [r10, #0xc]
orr r7, r7, #0x4
str r7, [r10, #0xc]
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* Unbypass PLL1 */
ldr r7, [r10]
bic r7, r7, #(0x1 << 16)
str r7, [r10]
.endm
.macro anatop_enter_idle
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/*
* check whether any PLL is enabled, as only when
* there is no PLLs enabled, 2P5 and 1P1 can be
* off and only enable weak ones.
*/
/* arm pll1 */
ldr r7, [r10, #0]
ands r7, r7, #(1 << 31)
bne 10f
/* sys pll2 */
ldr r7, [r10, #0x30]
ands r7, r7, #(1 << 31)
bne 10f
/* usb pll3 */
ldr r7, [r10, #0x10]
ands r7, r7, #(1 << 31)
bne 10f
/* audio pll4 */
ldr r7, [r10, #0x70]
ands r7, r7, #(1 << 31)
bne 10f
/* vidio pll5 */
ldr r7, [r10, #0xa0]
ands r7, r7, #(1 << 31)
bne 10f
/* enet pll6 */
ldr r7, [r10, #0xe0]
ands r7, r7, #(1 << 31)
bne 10f
/* usb host pll7 */
ldr r7, [r10, #0x20]
ands r7, r7, #(1 << 31)
bne 10f
/* enable weak 2P5 and turn off regular 2P5 */
ldr r7, [r10, #0x130]
orr r7, r7, #0x40000
str r7, [r10, #0x130]
bic r7, r7, #0x1
str r7, [r10, #0x130]
/* enable weak 1p1 and turn off regular 1P1 */
ldr r7, [r10, #0x110]
orr r7, r7, #0x40000
str r7, [r10, #0x110]
bic r7, r7, #0x1
str r7, [r10, #0x110]
/* check whether ARM LDO is bypassed */
ldr r7, [r10, #0x140]
and r7, r7, #0x1f
cmp r7, #0x1f
bne 10f
/* low power band gap enable */
ldr r7, [r10, #0x270]
orr r7, r7, #0x20
str r7, [r10, #0x270]
/* turn off the bias current from the regular bandgap */
ldr r7, [r10, #0x270]
orr r7, r7, #0x80
str r7, [r10, #0x270]
/*
* clear the REFTOP_SELFBIASOFF,
* self-bias circuit of the band gap.
* Per RM, should be cleared when
* band gap is powered down.
*/
ldr r7, [r10, #0x150]
bic r7, r7, #0x8
str r7, [r10, #0x150]
/* turn off regular bandgap */
ldr r7, [r10, #0x150]
orr r7, r7, #0x1
str r7, [r10, #0x150]
/* switch to RC-OSC */
ldr r7, [r10, #0x270]
orr r7, r7, #0x10
str r7, [r10, #0x270]
/* turn off XTAL-OSC */
ldr r7, [r10, #0x150]
orr r7, r7, #0x40000000
str r7, [r10, #0x150]
10:
/* lower OSC current by 37.5% */
ldr r7, [r10, #0x150]
orr r7, r7, #0x6000
str r7, [r10, #0x150]
/* disconnect vdd_high_in and vdd_snvs_in */
ldr r7, [r10, #0x150]
orr r7, r7, #0x1000
str r7, [r10, #0x150]
.endm
.macro anatop_exit_idle
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* increase OSC current to normal */
ldr r7, [r10, #0x150]
bic r7, r7, #0x6000
str r7, [r10, #0x150]
/* turn on XTAL-OSC and detector */
ldr r7, [r10, #0x150]
bic r7, r7, #0x40000000
orr r7, r7, #0x10000
str r7, [r10, #0x150]
/* wait for XTAL stable */
14:
ldr r7, [r10, #0x150]
ands r7, r7, #0x8000
beq 14b
/* switch to XTAL-OSC */
ldr r7, [r10, #0x270]
bic r7, r7, #0x10
str r7, [r10, #0x270]
/* turn off XTAL-OSC detector */
ldr r7, [r10, #0x150]
bic r7, r7, #0x10000
str r7, [r10, #0x150]
15:
/* check whether we need to enable 2P5/1P1 */
ldr r7, [r10, #0x110]
ands r7, r7, #0x40000
beq 11f
/* check whether ARM LDO is bypassed */
ldr r7, [r10, #0x140]
and r7, r7, #0x1f
cmp r7, #0x1f
bne 12f
/* turn on regular bandgap and wait for stable */
ldr r7, [r10, #0x150]
bic r7, r7, #0x1
str r7, [r10, #0x150]
13:
ldr r7, [r10, #0x150]
ands r7, #0x80
beq 13b
/*
* set the REFTOP_SELFBIASOFF,
* self-bias circuit of the band gap.
*/
ldr r7, [r10, #0x150]
orr r7, r7, #0x8
str r7, [r10, #0x150]
/* turn on the bias current from the regular bandgap */
ldr r7, [r10, #0x270]
bic r7, r7, #0x80
str r7, [r10, #0x270]
/* low power band gap disable */
ldr r7, [r10, #0x270]
bic r7, r7, #0x20
str r7, [r10, #0x270]
12:
/* enable regular 2P5 and turn off weak 2P5 */
ldr r7, [r10, #0x130]
orr r7, r7, #0x1
str r7, [r10, #0x130]
/* Ensure the 2P5 is up. */
3:
ldr r7, [r10, #0x130]
ands r7, r7, #0x20000
beq 3b
ldr r7, [r10, #0x130]
bic r7, r7, #0x40000
str r7, [r10, #0x130]
/* enable regular 1p1 and turn off weak 1P1 */
ldr r7, [r10, #0x110]
orr r7, r7, #0x1
str r7, [r10, #0x110]
4:
ldr r7, [r10, #0x110]
ands r7, r7, #0x20000
beq 4b
ldr r7, [r10, #0x110]
bic r7, r7, #0x40000
str r7, [r10, #0x110]
11:
.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 mmdc_enter_dvfs_mode
/* disable automatic power savings. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
/* disable power down timer */
ldr r7, [r10, #0x4]
bic r7, r7, #0xff00
str r7, [r10, #0x4]
/* make the DDR explicitly enter self-refresh. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
5:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
beq 5b
.endm
.macro resume_mmdc
/* restore MMDC IO */
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET]
ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET
add r7, r7, r0
6:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r10, r8]
subs r6, r6, #0x1
bne 6b
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET]
/* reset read FIFO, RST_RD_FIFO */
ldr r7, =MX6Q_MMDC_MPDGCTRL0
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
7:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 7b
/* reset FIFO a second time */
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
8:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 8b
/* let DDR out of self-refresh */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
9:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
bne 9b
/* enable power down timer */
ldr r7, [r10, #0x4]
orr r7, r7, #0x5500
str r7, [r10, #0x4]
/* enable DDR auto power saving */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
.endm
.macro tlb_set_to_ocram
/* save ttbr */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the BTAC. */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* 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
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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 tlb_back_to_ddr
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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 ttbr */
ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
.extern iram_tlb_phys_addr
/* imx6ul_low_power_idle */
.align 3
ENTRY(imx6ul_low_power_idle)
mx6ul_lpm_wfi_start:
push {r4 - r10}
/* get necessary info from pm_info */
ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]
/*
* counting the resume address in iram
* to set it in SRC register.
*/
ldr r5, =imx6ul_low_power_idle
ldr r6, =wakeup
sub r6, r6, r5
add r8, r1, r2
add r3, r8, r6
/* store physical resume addr and pm_info address. */
ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET]
str r3, [r10, #0x20]
str r1, [r10, #0x24]
/* set ARM power to be gated */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r7, =0x1
str r7, [r10, #0x2a0]
disable_l1_dcache
tlb_set_to_ocram
/* make sure MMDC in self-refresh */
ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
mmdc_enter_dvfs_mode
/* save DDR IO settings */
ldr r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldr r6, =0x0
ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET
add r8, r8, r0
save_and_set_mmdc_io_lpm:
ldr r9, [r8], #0x4
ldr r5, [r10, r9]
str r6, [r10, r9]
str r5, [r8], #0x4
subs r7, r7, #0x1
bne save_and_set_mmdc_io_lpm
mov r5, #0x0
ccm_enter_idle
anatop_enter_idle
/*
* mask all GPC interrupts before
* enabling the RBC counters to
* avoid the counter starting too
* early if an interupt is already
* pending.
*/
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r4, [r10, #MX6Q_GPC_IMR1]
ldr r5, [r10, #MX6Q_GPC_IMR2]
ldr r6, [r10, #MX6Q_GPC_IMR3]
ldr r7, [r10, #MX6Q_GPC_IMR4]
ldr r3, =0xffffffff
str r3, [r10, #MX6Q_GPC_IMR1]
str r3, [r10, #MX6Q_GPC_IMR2]
str r3, [r10, #MX6Q_GPC_IMR3]
str r3, [r10, #MX6Q_GPC_IMR4]
/*
* enable the RBC bypass counter here
* to hold off the interrupts. RBC counter
* = 4 (120us). With this setting, the latency
* from wakeup interrupt to ARM power up
* is ~130uS.
*/
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
ldr r3, [r10, #MX6Q_CCM_CCR]
bic r3, r3, #(0x3f << 21)
orr r3, r3, #(0x4 << 21)
str r3, [r10, #MX6Q_CCM_CCR]
/* enable the counter. */
ldr r3, [r10, #MX6Q_CCM_CCR]
orr r3, r3, #(0x1 << 27)
str r3, [r10, #MX6Q_CCM_CCR]
/* unmask all the GPC interrupts. */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
str r4, [r10, #MX6Q_GPC_IMR1]
str r5, [r10, #MX6Q_GPC_IMR2]
str r6, [r10, #MX6Q_GPC_IMR3]
str r7, [r10, #MX6Q_GPC_IMR4]
/*
* now delay for a short while (3usec)
* ARM is at 24MHz at this point
* so a short loop should be enough.
* this delay is required to ensure that
* the RBC counter can start counting in
* case an interrupt is already pending
* or in case an interrupt arrives just
* as ARM is about to assert DSM_request.
*/
ldr r4, =50
rbc_loop:
subs r4, r4, #0x1
bne rbc_loop
wfi
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
mov r5, #0x0
anatop_exit_idle
ccm_exit_idle
/* clear ARM power gate setting */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r7, =0x0
str r7, [r10, #0x2a0]
resume_mmdc
/* enable d-cache */
mrc p15, 0, r7, c1, c0, 0
orr r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
tlb_back_to_ddr
/* Restore registers */
pop {r4 - r10}
mov pc, lr
wakeup:
/* invalidate L1 I-cache first */
mov r1, #0x0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 6
/* enable the Icache and branch prediction */
mov r1, #0x1800
mcr p15, 0, r1, 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 r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET]
mov r7, #0x0
str r7, [r10, #MX6Q_SRC_GPR1]
str r7, [r10, #MX6Q_SRC_GPR2]
/* clear ARM power gate setting */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET]
ldr r7, =0x0
str r7, [r10, #0x2a0]
mov r5, #0x1
anatop_exit_idle
ccm_exit_idle
resume_mmdc
/* Restore registers */
mov pc, lr
.ltorg
mx6ul_lpm_wfi_end:

View File

@ -0,0 +1,764 @@
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#define PM_INFO_PBASE_OFFSET 0x0
#define PM_INFO_RESUME_ADDR_OFFSET 0x4
#define PM_INFO_PM_INFO_SIZE_OFFSET 0x8
#define PM_INFO_PM_INFO_TTBR_OFFSET 0xc
#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10
#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14
#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18
#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c
#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20
#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24
#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28
#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c
#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x30
#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x34
#define PM_INFO_MX6Q_SRC_P_OFFSET 0x38
#define PM_INFO_MX6Q_SRC_V_OFFSET 0x3c
#define PM_INFO_MMDC_IO_NUM_OFFSET 0x40
#define PM_INFO_MMDC_IO_VAL_OFFSET 0x44
#define MX6Q_MMDC_MAPSR 0x404
#define MX6Q_MMDC_MPDGCTRL0 0x83c
#define MX6Q_SRC_GPR1 0x20
#define MX6Q_SRC_GPR2 0x24
#define MX6Q_GPC_IMR1 0x08
#define MX6Q_GPC_IMR2 0x0c
#define MX6Q_GPC_IMR3 0x10
#define MX6Q_GPC_IMR4 0x14
#define MX6Q_CCM_CCR 0x0
.globl mx6ull_lpm_wfi_start
.globl mx6ull_lpm_wfi_end
.macro pll_do_wait_lock
1:
ldr r7, [r10, r8]
ands r7, #0x80000000
beq 1b
.endm
.macro ccm_do_wait
2:
ldr r7, [r10, #0x48]
cmp r7, #0x0
bne 2b
.endm
.macro ccm_enter_idle
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
/* set ahb to 3MHz */
ldr r7, [r10, #0x14]
orr r7, r7, #0x1c00
str r7, [r10, #0x14]
/* set perclk to 6MHz */
ldr r7, [r10, #0x1c]
bic r7, r7, #0x3f
orr r7, r7, #0x3
str r7, [r10, #0x1c]
/* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */
ldr r7, [r10, #0x14]
orr r7, r7, #0x2
orr r7, r7, #(0x7 << 3)
str r7, [r10, #0x14]
ccm_do_wait
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/*
* disable pll2, suppose when system enter low
* power idle mode, only 396MHz pfd needs pll2,
* now we switch arm clock to OSC, we can disable
* pll2 now, gate pll2_pfd2 first.
*/
ldr r7, [r10, #0x100]
orr r7, #0x800000
str r7, [r10, #0x100]
ldr r7, [r10, #0x30]
orr r7, r7, #0x1000
bic r7, r7, #0x2000
str r7, [r10, #0x30]
.endm
.macro ccm_exit_idle
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* enable pll2 and pll2_pfd2 */
ldr r7, [r10, #0x30]
bic r7, r7, #0x1000
orr r7, r7, #0x2000
str r7, [r10, #0x30]
ldr r8, =0x30
pll_do_wait_lock
ldr r7, [r10, #0x100]
bic r7, #0x800000
str r7, [r10, #0x100]
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET]
/* set perclk back to 24MHz */
ldr r7, [r10, #0x1c]
bic r7, r7, #0x3f
str r7, [r10, #0x1c]
/* set mmdc back to 24MHz */
ldr r7, [r10, #0x14]
bic r7, r7, #0x7
bic r7, r7, #(0x7 << 3)
str r7, [r10, #0x14]
/* set ahb div back to 24MHz */
ldr r7, [r10, #0x14]
bic r7, r7, #0x1c00
str r7, [r10, #0x14]
ccm_do_wait
.endm
.macro anatop_enter_idle
ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
/*
* check whether any PLL is enabled, as only when
* there is no PLLs enabled, 2P5 and 1P1 can be
* off and only enable weak ones.
*/
/* arm pll1 */
ldr r7, [r10, #0]
ands r7, r7, #(1 << 31)
bne 10f
/* sys pll2 */
ldr r7, [r10, #0x30]
ands r7, r7, #(1 << 31)
bne 10f
/* usb pll3 */
ldr r7, [r10, #0x10]
ands r7, r7, #(1 << 31)
bne 10f
/* audio pll4 */
ldr r7, [r10, #0x70]
ands r7, r7, #(1 << 31)
bne 10f
/* vidio pll5 */
ldr r7, [r10, #0xa0]
ands r7, r7, #(1 << 31)
bne 10f
/* enet pll6 */
ldr r7, [r10, #0xe0]
ands r7, r7, #(1 << 31)
bne 10f
/* usb host pll7 */
ldr r7, [r10, #0x20]
ands r7, r7, #(1 << 31)
bne 10f
/* enable weak 2P5 and turn off regular 2P5 */
ldr r7, [r10, #0x130]
orr r7, r7, #0x40000
str r7, [r10, #0x130]
bic r7, r7, #0x1
str r7, [r10, #0x130]
/* enable weak 1p1 and turn off regular 1P1 */
ldr r7, [r10, #0x110]
orr r7, r7, #0x40000
str r7, [r10, #0x110]
bic r7, r7, #0x1
str r7, [r10, #0x110]
/* check whether ARM LDO is bypassed */
ldr r7, [r10, #0x140]
and r7, r7, #0x1f
cmp r7, #0x1f
bne 10f
/* low power band gap enable */
ldr r7, [r10, #0x270]
orr r7, r7, #0x20
str r7, [r10, #0x270]
/* turn off the bias current from the regular bandgap */
ldr r7, [r10, #0x270]
orr r7, r7, #0x80
str r7, [r10, #0x270]
/*
* clear the REFTOP_SELFBIASOFF,
* self-bias circuit of the band gap.
* Per RM, should be cleared when
* band gap is powered down.
*/
ldr r7, [r10, #0x150]
bic r7, r7, #0x8
str r7, [r10, #0x150]
/* turn off regular bandgap */
ldr r7, [r10, #0x150]
orr r7, r7, #0x1
str r7, [r10, #0x150]
10:
/* switch to RC-OSC */
ldr r7, [r10, #0x270]
orr r7, r7, #0x10
str r7, [r10, #0x270]
/* turn off XTAL-OSC */
ldr r7, [r10, #0x150]
orr r7, r7, #0x40000000
str r7, [r10, #0x150]
/* lower OSC current by 37.5% */
ldr r7, [r10, #0x150]
orr r7, r7, #0x6000
str r7, [r10, #0x150]
/* disconnect vdd_high_in and vdd_snvs_in */
ldr r7, [r10, #0x150]
orr r7, r7, #0x1000
str r7, [r10, #0x150]
.endm
.macro anatop_exit_idle
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
/* increase OSC current to normal */
ldr r7, [r10, #0x150]
bic r7, r7, #0x6000
str r7, [r10, #0x150]
/* turn on XTAL-OSC and detector */
ldr r7, [r10, #0x150]
bic r7, r7, #0x40000000
orr r7, r7, #0x10000
str r7, [r10, #0x150]
/* wait for XTAL stable */
14:
ldr r7, [r10, #0x150]
ands r7, r7, #0x8000
beq 14b
/* switch to XTAL-OSC */
ldr r7, [r10, #0x270]
bic r7, r7, #0x10
str r7, [r10, #0x270]
/* turn off XTAL-OSC detector */
ldr r7, [r10, #0x150]
bic r7, r7, #0x10000
str r7, [r10, #0x150]
15:
/* check whether we need to enable 2P5/1P1 */
ldr r7, [r10, #0x110]
ands r7, r7, #0x40000
beq 11f
/* check whether ARM LDO is bypassed */
ldr r7, [r10, #0x140]
and r7, r7, #0x1f
cmp r7, #0x1f
bne 12f
/* turn on regular bandgap and wait for stable */
ldr r7, [r10, #0x150]
bic r7, r7, #0x1
str r7, [r10, #0x150]
13:
ldr r7, [r10, #0x150]
ands r7, #0x80
beq 13b
/*
* set the REFTOP_SELFBIASOFF,
* self-bias circuit of the band gap.
*/
ldr r7, [r10, #0x150]
orr r7, r7, #0x8
str r7, [r10, #0x150]
/* turn on the bias current from the regular bandgap */
ldr r7, [r10, #0x270]
bic r7, r7, #0x80
str r7, [r10, #0x270]
/* low power band gap disable */
ldr r7, [r10, #0x270]
bic r7, r7, #0x20
str r7, [r10, #0x270]
12:
/* enable regular 2P5 and turn off weak 2P5 */
ldr r7, [r10, #0x130]
orr r7, r7, #0x1
str r7, [r10, #0x130]
/* Ensure the 2P5 is up. */
3:
ldr r7, [r10, #0x130]
ands r7, r7, #0x20000
beq 3b
ldr r7, [r10, #0x130]
bic r7, r7, #0x40000
str r7, [r10, #0x130]
/* enable regular 1p1 and turn off weak 1P1 */
ldr r7, [r10, #0x110]
orr r7, r7, #0x1
str r7, [r10, #0x110]
4:
ldr r7, [r10, #0x110]
ands r7, r7, #0x20000
beq 4b
ldr r7, [r10, #0x110]
bic r7, r7, #0x40000
str r7, [r10, #0x110]
11:
.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 mmdc_enter_dvfs_mode
/* disable automatic power savings. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
/* disable power down timer */
ldr r7, [r10, #0x4]
bic r7, r7, #0xff00
str r7, [r10, #0x4]
/* make the DDR explicitly enter self-refresh. */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
orr r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
5:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
beq 5b
.endm
.macro resume_mmdc
/* restore MMDC IO */
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET]
ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET
add r7, r7, r0
6:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r10, r8]
subs r6, r6, #0x1
bne 6b
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET]
/* reset read FIFO, RST_RD_FIFO */
ldr r7, =MX6Q_MMDC_MPDGCTRL0
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
7:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 7b
/* reset FIFO a second time */
ldr r6, [r10, r7]
orr r6, r6, #(1 << 31)
str r6, [r10, r7]
8:
ldr r6, [r10, r7]
ands r6, r6, #(1 << 31)
bne 8b
/* let DDR out of self-refresh */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #(1 << 21)
str r7, [r10, #MX6Q_MMDC_MAPSR]
9:
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
bne 9b
/* enable power down timer */
ldr r7, [r10, #0x4]
orr r7, r7, #0x5500
str r7, [r10, #0x4]
/* enable DDR auto power saving */
ldr r7, [r10, #MX6Q_MMDC_MAPSR]
bic r7, r7, #0x1
str r7, [r10, #MX6Q_MMDC_MAPSR]
.endm
.macro tlb_set_to_ocram
/* save ttbr */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the BTAC. */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* 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
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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 tlb_back_to_ddr
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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 ttbr */
ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
.extern iram_tlb_phys_addr
/* imx6ull_low_power_idle */
.align 3
ENTRY(imx6ull_low_power_idle)
mx6ull_lpm_wfi_start:
push {r4 - r10}
/* get necessary info from pm_info */
ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]
/*
* counting the resume address in iram
* to set it in SRC register.
*/
ldr r5, =imx6ull_low_power_idle
ldr r6, =wakeup
sub r6, r6, r5
add r8, r1, r2
add r3, r8, r6
/* store physical resume addr and pm_info address. */
ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET]
str r3, [r10, #0x20]
str r1, [r10, #0x24]
/* set ARM power to be gated */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r7, =0x1
str r7, [r10, #0x2a0]
disable_l1_dcache
tlb_set_to_ocram
/* make sure MMDC in self-refresh */
ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
mmdc_enter_dvfs_mode
/* save DDR IO settings */
ldr r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldr r6, =0x0
ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET
add r8, r8, r0
save_and_set_mmdc_io_lpm:
ldr r9, [r8], #0x4
ldr r5, [r10, r9]
str r6, [r10, r9]
str r5, [r8], #0x4
subs r7, r7, #0x1
bne save_and_set_mmdc_io_lpm
mov r5, #0x0
ccm_enter_idle
anatop_enter_idle
/*
* mask all GPC interrupts before
* enabling the RBC counters to
* avoid the counter starting too
* early if an interupt is already
* pending.
*/
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r4, [r10, #MX6Q_GPC_IMR1]
ldr r5, [r10, #MX6Q_GPC_IMR2]
ldr r6, [r10, #MX6Q_GPC_IMR3]
ldr r7, [r10, #MX6Q_GPC_IMR4]
ldr r3, =0xffffffff
str r3, [r10, #MX6Q_GPC_IMR1]
str r3, [r10, #MX6Q_GPC_IMR2]
str r3, [r10, #MX6Q_GPC_IMR3]
str r3, [r10, #MX6Q_GPC_IMR4]
/*
* enable the RBC bypass counter here
* to hold off the interrupts. RBC counter
* = 4 (120us). With this setting, the latency
* from wakeup interrupt to ARM power up
* is ~130uS.
*/
ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]
ldr r3, [r10, #MX6Q_CCM_CCR]
bic r3, r3, #(0x3f << 21)
orr r3, r3, #(0x4 << 21)
str r3, [r10, #MX6Q_CCM_CCR]
/* enable the counter. */
ldr r3, [r10, #MX6Q_CCM_CCR]
orr r3, r3, #(0x1 << 27)
str r3, [r10, #MX6Q_CCM_CCR]
/* unmask all the GPC interrupts. */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
str r4, [r10, #MX6Q_GPC_IMR1]
str r5, [r10, #MX6Q_GPC_IMR2]
str r6, [r10, #MX6Q_GPC_IMR3]
str r7, [r10, #MX6Q_GPC_IMR4]
/*
* now delay for a short while (3usec)
* ARM is at 24MHz at this point
* so a short loop should be enough.
* this delay is required to ensure that
* the RBC counter can start counting in
* case an interrupt is already pending
* or in case an interrupt arrives just
* as ARM is about to assert DSM_request.
*/
ldr r4, =50
rbc_loop:
subs r4, r4, #0x1
bne rbc_loop
wfi
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
mov r5, #0x0
anatop_exit_idle
ccm_exit_idle
/* clear ARM power gate setting */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]
ldr r7, =0x0
str r7, [r10, #0x2a0]
resume_mmdc
/* enable d-cache */
mrc p15, 0, r7, c1, c0, 0
orr r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
tlb_back_to_ddr
/* Restore registers */
pop {r4 - r10}
mov pc, lr
wakeup:
/* invalidate L1 I-cache first */
mov r1, #0x0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 6
/* enable the Icache and branch prediction */
mov r1, #0x1800
mcr p15, 0, r1, 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 r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET]
mov r7, #0x0
str r7, [r10, #MX6Q_SRC_GPR1]
str r7, [r10, #MX6Q_SRC_GPR2]
/* clear ARM power gate setting */
ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET]
ldr r7, =0x0
str r7, [r10, #0x2a0]
mov r5, #0x1
anatop_exit_idle
ccm_exit_idle
resume_mmdc
/* Restore registers */
mov pc, lr
.ltorg
mx6ull_lpm_wfi_end:

View File

@ -0,0 +1,788 @@
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#define PM_INFO_VBASE_OFFSET 0x0
#define PM_INFO_PBASE_OFFSET 0x4
#define PM_INFO_RESUME_ADDR_OFFSET 0x8
#define PM_INFO_PM_INFO_SIZE_OFFSET 0xc
#define PM_INFO_PM_INFO_TTBR_OFFSET 0x10
#define PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET 0x14
#define PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET 0x18
#define PM_INFO_VAL_OFFSET 0x1c
#define PM_INFO_FLAG0_OFFSET 0x20
#define PM_INFO_FLAG1_OFFSET 0x24
#define PM_INFO_MX7D_DDRC_P_OFFSET 0x28
#define PM_INFO_MX7D_DDRC_V_OFFSET 0x2c
#define PM_INFO_MX7D_CCM_P_OFFSET 0x30
#define PM_INFO_MX7D_CCM_V_OFFSET 0x34
#define PM_INFO_MX7D_ANATOP_P_OFFSET 0x38
#define PM_INFO_MX7D_ANATOP_V_OFFSET 0x3c
#define PM_INFO_MX7D_SRC_P_OFFSET 0x40
#define PM_INFO_MX7D_SRC_V_OFFSET 0x44
#define PM_INFO_MX7D_IOMUXC_GPR_P_OFFSET 0x48
#define PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET 0x4c
#define PM_INFO_MX7D_GPC_P_OFFSET 0x50
#define PM_INFO_MX7D_GPC_V_OFFSET 0x54
#define PM_INFO_MX7D_GIC_DIST_P_OFFSET 0x58
#define PM_INFO_MX7D_GIC_DIST_V_OFFSET 0x5c
#define MX7D_SRC_GPR1 0x74
#define MX7D_SRC_GPR2 0x78
#define MX7D_SRC_GPR3 0x7c
#define MX7D_SRC_GPR4 0x80
#define MX7D_GPC_IMR1 0x30
#define MX7D_GPC_IMR2 0x34
#define MX7D_GPC_IMR3 0x38
#define MX7D_GPC_IMR4 0x3c
#define DDRC_STAT 0x4
#define DDRC_PWRCTL 0x30
#define DDRC_DBG1 0x304
#define DDRC_DBGCAM 0x308
#define DDRC_PSTAT 0x3fc
#define DDRC_PCTRL_0 0x490
/*
* imx_pen_lock
*
* The reference link of Peterson's algorithm:
* http://en.wikipedia.org/wiki/Peterson's_algorithm
*
* val1 = r1 = !turn (inverted from Peterson's algorithm)
* on cpu 0:
* r2 = flag[0] (in flag0)
* r3 = flag[1] (in flag1)
* on cpu1:
* r2 = flag[1] (in flag1)
* r3 = flag[0] (in flag0)
*
*/
.macro imx_pen_lock
mov r8, r0
mrc p15, 0, r5, c0, c0, 5
and r5, r5, #3
add r6, r8, #PM_INFO_VAL_OFFSET
cmp r5, #0
addeq r7, r8, #PM_INFO_FLAG0_OFFSET
addeq r8, r8, #PM_INFO_FLAG1_OFFSET
addne r7, r8, #PM_INFO_FLAG1_OFFSET
addne r8, r8, #PM_INFO_FLAG0_OFFSET
mov r9, #1
str r9, [r7]
dsb
str r5, [r6]
1:
dsb
ldr r9, [r8]
cmp r9, #1
ldreq r9, [r6]
cmpeq r9, r5
beq 1b
.endm
.macro imx_pen_unlock
dsb
mrc p15, 0, r6, c0, c0, 5
and r6, r6, #3
cmp r6, #0
addeq r7, r0, #PM_INFO_FLAG0_OFFSET
addne r7, r0, #PM_INFO_FLAG1_OFFSET
mov r9, #0
str r9, [r7]
.endm
.macro disable_l1_dcache
push {r0 - r12, lr}
ldr r7, =v7_flush_dcache_all
mov lr, pc
mov pc, r7
pop {r0 - r12, 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 - r12, lr}
ldr r7, =v7_flush_dcache_all
mov lr, pc
mov pc, r7
pop {r0 - r12, lr}
#ifdef CONFIG_SMP
clrex
/* Turn off SMP bit. */
mrc p15, 0, r8, c1, c0, 1
bic r8, r8, #0x40
mcr p15, 0, r8, c1, c0, 1
isb
dsb
#endif
.endm
.macro tlb_set_to_ocram
/* save ttbr */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
/* 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 r7, [r6]
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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 tlb_back_to_ddr
/* 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 ttbr */
ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
/* r10 must be DDRC base address */
.macro ddrc_enter_self_refresh
ldr r10, [r0, #PM_INFO_MX7D_DDRC_V_OFFSET]
/* disable port */
ldr r7, =0x0
str r7, [r10, #DDRC_PCTRL_0]
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r10, #DDRC_PWRCTL]
/* wait rw port_busy clear */
ldr r6, =(0x1 << 16)
orr r6, r6, #0x1
2:
ldr r7, [r10, #DDRC_PSTAT]
ands r7, r7, r6
bne 2b
ldr r7, =0x1
str r7, [r10, #DDRC_DBG1]
ldr r6, =0x36000000
11:
ldr r7, [r10, #DDRC_DBGCAM]
and r7, r7, r6
cmp r7, r6
bne 11b
/* enter self-refresh bit 5 */
ldr r7, =(0x1 << 5)
str r7, [r10, #DDRC_PWRCTL]
/* wait until self-refresh mode entered */
3:
ldr r7, [r10, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
bne 3b
4:
ldr r7, [r10, #DDRC_STAT]
ands r7, r7, #0x20
beq 4b
/* disable dram clk */
ldr r7, [r10, #DDRC_PWRCTL]
orr r7, r7, #(1 << 3)
str r7, [r10, #DDRC_PWRCTL]
/*
* TO1.1 adds feature of DDR pads power down,
* although TO1.0 has no such function, but it is
* NOT harmful to program GPR registers for TO1.0,
* it can avoid the logic of version check in idle
* thread.
*/
ldr r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET]
ldr r7, =0xf0000
str r7, [r10]
/* delay 20us, measured by gpio */
ldr r7, =20
12:
subs r7, r7, #0x1
bne 12b
.endm
/* r10 must be DDRC base address */
.macro ddrc_exit_self_refresh
cmp r5, #0x1
ldreq r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_P_OFFSET]
ldrne r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET]
ldr r7, =0x0
str r7, [r10]
ldr r7, =20
13:
subs r7, r7, #0x1
bne 13b
cmp r5, #0x1
ldreq r10, [r0, #PM_INFO_MX7D_DDRC_P_OFFSET]
ldrne r10, [r0, #PM_INFO_MX7D_DDRC_V_OFFSET]
ldr r7, =0x0
str r7, [r10, #DDRC_DBG1]
ldr r6, =0x30000000
14:
ldr r7, [r10, #DDRC_DBGCAM]
and r7, r7, r6
cmp r7, r6
bne 14b
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r10, #DDRC_PWRCTL]
/* wait until self-refresh mode exited */
5:
ldr r7, [r10, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
beq 5b
/* enable auto self-refresh */
ldr r7, [r10, #DDRC_PWRCTL]
orr r7, r7, #(1 << 0)
str r7, [r10, #DDRC_PWRCTL]
ldr r7, =0x1
str r7, [r10, #DDRC_PCTRL_0]
.endm
.macro pll_do_wait_lock
6:
ldr r7, [r10, r8]
ands r7, #0x80000000
beq 6b
.endm
.macro ccm_enter_idle
ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET]
/* ungate pfd1 332m for lower axi */
ldr r7, =0x8000
str r7, [r10, #0xc8]
ldr r10, [r0, #PM_INFO_MX7D_CCM_V_OFFSET]
/* switch ARM CLK to OSC */
ldr r8, =0x8000
ldr r7, [r10, r8]
bic r7, r7, #0x7000000
str r7, [r10, r8]
/* lower AXI clk from 24MHz to 3MHz */
ldr r8, =0x8800
ldr r7, [r10, r8]
orr r7, r7, #0x7
str r7, [r10, r8]
/* lower AHB clk from 24MHz to 3MHz */
ldr r8, =0x9000
ldr r7, [r10, r8]
orr r7, r7, #0x7
str r7, [r10, r8]
/* gate dram clk */
ldr r8, =0x9880
ldr r7, [r10, r8]
bic r7, r7, #0x10000000
str r7, [r10, r8]
ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET]
/* gate pfd1 332m */
ldr r7, =0x8000
str r7, [r10, #0xc4]
/* gate system pll pfd div 1 */
ldr r7, =0x10
str r7, [r10, #0xb4]
/* power down ARM, 480 and DRAM PLL */
ldr r7, =0x1000
str r7, [r10, #0x64]
str r7, [r10, #0xb4]
ldr r7, =0x100000
str r7, [r10, #0x74]
.endm
.macro ccm_exit_idle
cmp r5, #0x1
ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET]
ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET]
/* power up ARM, 480 and DRAM PLL */
ldr r7, =0x1000
str r7, [r10, #0x68]
ldr r8, =0x60
pll_do_wait_lock
ldr r7, =0x1000
str r7, [r10, #0xb8]
ldr r8, =0xb0
pll_do_wait_lock
ldr r7, =0x100000
str r7, [r10, #0x78]
ldr r8, =0x70
pll_do_wait_lock
/* ungate pfd1 332m for lower axi */
ldr r7, =0x8000
str r7, [r10, #0xc8]
/* ungate system pll pfd div 1 */
ldr r7, =0x10
str r7, [r10, #0xb8]
cmp r5, #0x1
ldreq r10, [r0, #PM_INFO_MX7D_CCM_P_OFFSET]
ldrne r10, [r0, #PM_INFO_MX7D_CCM_V_OFFSET]
/* switch ARM CLK to PLL */
ldr r8, =0x8000
ldr r7, [r10, r8]
orr r7, r7, #0x1000000
str r7, [r10, r8]
/* restore AXI clk from 3MHz to 24MHz */
ldr r8, =0x8800
ldr r7, [r10, r8]
bic r7, r7, #0x7
str r7, [r10, r8]
/* restore AHB clk from 3MHz to 24MHz */
ldr r8, =0x9000
ldr r7, [r10, r8]
bic r7, r7, #0x7
str r7, [r10, r8]
/* ungate dram clk */
ldr r8, =0x9880
ldr r7, [r10, r8]
orr r7, r7, #0x10000000
str r7, [r10, r8]
cmp r5, #0x1
ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET]
ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET]
/* gate pfd1 332m for lower axi */
ldr r7, =0x8000
str r7, [r10, #0xc4]
.endm
.macro anatop_enter_idle
ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET]
/* XTAL to RC-OSC switch */
ldr r7, [r10]
orr r7, r7, #0x1000
str r7, [r10]
/* power down XTAL */
ldr r7, [r10]
orr r7, r7, #0x1
str r7, [r10]
/* enable weak 1P0A */
ldr r7, [r10, #0x200]
orr r7, r7, #0x40000
str r7, [r10, #0x200]
/* disable LDO 1P0A */
ldr r7, [r10, #0x200]
bic r7, r7, #0x1
str r7, [r10, #0x200]
/* disable LDO 1P0D */
ldr r7, [r10, #0x210]
bic r7, r7, #0x1
str r7, [r10, #0x210]
/* disable LDO 1P2 */
ldr r7, [r10, #0x220]
bic r7, r7, #0x1
str r7, [r10, #0x220]
/* switch to low power bandgap */
ldr r7, [r10, #0x270]
orr r7, r7, #0x400
str r7, [r10, #0x270]
/* power down normal bandgap */
orr r7, r7, #0x1
str r7, [r10, #0x270]
.endm
.macro anatop_exit_idle
cmp r5, #0x1
ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET]
ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET]
/* power on normal bandgap */
ldr r7, [r10, #0x270]
bic r7, r7, #0x1
str r7, [r10, #0x270]
/* switch to normal bandgap */
bic r7, r7, #0x400
str r7, [r10, #0x270]
/* enable LDO 1P2 */
ldr r7, [r10, #0x220]
orr r7, r7, #0x1
str r7, [r10, #0x220]
7:
ldr r7, [r10, #0x220]
ands r7, #0x20000
beq 7b
/* enable LDO 1P0D */
ldr r7, [r10, #0x210]
orr r7, r7, #0x1
str r7, [r10, #0x210]
8:
ldr r7, [r10, #0x210]
ands r7, #0x20000
beq 8b
/* enable LDO 1P0A */
ldr r7, [r10, #0x200]
orr r7, r7, #0x1
str r7, [r10, #0x200]
9:
ldr r7, [r10, #0x200]
ands r7, #0x20000
beq 9b
/* disable weak 1P0A */
ldr r7, [r10, #0x200]
bic r7, r7, #0x40000
str r7, [r10, #0x200]
/* power up XTAL and wait */
ldr r7, [r10]
bic r7, r7, #0x1
str r7, [r10]
10:
ldr r7, [r10]
ands r7, r7, #0x4
beq 10b
/* RC-OSC to XTAL switch */
ldr r7, [r10]
bic r7, r7, #0x1000
str r7, [r10]
.endm
.extern iram_tlb_phys_addr
.align 3
ENTRY(imx7d_low_power_idle)
push {r0 - r12}
/* get necessary info from pm_info */
ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]
/*
* counting the resume address in iram
* to set it in SRC register.
*/
ldr r5, =imx7d_low_power_idle
ldr r6, =wakeup
sub r6, r6, r5
add r8, r1, r2
add r3, r8, r6
/* r11 is cpu id */
mrc p15, 0, r11, c0, c0, 5
and r11, r11, #3
cmp r11, #0x0
ldreq r6, =MX7D_SRC_GPR1
ldreq r7, =MX7D_SRC_GPR2
ldrne r6, =MX7D_SRC_GPR3
ldrne r7, =MX7D_SRC_GPR4
/* store physical resume addr and pm_info address. */
ldr r10, [r0, #PM_INFO_MX7D_SRC_V_OFFSET]
str r3, [r10, r6]
str r1, [r10, r7]
disable_l1_dcache
tlb_set_to_ocram
/* check last to sleep */
ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET]
ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET]
cmp r6, r7
bne lpi_enter_done
ddrc_enter_self_refresh
ccm_enter_idle
anatop_enter_idle
ldr r10, [r0, #PM_INFO_MX7D_GIC_DIST_V_OFFSET]
ldr r7, =0x0
ldr r8, =0x1000
str r7, [r10, r8]
ldr r10, [r0, #PM_INFO_MX7D_GPC_V_OFFSET]
ldr r4, [r10, #MX7D_GPC_IMR1]
ldr r5, [r10, #MX7D_GPC_IMR2]
ldr r6, [r10, #MX7D_GPC_IMR3]
ldr r7, [r10, #MX7D_GPC_IMR4]
ldr r8, =0xffffffff
str r8, [r10, #MX7D_GPC_IMR1]
str r8, [r10, #MX7D_GPC_IMR2]
str r8, [r10, #MX7D_GPC_IMR3]
str r8, [r10, #MX7D_GPC_IMR4]
/*
* enable the RBC bypass counter here
* to hold off the interrupts. RBC counter
* = 8 (240us). With this setting, the latency
* from wakeup interrupt to ARM power up
* is ~250uS.
*/
ldr r8, [r10, #0x14]
bic r8, r8, #(0x3f << 24)
orr r8, r8, #(0x8 << 24)
str r8, [r10, #0x14]
/* enable the counter. */
ldr r8, [r10, #0x14]
orr r8, r8, #(0x1 << 30)
str r8, [r10, #0x14]
/* unmask all the GPC interrupts. */
str r4, [r10, #MX7D_GPC_IMR1]
str r5, [r10, #MX7D_GPC_IMR2]
str r6, [r10, #MX7D_GPC_IMR3]
str r7, [r10, #MX7D_GPC_IMR4]
/*
* now delay for a short while (30usec)
* ARM is at 24MHz at this point
* so a short loop should be enough.
* this delay is required to ensure that
* the RBC counter can start counting in
* case an interrupt is already pending
* or in case an interrupt arrives just
* as ARM is about to assert DSM_request.
*/
ldr r4, =5
rbc_loop:
subs r4, r4, #0x1
bne rbc_loop
lpi_enter_done:
imx_pen_unlock
wfi
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
imx_pen_lock
/* check first to wake */
ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET]
ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET]
cmp r6, r7
bne skip_lpi_flow
ldr r5, =0x0
anatop_exit_idle
ccm_exit_idle
ddrc_exit_self_refresh
ldr r10, [r0, #PM_INFO_MX7D_GIC_DIST_V_OFFSET]
ldr r7, =0x1
ldr r8, =0x1000
str r7, [r10, r8]
skip_lpi_flow:
tlb_back_to_ddr
#ifdef CONFIG_SMP
/* Turn on SMP bit. */
mrc p15, 0, r7, c1, c0, 1
orr r7, r7, #0x40
mcr p15, 0, r7, c1, c0, 1
isb
#endif
/* enable d-cache */
mrc p15, 0, r7, c1, c0, 0
orr r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
dsb
isb
/* Restore registers */
pop {r0 - r12}
mov pc, lr
wakeup:
/* invalidate L1 I-cache first */
mov r1, #0x0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 0
mcr p15, 0, r1, c7, c5, 6
/* enable the Icache and branch prediction */
mov r1, #0x1800
mcr p15, 0, r1, c1, c0, 0
isb
imx_pen_lock
/* check first to wake */
ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET]
ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET]
cmp r6, r7
bne wakeup_skip_lpi_flow
ldr r5, =0x1
anatop_exit_idle
ccm_exit_idle
ddrc_exit_self_refresh
wakeup_skip_lpi_flow:
/* get physical resume address from pm_info. */
ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
/* Restore registers */
mov pc, lr
.ltorg
ENDPROC(imx7d_low_power_idle)

View File

@ -0,0 +1,618 @@
/*
* Copyright (C) 2012-2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include "hardware.h"
#define PL310_AUX_CTRL 0x104
#define PL310_DCACHE_LOCKDOWN_BASE 0x900
#define PL310_AUX_16WAY_BIT 0x10000
#define PL310_LOCKDOWN_NBREGS 8
#define PL310_LOCKDOWN_SZREG 4
#define PL310_8WAYS_MASK 0x00FF
#define PL310_16WAYS_UPPERMASK 0xFF00
.globl imx6_lpddr2_freq_change_start
.globl imx6_lpddr2_freq_change_end
.macro mx6sl_switch_to_24MHz
/*
* Set MMDC clock to be sourced from PLL3.
* Ensure first periph2_clk2 is sourced from PLL3.
* Set the PERIPH2_CLK2_PODF to divide by 2.
*/
ldr r6, [r2, #0x14]
bic r6, r6, #0x7
orr r6, r6, #0x1
str r6, [r2, #0x14]
/* Select PLL3 to source MMDC. */
ldr r6, [r2, #0x18]
bic r6, r6, #0x100000
str r6, [r2, #0x18]
/* Swtich periph2_clk_sel to run from PLL3. */
ldr r6, [r2, #0x14]
orr r6, r6, #0x4000000
str r6, [r2, #0x14]
periph2_clk_switch1:
ldr r6, [r2, #0x48]
cmp r6, #0
bne periph2_clk_switch1
/*
* Need to clock gate the 528 PFDs before
* powering down PLL2.
* Only the PLL2_PFD2_400M should be ON
* at this time, so only clock gate that one.
*/
ldr r6, [r3, #0x100]
orr r6, r6, #0x800000
str r6, [r3, #0x100]
/*
* Set PLL2 to bypass state. We should be here
* only if MMDC is not sourced from PLL2.
*/
ldr r6, [r3, #0x30]
orr r6, r6, #0x10000
str r6, [r3, #0x30]
ldr r6, [r3, #0x30]
orr r6, r6, #0x1000
str r6, [r3, #0x30]
/* Ensure pre_periph2_clk_mux is set to pll2 */
ldr r6, [r2, #0x18]
bic r6, r6, #0x600000
str r6, [r2, #0x18]
/* Set MMDC clock to be sourced from the bypassed PLL2. */
ldr r6, [r2, #0x14]
bic r6, r6, #0x4000000
str r6, [r2, #0x14]
periph2_clk_switch2:
ldr r6, [r2, #0x48]
cmp r6, #0
bne periph2_clk_switch2
/*
* Now move MMDC back to periph2_clk2 source.
* after selecting PLL2 as the option.
* Select PLL2 as the source.
*/
ldr r6, [r2, #0x18]
orr r6, r6, #0x100000
str r6, [r2, #0x18]
/* set periph2_clk2_podf to divide by 1. */
ldr r6, [r2, #0x14]
bic r6, r6, #0x7
str r6, [r2, #0x14]
/* Now move periph2_clk to periph2_clk2 source */
ldr r6, [r2, #0x14]
orr r6, r6, #0x4000000
str r6, [r2, #0x14]
periph2_clk_switch3:
ldr r6, [r2, #0x48]
cmp r6, #0
bne periph2_clk_switch3
/* Now set the MMDC PODF back to 1.*/
ldr r6, [r2, #0x14]
bic r6, r6, #0x38
str r6, [r2, #0x14]
mmdc_podf0:
ldr r6, [r2, #0x48]
cmp r6, #0
bne mmdc_podf0
.endm
.macro ddr_switch_400MHz
/* Set MMDC divider first, in case PLL3 is at 480MHz. */
ldr r6, [r3, #0x10]
and r6, r6, #0x10000
cmp r6, #0x10000
beq pll3_in_bypass
/* Set MMDC divder to divide by 2. */
ldr r6, [r2, #0x14]
bic r6, r6, #0x38
orr r6, r6, #0x8
str r6, [r2, #0x14]
mmdc_podf:
ldr r6, [r2, #0x48]
cmp r6, #0
bne mmdc_podf
pll3_in_bypass:
/*
* Check if we are switching between
* 400Mhz <-> 100MHz.If so, we should
* try to source MMDC from PLL2_200M.
*/
cmp r1, #0
beq not_low_bus_freq
/* Ensure that MMDC is sourced from PLL2 mux first. */
ldr r6, [r2, #0x14]
bic r6, r6, #0x4000000
str r6, [r2, #0x14]
periph2_clk_switch4:
ldr r6, [r2, #0x48]
cmp r6, #0
bne periph2_clk_switch4
not_low_bus_freq:
/* Now ensure periph2_clk2_sel mux is set to PLL3 */
ldr r6, [r2, #0x18]
bic r6, r6, #0x100000
str r6, [r2, #0x18]
/* Now switch MMDC to PLL3. */
ldr r6, [r2, #0x14]
orr r6, r6, #0x4000000
str r6, [r2, #0x14]
periph2_clk_switch5:
ldr r6, [r2, #0x48]
cmp r6, #0
bne periph2_clk_switch5
/*
* Check if PLL2 is already unlocked.
* If so do nothing with PLL2.
*/
cmp r1, #0
beq pll2_already_on
/* Now power up PLL2 and unbypass it. */
ldr r6, [r3, #0x30]
bic r6, r6, #0x1000
str r6, [r3, #0x30]
/* Make sure PLL2 has locked.*/
wait_for_pll_lock:
ldr r6, [r3, #0x30]
and r6, r6, #0x80000000
cmp r6, #0x80000000
bne wait_for_pll_lock
ldr r6, [r3, #0x30]
bic r6, r6, #0x10000
str r6, [r3, #0x30]
/*
* Need to enable the 528 PFDs after
* powering up PLL2.
* Only the PLL2_PFD2_400M should be ON
* as it feeds the MMDC. Rest should have
* been managed by clock code.
*/
ldr r6, [r3, #0x100]
bic r6, r6, #0x800000
str r6, [r3, #0x100]
pll2_already_on:
/*
* Now switch MMDC clk back to pll2_mux option.
* Ensure pre_periph2_clk2 is set to pll2_pfd_400M.
* If switching to audio DDR freq, set the
* pre_periph2_clk2 to PLL2_PFD_200M
*/
ldr r6, =400000000
cmp r6, r0
bne use_pll2_pfd_200M
ldr r6, [r2, #0x18]
bic r6, r6, #0x600000
orr r6, r6, #0x200000
str r6, [r2, #0x18]
ldr r6, =400000000
b cont2
use_pll2_pfd_200M:
ldr r6, [r2, #0x18]
orr r6, r6, #0x600000
str r6, [r2, #0x18]
ldr r6, =200000000
cont2:
ldr r4, [r2, #0x14]
bic r4, r4, #0x4000000
str r4, [r2, #0x14]
periph2_clk_switch6:
ldr r4, [r2, #0x48]
cmp r4, #0
bne periph2_clk_switch6
change_divider_only:
/*
* Calculate the MMDC divider
* based on the requested freq.
*/
ldr r4, =0
Loop2:
sub r6, r6, r0
cmp r6, r0
blt Div_Found
add r4, r4, #1
bgt Loop2
/* Shift divider into correct offset. */
lsl r4, r4, #3
Div_Found:
/* Set the MMDC PODF. */
ldr r6, [r2, #0x14]
bic r6, r6, #0x38
orr r6, r6, r4
str r6, [r2, #0x14]
mmdc_podf1:
ldr r6, [r2, #0x48]
cmp r6, #0
bne mmdc_podf1
.endm
.macro mmdc_clk_lower_100MHz
/*
* Prior to reducing the DDR frequency (at 528/400 MHz),
* read the Measure unit count bits (MU_UNIT_DEL_NUM)
*/
ldr r5, =0x8B8
ldr r6, [r8, r5]
/* Original MU unit count */
mov r6, r6, LSR #16
ldr r4, =0x3FF
and r6, r6, r4
/* Original MU unit count * 2 */
mov r7, r6, LSL #1
/*
* Bypass the automatic measure unit when below 100 MHz
* by setting the Measure unit bypass enable bit (MU_BYP_EN)
*/
ldr r6, [r8, r5]
orr r6, r6, #0x400
str r6, [r8, r5]
/*
* Double the measure count value read in step 1 and program it in the
* measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
* Register for the reduced frequency operation below 100 MHz
*/
ldr r6, [r8, r5]
ldr r4, =0x3FF
bic r6, r6, r4
orr r6, r6, r7
str r6, [r8, r5]
.endm
.macro mmdc_clk_above_100MHz
/* Make sure that the PHY measurement unit is NOT in bypass mode */
ldr r5, =0x8B8
ldr r6, [r8, r5]
bic r6, r6, #0x400
str r6, [r8, r5]
/* Now perform a Force Measurement. */
ldr r6, [r8, r5]
orr r6, r6, #0x800
str r6, [r8, r5]
/* Wait for FRC_MSR to clear. */
force_measure1:
ldr r6, [r8, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure1
.endm
/*
* mx6_lpddr2_freq_change
*
* Make sure DDR is in self-refresh.
* IRQs are already disabled.
* r0 : DDR freq.
* r1: low_bus_freq_mode flag
*/
.align 3
ENTRY(mx6_lpddr2_freq_change)
imx6_lpddr2_freq_change_start:
push {r4-r10}
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* 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 Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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
/* Disable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
#ifdef CONFIG_CACHE_L2X0
/*
* Need to make sure the buffers in L2 are drained.
* Performing a sync operation does this.
*/
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
/* Wait for background operations to complete. */
wait_for_l2_to_idle:
ldr r6, [r7, #0x730]
cmp r6, #0x0
bne wait_for_l2_to_idle
mov r6, #0x0
str r6, [r7, #0x730]
/*
* The second dsb might be needed to keep cache sync (device write)
* ordering with the memory accesses before it.
*/
dsb
isb
ldr r3, [r7, #PL310_AUX_CTRL]
tst r3, #PL310_AUX_16WAY_BIT
mov r3, #PL310_8WAYS_MASK
orrne r3, #PL310_16WAYS_UPPERMASK
mov r6, #PL310_LOCKDOWN_NBREGS
add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE
1: /* lock Dcache and Icache */
str r3, [r5], #PL310_LOCKDOWN_SZREG
str r3, [r5], #PL310_LOCKDOWN_SZREG
subs r6, r6, #1
bne 1b
#endif
ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR)
ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
ldr r8, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
/* Disable Automatic power savings. */
ldr r6, [r8, #0x404]
orr r6, r6, #0x01
str r6, [r8, #0x404]
/* MMDC0_MDPDC disable power down timer */
ldr r6, [r8, #0x4]
bic r6, r6, #0xff00
str r6, [r8, #0x4]
/* Delay for a while */
ldr r10, =10
delay1:
ldr r7, =0
cont1:
ldr r6, [r8, r7]
add r7, r7, #4
cmp r7, #16
bne cont1
sub r10, r10, #1
cmp r10, #0
bgt delay1
/* Make the DDR explicitly enter self-refresh. */
ldr r6, [r8, #0x404]
orr r6, r6, #0x200000
str r6, [r8, #0x404]
poll_dvfs_set_1:
ldr r6, [r8, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
bne poll_dvfs_set_1
/* set SBS step-by-step mode */
ldr r6, [r8, #0x410]
orr r6, r6, #0x100
str r6, [r8, #0x410]
ldr r10, =100000000
cmp r0, r10
bgt set_ddr_mu_above_100
mmdc_clk_lower_100MHz
set_ddr_mu_above_100:
ldr r10, =24000000
cmp r0, r10
beq set_to_24MHz
ddr_switch_400MHz
ldr r10,=100000000
cmp r0, r10
blt done
mmdc_clk_above_100MHz
b done
set_to_24MHz:
mx6sl_switch_to_24MHz
done:
/* clear DVFS - exit from self refresh mode */
ldr r6, [r8, #0x404]
bic r6, r6, #0x200000
str r6, [r8, #0x404]
poll_dvfs_clear_1:
ldr r6, [r8, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
beq poll_dvfs_clear_1
/* Enable Automatic power savings. */
ldr r6, [r8, #0x404]
bic r6, r6, #0x01
str r6, [r8, #0x404]
ldr r10, =24000000
cmp r0, r10
beq skip_power_down
/* Enable MMDC power down timer. */
ldr r6, [r8, #0x4]
orr r6, r6, #0x5500
str r6, [r8, #0x4]
skip_power_down:
/* clear SBS - unblock DDR accesses */
ldr r6, [r8, #0x410]
bic r6, r6, #0x100
str r6, [r8, #0x410]
#ifdef CONFIG_CACHE_L2X0
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
ldr r3, [r7, #PL310_AUX_CTRL]
tst r3, #PL310_AUX_16WAY_BIT
mov r6, #PL310_LOCKDOWN_NBREGS
mov r3, #0x00 /* 8 ways mask */
orrne r3, #0x0000 /* 16 ways mask */
add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE
1: /* lock Dcache and Icache */
str r3, [r5], #PL310_LOCKDOWN_SZREG
str r3, [r5], #PL310_LOCKDOWN_SZREG
subs r6, r6, #1
bne 1b
#endif
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
pop {r4-r10}
/* Restore registers */
mov pc, lr
/*
* Add ltorg here to ensure that all
* literals are stored here and are
* within the text space.
*/
.ltorg
imx6_lpddr2_freq_change_end:

View File

@ -0,0 +1,765 @@
/*
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include <asm/smp_scu.h>
#include "hardware.h"
#define CCM_CBCDR 0x14
#define CCM_CBCMR 0x18
#define CCM_CSCMR1 0x1c
#define CCM_CDHIPR 0x48
.globl mx6q_lpddr2_freq_change_start
.globl mx6q_lpddr2_freq_change_end
.macro wait_for_ccm_handshake
/* wait for div update */
1:
ldr r9, [r2, #CCM_CDHIPR]
cmp r9, #0
bne 1b
.endm
.macro set_mmdc_misc_ralat_2_cycles
/* Set MMDCx_MISC[RALAT] = 2 cycles */
ldr r6, [r8, #0x18]
bic r6, r6, #(0x7 << 6)
orr r6, r6, #(0x2 << 6)
str r6, [r8, #0x18]
/* Check if lpddr2 channel 1 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq 1f
ldr r6, [r4, #0x18]
bic r6, r6, #(0x7 << 6)
orr r6, r6, #(0x2 << 6)
str r6, [r4, #0x18]
1:
.endm
.macro switch_to_400MHz
/* set the MMDC_DIV=1, AXI_DIV=2, AHB_DIV=3 */
ldr r9, [r2, #CCM_CBCDR]
ldr r6, =0x3f1f00
bic r9, r9, r6
orr r9, r9, #(0x9 << 8)
orr r9, r9, #(1 << 16)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/* check periph_clk_sel */
ldr r9, [r2, #CCM_CBCDR]
and r9, r9, #(1 << 25)
cmp r9, #(1 << 25)
bne skip_periph_clk_switch_400m
/* now switch periph_clk back. */
ldr r9, [r2, #CCM_CBCDR]
bic r9, r9, #(1 << 25)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
skip_periph_clk_switch_400m:
.endm
.macro switch_to_100MHz
/* set the MMDC_DIV=4, AXI_DIV=8, AHB_DIV=8 */
ldr r9, [r2, #CCM_CBCDR]
ldr r6, =0x3f1f00
bic r9, r9, r6
orr r9, r9, #(0x1F << 16)
orr r9, r9, #(0x1D << 8)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/* check if periph_clk_sel is already set. */
ldr r9, [r2, #CCM_CBCDR]
and r9, r9, #(1 << 25)
cmp r9, #(1 << 25)
bne skip_periph_clk_switch_100m
/* now switch periph_clk back. */
ldr r9, [r2, #CCM_CBCDR]
bic r9, r9, #(1 << 25)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
skip_periph_clk_switch_100m:
.endm
.macro switch_to_24MHz
/*
* change the freq now try setting DDR to 24MHz.
* source it from the periph_clk2 ensure the
* periph_clk2 is sourced from 24MHz and the
* divider is 1.
*/
ldr r9, [r2, #CCM_CBCMR]
bic r9, r9, #(0x3 << 12)
orr r9, r9, #(1 << 12)
str r9, [r2, #CCM_CBCMR]
ldr r9, [r2, #CCM_CBCDR]
bic r9, r9, #(0x7 << 27)
str r9, [r2, #CCM_CBCDR]
/* now switch periph_clk to 24MHz. */
ldr r9, [r2, #CCM_CBCDR]
orr r9, r9, #(1 << 25)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/* change all the dividers to 1. */
ldr r9, [r2, #CCM_CBCDR]
ldr r6, =0x3f1f00
bic r9, r9, r6
orr r9, r9, #(1 << 8)
str r9, [r2, #CCM_CBCDR]
/* Wait for the divider to change. */
wait_for_ccm_handshake
.endm
.macro switch_to_24MHZ_from_pll2
/* Change DDR freq settings from pll2_pfd2 (div 2) */
ldr r9, [r2, #CCM_CBCMR]
bic r9, r9, #(0x3 << 18)
orr r9, r9, #(0x3 << 18)
str r9, [r2, #CCM_CBCMR]
ldr r9, [r2, #CCM_CBCDR]
bic r9, r9, #(1 << 25)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
ldr r9, [r2, #CCM_CBCDR]
ldr r6, =0x3f1f00
bic r9, r9, r6
orr r9, r9, #(1 << 8)
orr r9, r9, #(0x7 << 19)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro set_timings_below_100MHz_operation
set_mmdc_misc_ralat_2_cycles
/* Adjust LPDDR2 timings for 24Mhz operation */
ldr r5, =0x03162073
str r5, [r8, #0xC] /* MMDC0_MDCFG0 */
ldr r7, =0x00020482
str r7, [r8, #0x10] /* MMDC0_MDCFG1 */
ldr r9, =0x00000049
str r9, [r8, #0x14] /* MMDC0_MDCFG2 */
ldr r10, =0x00020333
str r10, [r8, #0x38] /* MMDC0_MDCFG3LP */
/* Check if lpddr2 channel 1 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_below_100Mhz_ch1_timings
str r5, [r4, #0xC] /* MMDC1_MDCFG0 */
str r7, [r4, #0x10] /* MMDC1_MDCFG1 */
str r9, [r4, #0x14] /* MMDC1_MDCFG2 */
str r10, [r4, #0x38] /* MMDC1_MDCFG3LP */
skip_below_100Mhz_ch1_timings:
.endm
.macro restore_mmdc_settings_info
/* restore timing from mmdc_settings_info */
ldr r6, [r1, #0x0]
ldr r7, [r1, #0x4]
1:
ldr r9, [r7], #0x4
ldr r10, [r7], #0x4
str r10, [r8, r9]
subs r6, r6, #0x1
bne 1b
/* Check if lpddr2 channel 1 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq 3f
ldr r6, [r1, #0x0]
ldr r7, [r1, #0x4]
2:
ldr r9, [r7], #0x4
ldr r10, [r7], #0x4
str r10, [r4, r9]
subs r6, r6, #0x1
bne 2b
3:
.endm
.macro mmdc_clk_lower_equal_100MHz
ldr r10, =100000000
cmp r0, r10
beq set_timmings_100MHz
set_timings_below_100MHz_operation
b common_to_lower_equal_100MHz
set_timmings_100MHz:
restore_mmdc_settings_info
set_mmdc_misc_ralat_2_cycles
common_to_lower_equal_100MHz:
/* if MMDC is not in 400MHz mode, skip double mu count */
ldr r5, [r1, #0x8]
ldr r6, =400000000
cmp r5, r6
bne skip_lower_force_measure_ch1
/*
* Prior to reducing the DDR frequency (at 528/400 MHz),
* read the Measure unit count bits (MU_UNIT_DEL_NUM)
*/
ldr r5, =0x8B8
ldr r6, [r8, r5]
/* Original MU unit count */
mov r6, r6, LSR #16
ldr r9, =0x3FF
and r6, r6, r9
/* Original MU unit count * 2 */
mov r7, r6, LSL #1
/*
* Bypass the automatic measure unit when below 100 MHz
* by setting the Measure unit bypass enable bit (MU_BYP_EN)
*/
ldr r6, [r8, r5]
orr r6, r6, #0x400
str r6, [r8, r5]
/*
* Double the measure count value read in step 1 and program it in the
* measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
* Register for the reduced frequency operation below 100 MHz
*/
ldr r6, [r8, r5]
ldr r9, =0x3FF
bic r6, r6, r9
orr r6, r6, r7
str r6, [r8, r5]
/* Now perform a Force Measurement. */
ldr r6, [r8, r5]
orr r6, r6, #0x800
str r6, [r8, r5]
/* Wait for FRC_MSR to clear. */
force_measure:
ldr r6, [r8, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_lower_force_measure_ch1
ldr r5, =0x8B8
ldr r6, [r4, r5]
/* Original MU unit count */
mov r6, r6, LSR #16
ldr r9, =0x3FF
and r6, r6, r9
/* Original MU unit count * 2 */
mov r7, r6, LSL #1
/*
* Bypass the automatic measure unit when below 100 MHz
* by setting the Measure unit bypass enable bit (MU_BYP_EN)
*/
ldr r6, [r4, r5]
orr r6, r6, #0x400
str r6, [r4, r5]
/*
* Double the measure count value read in step 1 and program it in the
* measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
* Register for the reduced frequency operation below 100 MHz
*/
ldr r6, [r4, r5]
ldr r9, =0x3FF
bic r6, r6, r9
orr r6, r6, r7
str r6, [r4, r5]
/* Now perform a Force Measurement. */
ldr r6, [r4, r5]
orr r6, r6, #0x800
str r6, [r4, r5]
/* Wait for FRC_MSR to clear. */
force_measure_ch1:
ldr r6, [r4, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure_ch1
skip_lower_force_measure_ch1:
.endm
.macro mmdc_clk_above_100MHz
restore_mmdc_settings_info
/* Make sure that the PHY measurement unit is NOT in bypass mode */
ldr r5, =0x8B8
ldr r6, [r8, r5]
bic r6, r6, #0x400
str r6, [r8, r5]
/* Now perform a Force Measurement. */
ldr r6, [r8, r5]
orr r6, r6, #0x800
str r6, [r8, r5]
/* Wait for FRC_MSR to clear. */
force_measure1:
ldr r6, [r8, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure1
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_above_force_measure_ch1
ldr r5, =0x8B8
ldr r6, [r4, r5]
bic r6, r6, #0x400
str r6, [r4, r5]
/* Now perform a Force Measurement. */
ldr r6, [r4, r5]
orr r6, r6, #0x800
str r6, [r4, r5]
/* Wait for FRC_MSR to clear. */
force_measure1_ch1:
ldr r6, [r4, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure1_ch1
skip_above_force_measure_ch1:
.endm
.macro disable_l1_dcache
/*
* Flush all data from the L1 data cache before disabling
* SCTLR.C bit.
*/
push {r0 - r11, lr}
ldr r7, =v7_flush_kern_cache_all
mov lr, pc
mov pc, r7
pop {r0 - r11, lr}
/* disable d-cache */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
push {r0 - r11, lr}
ldr r7, =v7_flush_kern_cache_all
mov lr, pc
mov pc, r7
pop {r0 - r11, lr}
.endm
/*
* mx6_lpddr2_freq_change
*
* Make sure DDR is in self-refresh.
* IRQs are already disabled.
* r0 : DDR freq.
* r1 : mmdc_settings_info
*/
.align 3
ENTRY(mx6q_lpddr2_freq_change)
mx6q_lpddr2_freq_change_start:
push {r2-r10}
/*
* Need to flush and disable L1 before
* disabling L2, we need data to
* coherent. Flushing L1 pushes
* everyhting to L2. We sync L2 later, but
* it can still have dirty lines.
* While exiting, we need to enable L2 first
* and then L1.
*/
disable_l1_dcache
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* 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
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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
#ifdef CONFIG_CACHE_L2X0
/*
* Need to make sure the buffers in L2 are drained.
* Performing a sync operation does this.
*/
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
/* Wait for background operations to complete. */
wait_for_l2_to_idle:
ldr r6, [r7, #0x730]
cmp r6, #0x0
bne wait_for_l2_to_idle
mov r6, #0x0
str r6, [r7, #0x730]
/*
* The second dsb might be needed to keep cache sync (device write)
* ordering with the memory accesses before it.
*/
dsb
isb
/* Disable L2. */
str r6, [r7, #0x100]
#endif
ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR)
ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
ldr r8, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
ldr r4, =IMX_IO_P2V(MX6Q_MMDC_P1_BASE_ADDR)
/* Disable Automatic power savings. */
ldr r6, [r8, #0x404]
orr r6, r6, #0x01
str r6, [r8, #0x404]
/* MMDC0_MDPDC disable power down timer */
ldr r6, [r8, #0x4]
bic r6, r6, #0xff00
str r6, [r8, #0x4]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_psd_ch1
ldr r6, [r4, #0x404]
orr r6, r6, #0x01
str r6, [r4, #0x404]
ldr r6, [r4, #0x4]
bic r6, r6, #0xff00
str r6, [r4, #0x4]
skip_psd_ch1:
/* Delay for a while */
ldr r10, =10
delay1:
ldr r7, =0
cont1:
ldr r6, [r8, r7]
add r7, r7, #4
cmp r7, #16
bne cont1
sub r10, r10, #1
cmp r10, #0
bgt delay1
/* Make the DDR explicitly enter self-refresh. */
ldr r6, [r8, #0x404]
orr r6, r6, #0x200000
str r6, [r8, #0x404]
poll_dvfs_set_1:
ldr r6, [r8, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
bne poll_dvfs_set_1
/* set SBS step-by-step mode */
ldr r6, [r8, #0x410]
orr r6, r6, #0x100
str r6, [r8, #0x410]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_sbs_ch1
ldr r6, [r4, #0x404]
orr r6, r6, #0x200000
str r6, [r4, #0x404]
poll_dvfs_set_2:
ldr r6, [r4, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
bne poll_dvfs_set_2
ldr r6, [r4, #0x410]
orr r6, r6, #0x100
str r6, [r4, #0x410]
skip_sbs_ch1:
ldr r10, =100000000
cmp r0, r10
bgt set_ddr_mu_above_100
mmdc_clk_lower_equal_100MHz
set_ddr_mu_above_100:
ldr r10, =24000000
cmp r0, r10
beq set_to_24MHz
ldr r10, =100000000
cmp r0, r10
beq set_to_100MHz
ldr r10, =400000000
cmp r0, r10
switch_to_400MHz
b done
set_to_24MHz:
/*
switch_to_24MHZ_from_pll2
*/
switch_to_24MHz
b done
set_to_100MHz:
switch_to_100MHz
done:
ldr r10,=100000000
cmp r0, r10
ble skip_mmdc_clk_check
mmdc_clk_above_100MHz
skip_mmdc_clk_check:
/* clear DVFS - exit from self refresh mode */
ldr r6, [r8, #0x404]
bic r6, r6, #0x200000
str r6, [r8, #0x404]
poll_dvfs_clear_1:
ldr r6, [r8, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
beq poll_dvfs_clear_1
/* Enable Automatic power savings. */
ldr r6, [r8, #0x404]
bic r6, r6, #0x01
str r6, [r8, #0x404]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_enable_psd_ch1
ldr r6, [r4, #0x404]
bic r6, r6, #0x200000
str r6, [r4, #0x404]
poll_dvfs_clear_2:
ldr r6, [r4, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
beq poll_dvfs_clear_2
ldr r6, [r4, #0x404]
bic r6, r6, #0x01
str r6, [r4, #0x404]
skip_enable_psd_ch1:
ldr r10, =24000000
cmp r0, r10
beq skip_power_down
/* Enable MMDC power down timer. */
ldr r6, [r8, #0x4]
orr r6, r6, #0x5500
str r6, [r8, #0x4]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_power_down
ldr r6, [r4, #0x4]
orr r6, r6, #0x5500
str r6, [r4, #0x4]
skip_power_down:
/* clear SBS - unblock DDR accesses */
ldr r6, [r8, #0x410]
bic r6, r6, #0x100
str r6, [r8, #0x410]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_disable_sbs_ch1
ldr r6, [r4, #0x410]
bic r6, r6, #0x100
str r6, [r4, #0x410]
skip_disable_sbs_ch1:
#ifdef CONFIG_CACHE_L2X0
/* Enable L2. */
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
ldr r6, =0x1
str r6, [r7, #0x100]
#endif
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
pop {r2-r10}
/* Restore registers */
mov pc, lr
/*
* Add ltorg here to ensure that all
* literals are stored here and are
* within the text space.
*/
.ltorg
mx6q_lpddr2_freq_change_end:

View File

@ -0,0 +1,460 @@
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include "hardware.h"
#define CCM_CBCDR 0x14
#define CCM_CBCMR 0x18
#define CCM_CSCMR1 0x1c
#define CCM_CDHIPR 0x48
#define L2_CACHE_SYNC 0x730
#define PL310_AUX_CTRL 0x104
#define PL310_DCACHE_LOCKDOWN_BASE 0x900
#define PL310_AUX_16WAY_BIT 0x10000
#define PL310_LOCKDOWN_NBREGS 8
#define PL310_LOCKDOWN_SZREG 4
#define PL310_8WAYS_MASK 0x00FF
#define PL310_16WAYS_UPPERMASK 0xFF00
#define MMDC0_MDPDC 0x4
#define MMDC0_MAPSR 0x404
#define MMDC0_MADPCR0 0x410
#define HIGH_BUS_MODE 0x0
.macro wait_for_ccm_handshake
1:
ldr r8, [r2, #CCM_CDHIPR]
cmp r8, #0
bne 1b
.endm
.macro switch_to_24MHz
/* periph2_clk2 sel to OSC_CLK */
ldr r8, [r2, #CCM_CBCMR]
orr r8, r8, #(1 << 20)
str r8, [r2, #CCM_CBCMR]
/* periph2_clk2_podf to 0 */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #0x7
str r8, [r2, #CCM_CBCDR]
/* periph2_clk sel to periph2_clk2 */
ldr r8, [r2, #CCM_CBCDR]
orr r8, r8, #(0x1 << 26)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/* fabric_mmdc_podf to 0 */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(0x7 << 3)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro switch_to_100MHz
/* check whether periph2_clk is from top path */
ldr r8, [r2, #CCM_CBCDR]
ands r8, #(1 << 26)
beq skip_periph2_clk2_switch_100m
/* now switch periph2_clk back. */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(1 << 26)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/*
* on i.MX6SLL, pre_periph2_clk will be always from
* pll2_pfd2, so no need to set pre_periph2_clk
* parent, just set the mmdc divider directly.
*/
skip_periph2_clk2_switch_100m:
/* fabric_mmdc_podf to 3 so that mmdc is 400 / 4 = 100MHz */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(0x7 << 3)
orr r8, r8, #(0x3 << 3)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro switch_to_400MHz
/* check whether periph2_clk is from top path */
ldr r8, [r2, #CCM_CBCDR]
ands r8, #(1 << 26)
beq skip_periph2_clk2_switch_400m
/* now switch periph2_clk back. */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(1 << 26)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/*
* on i.MX6SLL, pre_periph2_clk will be always from
* pll2_pfd2, so no need to set pre_periph2_clk
* parent, just set the mmdc divider directly.
*/
skip_periph2_clk2_switch_400m:
/* fabric_mmdc_podf to 0 */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(0x7 << 3)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro mmdc_clk_lower_100MHz
/* if MMDC is not in 400MHz mode, skip double mu count */
cmp r1, #HIGH_BUS_MODE
bne 1f
/*
* Prior to reducing the DDR frequency (at 528/400 MHz),
* read the Measure unit count bits (MU_UNIT_DEL_NUM)
*/
ldr r8, =0x8B8
ldr r6, [r5, r8]
/* Original MU unit count */
mov r6, r6, LSR #16
ldr r4, =0x3FF
and r6, r6, r4
/* Original MU unit count * 2 */
mov r7, r6, LSL #1
/*
* Bypass the automatic measure unit when below 100 MHz
* by setting the Measure unit bypass enable bit (MU_BYP_EN)
*/
ldr r6, [r5, r8]
orr r6, r6, #0x400
str r6, [r5, r8]
/*
* Double the measure count value read in step 1 and program it in the
* measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
* Register for the reduced frequency operation below 100 MHz
*/
ldr r6, [r5, r8]
ldr r4, =0x3FF
bic r6, r6, r4
orr r6, r6, r7
str r6, [r5, r8]
/* For freq lower than 100MHz, need to set RALAT to 2 */
ldr r6, [r5, #0x18]
bic r6, r6, #(0x7 << 6)
orr r6, r6, #(0x2 << 6)
str r6, [r5, #0x18]
1:
.endm
.macro mmdc_clk_above_100MHz
/* Make sure that the PHY measurement unit is NOT in bypass mode */
ldr r8, =0x8B8
ldr r6, [r5, r8]
bic r6, r6, #0x400
str r6, [r5, r8]
/* Now perform a Force Measurement. */
ldr r6, [r5, r8]
orr r6, r6, #0x800
str r6, [r5, r8]
/* Wait for FRC_MSR to clear. */
force_measure1:
ldr r6, [r5, r8]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure1
/* For freq higher than 100MHz, need to set RALAT to 5 */
ldr r6, [r5, #0x18]
bic r6, r6, #(0x7 << 6)
orr r6, r6, #(0x5 << 6)
str r6, [r5, #0x18]
.endm
.align 3
/*
* Below code can be used by i.MX6SLL when changing the
* frequency of MMDC. the MMDC is the same on these two SOCs.
*/
ENTRY(imx6sll_lpddr2_freq_change)
push {r2 - r8}
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* 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
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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
/* Disable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
#ifdef CONFIG_CACHE_L2X0
/*
* Need to make sure the buffers in L2 are drained.
* Performing a sync operation does this.
*/
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
mov r6, #0x0
str r6, [r7, #L2_CACHE_SYNC]
/*
* The second dsb might be needed to keep cache sync (device write)
* ordering with the memory accesses before it.
*/
dsb
isb
ldr r3, [r7, #PL310_AUX_CTRL]
tst r3, #PL310_AUX_16WAY_BIT
mov r3, #PL310_8WAYS_MASK
orrne r3, #PL310_16WAYS_UPPERMASK
mov r6, #PL310_LOCKDOWN_NBREGS
add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE
1: /* lock Dcache and Icache */
str r3, [r5], #PL310_LOCKDOWN_SZREG
str r3, [r5], #PL310_LOCKDOWN_SZREG
subs r6, r6, #1
bne 1b
#endif
ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR)
ldr r5, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
/* Disable Automatic power savings. */
ldr r6, [r5, #MMDC0_MAPSR]
orr r6, r6, #0x1
str r6, [r5, #MMDC0_MAPSR]
/* Delay for a while */
ldr r8, =10
delay:
ldr r7, =0
cont:
ldr r6, [r5, r7]
add r7, r7, #4
cmp r7, #16
bne cont
sub r8, r8, #1
cmp r8, #0
bgt delay
/* Make the DDR explicitly enter self-refresh. */
ldr r6, [r5, #MMDC0_MAPSR]
orr r6, r6, #0x200000
str r6, [r5, #MMDC0_MAPSR]
poll_dvfs_set_1:
ldr r6, [r5, #MMDC0_MAPSR]
and r6, r6, #0x2000000
cmp r6, #0x2000000
bne poll_dvfs_set_1
/* set SBS step-by-step mode */
ldr r6, [r5, #MMDC0_MADPCR0]
orr r6, r6, #0x100
str r6, [r5, #MMDC0_MADPCR0]
ldr r6, =100000000
cmp r0, r6
bgt set_ddr_mu_above_100
mmdc_clk_lower_100MHz
set_ddr_mu_above_100:
ldr r6, =24000000
cmp r0, r6
beq set_to_24MHz
ldr r6, =100000000
cmp r0, r6
beq set_to_100MHz
switch_to_400MHz
mmdc_clk_above_100MHz
b done
set_to_24MHz:
switch_to_24MHz
b done
set_to_100MHz:
switch_to_100MHz
done:
/* clear DVFS - exit from self refresh mode */
ldr r6, [r5, #MMDC0_MAPSR]
bic r6, r6, #0x200000
str r6, [r5, #MMDC0_MAPSR]
poll_dvfs_clear_1:
ldr r6, [r5, #MMDC0_MAPSR]
and r6, r6, #0x2000000
cmp r6, #0x2000000
beq poll_dvfs_clear_1
/* Enable Automatic power savings. */
ldr r6, [r5, #MMDC0_MAPSR]
bic r6, r6, #0x1
str r6, [r5, #MMDC0_MAPSR]
/* clear SBS - unblock DDR accesses */
ldr r6, [r5, #MMDC0_MADPCR0]
bic r6, r6, #0x100
str r6, [r5, #MMDC0_MADPCR0]
ldr r6, =0xa0000000
str r6, [r5, #0x83c]
#ifdef CONFIG_CACHE_L2X0
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
ldr r3, [r7, #PL310_AUX_CTRL]
tst r3, #PL310_AUX_16WAY_BIT
mov r6, #PL310_LOCKDOWN_NBREGS
mov r3, #0x00 /* 8 ways mask */
orrne r3, #0x0000 /* 16 ways mask */
add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE
1: /* lock Dcache and Icache */
str r3, [r5], #PL310_LOCKDOWN_SZREG
str r3, [r5], #PL310_LOCKDOWN_SZREG
subs r6, r6, #1
bne 1b
#endif
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
/* Restore registers */
pop {r2 - r8}
mov pc, lr

View File

@ -0,0 +1,492 @@
/*
* Copyright (C) 2014-2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include "hardware.h"
#define CCM_CBCDR 0x14
#define CCM_CBCMR 0x18
#define CCM_CSCMR1 0x1c
#define CCM_CDHIPR 0x48
#define L2_CACHE_SYNC 0x730
#define PL310_AUX_CTRL 0x104
#define PL310_DCACHE_LOCKDOWN_BASE 0x900
#define PL310_AUX_16WAY_BIT 0x10000
#define PL310_LOCKDOWN_NBREGS 8
#define PL310_LOCKDOWN_SZREG 4
#define PL310_8WAYS_MASK 0x00FF
#define PL310_16WAYS_UPPERMASK 0xFF00
#define MMDC0_MDPDC 0x4
#define MMDC0_MAPSR 0x404
#define MMDC0_MADPCR0 0x410
#define HIGH_BUS_MODE 0x0
/* Check if the cpu is cortex-a7 */
.macro is_ca7
/* Read the primary cpu number is MPIDR */
mrc p15, 0, r6, c0, c0, 0
ldr r7, =0xfff0
and r6, r6, r7
ldr r7, =0xc070
cmp r6, r7
.endm
.macro wait_for_ccm_handshake
1:
ldr r8, [r2, #CCM_CDHIPR]
cmp r8, #0
bne 1b
.endm
.macro switch_to_24MHz
/* periph2_clk2 sel to OSC_CLK */
ldr r8, [r2, #CCM_CBCMR]
orr r8, r8, #(1 << 20)
str r8, [r2, #CCM_CBCMR]
/* periph2_clk2_podf to 0 */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #0x7
str r8, [r2, #CCM_CBCDR]
/* periph2_clk sel to periph2_clk2 */
ldr r8, [r2, #CCM_CBCDR]
orr r8, r8, #(0x1 << 26)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/* fabric_mmdc_podf to 0 */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(0x7 << 3)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro switch_to_100MHz
/* check whether periph2_clk is from top path */
ldr r8, [r2, #CCM_CBCDR]
ands r8, #(1 << 26)
beq skip_periph2_clk2_switch_100m
/* now switch periph2_clk back. */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(1 << 26)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/*
* on i.MX6SX, pre_periph2_clk will be always from
* pll2_pfd2, so no need to set pre_periph2_clk
* parent, just set the mmdc divider directly.
*/
skip_periph2_clk2_switch_100m:
/* fabric_mmdc_podf to 3 so that mmdc is 400 / 4 = 100MHz */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(0x7 << 3)
orr r8, r8, #(0x3 << 3)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro switch_to_400MHz
/* check whether periph2_clk is from top path */
ldr r8, [r2, #CCM_CBCDR]
ands r8, #(1 << 26)
beq skip_periph2_clk2_switch_400m
/* now switch periph2_clk back. */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(1 << 26)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/*
* on i.MX6SX, pre_periph2_clk will be always from
* pll2_pfd2, so no need to set pre_periph2_clk
* parent, just set the mmdc divider directly.
*/
skip_periph2_clk2_switch_400m:
/* fabric_mmdc_podf to 0 */
ldr r8, [r2, #CCM_CBCDR]
bic r8, r8, #(0x7 << 3)
str r8, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro mmdc_clk_lower_100MHz
/* if MMDC is not in 400MHz mode, skip double mu count */
cmp r1, #HIGH_BUS_MODE
bne 1f
/*
* Prior to reducing the DDR frequency (at 528/400 MHz),
* read the Measure unit count bits (MU_UNIT_DEL_NUM)
*/
ldr r8, =0x8B8
ldr r6, [r5, r8]
/* Original MU unit count */
mov r6, r6, LSR #16
ldr r4, =0x3FF
and r6, r6, r4
/* Original MU unit count * 2 */
mov r7, r6, LSL #1
/*
* Bypass the automatic measure unit when below 100 MHz
* by setting the Measure unit bypass enable bit (MU_BYP_EN)
*/
ldr r6, [r5, r8]
orr r6, r6, #0x400
str r6, [r5, r8]
/*
* Double the measure count value read in step 1 and program it in the
* measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
* Register for the reduced frequency operation below 100 MHz
*/
ldr r6, [r5, r8]
ldr r4, =0x3FF
bic r6, r6, r4
orr r6, r6, r7
str r6, [r5, r8]
/* For freq lower than 100MHz, need to set RALAT to 2 */
ldr r6, [r5, #0x18]
bic r6, r6, #(0x7 << 6)
orr r6, r6, #(0x2 << 6)
str r6, [r5, #0x18]
1:
.endm
.macro mmdc_clk_above_100MHz
/* Make sure that the PHY measurement unit is NOT in bypass mode */
ldr r8, =0x8B8
ldr r6, [r5, r8]
bic r6, r6, #0x400
str r6, [r5, r8]
/* Now perform a Force Measurement. */
ldr r6, [r5, r8]
orr r6, r6, #0x800
str r6, [r5, r8]
/* Wait for FRC_MSR to clear. */
force_measure1:
ldr r6, [r5, r8]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure1
/* For freq higher than 100MHz, need to set RALAT to 5 */
ldr r6, [r5, #0x18]
bic r6, r6, #(0x7 << 6)
orr r6, r6, #(0x5 << 6)
str r6, [r5, #0x18]
.endm
.align 3
/*
* Below code can be used by i.MX6SX and i.MX6UL when changing the
* frequency of MMDC. the MMDC is the same on these two SOCs.
*/
ENTRY(imx6_up_lpddr2_freq_change)
push {r2 - r8}
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* 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
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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
/* Disable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
is_ca7
beq skip_disable_l2
#ifdef CONFIG_CACHE_L2X0
/*
* Need to make sure the buffers in L2 are drained.
* Performing a sync operation does this.
*/
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
mov r6, #0x0
str r6, [r7, #L2_CACHE_SYNC]
/*
* The second dsb might be needed to keep cache sync (device write)
* ordering with the memory accesses before it.
*/
dsb
isb
ldr r3, [r7, #PL310_AUX_CTRL]
tst r3, #PL310_AUX_16WAY_BIT
mov r3, #PL310_8WAYS_MASK
orrne r3, #PL310_16WAYS_UPPERMASK
mov r6, #PL310_LOCKDOWN_NBREGS
add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE
1: /* lock Dcache and Icache */
str r3, [r5], #PL310_LOCKDOWN_SZREG
str r3, [r5], #PL310_LOCKDOWN_SZREG
subs r6, r6, #1
bne 1b
#endif
skip_disable_l2:
ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR)
ldr r5, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
/* Disable Automatic power savings. */
ldr r6, [r5, #MMDC0_MAPSR]
orr r6, r6, #0x1
str r6, [r5, #MMDC0_MAPSR]
/* MMDC0_MDPDC disable power down timer */
ldr r6, [r5, #MMDC0_MDPDC]
bic r6, r6, #0xff00
str r6, [r5, #MMDC0_MDPDC]
/* Delay for a while */
ldr r8, =10
delay:
ldr r7, =0
cont:
ldr r6, [r5, r7]
add r7, r7, #4
cmp r7, #16
bne cont
sub r8, r8, #1
cmp r8, #0
bgt delay
/* Make the DDR explicitly enter self-refresh. */
ldr r6, [r5, #MMDC0_MAPSR]
orr r6, r6, #0x200000
str r6, [r5, #MMDC0_MAPSR]
poll_dvfs_set_1:
ldr r6, [r5, #MMDC0_MAPSR]
and r6, r6, #0x2000000
cmp r6, #0x2000000
bne poll_dvfs_set_1
/* set SBS step-by-step mode */
ldr r6, [r5, #MMDC0_MADPCR0]
orr r6, r6, #0x100
str r6, [r5, #MMDC0_MADPCR0]
ldr r6, =100000000
cmp r0, r6
bgt set_ddr_mu_above_100
mmdc_clk_lower_100MHz
set_ddr_mu_above_100:
ldr r6, =24000000
cmp r0, r6
beq set_to_24MHz
ldr r6, =100000000
cmp r0, r6
beq set_to_100MHz
switch_to_400MHz
mmdc_clk_above_100MHz
b done
set_to_24MHz:
switch_to_24MHz
b done
set_to_100MHz:
switch_to_100MHz
done:
/* clear DVFS - exit from self refresh mode */
ldr r6, [r5, #MMDC0_MAPSR]
bic r6, r6, #0x200000
str r6, [r5, #MMDC0_MAPSR]
poll_dvfs_clear_1:
ldr r6, [r5, #MMDC0_MAPSR]
and r6, r6, #0x2000000
cmp r6, #0x2000000
beq poll_dvfs_clear_1
/* Enable Automatic power savings. */
ldr r6, [r5, #MMDC0_MAPSR]
bic r6, r6, #0x1
str r6, [r5, #MMDC0_MAPSR]
ldr r6, =24000000
cmp r0, r6
beq skip_power_down
/* Enable MMDC power down timer. */
ldr r6, [r5, #MMDC0_MDPDC]
orr r6, r6, #0x5500
str r6, [r5, #MMDC0_MDPDC]
skip_power_down:
/* clear SBS - unblock DDR accesses */
ldr r6, [r5, #MMDC0_MADPCR0]
bic r6, r6, #0x100
str r6, [r5, #MMDC0_MADPCR0]
is_ca7
beq skip_enable_l2
#ifdef CONFIG_CACHE_L2X0
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
ldr r3, [r7, #PL310_AUX_CTRL]
tst r3, #PL310_AUX_16WAY_BIT
mov r6, #PL310_LOCKDOWN_NBREGS
mov r3, #0x00 /* 8 ways mask */
orrne r3, #0x0000 /* 16 ways mask */
add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE
1: /* lock Dcache and Icache */
str r3, [r5], #PL310_LOCKDOWN_SZREG
str r3, [r5], #PL310_LOCKDOWN_SZREG
subs r6, r6, #1
bne 1b
#endif
skip_enable_l2:
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
/* Restore registers */
pop {r2 - r8}
mov pc, lr

View File

@ -0,0 +1,444 @@
/*
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include "hardware.h"
#define DDRC_MSTR 0x0
#define DDRC_STAT 0x4
#define DDRC_PWRCTL 0x30
#define DDRC_RFSHTMG 0x64
#define DDRC_DBG1 0x304
#define DDRC_PSTAT 0x3fc
#define DDRC_PCTRL_0 0x490
#define DDRC_DFIMISC 0x1b0
#define DDRC_DBGCAM 0x308
#define DDRC_SWCTL 0x320
#define DDRC_SWSTAT 0x324
#define DDRPHY_LP_CON0 0x18
#define IOMUXC_GPR8 0x20
#define DDRPHY_PHY_CON1 0x4
#define DDRPHY_MDLL_CON0 0xb0
#define DDRPHY_MDLL_CON1 0xb4
#define DDRPHY_OFFSETD_CON0 0x50
#define DDRPHY_OFFSETR_CON0 0x20
#define DDRPHY_OFFSETR_CON1 0x24
#define DDRPHY_OFFSETR_CON2 0x28
#define DDRPHY_OFFSETW_CON0 0x30
#define DDRPHY_OFFSETW_CON1 0x34
#define DDRPHY_OFFSETW_CON2 0x38
#define DDRPHY_RFSHTMG 0x64
#define DDRPHY_CA_WLDSKEW_CON0 0x6c
#define DDRPHY_CA_DSKEW_CON0 0x7c
#define DDRPHY_CA_DSKEW_CON1 0x80
#define DDRPHY_CA_DSKEW_CON2 0x84
#define ANADIG_DIGPROG 0x800
.align 3
.macro ddrc_prepare
/* disable port */
ldr r7, =0x0
str r7, [r4, #DDRC_PCTRL_0]
/* wait port busy done */
ldr r6, =0x10001
1:
ldr r7, [r4, #DDRC_PSTAT]
and r7, r7, r6
cmp r7, #0
bne 1b
ldr r7, =0x20
str r7, [r4, #DDRC_PWRCTL]
ldr r6, =0x23
2:
ldr r7, [r4, #DDRC_STAT]
and r7, r7, r6
cmp r7, r6
bne 2b
ldr r7, =0x1
str r7, [r4, #DDRC_DBG1]
ldr r6, =0x30000000
3:
ldr r7, [r4, #DDRC_DBGCAM]
and r7, r7, r6
cmp r7, r6
bne 3b
ldr r7, =0x0
str r7, [r4, #DDRC_SWCTL]
ldr r7, =0x0
str r7, [r4, #DDRC_DFIMISC]
ldr r7, =0x1
str r7, [r4, #DDRC_SWCTL]
ldr r6, =0x1
4:
ldr r7, [r4, #DDRC_SWSTAT]
and r7, r7, r6
cmp r7, r6
bne 4b
.endm
.macro ddrc_done
ldr r7, =0x0
str r7, [r4, #DDRC_PWRCTL]
ldr r6, =0x3
5:
ldr r7, [r4, #DDRC_STAT]
and r7, r7, r6
cmp r7, r6
beq 5b
ldr r7, =0x0
str r7, [r4, #DDRC_DBG1]
ldr r7, =0x1
str r7, [r4, #DDRC_PCTRL_0]
/* enable auto self-refresh */
ldr r7, [r4, #DDRC_PWRCTL]
orr r7, r7, #(1 << 0)
str r7, [r4, #DDRC_PWRCTL]
.endm
.macro switch_to_below_100m
/* LPDDR2 and LPDDR3 has different setting */
ldr r8, [r4, #DDRC_MSTR]
ands r8, r8, #0x4
bne 9f
/* LPDDR3 */
ldr r7, =0x00000100
str r7, [r5, #DDRPHY_PHY_CON1]
b 10f
9:
/* LPDDR2 */
ldr r7, =0x10010100
str r7, [r5, #DDRPHY_PHY_CON1]
10:
ldr r6, =24000000
cmp r0, r6
beq 16f
ldr r7, =0x0005000B
str r7, [r4, #DDRC_RFSHTMG]
b 6f
16:
ldr r7, =0x00010003
str r7, [r4, #DDRC_RFSHTMG]
/* dram alt sel set to OSC */
ldr r7, =0x10000000
ldr r8, =0xa080
str r7, [r2, r8]
/* dram root set to from dram alt, div by 1 */
ldr r7, =0x11000000
ldr r8, =0x9880
str r7, [r2, r8]
b 7f
6:
/* dram alt sel set to pfd0_392m */
ldr r7, =0x15000000
ldr r8, =0xa080
str r7, [r2, r8]
/* dram root set to from dram alt, div by 4 */
ldr r7, =0x11000003
ldr r8, =0x9880
str r7, [r2, r8]
7:
ldr r7, =0x202ffd0
str r7, [r5, #DDRPHY_MDLL_CON0]
ldr r7, =0x7f
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x7f7f7f7f
str r7, [r5, #DDRPHY_OFFSETR_CON0]
str r7, [r5, #DDRPHY_OFFSETR_CON1]
ldr r7, =0x7f
str r7, [r5, #DDRPHY_OFFSETR_CON2]
ldr r7, =0x7f7f7f7f
str r7, [r5, #DDRPHY_OFFSETW_CON0]
str r7, [r5, #DDRPHY_OFFSETW_CON1]
ldr r7, =0x7f
str r7, [r5, #DDRPHY_OFFSETW_CON2]
ldr r7, [r9, #ANADIG_DIGPROG]
and r7, r7, #0x11
cmp r7, #0x11
bne 11f
ldr r7, =0x0
str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0]
ldr r7, =0x60606060
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
ldr r7, =0x00006060
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
b 12f
11:
ldr r7, =0x0
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
12:
ldr r7, =0x100007f
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x7f
str r7, [r5, #DDRPHY_OFFSETD_CON0]
.endm
.macro switch_to_533m
ldr r7, =0x10210100
str r7, [r5, #DDRPHY_PHY_CON1]
ldr r7, =0x00200038
str r7, [r4, #DDRC_RFSHTMG]
/* dram root set to from dram main, div by 2 */
ldr r7, =0x10000001
ldr r8, =0x9880
str r7, [r2, r8]
ldr r7, =0x1010007e
str r7, [r5, #DDRPHY_MDLL_CON0]
ldr r7, =0x10000008
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x08080808
str r7, [r5, #DDRPHY_OFFSETR_CON0]
str r7, [r5, #DDRPHY_OFFSETR_CON1]
ldr r7, =0x8
str r7, [r5, #DDRPHY_OFFSETR_CON2]
ldr r7, =0x08080808
str r7, [r5, #DDRPHY_OFFSETW_CON0]
str r7, [r5, #DDRPHY_OFFSETW_CON1]
ldr r7, =0x8
str r7, [r5, #DDRPHY_OFFSETW_CON2]
/* LPDDR2 and LPDDR3 has different setting */
ldr r8, [r4, #DDRC_MSTR]
ands r8, r8, #0x4
beq 15f
ldr r7, [r9, #ANADIG_DIGPROG]
and r7, r7, #0x11
cmp r7, #0x11
bne 14f
ldr r7, =0x08080808
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
ldr r7, =0x0a0a0808
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
ldr r7, =0x0a0a0a0a
str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0]
b 14f
15:
ldr r7, [r9, #ANADIG_DIGPROG]
and r7, r7, #0x11
cmp r7, #0x11
bne 13f
ldr r7, =0x1c1c1c1c
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
ldr r7, =0x30301c1c
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
ldr r7, =0x30303030
str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0]
b 14f
13:
ldr r7, =0x08080808
str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
ldr r7, =0x0808
str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
14:
ldr r7, =0x11000008
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r7, =0x10000008
str r7, [r5, #DDRPHY_OFFSETD_CON0]
ldr r6, =0x4
8:
ldr r7, [r5, #DDRPHY_MDLL_CON1]
and r7, r7, r6
cmp r7, r6
bne 8b
.endm
ENTRY(imx_lpddr3_freq_change)
push {r2 - r9}
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* 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
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, 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
/* Disable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
ldr r2, =IMX_IO_P2V(MX7D_CCM_BASE_ADDR)
ldr r3, =IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR)
ldr r4, =IMX_IO_P2V(MX7D_DDRC_BASE_ADDR)
ldr r5, =IMX_IO_P2V(MX7D_DDRC_PHY_BASE_ADDR)
ldr r9, =IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR)
ddrc_prepare
ldr r6, =100000000
cmp r0, r6
bgt set_to_533m
set_to_below_100m:
switch_to_below_100m
b done
set_to_533m:
switch_to_533m
b done
done:
ddrc_done
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
/* Restore the TTBCR */
dsb
isb
/* 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
dsb
isb
/* 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
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
/* Restore registers */
pop {r2 - r9}
mov pc, lr
ENDPROC(imx_lpddr3_freq_change)

View File

@ -106,6 +106,18 @@ static int ar8031_phy_fixup(struct phy_device *dev)
{
u16 val;
/* Set RGMII IO voltage to 1.8V */
phy_write(dev, 0x1d, 0x1f);
phy_write(dev, 0x1e, 0x8);
/* disable phy AR8031 SmartEEE function. */
phy_write(dev, 0xd, 0x3);
phy_write(dev, 0xe, 0x805d);
phy_write(dev, 0xd, 0x4003);
val = phy_read(dev, 0xe);
val &= ~(0x1 << 8);
phy_write(dev, 0xe, val);
/* To enable AR8031 output a 125MHz clk from CLK_25M */
phy_write(dev, 0xd, 0x7);
phy_write(dev, 0xe, 0x8016);
@ -223,6 +235,58 @@ put_node:
of_node_put(np);
}
static void __init imx6q_csi_mux_init(void)
{
/*
* MX6Q SabreSD board:
* IPU1 CSI0 connects to parallel interface.
* Set GPR1 bit 19 to 0x1.
*
* MX6DL SabreSD board:
* IPU1 CSI0 connects to parallel interface.
* Set GPR13 bit 0-2 to 0x4.
* IPU1 CSI1 connects to MIPI CSI2 virtual channel 1.
* Set GPR13 bit 3-5 to 0x1.
*/
struct regmap *gpr;
gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr");
if (!IS_ERR(gpr)) {
if (of_machine_is_compatible("fsl,imx6q-sabresd") ||
of_machine_is_compatible("fsl,imx6q-sabreauto") ||
of_machine_is_compatible("fsl,imx6qp-sabresd") ||
of_machine_is_compatible("fsl,imx6qp-sabreauto"))
regmap_update_bits(gpr, IOMUXC_GPR1, 1 << 19, 1 << 19);
else if (of_machine_is_compatible("fsl,imx6dl-sabresd") ||
of_machine_is_compatible("fsl,imx6dl-sabreauto"))
regmap_update_bits(gpr, IOMUXC_GPR13, 0x3F, 0x0C);
} else {
pr_err("%s(): failed to find fsl,imx6q-iomux-gpr regmap\n",
__func__);
}
}
static void __init imx6q_enet_clk_sel(void)
{
struct regmap *gpr;
gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr");
if (!IS_ERR(gpr))
regmap_update_bits(gpr, IOMUXC_GPR5,
IMX6Q_GPR5_ENET_TX_CLK_SEL, IMX6Q_GPR5_ENET_TX_CLK_SEL);
else
pr_err("failed to find fsl,imx6q-iomux-gpr regmap\n");
}
static inline void imx6q_enet_init(void)
{
imx6_enet_mac_init("fsl,imx6q-fec", "fsl,imx6q-ocotp");
imx6q_enet_phy_init();
imx6q_1588_init();
if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0)
imx6q_enet_clk_sel();
}
static void __init imx6q_axi_init(void)
{
struct regmap *gpr;
@ -270,13 +334,12 @@ static void __init imx6q_init_machine(void)
if (parent == NULL)
pr_warn("failed to initialize soc device\n");
imx6q_enet_phy_init();
of_platform_default_populate(NULL, NULL, parent);
imx_anatop_init();
imx6q_enet_init();
imx6q_csi_mux_init();
cpu_is_imx6q() ? imx6q_pm_init() : imx6dl_pm_init();
imx6q_1588_init();
imx6q_axi_init();
}
@ -300,6 +363,8 @@ static void __init imx6q_map_io(void)
{
debug_ll_io_init();
imx_scu_map_io();
imx6_pm_map_io();
imx_busfreq_map_io();
}
static void __init imx6q_init_irq(void)

View File

@ -16,7 +16,7 @@
#include "cpuidle.h"
#include "hardware.h"
static void __init imx6sl_fec_init(void)
static void __init imx6sl_fec_clk_init(void)
{
struct regmap *gpr;
@ -32,6 +32,12 @@ static void __init imx6sl_fec_init(void)
}
}
static inline void imx6sl_fec_init(void)
{
imx6sl_fec_clk_init();
imx6_enet_mac_init("fsl,imx6sl-fec", "fsl,imx6sl-ocotp");
}
static void __init imx6sl_init_late(void)
{
/* imx6sl reuses imx6q cpufreq driver */
@ -41,7 +47,7 @@ static void __init imx6sl_init_late(void)
if (IS_ENABLED(CONFIG_SOC_IMX6SL) && cpu_is_imx6sl())
imx6sl_cpuidle_init();
else if (IS_ENABLED(CONFIG_SOC_IMX6SLL))
imx6sx_cpuidle_init();
imx6sll_cpuidle_init();
}
static void __init imx6sl_init_machine(void)
@ -54,6 +60,7 @@ static void __init imx6sl_init_machine(void)
of_platform_default_populate(NULL, NULL, parent);
imx_anatop_init();
if (cpu_is_imx6sl())
imx6sl_fec_init();
imx_anatop_init();
@ -73,6 +80,14 @@ static void __init imx6sl_init_irq(void)
imx6_pm_ccm_init("fsl,imx6sll-ccm");
}
static void __init imx6sl_map_io(void)
{
imx6_pm_map_io();
#ifdef CONFIG_CPU_FREQ
imx_busfreq_map_io();
#endif
}
static const char * const imx6sl_dt_compat[] __initconst = {
"fsl,imx6sl",
"fsl,imx6sll",
@ -82,6 +97,7 @@ static const char * const imx6sl_dt_compat[] __initconst = {
DT_MACHINE_START(IMX6SL, "Freescale i.MX6 SoloLite (Device Tree)")
.l2c_aux_val = 0,
.l2c_aux_mask = ~0,
.map_io = imx6sl_map_io,
.init_irq = imx6sl_init_irq,
.init_machine = imx6sl_init_machine,
.init_late = imx6sl_init_late,

View File

@ -23,6 +23,14 @@ static int ar8031_phy_fixup(struct phy_device *dev)
phy_write(dev, 0x1d, 0x1f);
phy_write(dev, 0x1e, 0x8);
/* disable phy AR8031 SmartEEE function. */
phy_write(dev, 0xd, 0x3);
phy_write(dev, 0xe, 0x805d);
phy_write(dev, 0xd, 0x4003);
val = phy_read(dev, 0xe);
val &= ~(0x1 << 8);
phy_write(dev, 0xe, val);
/* introduce tx clock delay */
phy_write(dev, 0x1d, 0x5);
val = phy_read(dev, 0x1e);
@ -57,6 +65,7 @@ static void __init imx6sx_enet_clk_sel(void)
static inline void imx6sx_enet_init(void)
{
imx6_enet_mac_init("fsl,imx6sx-fec", "fsl,imx6sx-ocotp");
imx6sx_enet_phy_init();
imx6sx_enet_clk_sel();
}
@ -71,6 +80,7 @@ static void __init imx6sx_init_machine(void)
of_platform_default_populate(NULL, NULL, parent);
imx_anatop_init();
imx6sx_enet_init();
imx_anatop_init();
imx6sx_pm_init();
@ -86,6 +96,13 @@ static void __init imx6sx_init_irq(void)
imx6_pm_ccm_init("fsl,imx6sx-ccm");
}
static void __init imx6sx_map_io(void)
{
debug_ll_io_init();
imx6_pm_map_io();
imx_busfreq_map_io();
}
static void __init imx6sx_init_late(void)
{
imx6sx_cpuidle_init();
@ -102,6 +119,7 @@ static const char * const imx6sx_dt_compat[] __initconst = {
DT_MACHINE_START(IMX6SX, "Freescale i.MX6 SoloX (Device Tree)")
.l2c_aux_val = 0,
.l2c_aux_mask = ~0,
.map_io = imx6sx_map_io,
.init_irq = imx6sx_init_irq,
.init_machine = imx6sx_init_machine,
.dt_compat = imx6sx_dt_compat,

View File

@ -52,6 +52,7 @@ static inline void imx6ul_enet_init(void)
{
imx6ul_enet_clk_init();
imx6ul_enet_phy_init();
imx6_enet_mac_init("fsl,imx6ul-fec", "fsl,imx6ul-ocotp");
}
static void __init imx6ul_init_machine(void)
@ -63,6 +64,7 @@ static void __init imx6ul_init_machine(void)
pr_warn("failed to initialize soc device\n");
of_platform_default_populate(NULL, NULL, parent);
imx_anatop_init();
imx6ul_enet_init();
imx_anatop_init();
imx6ul_pm_init();
@ -78,12 +80,18 @@ static void __init imx6ul_init_irq(void)
static void __init imx6ul_init_late(void)
{
imx6sx_cpuidle_init();
imx6ul_cpuidle_init();
if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ))
platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0);
}
static void __init imx6ul_map_io(void)
{
imx6_pm_map_io();
imx_busfreq_map_io();
}
static const char * const imx6ul_dt_compat[] __initconst = {
"fsl,imx6ul",
"fsl,imx6ull",
@ -91,6 +99,7 @@ static const char * const imx6ul_dt_compat[] __initconst = {
};
DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,

View File

@ -13,6 +13,13 @@
#include <asm/mach/map.h>
#include "common.h"
#include "cpuidle.h"
static struct property device_disabled = {
.name = "status",
.length = sizeof("disabled"),
.value = "disabled",
};
static int ar8031_phy_fixup(struct phy_device *dev)
{
@ -57,6 +64,23 @@ static void __init imx7d_enet_phy_init(void)
}
}
static void __init imx7d_enet_mdio_fixup(void)
{
struct regmap *gpr;
/* The management data input/output (MDIO) bus where often high-speed,
* open-drain operation is required. i.MX7D TO1.0 ENET MDIO pin has no
* open drain as IC ticket number: TKT252980, i.MX7D TO1.1 fix the issue.
* GPR1[8:7] are reserved bits at TO1.0, there no need to add version check.
*/
gpr = syscon_regmap_lookup_by_compatible("fsl,imx7d-iomuxc-gpr");
if (!IS_ERR(gpr))
regmap_update_bits(gpr, IOMUXC_GPR0, IMX7D_GPR0_ENET_MDIO_OPEN_DRAIN_MASK,
IMX7D_GPR0_ENET_MDIO_OPEN_DRAIN_MASK);
else
pr_err("failed to find fsl,imx7d-iomux-gpr regmap\n");
}
static void __init imx7d_enet_clk_sel(void)
{
struct regmap *gpr;
@ -72,10 +96,23 @@ static void __init imx7d_enet_clk_sel(void)
static inline void imx7d_enet_init(void)
{
imx6_enet_mac_init("fsl,imx7d-fec", "fsl,imx7d-ocotp");
imx7d_enet_mdio_fixup();
imx7d_enet_phy_init();
imx7d_enet_clk_sel();
}
static inline void imx7d_disable_arm_arch_timer(void)
{
struct device_node *node;
node = of_find_compatible_node(NULL, NULL, "arm,armv7-timer");
if (node) {
pr_info("disable arm arch timer for nosmp!\n");
of_add_property(node, &device_disabled);
}
}
static void __init imx7d_init_machine(void)
{
struct device *parent;
@ -84,21 +121,36 @@ static void __init imx7d_init_machine(void)
if (parent == NULL)
pr_warn("failed to initialize soc device\n");
imx_anatop_init();
of_platform_default_populate(NULL, NULL, parent);
imx7d_pm_init();
imx_anatop_init();
imx7d_enet_init();
}
static void __init imx7d_init_late(void)
{
imx7d_cpuidle_init();
if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT))
platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0);
}
static void __init imx7d_init_irq(void)
{
imx_gpcv2_check_dt();
imx_init_revision_from_anatop();
imx_src_init();
irqchip_init();
#ifndef CONFIG_SMP
imx7d_disable_arm_arch_timer();
#endif
}
static void __init imx7d_map_io(void)
{
debug_ll_io_init();
imx7_pm_map_io();
imx_busfreq_map_io();
}
static const char *const imx7d_dt_compat[] __initconst = {
@ -108,6 +160,8 @@ static const char *const imx7d_dt_compat[] __initconst = {
};
DT_MACHINE_START(IMX7D, "Freescale i.MX7 Dual (Device Tree)")
.map_io = imx7d_map_io,
.smp = smp_ops(imx_smp_ops),
.init_irq = imx7d_init_irq,
.init_machine = imx7d_init_machine,
.init_late = imx7d_init_late,

View File

@ -10,6 +10,7 @@
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include "common.h"
#include "cpuidle.h"
@ -17,6 +18,15 @@
#define SIM_JTAG_ID_REG 0x8c
/* static IO mapping, and ioremap() could always share the same mapping. */
static struct map_desc mx7ulp_io_desc[] __initdata = {
mx7ulp_aips_map_entry(1, MT_DEVICE),
mx7ulp_aips_map_entry(2, MT_DEVICE),
mx7ulp_aips_map_entry(3, MT_DEVICE),
mx7ulp_aips_map_entry(4, MT_DEVICE),
mx7ulp_aips_map_entry(5, MT_DEVICE),
};
static void __init imx7ulp_set_revision(void)
{
struct regmap *sim;
@ -65,12 +75,23 @@ static const char *const imx7ulp_dt_compat[] __initconst = {
NULL,
};
static void __init imx7ulp_map_io(void)
{
iotable_init(mx7ulp_io_desc, ARRAY_SIZE(mx7ulp_io_desc));
imx7ulp_pm_map_io();
}
static void __init imx7ulp_init_late(void)
{
if (IS_ENABLED(CONFIG_ARM_IMX7ULP_CPUFREQ))
platform_device_register_simple("imx7ulp-cpufreq", -1, NULL, 0);
imx7ulp_cpuidle_init();
imx7ulp_enable_nmi();
}
DT_MACHINE_START(IMX7ulp, "Freescale i.MX7ULP (Device Tree)")
.map_io = imx7ulp_map_io,
.init_machine = imx7ulp_init_machine,
.dt_compat = imx7ulp_dt_compat,
.init_late = imx7ulp_init_late,

View File

@ -59,6 +59,7 @@
#define to_mmdc_pmu(p) container_of(p, struct mmdc_pmu, pmu)
static int ddr_type;
static int lpddr2_2ch_mode;
struct fsl_mmdc_devtype_data {
unsigned int flags;
@ -575,6 +576,11 @@ int imx_mmdc_get_ddr_type(void)
return ddr_type;
}
int imx_mmdc_get_lpddr2_2ch_mode(void)
{
return lpddr2_2ch_mode;
}
static struct platform_driver imx_mmdc_driver = {
.driver = {
.name = "imx-mmdc",

View File

@ -0,0 +1,434 @@
/*
* Copyright (C) 2014-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/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include "common.h"
#include "hardware.h"
#define MU_ATR0_OFFSET 0x0
#define MU_ARR0_OFFSET 0x10
#define MU_ARR1_OFFSET 0x14
#define MU_ASR 0x20
#define MU_ACR 0x24
#define MX7ULP_MU_TR0 0x20
#define MX7ULP_MU_RR0 0x40
#define MX7ULP_MU_RR1 0x44
#define MX7ULP_MU_SR 0x60
#define MX7ULP_MU_CR 0x64
#define MU_LPM_HANDSHAKE_INDEX 0
#define MU_RPMSG_HANDSHAKE_INDEX 1
#define MU_LPM_BUS_HIGH_READY_FOR_M4 0xFFFF6666
#define MU_LPM_M4_FREQ_CHANGE_READY 0xFFFF7777
#define MU_LPM_M4_REQUEST_HIGH_BUS 0x2222CCCC
#define MU_LPM_M4_RELEASE_HIGH_BUS 0x2222BBBB
#define MU_LPM_M4_WAKEUP_SRC_VAL 0x55555000
#define MU_LPM_M4_WAKEUP_SRC_MASK 0xFFFFF000
#define MU_LPM_M4_WAKEUP_IRQ_MASK 0xFF0
#define MU_LPM_M4_WAKEUP_IRQ_SHIFT 0x4
#define MU_LPM_M4_WAKEUP_ENABLE_MASK 0xF
#define MU_LPM_M4_WAKEUP_ENABLE_SHIFT 0x0
#define MU_LPM_M4_RUN_MODE 0x5A5A0001
#define MU_LPM_M4_WAIT_MODE 0x5A5A0002
#define MU_LPM_M4_STOP_MODE 0x5A5A0003
#define MAX_NUM 10 /* enlarge it if overflow happen */
static void __iomem *mu_base;
static u32 m4_message[MAX_NUM];
static u32 in_idx, out_idx;
static struct delayed_work mu_work;
static u32 m4_wake_irqs[4];
static bool m4_freq_low;
struct irq_domain *domain;
static bool m4_in_stop;
static struct clk *clk;
static DEFINE_SPINLOCK(mu_lock);
void imx_mu_set_m4_run_mode(void)
{
m4_in_stop = false;
}
bool imx_mu_is_m4_in_stop(void)
{
return m4_in_stop;
}
bool imx_mu_is_m4_in_low_freq(void)
{
return m4_freq_low;
}
void imx_mu_enable_m4_irqs_in_gic(bool enable)
{
int i, j;
for (i = 0; i < 4; i++) {
if (m4_wake_irqs[i] == 0)
continue;
for (j = 0; j < 32; j++) {
if (m4_wake_irqs[i] & (1 << j)) {
if (enable)
enable_irq(irq_find_mapping(
domain, i * 32 + j));
else
disable_irq(irq_find_mapping(
domain, i * 32 + j));
}
}
}
}
static irqreturn_t mcc_m4_dummy_isr(int irq, void *param)
{
return IRQ_HANDLED;
}
static int imx_mu_send_message(unsigned int index, unsigned int data)
{
u32 val, ep;
int i, te_flag = 0;
unsigned long timeout = jiffies + msecs_to_jiffies(500);
/* wait for transfer buffer empty, and no event pending */
do {
if (cpu_is_imx7ulp())
val = readl_relaxed(mu_base + MX7ULP_MU_SR);
else
val = readl_relaxed(mu_base + MU_ASR);
ep = val & BIT(4);
if (time_after(jiffies, timeout)) {
pr_err("Waiting MU transmit buffer empty timeout!\n");
return -EIO;
}
} while (((val & (1 << (20 + 3 - index))) == 0) || (ep == BIT(4)));
if (cpu_is_imx7ulp())
writel_relaxed(data, mu_base + index * 0x4 + MX7ULP_MU_TR0);
else
writel_relaxed(data, mu_base + index * 0x4 + MU_ATR0_OFFSET);
/*
* make a double check that TEn is not empty after write
*/
if (cpu_is_imx7ulp())
val = readl_relaxed(mu_base + MX7ULP_MU_SR);
else
val = readl_relaxed(mu_base + MU_ASR);
ep = val & BIT(4);
if (((val & (1 << (20 + (3 - index)))) == 0) || (ep == BIT(4)))
return 0;
else
te_flag = 1;
/*
* Make sure that TEn flag is changed, after the ATRn is filled up.
*/
for (i = 0; i < 100; i++) {
if (cpu_is_imx7ulp())
val = readl_relaxed(mu_base + MX7ULP_MU_SR);
else
val = readl_relaxed(mu_base + MU_ASR);
ep = val & BIT(4);
if (((val & (1 << (20 + 3 - index))) == 0) || (ep == BIT(4))) {
/*
* BUG here. TEn flag is changes, after the
* ATRn is filled with MSG for a while.
*/
te_flag = 0;
break;
} else if (time_after(jiffies, timeout)) {
/* Can't see TEn 1->0, maybe already handled! */
te_flag = 1;
break;
}
}
if (te_flag == 0)
pr_info("BUG: TEn is not changed immediately"
"when ATRn is filled up.\n");
return 0;
}
static void mu_work_handler(struct work_struct *work)
{
int ret;
u32 irq, enable, idx, mask, virq;
struct of_phandle_args args;
u32 message;
unsigned long flags;
spin_lock_irqsave(&mu_lock, flags);
message = m4_message[out_idx % MAX_NUM];
spin_unlock_irqrestore(&mu_lock, flags);
pr_debug("receive M4 message 0x%x\n", message);
switch (message) {
case MU_LPM_M4_RUN_MODE:
case MU_LPM_M4_WAIT_MODE:
m4_in_stop = false;
break;
case MU_LPM_M4_STOP_MODE:
m4_in_stop = true;
break;
case MU_LPM_M4_REQUEST_HIGH_BUS:
request_bus_freq(BUS_FREQ_HIGH);
#ifdef CONFIG_SOC_IMX6SX
if (cpu_is_imx6sx())
imx6sx_set_m4_highfreq(true);
#endif
imx_mu_send_message(MU_LPM_HANDSHAKE_INDEX,
MU_LPM_BUS_HIGH_READY_FOR_M4);
m4_freq_low = false;
break;
case MU_LPM_M4_RELEASE_HIGH_BUS:
release_bus_freq(BUS_FREQ_HIGH);
#ifdef CONFIG_SOC_IMX6SX
if (cpu_is_imx6sx()) {
imx6sx_set_m4_highfreq(false);
imx_mu_send_message(MU_LPM_HANDSHAKE_INDEX,
MU_LPM_M4_FREQ_CHANGE_READY);
}
#endif
m4_freq_low = true;
break;
default:
if ((message & MU_LPM_M4_WAKEUP_SRC_MASK) ==
MU_LPM_M4_WAKEUP_SRC_VAL) {
irq = (message & MU_LPM_M4_WAKEUP_IRQ_MASK) >>
MU_LPM_M4_WAKEUP_IRQ_SHIFT;
enable = (message & MU_LPM_M4_WAKEUP_ENABLE_MASK) >>
MU_LPM_M4_WAKEUP_ENABLE_SHIFT;
/* to hwirq start from 0 */
irq -= 32;
idx = irq / 32;
mask = 1 << irq % 32;
args.np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-gpc");
args.args_count = 3;
args.args[0] = 0;
args.args[1] = irq;
args.args[2] = IRQ_TYPE_LEVEL_HIGH;
virq = irq_create_of_mapping(&args);
if (enable && can_request_irq(virq, 0)) {
ret = request_irq(virq, mcc_m4_dummy_isr,
IRQF_NO_SUSPEND, "imx-m4-dummy", NULL);
if (ret) {
pr_err("%s: register interrupt %d failed, rc %d\n",
__func__, virq, ret);
break;
}
disable_irq(virq);
m4_wake_irqs[idx] = m4_wake_irqs[idx] | mask;
}
imx_gpc_add_m4_wake_up_irq(irq, enable);
}
break;
}
spin_lock_irqsave(&mu_lock, flags);
m4_message[out_idx % MAX_NUM] = 0;
out_idx++;
spin_unlock_irqrestore(&mu_lock, flags);
/* enable RIE3 interrupt */
if (cpu_is_imx7ulp())
writel_relaxed(readl_relaxed(mu_base + MX7ULP_MU_CR) | BIT(27),
mu_base + MX7ULP_MU_CR);
else
writel_relaxed(readl_relaxed(mu_base + MU_ACR) | BIT(27),
mu_base + MU_ACR);
}
int imx_mu_lpm_ready(bool ready)
{
u32 val;
if (cpu_is_imx7ulp()) {
val = readl_relaxed(mu_base + MX7ULP_MU_CR);
if (ready)
writel_relaxed(val | BIT(0), mu_base + MX7ULP_MU_CR);
else
writel_relaxed(val & ~BIT(0), mu_base + MX7ULP_MU_CR);
} else {
val = readl_relaxed(mu_base + MU_ACR);
if (ready)
writel_relaxed(val | BIT(0), mu_base + MU_ACR);
else
writel_relaxed(val & ~BIT(0), mu_base + MU_ACR);
}
return 0;
}
static irqreturn_t imx_mu_isr(int irq, void *param)
{
u32 irqs;
unsigned long flags;
if (cpu_is_imx7ulp())
irqs = readl_relaxed(mu_base + MX7ULP_MU_SR);
else
irqs = readl_relaxed(mu_base + MU_ASR);
if (irqs & (1 << 27)) {
spin_lock_irqsave(&mu_lock, flags);
/* get message from receive buffer */
if (cpu_is_imx7ulp())
m4_message[in_idx % MAX_NUM] = readl_relaxed(mu_base +
MX7ULP_MU_RR0);
else
m4_message[in_idx % MAX_NUM] = readl_relaxed(mu_base +
MU_ARR0_OFFSET);
/* disable RIE3 interrupt */
if (cpu_is_imx7ulp())
writel_relaxed(readl_relaxed(mu_base + MX7ULP_MU_CR)
& (~BIT(27)), mu_base + MX7ULP_MU_CR);
else
writel_relaxed(readl_relaxed(mu_base + MU_ACR)
& (~BIT(27)), mu_base + MU_ACR);
in_idx++;
if (in_idx == out_idx) {
spin_unlock_irqrestore(&mu_lock, flags);
pr_err("MU overflow!\n");
return IRQ_HANDLED;
}
spin_unlock_irqrestore(&mu_lock, flags);
schedule_delayed_work(&mu_work, 0);
}
return IRQ_HANDLED;
}
static int imx_mu_probe(struct platform_device *pdev)
{
int ret;
u32 irq;
struct device_node *np;
struct device *dev = &pdev->dev;
np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-mu-lp");
mu_base = of_iomap(np, 0);
WARN_ON(!mu_base);
ret = of_device_is_compatible(np, "fsl,imx7ulp-mu-lp");
if (ret)
irq = platform_get_irq(pdev, 1);
else
irq = platform_get_irq(pdev, 0);
ret = request_irq(irq, imx_mu_isr,
IRQF_NO_SUSPEND | IRQF_SHARED, "imx-mu-lp", dev);
if (ret) {
pr_err("%s: register interrupt %d failed, rc %d\n",
__func__, irq, ret);
return ret;
}
ret = of_device_is_compatible(np, "fsl,imx7d-mu-lp");
if (ret) {
clk = devm_clk_get(&pdev->dev, "mu");
if (IS_ERR(clk)) {
dev_err(&pdev->dev,
"mu clock source missing or invalid\n");
return PTR_ERR(clk);
} else {
ret = clk_prepare_enable(clk);
if (ret) {
dev_err(&pdev->dev,
"unable to enable mu clock\n");
return ret;
}
}
/* MU always as a wakeup source for low power mode */
imx_gpcv2_add_m4_wake_up_irq(irq_to_desc(irq)->irq_data.hwirq,
true);
} else {
/* MU always as a wakeup source for low power mode */
imx_gpc_add_m4_wake_up_irq(irq_to_desc(irq)->irq_data.hwirq, true);
}
INIT_DELAYED_WORK(&mu_work, mu_work_handler);
/* bit0 of MX7ULP_MU_CR used to let m4 to know MU is ready now */
if (cpu_is_imx7ulp())
writel_relaxed(readl_relaxed(mu_base + MX7ULP_MU_CR) |
BIT(0) | BIT(26) | BIT(27), mu_base + MX7ULP_MU_CR);
else
writel_relaxed(readl_relaxed(mu_base + MU_ACR) |
BIT(26) | BIT(27), mu_base + MU_ACR);
pr_info("MU is ready for cross core communication!\n");
return 0;
}
static const struct of_device_id imx_mu_ids[] = {
{ .compatible = "fsl,imx6sx-mu-lp" },
{ .compatible = "fsl,imx7d-mu-lp" },
{ .compatible = "fsl,imx7ulp-mu-lp" },
{ }
};
#ifdef CONFIG_PM_SLEEP
static int mu_suspend(struct device *dev)
{
return 0;
}
static int mu_resume(struct device *dev)
{
if (!cpu_is_imx7ulp())
return 0;
writel_relaxed(readl_relaxed(mu_base + MX7ULP_MU_CR) |
BIT(0) | BIT(26) | BIT(27), mu_base + MX7ULP_MU_CR);
return 0;
}
#endif
static const struct dev_pm_ops mu_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(mu_suspend, mu_resume)
};
static struct platform_driver imx_mu_driver = {
.driver = {
.name = "imx-mu-lp",
.owner = THIS_MODULE,
.pm = &mu_pm_ops,
.of_match_table = imx_mu_ids,
},
.probe = imx_mu_probe,
};
static int __init imx_mu_init(void)
{
return platform_driver_register(&imx_mu_driver);
}
subsys_initcall(imx_mu_init);

View File

@ -0,0 +1,51 @@
/*
* Copyright 2004-2015 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
* * 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.
* */
#ifndef __ASM_ARCH_MXC_IOMAP_H__
#define __ASM_ARCH_MXC_IOMAP_H__
#define MX6Q_IO_P2V(x) IMX_IO_P2V(x)
#define MX6Q_IO_ADDRESS(x) IOMEM(MX6Q_IO_P2V(x))
#define MX6Q_L2_BASE_ADDR 0x00a02000
#define MX6Q_L2_SIZE 0x1000
#define MX6Q_IOMUXC_BASE_ADDR 0x020e0000
#define MX6Q_IOMUXC_SIZE 0x4000
#define MX6Q_SRC_BASE_ADDR 0x020d8000
#define MX6Q_SRC_SIZE 0x4000
#define MX6Q_CCM_BASE_ADDR 0x020c4000
#define MX6Q_CCM_SIZE 0x4000
#define MX6Q_ANATOP_BASE_ADDR 0x020c8000
#define MX6Q_ANATOP_SIZE 0x1000
#define MX6Q_GPC_BASE_ADDR 0x020dc000
#define MX6Q_GPC_SIZE 0x4000
#define MX6Q_SEMA4_BASE_ADDR 0x02290000
#define MX6Q_SEMA4_SIZE 0x4000
#define MX6Q_MMDC_P0_BASE_ADDR 0x021b0000
#define MX6Q_MMDC_P0_SIZE 0x4000
#define MX6Q_MMDC_P1_BASE_ADDR 0x021b4000
#define MX6Q_MMDC_P1_SIZE 0x4000
#define MX6Q_AIPS1_BASE_ADDR 0x02000000
#define MX6Q_AIPS1_SIZE 0x100000
#define MX6Q_AIPS2_BASE_ADDR 0x02100000
#define MX6Q_AIPS2_SIZE 0x100000
#define MX6Q_AIPS3_BASE_ADDR 0x02200000
#define MX6Q_AIPS3_SIZE 0x100000
#define MX6SX_IRAM_TLB_BASE_ADDR 0x008f8000
#define MX6Q_IRAM_TLB_BASE_ADDR 0x00900000
#define MX6Q_IRAM_TLB_SIZE 0x4000
#define TT_ATTRIB_NON_CACHEABLE_1M 0x802
#define MX6_SUSPEND_IRAM_DATA_SIZE 256
#define MX6SL_WFI_IRAM_DATA_SIZE 100
#define MX6_SUSPEND_IRAM_ADDR_OFFSET 0
#define MX6_CPUIDLE_IRAM_ADDR_OFFSET 0x1000
#endif

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
* * 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.
* */
#ifndef __ASM_ARCH_MX7_IOMAP_H__
#define __ASM_ARCH_MX7_IOMAP_H__
#define MX7D_IO_P2V(x) IMX_IO_P2V(x)
#define MX7D_IO_ADDRESS(x) IOMEM(MX7D_IO_P2V(x))
#define MX7D_LPSR_BASE_ADDR 0x30270000
#define MX7D_LPSR_SIZE 0x10000
#define MX7D_CCM_BASE_ADDR 0x30380000
#define MX7D_CCM_SIZE 0x10000
#define MX7D_IOMUXC_BASE_ADDR 0x30330000
#define MX7D_IOMUXC_SIZE 0x10000
#define MX7D_IOMUXC_GPR_BASE_ADDR 0x30340000
#define MX7D_IOMUXC_GPR_SIZE 0x10000
#define MX7D_ANATOP_BASE_ADDR 0x30360000
#define MX7D_ANATOP_SIZE 0x10000
#define MX7D_SNVS_BASE_ADDR 0x30370000
#define MX7D_SNVS_SIZE 0x10000
#define MX7D_GPC_BASE_ADDR 0x303a0000
#define MX7D_GPC_SIZE 0x10000
#define MX7D_SRC_BASE_ADDR 0x30390000
#define MX7D_SRC_SIZE 0x10000
#define MX7D_DDRC_BASE_ADDR 0x307a0000
#define MX7D_DDRC_SIZE 0x10000
#define MX7D_DDRC_PHY_BASE_ADDR 0x30790000
#define MX7D_DDRC_PHY_SIZE 0x10000
#define MX7D_AIPS1_BASE_ADDR 0x30000000
#define MX7D_AIPS1_SIZE 0x400000
#define MX7D_AIPS2_BASE_ADDR 0x30400000
#define MX7D_AIPS2_SIZE 0x400000
#define MX7D_AIPS3_BASE_ADDR 0x30900000
#define MX7D_AIPS3_SIZE 0x300000
#define MX7D_GIC_BASE_ADDR 0x31000000
#define MX7D_GIC_SIZE 0x100000
#define TT_ATTRIB_NON_CACHEABLE_1M 0x802
#define MX7_IRAM_TLB_SIZE 0x4000
#define MX7_SUSPEND_OCRAM_SIZE 0x1000
#define MX7_CPUIDLE_OCRAM_ADDR_OFFSET 0x1000
#define MX7_CPUIDLE_OCRAM_SIZE 0x1000
#define MX7_BUSFREQ_OCRAM_ADDR_OFFSET 0x2000
#define MX7_BUSFREQ_OCRAM_SIZE 0x1000
#endif

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved.
* Copyright NXP 2017.
*/
/*
* 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.
*/
#ifndef __ASM_ARCH_MX7ULP_IOMAP_H__
#define __ASM_ARCH_MX7ULP_IOMAP_H__
#define MX7ULP_IO_P2V(x) IMX_IO_P2V(x)
#define MX7ULP_IO_ADDRESS(x) IOMEM(MX7ULP_IO_P2V(x))
#define MX7ULP_AIPS1_BASE_ADDR 0x40000000
#define MX7ULP_AIPS1_SIZE 0x100000
#define MX7ULP_AIPS2_BASE_ADDR 0x40300000
#define MX7ULP_AIPS2_SIZE 0x100000
#define MX7ULP_AIPS3_BASE_ADDR 0x40400000
#define MX7ULP_AIPS3_SIZE 0x100000
#define MX7ULP_AIPS4_BASE_ADDR 0x40a00000
#define MX7ULP_AIPS4_SIZE 0x100000
#define MX7ULP_AIPS5_BASE_ADDR 0x41000000
#define MX7ULP_AIPS5_SIZE 0x100000
#define MX7ULP_GPIOC_BASE_ADDR 0x400f0000
#define MX7ULP_GPIOC_SIZE 0x1000
#define MX7ULP_PCC3_BASE_ADDR 0x40b30000
#define MX7ULP_PCC3_SIZE 0x1000
#define MX7ULP_SCG1_BASE_ADDR 0x403e0000
#define MX7ULP_SCG1_SIZE 0x1000
#define MX7ULP_PCC2_BASE_ADDR 0x403f0000
#define MX7ULP_PCC2_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_SMC1_BASE_ADDR 0x40410000
#define MX7ULP_SMC1_SIZE 0x1000
#define MX7ULP_MMDC_BASE_ADDR 0x40ab0000
#define MX7ULP_MMDC_SIZE 0x1000
#define MX7ULP_IOMUXC1_BASE_ADDR 0x40ac0000
#define MX7ULP_IOMUXC1_BASE__SIZE 0x1000
#define MX7ULP_MMDC_IO_BASE_ADDR 0x40ad0000
#define MX7ULP_MMDC_IO_SIZE 0x1000
/* below is just used for static mapping of the AIPSx's memory region */
#define MX7ULP_AIPS_VIRT_BASE(x) (0xf4000000 + ((x) * SZ_1M))
#define mx7ulp_aips_map_entry(index, _type) { \
.virtual = MX7ULP_AIPS_VIRT_BASE(index), \
.pfn = __phys_to_pfn(MX7ULP_AIPS ## index ## _BASE_ADDR), \
.length = SZ_1M, \
.type = _type, \
}
#define TT_ATTRIB_NON_CACHEABLE_1M 0x802
#define MX7ULP_IRAM_TLB_SIZE 0x4000
#define MX7ULP_SUSPEND_OCRAM_SIZE 0x1000
#endif

View File

@ -33,7 +33,13 @@
#define MXC_CPU_IMX7D 0x72
#define MXC_CPU_IMX7ULP 0xff
#define IMX_DDR_TYPE_DDR3 0
#define IMX_DDR_TYPE_LPDDR2 1
#define IMX_DDR_TYPE_LPDDR3 2
#define IMX_MMDC_DDR_TYPE_LPDDR3 3
#define IMX_LPDDR2_1CH_MODE 0
#define IMX_LPDDR2_2CH_MODE 1
#ifndef __ASSEMBLY__
extern unsigned int __mxc_cpu_type;
@ -85,11 +91,28 @@ static inline bool cpu_is_imx6q(void)
return __mxc_cpu_type == MXC_CPU_IMX6Q;
}
static inline bool cpu_is_imx6(void)
{
return __mxc_cpu_type == MXC_CPU_IMX6Q ||
__mxc_cpu_type == MXC_CPU_IMX6DL ||
__mxc_cpu_type == MXC_CPU_IMX6SL ||
__mxc_cpu_type == MXC_CPU_IMX6SX ||
__mxc_cpu_type == MXC_CPU_IMX6UL ||
__mxc_cpu_type == MXC_CPU_IMX6ULL ||
__mxc_cpu_type == MXC_CPU_IMX6SLL ||
__mxc_cpu_type == MXC_CPU_IMX6ULZ;
}
static inline bool cpu_is_imx7d(void)
{
return __mxc_cpu_type == MXC_CPU_IMX7D;
}
static inline bool cpu_is_imx7ulp(void)
{
return __mxc_cpu_type == MXC_CPU_IMX7ULP;
}
struct cpu_op {
u32 cpu_rate;
};

View File

@ -18,7 +18,7 @@
#include "hardware.h"
u32 g_diag_reg;
static void __iomem *scu_base;
void __iomem *scu_base;
static struct map_desc scu_io_desc __initdata = {
/* .virtual and .pfn are run-time assigned */
@ -47,15 +47,39 @@ static int imx_boot_secondary(unsigned int cpu, struct task_struct *idle)
return 0;
}
#define MXC_ARCH_CA7 0xc07
static unsigned long __mxc_arch_type;
static inline bool arm_is_ca7(void)
{
return __mxc_arch_type == MXC_ARCH_CA7;
}
/*
* Initialise the CPU possible map early - this describes the CPUs
* which may be present or become present in the system.
*/
static void __init imx_smp_init_cpus(void)
{
unsigned long arch_type;
int i, ncores;
ncores = scu_get_core_count(scu_base);
asm volatile(
".align 4\n"
"mrc p15, 0, %0, c0, c0, 0\n"
: "=r" (arch_type)
);
/* MIDR[15:4] defines ARCH type */
__mxc_arch_type = (arch_type >> 4) & 0xfff;
if (arm_is_ca7()) {
unsigned long val;
/* CA7 core number, [25:24] of CP15 L2CTLR */
asm volatile("mrc p15, 1, %0, c9, c0, 2" : "=r" (val));
ncores = ((val >> 24) & 0x3) + 1;
} else {
ncores = scu_get_core_count(scu_base);
}
for (i = ncores; i < NR_CPUS; i++)
set_cpu_possible(i, false);
@ -63,11 +87,15 @@ static void __init imx_smp_init_cpus(void)
void imx_smp_prepare(void)
{
if (arm_is_ca7())
return;
scu_enable(scu_base);
}
static void __init imx_smp_prepare_cpus(unsigned int max_cpus)
{
if (arm_is_ca7())
return;
imx_smp_prepare();
/*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,64 +5,814 @@
* Author: Dong Aisheng <aisheng.dong@nxp.com>
*/
#include <linux/delay.h>
#include <linux/console.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/genalloc.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_fdt.h>
#include <linux/of_irq.h>
#include <linux/psci.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <asm/cacheflush.h>
#include <asm/fncpy.h>
#include <asm/mach/map.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/tlb.h>
#include <uapi/linux/psci.h>
#include "common.h"
#include "hardware.h"
#define MU_SR 0x60
#define PMPROT 0x8
#define PMCTRL 0x10
#define PMSTAT 0x18
#define SRS 0x20
#define RPC 0x24
#define SSRS 0x28
#define SRIE 0x2c
#define SRIF 0x30
#define CSRE 0x34
#define MR 0x40
#define PMC1_HSRUN 0x4
#define PMC1_RUN 0x8
#define PMC1_VLPR 0xc
#define PMC1_STOP 0x10
#define PMC1_VLPS 0x14
#define PMC1_LLS 0x18
#define PMC1_VLLS 0x1c
#define PMC1_STATUS 0x20
#define PMC1_CTRL 0x24
#define PMC0_CTRL 0x28
#define BM_PMPROT_AHSRUN (1 << 7)
#define BM_PMPROT_AVLP (1 << 5)
#define BM_PMPROT_ALLS (1 << 3)
#define BM_PMPROT_AVLLS (1 << 1)
#define BM_PMCTRL_STOPA (1 << 24)
#define BM_PMCTRL_PSTOPO (3 << 16)
#define BM_PMCTRL_RUNM (3 << 8)
#define BM_PMCTRL_STOPM (7 << 0)
#define BM_VLPS_RBBEN (1 << 28)
#define BM_CTRL_LDOEN (1 << 31)
#define BM_CTRL_LDOOKDIS (1 << 30)
#define BM_VLLS_MON1P2HVDHP (1 << 5)
#define BM_VLLS_MON1P2LVDHP (1 << 4)
#define SMC_PMCTRL 0x10
#define BP_PMCTRL_PSTOPO 16
#define PSTOPO_PSTOP3 0x3
#define PSTOPO_PSTOP2 0x2
#define PSTOPO_PSTOP1 0x1
#define BP_PMCTRL_RUNM 8
#define RUNM_RUN 0
#define BP_PMCTRL_STOPM 0
#define STOPM_STOP 0
#define BP_PMCTRL_PSTOPO 16
#define BM_PMCTRL_PSTOPO (3 << BP_PMCTRL_PSTOPO)
#define BM_PMCTRL_RUNM (3 << BP_PMCTRL_RUNM)
#define BM_PMCTRL_STOPM (7 << BP_PMCTRL_STOPM)
#define MX7ULP_MAX_MMDC_IO_NUM 64
#define MX7ULP_MAX_MMDC_NUM 50
#define MX7ULP_MAX_IOMUX_NUM 116
#define MX7ULP_MAX_SELECT_INPUT_NUM 78
#define IOMUX_START 0x0
#define SELECT_INPUT_START 0x200
#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 GPIO_PDOR 0x0
#define GPIO_PDDR 0x14
#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
#define ADDR_1M_MASK 0xFFF00000
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 *gpio_base[4];
static void __iomem *suspend_ocram_base;
static void (*imx7ulp_suspend_in_ocram_fn)(void __iomem *sram_base);
static u32 tpm5_regs[4];
static u32 lpuart4_regs[4];
static u32 pcc2_regs[24][2] = {
{0x20, 0}, {0x3c, 0}, {0x40, 0}, {0x6c, 0},
{0x84, 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[17] = {
0x14, 0x30, 0x40, 0x304,
0x500, 0x504, 0x508, 0x50c,
0x510, 0x514, 0x600, 0x604,
0x608, 0x60c, 0x610, 0x614,
0x104,
};
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_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 = {
0x0, 0x4, 0x8, 0xc,
0x10, 0x14, 0x18, 0x1c,
0x20, 0x24, 0x28, 0x2c,
0x30, 0x34, 0x38, 0x3c,
0x40, 0x44, 0x48, 0x4c,
0x50, 0x54, 0x58, 0x5c,
0x60, 0x64, 0x68, 0x6c,
0x70, 0x74, 0x78, 0x7c,
0x80, 0x84, 0x88, 0x8c,
0x90, 0x94, 0x98, 0x9c,
0xa0, 0xa4, 0xa8, 0xac,
0xb0, 0xb4, 0xb8, 0xbc,
0xc0, 0xc4, 0xc8, 0xcc,
0xd0, 0xd4, 0xd8, 0xdc,
0xe8, 0xf8, 0xfc, 0x120,
0x124,
};
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, 0x01c, 0x01c,
0x01c, 0x01c, 0x01c, 0x01c,
0x01c, 0x01c, 0x01c, 0x01c,
0x01c, 0x01c, 0x83c, 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, 0x00008050, 0x00008058,
0x003F8030, 0x003F8038, 0xFF0A8030, 0xFF0A8038,
0x04028030, 0x04028038, 0x83018030, 0x83018038,
0x01038030, 0x01038038, 0x20000000, 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. */
void __iomem *sim_base;
void __iomem *scg1_base;
void __iomem *mmdc_base;
void __iomem *mmdc_io_base;
void __iomem *smc1_base;
u32 scg1[17];
u32 ttbr1; /* Store TTBR1 */
u32 gpio[4][2];
u32 iomux_num; /* Number of IOs which need saved/restored. */
u32 iomux_val[MX7ULP_MAX_IOMUX_NUM]; /* To save value */
u32 select_input_num; /* Number of select input which need saved/restored. */
u32 select_input_val[MX7ULP_MAX_SELECT_INPUT_NUM]; /* To save value */
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 void __iomem *aips1_base;
static void __iomem *aips2_base;
static void __iomem *aips3_base;
static void __iomem *aips4_base;
static void __iomem *aips5_base;
static const char * const low_power_ocram_match[] __initconst = {
"fsl,lpm-sram",
NULL
};
static void imx7ulp_gpio_save(void)
{
int i;
for (i = 0; i < 4; i++) {
pm_info->gpio[i][0] = readl_relaxed(gpio_base[i] + GPIO_PDOR);
pm_info->gpio[i][1] = readl_relaxed(gpio_base[i] + GPIO_PDDR);
}
}
static void imx7ulp_scg1_save(void)
{
int i;
for (i = 0; i < 17; 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 < 24; 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 < 24; i++)
writel_relaxed(pcc2_regs[i][1], pcc2_base + pcc2_regs[i][0]);
}
static inline void imx7ulp_iomuxc_save(void)
{
int i;
pm_info->iomux_num = MX7ULP_MAX_IOMUX_NUM;
pm_info->select_input_num = MX7ULP_MAX_SELECT_INPUT_NUM;
for (i = 0; i < pm_info->iomux_num; i++)
pm_info->iomux_val[i] =
readl_relaxed(iomuxc1_base +
IOMUX_START + i * 0x4);
for (i = 0; i < pm_info->select_input_num; i++)
pm_info->select_input_val[i] =
readl_relaxed(iomuxc1_base +
SELECT_INPUT_START + i * 0x4);
}
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(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_set_dgo(u32 val)
{
writel_relaxed(val, pm_info->sim_base + DGO_GPR3);
writel_relaxed(val, pm_info->sim_base + DGO_GPR4);
}
int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode)
{
u32 val = readl_relaxed(smc1_base + SMC_PMCTRL);
u32 val1 = BM_PMPROT_AHSRUN | BM_PMPROT_AVLP | BM_PMPROT_AVLLS;
u32 val2 = readl_relaxed(smc1_base + PMCTRL);
u32 val3 = readl_relaxed(pmc0_base + PMC0_CTRL);
/* clear all */
val &= ~(BM_PMCTRL_RUNM | BM_PMCTRL_STOPM | BM_PMCTRL_PSTOPO);
val2 &= ~(BM_PMCTRL_RUNM |
BM_PMCTRL_STOPM | BM_PMCTRL_PSTOPO);
val3 |= BM_CTRL_LDOOKDIS;
switch (mode) {
case ULP_PM_RUN:
/* system/bus clock enabled */
val |= PSTOPO_PSTOP3 << BP_PMCTRL_PSTOPO;
val2 |= 0x3 << BP_PMCTRL_PSTOPO;
break;
case ULP_PM_WAIT:
/* system clock disabled, bus clock enabled */
val |= PSTOPO_PSTOP2 << BP_PMCTRL_PSTOPO;
val2 |= 0x2 << BP_PMCTRL_PSTOPO;
break;
case ULP_PM_STOP:
/* system/bus clock disabled */
val |= PSTOPO_PSTOP1 << BP_PMCTRL_PSTOPO;
val2 |= 0x1 << BP_PMCTRL_PSTOPO;
break;
case ULP_PM_VLPS:
val2 |= 0x2 << BP_PMCTRL_STOPM;
break;
case ULP_PM_VLLS:
val2 |= 0x4 << BP_PMCTRL_STOPM;
break;
default:
return -EINVAL;
}
writel_relaxed(val, smc1_base + SMC_PMCTRL);
writel_relaxed(val1, smc1_base + PMPROT);
writel_relaxed(val2, smc1_base + PMCTRL);
writel_relaxed(val3, pmc0_base + PMC0_CTRL);
return 0;
}
void __init imx7ulp_pm_init(void)
#define MX7ULP_SUSPEND_POWERDWN_PARAM \
((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
#define MX7ULP_SUSPEND_STANDBY_PARAM \
((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_STANDBY << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
static int imx7ulp_suspend_finish(unsigned long val)
{
u32 state;
if (val == 0)
state = MX7ULP_SUSPEND_POWERDWN_PARAM;
else
state = MX7ULP_SUSPEND_STANDBY_PARAM;
if (psci_ops.cpu_suspend)
return psci_ops.cpu_suspend(state, __pa(cpu_resume));
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:
if (psci_ops.cpu_suspend)
/* Zzz ... */
cpu_suspend(1, imx7ulp_suspend_finish);
else {
imx7ulp_set_lpm(ULP_PM_VLPS);
writel_relaxed(
readl_relaxed(pmc1_base + PMC1_VLPS) | BM_VLPS_RBBEN,
pmc1_base + PMC1_VLPS);
/* Zzz ... */
cpu_suspend(0, imx7ulp_suspend_finish);
writel_relaxed(
readl_relaxed(pmc1_base + PMC1_VLPS) & ~BM_VLPS_RBBEN,
pmc1_base + PMC1_VLPS);
imx7ulp_set_lpm(ULP_PM_RUN);
}
break;
case PM_SUSPEND_MEM:
if (psci_ops.cpu_suspend) {
/* Zzz ... */
cpu_suspend(0, imx7ulp_suspend_finish);
} else {
imx7ulp_gpio_save();
imx7ulp_scg1_save();
imx7ulp_pcc2_save();
imx7ulp_pcc3_save();
imx7ulp_tpm_save();
if (!console_suspend_enabled)
imx7ulp_lpuart_save();
imx7ulp_iomuxc_save();
imx7ulp_set_lpm(ULP_PM_VLLS);
/* Zzz ... */
cpu_suspend(0, imx7ulp_suspend_finish);
imx7ulp_pcc2_restore();
imx7ulp_pcc3_restore();
if (!console_suspend_enabled)
imx7ulp_lpuart_restore();
imx7ulp_set_dgo(0);
imx7ulp_tpm_restore();
imx7ulp_set_lpm(ULP_PM_RUN);
}
break;
default:
return -EINVAL;
}
return 0;
}
/* Put CA7 into VLLS mode before M4 power off CA7 */
void imx7ulp_poweroff(void)
{
imx7ulp_set_lpm(ULP_PM_VLLS);
cpu_suspend(0, imx7ulp_suspend_finish);
}
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 int __init imx7ulp_dt_find_lpsram(unsigned long node, const char *uname,
int depth, void *data)
{
unsigned long lpram_addr;
const __be32 *prop = of_get_flat_dt_prop(node, "reg", NULL);
if (of_flat_dt_match(node, low_power_ocram_match)) {
if (!prop)
return -EINVAL;
lpram_addr = be32_to_cpup(prop);
/* We need to create a 1M page table entry. */
iram_tlb_io_desc.virtual =
IMX_IO_P2V(lpram_addr & ADDR_1M_MASK);
iram_tlb_io_desc.pfn = __phys_to_pfn(lpram_addr & ADDR_1M_MASK);
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 imx7ulp_pm_map_io(void)
{
/*
* 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(imx7ulp_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;
}
}
void __init imx7ulp_pm_common_init(const struct imx7ulp_pm_socdata
*socdata)
{
struct device_node *np;
unsigned long sram_paddr = 0;
const u32 *mmdc_offset_array;
const u32 *mmdc_io_offset_array;
unsigned long i, j;
int ret;
if (psci_ops.cpu_suspend) {
aips1_base = ioremap(MX7ULP_AIPS1_BASE_ADDR, SZ_1M);
aips2_base = ioremap(MX7ULP_AIPS2_BASE_ADDR, SZ_1M);
aips3_base = ioremap(MX7ULP_AIPS3_BASE_ADDR, SZ_1M);
aips4_base = ioremap(MX7ULP_AIPS4_BASE_ADDR, SZ_1M);
aips5_base = ioremap(MX7ULP_AIPS5_BASE_ADDR, SZ_1M);
} else {
/* Set all entries to 0 except first 3 words reserved for M4. */
memset((void *)iram_tlb_base_addr, 0, MX7ULP_IRAM_TLB_SIZE);
/*
* Make sure the IRAM virtual address has a mapping in the IRAM
* page table.
*
* Only use the top 12 bits [31-20] when storing the physical
* address in the page table as only these bits are required
* for 1M mapping.
*/
j = ((iram_tlb_base_addr >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + j) =
(iram_tlb_phys_addr & ADDR_1M_MASK) |
TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the AIPS1 virtual address has a mapping in the
* IRAM page table.
*/
aips1_base = ioremap(MX7ULP_AIPS1_BASE_ADDR, SZ_1M);
j = (((u32)aips1_base >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + j) =
((MX7ULP_AIPS1_BASE_ADDR) & ADDR_1M_MASK) |
TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the AIPS2 virtual address has a mapping in the
* IRAM page table.
*/
aips2_base = ioremap(MX7ULP_AIPS2_BASE_ADDR, SZ_1M);
j = (((u32)aips2_base >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + j) =
((MX7ULP_AIPS2_BASE_ADDR) & ADDR_1M_MASK) |
TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the AIPS3 virtual address has a mapping in the
* IRAM page table.
*/
aips3_base = ioremap(MX7ULP_AIPS3_BASE_ADDR, SZ_1M);
j = (((u32)aips3_base >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + j) =
((MX7ULP_AIPS3_BASE_ADDR) & ADDR_1M_MASK) |
TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the AIPS4 virtual address has a mapping in the
* IRAM page table.
*/
aips4_base = ioremap(MX7ULP_AIPS4_BASE_ADDR, SZ_1M);
j = (((u32)aips4_base >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + j) =
((MX7ULP_AIPS4_BASE_ADDR) & ADDR_1M_MASK) |
TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the AIPS5 virtual address has a mapping in the
* IRAM page table.
*/
aips5_base = ioremap(MX7ULP_AIPS5_BASE_ADDR, SZ_1M);
j = (((u32)aips5_base >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + j) =
((MX7ULP_AIPS5_BASE_ADDR) & ADDR_1M_MASK) |
TT_ATTRIB_NON_CACHEABLE_1M;
}
np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-smc1");
smc1_base = of_iomap(np, 0);
WARN_ON(!smc1_base);
np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-pmc0");
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-iomuxc1");
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 = NULL;
for (i = 0; i < 4; i++) {
np = of_find_compatible_node(np, NULL, "fsl,vf610-gpio");
gpio_base[i] = of_iomap(np, 1);
WARN_ON(!gpio_base[i]);
}
if (psci_ops.cpu_suspend) {
pm_info = kzalloc(SZ_16K, GFP_KERNEL);
if (!pm_info)
panic("pm info allocation failed\n");
} else {
/*
* 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->scg1_base = aips2_base +
(MX7ULP_SCG1_BASE_ADDR & ~ADDR_1M_MASK);
pm_info->smc1_base = aips3_base +
(MX7ULP_SMC1_BASE_ADDR & ~ADDR_1M_MASK);
pm_info->mmdc_base = aips4_base +
(MX7ULP_MMDC_BASE_ADDR & ~ADDR_1M_MASK);
pm_info->mmdc_io_base = aips4_base +
(MX7ULP_MMDC_IO_BASE_ADDR & ~ADDR_1M_MASK);
pm_info->sim_base = aips5_base +
(MX7ULP_SIM_BASE_ADDR & ~ADDR_1M_MASK);
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 +
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];
if (!psci_ops.cpu_suspend) {
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(ULP_PM_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);
pm_system_wakeup();
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;
}
}

View File

@ -0,0 +1,353 @@
/*
* Copyright 2017-2018 NXP
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/imx_rpmsg.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_qos.h>
#include <linux/reboot.h>
#include <linux/rpmsg.h>
#include <linux/uaccess.h>
#include <linux/virtio.h>
#include "common.h"
#define RPMSG_TIMEOUT 1000
#define PM_RPMSG_TYPE 0
#define HEATBEAT_RPMSG_TYPE 2
enum pm_rpmsg_cmd {
PM_RPMSG_MODE,
PM_RPMSG_HEART_BEAT,
PM_RPMSG_HEART_BEAT_OFF,
};
enum pm_rpmsg_power_mode {
PM_RPMSG_HSRUN,
PM_RPMSG_RUN,
PM_RPMSG_VLPR,
PM_RPMSG_WAIT,
PM_RPMSG_VLPS,
PM_RPMSG_VLLS,
PM_RPMSG_REBOOT,
PM_RPMSG_SHUTDOWN,
};
struct pm_rpmsg_info {
struct rpmsg_device *rpdev;
struct device *dev;
struct pm_rpmsg_data *msg;
struct pm_qos_request pm_qos_req;
struct notifier_block restart_handler;
struct completion cmd_complete;
bool first_flag;
struct mutex lock;
};
static struct pm_rpmsg_info pm_rpmsg;
static struct delayed_work heart_beat_work;
static bool heartbeat_off;
struct pm_rpmsg_data {
struct imx_rpmsg_head header;
u8 data;
} __attribute__ ((packed));
static int pm_send_message(struct pm_rpmsg_data *msg,
struct pm_rpmsg_info *info, bool ack)
{
int err;
if (!info->rpdev) {
dev_dbg(info->dev,
"rpmsg channel not ready, m4 image ready?\n");
return -EINVAL;
}
mutex_lock(&info->lock);
pm_qos_add_request(&info->pm_qos_req,
PM_QOS_CPU_DMA_LATENCY, 0);
reinit_completion(&info->cmd_complete);
err = rpmsg_send(info->rpdev->ept, (void *)msg,
sizeof(struct pm_rpmsg_data));
if (err) {
dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err);
goto err_out;
}
if (ack) {
err = wait_for_completion_timeout(&info->cmd_complete,
msecs_to_jiffies(RPMSG_TIMEOUT));
if (!err) {
dev_err(&info->rpdev->dev, "rpmsg_send timeout!\n");
err = -ETIMEDOUT;
goto err_out;
}
if (info->msg->data != 0) {
dev_err(&info->rpdev->dev, "rpmsg not ack %d!\n",
info->msg->data);
err = -EINVAL;
goto err_out;
}
err = 0;
}
err_out:
pm_qos_remove_request(&info->pm_qos_req);
mutex_unlock(&info->lock);
return err;
}
static int pm_vlls_notify_m4(bool enter)
{
struct pm_rpmsg_data msg;
msg.header.cate = IMX_RMPSG_LIFECYCLE;
msg.header.major = IMX_RMPSG_MAJOR;
msg.header.minor = IMX_RMPSG_MINOR;
msg.header.type = PM_RPMSG_TYPE;
msg.header.cmd = PM_RPMSG_MODE;
msg.data = enter ? PM_RPMSG_VLLS : PM_RPMSG_RUN;
return pm_send_message(&msg, &pm_rpmsg, true);
}
void pm_shutdown_notify_m4(void)
{
struct pm_rpmsg_data msg;
msg.header.cate = IMX_RMPSG_LIFECYCLE;
msg.header.major = IMX_RMPSG_MAJOR;
msg.header.minor = IMX_RMPSG_MINOR;
msg.header.type = PM_RPMSG_TYPE;
msg.header.cmd = PM_RPMSG_MODE;
msg.data = PM_RPMSG_SHUTDOWN;
/* No ACK from M4 */
pm_send_message(&msg, &pm_rpmsg, false);
imx7ulp_poweroff();
}
void pm_reboot_notify_m4(void)
{
struct pm_rpmsg_data msg;
msg.header.cate = IMX_RMPSG_LIFECYCLE;
msg.header.major = IMX_RMPSG_MAJOR;
msg.header.minor = IMX_RMPSG_MINOR;
msg.header.type = PM_RPMSG_TYPE;
msg.header.cmd = PM_RPMSG_MODE;
msg.data = PM_RPMSG_REBOOT;
pm_send_message(&msg, &pm_rpmsg, true);
}
void pm_heartbeat_off_notify_m4(bool enter)
{
struct pm_rpmsg_data msg;
msg.header.cate = IMX_RMPSG_LIFECYCLE;
msg.header.major = IMX_RMPSG_MAJOR;
msg.header.minor = IMX_RMPSG_MINOR;
msg.header.type = PM_RPMSG_TYPE;
msg.header.cmd = PM_RPMSG_HEART_BEAT_OFF;
msg.data = enter ? 0 : 1;
pm_send_message(&msg, &pm_rpmsg, true);
}
static void pm_heart_beat_work_handler(struct work_struct *work)
{
struct pm_rpmsg_data msg;
/* Notify M4 side A7 in RUN mode at boot time */
if (pm_rpmsg.first_flag) {
pm_vlls_notify_m4(false);
pm_heartbeat_off_notify_m4(heartbeat_off);
pm_rpmsg.first_flag = false;
}
if (!heartbeat_off) {
msg.header.cate = IMX_RMPSG_LIFECYCLE;
msg.header.major = IMX_RMPSG_MAJOR;
msg.header.minor = IMX_RMPSG_MINOR;
msg.header.type = HEATBEAT_RPMSG_TYPE;
msg.header.cmd = PM_RPMSG_HEART_BEAT;
msg.data = 0;
pm_send_message(&msg, &pm_rpmsg, false);
schedule_delayed_work(&heart_beat_work,
msecs_to_jiffies(30000));
}
}
static void pm_poweroff_rpmsg(void)
{
pm_shutdown_notify_m4();
pr_emerg("Unable to poweroff system\n");
}
static int pm_restart_handler(struct notifier_block *this, unsigned long mode,
void *cmd)
{
pm_reboot_notify_m4();
return NOTIFY_DONE;
}
static int pm_rpmsg_probe(struct rpmsg_device *rpdev)
{
int ret;
pm_rpmsg.rpdev = rpdev;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
init_completion(&pm_rpmsg.cmd_complete);
mutex_init(&pm_rpmsg.lock);
INIT_DELAYED_WORK(&heart_beat_work,
pm_heart_beat_work_handler);
pm_rpmsg.first_flag = true;
schedule_delayed_work(&heart_beat_work, 0);
pm_rpmsg.restart_handler.notifier_call = pm_restart_handler;
pm_rpmsg.restart_handler.priority = 128;
ret = register_restart_handler(&pm_rpmsg.restart_handler);
if (ret)
dev_err(&rpdev->dev, "cannot register restart handler\n");
pm_power_off = pm_poweroff_rpmsg;
return 0;
}
static int pm_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
struct pm_rpmsg_data *msg = (struct pm_rpmsg_data *)data;
pm_rpmsg.msg = msg;
complete(&pm_rpmsg.cmd_complete);
return 0;
}
static void pm_rpmsg_remove(struct rpmsg_device *rpdev)
{
dev_info(&rpdev->dev, "pm rpmsg driver is removed\n");
}
static struct rpmsg_device_id pm_rpmsg_id_table[] = {
{ .name = "rpmsg-life-cycle-channel" },
{ },
};
static struct rpmsg_driver pm_rpmsg_driver = {
.drv.name = "pm_rpmsg",
.drv.owner = THIS_MODULE,
.id_table = pm_rpmsg_id_table,
.probe = pm_rpmsg_probe,
.callback = pm_rpmsg_cb,
.remove = pm_rpmsg_remove,
};
#ifdef CONFIG_PM_SLEEP
static int pm_heartbeat_suspend(struct device *dev)
{
int err;
err = pm_vlls_notify_m4(true);
if (err)
return err;
cancel_delayed_work_sync(&heart_beat_work);
return 0;
}
static int pm_heartbeat_resume(struct device *dev)
{
int err;
err = pm_vlls_notify_m4(false);
if (err)
return err;
schedule_delayed_work(&heart_beat_work,
msecs_to_jiffies(10000));
return 0;
}
#endif
static int pm_heartbeat_probe(struct platform_device *pdev)
{
platform_set_drvdata(pdev, &pm_rpmsg);
return register_rpmsg_driver(&pm_rpmsg_driver);
}
static const struct of_device_id pm_heartbeat_id[] = {
{"fsl,heartbeat-rpmsg",},
{},
};
MODULE_DEVICE_TABLE(of, pm_heartbeat_id);
static const struct dev_pm_ops pm_heartbeat_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_heartbeat_suspend,
pm_heartbeat_resume)
};
static struct platform_driver pm_heartbeat_driver = {
.driver = {
.name = "heartbeat-rpmsg",
.owner = THIS_MODULE,
.of_match_table = pm_heartbeat_id,
.pm = &pm_heartbeat_ops,
},
.probe = pm_heartbeat_probe,
};
static int __init setup_heartbeat(char *str)
{
heartbeat_off = true;
return 1;
};
__setup("heartbeat_off", setup_heartbeat);
module_platform_driver(pm_heartbeat_driver);
MODULE_DESCRIPTION("Freescale PM rpmsg driver");
MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include <asm/smp_scu.h>
#include "hardware.h"
.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
#ifdef CONFIG_SMP
.align 3
ENTRY(imx7_smp_wfe)
push {r4 - r11, lr}
dsb
isb
disable_l1_dcache
isb
/* Turn off SMP bit. */
mrc p15, 0, r8, c1, c0, 1
bic r8, r8, #0x40
mcr p15, 0, r8, c1, c0, 1
isb
/* Set flag of entering WFE. */
mov r7, #0xff
lsl r7, r7, r0
mov r6, #SCU_PM_DORMANT
lsl r6, r6, r0
ldr r8, [r1, #0x4]
bic r8, r8, r7
orr r6, r6, r8
str r6, [r1, #0x4]
go_back_wfe:
wfe
/* Offset 0x0 stores busfeq done flag */
ldr r6, [r1]
cmp r6, #1
beq go_back_wfe
/* Turn ON SMP bit. */
mrc p15, 0, r8, c1, c0, 1
orr r8, r8, #0x40
mcr p15, 0, r8, c1, c0, 1
isb
/* Enable L1 data cache. */
mrc p15, 0, r8, c1, c0, 0
orr r8, r8, #0x4
mcr p15, 0, r8, c1, c0, 0
isb
/* Set flag of exiting WFE. */
mov r7, #0xff
lsl r7, r7, r0
mov r6, #SCU_PM_NORMAL
lsl r6, r6, r0
ldr r8, [r1, #0x4]
bic r8, r8, r7
orr r6, r6, r8
str r6, [r1, #0x4]
/* Pop all saved registers. */
pop {r4 - r11, lr}
mov pc, lr
.ltorg
ENDPROC(imx7_smp_wfe)
#endif

View File

@ -0,0 +1,186 @@
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/linkage.h>
#include <asm/smp_scu.h>
#include "hardware.h"
#ifdef CONFIG_SMP
.extern scu_base
#endif
.globl wfe_smp_freq_change_start
.globl wfe_smp_freq_change_end
#ifdef CONFIG_SMP
.align 3
.macro disable_l1_dcache
/*
* Flush all data from the L1 data cache before disabling
* SCTLR.C bit.
*/
push {r0 - r11, lr}
ldr r7, =v7_flush_kern_cache_all
mov lr, pc
mov pc, r7
pop {r0 - r11, lr}
/* disable d-cache */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
push {r0 - r11, lr}
ldr r7, =v7_flush_kern_cache_all
mov lr, pc
mov pc, r7
pop {r0 - r11, lr}
.endm
ENTRY(wfe_smp_freq_change)
wfe_smp_freq_change_start:
push {r4 - r11, lr}
mov r6, r0
mov r7, r1
dsb
isb
disable_l1_dcache
isb
/* Turn off SMP bit. */
mrc p15, 0, r8, c1, c0, 1
bic r8, r8, #0x40
mcr p15, 0, r8, c1, c0, 1
isb
/* Inform the SCU we are going to enter WFE. */
push {r0 - r11, lr}
ldr r0,=scu_base
ldr r0, [r0]
mov r1, #SCU_PM_DORMANT
ldr r3, =scu_power_mode
mov lr, pc
mov pc, r3
pop {r0 - r11, lr}
go_back_wfe:
wfe
ldr r3, [r7]
cmp r3, #1
beq go_back_wfe
/* Turn ON SMP bit. */
mrc p15, 0, r8, c1, c0, 1
orr r8, r8, #0x40
mcr p15, 0, r8, c1, c0, 1
isb
/* Enable L1 data cache. */
mrc p15, 0, r8, c1, c0, 0
orr r8, r8, #0x4
mcr p15, 0, r8, c1, c0, 0
isb
/* Inform the SCU we have exited WFE. */
push {r0 - r11, lr}
ldr r0,=scu_base
ldr r0, [r0]
mov r1, #SCU_PM_NORMAL
ldr r3, =scu_power_mode
mov lr, pc
mov pc, r3
pop {r0 - r11, lr}
/* Pop all saved registers. */
pop {r4 - r11, lr}
mov pc, lr
.ltorg
wfe_smp_freq_change_end:
ENDPROC(wfe_smp_freq_change)
#ifdef CONFIG_OPTEE
/**
* @brief Switch CPU in WFE mode while bus frequency change
* on-going
*
* @param[in] r0 CPU in WFE Status
* @param[in] r1 Bus frequency change status
*/
.globl imx_smp_wfe_optee_end
ENTRY(imx_smp_wfe_optee)
push {r4-r11, lr}
dsb
isb
disable_l1_dcache
isb
/* Set flag CPU entering WFE. */
mov r4, #1
str r4, [r0]
dsb
isb
1:
wfe
/* Check if busfreq is done, else loop */
ldr r4, [r1]
cmp r4, #1
beq 1b
/* Enable L1 data cache. */
mrc p15, 0, r4, c1, c0, 0
orr r4, r4, #0x4
mcr p15, 0, r4, c1, c0, 0
isb
/* Set flag CPU exiting WFE. */
mov r4, #0
str r4, [r0]
/* Pop all saved registers. */
pop {r4-r11, lr}
mov pc, lr
.ltorg
imx_smp_wfe_optee_end:
ENDPROC(imx_smp_wfe_optee)
#endif
#endif

View File

@ -12,6 +12,7 @@
#include <linux/smp.h>
#include <asm/smp_plat.h>
#include "common.h"
#include "hardware.h"
#define SRC_SCR 0x000
#define SRC_GPR1 0x020
@ -23,9 +24,18 @@
#define BP_SRC_SCR_SW_IPU2_RST 12
#define BP_SRC_SCR_CORE1_RST 14
#define BP_SRC_SCR_CORE1_ENABLE 22
/* below is for i.MX7D */
#define SRC_GPR1_V2 0x074
#define SRC_A7RCR0 0x004
#define SRC_A7RCR1 0x008
#define SRC_M4RCR 0x00C
#define BP_SRC_A7RCR0_A7_CORE_RESET0 0
#define BP_SRC_A7RCR1_A7_CORE1_ENABLE 1
static void __iomem *src_base;
static DEFINE_SPINLOCK(scr_lock);
static DEFINE_SPINLOCK(src_lock);
static bool m4_is_enabled;
static const int sw_reset_bits[5] = {
BP_SRC_SCR_SW_GPU_RST,
@ -35,6 +45,11 @@ static const int sw_reset_bits[5] = {
BP_SRC_SCR_SW_IPU2_RST
};
bool imx_src_is_m4_enabled(void)
{
return m4_is_enabled;
}
static int imx_src_reset_module(struct reset_controller_dev *rcdev,
unsigned long sw_reset_idx)
{
@ -51,11 +66,11 @@ static int imx_src_reset_module(struct reset_controller_dev *rcdev,
bit = 1 << sw_reset_bits[sw_reset_idx];
spin_lock_irqsave(&scr_lock, flags);
spin_lock_irqsave(&src_lock, flags);
val = readl_relaxed(src_base + SRC_SCR);
val |= bit;
writel_relaxed(val, src_base + SRC_SCR);
spin_unlock_irqrestore(&scr_lock, flags);
spin_unlock_irqrestore(&src_lock, flags);
timeout = jiffies + msecs_to_jiffies(1000);
while (readl(src_base + SRC_SCR) & bit) {
@ -81,32 +96,59 @@ void imx_enable_cpu(int cpu, bool enable)
u32 mask, val;
cpu = cpu_logical_map(cpu);
mask = 1 << (BP_SRC_SCR_CORE1_ENABLE + cpu - 1);
spin_lock(&scr_lock);
val = readl_relaxed(src_base + SRC_SCR);
val = enable ? val | mask : val & ~mask;
val |= 1 << (BP_SRC_SCR_CORE1_RST + cpu - 1);
writel_relaxed(val, src_base + SRC_SCR);
spin_unlock(&scr_lock);
spin_lock(&src_lock);
if (cpu_is_imx7d()) {
/* enable core */
if (enable)
imx_gpcv2_set_core1_pdn_pup_by_software(false);
mask = 1 << (BP_SRC_A7RCR1_A7_CORE1_ENABLE + cpu - 1);
val = readl_relaxed(src_base + SRC_A7RCR1);
val = enable ? val | mask : val & ~mask;
writel_relaxed(val, src_base + SRC_A7RCR1);
} else {
mask = 1 << (BP_SRC_SCR_CORE1_ENABLE + cpu - 1);
val = readl_relaxed(src_base + SRC_SCR);
val = enable ? val | mask : val & ~mask;
val |= 1 << (BP_SRC_SCR_CORE1_RST + cpu - 1);
writel_relaxed(val, src_base + SRC_SCR);
}
spin_unlock(&src_lock);
}
void imx_set_cpu_jump(int cpu, void *jump_addr)
{
spin_lock(&src_lock);
cpu = cpu_logical_map(cpu);
writel_relaxed(__pa_symbol(jump_addr),
src_base + SRC_GPR1 + cpu * 8);
if (cpu_is_imx7d())
writel_relaxed(__pa_symbol(jump_addr),
src_base + SRC_GPR1_V2 + cpu * 8);
else
writel_relaxed(__pa_symbol(jump_addr),
src_base + SRC_GPR1 + cpu * 8);
spin_unlock(&src_lock);
}
u32 imx_get_cpu_arg(int cpu)
{
cpu = cpu_logical_map(cpu);
return readl_relaxed(src_base + SRC_GPR1 + cpu * 8 + 4);
if (cpu_is_imx7d())
return readl_relaxed(src_base + SRC_GPR1_V2
+ cpu * 8 + 4);
else
return readl_relaxed(src_base + SRC_GPR1
+ cpu * 8 + 4);
}
void imx_set_cpu_arg(int cpu, u32 arg)
{
cpu = cpu_logical_map(cpu);
writel_relaxed(arg, src_base + SRC_GPR1 + cpu * 8 + 4);
if (cpu_is_imx7d())
writel_relaxed(arg, src_base + SRC_GPR1_V2
+ cpu * 8 + 4);
else
writel_relaxed(arg, src_base + SRC_GPR1
+ cpu * 8 + 4);
}
void __init imx_src_init(void)
@ -120,6 +162,15 @@ void __init imx_src_init(void)
src_base = of_iomap(np, 0);
WARN_ON(!src_base);
if (cpu_is_imx7d()) {
val = readl_relaxed(src_base + SRC_M4RCR);
if (((val & BIT(3)) == BIT(3)) && !(val & BIT(0)))
m4_is_enabled = true;
else
m4_is_enabled = false;
return;
}
imx_reset_controller.of_node = np;
if (IS_ENABLED(CONFIG_RESET_CONTROLLER))
reset_controller_register(&imx_reset_controller);
@ -128,9 +179,17 @@ void __init imx_src_init(void)
* force warm reset sources to generate cold reset
* for a more reliable restart
*/
spin_lock(&scr_lock);
spin_lock(&src_lock);
val = readl_relaxed(src_base + SRC_SCR);
/* bit 4 is m4c_non_sclr_rst on i.MX6SX */
if (cpu_is_imx6sx() && ((val &
(1 << BP_SRC_SCR_SW_OPEN_VG_RST)) == 0))
m4_is_enabled = true;
else
m4_is_enabled = false;
val &= ~(1 << BP_SRC_SCR_WARM_RESET_ENABLE);
writel_relaxed(val, src_base + SRC_SCR);
spin_unlock(&scr_lock);
spin_unlock(&src_lock);
}

View File

@ -41,23 +41,32 @@
#define PM_INFO_RESUME_ADDR_OFFSET 0x4
#define PM_INFO_DDR_TYPE_OFFSET 0x8
#define PM_INFO_PM_INFO_SIZE_OFFSET 0xC
#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10
#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14
#define PM_INFO_MX6Q_SRC_P_OFFSET 0x18
#define PM_INFO_MX6Q_SRC_V_OFFSET 0x1C
#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x20
#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x24
#define PM_INFO_MX6Q_CCM_P_OFFSET 0x28
#define PM_INFO_MX6Q_CCM_V_OFFSET 0x2C
#define PM_INFO_MX6Q_GPC_P_OFFSET 0x30
#define PM_INFO_MX6Q_GPC_V_OFFSET 0x34
#define PM_INFO_MX6Q_L2_P_OFFSET 0x38
#define PM_INFO_MX6Q_L2_V_OFFSET 0x3C
#define PM_INFO_MMDC_IO_NUM_OFFSET 0x40
#define PM_INFO_MMDC_IO_VAL_OFFSET 0x44
#define PM_INFO_MX6Q_MMDC0_P_OFFSET 0x10
#define PM_INFO_MX6Q_MMDC0_V_OFFSET 0x14
#define PM_INFO_MX6Q_MMDC1_P_OFFSET 0x18
#define PM_INFO_MX6Q_MMDC1_V_OFFSET 0x1C
#define PM_INFO_MX6Q_SRC_P_OFFSET 0x20
#define PM_INFO_MX6Q_SRC_V_OFFSET 0x24
#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x28
#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x2C
#define PM_INFO_MX6Q_CCM_P_OFFSET 0x30
#define PM_INFO_MX6Q_CCM_V_OFFSET 0x34
#define PM_INFO_MX6Q_GPC_P_OFFSET 0x38
#define PM_INFO_MX6Q_GPC_V_OFFSET 0x3C
#define PM_INFO_MX6Q_L2_P_OFFSET 0x40
#define PM_INFO_MX6Q_L2_V_OFFSET 0x44
#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x48
#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x4C
#define PM_INFO_MX6Q_TTBR1_V_OFFSET 0x50
#define PM_INFO_MMDC_IO_NUM_OFFSET 0x54
#define PM_INFO_MMDC_IO_VAL_OFFSET 0x58
/* below offsets depends on MX6_MAX_MMDC_IO_NUM(36) definition */
#define PM_INFO_MMDC_NUM_OFFSET 0x208
#define PM_INFO_MMDC_VAL_OFFSET 0x20C
#define MX6Q_SRC_GPR1 0x20
#define MX6Q_SRC_GPR2 0x24
#define MX6Q_MMDC_MISC 0x18
#define MX6Q_MMDC_MAPSR 0x404
#define MX6Q_MMDC_MPDGCTRL0 0x83c
#define MX6Q_GPC_IMR1 0x08
@ -65,9 +74,49 @@
#define MX6Q_GPC_IMR3 0x10
#define MX6Q_GPC_IMR4 0x14
#define MX6Q_CCM_CCR 0x0
#define MX6Q_ANATOP_CORE 0x140
.align 3
/* Check if the cpu is cortex-a7 */
.macro is_cortex_a7
/* Read the primary cpu number is MPIDR */
mrc p15, 0, r5, c0, c0, 0
ldr r6, =0xfff0
and r5, r5, r6
ldr r6, =0xc070
cmp r5, r6
.endm
.macro disable_l1_cache
/*
* 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 sync_l2_cache
/* sync L2 cache to drain L2's buffers to DRAM. */
@ -86,29 +135,8 @@
.endm
.macro resume_mmdc
/* restore MMDC IO */
cmp r5, #0x0
ldreq r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldrne r11, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET]
ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_IO_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
cmp r5, #0x0
ldreq r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
ldrne r11, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET]
cmp r3, #IMX_DDR_TYPE_LPDDR2
bne 4f
/* r11 must be MMDC base address */
.macro reset_read_fifo
/* reset read FIFO, RST_RD_FIFO */
ldr r7, =MX6Q_MMDC_MPDGCTRL0
@ -128,23 +156,294 @@
ldr r6, [r11, r7]
ands r6, r6, #(1 << 31)
bne 3b
/* check if lppdr2 2 channel mode is enabled */
ldr r7, =MX6Q_MMDC_MISC
ldr r6, [r11, r7]
ands r6, r6, #(1 << 2)
beq 6f
ldr r7, =MX6Q_MMDC_MPDGCTRL0
ldr r6, [r12, r7]
orr r6, r6, #(1 << 31)
str r6, [r12, r7]
4:
ldr r6, [r12, r7]
ands r6, r6, #(1 << 31)
bne 4b
ldr r6, [r12, r7]
orr r6, r6, #(1 << 31)
str r6, [r12, r7]
5:
ldr r6, [r12, r7]
ands r6, r6, #(1 << 31)
bne 5b
6:
.endm
/* r11 must be MMDC base address */
.macro mmdc_out_and_auto_self_refresh
/* let DDR out of self-refresh */
ldr r7, [r11, #MX6Q_MMDC_MAPSR]
bic r7, r7, #(1 << 21)
str r7, [r11, #MX6Q_MMDC_MAPSR]
5:
7:
ldr r7, [r11, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
bne 5b
bne 7b
/* enable DDR auto power saving */
ldr r7, [r11, #MX6Q_MMDC_MAPSR]
bic r7, r7, #0x1
str r7, [r11, #MX6Q_MMDC_MAPSR]
/* check if lppdr2 2 channel mode is enabled */
ldr r7, =MX6Q_MMDC_MISC
ldr r6, [r11, r7]
ands r6, r6, #(1 << 2)
beq 9f
ldr r7, [r12, #MX6Q_MMDC_MAPSR]
bic r7, r7, #(1 << 21)
str r7, [r12, #MX6Q_MMDC_MAPSR]
8:
ldr r7, [r12, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
bne 8b
ldr r7, [r12, #MX6Q_MMDC_MAPSR]
bic r7, r7, #0x1
str r7, [r12, #MX6Q_MMDC_MAPSR]
9:
.endm
/* r10 must be iomuxc base address */
.macro resume_iomuxc_gpr
add r10, r10, #0x4000
/* IOMUXC GPR DRAM_RESET_BYPASS */
ldr r4, [r10, #0x8]
bic r4, r4, #(0x1 << 27)
str r4, [r10, #0x8]
/* IOMUXC GPR DRAM_CKE_BYPASS */
ldr r4, [r10, #0x8]
bic r4, r4, #(0x1 << 31)
str r4, [r10, #0x8]
.endm
.macro resume_io
/* restore MMDC IO */
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET]
ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET
add r7, r7, r0
10:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x8
str r9, [r10, r8]
subs r6, r6, #0x1
bne 10b
cmp r5, #0x0
/* Here only MMDC0 is set */
ldreq r11, [r0, #PM_INFO_MX6Q_MMDC0_V_OFFSET]
ldrne r11, [r0, #PM_INFO_MX6Q_MMDC0_P_OFFSET]
ldreq r12, [r0, #PM_INFO_MX6Q_MMDC1_V_OFFSET]
ldrne r12, [r0, #PM_INFO_MX6Q_MMDC1_P_OFFSET]
reset_read_fifo
mmdc_out_and_auto_self_refresh
.endm
.macro resume_mmdc_io
cmp r5, #0x0
ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET]
ldreq r11, [r0, #PM_INFO_MX6Q_MMDC0_V_OFFSET]
ldrne r11, [r0, #PM_INFO_MX6Q_MMDC0_P_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], #0x8
str r9, [r10, r8]
subs r6, r6, #0x1
bne 11b
/* check whether we need to restore MMDC */
cmp r5, #0x0
beq 12f
/* check whether last suspend is with M/F mix off */
ldr r9, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET]
ldr r6, [r9, #0x220]
cmp r6, #0x0
bne 13f
12:
resume_iomuxc_gpr
reset_read_fifo
b 17f
13:
/* restore MMDC settings */
ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_VAL_OFFSET
add r7, r7, r0
14:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r11, r8]
subs r6, r6, #0x1
bne 14b
/* let DDR enter self-refresh */
ldr r7, [r11, #MX6Q_MMDC_MAPSR]
orr r7, r7, #(1 << 20)
str r7, [r11, #MX6Q_MMDC_MAPSR]
15:
ldr r7, [r11, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 24)
beq 15b
resume_iomuxc_gpr
reset_read_fifo
/* let DDR out of self-refresh */
ldr r7, [r11, #MX6Q_MMDC_MAPSR]
bic r7, r7, #(1 << 20)
str r7, [r11, #MX6Q_MMDC_MAPSR]
16:
ldr r7, [r11, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 24)
bne 16b
/* kick off MMDC */
ldr r4, =0x0
str r4, [r11, #0x1c]
17:
mmdc_out_and_auto_self_refresh
.endm
.macro store_ttbr1
/* Store TTBR1 to pm_info->ttbr1 */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_MX6Q_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
/* Disable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
is_cortex_a7
beq 17f
#ifdef CONFIG_CACHE_L2X0
ldr r8, [r0, #PM_INFO_MX6Q_L2_V_OFFSET]
mov r6, #0x0
str r6, [r8, #0x100]
dsb
isb
#endif
17:
.endm
.macro restore_ttbr1
is_cortex_a7
beq 18f
#ifdef CONFIG_CACHE_L2X0
/* Enable L2. */
ldr r8, [r0, #PM_INFO_MX6Q_L2_V_OFFSET]
ldr r7, =0x1
str r7, [r8, #0x100]
#endif
18:
/* 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_MX6Q_TTBR1_V_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
ENTRY(imx6_suspend)
ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
@ -179,10 +478,25 @@ ENTRY(imx6_suspend)
str r9, [r11, #MX6Q_SRC_GPR1]
str r1, [r11, #MX6Q_SRC_GPR2]
/*
* Check if the cpu is Cortex-A7, for Cortex-A7
* the cache implementation is not the same as
* Cortex-A9, so the cache maintenance operation
* is different.
*/
is_cortex_a7
beq a7_dache_flush
/* need to sync L2 cache before DSM. */
sync_l2_cache
b ttbr_store
a7_dache_flush:
disable_l1_cache
ttbr_store:
store_ttbr1
ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET]
ldr r11, [r0, #PM_INFO_MX6Q_MMDC0_V_OFFSET]
ldr r12, [r0, #PM_INFO_MX6Q_MMDC1_V_OFFSET]
/*
* put DDR explicitly into self-refresh and
* disable automatic power savings.
@ -201,31 +515,59 @@ poll_dvfs_set:
ands r7, r7, #(1 << 25)
beq poll_dvfs_set
/* check if lppdr2 2 channel mode is enabled */
ldr r7, =MX6Q_MMDC_MISC
ldr r6, [r11, r7]
ands r6, r6, #(1 << 2)
beq skip_self_refresh_ch1
ldr r7, [r12, #MX6Q_MMDC_MAPSR]
orr r7, r7, #0x1
str r7, [r12, #MX6Q_MMDC_MAPSR]
ldr r7, [r12, #MX6Q_MMDC_MAPSR]
orr r7, r7, #(1 << 21)
str r7, [r12, #MX6Q_MMDC_MAPSR]
poll_dvfs_set_ch1:
ldr r7, [r12, #MX6Q_MMDC_MAPSR]
ands r7, r7, #(1 << 25)
beq poll_dvfs_set_ch1
skip_self_refresh_ch1:
/* use r11 to store the IO address */
ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]
ldr r6, =0x0
ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET
add r8, r8, r0
/* LPDDR2's last 3 IOs need special setting */
cmp r3, #IMX_DDR_TYPE_LPDDR2
subeq r7, r7, #0x3
set_mmdc_io_lpm:
ldr r9, [r8], #0x8
str r6, [r11, r9]
subs r7, r7, #0x1
ldr r7, [r8], #0x8
ldr r9, [r8], #0x4
str r9, [r11, r7]
subs r6, r6, #0x1
bne set_mmdc_io_lpm
cmp r3, #IMX_DDR_TYPE_LPDDR2
bne set_mmdc_io_lpm_done
ldr r6, =0x1000
ldr r9, [r8], #0x8
str r6, [r11, r9]
ldr r9, [r8], #0x8
str r6, [r11, r9]
ldr r6, =0x80000
ldr r9, [r8]
str r6, [r11, r9]
set_mmdc_io_lpm_done:
/* check whether it supports Mega/Fast off */
ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET]
cmp r6, #0x0
beq set_mmdc_lpm_done
/* IOMUXC GPR DRAM_RESET */
add r11, r11, #0x4000
ldr r6, [r11, #0x8]
orr r6, r6, #(0x1 << 28)
str r6, [r11, #0x8]
/* IOMUXC GPR DRAM_RESET_BYPASS */
ldr r6, [r11, #0x8]
orr r6, r6, #(0x1 << 27)
str r6, [r11, #0x8]
/* IOMUXC GPR DRAM_CKE_BYPASS */
ldr r6, [r11, #0x8]
orr r6, r6, #(0x1 << 31)
str r6, [r11, #0x8]
set_mmdc_lpm_done:
/*
* mask all GPC interrupts before
@ -285,6 +627,27 @@ rbc_loop:
subs r6, r6, #0x1
bne rbc_loop
/*
* ERR005852 Analog: Transition from Deep Sleep Mode to
* LDO Bypass Mode may cause the slow response of the
* VDDARM_CAP output.
*
* Software workaround:
* if internal ldo(VDDARM) bypassed, switch to analog bypass
* mode (0x1E), prio to entering DSM, and then, revert to the
* normal bypass mode, when exiting from DSM.
*/
ldr r11, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET]
ldr r10, [r11, #MX6Q_ANATOP_CORE]
and r10, r10, #0x1f
cmp r10, #0x1f
bne ldo_check_done1
ldo_analog_bypass:
ldr r10, [r11, #MX6Q_ANATOP_CORE]
bic r10, r10, #0x1f
orr r10, r10, #0x1e
str r10, [r11, #MX6Q_ANATOP_CORE]
ldo_check_done1:
/* Zzz, enter stop mode */
wfi
nop
@ -297,8 +660,28 @@ rbc_loop:
* wakeup source, system should auto
* resume, we need to restore MMDC IO first
*/
/* restore it with 0x1f if use ldo bypass mode.*/
ldr r10, [r11, #MX6Q_ANATOP_CORE]
and r10, r10, #0x1f
cmp r10, #0x1e
bne ldo_check_done2
ldo_bypass_restore:
ldr r10, [r11, #MX6Q_ANATOP_CORE]
orr r10, r10, #0x1f
str r10, [r11, #MX6Q_ANATOP_CORE]
ldo_check_done2:
mov r5, #0x0
resume_mmdc
/* check whether it supports Mega/Fast off */
ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET]
cmp r6, #0x0
beq only_resume_io
resume_mmdc_io
b resume_mmdc_done
only_resume_io:
resume_io
resume_mmdc_done:
restore_ttbr1
/* return to suspend finish */
ret lr
@ -313,6 +696,16 @@ resume:
mcr p15, 0, r6, c1, c0, 0
isb
/* restore it with 0x1f if use ldo bypass mode.*/
ldr r11, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]
ldr r7, [r11, #MX6Q_ANATOP_CORE]
and r7, r7, #0x1f
cmp r7, #0x1e
bne ldo_check_done3
ldr r7, [r11, #MX6Q_ANATOP_CORE]
orr r7, r7, #0x1f
str r7, [r11, #MX6Q_ANATOP_CORE]
ldo_check_done3:
/* get physical resume address from pm_info. */
ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
/* clear core0's entry and parameter */
@ -323,7 +716,16 @@ resume:
ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET]
mov r5, #0x1
resume_mmdc
/* check whether it supports Mega/Fast off */
ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET]
cmp r6, #0x0
beq dsm_only_resume_io
resume_mmdc_io
b dsm_resume_mmdc_done
dsm_only_resume_io:
ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET]
resume_io
dsm_resume_mmdc_done:
ret lr
ENDPROC(imx6_suspend)
@ -336,8 +738,11 @@ ENDPROC(imx6_suspend)
ENTRY(v7_cpu_resume)
bl v7_invalidate_l1
is_cortex_a7
beq done
#ifdef CONFIG_CACHE_L2X0
bl l2c310_early_resume
#endif
done:
b cpu_resume
ENDPROC(v7_cpu_resume)

View File

@ -0,0 +1,714 @@
/*
* 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_SNVS_P_OFFSET 0x4c
#define PM_INFO_MX7_SNVS_V_OFFSET 0x50
#define PM_INFO_MX7_ANATOP_P_OFFSET 0x54
#define PM_INFO_MX7_ANATOP_V_OFFSET 0x58
#define PM_INFO_MX7_LPSR_P_OFFSET 0x5c
#define PM_INFO_MX7_LPSR_V_OFFSET 0x60
#define PM_INFO_MX7_GIC_DIST_P_OFFSET 0x64
#define PM_INFO_MX7_GIC_DIST_V_OFFSET 0x68
#define PM_INFO_MX7_TTBR1_V_OFFSET 0x6c
#define PM_INFO_DDRC_REG_NUM_OFFSET 0x70
#define PM_INFO_DDRC_REG_OFFSET 0x74
#define PM_INFO_DDRC_VALUE_OFFSET 0x78
#define PM_INFO_DDRC_PHY_REG_NUM_OFFSET 0x174
#define PM_INFO_DDRC_PHY_REG_OFFSET 0x178
#define PM_INFO_DDRC_PHY_VALUE_OFFSET 0x17c
#define MX7_SRC_GPR1 0x74
#define MX7_SRC_GPR2 0x78
#define GPC_PGC_C0 0x800
#define GPC_PGC_FM 0xa00
#define ANADIG_SNVS_MISC_CTRL 0x380
#define ANADIG_SNVS_MISC_CTRL_SET 0x384
#define ANADIG_SNVS_MISC_CTRL_CLR 0x388
#define ANADIG_DIGPROG 0x800
#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
#define CCM_SNVS_LPCG 0x250
#define MX7D_GPC_IMR1 0x30
#define MX7D_GPC_IMR2 0x34
#define MX7D_GPC_IMR3 0x38
#define MX7D_GPC_IMR4 0x3c
.align 3
.macro disable_l1_dcache
/*
* Flush all data from the L1 data cache before disabling
* SCTLR.C bit.
*/
push {r0 - r10, lr}
ldr r7, =v7_flush_dcache_all
mov lr, pc
mov pc, r7
pop {r0 - r10, lr}
/* disable d-cache */
mrc p15, 0, r7, c1, c0, 0
bic r7, r7, #(1 << 2)
mcr p15, 0, r7, c1, c0, 0
dsb
isb
push {r0 - r10, lr}
ldr r7, =v7_flush_dcache_all
mov lr, pc
mov pc, r7
pop {r0 - r10, lr}
.endm
.macro store_ttbr1
/* Store TTBR1 to pm_info->ttbr1 */
mrc p15, 0, r7, c2, c0, 1
str r7, [r0, #PM_INFO_MX7_TTBR1_V_OFFSET]
/* Disable Branch Prediction, Z bit in SCTLR. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x800
mcr p15, 0, r6, c1, c0, 0
/* Flush the BTAC. */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
ldr r6, =iram_tlb_phys_addr
ldr r6, [r6]
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r6, c2, c0, 1
/* Read TTBCR and set PD0=1, N = 1 */
mrc p15, 0, r6, c2, c0, 2
orr r6, r6, #0x11
mcr p15, 0, r6, c2, c0, 2
dsb
isb
/* flush the TLB */
ldr r6, =0x0
mcr p15, 0, r6, c8, c3, 0
.endm
.macro restore_ttbr1
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
/* Restore TTBCR */
/* Read TTBCR and set PD0=0, N = 0 */
mrc p15, 0, r6, c2, c0, 2
bic r6, r6, #0x11
mcr p15, 0, r6, c2, c0, 2
dsb
isb
/* flush the TLB */
ldr r6, =0x0
mcr p15, 0, r6, c8, c3, 0
/* Enable Branch Prediction, Z bit in SCTLR. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x800
mcr p15, 0, r6, c1, c0, 0
/* Flush the Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* Restore TTBR1, get the origin ttbr1 from pm info */
ldr r7, [r0, #PM_INFO_MX7_TTBR1_V_OFFSET]
mcr p15, 0, r7, c2, c0, 1
.endm
.macro ddrc_enter_self_refresh
ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r11, #DDRC_PWRCTL]
/* wait rw port_busy clear */
ldr r6, =(0x1 << 16)
orr r6, r6, #0x1
1:
ldr r7, [r11, #DDRC_PSTAT]
ands r7, r7, r6
bne 1b
/* enter self-refresh bit 5 */
ldr r7, =(0x1 << 5)
str r7, [r11, #DDRC_PWRCTL]
/* wait until self-refresh mode entered */
2:
ldr r7, [r11, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
bne 2b
3:
ldr r7, [r11, #DDRC_STAT]
ands r7, r7, #0x20
beq 3b
/* disable dram clk */
ldr r7, [r11, #DDRC_PWRCTL]
orr r7, r7, #(1 << 3)
str r7, [r11, #DDRC_PWRCTL]
.endm
.macro ddrc_exit_self_refresh
cmp r5, #0x0
ldreq r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
ldrne r11, [r0, #PM_INFO_MX7_DDRC_P_OFFSET]
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r11, #DDRC_PWRCTL]
/* wait until self-refresh mode entered */
4:
ldr r7, [r11, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
beq 4b
/* enable auto self-refresh */
ldr r7, [r11, #DDRC_PWRCTL]
orr r7, r7, #(1 << 0)
str r7, [r11, #DDRC_PWRCTL]
.endm
.macro wait_delay
5:
subs r6, r6, #0x1
bne 5b
.endm
.macro ddr_enter_retention
ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r11, #DDRC_PCTRL_0]
/* wait rw port_busy clear */
ldr r6, =(0x1 << 16)
orr r6, r6, #0x1
6:
ldr r7, [r11, #DDRC_PSTAT]
ands r7, r7, r6
bne 6b
ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET]
/* enter self-refresh bit 5 */
ldr r7, =(0x1 << 5)
str r7, [r11, #DDRC_PWRCTL]
/* wait until self-refresh mode entered */
7:
ldr r7, [r11, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
bne 7b
8:
ldr r7, [r11, #DDRC_STAT]
ands r7, r7, #0x20
beq 8b
/* disable dram clk */
ldr r7, =(0x1 << 5)
orr r7, r7, #(1 << 3)
str r7, [r11, #DDRC_PWRCTL]
ldr r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET]
ldr r7, [r11, #ANADIG_DIGPROG]
and r7, r7, #0xff
cmp r7, #0x11
bne 10f
/* TO 1.1 */
ldr r11, [r0, #PM_INFO_MX7_IOMUXC_GPR_V_OFFSET]
ldr r7, =0x38000000
str r7, [r11]
/* LPSR mode need to use TO1.0 flow as IOMUX lost power */
ldr r10, [r0, #PM_INFO_MX7_LPSR_V_OFFSET]
ldr r7, [r10]
cmp r7, #0x0
beq 11f
10:
/* 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]
11:
/* turn off ddr power */
ldr r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET]
ldr r7, =(0x1 << 29)
str r7, [r11, #ANADIG_SNVS_MISC_CTRL_SET]
ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET]
ldr r6, =0x1000
ldr r7, [r11, r6]
orr r7, r7, #0x1
str r7, [r11, r6]
.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_CLR]
ldr r6, =50
wait_delay
/* 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]
13:
ldr r6, [r0, #PM_INFO_DDRC_REG_NUM_OFFSET]
ldr r7, =PM_INFO_DDRC_REG_OFFSET
add r7, r7, r0
14:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r3, r8]
subs r6, r6, #0x1
bne 14b
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, [r1, #ANADIG_DIGPROG]
and r7, r7, #0xff
cmp r7, #0x11
bne 12f
/*
* TKT262940:
* System hang when press RST for DDR PAD is
* in retention mode, fixed on TO1.1
*/
ldr r7, [r11]
bic r7, r7, #(1 << 27)
str r7, [r11]
ldr r7, [r11]
bic r7, r7, #(1 << 29)
str r7, [r11]
12:
ldr r7, =(0x1 << 30)
str r7, [r1, #ANADIG_SNVS_MISC_CTRL_SET]
/* 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
15:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r4, r8]
subs r6, r6, #0x1
bne 15b
ldr r7, =0x0
add r9, r10, #0x4000
str r7, [r9, #0x130]
ldr r7, =0x170
orr r7, r7, #0x8
str r7, [r11, #0x20]
ldr r7, =0x2
add r9, r10, #0x4000
str r7, [r9, #0x130]
ldr r7, =0xf
str r7, [r4, #DDRPHY_LP_CON0]
/* wait until self-refresh mode entered */
16:
ldr r7, [r3, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x3
bne 16b
ldr r7, =0x0
str r7, [r3, #DDRC_SWCTL]
ldr r7, =0x1
str r7, [r3, #DDRC_DFIMISC]
ldr r7, =0x1
str r7, [r3, #DDRC_SWCTL]
17:
ldr r7, [r3, #DDRC_SWSTAT]
and r7, r7, #0x1
cmp r7, #0x1
bne 17b
18:
ldr r7, [r3, #DDRC_STAT]
and r7, r7, #0x20
cmp r7, #0x20
bne 18b
/* let DDR out of self-refresh */
ldr r7, =0x0
str r7, [r3, #DDRC_PWRCTL]
19:
ldr r7, [r3, #DDRC_STAT]
and r7, r7, #0x30
cmp r7, #0x0
bne 19b
20:
ldr r7, [r3, #DDRC_STAT]
and r7, r7, #0x3
cmp r7, #0x1
bne 20b
/* enable port */
ldr r7, =0x1
str r7, [r3, #DDRC_PCTRL_0]
/* enable auto self-refresh */
ldr r7, [r3, #DDRC_PWRCTL]
orr r7, r7, #(1 << 0)
str r7, [r3, #DDRC_PWRCTL]
.endm
ENTRY(imx7_suspend)
push {r4-r12}
/* make sure SNVS clk is enabled */
ldr r11, [r0, #PM_INFO_MX7_CCM_V_OFFSET]
add r11, r11, #0x4000
ldr r7, =0x3
str r7, [r11, #CCM_SNVS_LPCG]
/* check whether it is a standby mode */
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11, #GPC_PGC_C0]
cmp r7, #0
beq ddr_only_self_refresh
/*
* The value of r0 is mapped the same in origin table and IRAM table,
* thus no need to care r0 here.
*/
ldr r1, [r0, #PM_INFO_PBASE_OFFSET]
ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET]
ldr r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]
/*
* counting the resume address in iram
* to set it in SRC register.
*/
ldr r6, =imx7_suspend
ldr r7, =resume
sub r7, r7, r6
add r8, r1, r4
add r9, r8, r7
ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET]
/* store physical resume addr and pm_info address. */
str r9, [r11, #MX7_SRC_GPR1]
str r1, [r11, #MX7_SRC_GPR2]
disable_l1_dcache
store_ttbr1
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11, #GPC_PGC_FM]
cmp r7, #0
beq ddr_only_self_refresh
ddr_enter_retention
/* enter LPSR mode if resume addr is valid */
ldr r11, [r0, #PM_INFO_MX7_LPSR_V_OFFSET]
ldr r7, [r11]
cmp r7, #0x0
beq ddr_retention_enter_out
/* disable STOP mode before entering LPSR */
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11]
bic r7, #0xf
str r7, [r11]
/* shut down vddsoc to enter lpsr mode */
ldr r11, [r0, #PM_INFO_MX7_SNVS_V_OFFSET]
ldr r7, [r11, #0x38]
orr r7, r7, #0x60
str r7, [r11, #0x38]
wait_shutdown:
wfi
nop
nop
nop
nop
b wait_shutdown
ddr_only_self_refresh:
ddrc_enter_self_refresh
b wfi
ddr_retention_enter_out:
ldr r11, [r0, #PM_INFO_MX7_GIC_DIST_V_OFFSET]
ldr r7, =0x0
ldr r8, =0x1000
str r7, [r11, r8]
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r4, [r11, #MX7D_GPC_IMR1]
ldr r5, [r11, #MX7D_GPC_IMR2]
ldr r6, [r11, #MX7D_GPC_IMR3]
ldr r7, [r11, #MX7D_GPC_IMR4]
ldr r8, =0xffffffff
str r8, [r11, #MX7D_GPC_IMR1]
str r8, [r11, #MX7D_GPC_IMR2]
str r8, [r11, #MX7D_GPC_IMR3]
str r8, [r11, #MX7D_GPC_IMR4]
/*
* enable the RBC bypass counter here
* to hold off the interrupts. RBC counter
* = 8 (240us). With this setting, the latency
* from wakeup interrupt to ARM power up
* is ~250uS.
*/
ldr r8, [r11, #0x14]
bic r8, r8, #(0x3f << 24)
orr r8, r8, #(0x8 << 24)
str r8, [r11, #0x14]
/* enable the counter. */
ldr r8, [r11, #0x14]
orr r8, r8, #(0x1 << 30)
str r8, [r11, #0x14]
/* unmask all the GPC interrupts. */
str r4, [r11, #MX7D_GPC_IMR1]
str r5, [r11, #MX7D_GPC_IMR2]
str r6, [r11, #MX7D_GPC_IMR3]
str r7, [r11, #MX7D_GPC_IMR4]
/*
* now delay for a short while (3usec)
* ARM is at 1GHz at this point
* so a short loop should be enough.
* this delay is required to ensure that
* the RBC counter can start counting in
* case an interrupt is already pending
* or in case an interrupt arrives just
* as ARM is about to assert DSM_request.
*/
ldr r7, =2000
rbc_loop:
subs r7, r7, #0x1
bne rbc_loop
wfi:
/* Zzz, enter stop mode */
wfi
nop
nop
nop
nop
mov r5, #0x0
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11, #GPC_PGC_FM]
cmp r7, #0
beq wfi_ddr_self_refresh_out
ddr_exit_retention
b wfi_ddr_retention_out
wfi_ddr_self_refresh_out:
ddrc_exit_self_refresh
wfi_ddr_retention_out:
/* check whether it is a standby mode */
ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET]
ldr r7, [r11, #GPC_PGC_C0]
cmp r7, #0
beq standby_out
ldr r11, [r0, #PM_INFO_MX7_GIC_DIST_V_OFFSET]
ldr r7, =0x1
ldr r8, =0x1000
str r7, [r11, r8]
restore_ttbr1
standby_out:
pop {r4-r12}
/* return to suspend finish */
mov pc, lr
resume:
/* invalidate L1 I-cache first */
mov r6, #0x0
mcr p15, 0, r6, c7, c5, 0
mcr p15, 0, r6, c7, c5, 6
/* enable the Icache and branch prediction */
mov r6, #0x1800
mcr p15, 0, r6, c1, c0, 0
isb
/* get physical resume address from pm_info. */
ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
/* clear core0's entry and parameter */
ldr r11, [r0, #PM_INFO_MX7_SRC_P_OFFSET]
mov r7, #0x0
str r7, [r11, #MX7_SRC_GPR1]
str r7, [r11, #MX7_SRC_GPR2]
mov r5, #0x1
ldr r11, [r0, #PM_INFO_MX7_GPC_P_OFFSET]
ldr r7, [r11, #GPC_PGC_FM]
cmp r7, #0
beq dsm_ddr_self_refresh_out
ddr_exit_retention
b dsm_ddr_retention_out
dsm_ddr_self_refresh_out:
ddrc_exit_self_refresh
dsm_ddr_retention_out:
mov pc, lr
ENDPROC(imx7_suspend)
ENTRY(ca7_cpu_resume)
bl v7_invalidate_l1
b cpu_resume
ENDPROC(ca7_cpu_resume)

View File

@ -0,0 +1,625 @@
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
* Copyright 2017 NXP
*
* 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;
*
* 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_VBASE_OFFSET 0x18
#define PM_INFO_PM_INFO_SCG1_VBASE_OFFSET 0x1c
#define PM_INFO_PM_INFO_MMDC_VBASE_OFFSET 0x20
#define PM_INFO_PM_INFO_MMDC_IO_VBASE_OFFSET 0x24
#define PM_INFO_PM_INFO_SMC1_VBASE_OFFSET 0x28
#define PM_INFO_PM_INFO_SCG1_VAL_OFFSET 0x2c
#define PM_INFO_MX7ULP_TTBR1_V_OFFSET 0x70
#define PM_INFO_MX7ULP_GPIO_REG_OFFSET 0x74
#define PM_INFO_IOMUX_NUM_OFFSET 0x94
#define PM_INFO_IOMUX_VAL_OFFSET 0x98
#define PM_INFO_SELECT_INPUT_NUM_OFFSET 0x268
#define PM_INFO_SELECT_INPUT_VAL_OFFSET 0x26c
#define PM_INFO_MMDC_IO_NUM_OFFSET 0x3a4
#define PM_INFO_MMDC_IO_VAL_OFFSET 0x3a8
/* below offsets depends on MX7ULP_MAX_MMDC_IO_NUM(36) definition */
#define PM_INFO_MMDC_NUM_OFFSET 0x5a8
#define PM_INFO_MMDC_VAL_OFFSET 0x5ac
#define DGO_CTRL0 0x50
#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
#define GPIO_PDOR 0x0
#define GPIO_PDDR 0x14
#define GPIO_PORT_NUM 0x4
#define GPIO_PORT_OFFSET 0x40
#define PMCTRL 0x10
#define IOMUX_OFFSET 0x0
#define SELECT_INPUT_OFFSET 0x200
.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, =MX7ULP_MMDC_IO_BASE_ADDR
ldr r11, =MX7ULP_MMDC_BASE_ADDR
/* 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]
ldr r7, [r11, #DGO_CTRL0]
orr r7, r7, #0xc
str r7, [r11, #DGO_CTRL0]
wait_dgo:
ldr r7, [r11, #DGO_CTRL0]
and r7, r7, #0x18000
cmp r7, #0x18000
bne wait_dgo
ldr r7, [r11, #DGO_CTRL0]
orr r7, r7, #0x18000
bic r7, r7, #0xc
str r7, [r11, #DGO_CTRL0]
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
/* put mmdc io into lpm */
ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_IO_VBASE_OFFSET]
ldr r10, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET
add r7, r7, r0
mmdc_io_lpm:
ldr r8, [r7], #0x8
mov r9, #0x0
str r9, [r11, r8]
subs r10, r10, #0x1
bne mmdc_io_lpm
/* switch NIC clock to FIRC */
ldr r10, [r0, #PM_INFO_PM_INFO_SCG1_VBASE_OFFSET]
ldr r7, [r10, #SCG_NICCCR]
bic r7, #(1 << 28)
str r7, [r10, #SCG_NICCCR]
/* switch RUN clock to FIRC */
ldr r7, [r10, #SCG_RCCR]
bic r7, #(0xf << 24)
orr r7, #(0x3 << 24)
str r7, [r10, #SCG_RCCR]
/* turn off SPLL and SPFD */
ldr r7, [r10, #SCG_SPLLPFD]
mov r8, r7
orr r7, r7, #(0x1 << 31)
orr r7, r7, #(0x1 << 23)
orr r7, r7, #(0x1 << 15)
orr r7, r7, #(0x1 << 7)
str r7, [r10, #SCG_SPLLPFD]
ldr r7, [r10, #SCG_SPLLCSR]
bic r7, r7, #0x1
str r7, [r10, #SCG_SPLLCSR]
/* turn off APLL and APFD */
ldr r7, [r10, #SCG_APLLPFD]
mov r9, r7
orr r7, r7, #(0x1 << 31)
orr r7, r7, #(0x1 << 23)
orr r7, r7, #(0x1 << 15)
orr r7, r7, #(0x1 << 7)
str r7, [r10, #SCG_APLLPFD]
ldr r7, [r10, #SCG_APLLCSR]
bic r7, r7, #0x1
str r7, [r10, #SCG_APLLCSR]
/* 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]
/* enable SPLL and SPFD */
ldr r10, [r0, #PM_INFO_PM_INFO_SCG1_VBASE_OFFSET]
ldr r7, [r10, #SCG_SPLLCSR]
orr r7, r7, #1
str r7, [r10, #SCG_SPLLCSR]
wait_spll:
ldr r7, [r10, #SCG_SPLLCSR]
ands r7, r7, #(1 << 24)
beq wait_spll
str r8, [r10, #SCG_SPLLPFD]
/* switch RUN clock to SPLL */
ldr r7, [r10, #SCG_RCCR]
bic r7, #(0xf << 24)
orr r7, #(0x6 << 24)
str r7, [r10, #SCG_RCCR]
/* enable APLL and APFD */
ldr r7, [r10, #SCG_APLLCSR]
orr r7, r7, #1
str r7, [r10, #SCG_APLLCSR]
wait_apll:
ldr r7, [r10, #SCG_APLLCSR]
ands r7, r7, #(1 << 24)
beq wait_apll
str r9, [r10, #SCG_APLLPFD]
/* switch NIC clock to DDR */
ldr r7, [r10, #SCG_NICCCR]
orr r7, #(1 << 28)
str r7, [r10, #SCG_NICCCR]
/* let mmdc io out of lpm */
ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_IO_VBASE_OFFSET]
ldr r10, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET]
ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET
add r7, r7, r0
mmdc_io_exit_lpm:
ldr r8, [r7], #0x4
ldr r9, [r7], #0x4
str r9, [r11, r8]
subs r10, r10, #0x1
bne mmdc_io_exit_lpm
/* let DDR out of self-refresh */
ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_VBASE_OFFSET]
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_SIM_BASE_ADDR
ldr r0, [r6, #DGO_GPR4]
/* 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]
/* restore system OSC div */
add r6, r5, #64
ldr r7, [r0, r6]
str r7, [r11, #SCG_SOSCDIV]
/* enable mmdc clock in pcc3 */
ldr r11, =MX7ULP_PCC3_BASE_ADDR
ldr r7, [r11, #0xac]
orr r7, r7, #(1 << 30)
str r7, [r11, #0xac]
/* enable GPIO clock in pcc2 */
ldr r11, =MX7ULP_PCC2_BASE_ADDR
ldr r7, [r11, #0x3c]
orr r7, r7, #(1 << 30)
str r7, [r11, #0x3c]
/* restore gpio settings */
ldr r10, =MX7ULP_GPIOC_BASE_ADDR
ldr r7, =PM_INFO_MX7ULP_GPIO_REG_OFFSET
add r7, r7, r0
ldr r6, =GPIO_PORT_NUM
12:
ldr r9, [r7], #0x4
str r9, [r10, #GPIO_PDOR]
ldr r9, [r7], #0x4
str r9, [r10, #GPIO_PDDR]
add r10, r10, #GPIO_PORT_OFFSET
subs r6, r6, #0x1
bne 12b
/* restore iomuxc settings */
ldr r10, =MX7ULP_IOMUXC1_BASE_ADDR
add r10, r10, #IOMUX_OFFSET
ldr r6, [r0, #PM_INFO_IOMUX_NUM_OFFSET]
ldr r7, =PM_INFO_IOMUX_VAL_OFFSET
add r7, r7, r0
13:
ldr r9, [r7], #0x4
str r9, [r10], #0x4
subs r6, r6, #0x1
bne 13b
/* restore select input settings */
ldr r10, =MX7ULP_IOMUXC1_BASE_ADDR
add r10, r10, #SELECT_INPUT_OFFSET
ldr r6, [r0, #PM_INFO_SELECT_INPUT_NUM_OFFSET]
ldr r7, =PM_INFO_SELECT_INPUT_VAL_OFFSET
add r7, r7, r0
14:
ldr r9, [r7], #0x4
str r9, [r10], #0x4
subs r6, r6, #0x1
bne 14b
/* isoack */
ldr r6, =MX7ULP_PMC1_BASE_ADDR
ldr r7, [r6, #PMC1_CTRL]
orr r7, r7, #(1 << 14)
str r7, [r6, #PMC1_CTRL]
restore_mmdc_settings
mov pc, lr
ENDPROC(imx7ulp_suspend)
ENTRY(imx7ulp_cpu_resume)
bl v7_invalidate_l1
b cpu_resume
ENDPROC(imx7ulp_cpu_resume)

View File

@ -2320,6 +2320,7 @@ void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
#endif
dev->archdata.dma_ops_setup = true;
}
EXPORT_SYMBOL(arch_setup_dma_ops);
void arch_teardown_dma_ops(struct device *dev)
{

View File

@ -399,6 +399,13 @@ void __iomem *ioremap_wc(resource_size_t res_cookie, size_t size)
}
EXPORT_SYMBOL(ioremap_wc);
void __iomem *ioremap_cache_ns(resource_size_t res_cookie, size_t size)
{
return arch_ioremap_caller(res_cookie, size, MT_MEMORY_RW_NS,
__builtin_return_address(0));
}
EXPORT_SYMBOL(ioremap_cache_ns);
/*
* Remap an arbitrary physical address space into the kernel virtual
* address space as memory. Needed when the kernel wants to execute

View File

@ -312,6 +312,13 @@ static struct mem_type mem_types[] __ro_after_init = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
.domain = DOMAIN_KERNEL,
},
[MT_MEMORY_RW_NS] = {
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_XN,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_XN,
.domain = DOMAIN_KERNEL,
},
[MT_ROM] = {
.prot_sect = PMD_TYPE_SECT,
.domain = DOMAIN_KERNEL,
@ -648,6 +655,7 @@ static void __init build_mem_type_table(void)
}
kern_pgprot |= PTE_EXT_AF;
vecs_pgprot |= PTE_EXT_AF;
mem_types[MT_MEMORY_RW_NS].prot_pte |= PTE_EXT_AF | cp->pte;
/*
* Set PXN for user mappings
@ -676,6 +684,7 @@ static void __init build_mem_type_table(void)
mem_types[MT_MEMORY_RWX].prot_pte |= kern_pgprot;
mem_types[MT_MEMORY_RW].prot_sect |= ecc_mask | cp->pmd;
mem_types[MT_MEMORY_RW].prot_pte |= kern_pgprot;
mem_types[MT_MEMORY_RW_NS].prot_sect |= ecc_mask | cp->pmd;
mem_types[MT_MEMORY_DMA_READY].prot_pte |= kern_pgprot;
mem_types[MT_MEMORY_RWX_NONCACHED].prot_sect |= ecc_mask;
mem_types[MT_ROM].prot_sect |= cp->pmd;

View File

@ -1046,7 +1046,7 @@ config XEN
Say Y if you want to run Linux in a Virtual Machine on Xen on ARM64.
config FORCE_MAX_ZONEORDER
int
int "Maximum zone order"
default "14" if (ARM64_64K_PAGES && TRANSPARENT_HUGEPAGE)
default "12" if (ARM64_16K_PAGES && TRANSPARENT_HUGEPAGE)
default "11"

View File

@ -171,6 +171,9 @@ config ARCH_MXC
select ARM64_ERRATUM_845719 if COMPAT
select IMX_GPCV2
select IMX_GPCV2_PM_DOMAINS
select HAVE_IMX_BUSFREQ
select IMX8M_BUSFREQ
select IMX8M_PM_DOMAINS
select PM
select PM_GENERIC_DOMAINS
select SOC_BUS
@ -179,6 +182,9 @@ config ARCH_MXC
This enables support for the ARMv8 based SoCs in the
NXP i.MX family.
config HAVE_IMX_BUSFREQ
bool "i.MX8M busfreq"
config ARCH_QCOM
bool "Qualcomm Platforms"
select GPIOLIB

View File

@ -170,6 +170,7 @@ extern void __iomem *ioremap_cache(phys_addr_t phys_addr, size_t size);
#define ioremap_nocache(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
#define ioremap_wc(addr, size) __ioremap((addr), (size), __pgprot(PROT_NORMAL_NC))
#define ioremap_wt(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
#define ioremap_cache_ns(addr, size) __ioremap((addr), (size), __pgprot(PROT_NORMAL_NS))
/*
* PCI configuration space mapping function.

View File

@ -37,6 +37,7 @@
#define PROT_NORMAL_NC (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_NC))
#define PROT_NORMAL_WT (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_WT))
#define PROT_NORMAL (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL))
#define PROT_NORMAL_NS (PTE_TYPE_PAGE | PTE_AF | PTE_PXN | PTE_UXN | PTE_DIRTY | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL))
#define PROT_SECT_DEVICE_nGnRE (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_DEVICE_nGnRE))
#define PROT_SECT_NORMAL (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_NORMAL))
@ -77,6 +78,7 @@
})
#define PAGE_S2 __pgprot(_PROT_DEFAULT | PAGE_S2_MEMATTR(NORMAL) | PTE_S2_RDONLY | PAGE_S2_XN)
#define PAGE_S2_NS __pgprot(PAGE_S2_MEMATTR(NORMAL) | PTE_S2_RDWR | PTE_TYPE_PAGE | PTE_AF)
#define PAGE_S2_DEVICE __pgprot(_PROT_DEFAULT | PAGE_S2_MEMATTR(DEVICE_nGnRE) | PTE_S2_RDONLY | PTE_S2_XN)
#define PAGE_NONE __pgprot(((_PAGE_DEFAULT) & ~PTE_VALID) | PTE_PROT_NONE | PTE_RDONLY | PTE_NG | PTE_PXN | PTE_UXN)

View File

@ -418,6 +418,11 @@ static inline pmd_t pmd_mkdevmap(pmd_t pmd)
__pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRnE) | PTE_PXN | PTE_UXN)
#define pgprot_writecombine(prot) \
__pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN)
#define pgprot_cached(prot) \
__pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL) | \
PTE_PXN | PTE_UXN)
#define pgprot_cached_ns(prot) \
__pgprot(pgprot_val(pgprot_cached(prot)) ^ PTE_SHARED)
#define pgprot_device(prot) \
__pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_PXN | PTE_UXN)
/*

View File

@ -15,6 +15,8 @@
#include <asm/cputype.h>
#include <asm/mmu.h>
extern bool TKT340553_SW_WORKAROUND;
/*
* Raw TLBI operations.
*
@ -149,8 +151,12 @@ static inline void flush_tlb_mm(struct mm_struct *mm)
unsigned long asid = __TLBI_VADDR(0, ASID(mm));
dsb(ishst);
__tlbi(aside1is, asid);
__tlbi_user(aside1is, asid);
if (TKT340553_SW_WORKAROUND && ASID(mm) >> 12) {
__tlbi(vmalle1is);
} else {
__tlbi(aside1is, asid);
__tlbi_user(aside1is, asid);
}
dsb(ish);
}
@ -160,8 +166,12 @@ static inline void flush_tlb_page_nosync(struct vm_area_struct *vma,
unsigned long addr = __TLBI_VADDR(uaddr, ASID(vma->vm_mm));
dsb(ishst);
__tlbi(vale1is, addr);
__tlbi_user(vale1is, addr);
if (TKT340553_SW_WORKAROUND && (uaddr >> 36 || (ASID(vma->vm_mm) >> 12))) {
__tlbi(vmalle1is);
} else {
__tlbi(vale1is, addr);
__tlbi_user(vale1is, addr);
}
}
static inline void flush_tlb_page(struct vm_area_struct *vma,
@ -183,6 +193,7 @@ static inline void __flush_tlb_range(struct vm_area_struct *vma,
{
unsigned long asid = ASID(vma->vm_mm);
unsigned long addr;
unsigned long mask = (1 << 20) - 1;
start = round_down(start, stride);
end = round_up(end, stride);
@ -197,10 +208,13 @@ static inline void __flush_tlb_range(struct vm_area_struct *vma,
start = __TLBI_VADDR(start, asid);
end = __TLBI_VADDR(end, asid);
mask <<= 24;
dsb(ishst);
for (addr = start; addr < end; addr += stride) {
if (last_level) {
if (TKT340553_SW_WORKAROUND && (addr & mask || (ASID(vma->vm_mm) >> 12))) {
__tlbi(vmalle1is);
} else if (last_level) {
__tlbi(vale1is, addr);
__tlbi_user(vale1is, addr);
} else {
@ -234,8 +248,12 @@ static inline void flush_tlb_kernel_range(unsigned long start, unsigned long end
end = __TLBI_VADDR(end, 0);
dsb(ishst);
for (addr = start; addr < end; addr += 1 << (PAGE_SHIFT - 12))
__tlbi(vaale1is, addr);
for (addr = start; addr < end; addr += 1 << (PAGE_SHIFT - 12)) {
if (TKT340553_SW_WORKAROUND && addr >> 24)
__tlbi(vmalle1is);
else
__tlbi(vaale1is, addr);
}
dsb(ish);
isb();
}
@ -249,7 +267,10 @@ static inline void __flush_tlb_kernel_pgtable(unsigned long kaddr)
unsigned long addr = __TLBI_VADDR(kaddr, 0);
dsb(ishst);
__tlbi(vaae1is, addr);
if (TKT340553_SW_WORKAROUND && addr >> 24)
__tlbi(vmalle1is);
else
__tlbi(vaae1is, addr);
dsb(ish);
isb();
}

View File

@ -57,3 +57,4 @@ void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
dev->dma_ops = &xen_swiotlb_dma_ops;
#endif
}
EXPORT_SYMBOL(arch_setup_dma_ops);

View File

@ -322,7 +322,7 @@ config ARCH_HIBERNATION_POSSIBLE
config ARCH_SUSPEND_POSSIBLE
def_bool y
depends on ADB_PMU || PPC_EFIKA || PPC_LITE5200 || PPC_83xx || \
(PPC_85xx && !PPC_E500MC) || PPC_86xx || PPC_PSERIES \
FSL_SOC_BOOKE || PPC_86xx || PPC_PSERIES \
|| 44x || 40x
config ARCH_SUSPEND_NONZERO_CPU
@ -977,8 +977,6 @@ config FSL_PCI
config FSL_PMC
bool
default y
depends on SUSPEND && (PPC_85xx || PPC_86xx)
help
Freescale MPC85xx/MPC86xx power management controller support
(suspend/resume). For MPC83xx see platforms/83xx/suspend.c

View File

@ -42,6 +42,13 @@ extern void flush_dcache_page(struct page *page);
#define flush_dcache_mmap_lock(mapping) do { } while (0)
#define flush_dcache_mmap_unlock(mapping) do { } while (0)
extern void __flush_disable_L1(void);
#ifdef CONFIG_FSL_SOC_BOOKE
extern void flush_dcache_L1(void);
#else
#define flush_dcache_L1() do { } while (0)
#endif
extern void flush_icache_range(unsigned long, unsigned long);
extern void flush_icache_user_range(struct vm_area_struct *vma,
struct page *page, unsigned long addr,

View File

@ -43,6 +43,14 @@ extern int machine_check_e500mc(struct pt_regs *regs);
extern int machine_check_e500(struct pt_regs *regs);
extern int machine_check_e200(struct pt_regs *regs);
extern int machine_check_47x(struct pt_regs *regs);
#if defined(CONFIG_E500) || defined(CONFIG_PPC_E500MC)
extern void __flush_caches_e500v2(void);
extern void __flush_caches_e500mc(void);
extern void __flush_caches_e5500(void);
extern void __flush_caches_e6500(void);
#endif
int machine_check_8xx(struct pt_regs *regs);
int machine_check_83xx(struct pt_regs *regs);
@ -70,6 +78,10 @@ struct cpu_spec {
/* flush caches inside the current cpu */
void (*cpu_down_flush)(void);
#if defined(CONFIG_E500) || defined(CONFIG_PPC_E500MC)
/* flush caches of the cpu which is running the function */
void (*cpu_flush_caches)(void);
#endif
/* number of performance monitor counters */
unsigned int num_pmcs;
enum powerpc_pmc_type pmc_type;

View File

@ -7,6 +7,9 @@
#ifndef __PPC_FSL_PM_H
#define __PPC_FSL_PM_H
#ifndef __ASSEMBLY__
#include <linux/suspend.h>
#define E500_PM_PH10 1
#define E500_PM_PH15 2
#define E500_PM_PH20 3
@ -42,6 +45,34 @@ struct fsl_pm_ops {
extern const struct fsl_pm_ops *qoriq_pm_ops;
struct fsm_reg_vals {
u32 offset;
u32 value;
};
void fsl_fsm_setup(void __iomem *base, struct fsm_reg_vals *val);
void fsl_epu_setup_default(void __iomem *epu_base);
void fsl_npc_setup_default(void __iomem *npc_base);
void fsl_fsm_clean(void __iomem *base, struct fsm_reg_vals *val);
void fsl_epu_clean_default(void __iomem *epu_base);
extern int fsl_dp_iomap(void);
extern void fsl_dp_iounmap(void);
extern int fsl_enter_epu_deepsleep(void);
extern void fsl_dp_enter_low(void __iomem *ccsr_base, void __iomem *dcsr_base,
void __iomem *pld_base, int pld_flag);
extern void fsl_booke_deep_sleep_resume(void);
int __init fsl_rcpm_init(void);
void set_pm_suspend_state(suspend_state_t state);
suspend_state_t pm_suspend_state(void);
void fsl_set_power_except(struct device *dev, int on);
#endif /* __ASSEMBLY__ */
#define T1040QDS_TETRA_FLAG 1
#define T104xRDB_CPLD_FLAG 2
#endif /* __PPC_FSL_PM_H */

View File

@ -81,6 +81,7 @@ obj-$(CONFIG_CRASH_DUMP) += crash_dump.o
ifneq ($(CONFIG_FA_DUMP)$(CONFIG_PRESERVE_FA_DUMP),)
obj-y += fadump.o
endif
obj-$(CONFIG_FSL_SOC) += fsl_pm.o
ifdef CONFIG_PPC32
obj-$(CONFIG_E500) += idle_e500.o
endif

View File

@ -365,6 +365,9 @@ int main(void)
OFFSET(CPU_SPEC_FEATURES, cpu_spec, cpu_features);
OFFSET(CPU_SPEC_SETUP, cpu_spec, cpu_setup);
OFFSET(CPU_SPEC_RESTORE, cpu_spec, cpu_restore);
#if defined(CONFIG_E500) || defined(CONFIG_PPC_E500MC)
OFFSET(CPU_FLUSH_CACHES, cpu_spec, cpu_flush_caches);
#endif
OFFSET(pbe_address, pbe, address);
OFFSET(pbe_orig_address, pbe, orig_address);

View File

@ -340,3 +340,84 @@ _GLOBAL(cpu_down_flush_e5500)
/* L1 Data Cache of e6500 contains no modified data, no flush is required */
_GLOBAL(cpu_down_flush_e6500)
blr
_GLOBAL(__flush_caches_e500v2)
mflr r0
bl flush_dcache_L1
mtlr r0
blr
_GLOBAL(__flush_caches_e500mc)
_GLOBAL(__flush_caches_e5500)
mflr r0
bl flush_dcache_L1
bl flush_backside_L2_cache
mtlr r0
blr
/* L1 Data Cache of e6500 contains no modified data, no flush is required */
_GLOBAL(__flush_caches_e6500)
blr
/* r3 = virtual address of L2 controller, WIMG = 01xx */
_GLOBAL(flush_disable_L2)
/* It's a write-through cache, so only invalidation is needed. */
mbar
isync
lwz r4, 0(r3)
li r5, 1
rlwimi r4, r5, 30, 0xc0000000
stw r4, 0(r3)
/* Wait for the invalidate to finish */
1: lwz r4, 0(r3)
andis. r4, r4, 0x4000
bne 1b
mbar
blr
/* r3 = virtual address of L2 controller, WIMG = 01xx */
_GLOBAL(invalidate_enable_L2)
mbar
isync
lwz r4, 0(r3)
li r5, 3
rlwimi r4, r5, 30, 0xc0000000
stw r4, 0(r3)
/* Wait for the invalidate to finish */
1: lwz r4, 0(r3)
andis. r4, r4, 0x4000
bne 1b
mbar
blr
/* Flush L1 d-cache, invalidate and disable d-cache and i-cache */
_GLOBAL(__flush_disable_L1)
mflr r10
bl flush_dcache_L1 /* Flush L1 d-cache */
mtlr r10
mfspr r4, SPRN_L1CSR0 /* Invalidate and disable d-cache */
li r5, 2
rlwimi r4, r5, 0, 3
msync
isync
mtspr SPRN_L1CSR0, r4
isync
1: mfspr r4, SPRN_L1CSR0 /* Wait for the invalidate to finish */
andi. r4, r4, 2
bne 1b
mfspr r4, SPRN_L1CSR1 /* Invalidate and disable i-cache */
li r5, 2
rlwimi r4, r5, 0, 3
mtspr SPRN_L1CSR1, r4
isync
blr

View File

@ -2051,6 +2051,7 @@ static struct cpu_spec __initdata cpu_specs[] = {
.machine_check = machine_check_e500,
.platform = "ppc8548",
.cpu_down_flush = cpu_down_flush_e500v2,
.cpu_flush_caches = __flush_caches_e500v2,
},
#else
{ /* e500mc */
@ -2071,6 +2072,7 @@ static struct cpu_spec __initdata cpu_specs[] = {
.machine_check = machine_check_e500mc,
.platform = "ppce500mc",
.cpu_down_flush = cpu_down_flush_e500mc,
.cpu_flush_caches = __flush_caches_e500mc,
},
#endif /* CONFIG_PPC_E500MC */
#endif /* CONFIG_PPC32 */
@ -2096,6 +2098,7 @@ static struct cpu_spec __initdata cpu_specs[] = {
.machine_check = machine_check_e500mc,
.platform = "ppce5500",
.cpu_down_flush = cpu_down_flush_e5500,
.cpu_flush_caches = __flush_caches_e5500,
},
{ /* e6500 */
.pvr_mask = 0xffff0000,
@ -2119,6 +2122,7 @@ static struct cpu_spec __initdata cpu_specs[] = {
.machine_check = machine_check_e500mc,
.platform = "ppce6500",
.cpu_down_flush = cpu_down_flush_e6500,
.cpu_flush_caches = __flush_caches_e6500,
},
#endif /* CONFIG_PPC_E500MC */
#ifdef CONFIG_PPC32

View File

@ -174,6 +174,10 @@ skpinv: addi r6,r6,1 /* Increment */
lis r6,MAS2_VAL(PAGE_OFFSET, BOOK3E_PAGESZ_64M, M_IF_NEEDED)@h
ori r6,r6,MAS2_VAL(PAGE_OFFSET, BOOK3E_PAGESZ_64M, M_IF_NEEDED)@l
mtspr SPRN_MAS2,r6
#ifdef ENTRY_DEEPSLEEP_SETUP
LOAD_REG_IMMEDIATE(r8, MEMORY_START)
ori r8,r8,(MAS3_SX|MAS3_SW|MAS3_SR)
#endif
mtspr SPRN_MAS3,r8
tlbwe
@ -216,12 +220,18 @@ next_tlb_setup:
#error You need to specify the mapping or not use this at all.
#endif
#ifdef ENTRY_DEEPSLEEP_SETUP
LOAD_REG_ADDR(r6, 2f)
mfmsr r7
rlwinm r7,r7,0,~(MSR_IS|MSR_DS)
#else
lis r7,MSR_KERNEL@h
ori r7,r7,MSR_KERNEL@l
bl 1f /* Find our address */
1: mflr r9
rlwimi r6,r9,0,20,31
addi r6,r6,(2f - 1b)
#endif
mtspr SPRN_SRR0,r6
mtspr SPRN_SRR1,r7
rfi /* start execution out of TLB1[0] entry */

View File

@ -0,0 +1,49 @@
/*
* Freescale General Power Management Implementation
*
* Copyright 2018 NXP
* Author: Wang Dongsheng <dongsheng.wang@freescale.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the above-listed copyright holders nor the
* names of any contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* ALTERNATIVELY, this software may be distributed under the terms of the
* GNU General Public License ("GPL") as published by the Free Software
* Foundation, either version 2 of that License or (at your option) any
* later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/suspend.h>
#include <asm/fsl_pm.h>
static suspend_state_t pm_state;
void set_pm_suspend_state(suspend_state_t state)
{
pm_state = state;
}
suspend_state_t pm_suspend_state(void)
{
return pm_state;
}

View File

@ -860,7 +860,7 @@ _GLOBAL(start_secondary_resume)
/*
* This subroutine clobbers r11 and r12
*/
enable_64b_mode:
_GLOBAL(enable_64b_mode)
mfmsr r11 /* grab the current MSR */
#ifdef CONFIG_PPC_BOOK3E
oris r11,r11,0x8000 /* CM bit set, we'll set ICM later */

View File

@ -89,6 +89,12 @@ void print_system_hash_info(void);
#endif /* CONFIG_PPC_MMU_NOHASH */
void settlbcam(int index, unsigned long virt, phys_addr_t phys,
unsigned long size, unsigned long flags, unsigned int pid);
void cleartlbcam(unsigned long virt, unsigned int pid);
#ifdef CONFIG_PPC32
void hash_preload(struct mm_struct *mm, unsigned long ea);

View File

@ -102,7 +102,7 @@ unsigned long p_block_mapped(phys_addr_t pa)
* an unsigned long (for example, 32-bit implementations cannot support a 4GB
* size).
*/
static void settlbcam(int index, unsigned long virt, phys_addr_t phys,
void settlbcam(int index, unsigned long virt, phys_addr_t phys,
unsigned long size, unsigned long flags, unsigned int pid)
{
unsigned int tsize;
@ -140,6 +140,18 @@ static void settlbcam(int index, unsigned long virt, phys_addr_t phys,
tlbcam_addrs[index].phys = phys;
}
void cleartlbcam(unsigned long virt, unsigned int pid)
{
int i = 0;
for (i = 0; i < NUM_TLBCAMS; i++) {
if (tlbcam_addrs[i].start == virt) {
TLBCAM[i].MAS1 = 0;
loadcam_entry(i);
return;
}
}
}
unsigned long calc_cam_sz(unsigned long ram, unsigned long virt,
phys_addr_t phys)
{

View File

@ -34,7 +34,6 @@
#include <sysdev/fsl_soc.h>
#include <sysdev/fsl_pci.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/qe_ic.h>
#include "mpc83xx.h"

View File

@ -14,7 +14,6 @@
#include <asm/io.h>
#include <asm/hw_irq.h>
#include <asm/ipic.h>
#include <soc/fsl/qe/qe_ic.h>
#include <sysdev/fsl_soc.h>
#include <sysdev/fsl_pci.h>
@ -90,24 +89,9 @@ void __init mpc83xx_ipic_init_IRQ(void)
}
#ifdef CONFIG_QUICC_ENGINE
void __init mpc83xx_qe_init_IRQ(void)
{
struct device_node *np;
np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic");
if (!np) {
np = of_find_node_by_type(NULL, "qeic");
if (!np)
return;
}
qe_ic_init(np, 0, qe_ic_cascade_low_ipic, qe_ic_cascade_high_ipic);
of_node_put(np);
}
void __init mpc83xx_ipic_and_qe_init_IRQ(void)
{
mpc83xx_ipic_init_IRQ();
mpc83xx_qe_init_IRQ();
}
#endif /* CONFIG_QUICC_ENGINE */

View File

@ -33,7 +33,6 @@
#include <sysdev/fsl_soc.h>
#include <sysdev/fsl_pci.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/qe_ic.h>
#include "mpc83xx.h"

View File

@ -22,7 +22,6 @@
#include <asm/ipic.h>
#include <asm/udbg.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/qe_ic.h>
#include <sysdev/fsl_soc.h>
#include <sysdev/fsl_pci.h>

View File

@ -41,7 +41,6 @@
#include <sysdev/fsl_pci.h>
#include <sysdev/simple_gpio.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/qe_ic.h>
#include "mpc83xx.h"

View File

@ -17,7 +17,6 @@
#include <asm/ipic.h>
#include <asm/udbg.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/qe_ic.h>
#include <sysdev/fsl_soc.h>
#include <sysdev/fsl_pci.h>

View File

@ -10,6 +10,8 @@ menuconfig FSL_SOC_BOOKE
select SERIAL_8250_EXTENDED if SERIAL_8250
select SERIAL_8250_SHARE_IRQ if SERIAL_8250
select FSL_CORENET_RCPM if PPC_E500MC
select FSL_QORIQ_PM if SUSPEND && PPC_E500MC
select FSL_PMC if SUSPEND && !PPC_E500MC
default y
if FSL_SOC_BOOKE
@ -292,3 +294,7 @@ endif # FSL_SOC_BOOKE
config TQM85xx
bool
config FSL_QORIQ_PM
bool
select FSL_SLEEP_FSM

View File

@ -3,7 +3,9 @@
# Makefile for the PowerPC 85xx linux kernel.
#
obj-$(CONFIG_SMP) += smp.o
obj-$(CONFIG_SUSPEND) += sleep.o
obj-$(CONFIG_FSL_PMC) += mpc85xx_pm_ops.o
obj-$(CONFIG_FSL_QORIQ_PM) += qoriq_pm.o deepsleep.o
obj-y += common.o

View File

@ -24,7 +24,6 @@
#include <asm/mpic.h>
#include <asm/ehv_pic.h>
#include <asm/swiotlb.h>
#include <soc/fsl/qe/qe_ic.h>
#include <linux/of_platform.h>
#include <sysdev/fsl_soc.h>
@ -38,8 +37,6 @@ void __init corenet_gen_pic_init(void)
unsigned int flags = MPIC_BIG_ENDIAN | MPIC_SINGLE_DEST_CPU |
MPIC_NO_RESET;
struct device_node *np;
if (ppc_md.get_irq == mpic_get_coreint_irq)
flags |= MPIC_ENABLE_COREINT;
@ -47,13 +44,6 @@ void __init corenet_gen_pic_init(void)
BUG_ON(mpic == NULL);
mpic_init(mpic);
np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic");
if (np) {
qe_ic_init(np, 0, qe_ic_cascade_low_mpic,
qe_ic_cascade_high_mpic);
of_node_put(np);
}
}
/*

View File

@ -0,0 +1,349 @@
/*
* Support deep sleep feature for T104x
*
* Copyright 2018 NXP
* Author: Chenhui Zhao <chenhui.zhao@freescale.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the above-listed copyright holders nor the
* names of any contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* ALTERNATIVELY, this software may be distributed under the terms of the
* GNU General Public License ("GPL") as published by the Free Software
* Foundation, either version 2 of that License or (at your option) any
* later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <sysdev/fsl_soc.h>
#include <asm/machdep.h>
#include <asm/fsl_pm.h>
#define SIZE_1MB 0x100000
#define SIZE_2MB 0x200000
#define CPC_CPCHDBCR0 0x10f00
#define CPC_CPCHDBCR0_SPEC_DIS 0x08000000
#define CCSR_SCFG_DPSLPCR 0xfc000
#define CCSR_SCFG_DPSLPCR_WDRR_EN 0x1
#define CCSR_SCFG_SPARECR2 0xfc504
#define CCSR_SCFG_SPARECR3 0xfc508
#define CCSR_GPIO1_GPDIR 0x130000
#define CCSR_GPIO1_GPODR 0x130004
#define CCSR_GPIO1_GPDAT 0x130008
#define CCSR_GPIO1_GPDIR_29 0x4
#define RCPM_BLOCK_OFFSET 0x00022000
#define EPU_BLOCK_OFFSET 0x00000000
#define NPC_BLOCK_OFFSET 0x00001000
#define CSTTACR0 0xb00
#define CG1CR0 0x31c
#define CCSR_LAW_BASE 0xC00
#define DCFG_BRR 0xE4 /* boot release register */
#define LCC_BSTRH 0x20 /* Boot space translation register high */
#define LCC_BSTRL 0x24 /* Boot space translation register low */
#define LCC_BSTAR 0x28 /* Boot space translation attribute register */
#define RCPM_PCTBENR 0x1A0 /* Physical Core Timebase Enable Register */
#define RCPM_BASE 0xE2000
#define DCFG_BASE 0xE0000
/* 128 bytes buffer for restoring data broke by DDR training initialization */
#define DDR_BUF_SIZE 128
static u8 ddr_buff[DDR_BUF_SIZE] __aligned(64);
static void *dcsr_base, *ccsr_base, *pld_base;
static int pld_flag;
/* for law */
struct fsl_law {
u32 lawbarh; /* LAWn base address high */
u32 lawbarl; /* LAWn base address low */
u32 lawar; /* LAWn attributes */
u32 reserved;
};
struct fsl_law *saved_law;
static u32 num_laws;
/* for nonboot cpu */
struct fsl_bstr {
u32 bstrh;
u32 bstrl;
u32 bstar;
u32 cpu_mask;
};
static struct fsl_bstr saved_bstr;
int fsl_dp_iomap(void)
{
struct device_node *np;
int ret = 0;
phys_addr_t ccsr_phy_addr, dcsr_phy_addr;
saved_law = NULL;
ccsr_base = NULL;
dcsr_base = NULL;
pld_base = NULL;
ccsr_phy_addr = get_immrbase();
if (ccsr_phy_addr == -1) {
pr_err("%s: Can't get the address of CCSR\n", __func__);
ret = -EINVAL;
goto ccsr_err;
}
ccsr_base = ioremap(ccsr_phy_addr, SIZE_2MB);
if (!ccsr_base) {
ret = -ENOMEM;
goto ccsr_err;
}
dcsr_phy_addr = get_dcsrbase();
if (dcsr_phy_addr == -1) {
pr_err("%s: Can't get the address of DCSR\n", __func__);
ret = -EINVAL;
goto dcsr_err;
}
dcsr_base = ioremap(dcsr_phy_addr, SIZE_1MB);
if (!dcsr_base) {
ret = -ENOMEM;
goto dcsr_err;
}
np = of_find_compatible_node(NULL, NULL, "fsl,tetra-fpga");
if (np) {
pld_flag = T1040QDS_TETRA_FLAG;
} else {
np = of_find_compatible_node(NULL, NULL, "fsl,deepsleep-cpld");
if (np) {
pld_flag = T104xRDB_CPLD_FLAG;
} else {
pr_err("%s: Can't find the FPGA/CPLD node\n",
__func__);
ret = -EINVAL;
goto pld_err;
}
}
pld_base = of_iomap(np, 0);
of_node_put(np);
np = of_find_compatible_node(NULL, NULL, "fsl,corenet-law");
if (!np) {
pr_err("%s: Can't find the node of \"law\"\n", __func__);
ret = -EINVAL;
goto alloc_err;
}
ret = of_property_read_u32(np, "fsl,num-laws", &num_laws);
if (ret) {
ret = -EINVAL;
goto alloc_err;
}
saved_law = kzalloc(sizeof(*saved_law) * num_laws, GFP_KERNEL);
if (!saved_law) {
ret = -ENOMEM;
goto alloc_err;
}
of_node_put(np);
return 0;
alloc_err:
iounmap(pld_base);
pld_base = NULL;
pld_err:
iounmap(dcsr_base);
dcsr_base = NULL;
dcsr_err:
iounmap(ccsr_base);
ccsr_base = NULL;
ccsr_err:
return ret;
}
void fsl_dp_iounmap(void)
{
if (dcsr_base) {
iounmap(dcsr_base);
dcsr_base = NULL;
}
if (ccsr_base) {
iounmap(ccsr_base);
ccsr_base = NULL;
}
if (pld_base) {
iounmap(pld_base);
pld_base = NULL;
}
kfree(saved_law);
saved_law = NULL;
}
static void fsl_dp_ddr_save(void *ccsr_base)
{
u32 ddr_buff_addr;
/*
* DDR training initialization will break 128 bytes at the beginning
* of DDR, therefore, save them so that the bootloader will restore
* them. Assume that DDR is mapped to the address space started with
* CONFIG_PAGE_OFFSET.
*/
memcpy(ddr_buff, (void *)CONFIG_PAGE_OFFSET, DDR_BUF_SIZE);
/* assume ddr_buff is in the physical address space of 4GB */
ddr_buff_addr = (u32)(__pa(ddr_buff) & 0xffffffff);
/*
* the bootloader will restore the first 128 bytes of DDR from
* the location indicated by the register SPARECR3
*/
out_be32(ccsr_base + CCSR_SCFG_SPARECR3, ddr_buff_addr);
}
static void fsl_dp_mp_save(void *ccsr)
{
struct fsl_bstr *dst = &saved_bstr;
dst->bstrh = in_be32(ccsr + LCC_BSTRH);
dst->bstrl = in_be32(ccsr + LCC_BSTRL);
dst->bstar = in_be32(ccsr + LCC_BSTAR);
dst->cpu_mask = in_be32(ccsr + DCFG_BASE + DCFG_BRR);
}
static void fsl_dp_mp_restore(void *ccsr)
{
struct fsl_bstr *src = &saved_bstr;
out_be32(ccsr + LCC_BSTRH, src->bstrh);
out_be32(ccsr + LCC_BSTRL, src->bstrl);
out_be32(ccsr + LCC_BSTAR, src->bstar);
/* release the nonboot cpus */
out_be32(ccsr + DCFG_BASE + DCFG_BRR, src->cpu_mask);
/* enable the time base */
out_be32(ccsr + RCPM_BASE + RCPM_PCTBENR, src->cpu_mask);
/* read back to sync write */
in_be32(ccsr + RCPM_BASE + RCPM_PCTBENR);
}
static void fsl_dp_law_save(void *ccsr)
{
int i;
struct fsl_law *dst = saved_law;
struct fsl_law *src = (void *)(ccsr + CCSR_LAW_BASE);
for (i = 0; i < num_laws; i++) {
dst->lawbarh = in_be32(&src->lawbarh);
dst->lawbarl = in_be32(&src->lawbarl);
dst->lawar = in_be32(&src->lawar);
dst++;
src++;
}
}
static void fsl_dp_law_restore(void *ccsr)
{
int i;
struct fsl_law *src = saved_law;
struct fsl_law *dst = (void *)(ccsr + CCSR_LAW_BASE);
for (i = 0; i < num_laws - 1; i++) {
out_be32(&dst->lawar, 0);
out_be32(&dst->lawbarl, src->lawbarl);
out_be32(&dst->lawbarh, src->lawbarh);
out_be32(&dst->lawar, src->lawar);
/* Read back so that we sync the writes */
in_be32(&dst->lawar);
src++;
dst++;
}
}
static void fsl_dp_set_resume_pointer(void *ccsr_base)
{
u32 resume_addr;
/* the bootloader will finally jump to this address to return kernel */
#ifdef CONFIG_PPC32
resume_addr = (u32)(__pa(fsl_booke_deep_sleep_resume));
#else
resume_addr = (u32)(__pa(*(u64 *)fsl_booke_deep_sleep_resume)
& 0xffffffff);
#endif
/* use the register SPARECR2 to save the resume address */
out_be32(ccsr_base + CCSR_SCFG_SPARECR2, resume_addr);
}
int fsl_enter_epu_deepsleep(void)
{
fsl_dp_ddr_save(ccsr_base);
fsl_dp_set_resume_pointer(ccsr_base);
fsl_dp_mp_save(ccsr_base);
fsl_dp_law_save(ccsr_base);
/* enable Warm Device Reset request. */
setbits32(ccsr_base + CCSR_SCFG_DPSLPCR, CCSR_SCFG_DPSLPCR_WDRR_EN);
/* set GPIO1_29 as an output pin (not open-drain), and output 0 */
clrbits32(ccsr_base + CCSR_GPIO1_GPDAT, CCSR_GPIO1_GPDIR_29);
clrbits32(ccsr_base + CCSR_GPIO1_GPODR, CCSR_GPIO1_GPDIR_29);
setbits32(ccsr_base + CCSR_GPIO1_GPDIR, CCSR_GPIO1_GPDIR_29);
/*
* Disable CPC speculation to avoid deep sleep hang, especially
* in secure boot mode. This bit will be cleared automatically
* when resuming from deep sleep.
*/
setbits32(ccsr_base + CPC_CPCHDBCR0, CPC_CPCHDBCR0_SPEC_DIS);
fsl_epu_setup_default(dcsr_base + EPU_BLOCK_OFFSET);
fsl_npc_setup_default(dcsr_base + NPC_BLOCK_OFFSET);
out_be32(dcsr_base + RCPM_BLOCK_OFFSET + CSTTACR0, 0x00001001);
out_be32(dcsr_base + RCPM_BLOCK_OFFSET + CG1CR0, 0x00000001);
fsl_dp_enter_low(ccsr_base, dcsr_base, pld_base, pld_flag);
fsl_dp_law_restore(ccsr_base);
fsl_dp_mp_restore(ccsr_base);
/* disable Warm Device Reset request */
clrbits32(ccsr_base + CCSR_SCFG_DPSLPCR, CCSR_SCFG_DPSLPCR_WDRR_EN);
fsl_epu_clean_default(dcsr_base + EPU_BLOCK_OFFSET);
return 0;
}

View File

@ -45,7 +45,6 @@
#include <sysdev/fsl_pci.h>
#include <sysdev/simple_gpio.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/qe_ic.h>
#include <asm/mpic.h>
#include <asm/swiotlb.h>
#include "smp.h"
@ -279,20 +278,6 @@ static void __init mpc85xx_mds_qeic_init(void)
of_node_put(np);
return;
}
np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic");
if (!np) {
np = of_find_node_by_type(NULL, "qeic");
if (!np)
return;
}
if (machine_is(p1021_mds))
qe_ic_init(np, 0, qe_ic_cascade_low_mpic,
qe_ic_cascade_high_mpic);
else
qe_ic_init(np, 0, qe_ic_cascade_muxed_mpic, NULL);
of_node_put(np);
}
#else
static void __init mpc85xx_mds_qe_init(void) { }

View File

@ -23,7 +23,6 @@
#include <asm/udbg.h>
#include <asm/mpic.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/qe_ic.h>
#include <sysdev/fsl_soc.h>
#include <sysdev/fsl_pci.h>
@ -44,10 +43,6 @@ void __init mpc85xx_rdb_pic_init(void)
{
struct mpic *mpic;
#ifdef CONFIG_QUICC_ENGINE
struct device_node *np;
#endif
if (of_machine_is_compatible("fsl,MPC85XXRDB-CAMP")) {
mpic = mpic_alloc(NULL, 0, MPIC_NO_RESET |
MPIC_BIG_ENDIAN |
@ -62,18 +57,6 @@ void __init mpc85xx_rdb_pic_init(void)
BUG_ON(mpic == NULL);
mpic_init(mpic);
#ifdef CONFIG_QUICC_ENGINE
np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic");
if (np) {
qe_ic_init(np, 0, qe_ic_cascade_low_mpic,
qe_ic_cascade_high_mpic);
of_node_put(np);
} else
pr_err("%s: Could not find qe-ic node\n", __func__);
#endif
}
/*

View File

@ -0,0 +1,222 @@
/*
* Support Power Management feature
*
* Copyright 2018 NXP
* Author: Chenhui Zhao <chenhui.zhao@freescale.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the above-listed copyright holders nor the
* names of any contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* ALTERNATIVELY, this software may be distributed under the terms of the
* GNU General Public License ("GPL") as published by the Free Software
* Foundation, either version 2 of that License or (at your option) any
* later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/suspend.h>
#include <linux/of_platform.h>
#include <linux/usb.h>
#include <asm/fsl_pm.h>
#define FSL_SLEEP 0x1
#define FSL_DEEP_SLEEP 0x2
int (*fsl_enter_deepsleep)(void);
/* specify the sleep state of the present platform */
unsigned int sleep_pm_state;
/* supported sleep modes by the present platform */
static unsigned int sleep_modes;
/**
* fsl_set_power_except - set which IP block is not powerdown when sleep,
* such as MAC, USB, etc.
*
* @dev: a pointer to the struct device
* @on: if 1, do not power down; if 0, power down.
*/
void fsl_set_power_except(struct device *dev, int on)
{
u32 value[2];
u32 pw_mask;
int ret;
struct device_node *mac_node;
const phandle *phandle_prop;
if (dev && !strncmp(dev->bus->name, "usb", 3)) {
struct usb_device *udev = container_of(dev,
struct usb_device, dev);
struct device *controller = udev->bus->controller;
ret = of_property_read_u32_array(controller->parent->of_node,
"sleep", value, 2);
} else
ret = of_property_read_u32_array(dev->of_node, "sleep",
value, 2);
if (ret) {
/* search fman mac node */
phandle_prop = of_get_property(dev->of_node, "fsl,fman-mac",
NULL);
if (phandle_prop == NULL)
goto err;
mac_node = of_find_node_by_phandle(*phandle_prop);
ret = of_property_read_u32_array(mac_node, "sleep", value, 2);
of_node_put(mac_node);
if (ret)
goto err;
}
/* get the second value, it is a mask */
pw_mask = value[1];
qoriq_pm_ops->set_ip_power(on, pw_mask);
return;
err:
dev_err(dev, "Can not set wakeup sources\n");
}
EXPORT_SYMBOL_GPL(fsl_set_power_except);
void qoriq_set_wakeup_source(struct device *dev, void *enable)
{
if (!device_may_wakeup(dev))
return;
fsl_set_power_except(dev, *((int *)enable));
}
static int qoriq_suspend_enter(suspend_state_t state)
{
int ret = 0;
int cpu;
switch (state) {
case PM_SUSPEND_STANDBY:
if (cur_cpu_spec->cpu_flush_caches)
cur_cpu_spec->cpu_flush_caches();
ret = qoriq_pm_ops->plat_enter_sleep();
break;
case PM_SUSPEND_MEM:
cpu = smp_processor_id();
qoriq_pm_ops->irq_mask(cpu);
ret = fsl_enter_deepsleep();
qoriq_pm_ops->irq_unmask(cpu);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int qoriq_suspend_valid(suspend_state_t state)
{
set_pm_suspend_state(state);
if (state == PM_SUSPEND_STANDBY && (sleep_modes & FSL_SLEEP))
return 1;
if (state == PM_SUSPEND_MEM && (sleep_modes & FSL_DEEP_SLEEP))
return 1;
set_pm_suspend_state(PM_SUSPEND_ON);
return 0;
}
static int qoriq_suspend_begin(suspend_state_t state)
{
const int enable = 1;
dpm_for_each_dev((void *)&enable, qoriq_set_wakeup_source);
if (state == PM_SUSPEND_MEM)
return fsl_dp_iomap();
return 0;
}
static void qoriq_suspend_end(void)
{
const int enable = 0;
dpm_for_each_dev((void *)&enable, qoriq_set_wakeup_source);
set_pm_suspend_state(PM_SUSPEND_ON);
fsl_dp_iounmap();
}
static const struct platform_suspend_ops qoriq_suspend_ops = {
.valid = qoriq_suspend_valid,
.enter = qoriq_suspend_enter,
.begin = qoriq_suspend_begin,
.end = qoriq_suspend_end,
};
static const struct of_device_id deepsleep_matches[] = {
{
.compatible = "fsl,t1040-rcpm",
},
{
.compatible = "fsl,t1024-rcpm",
},
{
.compatible = "fsl,t1023-rcpm",
},
{},
};
static int __init qoriq_suspend_init(void)
{
struct device_node *np;
sleep_modes = FSL_SLEEP;
sleep_pm_state = PLAT_PM_SLEEP;
np = of_find_compatible_node(NULL, NULL, "fsl,qoriq-rcpm-2.0");
if (np)
sleep_pm_state = PLAT_PM_LPM20;
np = of_find_matching_node_and_match(NULL, deepsleep_matches, NULL);
if (np) {
fsl_enter_deepsleep = fsl_enter_epu_deepsleep;
sleep_modes |= FSL_DEEP_SLEEP;
}
suspend_set_ops(&qoriq_suspend_ops);
set_pm_suspend_state(PM_SUSPEND_ON);
return 0;
}
arch_initcall(qoriq_suspend_init);

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,6 @@
#include <asm/udbg.h>
#include <asm/mpic.h>
#include <soc/fsl/qe/qe.h>
#include <soc/fsl/qe/qe_ic.h>
#include <sysdev/fsl_soc.h>
#include <sysdev/fsl_pci.h>
@ -31,26 +30,12 @@ static void __init twr_p1025_pic_init(void)
{
struct mpic *mpic;
#ifdef CONFIG_QUICC_ENGINE
struct device_node *np;
#endif
mpic = mpic_alloc(NULL, 0, MPIC_BIG_ENDIAN |
MPIC_SINGLE_DEST_CPU,
0, 256, " OpenPIC ");
BUG_ON(mpic == NULL);
mpic_init(mpic);
#ifdef CONFIG_QUICC_ENGINE
np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic");
if (np) {
qe_ic_init(np, 0, qe_ic_cascade_low_mpic,
qe_ic_cascade_high_mpic);
of_node_put(np);
} else
pr_err("Could not find qe-ic node\n");
#endif
}
/* ************************************************************************

View File

@ -5,6 +5,7 @@ menuconfig PPC_86xx
depends on PPC_BOOK3S_32
select FSL_SOC
select ALTIVEC
select FSL_PMC if SUSPEND
help
The Freescale E600 SoCs have 74xx cores.

Some files were not shown because too many files have changed in this diff Show More