diff options
Diffstat (limited to 'arch/arm/mach-imx')
65 files changed, 7545 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig new file mode 100644 index 000000000..ab767f059 --- /dev/null +++ b/arch/arm/mach-imx/Kconfig @@ -0,0 +1,268 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig ARCH_MXC + bool "Freescale i.MX family" + depends on ((ARCH_MULTI_V4T || ARCH_MULTI_V5) && CPU_LITTLE_ENDIAN) || \ + ARCH_MULTI_V6_V7 || ARM_SINGLE_ARMV7M + select CLKSRC_IMX_GPT + select GENERIC_IRQ_CHIP + select GPIOLIB + select PINCTRL + select PM_OPP if PM + select SOC_BUS + select SRAM + help + Support for Freescale MXC/iMX-based family of processors + +if ARCH_MXC + +config MXC_TZIC + bool + +config MXC_AVIC + bool + +config HAVE_IMX_ANATOP + bool + +config HAVE_IMX_GPC + bool + select PM_GENERIC_DOMAINS if PM + +config HAVE_IMX_MMDC + bool + +config HAVE_IMX_SRC + def_bool y if SMP + select ARCH_HAS_RESET_CONTROLLER + +if ARCH_MULTI_V6 + +comment "ARM1136 platforms" + +config SOC_IMX31 + bool "i.MX31 support" + select CPU_V6 + select MXC_AVIC + help + This enables support for Freescale i.MX31 processor + +config SOC_IMX35 + bool "i.MX35 support" + select MXC_AVIC + select PINCTRL_IMX35 + help + This enables support for Freescale i.MX35 processor + +endif + +if ARCH_MULTI_V4T + +config SOC_IMX1 + bool "i.MX1 support" + select CPU_ARM920T + select MXC_AVIC + select PINCTRL_IMX1 + help + This enables support for Freescale i.MX1 processor + +endif + +if ARCH_MULTI_V5 + +config SOC_IMX25 + bool "i.MX25 support" + select CPU_ARM926T + select MXC_AVIC + select PINCTRL_IMX25 + help + This enables support for Freescale i.MX25 processor + +config SOC_IMX27 + bool "i.MX27 support" + select CPU_ARM926T + select MXC_AVIC + select PINCTRL_IMX27 + help + This enables support for Freescale i.MX27 processor + +endif + +if ARCH_MULTI_V7 + +comment "Cortex-A platforms" + +config SOC_IMX5 + bool + select HAVE_IMX_SRC + select MXC_TZIC + +config SOC_IMX50 + bool "i.MX50 support" + select PINCTRL_IMX50 + select SOC_IMX5 + + help + This enables support for Freescale i.MX50 processor. + +config SOC_IMX51 + bool "i.MX51 support" + select PINCTRL_IMX51 + select SOC_IMX5 + help + This enables support for Freescale i.MX51 processor + +config SOC_IMX53 + bool "i.MX53 support" + select PINCTRL_IMX53 + select SOC_IMX5 + + help + This enables support for Freescale i.MX53 processor. + +config SOC_IMX6 + bool + select ARM_CPU_SUSPEND if (PM || CPU_IDLE) + select ARM_GIC + select HAVE_IMX_ANATOP + select HAVE_IMX_GPC + select HAVE_IMX_MMDC + select HAVE_IMX_SRC + select MFD_SYSCON + select PL310_ERRATA_769419 if CACHE_L2X0 + +config SOC_IMX6Q + bool "i.MX6 Quad/DualLite support" + select ARM_ERRATA_764369 if SMP + select ARM_ERRATA_754322 + select ARM_ERRATA_775420 + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD + select PINCTRL_IMX6Q + select SOC_IMX6 + + help + This enables support for Freescale i.MX6 Quad processor. + +config SOC_IMX6SL + bool "i.MX6 SoloLite support" + select ARM_ERRATA_754322 + select ARM_ERRATA_775420 + select PINCTRL_IMX6SL + select SOC_IMX6 + + help + This enables support for Freescale i.MX6 SoloLite processor. + +config SOC_IMX6SLL + bool "i.MX6 SoloLiteLite support" + select ARM_ERRATA_754322 + select ARM_ERRATA_775420 + select PINCTRL_IMX6SLL + select SOC_IMX6 + + help + This enables support for Freescale i.MX6 SoloLiteLite processor. + +config SOC_IMX6SX + bool "i.MX6 SoloX support" + select ARM_ERRATA_754322 + select ARM_ERRATA_775420 + select PINCTRL_IMX6SX + select SOC_IMX6 + + help + This enables support for Freescale i.MX6 SoloX processor. + +config SOC_IMX6UL + bool "i.MX6 UltraLite support" + select PINCTRL_IMX6UL + select SOC_IMX6 + select ARM_ERRATA_814220 + + help + This enables support for Freescale i.MX6 UltraLite processor. + +config SOC_LS1021A + bool "Freescale LS1021A support" + select ARM_GIC + select HAVE_ARM_ARCH_TIMER + select ZONE_DMA if ARM_LPAE + help + This enables support for Freescale LS1021A processor. + +endif + +if ARCH_MULTI_V7 || ARM_SINGLE_ARMV7M + +comment "Cortex-A/Cortex-M asymmetric multiprocessing platforms" + +config SOC_IMX7D_CA7 + bool + select ARM_GIC + select HAVE_ARM_ARCH_TIMER + select HAVE_IMX_ANATOP + select HAVE_IMX_MMDC + select HAVE_IMX_SRC + select IMX_GPCV2 + +config SOC_IMX7D_CM4 + bool + select ARMV7M_SYSTICK + +config SOC_IMX7D + bool "i.MX7 Dual support" + select PINCTRL_IMX7D + select SOC_IMX7D_CA7 if ARCH_MULTI_V7 + select SOC_IMX7D_CM4 if ARM_SINGLE_ARMV7M + select ARM_ERRATA_814220 if ARCH_MULTI_V7 + help + This enables support for Freescale i.MX7 Dual processor. + +config SOC_IMX7ULP + bool "i.MX7ULP support" + select CLKSRC_IMX_TPM + select PINCTRL_IMX7ULP + select SOC_IMX7D_CA7 if ARCH_MULTI_V7 + select SOC_IMX7D_CM4 if ARM_SINGLE_ARMV7M + help + This enables support for Freescale i.MX7 Ultra Low Power processor. + +config SOC_IMXRT + bool "i.MXRT support" + depends on ARM_SINGLE_ARMV7M + select ARMV7M_SYSTICK if ARM_SINGLE_ARMV7M + help + This enables support for Freescale i.MXRT Crossover processor. + +config SOC_VF610 + bool "Vybrid Family VF610 support" + select ARM_GIC if ARCH_MULTI_V7 + select PINCTRL_VF610 + + help + This enables support for Freescale Vybrid VF610 processor. + +choice + prompt "Clocksource for scheduler clock" + depends on SOC_VF610 + default VF_USE_ARM_GLOBAL_TIMER + + config VF_USE_ARM_GLOBAL_TIMER + bool "Use ARM Global Timer" + depends on ARCH_MULTI_V7 + select ARM_GLOBAL_TIMER + select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK + help + Use the ARM Global Timer as clocksource + + config VF_USE_PIT_TIMER + bool "Use PIT timer" + select VF_PIT_TIMER + help + Use SoC Periodic Interrupt Timer (PIT) as clocksource + +endchoice + +endif + +endif diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile new file mode 100644 index 000000000..6fb3965b9 --- /dev/null +++ b/arch/arm/mach-imx/Makefile @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-y := cpu.o system.o irq-common.o + +obj-$(CONFIG_SOC_IMX25) += cpu-imx25.o mach-imx25.o pm-imx25.o + +obj-$(CONFIG_SOC_IMX27) += cpu-imx27.o pm-imx27.o mach-imx27.o + +obj-$(CONFIG_SOC_IMX31) += mm-imx3.o cpu-imx31.o mach-imx31.o +obj-$(CONFIG_SOC_IMX35) += mm-imx3.o cpu-imx35.o mach-imx35.o + +imx5-pm-$(CONFIG_PM) += pm-imx5.o +obj-$(CONFIG_SOC_IMX5) += cpu-imx5.o $(imx5-pm-y) + +obj-$(CONFIG_MXC_TZIC) += tzic.o +obj-$(CONFIG_MXC_AVIC) += avic.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 +endif + +ifdef CONFIG_SND_SOC_IMX_PCM_FIQ +obj-y += ssi-fiq.o +obj-y += ssi-fiq-ksym.o +endif + +obj-$(CONFIG_HAVE_IMX_ANATOP) += anatop.o +obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o +obj-$(CONFIG_HAVE_IMX_MMDC) += mmdc.o +obj-$(CONFIG_HAVE_IMX_SRC) += src.o +ifneq ($(CONFIG_SOC_IMX6)$(CONFIG_SOC_IMX7D_CA7)$(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_IMX6UL) += mach-imx6ul.o +obj-$(CONFIG_SOC_IMX7D_CA7) += mach-imx7d.o +obj-$(CONFIG_SOC_IMX7D_CM4) += mach-imx7d-cm4.o +obj-$(CONFIG_SOC_IMX7ULP) += mach-imx7ulp.o pm-imx7ulp.o + +ifeq ($(CONFIG_SUSPEND),y) +AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o +obj-$(CONFIG_SOC_IMX53) += suspend-imx53.o +endif +ifeq ($(CONFIG_ARM_CPU_SUSPEND),y) +AFLAGS_resume-imx6.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_IMX6) += resume-imx6.o +endif +obj-$(CONFIG_SOC_IMX6) += pm-imx6.o + +obj-$(CONFIG_SOC_IMX1) += mach-imx1.o +obj-$(CONFIG_SOC_IMX50) += mach-imx50.o +obj-$(CONFIG_SOC_IMX51) += mach-imx51.o +obj-$(CONFIG_SOC_IMX53) += mach-imx53.o + +obj-$(CONFIG_SOC_IMXRT) += mach-imxrt.o + +obj-$(CONFIG_SOC_VF610) += mach-vf610.o + +obj-$(CONFIG_SOC_LS1021A) += mach-ls1021a.o diff --git a/arch/arm/mach-imx/anatop.c b/arch/arm/mach-imx/anatop.c new file mode 100644 index 000000000..7bb47eb3f --- /dev/null +++ b/arch/arm/mach-imx/anatop.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2013-2015 Freescale Semiconductor, Inc. + * Copyright 2017-2018 NXP. + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include "common.h" +#include "hardware.h" + +#define REG_SET 0x4 +#define REG_CLR 0x8 + +#define ANADIG_REG_2P5 0x130 +#define ANADIG_REG_CORE 0x140 +#define ANADIG_ANA_MISC0 0x150 +#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_ANA_MISC0_STOP_MODE_CONFIG 0x1000 +/* Below MISC0_DISCON_HIGH_SNVS is only for i.MX6SL */ +#define BM_ANADIG_ANA_MISC0_DISCON_HIGH_SNVS 0x2000 + +static struct regmap *anatop; + +static void imx_anatop_enable_weak2p5(bool enable) +{ + u32 reg, val; + + regmap_read(anatop, ANADIG_ANA_MISC0, &val); + + /* 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; + regmap_write(anatop, reg, BM_ANADIG_REG_2P5_ENABLE_WEAK_LINREG); +} + +static void imx_anatop_enable_fet_odrive(bool enable) +{ + regmap_write(anatop, ANADIG_REG_CORE + (enable ? REG_SET : REG_CLR), + BM_ANADIG_REG_CORE_FET_ODRIVE); +} + +static inline void imx_anatop_enable_2p5_pulldown(bool enable) +{ + regmap_write(anatop, ANADIG_REG_2P5 + (enable ? REG_SET : REG_CLR), + BM_ANADIG_REG_2P5_ENABLE_PULLDOWN); +} + +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); +} + +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); + + imx_anatop_enable_fet_odrive(true); + + if (cpu_is_imx6sl()) + 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); + + imx_anatop_enable_fet_odrive(false); + + if (cpu_is_imx6sl()) + imx_anatop_disconnect_high_snvs(false); +} + +void __init imx_init_revision_from_anatop(void) +{ + struct device_node *np, *src_np; + void __iomem *anatop_base; + unsigned int revision; + u32 digprog; + u16 offset = ANADIG_DIGPROG; + u8 major_part, minor_part; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop"); + anatop_base = of_iomap(np, 0); + WARN_ON(!anatop_base); + if (of_device_is_compatible(np, "fsl,imx6sl-anatop")) + offset = ANADIG_DIGPROG_IMX6SL; + if (of_device_is_compatible(np, "fsl,imx7d-anatop")) + offset = ANADIG_DIGPROG_IMX7D; + digprog = readl_relaxed(anatop_base + offset); + iounmap(anatop_base); + + /* + * On i.MX7D digprog value match linux version format, so + * it needn't map again and we can use register value directly. + */ + if (of_device_is_compatible(np, "fsl,imx7d-anatop")) { + revision = digprog & 0xff; + } else { + /* + * MAJOR: [15:8], the major silicon revison; + * MINOR: [7: 0], the minor silicon revison; + * + * please refer to the i.MX RM for the detailed + * silicon revison bit define. + * format the major part and minor part to match the + * linux kernel soc version format. + */ + 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; + + src_np = of_find_compatible_node(NULL, NULL, + "fsl,imx6ul-src"); + src_base = of_iomap(src_np, 0); + of_node_put(src_np); + 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); + } + } + } + of_node_put(np); + + mxc_set_cpu_type(digprog >> 16 & 0xff); + imx_set_soc_revision(revision); +} + +void __init imx_anatop_init(void) +{ + anatop = syscon_regmap_lookup_by_compatible("fsl,imx6q-anatop"); + if (IS_ERR(anatop)) + pr_err("%s: failed to find imx6q-anatop regmap!\n", __func__); +} diff --git a/arch/arm/mach-imx/avic.c b/arch/arm/mach-imx/avic.c new file mode 100644 index 000000000..cf6546ddc --- /dev/null +++ b/arch/arm/mach-imx/avic.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + */ + +#include <linux/module.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/irqchip.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <asm/mach/irq.h> +#include <asm/exception.h> + +#include "common.h" +#include "hardware.h" +#include "irq-common.h" + +#define AVIC_INTCNTL 0x00 /* int control reg */ +#define AVIC_NIMASK 0x04 /* int mask reg */ +#define AVIC_INTENNUM 0x08 /* int enable number reg */ +#define AVIC_INTDISNUM 0x0C /* int disable number reg */ +#define AVIC_INTENABLEH 0x10 /* int enable reg high */ +#define AVIC_INTENABLEL 0x14 /* int enable reg low */ +#define AVIC_INTTYPEH 0x18 /* int type reg high */ +#define AVIC_INTTYPEL 0x1C /* int type reg low */ +#define AVIC_NIPRIORITY(x) (0x20 + 4 * (7 - (x))) /* int priority */ +#define AVIC_NIVECSR 0x40 /* norm int vector/status */ +#define AVIC_FIVECSR 0x44 /* fast int vector/status */ +#define AVIC_INTSRCH 0x48 /* int source reg high */ +#define AVIC_INTSRCL 0x4C /* int source reg low */ +#define AVIC_INTFRCH 0x50 /* int force reg high */ +#define AVIC_INTFRCL 0x54 /* int force reg low */ +#define AVIC_NIPNDH 0x58 /* norm int pending high */ +#define AVIC_NIPNDL 0x5C /* norm int pending low */ +#define AVIC_FIPNDH 0x60 /* fast int pending high */ +#define AVIC_FIPNDL 0x64 /* fast int pending low */ + +#define AVIC_NUM_IRQS 64 + +/* low power interrupt mask registers */ +#define MX25_CCM_LPIMR0 0x68 +#define MX25_CCM_LPIMR1 0x6C + +static void __iomem *avic_base; +static void __iomem *mx25_ccm_base; +static struct irq_domain *domain; + +#ifdef CONFIG_FIQ +static int avic_set_irq_fiq(unsigned int hwirq, unsigned int type) +{ + unsigned int irqt; + + if (hwirq >= AVIC_NUM_IRQS) + return -EINVAL; + + if (hwirq < AVIC_NUM_IRQS / 2) { + irqt = imx_readl(avic_base + AVIC_INTTYPEL) & ~(1 << hwirq); + imx_writel(irqt | (!!type << hwirq), avic_base + AVIC_INTTYPEL); + } else { + hwirq -= AVIC_NUM_IRQS / 2; + irqt = imx_readl(avic_base + AVIC_INTTYPEH) & ~(1 << hwirq); + imx_writel(irqt | (!!type << hwirq), avic_base + AVIC_INTTYPEH); + } + + return 0; +} +#endif /* CONFIG_FIQ */ + + +static struct mxc_extra_irq avic_extra_irq = { +#ifdef CONFIG_FIQ + .set_irq_fiq = avic_set_irq_fiq, +#endif +}; + +#ifdef CONFIG_PM +static u32 avic_saved_mask_reg[2]; + +static void avic_irq_suspend(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct irq_chip_type *ct = gc->chip_types; + int idx = d->hwirq >> 5; + + avic_saved_mask_reg[idx] = imx_readl(avic_base + ct->regs.mask); + imx_writel(gc->wake_active, avic_base + ct->regs.mask); + + if (mx25_ccm_base) { + u8 offs = d->hwirq < AVIC_NUM_IRQS / 2 ? + MX25_CCM_LPIMR0 : MX25_CCM_LPIMR1; + /* + * The interrupts which are still enabled will be used as wakeup + * sources. Allow those interrupts in low-power mode. + * The LPIMR registers use 0 to allow an interrupt, the AVIC + * registers use 1. + */ + imx_writel(~gc->wake_active, mx25_ccm_base + offs); + } +} + +static void avic_irq_resume(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct irq_chip_type *ct = gc->chip_types; + int idx = d->hwirq >> 5; + + imx_writel(avic_saved_mask_reg[idx], avic_base + ct->regs.mask); + + if (mx25_ccm_base) { + u8 offs = d->hwirq < AVIC_NUM_IRQS / 2 ? + MX25_CCM_LPIMR0 : MX25_CCM_LPIMR1; + + imx_writel(0xffffffff, mx25_ccm_base + offs); + } +} + +#else +#define avic_irq_suspend NULL +#define avic_irq_resume NULL +#endif + +static __init void avic_init_gc(int idx, unsigned int irq_start) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + + gc = irq_alloc_generic_chip("mxc-avic", 1, irq_start, avic_base, + handle_level_irq); + gc->private = &avic_extra_irq; + gc->wake_enabled = IRQ_MSK(32); + + ct = gc->chip_types; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + ct->chip.irq_ack = irq_gc_mask_clr_bit; + ct->chip.irq_set_wake = irq_gc_set_wake; + ct->chip.irq_suspend = avic_irq_suspend; + ct->chip.irq_resume = avic_irq_resume; + ct->regs.mask = !idx ? AVIC_INTENABLEL : AVIC_INTENABLEH; + ct->regs.ack = ct->regs.mask; + + irq_setup_generic_chip(gc, IRQ_MSK(32), 0, IRQ_NOREQUEST, 0); +} + +static void __exception_irq_entry avic_handle_irq(struct pt_regs *regs) +{ + u32 nivector; + + do { + nivector = imx_readl(avic_base + AVIC_NIVECSR) >> 16; + if (nivector == 0xffff) + break; + + generic_handle_domain_irq(domain, nivector); + } while (1); +} + +/* + * This function initializes the AVIC hardware and disables all the + * interrupts. It registers the interrupt enable and disable functions + * to the kernel for each interrupt source. + */ +static void __init mxc_init_irq(void __iomem *irqbase) +{ + struct device_node *np; + int irq_base; + int i; + + avic_base = irqbase; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx25-ccm"); + mx25_ccm_base = of_iomap(np, 0); + + if (mx25_ccm_base) { + /* + * By default, we mask all interrupts. We set the actual mask + * before we go into low-power mode. + */ + imx_writel(0xffffffff, mx25_ccm_base + MX25_CCM_LPIMR0); + imx_writel(0xffffffff, mx25_ccm_base + MX25_CCM_LPIMR1); + } + + /* put the AVIC into the reset value with + * all interrupts disabled + */ + imx_writel(0, avic_base + AVIC_INTCNTL); + imx_writel(0x1f, avic_base + AVIC_NIMASK); + + /* disable all interrupts */ + imx_writel(0, avic_base + AVIC_INTENABLEH); + imx_writel(0, avic_base + AVIC_INTENABLEL); + + /* all IRQ no FIQ */ + imx_writel(0, avic_base + AVIC_INTTYPEH); + imx_writel(0, avic_base + AVIC_INTTYPEL); + + irq_base = irq_alloc_descs(-1, 0, AVIC_NUM_IRQS, numa_node_id()); + WARN_ON(irq_base < 0); + + np = of_find_compatible_node(NULL, NULL, "fsl,avic"); + domain = irq_domain_add_legacy(np, AVIC_NUM_IRQS, irq_base, 0, + &irq_domain_simple_ops, NULL); + WARN_ON(!domain); + + for (i = 0; i < AVIC_NUM_IRQS / 32; i++, irq_base += 32) + avic_init_gc(i, irq_base); + + /* Set default priority value (0) for all IRQ's */ + for (i = 0; i < 8; i++) + imx_writel(0, avic_base + AVIC_NIPRIORITY(i)); + + set_handle_irq(avic_handle_irq); + +#ifdef CONFIG_FIQ + /* Initialize FIQ */ + init_FIQ(FIQ_START); +#endif + + printk(KERN_INFO "MXC IRQ initialized\n"); +} + +static int __init imx_avic_init(struct device_node *node, + struct device_node *parent) +{ + void __iomem *avic_base; + + avic_base = of_iomap(node, 0); + BUG_ON(!avic_base); + mxc_init_irq(avic_base); + return 0; +} + +IRQCHIP_DECLARE(imx_avic, "fsl,avic", imx_avic_init); diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h new file mode 100644 index 000000000..13f3068e9 --- /dev/null +++ b/arch/arm/mach-imx/common.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2004-2014 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + +#ifndef __ASM_ARCH_MXC_COMMON_H__ +#define __ASM_ARCH_MXC_COMMON_H__ + +#include <linux/reboot.h> + +struct irq_data; +struct platform_device; +struct pt_regs; +struct clk; +struct device_node; +enum mxc_cpu_pwr_mode; +struct of_device_id; + +void mx31_map_io(void); +void mx35_map_io(void); +void imx21_init_early(void); +void imx31_init_early(void); +void imx35_init_early(void); +void mx31_init_irq(void); +void mx35_init_irq(void); +void mxc_set_cpu_type(unsigned int type); +void mxc_restart(enum reboot_mode, const char *); +void mxc_arch_reset_init(void __iomem *); +void imx1_reset_init(void __iomem *); +void imx_set_aips(void __iomem *); +void imx_aips_allow_unprivileged_access(const char *compat); +int mxc_device_init(void); +void imx_set_soc_revision(unsigned int rev); +void imx_init_revision_from_anatop(void); +void imx6_enable_rbc(bool enable); +void imx_gpc_check_dt(void); +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 imx25_pm_init(void); +void imx27_pm_init(void); +void imx5_pmu_init(void); + +enum mxc_cpu_pwr_mode { + WAIT_CLOCKED, /* wfi only */ + WAIT_UNCLOCKED, /* WAIT */ + WAIT_UNCLOCKED_POWER_OFF, /* WAIT + SRPG */ + STOP_POWER_ON, /* just STOP */ + STOP_POWER_OFF, /* STOP + SRPG */ +}; + +enum ulp_cpu_pwr_mode { + ULP_PM_HSRUN, /* High speed run mode */ + ULP_PM_RUN, /* Run mode */ + ULP_PM_WAIT, /* Wait mode */ + ULP_PM_STOP, /* Stop mode */ + ULP_PM_VLPS, /* Very low power stop mode */ + ULP_PM_VLLS, /* very low leakage stop mode */ +}; + +void imx_enable_cpu(int cpu, bool enable); +void imx_set_cpu_jump(int cpu, void *jump_addr); +u32 imx_get_cpu_arg(int cpu); +void imx_set_cpu_arg(int cpu, u32 arg); +#ifdef CONFIG_SMP +void v7_secondary_startup(void); +void imx_scu_map_io(void); +void imx_smp_prepare(void); +#else +static inline void imx_scu_map_io(void) {} +static inline void imx_smp_prepare(void) {} +#endif +void imx_src_init(void); +void imx7_src_init(void); +void imx_gpc_pre_suspend(bool arm_power_off); +void imx_gpc_post_resume(void); +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); +void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn); +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); +int imx_mmdc_get_ddr_type(void); +int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode); + +void imx_cpu_die(unsigned int cpu); +int imx_cpu_kill(unsigned int cpu); + +#ifdef CONFIG_SUSPEND +void imx53_suspend(void __iomem *ocram_vbase); +extern const u32 imx53_suspend_sz; +void imx6_suspend(void __iomem *ocram_vbase); +#else +static inline void imx53_suspend(void __iomem *ocram_vbase) {} +static const u32 imx53_suspend_sz; +static inline void imx6_suspend(void __iomem *ocram_vbase) {} +#endif + +void v7_cpu_resume(void); + +void imx6_pm_ccm_init(const char *ccm_compat); +void imx6q_pm_init(void); +void imx6dl_pm_init(void); +void imx6sl_pm_init(void); +void imx6sx_pm_init(void); +void imx6ul_pm_init(void); +void imx7ulp_pm_init(void); + +#ifdef CONFIG_PM +void imx51_pm_init(void); +void imx53_pm_init(void); +#else +static inline void imx51_pm_init(void) {} +static inline void imx53_pm_init(void) {} +#endif + +#ifdef CONFIG_NEON +int mx51_neon_fixup(void); +#else +static inline int mx51_neon_fixup(void) { return 0; } +#endif + +#ifdef CONFIG_CACHE_L2X0 +void imx_init_l2cache(void); +#else +static inline void imx_init_l2cache(void) {} +#endif + +extern const struct smp_operations imx_smp_ops; +extern const struct smp_operations imx7_smp_ops; +extern const struct smp_operations ls1021a_smp_ops; + +#endif diff --git a/arch/arm/mach-imx/cpu-imx25.c b/arch/arm/mach-imx/cpu-imx25.c new file mode 100644 index 000000000..cc86977d0 --- /dev/null +++ b/arch/arm/mach-imx/cpu-imx25.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MX25 CPU type detection + * + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved + */ +#include <linux/module.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include "iim.h" +#include "hardware.h" + +static int mx25_cpu_rev = -1; + +static int mx25_read_cpu_rev(void) +{ + u32 rev; + void __iomem *iim_base; + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx25-iim"); + iim_base = of_iomap(np, 0); + of_node_put(np); + BUG_ON(!iim_base); + rev = readl(iim_base + MXC_IIMSREV); + iounmap(iim_base); + + switch (rev) { + case 0x00: + return IMX_CHIP_REVISION_1_0; + case 0x01: + return IMX_CHIP_REVISION_1_1; + case 0x02: + return IMX_CHIP_REVISION_1_2; + default: + return IMX_CHIP_REVISION_UNKNOWN; + } +} + +int mx25_revision(void) +{ + if (mx25_cpu_rev == -1) + mx25_cpu_rev = mx25_read_cpu_rev(); + + return mx25_cpu_rev; +} +EXPORT_SYMBOL(mx25_revision); diff --git a/arch/arm/mach-imx/cpu-imx27.c b/arch/arm/mach-imx/cpu-imx27.c new file mode 100644 index 000000000..1d2893908 --- /dev/null +++ b/arch/arm/mach-imx/cpu-imx27.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + */ + +/* + * i.MX27 specific CPU detection code + */ + +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/module.h> + +#include "hardware.h" + +static int mx27_cpu_rev = -1; +static int mx27_cpu_partnumber; + +#define SYS_CHIP_ID 0x00 /* The offset of CHIP ID register */ +#define SYSCTRL_OFFSET 0x800 /* Offset from CCM base address */ + +static int mx27_read_cpu_rev(void) +{ + void __iomem *ccm_base; + struct device_node *np; + u32 val; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx27-ccm"); + ccm_base = of_iomap(np, 0); + of_node_put(np); + BUG_ON(!ccm_base); + /* + * now we have access to the IO registers. As we need + * the silicon revision very early we read it here to + * avoid any further hooks + */ + val = imx_readl(ccm_base + SYSCTRL_OFFSET + SYS_CHIP_ID); + + mx27_cpu_partnumber = (int)((val >> 12) & 0xFFFF); + + switch (val >> 28) { + case 0: + return IMX_CHIP_REVISION_1_0; + case 1: + return IMX_CHIP_REVISION_2_0; + case 2: + return IMX_CHIP_REVISION_2_1; + default: + return IMX_CHIP_REVISION_UNKNOWN; + } +} + +/* + * Returns: + * the silicon revision of the cpu + * -EINVAL - not a mx27 + */ +int mx27_revision(void) +{ + if (mx27_cpu_rev == -1) + mx27_cpu_rev = mx27_read_cpu_rev(); + + if (mx27_cpu_partnumber != 0x8821) + return -EINVAL; + + return mx27_cpu_rev; +} +EXPORT_SYMBOL(mx27_revision); diff --git a/arch/arm/mach-imx/cpu-imx31.c b/arch/arm/mach-imx/cpu-imx31.c new file mode 100644 index 000000000..35c544924 --- /dev/null +++ b/arch/arm/mach-imx/cpu-imx31.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MX31 CPU type detection + * + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + */ + +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/io.h> + +#include "common.h" +#include "hardware.h" +#include "iim.h" + +static int mx31_cpu_rev = -1; + +static struct { + u8 srev; + const char *name; + unsigned int rev; +} mx31_cpu_type[] = { + { .srev = 0x00, .name = "i.MX31(L)", .rev = IMX_CHIP_REVISION_1_0 }, + { .srev = 0x10, .name = "i.MX31", .rev = IMX_CHIP_REVISION_1_1 }, + { .srev = 0x11, .name = "i.MX31L", .rev = IMX_CHIP_REVISION_1_1 }, + { .srev = 0x12, .name = "i.MX31", .rev = IMX_CHIP_REVISION_1_1 }, + { .srev = 0x13, .name = "i.MX31L", .rev = IMX_CHIP_REVISION_1_1 }, + { .srev = 0x14, .name = "i.MX31", .rev = IMX_CHIP_REVISION_1_2 }, + { .srev = 0x15, .name = "i.MX31L", .rev = IMX_CHIP_REVISION_1_2 }, + { .srev = 0x28, .name = "i.MX31", .rev = IMX_CHIP_REVISION_2_0 }, + { .srev = 0x29, .name = "i.MX31L", .rev = IMX_CHIP_REVISION_2_0 }, +}; + +static int mx31_read_cpu_rev(void) +{ + void __iomem *iim_base; + struct device_node *np; + u32 i, srev; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx31-iim"); + iim_base = of_iomap(np, 0); + of_node_put(np); + BUG_ON(!iim_base); + + /* read SREV register from IIM module */ + srev = imx_readl(iim_base + MXC_IIMSREV); + srev &= 0xff; + + for (i = 0; i < ARRAY_SIZE(mx31_cpu_type); i++) + if (srev == mx31_cpu_type[i].srev) { + imx_print_silicon_rev(mx31_cpu_type[i].name, + mx31_cpu_type[i].rev); + return mx31_cpu_type[i].rev; + } + + imx_print_silicon_rev("i.MX31", IMX_CHIP_REVISION_UNKNOWN); + return IMX_CHIP_REVISION_UNKNOWN; +} + +int mx31_revision(void) +{ + if (mx31_cpu_rev == -1) + mx31_cpu_rev = mx31_read_cpu_rev(); + + return mx31_cpu_rev; +} +EXPORT_SYMBOL(mx31_revision); diff --git a/arch/arm/mach-imx/cpu-imx35.c b/arch/arm/mach-imx/cpu-imx35.c new file mode 100644 index 000000000..1fe75b39c --- /dev/null +++ b/arch/arm/mach-imx/cpu-imx35.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MX35 CPU type detection + * + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + */ +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/io.h> + +#include "hardware.h" +#include "iim.h" + +static int mx35_cpu_rev = -1; + +static int mx35_read_cpu_rev(void) +{ + void __iomem *iim_base; + struct device_node *np; + u32 rev; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx35-iim"); + iim_base = of_iomap(np, 0); + of_node_put(np); + BUG_ON(!iim_base); + + rev = imx_readl(iim_base + MXC_IIMSREV); + switch (rev) { + case 0x00: + return IMX_CHIP_REVISION_1_0; + case 0x10: + return IMX_CHIP_REVISION_2_0; + case 0x11: + return IMX_CHIP_REVISION_2_1; + default: + return IMX_CHIP_REVISION_UNKNOWN; + } +} + +int mx35_revision(void) +{ + if (mx35_cpu_rev == -1) + mx35_cpu_rev = mx35_read_cpu_rev(); + + return mx35_cpu_rev; +} +EXPORT_SYMBOL(mx35_revision); diff --git a/arch/arm/mach-imx/cpu-imx5.c b/arch/arm/mach-imx/cpu-imx5.c new file mode 100644 index 000000000..a67c89bf1 --- /dev/null +++ b/arch/arm/mach-imx/cpu-imx5.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This file contains the CPU initialization code. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include "hardware.h" +#include "common.h" + +static int mx5_cpu_rev = -1; + +#define IIM_SREV 0x24 + +static u32 imx5_read_srev_reg(const char *compat) +{ + void __iomem *iim_base; + struct device_node *np; + u32 srev; + + np = of_find_compatible_node(NULL, NULL, compat); + iim_base = of_iomap(np, 0); + of_node_put(np); + WARN_ON(!iim_base); + + srev = readl(iim_base + IIM_SREV) & 0xff; + + iounmap(iim_base); + + return srev; +} + +static int get_mx51_srev(void) +{ + u32 rev = imx5_read_srev_reg("fsl,imx51-iim"); + + switch (rev) { + case 0x0: + return IMX_CHIP_REVISION_2_0; + case 0x10: + return IMX_CHIP_REVISION_3_0; + default: + return IMX_CHIP_REVISION_UNKNOWN; + } +} + +/* + * Returns: + * the silicon revision of the cpu + */ +int mx51_revision(void) +{ + if (mx5_cpu_rev == -1) + mx5_cpu_rev = get_mx51_srev(); + + return mx5_cpu_rev; +} +EXPORT_SYMBOL(mx51_revision); + +#ifdef CONFIG_NEON + +/* + * All versions of the silicon before Rev. 3 have broken NEON implementations. + * Dependent on link order - so the assumption is that vfp_init is called + * before us. + */ +int __init mx51_neon_fixup(void) +{ + if (mx51_revision() < IMX_CHIP_REVISION_3_0 && + (elf_hwcap & HWCAP_NEON)) { + elf_hwcap &= ~HWCAP_NEON; + pr_info("Turning off NEON support, detected broken NEON implementation\n"); + } + return 0; +} + +#endif + +static int get_mx53_srev(void) +{ + u32 rev = imx5_read_srev_reg("fsl,imx53-iim"); + + switch (rev) { + case 0x0: + return IMX_CHIP_REVISION_1_0; + case 0x2: + return IMX_CHIP_REVISION_2_0; + case 0x3: + return IMX_CHIP_REVISION_2_1; + default: + return IMX_CHIP_REVISION_UNKNOWN; + } +} + +/* + * Returns: + * the silicon revision of the cpu + */ +int mx53_revision(void) +{ + if (mx5_cpu_rev == -1) + mx5_cpu_rev = get_mx53_srev(); + + return mx5_cpu_rev; +} +EXPORT_SYMBOL(mx53_revision); + +#define ARM_GPC 0x4 +#define DBGEN BIT(16) + +/* + * This enables the DBGEN bit in ARM_GPC register, which is + * required for accessing some performance counter features. + * Technically it is only required while perf is used, but to + * keep the source code simple we just enable it all the time + * when the kernel configuration allows using the feature. + */ +void __init imx5_pmu_init(void) +{ + void __iomem *tigerp_base; + struct device_node *np; + u32 gpc; + + if (!IS_ENABLED(CONFIG_ARM_PMU)) + return; + + np = of_find_compatible_node(NULL, NULL, "arm,cortex-a8-pmu"); + if (!np) + return; + + if (!of_property_read_bool(np, "secure-reg-access")) + goto exit; + + of_node_put(np); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx51-tigerp"); + if (!np) + return; + + tigerp_base = of_iomap(np, 0); + if (!tigerp_base) + goto exit; + + gpc = readl_relaxed(tigerp_base + ARM_GPC); + gpc |= DBGEN; + writel_relaxed(gpc, tigerp_base + ARM_GPC); + iounmap(tigerp_base); +exit: + of_node_put(np); + +} diff --git a/arch/arm/mach-imx/cpu.c b/arch/arm/mach-imx/cpu.c new file mode 100644 index 000000000..65c7224f5 --- /dev/null +++ b/arch/arm/mach-imx/cpu.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/err.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include "hardware.h" +#include "common.h" + +unsigned int __mxc_cpu_type; +static unsigned int imx_soc_revision; + +void mxc_set_cpu_type(unsigned int type) +{ + __mxc_cpu_type = type; +} + +void imx_set_soc_revision(unsigned int rev) +{ + imx_soc_revision = rev; +} + +unsigned int imx_get_soc_revision(void) +{ + return imx_soc_revision; +} + +void imx_print_silicon_rev(const char *cpu, int srev) +{ + if (srev == IMX_CHIP_REVISION_UNKNOWN) + pr_info("CPU identified as %s, unknown revision\n", cpu); + else + pr_info("CPU identified as %s, silicon rev %d.%d\n", + cpu, (srev >> 4) & 0xf, srev & 0xf); +} + +void __init imx_set_aips(void __iomem *base) +{ + unsigned int reg; +/* + * Set all MPROTx to be non-bufferable, trusted for R/W, + * not forced to user-mode. + */ + imx_writel(0x77777777, base + 0x0); + imx_writel(0x77777777, base + 0x4); + +/* + * Set all OPACRx to be non-bufferable, to not require + * supervisor privilege level for access, allow for + * write access and untrusted master access. + */ + imx_writel(0x0, base + 0x40); + imx_writel(0x0, base + 0x44); + imx_writel(0x0, base + 0x48); + imx_writel(0x0, base + 0x4C); + reg = imx_readl(base + 0x50) & 0x00FFFFFF; + imx_writel(reg, base + 0x50); +} + +void __init imx_aips_allow_unprivileged_access( + const char *compat) +{ + void __iomem *aips_base_addr; + struct device_node *np; + + for_each_compatible_node(np, NULL, compat) { + aips_base_addr = of_iomap(np, 0); + WARN_ON(!aips_base_addr); + imx_set_aips(aips_base_addr); + } +} diff --git a/arch/arm/mach-imx/cpuidle-imx5.c b/arch/arm/mach-imx/cpuidle-imx5.c new file mode 100644 index 000000000..a8457c4eb --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx5.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. + */ + +#include <linux/cpuidle.h> +#include <linux/module.h> +#include <asm/system_misc.h> +#include "cpuidle.h" + +static int imx5_cpuidle_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + arm_pm_idle(); + return index; +} + +static struct cpuidle_driver imx5_cpuidle_driver = { + .name = "imx5_cpuidle", + .owner = THIS_MODULE, + .states[0] = { + .enter = imx5_cpuidle_enter, + .exit_latency = 2, + .target_residency = 1, + .name = "IMX5 SRPG", + .desc = "CPU state retained,powered off", + }, + .state_count = 1, +}; + +int __init imx5_cpuidle_init(void) +{ + return cpuidle_register(&imx5_cpuidle_driver, NULL); +} diff --git a/arch/arm/mach-imx/cpuidle-imx6q.c b/arch/arm/mach-imx/cpuidle-imx6q.c new file mode 100644 index 000000000..d086cbae0 --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx6q.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. + */ + +#include <linux/context_tracking.h> +#include <linux/cpuidle.h> +#include <linux/module.h> +#include <asm/cpuidle.h> + +#include <soc/imx/cpuidle.h> + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +static int num_idle_cpus = 0; +static DEFINE_RAW_SPINLOCK(cpuidle_lock); + +static int imx6q_enter_wait(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + raw_spin_lock(&cpuidle_lock); + if (++num_idle_cpus == num_online_cpus()) + imx6_set_lpm(WAIT_UNCLOCKED); + raw_spin_unlock(&cpuidle_lock); + + ct_idle_enter(); + cpu_do_idle(); + ct_idle_exit(); + + raw_spin_lock(&cpuidle_lock); + if (num_idle_cpus-- == num_online_cpus()) + imx6_set_lpm(WAIT_CLOCKED); + raw_spin_unlock(&cpuidle_lock); + + return index; +} + +static struct cpuidle_driver imx6q_cpuidle_driver = { + .name = "imx6q_cpuidle", + .owner = THIS_MODULE, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT */ + { + .exit_latency = 50, + .target_residency = 75, + .flags = CPUIDLE_FLAG_TIMER_STOP | CPUIDLE_FLAG_RCU_IDLE, + .enter = imx6q_enter_wait, + .name = "WAIT", + .desc = "Clock off", + }, + }, + .state_count = 2, + .safe_state_index = 0, +}; + +/* + * i.MX6 Q/DL has an erratum (ERR006687) that prevents the FEC from waking the + * CPUs when they are in wait(unclocked) state. As the hardware workaround isn't + * applicable to all boards, disable the deeper idle state when the workaround + * isn't present and the FEC is in use. + */ +void imx6q_cpuidle_fec_irqs_used(void) +{ + cpuidle_driver_state_disabled(&imx6q_cpuidle_driver, 1, true); +} +EXPORT_SYMBOL_GPL(imx6q_cpuidle_fec_irqs_used); + +void imx6q_cpuidle_fec_irqs_unused(void) +{ + cpuidle_driver_state_disabled(&imx6q_cpuidle_driver, 1, false); +} +EXPORT_SYMBOL_GPL(imx6q_cpuidle_fec_irqs_unused); + +int __init imx6q_cpuidle_init(void) +{ + /* Set INT_MEM_CLK_LPM bit to get a reliable WAIT mode support */ + imx6_set_int_mem_clk_lpm(true); + + return cpuidle_register(&imx6q_cpuidle_driver, NULL); +} diff --git a/arch/arm/mach-imx/cpuidle-imx6sl.c b/arch/arm/mach-imx/cpuidle-imx6sl.c new file mode 100644 index 000000000..b86ffbeb2 --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx6sl.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Freescale Semiconductor, Inc. + */ + +#include <linux/clk/imx.h> +#include <linux/cpuidle.h> +#include <linux/module.h> +#include <asm/cpuidle.h> + +#include "common.h" +#include "cpuidle.h" + +static int imx6sl_enter_wait(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + 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); + imx6_set_lpm(WAIT_CLOCKED); + + return index; +} + +static struct cpuidle_driver imx6sl_cpuidle_driver = { + .name = "imx6sl_cpuidle", + .owner = THIS_MODULE, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT */ + { + .exit_latency = 50, + .target_residency = 75, + .flags = CPUIDLE_FLAG_TIMER_STOP, + .enter = imx6sl_enter_wait, + .name = "WAIT", + .desc = "Clock off", + }, + }, + .state_count = 2, + .safe_state_index = 0, +}; + +int __init imx6sl_cpuidle_init(void) +{ + return cpuidle_register(&imx6sl_cpuidle_driver, NULL); +} diff --git a/arch/arm/mach-imx/cpuidle-imx6sx.c b/arch/arm/mach-imx/cpuidle-imx6sx.c new file mode 100644 index 000000000..74ea1720e --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx6sx.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Freescale Semiconductor, Inc. + */ + +#include <linux/cpuidle.h> +#include <linux/cpu_pm.h> +#include <linux/module.h> +#include <asm/cacheflush.h> +#include <asm/cpuidle.h> +#include <asm/suspend.h> + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +static int imx6sx_idle_finish(unsigned long val) +{ + /* + * for Cortex-A7 which has an internal L2 + * cache, need to flush it before powering + * down ARM platform, since flushing L1 cache + * here again has very small overhead, compared + * to adding conditional code for L2 cache type, + * just call flush_cache_all() is fine. + */ + flush_cache_all(); + cpu_do_idle(); + + return 0; +} + +static int imx6sx_enter_wait(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + imx6_set_lpm(WAIT_UNCLOCKED); + + switch (index) { + case 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(); + + cpu_suspend(0, imx6sx_idle_finish); + + cpu_cluster_pm_exit(); + cpu_pm_exit(); + imx_gpc_set_arm_power_in_lpm(false); + imx6_enable_rbc(false); + break; + default: + break; + } + + imx6_set_lpm(WAIT_CLOCKED); + + return index; +} + +static struct cpuidle_driver imx6sx_cpuidle_driver = { + .name = "imx6sx_cpuidle", + .owner = THIS_MODULE, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT */ + { + .exit_latency = 50, + .target_residency = 75, + .flags = CPUIDLE_FLAG_TIMER_STOP, + .enter = imx6sx_enter_wait, + .name = "WAIT", + .desc = "Clock off", + }, + /* WAIT + ARM power off */ + { + /* + * ARM gating 31us * 5 + RBC clear 65us + * and some margin for SW execution, here set it + * to 300us. + */ + .exit_latency = 300, + .target_residency = 500, + .flags = CPUIDLE_FLAG_TIMER_STOP, + .enter = imx6sx_enter_wait, + .name = "LOW-POWER-IDLE", + .desc = "ARM power off", + }, + }, + .state_count = 3, + .safe_state_index = 0, +}; + +int __init imx6sx_cpuidle_init(void) +{ + 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); + + return cpuidle_register(&imx6sx_cpuidle_driver, NULL); +} diff --git a/arch/arm/mach-imx/cpuidle-imx7ulp.c b/arch/arm/mach-imx/cpuidle-imx7ulp.c new file mode 100644 index 000000000..ca86c967d --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx7ulp.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2018 NXP + * Anson Huang <Anson.Huang@nxp.com> + */ + +#include <linux/cpuidle.h> +#include <linux/module.h> +#include <asm/cpuidle.h> + +#include "common.h" +#include "cpuidle.h" + +static int imx7ulp_enter_wait(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + if (index == 1) + imx7ulp_set_lpm(ULP_PM_WAIT); + else + imx7ulp_set_lpm(ULP_PM_STOP); + + cpu_do_idle(); + + imx7ulp_set_lpm(ULP_PM_RUN); + + return index; +} + +static struct cpuidle_driver imx7ulp_cpuidle_driver = { + .name = "imx7ulp_cpuidle", + .owner = THIS_MODULE, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT */ + { + .exit_latency = 50, + .target_residency = 75, + .enter = imx7ulp_enter_wait, + .name = "WAIT", + .desc = "PSTOP2", + }, + /* STOP */ + { + .exit_latency = 100, + .target_residency = 150, + .enter = imx7ulp_enter_wait, + .name = "STOP", + .desc = "PSTOP1", + }, + }, + .state_count = 3, + .safe_state_index = 0, +}; + +int __init imx7ulp_cpuidle_init(void) +{ + return cpuidle_register(&imx7ulp_cpuidle_driver, NULL); +} diff --git a/arch/arm/mach-imx/cpuidle.h b/arch/arm/mach-imx/cpuidle.h new file mode 100644 index 000000000..ce552c096 --- /dev/null +++ b/arch/arm/mach-imx/cpuidle.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + */ + +#ifdef CONFIG_CPU_IDLE +extern int imx5_cpuidle_init(void); +extern int imx6q_cpuidle_init(void); +extern int imx6sl_cpuidle_init(void); +extern int imx6sx_cpuidle_init(void); +extern int imx7ulp_cpuidle_init(void); +#else +static inline int imx5_cpuidle_init(void) +{ + return 0; +} +static inline int imx6q_cpuidle_init(void) +{ + return 0; +} +static inline int imx6sl_cpuidle_init(void) +{ + return 0; +} +static inline int imx6sx_cpuidle_init(void) +{ + return 0; +} +static inline int imx7ulp_cpuidle_init(void) +{ + return 0; +} +#endif diff --git a/arch/arm/mach-imx/crmregs-imx3.h b/arch/arm/mach-imx/crmregs-imx3.h new file mode 100644 index 000000000..3e6951eee --- /dev/null +++ b/arch/arm/mach-imx/crmregs-imx3.h @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2008 by Sascha Hauer <kernel@pengutronix.de> + */ + +#ifndef __ARCH_ARM_MACH_MX3_CRM_REGS_H__ +#define __ARCH_ARM_MACH_MX3_CRM_REGS_H__ + +#define CKIH_CLK_FREQ 26000000 +#define CKIH_CLK_FREQ_27MHZ 27000000 +#define CKIL_CLK_FREQ 32768 + +extern void __iomem *mx3_ccm_base; + +/* Register addresses */ +#define MXC_CCM_CCMR 0x00 +#define MXC_CCM_PDR0 0x04 +#define MXC_CCM_PDR1 0x08 +#define MX35_CCM_PDR2 0x0C +#define MXC_CCM_RCSR 0x0C +#define MX35_CCM_PDR3 0x10 +#define MXC_CCM_MPCTL 0x10 +#define MX35_CCM_PDR4 0x14 +#define MXC_CCM_UPCTL 0x14 +#define MX35_CCM_RCSR 0x18 +#define MXC_CCM_SRPCTL 0x18 +#define MX35_CCM_MPCTL 0x1C +#define MXC_CCM_COSR 0x1C +#define MX35_CCM_PPCTL 0x20 +#define MXC_CCM_CGR0 0x20 +#define MX35_CCM_ACMR 0x24 +#define MXC_CCM_CGR1 0x24 +#define MX35_CCM_COSR 0x28 +#define MXC_CCM_CGR2 0x28 +#define MX35_CCM_CGR0 0x2C +#define MXC_CCM_WIMR 0x2C +#define MX35_CCM_CGR1 0x30 +#define MXC_CCM_LDC 0x30 +#define MX35_CCM_CGR2 0x34 +#define MXC_CCM_DCVR0 0x34 +#define MX35_CCM_CGR3 0x38 +#define MXC_CCM_DCVR1 0x38 +#define MXC_CCM_DCVR2 0x3C +#define MXC_CCM_DCVR3 0x40 +#define MXC_CCM_LTR0 0x44 +#define MXC_CCM_LTR1 0x48 +#define MXC_CCM_LTR2 0x4C +#define MXC_CCM_LTR3 0x50 +#define MXC_CCM_LTBR0 0x54 +#define MXC_CCM_LTBR1 0x58 +#define MXC_CCM_PMCR0 0x5C +#define MXC_CCM_PMCR1 0x60 +#define MXC_CCM_PDR2 0x64 + +/* Register bit definitions */ +#define MXC_CCM_CCMR_WBEN (1 << 27) +#define MXC_CCM_CCMR_CSCS (1 << 25) +#define MXC_CCM_CCMR_PERCS (1 << 24) +#define MXC_CCM_CCMR_SSI1S_OFFSET 18 +#define MXC_CCM_CCMR_SSI1S_MASK (0x3 << 18) +#define MXC_CCM_CCMR_SSI2S_OFFSET 21 +#define MXC_CCM_CCMR_SSI2S_MASK (0x3 << 21) +#define MXC_CCM_CCMR_LPM_OFFSET 14 +#define MXC_CCM_CCMR_LPM_MASK (0x3 << 14) +#define MXC_CCM_CCMR_LPM_WAIT_MX35 (0x1 << 14) +#define MXC_CCM_CCMR_FIRS_OFFSET 11 +#define MXC_CCM_CCMR_FIRS_MASK (0x3 << 11) +#define MXC_CCM_CCMR_UPE (1 << 9) +#define MXC_CCM_CCMR_SPE (1 << 8) +#define MXC_CCM_CCMR_MDS (1 << 7) +#define MXC_CCM_CCMR_SBYCS (1 << 4) +#define MXC_CCM_CCMR_MPE (1 << 3) +#define MXC_CCM_CCMR_PRCS_OFFSET 1 +#define MXC_CCM_CCMR_PRCS_MASK (0x3 << 1) + +#define MXC_CCM_PDR0_CSI_PODF_OFFSET 26 +#define MXC_CCM_PDR0_CSI_PODF_MASK (0x3F << 26) +#define MXC_CCM_PDR0_CSI_PRDF_OFFSET 23 +#define MXC_CCM_PDR0_CSI_PRDF_MASK (0x7 << 23) +#define MXC_CCM_PDR0_PER_PODF_OFFSET 16 +#define MXC_CCM_PDR0_PER_PODF_MASK (0x1F << 16) +#define MXC_CCM_PDR0_HSP_PODF_OFFSET 11 +#define MXC_CCM_PDR0_HSP_PODF_MASK (0x7 << 11) +#define MXC_CCM_PDR0_NFC_PODF_OFFSET 8 +#define MXC_CCM_PDR0_NFC_PODF_MASK (0x7 << 8) +#define MXC_CCM_PDR0_IPG_PODF_OFFSET 6 +#define MXC_CCM_PDR0_IPG_PODF_MASK (0x3 << 6) +#define MXC_CCM_PDR0_MAX_PODF_OFFSET 3 +#define MXC_CCM_PDR0_MAX_PODF_MASK (0x7 << 3) +#define MXC_CCM_PDR0_MCU_PODF_OFFSET 0 +#define MXC_CCM_PDR0_MCU_PODF_MASK 0x7 + +#define MXC_CCM_PDR1_USB_PRDF_OFFSET 30 +#define MXC_CCM_PDR1_USB_PRDF_MASK (0x3 << 30) +#define MXC_CCM_PDR1_USB_PODF_OFFSET 27 +#define MXC_CCM_PDR1_USB_PODF_MASK (0x7 << 27) +#define MXC_CCM_PDR1_FIRI_PRE_PODF_OFFSET 24 +#define MXC_CCM_PDR1_FIRI_PRE_PODF_MASK (0x7 << 24) +#define MXC_CCM_PDR1_FIRI_PODF_OFFSET 18 +#define MXC_CCM_PDR1_FIRI_PODF_MASK (0x3F << 18) +#define MXC_CCM_PDR1_SSI2_PRE_PODF_OFFSET 15 +#define MXC_CCM_PDR1_SSI2_PRE_PODF_MASK (0x7 << 15) +#define MXC_CCM_PDR1_SSI2_PODF_OFFSET 9 +#define MXC_CCM_PDR1_SSI2_PODF_MASK (0x3F << 9) +#define MXC_CCM_PDR1_SSI1_PRE_PODF_OFFSET 6 +#define MXC_CCM_PDR1_SSI1_PRE_PODF_MASK (0x7 << 6) +#define MXC_CCM_PDR1_SSI1_PODF_OFFSET 0 +#define MXC_CCM_PDR1_SSI1_PODF_MASK 0x3F + +/* Bit definitions for RCSR */ +#define MXC_CCM_RCSR_NF16B 0x80000000 + +/* + * LTR0 register offsets + */ +#define MXC_CCM_LTR0_DIV3CK_OFFSET 1 +#define MXC_CCM_LTR0_DIV3CK_MASK (0x3 << 1) +#define MXC_CCM_LTR0_DNTHR_OFFSET 16 +#define MXC_CCM_LTR0_DNTHR_MASK (0x3F << 16) +#define MXC_CCM_LTR0_UPTHR_OFFSET 22 +#define MXC_CCM_LTR0_UPTHR_MASK (0x3F << 22) + +/* + * LTR1 register offsets + */ +#define MXC_CCM_LTR1_PNCTHR_OFFSET 0 +#define MXC_CCM_LTR1_PNCTHR_MASK 0x3F +#define MXC_CCM_LTR1_UPCNT_OFFSET 6 +#define MXC_CCM_LTR1_UPCNT_MASK (0xFF << 6) +#define MXC_CCM_LTR1_DNCNT_OFFSET 14 +#define MXC_CCM_LTR1_DNCNT_MASK (0xFF << 14) +#define MXC_CCM_LTR1_LTBRSR_MASK 0x400000 +#define MXC_CCM_LTR1_LTBRSR_OFFSET 22 +#define MXC_CCM_LTR1_LTBRSR 0x400000 +#define MXC_CCM_LTR1_LTBRSH 0x800000 + +/* + * LTR2 bit definitions. x ranges from 0 for WSW9 to 6 for WSW15 + */ +#define MXC_CCM_LTR2_WSW_OFFSET(x) (11 + (x) * 3) +#define MXC_CCM_LTR2_WSW_MASK(x) (0x7 << \ + MXC_CCM_LTR2_WSW_OFFSET((x))) +#define MXC_CCM_LTR2_EMAC_OFFSET 0 +#define MXC_CCM_LTR2_EMAC_MASK 0x1FF + +/* + * LTR3 bit definitions. x ranges from 0 for WSW0 to 8 for WSW8 + */ +#define MXC_CCM_LTR3_WSW_OFFSET(x) (5 + (x) * 3) +#define MXC_CCM_LTR3_WSW_MASK(x) (0x7 << \ + MXC_CCM_LTR3_WSW_OFFSET((x))) + +#define MXC_CCM_PMCR0_DFSUP1 0x80000000 +#define MXC_CCM_PMCR0_DFSUP1_SPLL (0 << 31) +#define MXC_CCM_PMCR0_DFSUP1_MPLL (1 << 31) +#define MXC_CCM_PMCR0_DFSUP0 0x40000000 +#define MXC_CCM_PMCR0_DFSUP0_PLL (0 << 30) +#define MXC_CCM_PMCR0_DFSUP0_PDR (1 << 30) +#define MXC_CCM_PMCR0_DFSUP_MASK (0x3 << 30) + +#define DVSUP_TURBO 0 +#define DVSUP_HIGH 1 +#define DVSUP_MEDIUM 2 +#define DVSUP_LOW 3 +#define MXC_CCM_PMCR0_DVSUP_TURBO (DVSUP_TURBO << 28) +#define MXC_CCM_PMCR0_DVSUP_HIGH (DVSUP_HIGH << 28) +#define MXC_CCM_PMCR0_DVSUP_MEDIUM (DVSUP_MEDIUM << 28) +#define MXC_CCM_PMCR0_DVSUP_LOW (DVSUP_LOW << 28) +#define MXC_CCM_PMCR0_DVSUP_OFFSET 28 +#define MXC_CCM_PMCR0_DVSUP_MASK (0x3 << 28) +#define MXC_CCM_PMCR0_UDSC 0x08000000 +#define MXC_CCM_PMCR0_UDSC_MASK (1 << 27) +#define MXC_CCM_PMCR0_UDSC_UP (1 << 27) +#define MXC_CCM_PMCR0_UDSC_DOWN (0 << 27) + +#define MXC_CCM_PMCR0_VSCNT_1 (0x0 << 24) +#define MXC_CCM_PMCR0_VSCNT_2 (0x1 << 24) +#define MXC_CCM_PMCR0_VSCNT_3 (0x2 << 24) +#define MXC_CCM_PMCR0_VSCNT_4 (0x3 << 24) +#define MXC_CCM_PMCR0_VSCNT_5 (0x4 << 24) +#define MXC_CCM_PMCR0_VSCNT_6 (0x5 << 24) +#define MXC_CCM_PMCR0_VSCNT_7 (0x6 << 24) +#define MXC_CCM_PMCR0_VSCNT_8 (0x7 << 24) +#define MXC_CCM_PMCR0_VSCNT_OFFSET 24 +#define MXC_CCM_PMCR0_VSCNT_MASK (0x7 << 24) +#define MXC_CCM_PMCR0_DVFEV 0x00800000 +#define MXC_CCM_PMCR0_DVFIS 0x00400000 +#define MXC_CCM_PMCR0_LBMI 0x00200000 +#define MXC_CCM_PMCR0_LBFL 0x00100000 +#define MXC_CCM_PMCR0_LBCF_4 (0x0 << 18) +#define MXC_CCM_PMCR0_LBCF_8 (0x1 << 18) +#define MXC_CCM_PMCR0_LBCF_12 (0x2 << 18) +#define MXC_CCM_PMCR0_LBCF_16 (0x3 << 18) +#define MXC_CCM_PMCR0_LBCF_OFFSET 18 +#define MXC_CCM_PMCR0_LBCF_MASK (0x3 << 18) +#define MXC_CCM_PMCR0_PTVIS 0x00020000 +#define MXC_CCM_PMCR0_UPDTEN 0x00010000 +#define MXC_CCM_PMCR0_UPDTEN_MASK (0x1 << 16) +#define MXC_CCM_PMCR0_FSVAIM 0x00008000 +#define MXC_CCM_PMCR0_FSVAI_OFFSET 13 +#define MXC_CCM_PMCR0_FSVAI_MASK (0x3 << 13) +#define MXC_CCM_PMCR0_DPVCR 0x00001000 +#define MXC_CCM_PMCR0_DPVV 0x00000800 +#define MXC_CCM_PMCR0_WFIM 0x00000400 +#define MXC_CCM_PMCR0_DRCE3 0x00000200 +#define MXC_CCM_PMCR0_DRCE2 0x00000100 +#define MXC_CCM_PMCR0_DRCE1 0x00000080 +#define MXC_CCM_PMCR0_DRCE0 0x00000040 +#define MXC_CCM_PMCR0_DCR 0x00000020 +#define MXC_CCM_PMCR0_DVFEN 0x00000010 +#define MXC_CCM_PMCR0_PTVAIM 0x00000008 +#define MXC_CCM_PMCR0_PTVAI_OFFSET 1 +#define MXC_CCM_PMCR0_PTVAI_MASK (0x3 << 1) +#define MXC_CCM_PMCR0_DPTEN 0x00000001 + +#define MXC_CCM_PMCR1_DVGP_OFFSET 0 +#define MXC_CCM_PMCR1_DVGP_MASK (0xF) + +#define MXC_CCM_PMCR1_PLLRDIS (0x1 << 7) +#define MXC_CCM_PMCR1_EMIRQ_EN (0x1 << 8) + +#define MXC_CCM_DCVR_ULV_MASK (0x3FF << 22) +#define MXC_CCM_DCVR_ULV_OFFSET 22 +#define MXC_CCM_DCVR_LLV_MASK (0x3FF << 12) +#define MXC_CCM_DCVR_LLV_OFFSET 12 +#define MXC_CCM_DCVR_ELV_MASK (0x3FF << 2) +#define MXC_CCM_DCVR_ELV_OFFSET 2 + +#define MXC_CCM_PDR2_MST2_PDF_MASK (0x3F << 7) +#define MXC_CCM_PDR2_MST2_PDF_OFFSET 7 +#define MXC_CCM_PDR2_MST1_PDF_MASK 0x3F +#define MXC_CCM_PDR2_MST1_PDF_OFFSET 0 + +#define MXC_CCM_COSR_CLKOSEL_MASK 0x0F +#define MXC_CCM_COSR_CLKOSEL_OFFSET 0 +#define MXC_CCM_COSR_CLKOUTDIV_MASK (0x07 << 6) +#define MXC_CCM_COSR_CLKOUTDIV_OFFSET 6 +#define MXC_CCM_COSR_CLKOEN (1 << 9) + +/* + * PMCR0 register offsets + */ +#define MXC_CCM_PMCR0_LBFL_OFFSET 20 +#define MXC_CCM_PMCR0_DFSUP0_OFFSET 30 +#define MXC_CCM_PMCR0_DFSUP1_OFFSET 31 + +#endif /* __ARCH_ARM_MACH_MX3_CRM_REGS_H__ */ diff --git a/arch/arm/mach-imx/gpc.c b/arch/arm/mach-imx/gpc.c new file mode 100644 index 000000000..ebc4339b8 --- /dev/null +++ b/arch/arm/mach-imx/gpc.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011-2013 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include "common.h" +#include "hardware.h" + +#define GPC_CNTR 0x0 +#define GPC_IMR1 0x008 +#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_CNTR_L2_PGE_SHIFT 22 + +#define IMR_NUM 4 +#define GPC_MAX_IRQS (IMR_NUM * 32) + +static void __iomem *gpc_base; +static u32 gpc_wake_irqs[IMR_NUM]; +static u32 gpc_saved_imrs[IMR_NUM]; + +void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw) +{ + writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) | + (sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PUPSCR); +} + +void imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw) +{ + writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) | + (sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PDNSCR); +} + +void imx_gpc_set_arm_power_in_lpm(bool power_off) +{ + writel_relaxed(power_off, gpc_base + GPC_PGC_CPU_PDN); +} + +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); + if (power_off) + val |= 1 << GPC_CNTR_L2_PGE_SHIFT; + writel_relaxed(val, gpc_base + GPC_CNTR); +} + +void imx_gpc_pre_suspend(bool arm_power_off) +{ + void __iomem *reg_imr1 = gpc_base + GPC_IMR1; + int i; + + /* Tell GPC to power off ARM core when suspend */ + if (arm_power_off) + imx_gpc_set_arm_power_in_lpm(arm_power_off); + + for (i = 0; i < IMR_NUM; i++) { + gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); + writel_relaxed(~gpc_wake_irqs[i], reg_imr1 + i * 4); + } +} + +void imx_gpc_post_resume(void) +{ + void __iomem *reg_imr1 = gpc_base + GPC_IMR1; + int i; + + /* Keep ARM core powered on for other low-power modes */ + imx_gpc_set_arm_power_in_lpm(false); + + for (i = 0; i < IMR_NUM; i++) + writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); +} + +static int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on) +{ + unsigned int idx = d->hwirq / 32; + u32 mask; + + mask = 1 << d->hwirq % 32; + gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask : + gpc_wake_irqs[idx] & ~mask; + + /* + * Do *not* call into the parent, as the GIC doesn't have any + * wake-up facility... + */ + return 0; +} + +void imx_gpc_mask_all(void) +{ + void __iomem *reg_imr1 = gpc_base + GPC_IMR1; + int i; + + for (i = 0; i < IMR_NUM; i++) { + gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); + writel_relaxed(~0, reg_imr1 + i * 4); + } +} + +void imx_gpc_restore_all(void) +{ + void __iomem *reg_imr1 = gpc_base + GPC_IMR1; + int i; + + for (i = 0; i < IMR_NUM; i++) + writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); +} + +void imx_gpc_hwirq_unmask(unsigned int hwirq) +{ + void __iomem *reg; + u32 val; + + reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4; + val = readl_relaxed(reg); + val &= ~(1 << hwirq % 32); + writel_relaxed(val, reg); +} + +void imx_gpc_hwirq_mask(unsigned int hwirq) +{ + void __iomem *reg; + u32 val; + + reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4; + val = readl_relaxed(reg); + val |= 1 << (hwirq % 32); + writel_relaxed(val, reg); +} + +static void imx_gpc_irq_unmask(struct irq_data *d) +{ + imx_gpc_hwirq_unmask(d->hwirq); + irq_chip_unmask_parent(d); +} + +static void imx_gpc_irq_mask(struct irq_data *d) +{ + imx_gpc_hwirq_mask(d->hwirq); + irq_chip_mask_parent(d); +} + +static struct irq_chip imx_gpc_chip = { + .name = "GPC", + .irq_eoi = irq_chip_eoi_parent, + .irq_mask = imx_gpc_irq_mask, + .irq_unmask = imx_gpc_irq_unmask, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_wake = imx_gpc_irq_set_wake, + .irq_set_type = irq_chip_set_type_parent, +#ifdef CONFIG_SMP + .irq_set_affinity = irq_chip_set_affinity_parent, +#endif +}; + +static int imx_gpc_domain_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + if (is_of_node(fwspec->fwnode)) { + if (fwspec->param_count != 3) + return -EINVAL; + + /* No PPI should point to this domain */ + if (fwspec->param[0] != 0) + return -EINVAL; + + *hwirq = fwspec->param[1]; + *type = fwspec->param[2]; + return 0; + } + + return -EINVAL; +} + +static int imx_gpc_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_gpc_chip, NULL); + + parent_fwspec = *fwspec; + parent_fwspec.fwnode = domain->parent->fwnode; + return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs, + &parent_fwspec); +} + +static const struct irq_domain_ops imx_gpc_domain_ops = { + .translate = imx_gpc_domain_translate, + .alloc = imx_gpc_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +static int __init imx_gpc_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *parent_domain, *domain; + int i; + + if (!parent) { + pr_err("%pOF: no parent, giving up\n", node); + return -ENODEV; + } + + parent_domain = irq_find_host(parent); + if (!parent_domain) { + pr_err("%pOF: unable to obtain parent domain\n", node); + 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_gpc_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 + i * 4); + + /* + * 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; +} +IRQCHIP_DECLARE(imx_gpc, "fsl,imx6q-gpc", imx_gpc_init); + +void __init imx_gpc_check_dt(void) +{ + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-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); + } + of_node_put(np); +} diff --git a/arch/arm/mach-imx/hardware.h b/arch/arm/mach-imx/hardware.h new file mode 100644 index 000000000..0760fff39 --- /dev/null +++ b/arch/arm/mach-imx/hardware.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2004-2007, 2014 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + */ + +#ifndef __ASM_ARCH_MXC_HARDWARE_H__ +#define __ASM_ARCH_MXC_HARDWARE_H__ + +#ifndef __ASSEMBLY__ +#include <asm/io.h> +#include <soc/imx/revision.h> +#endif +#include <linux/sizes.h> + +#define addr_in_module(addr, mod) \ + ((unsigned long)(addr) - mod ## _BASE_ADDR < mod ## _SIZE) + +#define IMX_IO_P2V_MODULE(addr, module) \ + (((addr) - module ## _BASE_ADDR) < module ## _SIZE ? \ + (addr) - (module ## _BASE_ADDR) + (module ## _BASE_ADDR_VIRT) : 0) + +/* + * This is rather complicated for humans and ugly to verify, but for a machine + * it's OK. Still more as it is usually only applied to constants. The upsides + * on using this approach are: + * + * - same mapping on all i.MX machines + * - works for assembler, too + * - no need to nurture #defines for virtual addresses + * + * The downside it, it's hard to verify (but I have a script for that). + * + * Obviously this needs to be injective for each SoC. In general it maps the + * whole address space to [0xf4000000, 0xf5ffffff]. So [0xf6000000,0xfeffffff] + * is free for per-machine use (e.g. KZM_ARM11_01 uses 64MiB there). + * + * It applies the following mappings for the different SoCs: + * + * mx1: + * IO 0x00200000+0x100000 -> 0xf4000000+0x100000 + * mx21: + * AIPI 0x10000000+0x100000 -> 0xf4400000+0x100000 + * SAHB1 0x80000000+0x100000 -> 0xf5000000+0x100000 + * X_MEMC 0xdf000000+0x004000 -> 0xf5f00000+0x004000 + * mx25: + * AIPS1 0x43f00000+0x100000 -> 0xf5300000+0x100000 + * AIPS2 0x53f00000+0x100000 -> 0xf5700000+0x100000 + * AVIC 0x68000000+0x100000 -> 0xf5800000+0x100000 + * mx27: + * AIPI 0x10000000+0x100000 -> 0xf4400000+0x100000 + * SAHB1 0x80000000+0x100000 -> 0xf5000000+0x100000 + * X_MEMC 0xd8000000+0x100000 -> 0xf5c00000+0x100000 + * mx31: + * AIPS1 0x43f00000+0x100000 -> 0xf5300000+0x100000 + * AIPS2 0x53f00000+0x100000 -> 0xf5700000+0x100000 + * AVIC 0x68000000+0x100000 -> 0xf5800000+0x100000 + * X_MEMC 0xb8000000+0x010000 -> 0xf5c00000+0x010000 + * SPBA0 0x50000000+0x100000 -> 0xf5400000+0x100000 + * mx35: + * AIPS1 0x43f00000+0x100000 -> 0xf5300000+0x100000 + * AIPS2 0x53f00000+0x100000 -> 0xf5700000+0x100000 + * AVIC 0x68000000+0x100000 -> 0xf5800000+0x100000 + * X_MEMC 0xb8000000+0x010000 -> 0xf5c00000+0x010000 + * SPBA0 0x50000000+0x100000 -> 0xf5400000+0x100000 + * mx51: + * TZIC 0x0fffc000+0x004000 -> 0xf4bfc000+0x004000 + * IRAM 0x1ffe0000+0x020000 -> 0xf4fe0000+0x020000 + * DEBUG 0x60000000+0x100000 -> 0xf5000000+0x100000 + * SPBA0 0x70000000+0x100000 -> 0xf5400000+0x100000 + * AIPS1 0x73f00000+0x100000 -> 0xf5700000+0x100000 + * AIPS2 0x83f00000+0x100000 -> 0xf5300000+0x100000 + * mx53: + * TZIC 0x0fffc000+0x004000 -> 0xf4bfc000+0x004000 + * DEBUG 0x40000000+0x100000 -> 0xf5000000+0x100000 + * SPBA0 0x50000000+0x100000 -> 0xf5400000+0x100000 + * AIPS1 0x53f00000+0x100000 -> 0xf5700000+0x100000 + * AIPS2 0x63f00000+0x100000 -> 0xf5300000+0x100000 + * mx6q: + * SCU 0x00a00000+0x004000 -> 0xf4000000+0x004000 + * CCM 0x020c4000+0x004000 -> 0xf42c4000+0x004000 + * ANATOP 0x020c8000+0x004000 -> 0xf42c8000+0x004000 + * UART4 0x021f0000+0x004000 -> 0xf42f0000+0x004000 + */ +#define IMX_IO_P2V(x) ( \ + (((x) & 0x80000000) >> 7) | \ + (0xf4000000 + \ + (((x) & 0x50000000) >> 6) + \ + (((x) & 0x0b000000) >> 4) + \ + (((x) & 0x000fffff)))) + +#define IMX_IO_ADDRESS(x) IOMEM(IMX_IO_P2V(x)) + +#include "mxc.h" + +#include "mx3x.h" +#include "mx31.h" +#include "mx35.h" +#include "mx2x.h" +#include "mx27.h" + +#define imx_map_entry(soc, name, _type) { \ + .virtual = soc ## _IO_P2V(soc ## _ ## name ## _BASE_ADDR), \ + .pfn = __phys_to_pfn(soc ## _ ## name ## _BASE_ADDR), \ + .length = soc ## _ ## name ## _SIZE, \ + .type = _type, \ +} + +#endif /* __ASM_ARCH_MXC_HARDWARE_H__ */ diff --git a/arch/arm/mach-imx/headsmp.S b/arch/arm/mach-imx/headsmp.S new file mode 100644 index 000000000..fcba58be8 --- /dev/null +++ b/arch/arm/mach-imx/headsmp.S @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/linkage.h> +#include <linux/init.h> +#include <asm/assembler.h> + +diag_reg_offset: + .word g_diag_reg - . + + .macro set_diag_reg + adr r0, diag_reg_offset + ldr r1, [r0] + add r1, r1, r0 @ r1 = physical &g_diag_reg + ldr r0, [r1] + mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register + .endm + +ENTRY(v7_secondary_startup) +ARM_BE8(setend be) @ go BE8 if entered LE + mrc p15, 0, r0, c0, c0, 0 + lsl r0, r0, #16 + lsr r0, r0, #20 + /* 0xc07 is cortex A7's ID */ + mov r1, #0xc00 + orr r1, #0x7 + cmp r0, r1 + beq secondary_startup + + set_diag_reg + b secondary_startup +ENDPROC(v7_secondary_startup) diff --git a/arch/arm/mach-imx/hotplug.c b/arch/arm/mach-imx/hotplug.c new file mode 100644 index 000000000..e24a46dc5 --- /dev/null +++ b/arch/arm/mach-imx/hotplug.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/errno.h> +#include <linux/jiffies.h> +#include <asm/cacheflush.h> +#include <asm/cp15.h> +#include <asm/proc-fns.h> + +#include "common.h" +#include "hardware.h" + +/* + * platform-specific code to shutdown a CPU + * + * Called with IRQs disabled + */ +void imx_cpu_die(unsigned int cpu) +{ + v7_exit_coherency_flush(louis); + /* + * We use the cpu jumping argument register to sync with + * imx_cpu_kill() which is running on cpu0 and waiting for + * the register being cleared to kill the cpu. + */ + imx_set_cpu_arg(cpu, ~0); + + while (1) + cpu_do_idle(); +} + +int imx_cpu_kill(unsigned int cpu) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(50); + + while (imx_get_cpu_arg(cpu) == 0) + if (time_after(jiffies, timeout)) + return 0; + imx_enable_cpu(cpu, false); + imx_set_cpu_arg(cpu, 0); + if (cpu_is_imx7d()) + imx_gpcv2_set_core1_pdn_pup_by_software(true); + return 1; +} diff --git a/arch/arm/mach-imx/iim.h b/arch/arm/mach-imx/iim.h new file mode 100644 index 000000000..72c3bca89 --- /dev/null +++ b/arch/arm/mach-imx/iim.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + */ + +#ifndef __ASM_ARCH_MXC_IIM_H__ +#define __ASM_ARCH_MXC_IIM_H__ + +/* Register offsets */ +#define MXC_IIMSTAT 0x0000 +#define MXC_IIMSTATM 0x0004 +#define MXC_IIMERR 0x0008 +#define MXC_IIMEMASK 0x000C +#define MXC_IIMFCTL 0x0010 +#define MXC_IIMUA 0x0014 +#define MXC_IIMLA 0x0018 +#define MXC_IIMSDAT 0x001C +#define MXC_IIMPREV 0x0020 +#define MXC_IIMSREV 0x0024 +#define MXC_IIMPRG_P 0x0028 +#define MXC_IIMSCS0 0x002C +#define MXC_IIMSCS1 0x0030 +#define MXC_IIMSCS2 0x0034 +#define MXC_IIMSCS3 0x0038 +#define MXC_IIMFBAC0 0x0800 +#define MXC_IIMJAC 0x0804 +#define MXC_IIMHWV1 0x0808 +#define MXC_IIMHWV2 0x080C +#define MXC_IIMHAB0 0x0810 +#define MXC_IIMHAB1 0x0814 +/* Definitions for i.MX27 TO2 */ +#define MXC_IIMMAC 0x0814 +#define MXC_IIMPREV_FUSE 0x0818 +#define MXC_IIMSREV_FUSE 0x081C +#define MXC_IIMSJC_CHALL_0 0x0820 +#define MXC_IIMSJC_CHALL_7 0x083C +#define MXC_IIMFB0UC17 0x0840 +#define MXC_IIMFB0UC255 0x0BFC +#define MXC_IIMFBAC1 0x0C00 +/* Definitions for i.MX27 TO2 */ +#define MXC_IIMSUID 0x0C04 +#define MXC_IIMKEY0 0x0C04 +#define MXC_IIMKEY20 0x0C54 +#define MXC_IIMSJC_RESP_0 0x0C58 +#define MXC_IIMSJC_RESP_7 0x0C74 +#define MXC_IIMFB1UC30 0x0C78 +#define MXC_IIMFB1UC255 0x0FFC + +/* Bit definitions */ + +#define MXC_IIMHWV1_WLOCK (0x1 << 7) +#define MXC_IIMHWV1_MCU_ENDIAN (0x1 << 6) +#define MXC_IIMHWV1_DSP_ENDIAN (0x1 << 5) +#define MXC_IIMHWV1_BOOT_INT (0x1 << 4) +#define MXC_IIMHWV1_SCC_DISABLE (0x1 << 3) +#define MXC_IIMHWV1_HANTRO_DISABLE (0x1 << 2) +#define MXC_IIMHWV1_MEMSTICK_DIS (0x1 << 1) + +#define MXC_IIMHWV2_WLOCK (0x1 << 7) +#define MXC_IIMHWV2_BP_SDMA (0x1 << 6) +#define MXC_IIMHWV2_SCM_DCM (0x1 << 5) + +#endif /* __ASM_ARCH_MXC_IIM_H__ */ diff --git a/arch/arm/mach-imx/irq-common.c b/arch/arm/mach-imx/irq-common.c new file mode 100644 index 000000000..2ce3e0130 --- /dev/null +++ b/arch/arm/mach-imx/irq-common.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) BitBox Ltd 2010 + */ + +#include <linux/module.h> +#include <linux/irq.h> +#include <linux/platform_data/asoc-imx-ssi.h> + +#include "irq-common.h" + +int mxc_set_irq_fiq(unsigned int irq, unsigned int type) +{ + struct irq_chip_generic *gc; + struct mxc_extra_irq *exirq; + int ret; + + ret = -ENOSYS; + + gc = irq_get_chip_data(irq); + if (gc && gc->private) { + exirq = gc->private; + if (exirq->set_irq_fiq) { + struct irq_data *d = irq_get_irq_data(irq); + ret = exirq->set_irq_fiq(irqd_to_hwirq(d), type); + } + } + + return ret; +} +EXPORT_SYMBOL(mxc_set_irq_fiq); diff --git a/arch/arm/mach-imx/irq-common.h b/arch/arm/mach-imx/irq-common.h new file mode 100644 index 000000000..1c2ac0fa4 --- /dev/null +++ b/arch/arm/mach-imx/irq-common.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) BitBox Ltd 2010 + */ + +#ifndef __PLAT_MXC_IRQ_COMMON_H__ +#define __PLAT_MXC_IRQ_COMMON_H__ + +/* all normal IRQs can be FIQs */ +#define FIQ_START 0 + +struct mxc_extra_irq +{ + int (*set_irq_fiq)(unsigned int irq, unsigned int type); +}; + +#endif diff --git a/arch/arm/mach-imx/mach-imx1.c b/arch/arm/mach-imx/mach-imx1.c new file mode 100644 index 000000000..8eca92d66 --- /dev/null +++ b/arch/arm/mach-imx/mach-imx1.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> + */ + +#include <linux/of_platform.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include "common.h" +#include "hardware.h" + +#define MX1_AVIC_ADDR 0x00223000 + +static void __init imx1_init_early(void) +{ + mxc_set_cpu_type(MXC_CPU_MX1); +} + +static const char * const imx1_dt_board_compat[] __initconst = { + "fsl,imx1", + NULL +}; + +DT_MACHINE_START(IMX1_DT, "Freescale i.MX1 (Device Tree Support)") + .init_early = imx1_init_early, + .dt_compat = imx1_dt_board_compat, + .restart = mxc_restart, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx25.c b/arch/arm/mach-imx/mach-imx25.c new file mode 100644 index 000000000..51927bd08 --- /dev/null +++ b/arch/arm/mach-imx/mach-imx25.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2012 Sascha Hauer, Pengutronix + */ + +#include <linux/irq.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> +#include "common.h" +#include "hardware.h" + +static void __init imx25_init_early(void) +{ + mxc_set_cpu_type(MXC_CPU_MX25); +} + +static void __init imx25_dt_init(void) +{ + imx_aips_allow_unprivileged_access("fsl,imx25-aips"); +} + +static const char * const imx25_dt_board_compat[] __initconst = { + "fsl,imx25", + NULL +}; + +DT_MACHINE_START(IMX25_DT, "Freescale i.MX25 (Device Tree Support)") + .init_early = imx25_init_early, + .init_machine = imx25_dt_init, + .init_late = imx25_pm_init, + .dt_compat = imx25_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx27.c b/arch/arm/mach-imx/mach-imx27.c new file mode 100644 index 000000000..e325c9468 --- /dev/null +++ b/arch/arm/mach-imx/mach-imx27.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2012 Sascha Hauer, Pengutronix + */ + +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/mm.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> +#include <asm/mach/time.h> + +#include "common.h" +#include "hardware.h" +#include "mx27.h" + +/* MX27 memory map definition */ +static struct map_desc imx27_io_desc[] __initdata = { + /* + * this fixed mapping covers: + * - AIPI1 + * - AIPI2 + * - AITC + * - ROM Patch + * - and some reserved space + */ + imx_map_entry(MX27, AIPI, MT_DEVICE), + /* + * this fixed mapping covers: + * - CSI + * - ATA + */ + imx_map_entry(MX27, SAHB1, MT_DEVICE), + /* + * this fixed mapping covers: + * - EMI + */ + imx_map_entry(MX27, X_MEMC, MT_DEVICE), +}; + +/* + * Initialize the memory map. It is called during the + * system startup to create static physical to virtual + * memory map for the IO modules. + */ +static void __init mx27_map_io(void) +{ + iotable_init(imx27_io_desc, ARRAY_SIZE(imx27_io_desc)); +} + +static void __init imx27_init_early(void) +{ + mxc_set_cpu_type(MXC_CPU_MX27); +} + +static const char * const imx27_dt_board_compat[] __initconst = { + "fsl,imx27", + NULL +}; + +DT_MACHINE_START(IMX27_DT, "Freescale i.MX27 (Device Tree Support)") + .map_io = mx27_map_io, + .init_early = imx27_init_early, + .init_late = imx27_pm_init, + .dt_compat = imx27_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx31.c b/arch/arm/mach-imx/mach-imx31.c new file mode 100644 index 000000000..e9a1092b6 --- /dev/null +++ b/arch/arm/mach-imx/mach-imx31.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2012 Sascha Hauer, Pengutronix + */ + +#include <asm/mach/arch.h> +#include "common.h" + +static const char * const imx31_dt_board_compat[] __initconst = { + "fsl,imx31", + NULL +}; + +DT_MACHINE_START(IMX31_DT, "Freescale i.MX31 (Device Tree Support)") + .map_io = mx31_map_io, + .init_early = imx31_init_early, + .dt_compat = imx31_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx35.c b/arch/arm/mach-imx/mach-imx35.c new file mode 100644 index 000000000..0fc08218b --- /dev/null +++ b/arch/arm/mach-imx/mach-imx35.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2012 Steffen Trumtrar, Pengutronix + * + * based on imx27-dt.c + */ + +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/clk-provider.h> +#include <linux/clocksource.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> +#include <asm/hardware/cache-l2x0.h> +#include "common.h" +#include "mx35.h" + +static const char * const imx35_dt_board_compat[] __initconst = { + "fsl,imx35", + NULL +}; + +DT_MACHINE_START(IMX35_DT, "Freescale i.MX35 (Device Tree Support)") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .map_io = mx35_map_io, + .init_early = imx35_init_early, + .dt_compat = imx35_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx50.c b/arch/arm/mach-imx/mach-imx50.c new file mode 100644 index 000000000..a2d35f9ba --- /dev/null +++ b/arch/arm/mach-imx/mach-imx50.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2013 Greg Ungerer <gerg@uclinux.org> + * Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/of_platform.h> +#include <asm/mach/arch.h> + +#include "common.h" +#include "hardware.h" + +static void __init imx50_init_early(void) +{ + mxc_set_cpu_type(MXC_CPU_MX50); +} + +static const char * const imx50_dt_board_compat[] __initconst = { + "fsl,imx50", + NULL +}; + +DT_MACHINE_START(IMX50_DT, "Freescale i.MX50 (Device Tree Support)") + .init_early = imx50_init_early, + .dt_compat = imx50_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx51.c b/arch/arm/mach-imx/mach-imx51.c new file mode 100644 index 000000000..8b748a185 --- /dev/null +++ b/arch/arm/mach-imx/mach-imx51.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> + +#include "common.h" +#include "hardware.h" + +static void __init imx51_init_early(void) +{ + mxc_set_cpu_type(MXC_CPU_MX51); +} + +/* + * The MIPI HSC unit has been removed from the i.MX51 Reference Manual by + * the Freescale marketing division. However this did not remove the + * hardware from the chip which still needs to be configured for proper + * IPU support. + */ +#define MX51_MIPI_HSC_BASE 0x83fdc000 +static void __init imx51_ipu_mipi_setup(void) +{ + void __iomem *hsc_addr; + + hsc_addr = ioremap(MX51_MIPI_HSC_BASE, SZ_16K); + WARN_ON(!hsc_addr); + + /* setup MIPI module to legacy mode */ + imx_writel(0xf00, hsc_addr); + + /* CSI mode: reserved; DI control mode: legacy (from Freescale BSP) */ + imx_writel(imx_readl(hsc_addr + 0x800) | 0x30ff, hsc_addr + 0x800); + + iounmap(hsc_addr); +} + +static void __init imx51_m4if_setup(void) +{ + void __iomem *m4if_base; + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx51-m4if"); + if (!np) + return; + + m4if_base = of_iomap(np, 0); + of_node_put(np); + if (!m4if_base) { + pr_err("Unable to map M4IF registers\n"); + return; + } + + /* + * Configure VPU and IPU with higher priorities + * in order to avoid artifacts during video playback + */ + writel_relaxed(0x00000203, m4if_base + 0x40); + writel_relaxed(0x00000000, m4if_base + 0x44); + writel_relaxed(0x00120125, m4if_base + 0x9c); + writel_relaxed(0x001901A3, m4if_base + 0x48); + iounmap(m4if_base); +} + +static void __init imx51_dt_init(void) +{ + imx51_ipu_mipi_setup(); + imx_src_init(); + imx51_m4if_setup(); + imx5_pmu_init(); + imx_aips_allow_unprivileged_access("fsl,imx51-aipstz"); +} + +static void __init imx51_init_late(void) +{ + mx51_neon_fixup(); + imx51_pm_init(); +} + +static const char * const imx51_dt_board_compat[] __initconst = { + "fsl,imx51", + NULL +}; + +DT_MACHINE_START(IMX51_DT, "Freescale i.MX51 (Device Tree Support)") + .init_early = imx51_init_early, + .init_machine = imx51_dt_init, + .init_late = imx51_init_late, + .dt_compat = imx51_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx53.c b/arch/arm/mach-imx/mach-imx53.c new file mode 100644 index 000000000..8223c5aee --- /dev/null +++ b/arch/arm/mach-imx/mach-imx53.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> + +#include "common.h" +#include "hardware.h" + +static void __init imx53_init_early(void) +{ + mxc_set_cpu_type(MXC_CPU_MX53); +} + +static void __init imx53_dt_init(void) +{ + imx_src_init(); + imx5_pmu_init(); + imx_aips_allow_unprivileged_access("fsl,imx53-aipstz"); +} + +static void __init imx53_init_late(void) +{ + imx53_pm_init(); +} + +static const char * const imx53_dt_board_compat[] __initconst = { + "fsl,imx53", + NULL +}; + +DT_MACHINE_START(IMX53_DT, "Freescale i.MX53 (Device Tree Support)") + .init_early = imx53_init_early, + .init_machine = imx53_dt_init, + .init_late = imx53_init_late, + .dt_compat = imx53_dt_board_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c new file mode 100644 index 000000000..c9d7c29d9 --- /dev/null +++ b/arch/arm/mach-imx/mach-imx6q.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011-2013 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/clk.h> +#include <linux/irqchip.h> +#include <linux/of_platform.h> +#include <linux/pci.h> +#include <linux/phy.h> +#include <linux/regmap.h> +#include <linux/micrel_phy.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +/* For imx6q sabrelite board: set KSZ9021RN RGMII pad skew */ +static int ksz9021rn_phy_fixup(struct phy_device *phydev) +{ + if (IS_BUILTIN(CONFIG_PHYLIB)) { + /* min rx data delay */ + phy_write(phydev, MICREL_KSZ9021_EXTREG_CTRL, + 0x8000 | MICREL_KSZ9021_RGMII_RX_DATA_PAD_SCEW); + phy_write(phydev, MICREL_KSZ9021_EXTREG_DATA_WRITE, 0x0000); + + /* max rx/tx clock delay, min rx/tx control delay */ + phy_write(phydev, MICREL_KSZ9021_EXTREG_CTRL, + 0x8000 | MICREL_KSZ9021_RGMII_CLK_CTRL_PAD_SCEW); + phy_write(phydev, MICREL_KSZ9021_EXTREG_DATA_WRITE, 0xf0f0); + phy_write(phydev, MICREL_KSZ9021_EXTREG_CTRL, + MICREL_KSZ9021_RGMII_CLK_CTRL_PAD_SCEW); + } + + return 0; +} + +/* + * fixup for PLX PEX8909 bridge to configure GPIO1-7 as output High + * as they are used for slots1-7 PERST# + */ +static void ventana_pciesw_early_fixup(struct pci_dev *dev) +{ + u32 dw; + + if (!of_machine_is_compatible("gw,ventana")) + return; + + if (dev->devfn != 0) + return; + + pci_read_config_dword(dev, 0x62c, &dw); + dw |= 0xaaa8; // GPIO1-7 outputs + pci_write_config_dword(dev, 0x62c, dw); + + pci_read_config_dword(dev, 0x644, &dw); + dw |= 0xfe; // GPIO1-7 output high + pci_write_config_dword(dev, 0x644, dw); + + msleep(100); +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_PLX, 0x8609, ventana_pciesw_early_fixup); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_PLX, 0x8606, ventana_pciesw_early_fixup); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_PLX, 0x8604, ventana_pciesw_early_fixup); + +static void __init imx6q_enet_phy_init(void) +{ + if (IS_BUILTIN(CONFIG_PHYLIB)) { + phy_register_fixup_for_uid(PHY_ID_KSZ9021, MICREL_PHY_ID_MASK, + ksz9021rn_phy_fixup); + } +} + +static void __init imx6q_1588_init(void) +{ + struct device_node *np; + struct clk *ptp_clk; + struct clk *enet_ref; + struct regmap *gpr; + u32 clksel; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-fec"); + if (!np) { + pr_warn("%s: failed to find fec node\n", __func__); + return; + } + + ptp_clk = of_clk_get(np, 2); + if (IS_ERR(ptp_clk)) { + pr_warn("%s: failed to get ptp clock\n", __func__); + goto put_node; + } + + enet_ref = clk_get_sys(NULL, "enet_ref"); + if (IS_ERR(enet_ref)) { + pr_warn("%s: failed to get enet clock\n", __func__); + goto put_ptp_clk; + } + + /* + * If enet_ref from ANATOP/CCM is the PTP clock source, we need to + * set bit IOMUXC_GPR1[21]. Or the PTP clock must be from pad + * (external OSC), and we need to clear the bit. + */ + clksel = clk_is_match(ptp_clk, enet_ref) ? + IMX6Q_GPR1_ENET_CLK_SEL_ANATOP : + IMX6Q_GPR1_ENET_CLK_SEL_PAD; + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (!IS_ERR(gpr)) + regmap_update_bits(gpr, IOMUXC_GPR1, + IMX6Q_GPR1_ENET_CLK_SEL_MASK, + clksel); + else + pr_err("failed to find fsl,imx6q-iomuxc-gpr regmap\n"); + + clk_put(enet_ref); +put_ptp_clk: + clk_put(ptp_clk); +put_node: + of_node_put(np); +} + +static void __init imx6q_axi_init(void) +{ + struct regmap *gpr; + unsigned int mask; + + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (!IS_ERR(gpr)) { + /* + * Enable the cacheable attribute of VPU and IPU + * AXI transactions. + */ + mask = IMX6Q_GPR4_VPU_WR_CACHE_SEL | + IMX6Q_GPR4_VPU_RD_CACHE_SEL | + IMX6Q_GPR4_VPU_P_WR_CACHE_VAL | + IMX6Q_GPR4_VPU_P_RD_CACHE_VAL_MASK | + IMX6Q_GPR4_IPU_WR_CACHE_CTL | + IMX6Q_GPR4_IPU_RD_CACHE_CTL; + regmap_update_bits(gpr, IOMUXC_GPR4, mask, mask); + + /* Increase IPU read QoS priority */ + regmap_update_bits(gpr, IOMUXC_GPR6, + IMX6Q_GPR6_IPU1_ID00_RD_QOS_MASK | + IMX6Q_GPR6_IPU1_ID01_RD_QOS_MASK, + (0xf << 16) | (0x7 << 20)); + regmap_update_bits(gpr, IOMUXC_GPR7, + IMX6Q_GPR7_IPU2_ID00_RD_QOS_MASK | + IMX6Q_GPR7_IPU2_ID01_RD_QOS_MASK, + (0xf << 16) | (0x7 << 20)); + } else { + pr_warn("failed to find fsl,imx6q-iomuxc-gpr regmap\n"); + } +} + +static void __init imx6q_init_machine(void) +{ + if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) + /* + * SoCs that identify as i.MX6Q >= rev 2.0 are really i.MX6QP. + * Quirk: i.MX6QP revision = i.MX6Q revision - (1, 0), + * e.g. i.MX6QP rev 1.1 identifies as i.MX6Q rev 2.1. + */ + imx_print_silicon_rev("i.MX6QP", imx_get_soc_revision() - 0x10); + else + imx_print_silicon_rev(cpu_is_imx6dl() ? "i.MX6DL" : "i.MX6Q", + imx_get_soc_revision()); + + imx6q_enet_phy_init(); + + of_platform_default_populate(NULL, NULL, NULL); + + imx_anatop_init(); + cpu_is_imx6q() ? imx6q_pm_init() : imx6dl_pm_init(); + imx6q_1588_init(); + imx6q_axi_init(); +} + +static void __init imx6q_init_late(void) +{ + /* + * WAIT mode is broken on imx6 Dual/Quad revision 1.0 and 1.1 so + * there is no point to run cpuidle on them. + * + * It does work on imx6 Solo/DualLite starting from 1.1 + */ + if ((cpu_is_imx6q() && imx_get_soc_revision() > IMX_CHIP_REVISION_1_1) || + (cpu_is_imx6dl() && imx_get_soc_revision() > IMX_CHIP_REVISION_1_0)) + imx6q_cpuidle_init(); + + if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ)) + platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0); +} + +static void __init imx6q_map_io(void) +{ + debug_ll_io_init(); + imx_scu_map_io(); +} + +static void __init imx6q_init_irq(void) +{ + imx_gpc_check_dt(); + imx_init_revision_from_anatop(); + imx_init_l2cache(); + imx_src_init(); + irqchip_init(); + imx6_pm_ccm_init("fsl,imx6q-ccm"); +} + +static const char * const imx6q_dt_compat[] __initconst = { + "fsl,imx6dl", + "fsl,imx6q", + "fsl,imx6qp", + NULL, +}; + +DT_MACHINE_START(IMX6Q, "Freescale i.MX6 Quad/DualLite (Device Tree)") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .smp = smp_ops(imx_smp_ops), + .map_io = imx6q_map_io, + .init_irq = imx6q_init_irq, + .init_machine = imx6q_init_machine, + .init_late = imx6q_init_late, + .dt_compat = imx6q_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx6sl.c b/arch/arm/mach-imx/mach-imx6sl.c new file mode 100644 index 000000000..f6e87363d --- /dev/null +++ b/arch/arm/mach-imx/mach-imx6sl.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2013 Freescale Semiconductor, Inc. + */ + +#include <linux/irqchip.h> +#include <linux/of_platform.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/regmap.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +static void __init imx6sl_fec_init(void) +{ + struct regmap *gpr; + + /* set FEC clock from internal PLL clock source */ + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6sl-iomuxc-gpr"); + if (!IS_ERR(gpr)) { + regmap_update_bits(gpr, IOMUXC_GPR1, + IMX6SL_GPR1_FEC_CLOCK_MUX2_SEL_MASK, 0); + regmap_update_bits(gpr, IOMUXC_GPR1, + IMX6SL_GPR1_FEC_CLOCK_MUX1_SEL_MASK, 0); + } else { + pr_err("failed to find fsl,imx6sl-iomux-gpr regmap\n"); + } +} + +static void __init imx6sl_init_late(void) +{ + /* imx6sl reuses imx6q cpufreq driver */ + if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ)) + platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0); + + if (IS_ENABLED(CONFIG_SOC_IMX6SL) && cpu_is_imx6sl()) + imx6sl_cpuidle_init(); + else if (IS_ENABLED(CONFIG_SOC_IMX6SLL)) + imx6sx_cpuidle_init(); +} + +static void __init imx6sl_init_machine(void) +{ + of_platform_default_populate(NULL, NULL, NULL); + + if (cpu_is_imx6sl()) + imx6sl_fec_init(); + imx_anatop_init(); + imx6sl_pm_init(); +} + +static void __init imx6sl_init_irq(void) +{ + imx_gpc_check_dt(); + imx_init_revision_from_anatop(); + imx_init_l2cache(); + imx_src_init(); + irqchip_init(); + if (cpu_is_imx6sl()) + imx6_pm_ccm_init("fsl,imx6sl-ccm"); + else + imx6_pm_ccm_init("fsl,imx6sll-ccm"); +} + +static const char * const imx6sl_dt_compat[] __initconst = { + "fsl,imx6sl", + "fsl,imx6sll", + NULL, +}; + +DT_MACHINE_START(IMX6SL, "Freescale i.MX6 SoloLite (Device Tree)") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .init_irq = imx6sl_init_irq, + .init_machine = imx6sl_init_machine, + .init_late = imx6sl_init_late, + .dt_compat = imx6sl_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx6sx.c b/arch/arm/mach-imx/mach-imx6sx.c new file mode 100644 index 000000000..e65ed5218 --- /dev/null +++ b/arch/arm/mach-imx/mach-imx6sx.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2014 Freescale Semiconductor, Inc. + */ + +#include <linux/irqchip.h> +#include <linux/of_platform.h> +#include <linux/phy.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include "common.h" +#include "cpuidle.h" + +static void __init imx6sx_enet_clk_sel(void) +{ + struct regmap *gpr; + + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6sx-iomuxc-gpr"); + if (!IS_ERR(gpr)) { + regmap_update_bits(gpr, IOMUXC_GPR1, + IMX6SX_GPR1_FEC_CLOCK_MUX_SEL_MASK, 0); + regmap_update_bits(gpr, IOMUXC_GPR1, + IMX6SX_GPR1_FEC_CLOCK_PAD_DIR_MASK, 0); + } else { + pr_err("failed to find fsl,imx6sx-iomux-gpr regmap\n"); + } +} + +static inline void imx6sx_enet_init(void) +{ + imx6sx_enet_clk_sel(); +} + +static void __init imx6sx_init_machine(void) +{ + of_platform_default_populate(NULL, NULL, NULL); + + imx6sx_enet_init(); + imx_anatop_init(); + imx6sx_pm_init(); +} + +static void __init imx6sx_init_irq(void) +{ + imx_gpc_check_dt(); + imx_init_revision_from_anatop(); + imx_init_l2cache(); + imx_src_init(); + irqchip_init(); + imx6_pm_ccm_init("fsl,imx6sx-ccm"); +} + +static void __init imx6sx_init_late(void) +{ + imx6sx_cpuidle_init(); + + if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ)) + platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0); +} + +static const char * const imx6sx_dt_compat[] __initconst = { + "fsl,imx6sx", + NULL, +}; + +DT_MACHINE_START(IMX6SX, "Freescale i.MX6 SoloX (Device Tree)") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .init_irq = imx6sx_init_irq, + .init_machine = imx6sx_init_machine, + .dt_compat = imx6sx_dt_compat, + .init_late = imx6sx_init_late, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx6ul.c b/arch/arm/mach-imx/mach-imx6ul.c new file mode 100644 index 000000000..35e81201c --- /dev/null +++ b/arch/arm/mach-imx/mach-imx6ul.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. + */ +#include <linux/irqchip.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/micrel_phy.h> +#include <linux/of_platform.h> +#include <linux/phy.h> +#include <linux/regmap.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +static void __init imx6ul_enet_clk_init(void) +{ + struct regmap *gpr; + + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6ul-iomuxc-gpr"); + if (!IS_ERR(gpr)) + regmap_update_bits(gpr, IOMUXC_GPR1, IMX6UL_GPR1_ENET_CLK_DIR, + IMX6UL_GPR1_ENET_CLK_OUTPUT); + else + pr_err("failed to find fsl,imx6ul-iomux-gpr regmap\n"); +} + +static inline void imx6ul_enet_init(void) +{ + imx6ul_enet_clk_init(); +} + +static void __init imx6ul_init_machine(void) +{ + imx_print_silicon_rev(cpu_is_imx6ull() ? "i.MX6ULL" : "i.MX6UL", + imx_get_soc_revision()); + + of_platform_default_populate(NULL, NULL, NULL); + imx6ul_enet_init(); + imx_anatop_init(); + imx6ul_pm_init(); +} + +static void __init imx6ul_init_irq(void) +{ + imx_init_revision_from_anatop(); + imx_src_init(); + irqchip_init(); + imx6_pm_ccm_init("fsl,imx6ul-ccm"); +} + +static void __init imx6ul_init_late(void) +{ + imx6sx_cpuidle_init(); + + if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ)) + platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0); +} + +static const char * const imx6ul_dt_compat[] __initconst = { + "fsl,imx6ul", + "fsl,imx6ull", + NULL, +}; + +DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)") + .init_irq = imx6ul_init_irq, + .init_machine = imx6ul_init_machine, + .init_late = imx6ul_init_late, + .dt_compat = imx6ul_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx7d-cm4.c b/arch/arm/mach-imx/mach-imx7d-cm4.c new file mode 100644 index 000000000..0800b5891 --- /dev/null +++ b/arch/arm/mach-imx/mach-imx7d-cm4.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Pengutronix, Oleksij Rempel <o.rempel@pengutronix.de> + */ + +#include <linux/kernel.h> +#include <asm/v7m.h> +#include <asm/mach/arch.h> + +static const char * const imx7d_cm4_dt_compat[] __initconst = { + "fsl,imx7d-cm4", + NULL, +}; + +DT_MACHINE_START(IMX7D, "Freescale i.MX7 Dual Cortex-M4 (Device Tree)") + .dt_compat = imx7d_cm4_dt_compat, + .restart = armv7m_restart, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx7d.c b/arch/arm/mach-imx/mach-imx7d.c new file mode 100644 index 000000000..6fdd06bcf --- /dev/null +++ b/arch/arm/mach-imx/mach-imx7d.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. + */ +#include <linux/irqchip.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx7-iomuxc-gpr.h> +#include <linux/of_platform.h> +#include <linux/phy.h> +#include <linux/regmap.h> + +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include "common.h" + +static int bcm54220_phy_fixup(struct phy_device *dev) +{ + /* enable RXC skew select RGMII copper mode */ + phy_write(dev, 0x1e, 0x21); + phy_write(dev, 0x1f, 0x7ea8); + phy_write(dev, 0x1e, 0x2f); + phy_write(dev, 0x1f, 0x71b7); + + return 0; +} + +#define PHY_ID_BCM54220 0x600d8589 + +static void __init imx7d_enet_phy_init(void) +{ + if (IS_BUILTIN(CONFIG_PHYLIB)) { + phy_register_fixup_for_uid(PHY_ID_BCM54220, 0xffffffff, + bcm54220_phy_fixup); + } +} + +static void __init imx7d_enet_clk_sel(void) +{ + struct regmap *gpr; + + gpr = syscon_regmap_lookup_by_compatible("fsl,imx7d-iomuxc-gpr"); + if (!IS_ERR(gpr)) { + regmap_update_bits(gpr, IOMUXC_GPR1, IMX7D_GPR1_ENET_TX_CLK_SEL_MASK, 0); + regmap_update_bits(gpr, IOMUXC_GPR1, IMX7D_GPR1_ENET_CLK_DIR_MASK, 0); + } else { + pr_err("failed to find fsl,imx7d-iomux-gpr regmap\n"); + } +} + +static inline void imx7d_enet_init(void) +{ + imx7d_enet_phy_init(); + imx7d_enet_clk_sel(); +} + +static void __init imx7d_init_machine(void) +{ + imx_anatop_init(); + imx7d_enet_init(); +} + +static void __init imx7d_init_late(void) +{ + 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_init_revision_from_anatop(); + imx7_src_init(); + irqchip_init(); +} + +static const char *const imx7d_dt_compat[] __initconst = { + "fsl,imx7d", + "fsl,imx7s", + NULL, +}; + +DT_MACHINE_START(IMX7D, "Freescale i.MX7 Dual (Device Tree)") + .smp = smp_ops(imx7_smp_ops), + .init_irq = imx7d_init_irq, + .init_machine = imx7d_init_machine, + .init_late = imx7d_init_late, + .dt_compat = imx7d_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imx7ulp.c b/arch/arm/mach-imx/mach-imx7ulp.c new file mode 100644 index 000000000..a3c8dadec --- /dev/null +++ b/arch/arm/mach-imx/mach-imx7ulp.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2018 NXP + * Author: Dong Aisheng <aisheng.dong@nxp.com> + */ + +#include <linux/irqchip.h> +#include <linux/mfd/syscon.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <asm/mach/arch.h> + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +#define SIM_JTAG_ID_REG 0x8c + +static void __init imx7ulp_set_revision(void) +{ + struct regmap *sim; + u32 revision; + + sim = syscon_regmap_lookup_by_compatible("fsl,imx7ulp-sim"); + if (IS_ERR(sim)) { + pr_warn("failed to find fsl,imx7ulp-sim regmap!\n"); + return; + } + + if (regmap_read(sim, SIM_JTAG_ID_REG, &revision)) { + pr_warn("failed to read sim regmap!\n"); + return; + } + + /* + * bit[31:28] of JTAG_ID register defines revision as below from B0: + * 0001 B0 + * 0010 B1 + * 0011 B2 + */ + switch (revision >> 28) { + case 1: + imx_set_soc_revision(IMX_CHIP_REVISION_2_0); + break; + case 2: + imx_set_soc_revision(IMX_CHIP_REVISION_2_1); + break; + case 3: + imx_set_soc_revision(IMX_CHIP_REVISION_2_2); + break; + default: + imx_set_soc_revision(IMX_CHIP_REVISION_1_0); + break; + } +} + +static void __init imx7ulp_init_machine(void) +{ + imx7ulp_pm_init(); + + mxc_set_cpu_type(MXC_CPU_IMX7ULP); + imx7ulp_set_revision(); + of_platform_default_populate(NULL, NULL, NULL); +} + +static const char *const imx7ulp_dt_compat[] __initconst = { + "fsl,imx7ulp", + NULL, +}; + +static void __init imx7ulp_init_late(void) +{ + if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT)) + platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0); + + imx7ulp_cpuidle_init(); +} + +DT_MACHINE_START(IMX7ulp, "Freescale i.MX7ULP (Device Tree)") + .init_machine = imx7ulp_init_machine, + .dt_compat = imx7ulp_dt_compat, + .init_late = imx7ulp_init_late, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-imxrt.c b/arch/arm/mach-imx/mach-imxrt.c new file mode 100644 index 000000000..2063a3059 --- /dev/null +++ b/arch/arm/mach-imx/mach-imxrt.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 + * Author(s): Giulio Benetti <giulio.benetti@benettiengineering.com> + */ + +#include <linux/kernel.h> +#include <asm/mach/arch.h> +#include <asm/v7m.h> + +static const char *const imxrt_compat[] __initconst = { + "fsl,imxrt1050", + NULL +}; + +DT_MACHINE_START(IMXRTDT, "IMXRT (Device Tree Support)") + .dt_compat = imxrt_compat, + .restart = armv7m_restart, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-ls1021a.c b/arch/arm/mach-imx/mach-ls1021a.c new file mode 100644 index 000000000..0034359a9 --- /dev/null +++ b/arch/arm/mach-imx/mach-ls1021a.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2013-2014 Freescale Semiconductor, Inc. + */ + +#include <asm/mach/arch.h> + +#include "common.h" + +static const char * const ls1021a_dt_compat[] __initconst = { + "fsl,ls1021a", + NULL, +}; + +DT_MACHINE_START(LS1021A, "Freescale LS1021A") + .smp = smp_ops(ls1021a_smp_ops), + .dt_compat = ls1021a_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mach-vf610.c b/arch/arm/mach-imx/mach-vf610.c new file mode 100644 index 000000000..208ff6406 --- /dev/null +++ b/arch/arm/mach-imx/mach-vf610.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2012-2013 Freescale Semiconductor, Inc. + */ + +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/io.h> + +#include <linux/irqchip.h> +#include <asm/mach/arch.h> +#include <asm/hardware/cache-l2x0.h> + +#include "common.h" +#include "hardware.h" + +#define MSCM_CPxCOUNT 0x00c +#define MSCM_CPxCFG1 0x014 + +static void __init vf610_detect_cpu(void) +{ + struct device_node *np; + u32 cpxcount, cpxcfg1; + unsigned int cpu_type; + void __iomem *mscm; + + np = of_find_compatible_node(NULL, NULL, "fsl,vf610-mscm-cpucfg"); + if (WARN_ON(!np)) + return; + + mscm = of_iomap(np, 0); + of_node_put(np); + + if (WARN_ON(!mscm)) + return; + + cpxcount = readl_relaxed(mscm + MSCM_CPxCOUNT); + cpxcfg1 = readl_relaxed(mscm + MSCM_CPxCFG1); + + iounmap(mscm); + + cpu_type = cpxcount ? MXC_CPU_VF600 : MXC_CPU_VF500; + + if (cpxcfg1) + cpu_type |= MXC_CPU_VFx10; + + mxc_set_cpu_type(cpu_type); +} + +static void __init vf610_init_machine(void) +{ + vf610_detect_cpu(); + + of_platform_default_populate(NULL, NULL, NULL); +} + +static const char * const vf610_dt_compat[] __initconst = { + "fsl,vf500", + "fsl,vf510", + "fsl,vf600", + "fsl,vf610", + "fsl,vf610m4", + NULL, +}; + +DT_MACHINE_START(VYBRID_VF610, "Freescale Vybrid VF5xx/VF6xx (Device Tree)") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .init_machine = vf610_init_machine, + .dt_compat = vf610_dt_compat, +MACHINE_END diff --git a/arch/arm/mach-imx/mm-imx3.c b/arch/arm/mach-imx/mm-imx3.c new file mode 100644 index 000000000..28db97289 --- /dev/null +++ b/arch/arm/mach-imx/mm-imx3.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 1999,2000 Arm Limited + * Copyright (C) 2000 Deep Blue Solutions Ltd + * Copyright (C) 2002 Shane Nay (shane@minirl.com) + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * - add MX31 specific definitions + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/pinctrl/machine.h> + +#include <asm/system_misc.h> +#include <asm/hardware/cache-l2x0.h> +#include <asm/mach/map.h> + +#include "common.h" +#include "crmregs-imx3.h" +#include "hardware.h" + +void __iomem *mx3_ccm_base; + +static void imx3_idle(void) +{ + unsigned long reg = 0; + + __asm__ __volatile__( + /* disable I and D cache */ + "mrc p15, 0, %0, c1, c0, 0\n" + "bic %0, %0, #0x00001000\n" + "bic %0, %0, #0x00000004\n" + "mcr p15, 0, %0, c1, c0, 0\n" + /* invalidate I cache */ + "mov %0, #0\n" + "mcr p15, 0, %0, c7, c5, 0\n" + /* clear and invalidate D cache */ + "mov %0, #0\n" + "mcr p15, 0, %0, c7, c14, 0\n" + /* WFI */ + "mov %0, #0\n" + "mcr p15, 0, %0, c7, c0, 4\n" + "nop\n" "nop\n" "nop\n" "nop\n" + "nop\n" "nop\n" "nop\n" + /* enable I and D cache */ + "mrc p15, 0, %0, c1, c0, 0\n" + "orr %0, %0, #0x00001000\n" + "orr %0, %0, #0x00000004\n" + "mcr p15, 0, %0, c1, c0, 0\n" + : "=r" (reg)); +} + +static void __iomem *imx3_ioremap_caller(phys_addr_t phys_addr, size_t size, + unsigned int mtype, void *caller) +{ + if (mtype == MT_DEVICE) { + /* + * Access all peripherals below 0x80000000 as nonshared device + * on mx3, but leave l2cc alone. Otherwise cache corruptions + * can occur. + */ + if (phys_addr < 0x80000000 && + !addr_in_module(phys_addr, MX3x_L2CC)) + mtype = MT_DEVICE_NONSHARED; + } + + return __arm_ioremap_caller(phys_addr, size, mtype, caller); +} + +#ifdef CONFIG_SOC_IMX31 +static struct map_desc mx31_io_desc[] __initdata = { + imx_map_entry(MX31, X_MEMC, MT_DEVICE), + imx_map_entry(MX31, AVIC, MT_DEVICE_NONSHARED), + imx_map_entry(MX31, AIPS1, MT_DEVICE_NONSHARED), + imx_map_entry(MX31, AIPS2, MT_DEVICE_NONSHARED), + imx_map_entry(MX31, SPBA0, MT_DEVICE_NONSHARED), +}; + +/* + * This function initializes the memory map. It is called during the + * system startup to create static physical to virtual memory mappings + * for the IO modules. + */ +void __init mx31_map_io(void) +{ + iotable_init(mx31_io_desc, ARRAY_SIZE(mx31_io_desc)); +} + +static void imx31_idle(void) +{ + int reg = imx_readl(mx3_ccm_base + MXC_CCM_CCMR); + reg &= ~MXC_CCM_CCMR_LPM_MASK; + imx_writel(reg, mx3_ccm_base + MXC_CCM_CCMR); + + imx3_idle(); +} + +void __init imx31_init_early(void) +{ + struct device_node *np; + + mxc_set_cpu_type(MXC_CPU_MX31); + arch_ioremap_caller = imx3_ioremap_caller; + arm_pm_idle = imx31_idle; + np = of_find_compatible_node(NULL, NULL, "fsl,imx31-ccm"); + mx3_ccm_base = of_iomap(np, 0); + BUG_ON(!mx3_ccm_base); +} +#endif /* ifdef CONFIG_SOC_IMX31 */ + +#ifdef CONFIG_SOC_IMX35 +static struct map_desc mx35_io_desc[] __initdata = { + imx_map_entry(MX35, X_MEMC, MT_DEVICE), + imx_map_entry(MX35, AVIC, MT_DEVICE_NONSHARED), + imx_map_entry(MX35, AIPS1, MT_DEVICE_NONSHARED), + imx_map_entry(MX35, AIPS2, MT_DEVICE_NONSHARED), + imx_map_entry(MX35, SPBA0, MT_DEVICE_NONSHARED), +}; + +void __init mx35_map_io(void) +{ + iotable_init(mx35_io_desc, ARRAY_SIZE(mx35_io_desc)); +} + +static void imx35_idle(void) +{ + int reg = imx_readl(mx3_ccm_base + MXC_CCM_CCMR); + reg &= ~MXC_CCM_CCMR_LPM_MASK; + reg |= MXC_CCM_CCMR_LPM_WAIT_MX35; + imx_writel(reg, mx3_ccm_base + MXC_CCM_CCMR); + + imx3_idle(); +} + +void __init imx35_init_early(void) +{ + struct device_node *np; + + mxc_set_cpu_type(MXC_CPU_MX35); + arm_pm_idle = imx35_idle; + arch_ioremap_caller = imx3_ioremap_caller; + np = of_find_compatible_node(NULL, NULL, "fsl,imx35-ccm"); + mx3_ccm_base = of_iomap(np, 0); + BUG_ON(!mx3_ccm_base); +} +#endif /* ifdef CONFIG_SOC_IMX35 */ diff --git a/arch/arm/mach-imx/mmdc.c b/arch/arm/mach-imx/mmdc.c new file mode 100644 index 000000000..3d76e8c28 --- /dev/null +++ b/arch/arm/mach-imx/mmdc.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2017 NXP + * Copyright 2011,2016 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/clk.h> +#include <linux/hrtimer.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/perf_event.h> +#include <linux/slab.h> + +#include "common.h" + +#define MMDC_MAPSR 0x404 +#define BP_MMDC_MAPSR_PSD 0 +#define BP_MMDC_MAPSR_PSS 4 + +#define MMDC_MDMISC 0x18 +#define BM_MMDC_MDMISC_DDR_TYPE 0x18 +#define BP_MMDC_MDMISC_DDR_TYPE 0x3 + +#define TOTAL_CYCLES 0x0 +#define BUSY_CYCLES 0x1 +#define READ_ACCESSES 0x2 +#define WRITE_ACCESSES 0x3 +#define READ_BYTES 0x4 +#define WRITE_BYTES 0x5 + +/* Enables, resets, freezes, overflow profiling*/ +#define DBG_DIS 0x0 +#define DBG_EN 0x1 +#define DBG_RST 0x2 +#define PRF_FRZ 0x4 +#define CYC_OVF 0x8 +#define PROFILE_SEL 0x10 + +#define MMDC_MADPCR0 0x410 +#define MMDC_MADPCR1 0x414 +#define MMDC_MADPSR0 0x418 +#define MMDC_MADPSR1 0x41C +#define MMDC_MADPSR2 0x420 +#define MMDC_MADPSR3 0x424 +#define MMDC_MADPSR4 0x428 +#define MMDC_MADPSR5 0x42C + +#define MMDC_NUM_COUNTERS 6 + +#define MMDC_FLAG_PROFILE_SEL 0x1 +#define MMDC_PRF_AXI_ID_CLEAR 0x0 + +#define to_mmdc_pmu(p) container_of(p, struct mmdc_pmu, pmu) + +static int ddr_type; + +struct fsl_mmdc_devtype_data { + unsigned int flags; +}; + +static const struct fsl_mmdc_devtype_data imx6q_data = { +}; + +static const struct fsl_mmdc_devtype_data imx6qp_data = { + .flags = MMDC_FLAG_PROFILE_SEL, +}; + +static const struct of_device_id imx_mmdc_dt_ids[] = { + { .compatible = "fsl,imx6q-mmdc", .data = (void *)&imx6q_data}, + { .compatible = "fsl,imx6qp-mmdc", .data = (void *)&imx6qp_data}, + { /* sentinel */ } +}; + +#ifdef CONFIG_PERF_EVENTS + +static enum cpuhp_state cpuhp_mmdc_state; +static DEFINE_IDA(mmdc_ida); + +PMU_EVENT_ATTR_STRING(total-cycles, mmdc_pmu_total_cycles, "event=0x00") +PMU_EVENT_ATTR_STRING(busy-cycles, mmdc_pmu_busy_cycles, "event=0x01") +PMU_EVENT_ATTR_STRING(read-accesses, mmdc_pmu_read_accesses, "event=0x02") +PMU_EVENT_ATTR_STRING(write-accesses, mmdc_pmu_write_accesses, "event=0x03") +PMU_EVENT_ATTR_STRING(read-bytes, mmdc_pmu_read_bytes, "event=0x04") +PMU_EVENT_ATTR_STRING(read-bytes.unit, mmdc_pmu_read_bytes_unit, "MB"); +PMU_EVENT_ATTR_STRING(read-bytes.scale, mmdc_pmu_read_bytes_scale, "0.000001"); +PMU_EVENT_ATTR_STRING(write-bytes, mmdc_pmu_write_bytes, "event=0x05") +PMU_EVENT_ATTR_STRING(write-bytes.unit, mmdc_pmu_write_bytes_unit, "MB"); +PMU_EVENT_ATTR_STRING(write-bytes.scale, mmdc_pmu_write_bytes_scale, "0.000001"); + +struct mmdc_pmu { + struct pmu pmu; + void __iomem *mmdc_base; + cpumask_t cpu; + struct hrtimer hrtimer; + unsigned int active_events; + int id; + struct device *dev; + struct perf_event *mmdc_events[MMDC_NUM_COUNTERS]; + struct hlist_node node; + struct fsl_mmdc_devtype_data *devtype_data; + struct clk *mmdc_ipg_clk; +}; + +/* + * Polling period is set to one second, overflow of total-cycles (the fastest + * increasing counter) takes ten seconds so one second is safe + */ +static unsigned int mmdc_pmu_poll_period_us = 1000000; + +module_param_named(pmu_pmu_poll_period_us, mmdc_pmu_poll_period_us, uint, + S_IRUGO | S_IWUSR); + +static ktime_t mmdc_pmu_timer_period(void) +{ + return ns_to_ktime((u64)mmdc_pmu_poll_period_us * 1000); +} + +static ssize_t mmdc_pmu_cpumask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmdc_pmu *pmu_mmdc = dev_get_drvdata(dev); + + return cpumap_print_to_pagebuf(true, buf, &pmu_mmdc->cpu); +} + +static struct device_attribute mmdc_pmu_cpumask_attr = + __ATTR(cpumask, S_IRUGO, mmdc_pmu_cpumask_show, NULL); + +static struct attribute *mmdc_pmu_cpumask_attrs[] = { + &mmdc_pmu_cpumask_attr.attr, + NULL, +}; + +static struct attribute_group mmdc_pmu_cpumask_attr_group = { + .attrs = mmdc_pmu_cpumask_attrs, +}; + +static struct attribute *mmdc_pmu_events_attrs[] = { + &mmdc_pmu_total_cycles.attr.attr, + &mmdc_pmu_busy_cycles.attr.attr, + &mmdc_pmu_read_accesses.attr.attr, + &mmdc_pmu_write_accesses.attr.attr, + &mmdc_pmu_read_bytes.attr.attr, + &mmdc_pmu_read_bytes_unit.attr.attr, + &mmdc_pmu_read_bytes_scale.attr.attr, + &mmdc_pmu_write_bytes.attr.attr, + &mmdc_pmu_write_bytes_unit.attr.attr, + &mmdc_pmu_write_bytes_scale.attr.attr, + NULL, +}; + +static struct attribute_group mmdc_pmu_events_attr_group = { + .name = "events", + .attrs = mmdc_pmu_events_attrs, +}; + +PMU_FORMAT_ATTR(event, "config:0-63"); +PMU_FORMAT_ATTR(axi_id, "config1:0-63"); + +static struct attribute *mmdc_pmu_format_attrs[] = { + &format_attr_event.attr, + &format_attr_axi_id.attr, + NULL, +}; + +static struct attribute_group mmdc_pmu_format_attr_group = { + .name = "format", + .attrs = mmdc_pmu_format_attrs, +}; + +static const struct attribute_group *attr_groups[] = { + &mmdc_pmu_events_attr_group, + &mmdc_pmu_format_attr_group, + &mmdc_pmu_cpumask_attr_group, + NULL, +}; + +static u32 mmdc_pmu_read_counter(struct mmdc_pmu *pmu_mmdc, int cfg) +{ + void __iomem *mmdc_base, *reg; + + mmdc_base = pmu_mmdc->mmdc_base; + + switch (cfg) { + case TOTAL_CYCLES: + reg = mmdc_base + MMDC_MADPSR0; + break; + case BUSY_CYCLES: + reg = mmdc_base + MMDC_MADPSR1; + break; + case READ_ACCESSES: + reg = mmdc_base + MMDC_MADPSR2; + break; + case WRITE_ACCESSES: + reg = mmdc_base + MMDC_MADPSR3; + break; + case READ_BYTES: + reg = mmdc_base + MMDC_MADPSR4; + break; + case WRITE_BYTES: + reg = mmdc_base + MMDC_MADPSR5; + break; + default: + return WARN_ONCE(1, + "invalid configuration %d for mmdc counter", cfg); + } + return readl(reg); +} + +static int mmdc_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node) +{ + struct mmdc_pmu *pmu_mmdc = hlist_entry_safe(node, struct mmdc_pmu, node); + int target; + + if (!cpumask_test_and_clear_cpu(cpu, &pmu_mmdc->cpu)) + return 0; + + target = cpumask_any_but(cpu_online_mask, cpu); + if (target >= nr_cpu_ids) + return 0; + + perf_pmu_migrate_context(&pmu_mmdc->pmu, cpu, target); + cpumask_set_cpu(target, &pmu_mmdc->cpu); + + return 0; +} + +static bool mmdc_pmu_group_event_is_valid(struct perf_event *event, + struct pmu *pmu, + unsigned long *used_counters) +{ + int cfg = event->attr.config; + + if (is_software_event(event)) + return true; + + if (event->pmu != pmu) + return false; + + return !test_and_set_bit(cfg, used_counters); +} + +/* + * Each event has a single fixed-purpose counter, so we can only have a + * single active event for each at any point in time. Here we just check + * for duplicates, and rely on mmdc_pmu_event_init to verify that the HW + * event numbers are valid. + */ +static bool mmdc_pmu_group_is_valid(struct perf_event *event) +{ + struct pmu *pmu = event->pmu; + struct perf_event *leader = event->group_leader; + struct perf_event *sibling; + unsigned long counter_mask = 0; + + set_bit(leader->attr.config, &counter_mask); + + if (event != leader) { + if (!mmdc_pmu_group_event_is_valid(event, pmu, &counter_mask)) + return false; + } + + for_each_sibling_event(sibling, leader) { + if (!mmdc_pmu_group_event_is_valid(sibling, pmu, &counter_mask)) + return false; + } + + return true; +} + +static int mmdc_pmu_event_init(struct perf_event *event) +{ + struct mmdc_pmu *pmu_mmdc = to_mmdc_pmu(event->pmu); + int cfg = event->attr.config; + + if (event->attr.type != event->pmu->type) + return -ENOENT; + + if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) + return -EOPNOTSUPP; + + if (event->cpu < 0) { + dev_warn(pmu_mmdc->dev, "Can't provide per-task data!\n"); + return -EOPNOTSUPP; + } + + if (event->attr.sample_period) + return -EINVAL; + + if (cfg < 0 || cfg >= MMDC_NUM_COUNTERS) + return -EINVAL; + + if (!mmdc_pmu_group_is_valid(event)) + return -EINVAL; + + event->cpu = cpumask_first(&pmu_mmdc->cpu); + return 0; +} + +static void mmdc_pmu_event_update(struct perf_event *event) +{ + struct mmdc_pmu *pmu_mmdc = to_mmdc_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + u64 delta, prev_raw_count, new_raw_count; + + do { + prev_raw_count = local64_read(&hwc->prev_count); + new_raw_count = mmdc_pmu_read_counter(pmu_mmdc, + event->attr.config); + } while (local64_cmpxchg(&hwc->prev_count, prev_raw_count, + new_raw_count) != prev_raw_count); + + delta = (new_raw_count - prev_raw_count) & 0xFFFFFFFF; + + local64_add(delta, &event->count); +} + +static void mmdc_pmu_event_start(struct perf_event *event, int flags) +{ + struct mmdc_pmu *pmu_mmdc = to_mmdc_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + void __iomem *mmdc_base, *reg; + u32 val; + + mmdc_base = pmu_mmdc->mmdc_base; + reg = mmdc_base + MMDC_MADPCR0; + + /* + * hrtimer is required because mmdc does not provide an interrupt so + * polling is necessary + */ + hrtimer_start(&pmu_mmdc->hrtimer, mmdc_pmu_timer_period(), + HRTIMER_MODE_REL_PINNED); + + local64_set(&hwc->prev_count, 0); + + writel(DBG_RST, reg); + + /* + * Write the AXI id parameter to MADPCR1. + */ + val = event->attr.config1; + reg = mmdc_base + MMDC_MADPCR1; + writel(val, reg); + + reg = mmdc_base + MMDC_MADPCR0; + val = DBG_EN; + if (pmu_mmdc->devtype_data->flags & MMDC_FLAG_PROFILE_SEL) + val |= PROFILE_SEL; + + writel(val, reg); +} + +static int mmdc_pmu_event_add(struct perf_event *event, int flags) +{ + struct mmdc_pmu *pmu_mmdc = to_mmdc_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + + int cfg = event->attr.config; + + if (flags & PERF_EF_START) + mmdc_pmu_event_start(event, flags); + + if (pmu_mmdc->mmdc_events[cfg] != NULL) + return -EAGAIN; + + pmu_mmdc->mmdc_events[cfg] = event; + pmu_mmdc->active_events++; + + local64_set(&hwc->prev_count, mmdc_pmu_read_counter(pmu_mmdc, cfg)); + + return 0; +} + +static void mmdc_pmu_event_stop(struct perf_event *event, int flags) +{ + struct mmdc_pmu *pmu_mmdc = to_mmdc_pmu(event->pmu); + void __iomem *mmdc_base, *reg; + + mmdc_base = pmu_mmdc->mmdc_base; + reg = mmdc_base + MMDC_MADPCR0; + + writel(PRF_FRZ, reg); + + reg = mmdc_base + MMDC_MADPCR1; + writel(MMDC_PRF_AXI_ID_CLEAR, reg); + + mmdc_pmu_event_update(event); +} + +static void mmdc_pmu_event_del(struct perf_event *event, int flags) +{ + struct mmdc_pmu *pmu_mmdc = to_mmdc_pmu(event->pmu); + int cfg = event->attr.config; + + pmu_mmdc->mmdc_events[cfg] = NULL; + pmu_mmdc->active_events--; + + if (pmu_mmdc->active_events == 0) + hrtimer_cancel(&pmu_mmdc->hrtimer); + + mmdc_pmu_event_stop(event, PERF_EF_UPDATE); +} + +static void mmdc_pmu_overflow_handler(struct mmdc_pmu *pmu_mmdc) +{ + int i; + + for (i = 0; i < MMDC_NUM_COUNTERS; i++) { + struct perf_event *event = pmu_mmdc->mmdc_events[i]; + + if (event) + mmdc_pmu_event_update(event); + } +} + +static enum hrtimer_restart mmdc_pmu_timer_handler(struct hrtimer *hrtimer) +{ + struct mmdc_pmu *pmu_mmdc = container_of(hrtimer, struct mmdc_pmu, + hrtimer); + + mmdc_pmu_overflow_handler(pmu_mmdc); + hrtimer_forward_now(hrtimer, mmdc_pmu_timer_period()); + + return HRTIMER_RESTART; +} + +static int mmdc_pmu_init(struct mmdc_pmu *pmu_mmdc, + void __iomem *mmdc_base, struct device *dev) +{ + *pmu_mmdc = (struct mmdc_pmu) { + .pmu = (struct pmu) { + .task_ctx_nr = perf_invalid_context, + .attr_groups = attr_groups, + .event_init = mmdc_pmu_event_init, + .add = mmdc_pmu_event_add, + .del = mmdc_pmu_event_del, + .start = mmdc_pmu_event_start, + .stop = mmdc_pmu_event_stop, + .read = mmdc_pmu_event_update, + .capabilities = PERF_PMU_CAP_NO_EXCLUDE, + }, + .mmdc_base = mmdc_base, + .dev = dev, + .active_events = 0, + }; + + pmu_mmdc->id = ida_simple_get(&mmdc_ida, 0, 0, GFP_KERNEL); + + return pmu_mmdc->id; +} + +static int imx_mmdc_remove(struct platform_device *pdev) +{ + struct mmdc_pmu *pmu_mmdc = platform_get_drvdata(pdev); + + ida_simple_remove(&mmdc_ida, pmu_mmdc->id); + cpuhp_state_remove_instance_nocalls(cpuhp_mmdc_state, &pmu_mmdc->node); + perf_pmu_unregister(&pmu_mmdc->pmu); + iounmap(pmu_mmdc->mmdc_base); + clk_disable_unprepare(pmu_mmdc->mmdc_ipg_clk); + kfree(pmu_mmdc); + return 0; +} + +static int imx_mmdc_perf_init(struct platform_device *pdev, void __iomem *mmdc_base, + struct clk *mmdc_ipg_clk) +{ + struct mmdc_pmu *pmu_mmdc; + char *name; + int ret; + const struct of_device_id *of_id = + of_match_device(imx_mmdc_dt_ids, &pdev->dev); + + pmu_mmdc = kzalloc(sizeof(*pmu_mmdc), GFP_KERNEL); + if (!pmu_mmdc) { + pr_err("failed to allocate PMU device!\n"); + return -ENOMEM; + } + + /* The first instance registers the hotplug state */ + if (!cpuhp_mmdc_state) { + ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, + "perf/arm/mmdc:online", NULL, + mmdc_pmu_offline_cpu); + if (ret < 0) { + pr_err("cpuhp_setup_state_multi failed\n"); + goto pmu_free; + } + cpuhp_mmdc_state = ret; + } + + ret = mmdc_pmu_init(pmu_mmdc, mmdc_base, &pdev->dev); + if (ret < 0) + goto pmu_free; + + name = devm_kasprintf(&pdev->dev, + GFP_KERNEL, "mmdc%d", ret); + if (!name) { + ret = -ENOMEM; + goto pmu_release_id; + } + + pmu_mmdc->mmdc_ipg_clk = mmdc_ipg_clk; + pmu_mmdc->devtype_data = (struct fsl_mmdc_devtype_data *)of_id->data; + + hrtimer_init(&pmu_mmdc->hrtimer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + pmu_mmdc->hrtimer.function = mmdc_pmu_timer_handler; + + cpumask_set_cpu(raw_smp_processor_id(), &pmu_mmdc->cpu); + + /* Register the pmu instance for cpu hotplug */ + cpuhp_state_add_instance_nocalls(cpuhp_mmdc_state, &pmu_mmdc->node); + + ret = perf_pmu_register(&(pmu_mmdc->pmu), name, -1); + if (ret) + goto pmu_register_err; + + platform_set_drvdata(pdev, pmu_mmdc); + return 0; + +pmu_register_err: + pr_warn("MMDC Perf PMU failed (%d), disabled\n", ret); + cpuhp_state_remove_instance_nocalls(cpuhp_mmdc_state, &pmu_mmdc->node); + hrtimer_cancel(&pmu_mmdc->hrtimer); +pmu_release_id: + ida_simple_remove(&mmdc_ida, pmu_mmdc->id); +pmu_free: + kfree(pmu_mmdc); + return ret; +} + +#else +#define imx_mmdc_remove NULL +#define imx_mmdc_perf_init(pdev, mmdc_base, mmdc_ipg_clk) 0 +#endif + +static int imx_mmdc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + void __iomem *mmdc_base, *reg; + struct clk *mmdc_ipg_clk; + u32 val; + int err; + + /* the ipg clock is optional */ + mmdc_ipg_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mmdc_ipg_clk)) + mmdc_ipg_clk = NULL; + + err = clk_prepare_enable(mmdc_ipg_clk); + if (err) { + dev_err(&pdev->dev, "Unable to enable mmdc ipg clock.\n"); + return err; + } + + mmdc_base = of_iomap(np, 0); + WARN_ON(!mmdc_base); + + reg = mmdc_base + MMDC_MDMISC; + /* Get ddr type */ + val = readl_relaxed(reg); + ddr_type = (val & BM_MMDC_MDMISC_DDR_TYPE) >> + BP_MMDC_MDMISC_DDR_TYPE; + + reg = mmdc_base + MMDC_MAPSR; + + /* Enable automatic power saving */ + val = readl_relaxed(reg); + val &= ~(1 << BP_MMDC_MAPSR_PSD); + writel_relaxed(val, reg); + + err = imx_mmdc_perf_init(pdev, mmdc_base, mmdc_ipg_clk); + if (err) { + iounmap(mmdc_base); + clk_disable_unprepare(mmdc_ipg_clk); + } + + return err; +} + +int imx_mmdc_get_ddr_type(void) +{ + return ddr_type; +} + +static struct platform_driver imx_mmdc_driver = { + .driver = { + .name = "imx-mmdc", + .of_match_table = imx_mmdc_dt_ids, + }, + .probe = imx_mmdc_probe, + .remove = imx_mmdc_remove, +}; + +static int __init imx_mmdc_init(void) +{ + return platform_driver_register(&imx_mmdc_driver); +} +postcore_initcall(imx_mmdc_init); diff --git a/arch/arm/mach-imx/mx27.h b/arch/arm/mach-imx/mx27.h new file mode 100644 index 000000000..241c04d70 --- /dev/null +++ b/arch/arm/mach-imx/mx27.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * + * This contains i.MX27-specific hardware definitions. For those + * hardware pieces that are common between i.MX21 and i.MX27, have a + * look at mx2x.h. + */ + +#ifndef __MACH_MX27_H__ +#define __MACH_MX27_H__ + +#define MX27_AIPI_BASE_ADDR 0x10000000 +#define MX27_AIPI_SIZE SZ_1M + +#define MX27_SAHB1_BASE_ADDR 0x80000000 +#define MX27_SAHB1_SIZE SZ_1M + +#define MX27_X_MEMC_BASE_ADDR 0xd8000000 +#define MX27_X_MEMC_SIZE SZ_1M + +#define MX27_IO_P2V(x) IMX_IO_P2V(x) + +#endif /* ifndef __MACH_MX27_H__ */ diff --git a/arch/arm/mach-imx/mx2x.h b/arch/arm/mach-imx/mx2x.h new file mode 100644 index 000000000..841c34e11 --- /dev/null +++ b/arch/arm/mach-imx/mx2x.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * + * This contains hardware definitions that are common between i.MX21 and + * i.MX27. + */ + +#ifndef __MACH_MX2x_H__ +#define __MACH_MX2x_H__ + +/* The following addresses are common between i.MX21 and i.MX27 */ + +/* Register offsets */ +#define MX2x_AIPI_BASE_ADDR 0x10000000 +#define MX2x_AIPI_SIZE SZ_1M +#define MX2x_DMA_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x01000) +#define MX2x_WDOG_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x02000) +#define MX2x_GPT1_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x03000) +#define MX2x_GPT2_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x04000) +#define MX2x_GPT3_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x05000) +#define MX2x_PWM_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x06000) +#define MX2x_RTC_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x07000) +#define MX2x_KPP_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x08000) +#define MX2x_OWIRE_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x09000) +#define MX2x_UART1_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x0a000) +#define MX2x_UART2_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x0b000) +#define MX2x_UART3_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x0c000) +#define MX2x_UART4_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x0d000) +#define MX2x_CSPI1_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x0e000) +#define MX2x_CSPI2_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x0f000) +#define MX2x_SSI1_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x10000) +#define MX2x_SSI2_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x11000) +#define MX2x_I2C_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x12000) +#define MX2x_SDHC1_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x13000) +#define MX2x_SDHC2_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x14000) +#define MX2x_GPIO_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x15000) +#define MX2x_AUDMUX_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x16000) +#define MX2x_CSPI3_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x17000) +#define MX2x_LCDC_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x21000) +#define MX2x_SLCDC_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x22000) +#define MX2x_USBOTG_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x24000) +#define MX2x_EMMA_PP_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x26000) +#define MX2x_EMMA_PRP_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x26400) +#define MX2x_CCM_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x27000) +#define MX2x_SYSCTRL_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x27800) +#define MX2x_JAM_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x3e000) +#define MX2x_MAX_BASE_ADDR (MX2x_AIPI_BASE_ADDR + 0x3f000) + +#define MX2x_AVIC_BASE_ADDR 0x10040000 + +#define MX2x_SAHB1_BASE_ADDR 0x80000000 +#define MX2x_SAHB1_SIZE SZ_1M +#define MX2x_CSI_BASE_ADDR (MX2x_SAHB1_BASE_ADDR + 0x0000) + +/* fixed interrupt numbers */ +#include <asm/irq.h> +#define MX2x_INT_CSPI3 (NR_IRQS_LEGACY + 6) +#define MX2x_INT_GPIO (NR_IRQS_LEGACY + 8) +#define MX2x_INT_SDHC2 (NR_IRQS_LEGACY + 10) +#define MX2x_INT_SDHC1 (NR_IRQS_LEGACY + 11) +#define MX2x_INT_I2C (NR_IRQS_LEGACY + 12) +#define MX2x_INT_SSI2 (NR_IRQS_LEGACY + 13) +#define MX2x_INT_SSI1 (NR_IRQS_LEGACY + 14) +#define MX2x_INT_CSPI2 (NR_IRQS_LEGACY + 15) +#define MX2x_INT_CSPI1 (NR_IRQS_LEGACY + 16) +#define MX2x_INT_UART4 (NR_IRQS_LEGACY + 17) +#define MX2x_INT_UART3 (NR_IRQS_LEGACY + 18) +#define MX2x_INT_UART2 (NR_IRQS_LEGACY + 19) +#define MX2x_INT_UART1 (NR_IRQS_LEGACY + 20) +#define MX2x_INT_KPP (NR_IRQS_LEGACY + 21) +#define MX2x_INT_RTC (NR_IRQS_LEGACY + 22) +#define MX2x_INT_PWM (NR_IRQS_LEGACY + 23) +#define MX2x_INT_GPT3 (NR_IRQS_LEGACY + 24) +#define MX2x_INT_GPT2 (NR_IRQS_LEGACY + 25) +#define MX2x_INT_GPT1 (NR_IRQS_LEGACY + 26) +#define MX2x_INT_WDOG (NR_IRQS_LEGACY + 27) +#define MX2x_INT_PCMCIA (NR_IRQS_LEGACY + 28) +#define MX2x_INT_NANDFC (NR_IRQS_LEGACY + 29) +#define MX2x_INT_CSI (NR_IRQS_LEGACY + 31) +#define MX2x_INT_DMACH0 (NR_IRQS_LEGACY + 32) +#define MX2x_INT_DMACH1 (NR_IRQS_LEGACY + 33) +#define MX2x_INT_DMACH2 (NR_IRQS_LEGACY + 34) +#define MX2x_INT_DMACH3 (NR_IRQS_LEGACY + 35) +#define MX2x_INT_DMACH4 (NR_IRQS_LEGACY + 36) +#define MX2x_INT_DMACH5 (NR_IRQS_LEGACY + 37) +#define MX2x_INT_DMACH6 (NR_IRQS_LEGACY + 38) +#define MX2x_INT_DMACH7 (NR_IRQS_LEGACY + 39) +#define MX2x_INT_DMACH8 (NR_IRQS_LEGACY + 40) +#define MX2x_INT_DMACH9 (NR_IRQS_LEGACY + 41) +#define MX2x_INT_DMACH10 (NR_IRQS_LEGACY + 42) +#define MX2x_INT_DMACH11 (NR_IRQS_LEGACY + 43) +#define MX2x_INT_DMACH12 (NR_IRQS_LEGACY + 44) +#define MX2x_INT_DMACH13 (NR_IRQS_LEGACY + 45) +#define MX2x_INT_DMACH14 (NR_IRQS_LEGACY + 46) +#define MX2x_INT_DMACH15 (NR_IRQS_LEGACY + 47) +#define MX2x_INT_EMMAPRP (NR_IRQS_LEGACY + 51) +#define MX2x_INT_EMMAPP (NR_IRQS_LEGACY + 52) +#define MX2x_INT_SLCDC (NR_IRQS_LEGACY + 60) +#define MX2x_INT_LCDC (NR_IRQS_LEGACY + 61) + +/* fixed DMA request numbers */ +#define MX2x_DMA_REQ_CSPI3_RX 1 +#define MX2x_DMA_REQ_CSPI3_TX 2 +#define MX2x_DMA_REQ_EXT 3 +#define MX2x_DMA_REQ_SDHC2 6 +#define MX2x_DMA_REQ_SDHC1 7 +#define MX2x_DMA_REQ_SSI2_RX0 8 +#define MX2x_DMA_REQ_SSI2_TX0 9 +#define MX2x_DMA_REQ_SSI2_RX1 10 +#define MX2x_DMA_REQ_SSI2_TX1 11 +#define MX2x_DMA_REQ_SSI1_RX0 12 +#define MX2x_DMA_REQ_SSI1_TX0 13 +#define MX2x_DMA_REQ_SSI1_RX1 14 +#define MX2x_DMA_REQ_SSI1_TX1 15 +#define MX2x_DMA_REQ_CSPI2_RX 16 +#define MX2x_DMA_REQ_CSPI2_TX 17 +#define MX2x_DMA_REQ_CSPI1_RX 18 +#define MX2x_DMA_REQ_CSPI1_TX 19 +#define MX2x_DMA_REQ_UART4_RX 20 +#define MX2x_DMA_REQ_UART4_TX 21 +#define MX2x_DMA_REQ_UART3_RX 22 +#define MX2x_DMA_REQ_UART3_TX 23 +#define MX2x_DMA_REQ_UART2_RX 24 +#define MX2x_DMA_REQ_UART2_TX 25 +#define MX2x_DMA_REQ_UART1_RX 26 +#define MX2x_DMA_REQ_UART1_TX 27 +#define MX2x_DMA_REQ_CSI_STAT 30 +#define MX2x_DMA_REQ_CSI_RX 31 + +#endif /* ifndef __MACH_MX2x_H__ */ diff --git a/arch/arm/mach-imx/mx31.h b/arch/arm/mach-imx/mx31.h new file mode 100644 index 000000000..08a72e25c --- /dev/null +++ b/arch/arm/mach-imx/mx31.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MACH_MX31_H__ +#define __MACH_MX31_H__ + +#define MX31_AIPS1_BASE_ADDR 0x43f00000 +#define MX31_AIPS1_SIZE SZ_1M +#define MX31_SPBA0_BASE_ADDR 0x50000000 +#define MX31_SPBA0_SIZE SZ_1M +#define MX31_AIPS2_BASE_ADDR 0x53f00000 +#define MX31_AIPS2_SIZE SZ_1M +#define MX31_AVIC_BASE_ADDR 0x68000000 +#define MX31_AVIC_SIZE SZ_1M +#define MX31_X_MEMC_BASE_ADDR 0xb8000000 +#define MX31_X_MEMC_SIZE SZ_64K + +#define MX31_IO_P2V(x) IMX_IO_P2V(x) + +#endif /* ifndef __MACH_MX31_H__ */ diff --git a/arch/arm/mach-imx/mx35.h b/arch/arm/mach-imx/mx35.h new file mode 100644 index 000000000..5a8a87a85 --- /dev/null +++ b/arch/arm/mach-imx/mx35.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MACH_MX35_H__ +#define __MACH_MX35_H__ + +#define MX35_AIPS1_BASE_ADDR 0x43f00000 +#define MX35_AIPS1_SIZE SZ_1M +#define MX35_SPBA0_BASE_ADDR 0x50000000 +#define MX35_SPBA0_SIZE SZ_1M +#define MX35_AIPS2_BASE_ADDR 0x53f00000 +#define MX35_AIPS2_SIZE SZ_1M +#define MX35_AVIC_BASE_ADDR 0x68000000 +#define MX35_AVIC_SIZE SZ_1M +#define MX35_X_MEMC_BASE_ADDR 0xb8000000 +#define MX35_X_MEMC_SIZE SZ_64K + +#define MX35_IO_P2V(x) IMX_IO_P2V(x) + +#endif /* ifndef __MACH_MX35_H__ */ diff --git a/arch/arm/mach-imx/mx3x.h b/arch/arm/mach-imx/mx3x.h new file mode 100644 index 000000000..74b379488 --- /dev/null +++ b/arch/arm/mach-imx/mx3x.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + +#ifndef __MACH_MX3x_H__ +#define __MACH_MX3x_H__ + +/* + * MX31 memory map: + * + * Virt Phys Size What + * --------------------------------------------------------------------------- + * FC000000 43F00000 1M AIPS 1 + * FC100000 50000000 1M SPBA + * FC200000 53F00000 1M AIPS 2 + * FC500000 60000000 128M ROMPATCH + * FC400000 68000000 128M AVIC + * 70000000 256M IPU (MAX M2) + * 80000000 256M CSD0 SDRAM/DDR + * 90000000 256M CSD1 SDRAM/DDR + * A0000000 128M CS0 Flash + * A8000000 128M CS1 Flash + * B0000000 32M CS2 + * B2000000 32M CS3 + * F4000000 B4000000 32M CS4 + * B6000000 32M CS5 + * FC320000 B8000000 64K NAND, SDRAM, WEIM, M3IF, EMI controllers + * C0000000 64M PCMCIA/CF + */ + +/* + * L2CC + */ +#define MX3x_L2CC_BASE_ADDR 0x30000000 +#define MX3x_L2CC_SIZE SZ_1M + +/* + * AIPS 1 + */ +#define MX3x_AIPS1_BASE_ADDR 0x43f00000 +#define MX3x_AIPS1_SIZE SZ_1M +#define MX3x_MAX_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x04000) +#define MX3x_EVTMON_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x08000) +#define MX3x_CLKCTL_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x0c000) +#define MX3x_ETB_SLOT4_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x10000) +#define MX3x_ETB_SLOT5_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x14000) +#define MX3x_ECT_CTIO_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x18000) +#define MX3x_I2C_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x80000) +#define MX3x_I2C3_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x84000) +#define MX3x_UART1_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x90000) +#define MX3x_UART2_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x94000) +#define MX3x_I2C2_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x98000) +#define MX3x_OWIRE_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0x9c000) +#define MX3x_SSI1_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0xa0000) +#define MX3x_CSPI1_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0xa4000) +#define MX3x_KPP_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0xa8000) +#define MX3x_IOMUXC_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0xac000) +#define MX3x_ECT_IP1_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0xb8000) +#define MX3x_ECT_IP2_BASE_ADDR (MX3x_AIPS1_BASE_ADDR + 0xbc000) + +/* + * SPBA global module enabled #0 + */ +#define MX3x_SPBA0_BASE_ADDR 0x50000000 +#define MX3x_SPBA0_SIZE SZ_1M +#define MX3x_UART3_BASE_ADDR (MX3x_SPBA0_BASE_ADDR + 0x0c000) +#define MX3x_CSPI2_BASE_ADDR (MX3x_SPBA0_BASE_ADDR + 0x10000) +#define MX3x_SSI2_BASE_ADDR (MX3x_SPBA0_BASE_ADDR + 0x14000) +#define MX3x_ATA_DMA_BASE_ADDR (MX3x_SPBA0_BASE_ADDR + 0x20000) +#define MX3x_MSHC1_BASE_ADDR (MX3x_SPBA0_BASE_ADDR + 0x24000) +#define MX3x_SPBA_CTRL_BASE_ADDR (MX3x_SPBA0_BASE_ADDR + 0x3c000) + +/* + * AIPS 2 + */ +#define MX3x_AIPS2_BASE_ADDR 0x53f00000 +#define MX3x_AIPS2_SIZE SZ_1M +#define MX3x_CCM_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0x80000) +#define MX3x_GPT1_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0x90000) +#define MX3x_EPIT1_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0x94000) +#define MX3x_EPIT2_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0x98000) +#define MX3x_GPIO3_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xa4000) +#define MX3x_SCC_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xac000) +#define MX3x_RNGA_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xb0000) +#define MX3x_IPU_CTRL_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xc0000) +#define MX3x_AUDMUX_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xc4000) +#define MX3x_GPIO1_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xcc000) +#define MX3x_GPIO2_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xd0000) +#define MX3x_SDMA_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xd4000) +#define MX3x_RTC_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xd8000) +#define MX3x_WDOG_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xdc000) +#define MX3x_PWM_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xe0000) +#define MX3x_RTIC_BASE_ADDR (MX3x_AIPS2_BASE_ADDR + 0xec000) + +/* + * ROMP and AVIC + */ +#define MX3x_ROMP_BASE_ADDR 0x60000000 +#define MX3x_ROMP_SIZE SZ_1M + +#define MX3x_AVIC_BASE_ADDR 0x68000000 +#define MX3x_AVIC_SIZE SZ_1M + +/* + * Memory regions and CS + */ +#define MX3x_IPU_MEM_BASE_ADDR 0x70000000 +#define MX3x_CSD0_BASE_ADDR 0x80000000 +#define MX3x_CSD1_BASE_ADDR 0x90000000 + +#define MX3x_CS0_BASE_ADDR 0xa0000000 +#define MX3x_CS1_BASE_ADDR 0xa8000000 +#define MX3x_CS2_BASE_ADDR 0xb0000000 +#define MX3x_CS3_BASE_ADDR 0xb2000000 + +#define MX3x_CS4_BASE_ADDR 0xb4000000 +#define MX3x_CS4_BASE_ADDR_VIRT 0xf6000000 +#define MX3x_CS4_SIZE SZ_32M + +#define MX3x_CS5_BASE_ADDR 0xb6000000 +#define MX3x_CS5_BASE_ADDR_VIRT 0xf8000000 +#define MX3x_CS5_SIZE SZ_32M + +/* + * NAND, SDRAM, WEIM, M3IF, EMI controllers + */ +#define MX3x_X_MEMC_BASE_ADDR 0xb8000000 +#define MX3x_X_MEMC_SIZE SZ_64K +#define MX3x_ESDCTL_BASE_ADDR (MX3x_X_MEMC_BASE_ADDR + 0x1000) +#define MX3x_WEIM_BASE_ADDR (MX3x_X_MEMC_BASE_ADDR + 0x2000) +#define MX3x_M3IF_BASE_ADDR (MX3x_X_MEMC_BASE_ADDR + 0x3000) +#define MX3x_EMI_CTL_BASE_ADDR (MX3x_X_MEMC_BASE_ADDR + 0x4000) +#define MX3x_PCMCIA_CTL_BASE_ADDR MX3x_EMI_CTL_BASE_ADDR + +#define MX3x_PCMCIA_MEM_BASE_ADDR 0xbc000000 + +/* + * Interrupt numbers + */ +#include <asm/irq.h> +#define MX3x_INT_I2C3 (NR_IRQS_LEGACY + 3) +#define MX3x_INT_I2C2 (NR_IRQS_LEGACY + 4) +#define MX3x_INT_RTIC (NR_IRQS_LEGACY + 6) +#define MX3x_INT_I2C (NR_IRQS_LEGACY + 10) +#define MX3x_INT_CSPI2 (NR_IRQS_LEGACY + 13) +#define MX3x_INT_CSPI1 (NR_IRQS_LEGACY + 14) +#define MX3x_INT_ATA (NR_IRQS_LEGACY + 15) +#define MX3x_INT_UART3 (NR_IRQS_LEGACY + 18) +#define MX3x_INT_IIM (NR_IRQS_LEGACY + 19) +#define MX3x_INT_RNGA (NR_IRQS_LEGACY + 22) +#define MX3x_INT_EVTMON (NR_IRQS_LEGACY + 23) +#define MX3x_INT_KPP (NR_IRQS_LEGACY + 24) +#define MX3x_INT_RTC (NR_IRQS_LEGACY + 25) +#define MX3x_INT_PWM (NR_IRQS_LEGACY + 26) +#define MX3x_INT_EPIT2 (NR_IRQS_LEGACY + 27) +#define MX3x_INT_EPIT1 (NR_IRQS_LEGACY + 28) +#define MX3x_INT_GPT (NR_IRQS_LEGACY + 29) +#define MX3x_INT_POWER_FAIL (NR_IRQS_LEGACY + 30) +#define MX3x_INT_UART2 (NR_IRQS_LEGACY + 32) +#define MX3x_INT_NANDFC (NR_IRQS_LEGACY + 33) +#define MX3x_INT_SDMA (NR_IRQS_LEGACY + 34) +#define MX3x_INT_MSHC1 (NR_IRQS_LEGACY + 39) +#define MX3x_INT_IPU_ERR (NR_IRQS_LEGACY + 41) +#define MX3x_INT_IPU_SYN (NR_IRQS_LEGACY + 42) +#define MX3x_INT_UART1 (NR_IRQS_LEGACY + 45) +#define MX3x_INT_ECT (NR_IRQS_LEGACY + 48) +#define MX3x_INT_SCC_SCM (NR_IRQS_LEGACY + 49) +#define MX3x_INT_SCC_SMN (NR_IRQS_LEGACY + 50) +#define MX3x_INT_GPIO2 (NR_IRQS_LEGACY + 51) +#define MX3x_INT_GPIO1 (NR_IRQS_LEGACY + 52) +#define MX3x_INT_WDOG (NR_IRQS_LEGACY + 55) +#define MX3x_INT_GPIO3 (NR_IRQS_LEGACY + 56) +#define MX3x_INT_EXT_POWER (NR_IRQS_LEGACY + 58) +#define MX3x_INT_EXT_TEMPER (NR_IRQS_LEGACY + 59) +#define MX3x_INT_EXT_SENSOR60 (NR_IRQS_LEGACY + 60) +#define MX3x_INT_EXT_SENSOR61 (NR_IRQS_LEGACY + 61) +#define MX3x_INT_EXT_WDOG (NR_IRQS_LEGACY + 62) +#define MX3x_INT_EXT_TV (NR_IRQS_LEGACY + 63) + +#define MX3x_PROD_SIGNATURE 0x1 /* For MX31 */ + +#endif /* ifndef __MACH_MX3x_H__ */ diff --git a/arch/arm/mach-imx/mxc.h b/arch/arm/mach-imx/mxc.h new file mode 100644 index 000000000..fe2d0f5ab --- /dev/null +++ b/arch/arm/mach-imx/mxc.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2004-2007, 2010-2015 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de) + */ + +#ifndef __ASM_ARCH_MXC_H__ +#define __ASM_ARCH_MXC_H__ + +#include <linux/types.h> +#include <soc/imx/cpu.h> + +#ifndef __ASM_ARCH_MXC_HARDWARE_H__ +#error "Do not include directly." +#endif + +#define IMX_DDR_TYPE_LPDDR2 1 + +#ifndef __ASSEMBLY__ + +#ifdef CONFIG_SOC_IMX6SL +static inline bool cpu_is_imx6sl(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6SL; +} +#else +static inline bool cpu_is_imx6sl(void) +{ + return false; +} +#endif + +static inline bool cpu_is_imx6dl(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6DL; +} + +static inline bool cpu_is_imx6sx(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6SX; +} + +static inline bool cpu_is_imx6ul(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6UL; +} + +static inline bool cpu_is_imx6ull(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6ULL; +} + +static inline bool cpu_is_imx6ulz(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6ULZ; +} + +static inline bool cpu_is_imx6sll(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6SLL; +} + +static inline bool cpu_is_imx6q(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6Q; +} + +static inline bool cpu_is_imx7d(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX7D; +} + +struct cpu_op { + u32 cpu_rate; +}; + +int tzic_enable_wake(void); + +extern struct cpu_op *(*get_cpu_op)(int *op); +#endif + +#define imx_readl readl_relaxed +#define imx_readw readw_relaxed +#define imx_writel writel_relaxed +#define imx_writew writew_relaxed + +#endif /* __ASM_ARCH_MXC_H__ */ diff --git a/arch/arm/mach-imx/platsmp.c b/arch/arm/mach-imx/platsmp.c new file mode 100644 index 000000000..972639038 --- /dev/null +++ b/arch/arm/mach-imx/platsmp.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/init.h> +#include <linux/of_address.h> +#include <linux/of.h> +#include <linux/smp.h> + +#include <asm/cacheflush.h> +#include <asm/page.h> +#include <asm/smp_scu.h> +#include <asm/mach/map.h> + +#include "common.h" +#include "hardware.h" + +u32 g_diag_reg; +static void __iomem *scu_base; + +static struct map_desc scu_io_desc __initdata = { + /* .virtual and .pfn are run-time assigned */ + .length = SZ_4K, + .type = MT_DEVICE, +}; + +void __init imx_scu_map_io(void) +{ + unsigned long base; + + /* Get SCU base */ + asm("mrc p15, 4, %0, c15, c0, 0" : "=r" (base)); + + scu_io_desc.virtual = IMX_IO_P2V(base); + scu_io_desc.pfn = __phys_to_pfn(base); + iotable_init(&scu_io_desc, 1); + + scu_base = IMX_IO_ADDRESS(base); +} + +static int imx_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + imx_set_cpu_jump(cpu, v7_secondary_startup); + imx_enable_cpu(cpu, true); + return 0; +} + +/* + * 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) +{ + int i, ncores; + + ncores = scu_get_core_count(scu_base); + + for (i = ncores; i < NR_CPUS; i++) + set_cpu_possible(i, false); +} + +void imx_smp_prepare(void) +{ + scu_enable(scu_base); +} + +static void __init imx_smp_prepare_cpus(unsigned int max_cpus) +{ + imx_smp_prepare(); + + /* + * The diagnostic register holds the errata bits. Mostly bootloader + * does not bring up secondary cores, so that when errata bits are set + * in bootloader, they are set only for boot cpu. But on a SMP + * configuration, it should be equally done on every single core. + * Read the register from boot cpu here, and will replicate it into + * secondary cores when booting them. + */ + asm("mrc p15, 0, %0, c15, c0, 1" : "=r" (g_diag_reg) : : "cc"); + sync_cache_w(&g_diag_reg); +} + +const struct smp_operations imx_smp_ops __initconst = { + .smp_init_cpus = imx_smp_init_cpus, + .smp_prepare_cpus = imx_smp_prepare_cpus, + .smp_boot_secondary = imx_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_die = imx_cpu_die, + .cpu_kill = imx_cpu_kill, +#endif +}; + +/* + * Initialise the CPU possible map early - this describes the CPUs + * which may be present or become present in the system. + */ +static void __init imx7_smp_init_cpus(void) +{ + struct device_node *np; + int i, ncores = 0; + + /* The iMX7D SCU does not report core count, get it from DT */ + for_each_of_cpu_node(np) + ncores++; + + for (i = ncores; i < NR_CPUS; i++) + set_cpu_possible(i, false); +} + +const struct smp_operations imx7_smp_ops __initconst = { + .smp_init_cpus = imx7_smp_init_cpus, + .smp_boot_secondary = imx_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_die = imx_cpu_die, + .cpu_kill = imx_cpu_kill, +#endif +}; + +#define DCFG_CCSR_SCRATCHRW1 0x200 + +static int ls1021a_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + arch_send_wakeup_ipi_mask(cpumask_of(cpu)); + + return 0; +} + +static void __init ls1021a_smp_prepare_cpus(unsigned int max_cpus) +{ + struct device_node *np; + void __iomem *dcfg_base; + unsigned long paddr; + + np = of_find_compatible_node(NULL, NULL, "fsl,ls1021a-dcfg"); + dcfg_base = of_iomap(np, 0); + of_node_put(np); + BUG_ON(!dcfg_base); + + paddr = __pa_symbol(secondary_startup); + writel_relaxed(cpu_to_be32(paddr), dcfg_base + DCFG_CCSR_SCRATCHRW1); + + iounmap(dcfg_base); +} + +const struct smp_operations ls1021a_smp_ops __initconst = { + .smp_prepare_cpus = ls1021a_smp_prepare_cpus, + .smp_boot_secondary = ls1021a_boot_secondary, +}; diff --git a/arch/arm/mach-imx/pm-imx25.c b/arch/arm/mach-imx/pm-imx25.c new file mode 100644 index 000000000..f253e5019 --- /dev/null +++ b/arch/arm/mach-imx/pm-imx25.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2016 NXP Semiconductors + */ + +#include <linux/kernel.h> +#include <linux/suspend.h> +#include <linux/io.h> + +static int imx25_suspend_enter(suspend_state_t state) +{ + if (!IS_ENABLED(CONFIG_PM)) + return 0; + + switch (state) { + case PM_SUSPEND_MEM: + cpu_do_idle(); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct platform_suspend_ops imx25_suspend_ops = { + .enter = imx25_suspend_enter, + .valid = suspend_valid_only_mem, +}; + +void __init imx25_pm_init(void) +{ + suspend_set_ops(&imx25_suspend_ops); +} diff --git a/arch/arm/mach-imx/pm-imx27.c b/arch/arm/mach-imx/pm-imx27.c new file mode 100644 index 000000000..237e8aa9f --- /dev/null +++ b/arch/arm/mach-imx/pm-imx27.c @@ -0,0 +1,52 @@ +/* + * i.MX27 Power Management Routines + * + * Based on Freescale's BSP + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License. + */ + +#include <linux/of_address.h> +#include <linux/kernel.h> +#include <linux/suspend.h> +#include <linux/io.h> + +#include "common.h" +#include "hardware.h" + +static int mx27_suspend_enter(suspend_state_t state) +{ + void __iomem *ccm_base; + struct device_node *np; + u32 cscr; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx27-ccm"); + ccm_base = of_iomap(np, 0); + BUG_ON(!ccm_base); + + switch (state) { + case PM_SUSPEND_MEM: + /* Clear MPEN and SPEN to disable MPLL/SPLL */ + cscr = imx_readl(ccm_base); + cscr &= 0xFFFFFFFC; + imx_writel(cscr, ccm_base); + /* Executes WFI */ + cpu_do_idle(); + break; + + default: + return -EINVAL; + } + return 0; +} + +static const struct platform_suspend_ops mx27_suspend_ops = { + .enter = mx27_suspend_enter, + .valid = suspend_valid_only_mem, +}; + +void __init imx27_pm_init(void) +{ + suspend_set_ops(&mx27_suspend_ops); +} diff --git a/arch/arm/mach-imx/pm-imx5.c b/arch/arm/mach-imx/pm-imx5.c new file mode 100644 index 000000000..2e3af2bc7 --- /dev/null +++ b/arch/arm/mach-imx/pm-imx5.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ +#include <linux/suspend.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/export.h> + +#include <linux/genalloc.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> + +#include <asm/cacheflush.h> +#include <asm/fncpy.h> +#include <asm/system_misc.h> +#include <asm/tlbflush.h> + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +#define MXC_CCM_CLPCR 0x54 +#define MXC_CCM_CLPCR_LPM_OFFSET 0 +#define MXC_CCM_CLPCR_LPM_MASK 0x3 +#define MXC_CCM_CLPCR_STBY_COUNT_OFFSET 9 +#define MXC_CCM_CLPCR_VSTBY (0x1 << 8) +#define MXC_CCM_CLPCR_SBYOS (0x1 << 6) + +#define MXC_CORTEXA8_PLAT_LPC 0xc +#define MXC_CORTEXA8_PLAT_LPC_DSM (1 << 0) +#define MXC_CORTEXA8_PLAT_LPC_DBG_DSM (1 << 1) + +#define MXC_SRPG_NEON_SRPGCR 0x280 +#define MXC_SRPG_ARM_SRPGCR 0x2a0 +#define MXC_SRPG_EMPGC0_SRPGCR 0x2c0 +#define MXC_SRPG_EMPGC1_SRPGCR 0x2d0 + +#define MXC_SRPGCR_PCR 1 + +/* + * The WAIT_UNCLOCKED_POWER_OFF state only requires <= 500ns to exit. + * This is also the lowest power state possible without affecting + * non-cpu parts of the system. For these reasons, imx5 should default + * to always using this state for cpu idling. The PM_SUSPEND_STANDBY also + * uses this state and needs to take no action when registers remain configured + * for this state. + */ +#define IMX5_DEFAULT_CPU_IDLE_STATE WAIT_UNCLOCKED_POWER_OFF + +struct imx5_suspend_io_state { + u32 offset; + u32 clear; + u32 set; + u32 saved_value; +}; + +struct imx5_pm_data { + phys_addr_t ccm_addr; + phys_addr_t cortex_addr; + phys_addr_t gpc_addr; + phys_addr_t m4if_addr; + phys_addr_t iomuxc_addr; + void (*suspend_asm)(void __iomem *ocram_vbase); + const u32 *suspend_asm_sz; + const struct imx5_suspend_io_state *suspend_io_config; + int suspend_io_count; +}; + +static const struct imx5_suspend_io_state imx53_suspend_io_config[] = { +#define MX53_DSE_HIGHZ_MASK (0x7 << 19) + {.offset = 0x584, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM0 */ + {.offset = 0x594, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM1 */ + {.offset = 0x560, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM2 */ + {.offset = 0x554, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM3 */ + {.offset = 0x574, .clear = MX53_DSE_HIGHZ_MASK}, /* CAS */ + {.offset = 0x588, .clear = MX53_DSE_HIGHZ_MASK}, /* RAS */ + {.offset = 0x578, .clear = MX53_DSE_HIGHZ_MASK}, /* SDCLK_0 */ + {.offset = 0x570, .clear = MX53_DSE_HIGHZ_MASK}, /* SDCLK_1 */ + + {.offset = 0x580, .clear = MX53_DSE_HIGHZ_MASK}, /* SDODT0 */ + {.offset = 0x564, .clear = MX53_DSE_HIGHZ_MASK}, /* SDODT1 */ + {.offset = 0x57c, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS0 */ + {.offset = 0x590, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS1 */ + {.offset = 0x568, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS2 */ + {.offset = 0x558, .clear = MX53_DSE_HIGHZ_MASK}, /* SDSQ3 */ + {.offset = 0x6f0, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_ADDS */ + {.offset = 0x718, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_BODS */ + {.offset = 0x71c, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B1DS */ + {.offset = 0x728, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B2DS */ + {.offset = 0x72c, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B3DS */ + + /* Controls the CKE signal which is required to leave self refresh */ + {.offset = 0x720, .clear = MX53_DSE_HIGHZ_MASK, .set = 1 << 19}, /* CTLDS */ +}; + +static const struct imx5_pm_data imx51_pm_data __initconst = { + .ccm_addr = 0x73fd4000, + .cortex_addr = 0x83fa0000, + .gpc_addr = 0x73fd8000, +}; + +static const struct imx5_pm_data imx53_pm_data __initconst = { + .ccm_addr = 0x53fd4000, + .cortex_addr = 0x63fa0000, + .gpc_addr = 0x53fd8000, + .m4if_addr = 0x63fd8000, + .iomuxc_addr = 0x53fa8000, + .suspend_asm = &imx53_suspend, + .suspend_asm_sz = &imx53_suspend_sz, + .suspend_io_config = imx53_suspend_io_config, + .suspend_io_count = ARRAY_SIZE(imx53_suspend_io_config), +}; + +#define MX5_MAX_SUSPEND_IOSTATE ARRAY_SIZE(imx53_suspend_io_config) + +/* + * This structure is for passing necessary data for low level ocram + * suspend code(arch/arm/mach-imx/suspend-imx53.S), if this struct + * definition is changed, the offset definition in that file + * must be also changed accordingly otherwise, the suspend to ocram + * function will be broken! + */ +struct imx5_cpu_suspend_info { + void __iomem *m4if_base; + void __iomem *iomuxc_base; + u32 io_count; + struct imx5_suspend_io_state io_state[MX5_MAX_SUSPEND_IOSTATE]; +} __aligned(8); + +static void __iomem *ccm_base; +static void __iomem *cortex_base; +static void __iomem *gpc_base; +static void __iomem *suspend_ocram_base; +static void (*imx5_suspend_in_ocram_fn)(void __iomem *ocram_vbase); + +/* + * set cpu low power mode before WFI instruction. This function is called + * mx5 because it can be used for mx51, and mx53. + */ +static void mx5_cpu_lp_set(enum mxc_cpu_pwr_mode mode) +{ + u32 plat_lpc, arm_srpgcr, ccm_clpcr; + u32 empgc0, empgc1; + int stop_mode = 0; + + /* always allow platform to issue a deep sleep mode request */ + plat_lpc = imx_readl(cortex_base + MXC_CORTEXA8_PLAT_LPC) & + ~(MXC_CORTEXA8_PLAT_LPC_DSM); + ccm_clpcr = imx_readl(ccm_base + MXC_CCM_CLPCR) & + ~(MXC_CCM_CLPCR_LPM_MASK); + arm_srpgcr = imx_readl(gpc_base + MXC_SRPG_ARM_SRPGCR) & + ~(MXC_SRPGCR_PCR); + empgc0 = imx_readl(gpc_base + MXC_SRPG_EMPGC0_SRPGCR) & + ~(MXC_SRPGCR_PCR); + empgc1 = imx_readl(gpc_base + MXC_SRPG_EMPGC1_SRPGCR) & + ~(MXC_SRPGCR_PCR); + + switch (mode) { + case WAIT_CLOCKED: + break; + case WAIT_UNCLOCKED: + ccm_clpcr |= 0x1 << MXC_CCM_CLPCR_LPM_OFFSET; + break; + case WAIT_UNCLOCKED_POWER_OFF: + case STOP_POWER_OFF: + plat_lpc |= MXC_CORTEXA8_PLAT_LPC_DSM + | MXC_CORTEXA8_PLAT_LPC_DBG_DSM; + if (mode == WAIT_UNCLOCKED_POWER_OFF) { + ccm_clpcr |= 0x1 << MXC_CCM_CLPCR_LPM_OFFSET; + ccm_clpcr &= ~MXC_CCM_CLPCR_VSTBY; + ccm_clpcr &= ~MXC_CCM_CLPCR_SBYOS; + stop_mode = 0; + } else { + ccm_clpcr |= 0x2 << MXC_CCM_CLPCR_LPM_OFFSET; + ccm_clpcr |= 0x3 << MXC_CCM_CLPCR_STBY_COUNT_OFFSET; + ccm_clpcr |= MXC_CCM_CLPCR_VSTBY; + ccm_clpcr |= MXC_CCM_CLPCR_SBYOS; + stop_mode = 1; + } + arm_srpgcr |= MXC_SRPGCR_PCR; + break; + case STOP_POWER_ON: + ccm_clpcr |= 0x2 << MXC_CCM_CLPCR_LPM_OFFSET; + break; + default: + printk(KERN_WARNING "UNKNOWN cpu power mode: %d\n", mode); + return; + } + + imx_writel(plat_lpc, cortex_base + MXC_CORTEXA8_PLAT_LPC); + imx_writel(ccm_clpcr, ccm_base + MXC_CCM_CLPCR); + imx_writel(arm_srpgcr, gpc_base + MXC_SRPG_ARM_SRPGCR); + imx_writel(arm_srpgcr, gpc_base + MXC_SRPG_NEON_SRPGCR); + + if (stop_mode) { + empgc0 |= MXC_SRPGCR_PCR; + empgc1 |= MXC_SRPGCR_PCR; + + imx_writel(empgc0, gpc_base + MXC_SRPG_EMPGC0_SRPGCR); + imx_writel(empgc1, gpc_base + MXC_SRPG_EMPGC1_SRPGCR); + } +} + +static int mx5_suspend_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_MEM: + mx5_cpu_lp_set(STOP_POWER_OFF); + break; + case PM_SUSPEND_STANDBY: + /* DEFAULT_IDLE_STATE already configured */ + break; + default: + return -EINVAL; + } + + if (state == PM_SUSPEND_MEM) { + local_flush_tlb_all(); + flush_cache_all(); + + /*clear the EMPGC0/1 bits */ + imx_writel(0, gpc_base + MXC_SRPG_EMPGC0_SRPGCR); + imx_writel(0, gpc_base + MXC_SRPG_EMPGC1_SRPGCR); + + if (imx5_suspend_in_ocram_fn) + imx5_suspend_in_ocram_fn(suspend_ocram_base); + else + cpu_do_idle(); + + } else { + cpu_do_idle(); + } + + /* return registers to default idle state */ + mx5_cpu_lp_set(IMX5_DEFAULT_CPU_IDLE_STATE); + return 0; +} + +static int mx5_pm_valid(suspend_state_t state) +{ + return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX); +} + +static const struct platform_suspend_ops mx5_suspend_ops = { + .valid = mx5_pm_valid, + .enter = mx5_suspend_enter, +}; + +static inline int imx5_cpu_do_idle(void) +{ + int ret = tzic_enable_wake(); + + if (likely(!ret)) + cpu_do_idle(); + + return ret; +} + +static void imx5_pm_idle(void) +{ + imx5_cpu_do_idle(); +} + +static int __init imx_suspend_alloc_ocram( + size_t size, + void __iomem **virt_out, + phys_addr_t *phys_out) +{ + struct device_node *node; + struct platform_device *pdev; + struct gen_pool *ocram_pool; + unsigned long ocram_base; + void __iomem *virt; + phys_addr_t phys; + int ret = 0; + + /* Copied from imx6: TODO factorize */ + node = of_find_compatible_node(NULL, NULL, "mmio-sram"); + if (!node) { + pr_warn("%s: failed to find ocram node!\n", __func__); + return -ENODEV; + } + + pdev = of_find_device_by_node(node); + if (!pdev) { + pr_warn("%s: failed to find ocram device!\n", __func__); + ret = -ENODEV; + goto put_node; + } + + ocram_pool = gen_pool_get(&pdev->dev, NULL); + if (!ocram_pool) { + pr_warn("%s: ocram pool unavailable!\n", __func__); + ret = -ENODEV; + goto put_device; + } + + ocram_base = gen_pool_alloc(ocram_pool, size); + if (!ocram_base) { + pr_warn("%s: unable to alloc ocram!\n", __func__); + ret = -ENOMEM; + goto put_device; + } + + phys = gen_pool_virt_to_phys(ocram_pool, ocram_base); + virt = __arm_ioremap_exec(phys, size, false); + if (phys_out) + *phys_out = phys; + if (virt_out) + *virt_out = virt; + +put_device: + put_device(&pdev->dev); +put_node: + of_node_put(node); + + return ret; +} + +static int __init imx5_suspend_init(const struct imx5_pm_data *soc_data) +{ + struct imx5_cpu_suspend_info *suspend_info; + int ret; + /* Need this to avoid compile error due to const typeof in fncpy.h */ + void (*suspend_asm)(void __iomem *) = soc_data->suspend_asm; + + if (!suspend_asm) + return 0; + + if (!soc_data->suspend_asm_sz || !*soc_data->suspend_asm_sz) + return -EINVAL; + + ret = imx_suspend_alloc_ocram( + *soc_data->suspend_asm_sz + sizeof(*suspend_info), + &suspend_ocram_base, NULL); + if (ret) + return ret; + + suspend_info = suspend_ocram_base; + + suspend_info->io_count = soc_data->suspend_io_count; + memcpy(suspend_info->io_state, soc_data->suspend_io_config, + sizeof(*suspend_info->io_state) * soc_data->suspend_io_count); + + suspend_info->m4if_base = ioremap(soc_data->m4if_addr, SZ_16K); + if (!suspend_info->m4if_base) { + ret = -ENOMEM; + goto failed_map_m4if; + } + + suspend_info->iomuxc_base = ioremap(soc_data->iomuxc_addr, SZ_16K); + if (!suspend_info->iomuxc_base) { + ret = -ENOMEM; + goto failed_map_iomuxc; + } + + imx5_suspend_in_ocram_fn = fncpy( + suspend_ocram_base + sizeof(*suspend_info), + suspend_asm, + *soc_data->suspend_asm_sz); + + return 0; + +failed_map_iomuxc: + iounmap(suspend_info->m4if_base); + +failed_map_m4if: + return ret; +} + +static int __init imx5_pm_common_init(const struct imx5_pm_data *data) +{ + int ret; + struct clk *gpc_dvfs_clk = clk_get(NULL, "gpc_dvfs"); + + if (IS_ERR(gpc_dvfs_clk)) + return PTR_ERR(gpc_dvfs_clk); + + ret = clk_prepare_enable(gpc_dvfs_clk); + if (ret) + return ret; + + arm_pm_idle = imx5_pm_idle; + + ccm_base = ioremap(data->ccm_addr, SZ_16K); + cortex_base = ioremap(data->cortex_addr, SZ_16K); + gpc_base = ioremap(data->gpc_addr, SZ_16K); + WARN_ON(!ccm_base || !cortex_base || !gpc_base); + + /* Set the registers to the default cpu idle state. */ + mx5_cpu_lp_set(IMX5_DEFAULT_CPU_IDLE_STATE); + + ret = imx5_cpuidle_init(); + if (ret) + pr_warn("%s: cpuidle init failed %d\n", __func__, ret); + + ret = imx5_suspend_init(data); + if (ret) + pr_warn("%s: No DDR LPM support with suspend %d!\n", + __func__, ret); + + suspend_set_ops(&mx5_suspend_ops); + + return 0; +} + +void __init imx51_pm_init(void) +{ + if (IS_ENABLED(CONFIG_SOC_IMX51)) + imx5_pm_common_init(&imx51_pm_data); +} + +void __init imx53_pm_init(void) +{ + if (IS_ENABLED(CONFIG_SOC_IMX53)) + imx5_pm_common_init(&imx53_pm_data); +} diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c new file mode 100644 index 000000000..045b9fdd3 --- /dev/null +++ b/arch/arm/mach-imx/pm-imx6.c @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011-2014 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/clk/imx.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/genalloc.h> +#include <linux/irqchip/arm-gic.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/suspend.h> +#include <asm/cacheflush.h> +#include <asm/fncpy.h> +#include <asm/proc-fns.h> +#include <asm/suspend.h> +#include <asm/tlb.h> + +#include "common.h" +#include "hardware.h" + +#define CCR 0x0 +#define BM_CCR_WB_COUNT (0x7 << 16) +#define BM_CCR_RBC_BYPASS_COUNT (0x3f << 21) +#define BM_CCR_RBC_EN (0x1 << 27) + +#define CLPCR 0x54 +#define BP_CLPCR_LPM 0 +#define BM_CLPCR_LPM (0x3 << 0) +#define BM_CLPCR_BYPASS_PMIC_READY (0x1 << 2) +#define BM_CLPCR_ARM_CLK_DIS_ON_LPM (0x1 << 5) +#define BM_CLPCR_SBYOS (0x1 << 6) +#define BM_CLPCR_DIS_REF_OSC (0x1 << 7) +#define BM_CLPCR_VSTBY (0x1 << 8) +#define BP_CLPCR_STBY_COUNT 9 +#define BM_CLPCR_STBY_COUNT (0x3 << 9) +#define BM_CLPCR_COSC_PWRDOWN (0x1 << 11) +#define BM_CLPCR_WB_PER_AT_LPM (0x1 << 16) +#define BM_CLPCR_WB_CORE_AT_LPM (0x1 << 17) +#define BM_CLPCR_BYP_MMDC_CH0_LPM_HS (0x1 << 19) +#define BM_CLPCR_BYP_MMDC_CH1_LPM_HS (0x1 << 21) +#define BM_CLPCR_MASK_CORE0_WFI (0x1 << 22) +#define BM_CLPCR_MASK_CORE1_WFI (0x1 << 23) +#define BM_CLPCR_MASK_CORE2_WFI (0x1 << 24) +#define BM_CLPCR_MASK_CORE3_WFI (0x1 << 25) +#define BM_CLPCR_MASK_SCU_IDLE (0x1 << 26) +#define BM_CLPCR_MASK_L2CC_IDLE (0x1 << 27) + +#define CGPR 0x64 +#define BM_CGPR_INT_MEM_CLK_LPM (0x1 << 17) + +#define MX6Q_SUSPEND_OCRAM_SIZE 0x1000 +#define MX6_MAX_MMDC_IO_NUM 33 + +static void __iomem *ccm_base; +static void __iomem *suspend_ocram_base; +static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase); + +/* + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx6_suspend code + * PM_INFO structure(imx6_cpu_pm_info) + * ======================== low address ======================= + */ + +struct imx6_pm_base { + phys_addr_t pbase; + void __iomem *vbase; +}; + +struct imx6_pm_socdata { + u32 ddr_type; + const char *mmdc_compat; + const char *src_compat; + const char *iomuxc_compat; + const char *gpc_compat; + const char *pl310_compat; + const u32 mmdc_io_num; + const u32 *mmdc_io_offset; +}; + +static const u32 imx6q_mmdc_io_offset[] __initconst = { + 0x5ac, 0x5b4, 0x528, 0x520, /* DQM0 ~ DQM3 */ + 0x514, 0x510, 0x5bc, 0x5c4, /* DQM4 ~ DQM7 */ + 0x56c, 0x578, 0x588, 0x594, /* CAS, RAS, SDCLK_0, SDCLK_1 */ + 0x5a8, 0x5b0, 0x524, 0x51c, /* SDQS0 ~ SDQS3 */ + 0x518, 0x50c, 0x5b8, 0x5c0, /* SDQS4 ~ SDQS7 */ + 0x784, 0x788, 0x794, 0x79c, /* GPR_B0DS ~ GPR_B3DS */ + 0x7a0, 0x7a4, 0x7a8, 0x748, /* GPR_B4DS ~ GPR_B7DS */ + 0x59c, 0x5a0, 0x750, 0x774, /* SODT0, SODT1, MODE_CTL, MODE */ + 0x74c, /* GPR_ADDS */ +}; + +static const u32 imx6dl_mmdc_io_offset[] __initconst = { + 0x470, 0x474, 0x478, 0x47c, /* DQM0 ~ DQM3 */ + 0x480, 0x484, 0x488, 0x48c, /* DQM4 ~ DQM7 */ + 0x464, 0x490, 0x4ac, 0x4b0, /* CAS, RAS, SDCLK_0, SDCLK_1 */ + 0x4bc, 0x4c0, 0x4c4, 0x4c8, /* DRAM_SDQS0 ~ DRAM_SDQS3 */ + 0x4cc, 0x4d0, 0x4d4, 0x4d8, /* DRAM_SDQS4 ~ DRAM_SDQS7 */ + 0x764, 0x770, 0x778, 0x77c, /* GPR_B0DS ~ GPR_B3DS */ + 0x780, 0x784, 0x78c, 0x748, /* GPR_B4DS ~ GPR_B7DS */ + 0x4b4, 0x4b8, 0x750, 0x760, /* SODT0, SODT1, MODE_CTL, MODE */ + 0x74c, /* GPR_ADDS */ +}; + +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, SDCKE1, RESET */ +}; + +static const u32 imx6sll_mmdc_io_offset[] __initconst = { + 0x294, 0x298, 0x29c, 0x2a0, /* DQM0 ~ DQM3 */ + 0x544, 0x54c, 0x554, 0x558, /* GPR_B0DS ~ GPR_B3DS */ + 0x530, 0x540, 0x2ac, 0x52c, /* MODE_CTL, MODE, SDCLK_0, GPR_ADDDS */ + 0x2a4, 0x2a8, /* SDCKE0, SDCKE1*/ +}; + +static const u32 imx6sx_mmdc_io_offset[] __initconst = { + 0x2ec, 0x2f0, 0x2f4, 0x2f8, /* DQM0 ~ DQM3 */ + 0x60c, 0x610, 0x61c, 0x620, /* GPR_B0DS ~ GPR_B3DS */ + 0x300, 0x2fc, 0x32c, 0x5f4, /* CAS, RAS, SDCLK_0, GPR_ADDS */ + 0x310, 0x314, 0x5f8, 0x608, /* SODT0, SODT1, MODE_CTL, MODE */ + 0x330, 0x334, 0x338, 0x33c, /* SDQS0 ~ SDQS3 */ +}; + +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 const struct imx6_pm_socdata imx6q_pm_data __initconst = { + .mmdc_compat = "fsl,imx6q-mmdc", + .src_compat = "fsl,imx6q-src", + .iomuxc_compat = "fsl,imx6q-iomuxc", + .gpc_compat = "fsl,imx6q-gpc", + .pl310_compat = "arm,pl310-cache", + .mmdc_io_num = ARRAY_SIZE(imx6q_mmdc_io_offset), + .mmdc_io_offset = imx6q_mmdc_io_offset, +}; + +static const struct imx6_pm_socdata imx6dl_pm_data __initconst = { + .mmdc_compat = "fsl,imx6q-mmdc", + .src_compat = "fsl,imx6q-src", + .iomuxc_compat = "fsl,imx6dl-iomuxc", + .gpc_compat = "fsl,imx6q-gpc", + .pl310_compat = "arm,pl310-cache", + .mmdc_io_num = ARRAY_SIZE(imx6dl_mmdc_io_offset), + .mmdc_io_offset = imx6dl_mmdc_io_offset, +}; + +static const struct imx6_pm_socdata imx6sl_pm_data __initconst = { + .mmdc_compat = "fsl,imx6sl-mmdc", + .src_compat = "fsl,imx6sl-src", + .iomuxc_compat = "fsl,imx6sl-iomuxc", + .gpc_compat = "fsl,imx6sl-gpc", + .pl310_compat = "arm,pl310-cache", + .mmdc_io_num = ARRAY_SIZE(imx6sl_mmdc_io_offset), + .mmdc_io_offset = imx6sl_mmdc_io_offset, +}; + +static const struct imx6_pm_socdata imx6sll_pm_data __initconst = { + .mmdc_compat = "fsl,imx6sll-mmdc", + .src_compat = "fsl,imx6sll-src", + .iomuxc_compat = "fsl,imx6sll-iomuxc", + .gpc_compat = "fsl,imx6sll-gpc", + .pl310_compat = "arm,pl310-cache", + .mmdc_io_num = ARRAY_SIZE(imx6sll_mmdc_io_offset), + .mmdc_io_offset = imx6sll_mmdc_io_offset, +}; + +static const struct imx6_pm_socdata imx6sx_pm_data __initconst = { + .mmdc_compat = "fsl,imx6sx-mmdc", + .src_compat = "fsl,imx6sx-src", + .iomuxc_compat = "fsl,imx6sx-iomuxc", + .gpc_compat = "fsl,imx6sx-gpc", + .pl310_compat = "arm,pl310-cache", + .mmdc_io_num = ARRAY_SIZE(imx6sx_mmdc_io_offset), + .mmdc_io_offset = imx6sx_mmdc_io_offset, +}; + +static const struct imx6_pm_socdata imx6ul_pm_data __initconst = { + .mmdc_compat = "fsl,imx6ul-mmdc", + .src_compat = "fsl,imx6ul-src", + .iomuxc_compat = "fsl,imx6ul-iomuxc", + .gpc_compat = "fsl,imx6ul-gpc", + .pl310_compat = NULL, + .mmdc_io_num = ARRAY_SIZE(imx6ul_mmdc_io_offset), + .mmdc_io_offset = imx6ul_mmdc_io_offset, +}; + +/* + * This structure is for passing necessary data for low level ocram + * suspend code(arch/arm/mach-imx/suspend-imx6.S), if this struct + * definition is changed, the offset definition in + * arch/arm/mach-imx/suspend-imx6.S must be also changed accordingly, + * otherwise, the suspend to ocram function will be broken! + */ +struct imx6_cpu_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 ddr_type; + u32 pm_info_size; /* Size of pm_info. */ + struct imx6_pm_base mmdc_base; + struct imx6_pm_base src_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; + 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); + +void imx6_set_int_mem_clk_lpm(bool enable) +{ + u32 val = readl_relaxed(ccm_base + CGPR); + + val &= ~BM_CGPR_INT_MEM_CLK_LPM; + if (enable) + val |= BM_CGPR_INT_MEM_CLK_LPM; + writel_relaxed(val, ccm_base + CGPR); +} + +void imx6_enable_rbc(bool enable) +{ + u32 val; + + /* + * need to mask all interrupts in GPC before + * operating RBC configurations + */ + imx_gpc_mask_all(); + + /* configure RBC enable bit */ + val = readl_relaxed(ccm_base + CCR); + val &= ~BM_CCR_RBC_EN; + val |= enable ? BM_CCR_RBC_EN : 0; + writel_relaxed(val, ccm_base + CCR); + + /* configure RBC count */ + val = readl_relaxed(ccm_base + CCR); + val &= ~BM_CCR_RBC_BYPASS_COUNT; + val |= enable ? BM_CCR_RBC_BYPASS_COUNT : 0; + writel(val, ccm_base + CCR); + + /* + * 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_gpc_restore_all(); +} + +static void imx6q_enable_wb(bool enable) +{ + u32 val; + + /* configure well bias enable bit */ + val = readl_relaxed(ccm_base + CLPCR); + val &= ~BM_CLPCR_WB_PER_AT_LPM; + val |= enable ? BM_CLPCR_WB_PER_AT_LPM : 0; + writel_relaxed(val, ccm_base + CLPCR); + + /* configure well bias count */ + val = readl_relaxed(ccm_base + CCR); + val &= ~BM_CCR_WB_COUNT; + val |= enable ? BM_CCR_WB_COUNT : 0; + writel_relaxed(val, ccm_base + CCR); +} + +int imx6_set_lpm(enum mxc_cpu_pwr_mode mode) +{ + u32 val = readl_relaxed(ccm_base + CLPCR); + + val &= ~BM_CLPCR_LPM; + switch (mode) { + case WAIT_CLOCKED: + break; + case WAIT_UNCLOCKED: + val |= 0x1 << BP_CLPCR_LPM; + val |= BM_CLPCR_ARM_CLK_DIS_ON_LPM; + break; + case STOP_POWER_ON: + val |= 0x2 << BP_CLPCR_LPM; + val &= ~BM_CLPCR_VSTBY; + val &= ~BM_CLPCR_SBYOS; + if (cpu_is_imx6sl()) + val |= BM_CLPCR_BYPASS_PMIC_READY; + if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() || + cpu_is_imx6ull() || cpu_is_imx6sll() || cpu_is_imx6ulz()) + val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS; + else + val |= BM_CLPCR_BYP_MMDC_CH1_LPM_HS; + break; + case WAIT_UNCLOCKED_POWER_OFF: + val |= 0x1 << BP_CLPCR_LPM; + val &= ~BM_CLPCR_VSTBY; + val &= ~BM_CLPCR_SBYOS; + break; + case STOP_POWER_OFF: + val |= 0x2 << BP_CLPCR_LPM; + val |= 0x3 << BP_CLPCR_STBY_COUNT; + val |= BM_CLPCR_VSTBY; + val |= BM_CLPCR_SBYOS; + if (cpu_is_imx6sl() || cpu_is_imx6sx()) + val |= BM_CLPCR_BYPASS_PMIC_READY; + if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() || + cpu_is_imx6ull() || cpu_is_imx6sll() || cpu_is_imx6ulz()) + val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS; + else + val |= BM_CLPCR_BYP_MMDC_CH1_LPM_HS; + break; + default: + return -EINVAL; + } + + /* + * ERR007265: CCM: 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_GINT. + * 2) Software should then unmask IRQ #32 in GPC before setting CCM + * Low-Power mode. + * 3) Software should mask IRQ #32 right after CCM Low-Power mode + * is set (set bits 0-1 of CCM_CLPCR). + * + * Note that IRQ #32 is GIC SPI #0. + */ + if (mode != WAIT_CLOCKED) + imx_gpc_hwirq_unmask(0); + writel_relaxed(val, ccm_base + CLPCR); + if (mode != WAIT_CLOCKED) + imx_gpc_hwirq_mask(0); + + return 0; +} + +static int imx6q_suspend_finish(unsigned long val) +{ + if (!imx6_suspend_in_ocram_fn) { + cpu_do_idle(); + } else { + /* + * call low level suspend function in ocram, + * as we need to float DDR IO. + */ + local_flush_tlb_all(); + /* check if need to flush internal L2 cache */ + if (!((struct imx6_cpu_pm_info *) + suspend_ocram_base)->l2_base.vbase) + flush_cache_all(); + imx6_suspend_in_ocram_fn(suspend_ocram_base); + } + + return 0; +} + +static int imx6q_pm_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_STANDBY: + imx6_set_lpm(STOP_POWER_ON); + imx6_set_int_mem_clk_lpm(true); + imx_gpc_pre_suspend(false); + if (cpu_is_imx6sl()) + imx6sl_set_wait_clk(true); + /* Zzz ... */ + cpu_do_idle(); + if (cpu_is_imx6sl()) + imx6sl_set_wait_clk(false); + imx_gpc_post_resume(); + imx6_set_lpm(WAIT_CLOCKED); + break; + case PM_SUSPEND_MEM: + imx6_set_lpm(STOP_POWER_OFF); + imx6_set_int_mem_clk_lpm(false); + imx6q_enable_wb(true); + /* + * For suspend into ocram, asm code already take care of + * RBC setting, so we do NOT need to do that here. + */ + if (!imx6_suspend_in_ocram_fn) + imx6_enable_rbc(true); + imx_gpc_pre_suspend(true); + imx_anatop_pre_suspend(); + /* Zzz ... */ + cpu_suspend(0, imx6q_suspend_finish); + if (cpu_is_imx6q() || cpu_is_imx6dl()) + imx_smp_prepare(); + imx_anatop_post_resume(); + imx_gpc_post_resume(); + imx6_enable_rbc(false); + imx6q_enable_wb(false); + imx6_set_int_mem_clk_lpm(true); + imx6_set_lpm(WAIT_CLOCKED); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int imx6q_pm_valid(suspend_state_t state) +{ + return (state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM); +} + +static const struct platform_suspend_ops imx6q_pm_ops = { + .enter = imx6q_pm_enter, + .valid = imx6q_pm_valid, +}; + +static int __init imx6_pm_get_base(struct imx6_pm_base *base, + const char *compat) +{ + struct device_node *node; + struct resource res; + int ret = 0; + + node = of_find_compatible_node(NULL, NULL, compat); + if (!node) + return -ENODEV; + + ret = of_address_to_resource(node, 0, &res); + if (ret) + goto put_node; + + base->pbase = res.start; + base->vbase = ioremap(res.start, resource_size(&res)); + if (!base->vbase) + ret = -ENOMEM; + +put_node: + of_node_put(node); + return ret; +} + +static int __init imx6q_suspend_init(const struct imx6_pm_socdata *socdata) +{ + phys_addr_t ocram_pbase; + struct device_node *node; + struct platform_device *pdev; + struct imx6_cpu_pm_info *pm_info; + struct gen_pool *ocram_pool; + unsigned long ocram_base; + int i, ret = 0; + const u32 *mmdc_offset_array; + + suspend_set_ops(&imx6q_pm_ops); + + if (!socdata) { + pr_warn("%s: invalid argument!\n", __func__); + return -EINVAL; + } + + node = of_find_compatible_node(NULL, NULL, "mmio-sram"); + if (!node) { + pr_warn("%s: failed to find ocram node!\n", __func__); + return -ENODEV; + } + + pdev = of_find_device_by_node(node); + if (!pdev) { + pr_warn("%s: failed to find ocram device!\n", __func__); + ret = -ENODEV; + goto put_node; + } + + ocram_pool = gen_pool_get(&pdev->dev, NULL); + if (!ocram_pool) { + pr_warn("%s: ocram pool unavailable!\n", __func__); + ret = -ENODEV; + goto put_device; + } + + ocram_base = gen_pool_alloc(ocram_pool, MX6Q_SUSPEND_OCRAM_SIZE); + if (!ocram_base) { + pr_warn("%s: unable to alloc ocram!\n", __func__); + ret = -ENOMEM; + goto put_device; + } + + ocram_pbase = gen_pool_virt_to_phys(ocram_pool, ocram_base); + + suspend_ocram_base = __arm_ioremap_exec(ocram_pbase, + MX6Q_SUSPEND_OCRAM_SIZE, false); + + memset(suspend_ocram_base, 0, sizeof(*pm_info)); + pm_info = suspend_ocram_base; + pm_info->pbase = ocram_pbase; + pm_info->resume_addr = __pa_symbol(v7_cpu_resume); + pm_info->pm_info_size = sizeof(*pm_info); + + /* + * ccm physical address is not used by asm code currently, + * so get ccm virtual address directly. + */ + pm_info->ccm_base.vbase = ccm_base; + + ret = imx6_pm_get_base(&pm_info->mmdc_base, socdata->mmdc_compat); + if (ret) { + pr_warn("%s: failed to get mmdc base %d!\n", __func__, ret); + goto put_device; + } + + ret = imx6_pm_get_base(&pm_info->src_base, socdata->src_compat); + if (ret) { + pr_warn("%s: failed to get src base %d!\n", __func__, ret); + goto src_map_failed; + } + + ret = imx6_pm_get_base(&pm_info->iomuxc_base, socdata->iomuxc_compat); + if (ret) { + pr_warn("%s: failed to get iomuxc base %d!\n", __func__, ret); + goto iomuxc_map_failed; + } + + ret = imx6_pm_get_base(&pm_info->gpc_base, socdata->gpc_compat); + if (ret) { + pr_warn("%s: failed to get gpc base %d!\n", __func__, ret); + goto gpc_map_failed; + } + + if (socdata->pl310_compat) { + ret = imx6_pm_get_base(&pm_info->l2_base, socdata->pl310_compat); + if (ret) { + pr_warn("%s: failed to get pl310-cache base %d!\n", + __func__, ret); + goto pl310_cache_map_failed; + } + } + + pm_info->ddr_type = imx_mmdc_get_ddr_type(); + pm_info->mmdc_io_num = socdata->mmdc_io_num; + mmdc_offset_array = socdata->mmdc_io_offset; + + for (i = 0; i < pm_info->mmdc_io_num; i++) { + pm_info->mmdc_io_val[i][0] = + mmdc_offset_array[i]; + pm_info->mmdc_io_val[i][1] = + readl_relaxed(pm_info->iomuxc_base.vbase + + mmdc_offset_array[i]); + } + + imx6_suspend_in_ocram_fn = fncpy( + suspend_ocram_base + sizeof(*pm_info), + &imx6_suspend, + MX6Q_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); + + __arm_iomem_set_ro(suspend_ocram_base, MX6Q_SUSPEND_OCRAM_SIZE); + + goto put_device; + +pl310_cache_map_failed: + iounmap(pm_info->gpc_base.vbase); +gpc_map_failed: + iounmap(pm_info->iomuxc_base.vbase); +iomuxc_map_failed: + iounmap(pm_info->src_base.vbase); +src_map_failed: + iounmap(pm_info->mmdc_base.vbase); +put_device: + put_device(&pdev->dev); +put_node: + of_node_put(node); + + return ret; +} + +static void __init imx6_pm_common_init(const struct imx6_pm_socdata + *socdata) +{ + struct regmap *gpr; + int ret; + + WARN_ON(!ccm_base); + + if (IS_ENABLED(CONFIG_SUSPEND)) { + ret = imx6q_suspend_init(socdata); + if (ret) + pr_warn("%s: No DDR LPM support with suspend %d!\n", + __func__, ret); + } + + /* + * This is for SW workaround step #1 of ERR007265, see comments + * in imx6_set_lpm for details of this errata. + * Force IOMUXC irq pending, so that the interrupt to GPC can be + * used to deassert dsm_request signal when the signal gets + * asserted unexpectedly. + */ + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (!IS_ERR(gpr)) + regmap_update_bits(gpr, IOMUXC_GPR1, IMX6Q_GPR1_GINT, + IMX6Q_GPR1_GINT); +} + +static void imx6_pm_stby_poweroff(void) +{ + gic_cpu_if_down(0); + imx6_set_lpm(STOP_POWER_OFF); + imx6q_suspend_finish(0); + + mdelay(1000); + + pr_emerg("Unable to poweroff system\n"); +} + +static int imx6_pm_stby_poweroff_probe(void) +{ + if (pm_power_off) { + pr_warn("%s: pm_power_off already claimed %p %ps!\n", + __func__, pm_power_off, pm_power_off); + return -EBUSY; + } + + pm_power_off = imx6_pm_stby_poweroff; + return 0; +} + +void __init imx6_pm_ccm_init(const char *ccm_compat) +{ + struct device_node *np; + u32 val; + + np = of_find_compatible_node(NULL, NULL, ccm_compat); + ccm_base = of_iomap(np, 0); + BUG_ON(!ccm_base); + + /* + * Initialize CCM_CLPCR_LPM into RUN mode to avoid ARM core + * clock being shut down unexpectedly by WAIT mode. + */ + val = readl_relaxed(ccm_base + CLPCR); + val &= ~BM_CLPCR_LPM; + writel_relaxed(val, ccm_base + CLPCR); + + if (of_property_read_bool(np, "fsl,pmic-stby-poweroff")) + imx6_pm_stby_poweroff_probe(); + + of_node_put(np); +} + +void __init imx6q_pm_init(void) +{ + imx6_pm_common_init(&imx6q_pm_data); +} + +void __init imx6dl_pm_init(void) +{ + imx6_pm_common_init(&imx6dl_pm_data); +} + +void __init imx6sl_pm_init(void) +{ + struct regmap *gpr; + + if (cpu_is_imx6sl()) { + imx6_pm_common_init(&imx6sl_pm_data); + } else { + imx6_pm_common_init(&imx6sll_pm_data); + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (!IS_ERR(gpr)) + regmap_update_bits(gpr, IOMUXC_GPR5, + IMX6SLL_GPR5_AFCG_X_BYPASS_MASK, 0); + } +} + +void __init imx6sx_pm_init(void) +{ + imx6_pm_common_init(&imx6sx_pm_data); +} + +void __init imx6ul_pm_init(void) +{ + imx6_pm_common_init(&imx6ul_pm_data); +} diff --git a/arch/arm/mach-imx/pm-imx7ulp.c b/arch/arm/mach-imx/pm-imx7ulp.c new file mode 100644 index 000000000..2e756d819 --- /dev/null +++ b/arch/arm/mach-imx/pm-imx7ulp.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2018 NXP + * Author: Dong Aisheng <aisheng.dong@nxp.com> + */ + +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include "common.h" + +#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 BM_PMCTRL_PSTOPO (3 << BP_PMCTRL_PSTOPO) +#define BM_PMCTRL_RUNM (3 << BP_PMCTRL_RUNM) +#define BM_PMCTRL_STOPM (7 << BP_PMCTRL_STOPM) + +static void __iomem *smc1_base; + +int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode) +{ + u32 val = readl_relaxed(smc1_base + SMC_PMCTRL); + + /* clear all */ + val &= ~(BM_PMCTRL_RUNM | BM_PMCTRL_STOPM | BM_PMCTRL_PSTOPO); + + switch (mode) { + case ULP_PM_RUN: + /* system/bus clock enabled */ + val |= PSTOPO_PSTOP3 << BP_PMCTRL_PSTOPO; + break; + case ULP_PM_WAIT: + /* system clock disabled, bus clock enabled */ + val |= PSTOPO_PSTOP2 << BP_PMCTRL_PSTOPO; + break; + case ULP_PM_STOP: + /* system/bus clock disabled */ + val |= PSTOPO_PSTOP1 << BP_PMCTRL_PSTOPO; + break; + default: + return -EINVAL; + } + + writel_relaxed(val, smc1_base + SMC_PMCTRL); + + return 0; +} + +void __init imx7ulp_pm_init(void) +{ + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-smc1"); + smc1_base = of_iomap(np, 0); + of_node_put(np); + WARN_ON(!smc1_base); + + imx7ulp_set_lpm(ULP_PM_RUN); +} diff --git a/arch/arm/mach-imx/resume-imx6.S b/arch/arm/mach-imx/resume-imx6.S new file mode 100644 index 000000000..5bd1ba7ef --- /dev/null +++ b/arch/arm/mach-imx/resume-imx6.S @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/asm-offsets.h> +#include <asm/hardware/cache-l2x0.h> +#include "hardware.h" + +/* + * The following code must assume it is running from physical address + * where absolute virtual addresses to the data section have to be + * turned into relative ones. + */ + +ENTRY(v7_cpu_resume) + bl v7_invalidate_l1 +#ifdef CONFIG_CACHE_L2X0 + bl l2c310_early_resume +#endif + b cpu_resume +ENDPROC(v7_cpu_resume) diff --git a/arch/arm/mach-imx/src.c b/arch/arm/mach-imx/src.c new file mode 100644 index 000000000..59a8e8cc4 --- /dev/null +++ b/arch/arm/mach-imx/src.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/smp.h> +#include <asm/smp_plat.h> +#include "common.h" +#include "hardware.h" + +#define SRC_SCR 0x000 +#define SRC_GPR1_V1 0x020 +#define SRC_GPR1_V2 0x074 +#define SRC_GPR1(gpr_v2) ((gpr_v2) ? SRC_GPR1_V2 : SRC_GPR1_V1) +#define BP_SRC_SCR_WARM_RESET_ENABLE 0 +#define BP_SRC_SCR_SW_GPU_RST 1 +#define BP_SRC_SCR_SW_VPU_RST 2 +#define BP_SRC_SCR_SW_IPU1_RST 3 +#define BP_SRC_SCR_SW_OPEN_VG_RST 4 +#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_A7RCR1 0x008 +#define BP_SRC_A7RCR1_A7_CORE1_ENABLE 1 +#define GPC_CPU_PGC_SW_PUP_REQ 0xf0 +#define GPC_CPU_PGC_SW_PDN_REQ 0xfc +#define GPC_PGC_C1 0x840 +#define BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7 0x2 + +static void __iomem *src_base; +static DEFINE_SPINLOCK(scr_lock); +static bool gpr_v2; +static void __iomem *gpc_base; + +static const int sw_reset_bits[5] = { + BP_SRC_SCR_SW_GPU_RST, + BP_SRC_SCR_SW_VPU_RST, + BP_SRC_SCR_SW_IPU1_RST, + BP_SRC_SCR_SW_OPEN_VG_RST, + BP_SRC_SCR_SW_IPU2_RST +}; + +static int imx_src_reset_module(struct reset_controller_dev *rcdev, + unsigned long sw_reset_idx) +{ + unsigned long timeout; + unsigned long flags; + int bit; + u32 val; + + if (sw_reset_idx >= ARRAY_SIZE(sw_reset_bits)) + return -EINVAL; + + bit = 1 << sw_reset_bits[sw_reset_idx]; + + spin_lock_irqsave(&scr_lock, flags); + val = readl_relaxed(src_base + SRC_SCR); + val |= bit; + writel_relaxed(val, src_base + SRC_SCR); + spin_unlock_irqrestore(&scr_lock, flags); + + timeout = jiffies + msecs_to_jiffies(1000); + while (readl(src_base + SRC_SCR) & bit) { + if (time_after(jiffies, timeout)) + return -ETIME; + cpu_relax(); + } + + return 0; +} + +static const struct reset_control_ops imx_src_ops = { + .reset = imx_src_reset_module, +}; + +static void imx_gpcv2_set_m_core_pgc(bool enable, u32 offset) +{ + writel_relaxed(enable, gpc_base + offset); +} + +/* + * The motivation for bringing up the second i.MX7D core inside the kernel + * is that legacy vendor bootloaders usually do not implement PSCI support. + * This is a significant blocker for systems in the field that are running old + * bootloader versions to upgrade to a modern mainline kernel version, as only + * one CPU of the i.MX7D would be brought up. + * Bring up the second i.MX7D core inside the kernel to make the migration + * path to mainline kernel easier for the existing iMX7D users. + */ +void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn) +{ + u32 reg = pdn ? GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ; + u32 val, pup; + int ret; + + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C1); + val = readl_relaxed(gpc_base + reg); + val |= BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7; + writel_relaxed(val, gpc_base + reg); + + ret = readl_relaxed_poll_timeout_atomic(gpc_base + reg, pup, + !(pup & BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7), + 5, 1000000); + if (ret < 0) { + pr_err("i.MX7D: CORE1_A7 power up timeout\n"); + val &= ~BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7; + writel_relaxed(val, gpc_base + reg); + } + + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C1); +} + +void imx_enable_cpu(int cpu, bool enable) +{ + u32 mask, val; + + cpu = cpu_logical_map(cpu); + spin_lock(&scr_lock); + if (gpr_v2) { + 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(&scr_lock); +} + +void imx_set_cpu_jump(int cpu, void *jump_addr) +{ + cpu = cpu_logical_map(cpu); + writel_relaxed(__pa_symbol(jump_addr), + src_base + SRC_GPR1(gpr_v2) + cpu * 8); +} + +u32 imx_get_cpu_arg(int cpu) +{ + cpu = cpu_logical_map(cpu); + return readl_relaxed(src_base + SRC_GPR1(gpr_v2) + cpu * 8 + 4); +} + +void imx_set_cpu_arg(int cpu, u32 arg) +{ + cpu = cpu_logical_map(cpu); + writel_relaxed(arg, src_base + SRC_GPR1(gpr_v2) + cpu * 8 + 4); +} + +void __init imx_src_init(void) +{ + struct device_node *np; + u32 val; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx51-src"); + if (!np) + return; + src_base = of_iomap(np, 0); + WARN_ON(!src_base); + + /* + * force warm reset sources to generate cold reset + * for a more reliable restart + */ + spin_lock(&scr_lock); + val = readl_relaxed(src_base + SRC_SCR); + val &= ~(1 << BP_SRC_SCR_WARM_RESET_ENABLE); + writel_relaxed(val, src_base + SRC_SCR); + spin_unlock(&scr_lock); +} + +void __init imx7_src_init(void) +{ + struct device_node *np; + + gpr_v2 = true; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7d-src"); + if (!np) + return; + + src_base = of_iomap(np, 0); + if (!src_base) + return; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7d-gpc"); + if (!np) + return; + + gpc_base = of_iomap(np, 0); + if (!gpc_base) + return; +} + +static const struct of_device_id imx_src_dt_ids[] = { + { .compatible = "fsl,imx51-src" }, + { /* sentinel */ } +}; + +static int imx_src_probe(struct platform_device *pdev) +{ + struct reset_controller_dev *rcdev; + + rcdev = devm_kzalloc(&pdev->dev, sizeof(*rcdev), GFP_KERNEL); + if (!rcdev) + return -ENOMEM; + + rcdev->ops = &imx_src_ops; + rcdev->dev = &pdev->dev; + rcdev->of_node = pdev->dev.of_node; + rcdev->nr_resets = ARRAY_SIZE(sw_reset_bits); + + return devm_reset_controller_register(&pdev->dev, rcdev); +} + +static struct platform_driver imx_src_driver = { + .driver = { + .name = "imx-src", + .of_match_table = imx_src_dt_ids, + }, + .probe = imx_src_probe, +}; +builtin_platform_driver(imx_src_driver); diff --git a/arch/arm/mach-imx/ssi-fiq-ksym.c b/arch/arm/mach-imx/ssi-fiq-ksym.c new file mode 100644 index 000000000..c1e7c3ac0 --- /dev/null +++ b/arch/arm/mach-imx/ssi-fiq-ksym.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Exported ksyms for the SSI FIQ handler + * + * Copyright (C) 2009, Sascha Hauer <s.hauer@pengutronix.de> + */ + +#include <linux/module.h> + +#include <linux/platform_data/asoc-imx-ssi.h> + +EXPORT_SYMBOL(imx_ssi_fiq_tx_buffer); +EXPORT_SYMBOL(imx_ssi_fiq_rx_buffer); +EXPORT_SYMBOL(imx_ssi_fiq_start); +EXPORT_SYMBOL(imx_ssi_fiq_end); +EXPORT_SYMBOL(imx_ssi_fiq_base); + diff --git a/arch/arm/mach-imx/ssi-fiq.S b/arch/arm/mach-imx/ssi-fiq.S new file mode 100644 index 000000000..68d7fdea9 --- /dev/null +++ b/arch/arm/mach-imx/ssi-fiq.S @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2009 Sascha Hauer <s.hauer@pengutronix.de> + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> + +/* + * r8 = bit 0-15: tx offset, bit 16-31: tx buffer size + * r9 = bit 0-15: rx offset, bit 16-31: rx buffer size + */ + +#define SSI_STX0 0x00 +#define SSI_SRX0 0x08 +#define SSI_SISR 0x14 +#define SSI_SIER 0x18 +#define SSI_SACNT 0x38 + +#define SSI_SACNT_AC97EN (1 << 0) + +#define SSI_SIER_TFE0_EN (1 << 0) +#define SSI_SISR_TFE0 (1 << 0) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SIER_RFF0_EN (1 << 2) + + .text + .global imx_ssi_fiq_start + .global imx_ssi_fiq_end + .global imx_ssi_fiq_base + .global imx_ssi_fiq_rx_buffer + .global imx_ssi_fiq_tx_buffer + +/* + * imx_ssi_fiq_start is _intentionally_ not marked as a function symbol + * using ENDPROC(). imx_ssi_fiq_start and imx_ssi_fiq_end are used to + * mark the function body so that it can be copied to the FIQ vector in + * the vectors page. imx_ssi_fiq_start should only be called as the result + * of an FIQ: calling it directly will not work. + */ +imx_ssi_fiq_start: + ldr r12, .L_imx_ssi_fiq_base + + /* TX */ + ldr r13, .L_imx_ssi_fiq_tx_buffer + + /* shall we send? */ + ldr r11, [r12, #SSI_SIER] + tst r11, #SSI_SIER_TFE0_EN + beq 1f + + /* TX FIFO empty? */ + ldr r11, [r12, #SSI_SISR] + tst r11, #SSI_SISR_TFE0 + beq 1f + + mov r10, #0x10000 + sub r10, #1 + and r10, r10, r8 /* r10: current buffer offset */ + + add r13, r13, r10 + + ldrh r11, [r13] + strh r11, [r12, #SSI_STX0] + + ldrh r11, [r13, #2] + strh r11, [r12, #SSI_STX0] + + ldrh r11, [r13, #4] + strh r11, [r12, #SSI_STX0] + + ldrh r11, [r13, #6] + strh r11, [r12, #SSI_STX0] + + add r10, #8 + lsr r11, r8, #16 /* r11: buffer size */ + cmp r10, r11 + lslgt r8, r11, #16 + addle r8, #8 +1: + /* RX */ + + /* shall we receive? */ + ldr r11, [r12, #SSI_SIER] + tst r11, #SSI_SIER_RFF0_EN + beq 1f + + /* RX FIFO full? */ + ldr r11, [r12, #SSI_SISR] + tst r11, #SSI_SISR_RFF0 + beq 1f + + ldr r13, .L_imx_ssi_fiq_rx_buffer + + mov r10, #0x10000 + sub r10, #1 + and r10, r10, r9 /* r10: current buffer offset */ + + add r13, r13, r10 + + ldr r11, [r12, #SSI_SACNT] + tst r11, #SSI_SACNT_AC97EN + + ldr r11, [r12, #SSI_SRX0] + strh r11, [r13] + + ldr r11, [r12, #SSI_SRX0] + strh r11, [r13, #2] + + /* dummy read to skip slot 12 */ + ldrne r11, [r12, #SSI_SRX0] + + ldr r11, [r12, #SSI_SRX0] + strh r11, [r13, #4] + + ldr r11, [r12, #SSI_SRX0] + strh r11, [r13, #6] + + /* dummy read to skip slot 12 */ + ldrne r11, [r12, #SSI_SRX0] + + add r10, #8 + lsr r11, r9, #16 /* r11: buffer size */ + cmp r10, r11 + lslgt r9, r11, #16 + addle r9, #8 + +1: + @ return from FIQ + subs pc, lr, #4 + + .align +.L_imx_ssi_fiq_base: +imx_ssi_fiq_base: + .word 0x0 +.L_imx_ssi_fiq_rx_buffer: +imx_ssi_fiq_rx_buffer: + .word 0x0 +.L_imx_ssi_fiq_tx_buffer: +imx_ssi_fiq_tx_buffer: + .word 0x0 +.L_imx_ssi_fiq_end: +imx_ssi_fiq_end: + diff --git a/arch/arm/mach-imx/suspend-imx53.S b/arch/arm/mach-imx/suspend-imx53.S new file mode 100644 index 000000000..46570ec2f --- /dev/null +++ b/arch/arm/mach-imx/suspend-imx53.S @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. + */ +/* + */ + +#include <linux/linkage.h> + +#define M4IF_MCR0_OFFSET (0x008C) +#define M4IF_MCR0_FDVFS (0x1 << 11) +#define M4IF_MCR0_FDVACK (0x1 << 27) + + .align 3 + +/* + * ==================== low level suspend ==================== + * + * On entry + * r0: pm_info structure address; + * + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx53_suspend code + * PM_INFO structure(imx5_cpu_suspend_info) + * ======================== low address ======================= + */ + +/* Offsets of members of struct imx5_cpu_suspend_info */ +#define SUSPEND_INFO_MX53_M4IF_V_OFFSET 0x0 +#define SUSPEND_INFO_MX53_IOMUXC_V_OFFSET 0x4 +#define SUSPEND_INFO_MX53_IO_COUNT_OFFSET 0x8 +#define SUSPEND_INFO_MX53_IO_STATE_OFFSET 0xc + +ENTRY(imx53_suspend) + stmfd sp!, {r4,r5,r6,r7} + + /* Save pad config */ + ldr r1, [r0, #SUSPEND_INFO_MX53_IO_COUNT_OFFSET] + cmp r1, #0 + beq skip_pad_conf_1 + + add r2, r0, #SUSPEND_INFO_MX53_IO_STATE_OFFSET + ldr r3, [r0, #SUSPEND_INFO_MX53_IOMUXC_V_OFFSET] + +1: + ldr r5, [r2], #12 /* IOMUXC register offset */ + ldr r6, [r3, r5] /* current value */ + str r6, [r2], #4 /* save area */ + subs r1, r1, #1 + bne 1b + +skip_pad_conf_1: + /* Set FDVFS bit of M4IF_MCR0 to request DDR to enter self-refresh */ + ldr r1, [r0, #SUSPEND_INFO_MX53_M4IF_V_OFFSET] + ldr r2,[r1, #M4IF_MCR0_OFFSET] + orr r2, r2, #M4IF_MCR0_FDVFS + str r2,[r1, #M4IF_MCR0_OFFSET] + + /* Poll FDVACK bit of M4IF_MCR to wait for DDR to enter self-refresh */ +wait_sr_ack: + ldr r2,[r1, #M4IF_MCR0_OFFSET] + ands r2, r2, #M4IF_MCR0_FDVACK + beq wait_sr_ack + + /* Set pad config */ + ldr r1, [r0, #SUSPEND_INFO_MX53_IO_COUNT_OFFSET] + cmp r1, #0 + beq skip_pad_conf_2 + + add r2, r0, #SUSPEND_INFO_MX53_IO_STATE_OFFSET + ldr r3, [r0, #SUSPEND_INFO_MX53_IOMUXC_V_OFFSET] + +2: + ldr r5, [r2], #4 /* IOMUXC register offset */ + ldr r6, [r2], #4 /* clear */ + ldr r7, [r3, r5] + bic r7, r7, r6 + ldr r6, [r2], #8 /* set */ + orr r7, r7, r6 + str r7, [r3, r5] + subs r1, r1, #1 + bne 2b + +skip_pad_conf_2: + /* Zzz, enter stop mode */ + wfi + nop + nop + nop + nop + + /* Restore pad config */ + ldr r1, [r0, #SUSPEND_INFO_MX53_IO_COUNT_OFFSET] + cmp r1, #0 + beq skip_pad_conf_3 + + add r2, r0, #SUSPEND_INFO_MX53_IO_STATE_OFFSET + ldr r3, [r0, #SUSPEND_INFO_MX53_IOMUXC_V_OFFSET] + +3: + ldr r5, [r2], #12 /* IOMUXC register offset */ + ldr r6, [r2], #4 /* saved value */ + str r6, [r3, r5] + subs r1, r1, #1 + bne 3b + +skip_pad_conf_3: + /* Clear FDVFS bit of M4IF_MCR0 to request DDR to exit self-refresh */ + ldr r1, [r0, #SUSPEND_INFO_MX53_M4IF_V_OFFSET] + ldr r2,[r1, #M4IF_MCR0_OFFSET] + bic r2, r2, #M4IF_MCR0_FDVFS + str r2,[r1, #M4IF_MCR0_OFFSET] + + /* Poll FDVACK bit of M4IF_MCR to wait for DDR to exit self-refresh */ +wait_ar_ack: + ldr r2,[r1, #M4IF_MCR0_OFFSET] + ands r2, r2, #M4IF_MCR0_FDVACK + bne wait_ar_ack + + /* Restore registers */ + ldmfd sp!, {r4,r5,r6,r7} + mov pc, lr + +ENDPROC(imx53_suspend) + +ENTRY(imx53_suspend_sz) + .word . - imx53_suspend diff --git a/arch/arm/mach-imx/suspend-imx6.S b/arch/arm/mach-imx/suspend-imx6.S new file mode 100644 index 000000000..e06f946b7 --- /dev/null +++ b/arch/arm/mach-imx/suspend-imx6.S @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/asm-offsets.h> +#include <asm/hardware/cache-l2x0.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 ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx6_suspend code + * PM_INFO structure(imx6_cpu_pm_info) + * ======================== low address ======================= + */ + +/* + * Below offsets are based on struct imx6_cpu_pm_info + * which defined in arch/arm/mach-imx/pm-imx6q.c, this + * structure contains necessary pm info for low level + * suspend related code. + */ +#define PM_INFO_PBASE_OFFSET 0x0 +#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 MX6Q_SRC_GPR1 0x20 +#define MX6Q_SRC_GPR2 0x24 +#define MX6Q_MMDC_MAPSR 0x404 +#define MX6Q_MMDC_MPDGCTRL0 0x83c +#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 + + .align 3 + .arm + + .macro sync_l2_cache + + /* sync L2 cache to drain L2's buffers to DRAM. */ +#ifdef CONFIG_CACHE_L2X0 + ldr r11, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] + teq r11, #0 + beq 6f + mov r6, #0x0 + str r6, [r11, #L2X0_CACHE_SYNC] +1: + ldr r6, [r11, #L2X0_CACHE_SYNC] + ands r6, r6, #0x1 + bne 1b +6: +#endif + + .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 + + /* reset read FIFO, RST_RD_FIFO */ + ldr r7, =MX6Q_MMDC_MPDGCTRL0 + ldr r6, [r11, r7] + orr r6, r6, #(1 << 31) + str r6, [r11, r7] +2: + ldr r6, [r11, r7] + ands r6, r6, #(1 << 31) + bne 2b + + /* reset FIFO a second time */ + ldr r6, [r11, r7] + orr r6, r6, #(1 << 31) + str r6, [r11, r7] +3: + ldr r6, [r11, r7] + ands r6, r6, #(1 << 31) + bne 3b +4: + /* let DDR out of self-refresh */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r11, #MX6Q_MMDC_MAPSR] +5: + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne 5b + + /* enable DDR auto power saving */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r11, #MX6Q_MMDC_MAPSR] + + .endm + +ENTRY(imx6_suspend) + 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, =imx6_suspend + ldr r7, =resume + sub r7, r7, r6 + add r8, r1, r4 + add r9, r8, r7 + + /* + * make sure TLB contain the addr we want, + * as we will access them after MMDC IO floated. + */ + + ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldr r6, [r11, #0x0] + ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r6, [r11, #0x0] + ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldr r6, [r11, #0x0] + + /* use r11 to store the IO address */ + ldr r11, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] + /* store physical resume addr and pm_info address. */ + str r9, [r11, #MX6Q_SRC_GPR1] + str r1, [r11, #MX6Q_SRC_GPR2] + + /* need to sync L2 cache before DSM. */ + sync_l2_cache + + ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + /* + * put DDR explicitly into self-refresh and + * disable automatic power savings. + */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r11, #MX6Q_MMDC_MAPSR] + + /* make the DDR explicitly enter self-refresh. */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 21) + str r7, [r11, #MX6Q_MMDC_MAPSR] + +poll_dvfs_set: + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + beq poll_dvfs_set + + ldr r11, [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 + /* 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 + 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: + + /* + * mask all GPC interrupts before + * enabling the RBC counters to + * avoid the counter starting too + * early if an interupt is already + * pending. + */ + ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r6, [r11, #MX6Q_GPC_IMR1] + ldr r7, [r11, #MX6Q_GPC_IMR2] + ldr r8, [r11, #MX6Q_GPC_IMR3] + ldr r9, [r11, #MX6Q_GPC_IMR4] + + ldr r10, =0xffffffff + str r10, [r11, #MX6Q_GPC_IMR1] + str r10, [r11, #MX6Q_GPC_IMR2] + str r10, [r11, #MX6Q_GPC_IMR3] + str r10, [r11, #MX6Q_GPC_IMR4] + + /* + * enable the RBC bypass counter here + * to hold off the interrupts. RBC counter + * = 32 (1ms), Minimum RBC delay should be + * 400us for the analog LDOs to power down. + */ + ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldr r10, [r11, #MX6Q_CCM_CCR] + bic r10, r10, #(0x3f << 21) + orr r10, r10, #(0x20 << 21) + str r10, [r11, #MX6Q_CCM_CCR] + + /* enable the counter. */ + ldr r10, [r11, #MX6Q_CCM_CCR] + orr r10, r10, #(0x1 << 27) + str r10, [r11, #MX6Q_CCM_CCR] + + /* unmask all the GPC interrupts. */ + ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + str r6, [r11, #MX6Q_GPC_IMR1] + str r7, [r11, #MX6Q_GPC_IMR2] + str r8, [r11, #MX6Q_GPC_IMR3] + str r9, [r11, #MX6Q_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 r6, =2000 +rbc_loop: + subs r6, r6, #0x1 + bne rbc_loop + + /* Zzz, enter stop mode */ + wfi + nop + nop + nop + nop + + /* + * run to here means there is pending + * wakeup source, system should auto + * resume, we need to restore MMDC IO first + */ + mov r5, #0x0 + resume_mmdc + + /* return to suspend finish */ + ret 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_MX6Q_SRC_P_OFFSET] + mov r7, #0x0 + str r7, [r11, #MX6Q_SRC_GPR1] + str r7, [r11, #MX6Q_SRC_GPR2] + + ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] + mov r5, #0x1 + resume_mmdc + + ret lr +ENDPROC(imx6_suspend) diff --git a/arch/arm/mach-imx/system.c b/arch/arm/mach-imx/system.c new file mode 100644 index 000000000..e88ca0271 --- /dev/null +++ b/arch/arm/mach-imx/system.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 1999 ARM Limited + * Copyright (C) 2000 Deep Blue Solutions Ltd + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * Copyright 2009 Ilya Yanok, Emcraft Systems Ltd, yanok@emcraft.com + */ + +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include <asm/system_misc.h> +#include <asm/proc-fns.h> +#include <asm/mach-types.h> +#include <asm/hardware/cache-l2x0.h> + +#include "common.h" +#include "hardware.h" + +static void __iomem *wdog_base; +static struct clk *wdog_clk; +static int wcr_enable = (1 << 2); + +/* + * Reset the system. It is called by machine_restart(). + */ +void mxc_restart(enum reboot_mode mode, const char *cmd) +{ + if (!wdog_base) + goto reset_fallback; + + if (!IS_ERR(wdog_clk)) + clk_enable(wdog_clk); + + /* Assert SRS signal */ + imx_writew(wcr_enable, wdog_base); + /* + * Due to imx6q errata ERR004346 (WDOG: WDOG SRS bit requires to be + * written twice), we add another two writes to ensure there must be at + * least two writes happen in the same one 32kHz clock period. We save + * the target check here, since the writes shouldn't be a huge burden + * for other platforms. + */ + imx_writew(wcr_enable, wdog_base); + imx_writew(wcr_enable, wdog_base); + + /* wait for reset to assert... */ + mdelay(500); + + pr_err("%s: Watchdog reset failed to assert reset\n", __func__); + + /* delay to allow the serial port to show the message */ + mdelay(50); + +reset_fallback: + /* we'll take a jump through zero as a poor second */ + soft_restart(0); +} + +void __init mxc_arch_reset_init(void __iomem *base) +{ + wdog_base = base; + + wdog_clk = clk_get_sys("imx2-wdt.0", NULL); + if (IS_ERR(wdog_clk)) + pr_warn("%s: failed to get wdog clock\n", __func__); + else + clk_prepare(wdog_clk); +} + +#ifdef CONFIG_SOC_IMX1 +void __init imx1_reset_init(void __iomem *base) +{ + wcr_enable = (1 << 0); + mxc_arch_reset_init(base); +} +#endif + +#ifdef CONFIG_CACHE_L2X0 +void __init imx_init_l2cache(void) +{ + void __iomem *l2x0_base; + struct device_node *np; + unsigned int val; + + np = of_find_compatible_node(NULL, NULL, "arm,pl310-cache"); + if (!np) + return; + + l2x0_base = of_iomap(np, 0); + if (!l2x0_base) + goto put_node; + + if (!(readl_relaxed(l2x0_base + L2X0_CTRL) & L2X0_CTRL_EN)) { + /* Configure the L2 PREFETCH and POWER registers */ + val = readl_relaxed(l2x0_base + L310_PREFETCH_CTRL); + val |= L310_PREFETCH_CTRL_DBL_LINEFILL | + L310_PREFETCH_CTRL_INSTR_PREFETCH | + L310_PREFETCH_CTRL_DATA_PREFETCH; + + /* Set perfetch offset to improve performance */ + val &= ~L310_PREFETCH_CTRL_OFFSET_MASK; + val |= 15; + + writel_relaxed(val, l2x0_base + L310_PREFETCH_CTRL); + } + + iounmap(l2x0_base); +put_node: + of_node_put(np); +} +#endif diff --git a/arch/arm/mach-imx/tzic.c b/arch/arm/mach-imx/tzic.c new file mode 100644 index 000000000..8b3d98d28 --- /dev/null +++ b/arch/arm/mach-imx/tzic.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C)2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include <asm/mach/irq.h> +#include <asm/exception.h> + +#include "common.h" +#include "hardware.h" +#include "irq-common.h" + +/* + ***************************************** + * TZIC Registers * + ***************************************** + */ + +#define TZIC_INTCNTL 0x0000 /* Control register */ +#define TZIC_INTTYPE 0x0004 /* Controller Type register */ +#define TZIC_IMPID 0x0008 /* Distributor Implementer Identification */ +#define TZIC_PRIOMASK 0x000C /* Priority Mask Reg */ +#define TZIC_SYNCCTRL 0x0010 /* Synchronizer Control register */ +#define TZIC_DSMINT 0x0014 /* DSM interrupt Holdoffregister */ +#define TZIC_INTSEC0(i) (0x0080 + ((i) << 2)) /* Interrupt Security Reg 0 */ +#define TZIC_ENSET0(i) (0x0100 + ((i) << 2)) /* Enable Set Reg 0 */ +#define TZIC_ENCLEAR0(i) (0x0180 + ((i) << 2)) /* Enable Clear Reg 0 */ +#define TZIC_SRCSET0 0x0200 /* Source Set Register 0 */ +#define TZIC_SRCCLAR0 0x0280 /* Source Clear Register 0 */ +#define TZIC_PRIORITY0 0x0400 /* Priority Register 0 */ +#define TZIC_PND0 0x0D00 /* Pending Register 0 */ +#define TZIC_HIPND(i) (0x0D80+ ((i) << 2)) /* High Priority Pending Register */ +#define TZIC_WAKEUP0(i) (0x0E00 + ((i) << 2)) /* Wakeup Config Register */ +#define TZIC_SWINT 0x0F00 /* Software Interrupt Rigger Register */ +#define TZIC_ID0 0x0FD0 /* Indentification Register 0 */ + +static void __iomem *tzic_base; +static struct irq_domain *domain; + +#define TZIC_NUM_IRQS 128 + +#ifdef CONFIG_FIQ +static int tzic_set_irq_fiq(unsigned int hwirq, unsigned int type) +{ + unsigned int index, mask, value; + + index = hwirq >> 5; + if (unlikely(index >= 4)) + return -EINVAL; + mask = 1U << (hwirq & 0x1F); + + value = imx_readl(tzic_base + TZIC_INTSEC0(index)) | mask; + if (type) + value &= ~mask; + imx_writel(value, tzic_base + TZIC_INTSEC0(index)); + + return 0; +} +#else +#define tzic_set_irq_fiq NULL +#endif + +#ifdef CONFIG_PM +static void tzic_irq_suspend(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + int idx = d->hwirq >> 5; + + imx_writel(gc->wake_active, tzic_base + TZIC_WAKEUP0(idx)); +} + +static void tzic_irq_resume(struct irq_data *d) +{ + int idx = d->hwirq >> 5; + + imx_writel(imx_readl(tzic_base + TZIC_ENSET0(idx)), + tzic_base + TZIC_WAKEUP0(idx)); +} + +#else +#define tzic_irq_suspend NULL +#define tzic_irq_resume NULL +#endif + +static struct mxc_extra_irq tzic_extra_irq = { +#ifdef CONFIG_FIQ + .set_irq_fiq = tzic_set_irq_fiq, +#endif +}; + +static __init void tzic_init_gc(int idx, unsigned int irq_start) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + + gc = irq_alloc_generic_chip("tzic", 1, irq_start, tzic_base, + handle_level_irq); + gc->private = &tzic_extra_irq; + gc->wake_enabled = IRQ_MSK(32); + + ct = gc->chip_types; + ct->chip.irq_mask = irq_gc_mask_disable_reg; + ct->chip.irq_unmask = irq_gc_unmask_enable_reg; + ct->chip.irq_set_wake = irq_gc_set_wake; + ct->chip.irq_suspend = tzic_irq_suspend; + ct->chip.irq_resume = tzic_irq_resume; + ct->regs.disable = TZIC_ENCLEAR0(idx); + ct->regs.enable = TZIC_ENSET0(idx); + + irq_setup_generic_chip(gc, IRQ_MSK(32), 0, IRQ_NOREQUEST, 0); +} + +static void __exception_irq_entry tzic_handle_irq(struct pt_regs *regs) +{ + u32 stat; + int i, irqofs, handled; + + do { + handled = 0; + + for (i = 0; i < 4; i++) { + stat = imx_readl(tzic_base + TZIC_HIPND(i)) & + imx_readl(tzic_base + TZIC_INTSEC0(i)); + + while (stat) { + handled = 1; + irqofs = fls(stat) - 1; + generic_handle_domain_irq(domain, irqofs + i * 32); + stat &= ~(1 << irqofs); + } + } + } while (handled); +} + +/* + * This function initializes the TZIC hardware and disables all the + * interrupts. It registers the interrupt enable and disable functions + * to the kernel for each interrupt source. + */ +static int __init tzic_init_dt(struct device_node *np, struct device_node *p) +{ + int irq_base; + int i; + + tzic_base = of_iomap(np, 0); + WARN_ON(!tzic_base); + + /* put the TZIC into the reset value with + * all interrupts disabled + */ + i = imx_readl(tzic_base + TZIC_INTCNTL); + + imx_writel(0x80010001, tzic_base + TZIC_INTCNTL); + imx_writel(0x1f, tzic_base + TZIC_PRIOMASK); + imx_writel(0x02, tzic_base + TZIC_SYNCCTRL); + + for (i = 0; i < 4; i++) + imx_writel(0xFFFFFFFF, tzic_base + TZIC_INTSEC0(i)); + + /* disable all interrupts */ + for (i = 0; i < 4; i++) + imx_writel(0xFFFFFFFF, tzic_base + TZIC_ENCLEAR0(i)); + + /* all IRQ no FIQ Warning :: No selection */ + + irq_base = irq_alloc_descs(-1, 0, TZIC_NUM_IRQS, numa_node_id()); + WARN_ON(irq_base < 0); + + domain = irq_domain_add_legacy(np, TZIC_NUM_IRQS, irq_base, 0, + &irq_domain_simple_ops, NULL); + WARN_ON(!domain); + + for (i = 0; i < 4; i++, irq_base += 32) + tzic_init_gc(i, irq_base); + + set_handle_irq(tzic_handle_irq); + +#ifdef CONFIG_FIQ + /* Initialize FIQ */ + init_FIQ(FIQ_START); +#endif + + pr_info("TrustZone Interrupt Controller (TZIC) initialized\n"); + + return 0; +} +IRQCHIP_DECLARE(tzic, "fsl,tzic", tzic_init_dt); + +/** + * tzic_enable_wake() - enable wakeup interrupt + * + * @return 0 if successful; non-zero otherwise + * + * This function provides an interrupt synchronization point that is required + * by tzic enabled platforms before entering imx specific low power modes (ie, + * those low power modes beyond the WAIT_CLOCKED basic ARM WFI only mode). + */ +int tzic_enable_wake(void) +{ + unsigned int i; + + imx_writel(1, tzic_base + TZIC_DSMINT); + if (unlikely(imx_readl(tzic_base + TZIC_DSMINT) == 0)) + return -EAGAIN; + + for (i = 0; i < 4; i++) + imx_writel(imx_readl(tzic_base + TZIC_ENSET0(i)), + tzic_base + TZIC_WAKEUP0(i)); + + return 0; +} |