From 0cc4444a6d1a7df18cadaebda9813d6dadd8997e Mon Sep 17 00:00:00 2001 From: Leonard Crestez Date: Mon, 13 May 2019 15:06:37 +0300 Subject: [PATCH 01/81] imx busfreq: Add API header file Add sufficient enough definitions so that drivers which call request_bus_freq and release_bus_freq can compile even if CONFIG_HAVE_IMX_BUSFREQ is missing. Signed-off-by: Leonard Crestez --- include/linux/busfreq-imx.h | 77 +++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 include/linux/busfreq-imx.h diff --git a/include/linux/busfreq-imx.h b/include/linux/busfreq-imx.h new file mode 100644 index 000000000000..39c71a9f55eb --- /dev/null +++ b/include/linux/busfreq-imx.h @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ASM_ARCH_MXC_BUSFREQ_H__ +#define __ASM_ARCH_MXC_BUSFREQ_H__ + +#include +#include + +/* + * This enumerates busfreq low power mode entry and exit. + */ +enum busfreq_event { + LOW_BUSFREQ_ENTER, + LOW_BUSFREQ_EXIT, +}; + +/* + * This enumerates the system bus and ddr frequencies in various modes. + * BUS_FREQ_HIGH - DDR @ 528MHz, AHB @ 132MHz. + * BUS_FREQ_MED - DDR @ 400MHz, AHB @ 132MHz + * BUS_FREQ_AUDIO - DDR @ 50MHz/100MHz, AHB @ 24MHz. + * BUS_FREQ_LOW - DDR @ 24MHz, AHB @ 24MHz. + * BUS_FREQ_ULTRA_LOW - DDR @ 1MHz, AHB - 3MHz. + * + * Drivers need to request/release the bus/ddr frequencies based on + * their performance requirements. Drivers cannot request/release + * BUS_FREQ_ULTRA_LOW mode as this mode is automatically entered from + * either BUS_FREQ_AUDIO or BUS_FREQ_LOW + * modes. + */ +enum bus_freq_mode { + BUS_FREQ_HIGH, + BUS_FREQ_MED, + BUS_FREQ_AUDIO, + BUS_FREQ_LOW, + BUS_FREQ_ULTRA_LOW, +}; + +#if defined(CONFIG_HAVE_IMX_BUSFREQ) && !defined(CONFIG_ARM64) +extern struct regulator *arm_reg; +extern struct regulator *soc_reg; +void request_bus_freq(enum bus_freq_mode mode); +void release_bus_freq(enum bus_freq_mode mode); +int register_busfreq_notifier(struct notifier_block *nb); +int unregister_busfreq_notifier(struct notifier_block *nb); +int get_bus_freq_mode(void); +#elif defined(CONFIG_HAVE_IMX_BUSFREQ) +void request_bus_freq(enum bus_freq_mode mode); +void release_bus_freq(enum bus_freq_mode mode); +int get_bus_freq_mode(void); +#else +static inline void request_bus_freq(enum bus_freq_mode mode) +{ +} +static inline void release_bus_freq(enum bus_freq_mode mode) +{ +} +static inline int register_busfreq_notifier(struct notifier_block *nb) +{ + return 0; +} +static inline int unregister_busfreq_notifier(struct notifier_block *nb) +{ + return 0; +} +static inline int get_bus_freq_mode(void) +{ + return BUS_FREQ_HIGH; +} +#endif +#endif From a4f408b44ebcaabb775d440a241f865357fab838 Mon Sep 17 00:00:00 2001 From: Zhou Peng Date: Thu, 24 Jan 2019 12:03:15 +0800 Subject: [PATCH 02/81] Add MU module for vpu dependence on QXP/QM Add MU module: drivers/soc/imx/Makefile drivers/soc/imx/mu/Makefile drivers/soc/imx/mu/mx8_mu.c include/linux/mx8_mu.h Signed-off-by: Zhou Peng --- drivers/soc/imx/Makefile | 1 + drivers/soc/imx/mu/Makefile | 1 + drivers/soc/imx/mu/mx8_mu.c | 187 ++++++++++++++++++++++++++++++++++++ include/linux/mx8_mu.h | 48 +++++++++ 4 files changed, 237 insertions(+) create mode 100644 drivers/soc/imx/mu/Makefile create mode 100644 drivers/soc/imx/mu/mx8_mu.c create mode 100644 include/linux/mx8_mu.h diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile index cf9ca42ff739..7d8f8a56ca43 100644 --- a/drivers/soc/imx/Makefile +++ b/drivers/soc/imx/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o obj-$(CONFIG_IMX_GPCV2_PM_DOMAINS) += gpcv2.o obj-$(CONFIG_ARCH_MXC) += soc-imx8.o obj-$(CONFIG_IMX_SCU_SOC) += soc-imx-scu.o +obj-y += mu/ diff --git a/drivers/soc/imx/mu/Makefile b/drivers/soc/imx/mu/Makefile new file mode 100644 index 000000000000..922308c3f90f --- /dev/null +++ b/drivers/soc/imx/mu/Makefile @@ -0,0 +1 @@ +obj-y += mx8_mu.o diff --git a/drivers/soc/imx/mu/mx8_mu.c b/drivers/soc/imx/mu/mx8_mu.c new file mode 100644 index 000000000000..2cd6dc880bbe --- /dev/null +++ b/drivers/soc/imx/mu/mx8_mu.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +static int version; + +/*! + * This function sets the Flag n of the MU. + */ +int32_t MU_SetFn(void __iomem *base, uint32_t Fn) +{ + uint32_t reg, offset; + + reg = Fn & (~MU_CR_Fn_MASK1); + if (reg > 0) + return -EINVAL; + + offset = unlikely(version == MU_VER_ID_V10) + ? MU_V10_ACR_OFFSET1 : MU_ACR_OFFSET1; + + reg = readl_relaxed(base + offset); + /* Clear ABFn. */ + reg &= ~MU_CR_Fn_MASK1; + reg |= Fn; + writel_relaxed(reg, base + offset); + + return 0; +} + +/*! + * This function reads the status from status register. + */ +uint32_t MU_ReadStatus(void __iomem *base) +{ + uint32_t reg, offset; + + offset = unlikely(version == MU_VER_ID_V10) + ? MU_V10_ASR_OFFSET1 : MU_ASR_OFFSET1; + + reg = readl_relaxed(base + offset); + + return reg; +} + +/*! + * This function enables specific RX full interrupt. + */ +void MU_EnableRxFullInt(void __iomem *base, uint32_t index) +{ + uint32_t reg, offset; + + offset = unlikely(version == MU_VER_ID_V10) + ? MU_V10_ACR_OFFSET1 : MU_ACR_OFFSET1; + + reg = readl_relaxed(base + offset); + reg &= ~(MU_CR_GIRn_MASK1 | MU_CR_NMI_MASK1); + reg |= MU_CR_RIE0_MASK1 >> index; + writel_relaxed(reg, base + offset); +} + +/*! + * This function enables specific general purpose interrupt. + */ +void MU_EnableGeneralInt(void __iomem *base, uint32_t index) +{ + uint32_t reg, offset; + + offset = unlikely(version == MU_VER_ID_V10) + ? MU_V10_ACR_OFFSET1 : MU_ACR_OFFSET1; + + reg = readl_relaxed(base + offset); + reg &= ~(MU_CR_GIRn_MASK1 | MU_CR_NMI_MASK1); + reg |= MU_CR_GIE0_MASK1 >> index; + writel_relaxed(reg, base + offset); +} + +/* + * Wait and send message to the other core. + */ +void MU_SendMessage(void __iomem *base, uint32_t regIndex, uint32_t msg) +{ + uint32_t mask = MU_SR_TE0_MASK1 >> regIndex; + + if (unlikely(version == MU_VER_ID_V10)) { + /* Wait TX register to be empty. */ + while (!(readl_relaxed(base + MU_V10_ASR_OFFSET1) & mask)) + ; + writel_relaxed(msg, base + MU_V10_ATR0_OFFSET1 + + (regIndex * 4)); + } else { + /* Wait TX register to be empty. */ + while (!(readl_relaxed(base + MU_ASR_OFFSET1) & mask)) + ; + writel_relaxed(msg, base + MU_ATR0_OFFSET1 + (regIndex * 4)); + } +} + +/* + * Wait and send message to the other core with timeout mechanism. + */ +void MU_SendMessageTimeout(void __iomem *base, uint32_t regIndex, uint32_t msg, + uint32_t t) +{ + uint32_t mask = MU_SR_TE0_MASK1 >> regIndex; + uint32_t timeout = t; + + if (unlikely(version == MU_VER_ID_V10)) { + /* Wait TX register to be empty. */ + while (!(readl_relaxed(base + MU_V10_ASR_OFFSET1) & mask)) { + udelay(10); + if (timeout-- == 0) + return; + }; + + writel_relaxed(msg, base + MU_V10_ATR0_OFFSET1 + + (regIndex * 4)); + } else { + /* Wait TX register to be empty. */ + while (!(readl_relaxed(base + MU_ASR_OFFSET1) & mask)) { + udelay(10); + if (timeout-- == 0) + return; + }; + + writel_relaxed(msg, base + MU_ATR0_OFFSET1 + (regIndex * 4)); + } +} + +/* + * Wait to receive message from the other core. + */ +void MU_ReceiveMsg(void __iomem *base, uint32_t regIndex, uint32_t *msg) +{ + uint32_t mask = MU_SR_RF0_MASK1 >> regIndex; + + if (unlikely(version == MU_VER_ID_V10)) { + /* Wait RX register to be full. */ + while (!(readl_relaxed(base + MU_V10_ASR_OFFSET1) & mask)) + ; + *msg = readl_relaxed(base + MU_V10_ARR0_OFFSET1 + + (regIndex * 4)); + } else { + /* Wait RX register to be full. */ + while (!(readl_relaxed(base + MU_ASR_OFFSET1) & mask)) + ; + *msg = readl_relaxed(base + MU_ARR0_OFFSET1 + (regIndex * 4)); + } +} + + + +void MU_Init(void __iomem *base) +{ + uint32_t reg, offset; + + version = readl_relaxed(base) >> 16; + + offset = unlikely(version == MU_VER_ID_V10) + ? MU_V10_ACR_OFFSET1 : MU_ACR_OFFSET1; + + reg = readl_relaxed(base + offset); + /* Clear GIEn, TIEn, GIRn and ABFn. */ + reg &= ~(MU_CR_GIEn_MASK1 | MU_CR_TIEn_MASK1 + | MU_CR_GIRn_MASK1 | MU_CR_NMI_MASK1 | MU_CR_Fn_MASK1); + + /* + * i.MX6SX and i.MX7D have multi-core power management which need + * to use RIE interrupts. + */ + if (!(of_machine_is_compatible("fsl,imx6sx") || + of_machine_is_compatible("fsl,imx7d"))) + reg &= ~MU_CR_RIEn_MASK1; + + writel_relaxed(reg, base + offset); +} + +/**@}*/ + diff --git a/include/linux/mx8_mu.h b/include/linux/mx8_mu.h new file mode 100644 index 000000000000..b31e52693db8 --- /dev/null +++ b/include/linux/mx8_mu.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#define MU_ATR0_OFFSET1 0x0 +#define MU_ARR0_OFFSET1 0x10 +#define MU_ASR_OFFSET1 0x20 +#define MU_ACR_OFFSET1 0x24 + +/* Registers offsets of the MU Version 1.0 */ +#define MU_V10_VER_OFFSET1 0x0 +#define MU_V10_ATR0_OFFSET1 0x20 +#define MU_V10_ARR0_OFFSET1 0x40 +#define MU_V10_ASR_OFFSET1 0x60 +#define MU_V10_ACR_OFFSET1 0x64 +#define MU_VER_ID_V10 0x0100 /* Version 1.0 */ + +#define MU_TR_COUNT1 4 +#define MU_RR_COUNT1 4 + +#define MU_CR_GIEn_MASK1 (0xF << 28) +#define MU_CR_RIEn_MASK1 (0xF << 24) +#define MU_CR_TIEn_MASK1 (0xF << 20) +#define MU_CR_GIRn_MASK1 (0xF << 16) +#define MU_CR_NMI_MASK1 (1 << 3) +#define MU_CR_Fn_MASK1 0x7 + +#define MU_SR_TE0_MASK1 (1 << 23) +#define MU_SR_RF0_MASK1 (1 << 27) +#define MU_CR_RIE0_MASK1 (1 << 27) +#define MU_CR_GIE0_MASK1 (1 << 31) + +#define MU_TR_COUNT 4 +#define MU_RR_COUNT 4 + + +void MU_Init(void __iomem *base); +void MU_SendMessage(void __iomem *base, uint32_t regIndex, uint32_t msg); +void MU_SendMessageTimeout(void __iomem *base, uint32_t regIndex, uint32_t msg, uint32_t t); +void MU_ReceiveMsg(void __iomem *base, uint32_t regIndex, uint32_t *msg); +void MU_EnableGeneralInt(void __iomem *base, uint32_t index); +void MU_EnableRxFullInt(void __iomem *base, uint32_t index); +uint32_t MU_ReadStatus(void __iomem *base); +int32_t MU_SetFn(void __iomem *base, uint32_t Fn); + From dc782dd284d797c5ca591b945c5a51e03698fb47 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Fri, 12 Apr 2019 15:11:09 +0800 Subject: [PATCH 03/81] ARM: imx: add i.MX7D bus-freq support Add i.MX7D bus-freq support, it supports High/Low/Audio bus mode. Signed-off-by: Anson Huang --- arch/arm/mach-imx/Kconfig | 8 + arch/arm/mach-imx/Makefile | 11 +- arch/arm/mach-imx/busfreq-imx.c | 764 ++++++++++++++++++++++++++++ arch/arm/mach-imx/busfreq_ddr3.c | 310 +++++++++++ arch/arm/mach-imx/busfreq_lpddr2.c | 373 ++++++++++++++ arch/arm/mach-imx/common.c | 166 ++++++ arch/arm/mach-imx/common.h | 8 + arch/arm/mach-imx/ddr3_freq_imx7d.S | 586 +++++++++++++++++++++ arch/arm/mach-imx/ddrc.c | 86 ++++ arch/arm/mach-imx/hardware.h | 12 +- arch/arm/mach-imx/lpddr3_freq_imx.S | 444 ++++++++++++++++ arch/arm/mach-imx/mach-imx7d.c | 8 + arch/arm/mach-imx/mx7.h | 54 ++ arch/arm/mach-imx/mxc.h | 2 + arch/arm/mach-imx/pm-imx7.c | 148 ++++++ arch/arm/mach-imx/smp_wfe.S | 110 ++++ 16 files changed, 3084 insertions(+), 6 deletions(-) create mode 100644 arch/arm/mach-imx/busfreq-imx.c create mode 100644 arch/arm/mach-imx/busfreq_ddr3.c create mode 100644 arch/arm/mach-imx/busfreq_lpddr2.c create mode 100644 arch/arm/mach-imx/common.c create mode 100644 arch/arm/mach-imx/ddr3_freq_imx7d.S create mode 100644 arch/arm/mach-imx/ddrc.c create mode 100644 arch/arm/mach-imx/lpddr3_freq_imx.S create mode 100644 arch/arm/mach-imx/mx7.h create mode 100644 arch/arm/mach-imx/pm-imx7.c create mode 100644 arch/arm/mach-imx/smp_wfe.S diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index 593bf1519608..29b15098f26b 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -43,6 +43,13 @@ config HAVE_IMX_GPC config HAVE_IMX_MMDC bool +config HAVE_IMX_DDRC + bool + select HAVE_IMX_BUSFREQ + +config HAVE_IMX_BUSFREQ + bool + config HAVE_IMX_SRC def_bool y if SMP select ARCH_HAS_RESET_CONTROLLER @@ -546,6 +553,7 @@ config SOC_IMX7D_CA7 select HAVE_IMX_MMDC select HAVE_IMX_SRC select IMX_GPCV2 + select HAVE_IMX_DDRC config SOC_IMX7D_CM4 bool diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 35ff620537e6..8703165f4d64 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -obj-y := cpu.o system.o irq-common.o +obj-y := cpu.o system.o irq-common.o common.o obj-$(CONFIG_SOC_IMX21) += mm-imx21.o @@ -72,6 +72,7 @@ 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 +obj-$(CONFIG_HAVE_IMX_DDRC) += ddrc.o ifneq ($(CONFIG_SOC_IMX6)$(CONFIG_SOC_LS1021A),) AFLAGS_headsmp.o :=-Wa,-march=armv7-a obj-$(CONFIG_SMP) += headsmp.o platsmp.o @@ -82,10 +83,16 @@ 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_CA7) += mach-imx7d.o pm-imx7.o ddr3_freq_imx7d.o smp_wfe.o \ + lpddr3_freq_imx.o obj-$(CONFIG_SOC_IMX7D_CM4) += mach-imx7d-cm4.o obj-$(CONFIG_SOC_IMX7ULP) += mach-imx7ulp.o pm-imx7ulp.o +obj-y += busfreq-imx.o busfreq_ddr3.o +AFLAGS_smp_wfe.o :=-Wa,-march=armv7-a +AFLAGS_ddr3_freq_imx7d.o :=-Wa,-march=armv7-a +AFLAGS_lpddr3_freq_imx.o :=-Wa,-march=armv7-a + ifeq ($(CONFIG_SUSPEND),y) AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c new file mode 100644 index 000000000000..976ba3ca7f29 --- /dev/null +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2017 NXP. + * Copyright 2018 NXP. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hardware.h" +#include "common.h" + +#define LPAPM_CLK 24000000 +#define LOW_AUDIO_CLK 50000000 +#define HIGH_AUDIO_CLK 100000000 + +#define LOW_POWER_RUN_VOLTAGE 950000 + +#define MMDC_MDMISC_DDR_TYPE_DDR3 0 +#define MMDC_MDMISC_DDR_TYPE_LPDDR2 1 + +unsigned int ddr_med_rate; +unsigned int ddr_normal_rate; +unsigned long ddr_freq_change_total_size; +unsigned long ddr_freq_change_iram_base; +unsigned long ddr_freq_change_iram_phys; + +static int ddr_type; +static int low_bus_freq_mode; +static int audio_bus_freq_mode; +static int ultra_low_bus_freq_mode; +static int high_bus_freq_mode; +static int med_bus_freq_mode; +static int bus_freq_scaling_initialized; +static bool cancel_reduce_bus_freq; +static struct device *busfreq_dev; +static int busfreq_suspended; +static int bus_freq_scaling_is_active; +static int high_bus_count, med_bus_count, audio_bus_count, low_bus_count; +static unsigned int ddr_low_rate; +static int cur_bus_freq_mode; + +extern unsigned long iram_tlb_phys_addr; +extern int unsigned long iram_tlb_base_addr; + +/* + * Bus frequency management by Linux + */ +extern int init_mmdc_lpddr2_settings(struct platform_device *dev); +extern int init_mmdc_lpddr2_settings_mx6q(struct platform_device *dev); +extern int init_mmdc_ddr3_settings_imx6_up(struct platform_device *dev); +extern int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *dev); +extern int init_ddrc_ddr_settings(struct platform_device *dev); +extern int update_ddr_freq_imx_smp(int ddr_rate); +extern int update_ddr_freq_imx6_up(int ddr_rate); +extern int update_lpddr2_freq(int ddr_rate); +extern int update_lpddr2_freq_smp(int ddr_rate); + + +/** + * @brief Functions to init and update the busfreq function of + * device and memory type + */ +static struct busfreq_func { + int (*init)(struct platform_device *dev); + int (*update)(int ddr_rate); +} busfreq_func = {NULL, NULL}; + +DEFINE_MUTEX(bus_freq_mutex); + +static struct clk *osc_clk; +static struct clk *ahb_clk; +static struct clk *axi_sel_clk; +static struct clk *dram_root; +static struct clk *dram_alt_sel; +static struct clk *dram_alt_root; +static struct clk *pfd0_392m; +static struct clk *pfd2_270m; +static struct clk *pfd1_332m; +static struct clk *pll_dram; +static struct clk *ahb_sel_clk; +static struct clk *axi_clk; + +static struct delayed_work low_bus_freq_handler; +static struct delayed_work bus_freq_daemon; + +static RAW_NOTIFIER_HEAD(busfreq_notifier_chain); + +static bool busfreq_notified_low = false; + +static int busfreq_notify(enum busfreq_event event) +{ + int ret; + + if (event == LOW_BUSFREQ_ENTER) { + WARN_ON(busfreq_notified_low); + busfreq_notified_low = true; + } else if (event == LOW_BUSFREQ_EXIT) { + WARN_ON(!busfreq_notified_low); + busfreq_notified_low = false; + } + ret = raw_notifier_call_chain(&busfreq_notifier_chain, event, NULL); + + return notifier_to_errno(ret); +} + +int register_busfreq_notifier(struct notifier_block *nb) +{ + return raw_notifier_chain_register(&busfreq_notifier_chain, nb); +} +EXPORT_SYMBOL(register_busfreq_notifier); + +int unregister_busfreq_notifier(struct notifier_block *nb) +{ + return raw_notifier_chain_unregister(&busfreq_notifier_chain, nb); +} +EXPORT_SYMBOL(unregister_busfreq_notifier); + +static void enter_lpm_imx7d(void) +{ + /* + * The AHB clock parent switch and divider change + * needs to keep previous/current parent enabled + * per design requirement, but when we switch the + * clock parent, previous AHB clock parent may be + * disabled by common clock framework, so here we + * have to make sure AHB's previous parent pfd2_270m + * is enabled during AHB set rate. + */ + clk_prepare_enable(pfd2_270m); + if (audio_bus_count) { + clk_prepare_enable(pfd0_392m); + busfreq_func.update(HIGH_AUDIO_CLK); + + clk_set_parent(dram_alt_sel, pfd0_392m); + clk_set_parent(dram_root, dram_alt_root); + if (high_bus_freq_mode) { + clk_set_parent(axi_sel_clk, osc_clk); + clk_set_parent(ahb_sel_clk, osc_clk); + clk_set_rate(ahb_clk, LPAPM_CLK); + } + clk_disable_unprepare(pfd0_392m); + audio_bus_freq_mode = 1; + low_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_AUDIO; + } else { + busfreq_func.update(LPAPM_CLK); + + clk_set_parent(dram_alt_sel, osc_clk); + clk_set_parent(dram_root, dram_alt_root); + if (high_bus_freq_mode) { + clk_set_parent(axi_sel_clk, osc_clk); + clk_set_parent(ahb_sel_clk, osc_clk); + clk_set_rate(ahb_clk, LPAPM_CLK); + } + low_bus_freq_mode = 1; + audio_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_LOW; + } + clk_disable_unprepare(pfd2_270m); +} + +static void exit_lpm_imx7d(void) +{ + clk_set_parent(axi_sel_clk, pfd1_332m); + clk_set_rate(ahb_clk, LPAPM_CLK / 2); + clk_set_parent(ahb_sel_clk, pfd2_270m); + + busfreq_func.update(ddr_normal_rate); + + clk_set_parent(dram_root, pll_dram); +} + +static void reduce_bus_freq(void) +{ + if (audio_bus_count && (low_bus_freq_mode || ultra_low_bus_freq_mode)) + busfreq_notify(LOW_BUSFREQ_EXIT); + else if (!audio_bus_count) + busfreq_notify(LOW_BUSFREQ_ENTER); + + if (cpu_is_imx7d()) + enter_lpm_imx7d(); + + med_bus_freq_mode = 0; + high_bus_freq_mode = 0; + + if (audio_bus_freq_mode) + dev_dbg(busfreq_dev, + "Bus freq set to audio mode. Count: high %d, med %d, audio %d\n", + high_bus_count, med_bus_count, audio_bus_count); + if (low_bus_freq_mode) + dev_dbg(busfreq_dev, + "Bus freq set to low mode. Count: high %d, med %d, audio %d\n", + high_bus_count, med_bus_count, audio_bus_count); +} + +static inline void cancel_low_bus_freq_handler(void) +{ + cancel_delayed_work(&low_bus_freq_handler); + cancel_reduce_bus_freq = true; +} + +static void reduce_bus_freq_handler(struct work_struct *work) +{ + mutex_lock(&bus_freq_mutex); + + if (!cancel_reduce_bus_freq) { + reduce_bus_freq(); + cancel_low_bus_freq_handler(); + } + + mutex_unlock(&bus_freq_mutex); +} + +/* + * Set the DDR, AHB to 24MHz. + * This mode will be activated only when none of the modules that + * need a higher DDR or AHB frequency are active. + */ +static int set_low_bus_freq(void) +{ + if (busfreq_suspended) + return 0; + + if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active) + return 0; + + cancel_reduce_bus_freq = false; + + /* + * Check to see if we need to got from + * low bus freq mode to audio bus freq mode. + * If so, the change needs to be done immediately. + */ + if (audio_bus_count && (low_bus_freq_mode || ultra_low_bus_freq_mode)) + reduce_bus_freq(); + else + /* + * Don't lower the frequency immediately. Instead + * scheduled a delayed work and drop the freq if + * the conditions still remain the same. + */ + schedule_delayed_work(&low_bus_freq_handler, + usecs_to_jiffies(3000000)); + return 0; +} + +/* + * Set the DDR to either 528MHz or 400MHz for iMX6qd + * or 400MHz for iMX6dl. + */ +static int set_high_bus_freq(int high_bus_freq) +{ + if (bus_freq_scaling_initialized && bus_freq_scaling_is_active) + cancel_low_bus_freq_handler(); + + if (busfreq_suspended) + return 0; + + if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active) + return 0; + + if (high_bus_freq_mode) + return 0; + + /* medium bus freq is only supported for MX6DQ */ + if (med_bus_freq_mode && !high_bus_freq) + return 0; + + if (low_bus_freq_mode || ultra_low_bus_freq_mode) + busfreq_notify(LOW_BUSFREQ_EXIT); + + if (cpu_is_imx7d()) + exit_lpm_imx7d(); + + high_bus_freq_mode = 1; + med_bus_freq_mode = 0; + low_bus_freq_mode = 0; + audio_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_HIGH; + + if (high_bus_freq_mode) + dev_dbg(busfreq_dev, + "Bus freq set to high mode. Count: high %d, med %d, audio %d\n", + high_bus_count, med_bus_count, audio_bus_count); + if (med_bus_freq_mode) + dev_dbg(busfreq_dev, + "Bus freq set to med mode. Count: high %d, med %d, audio %d\n", + high_bus_count, med_bus_count, audio_bus_count); + + return 0; +} + +void request_bus_freq(enum bus_freq_mode mode) +{ + mutex_lock(&bus_freq_mutex); + + if (mode == BUS_FREQ_ULTRA_LOW) { + dev_dbg(busfreq_dev, "This mode cannot be requested!\n"); + mutex_unlock(&bus_freq_mutex); + return; + } + + if (mode == BUS_FREQ_HIGH) + high_bus_count++; + else if (mode == BUS_FREQ_MED) + med_bus_count++; + else if (mode == BUS_FREQ_AUDIO) + audio_bus_count++; + else if (mode == BUS_FREQ_LOW) + low_bus_count++; + + if (busfreq_suspended || !bus_freq_scaling_initialized || + !bus_freq_scaling_is_active) { + mutex_unlock(&bus_freq_mutex); + return; + } + + cancel_low_bus_freq_handler(); + + if ((mode == BUS_FREQ_HIGH) && (!high_bus_freq_mode)) { + set_high_bus_freq(1); + mutex_unlock(&bus_freq_mutex); + return; + } + + if ((mode == BUS_FREQ_MED) && (!high_bus_freq_mode) && + (!med_bus_freq_mode)) { + set_high_bus_freq(0); + mutex_unlock(&bus_freq_mutex); + return; + } + if ((mode == BUS_FREQ_AUDIO) && (!high_bus_freq_mode) && + (!med_bus_freq_mode) && (!audio_bus_freq_mode)) { + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); + return; + } + mutex_unlock(&bus_freq_mutex); +} +EXPORT_SYMBOL(request_bus_freq); + +void release_bus_freq(enum bus_freq_mode mode) +{ + mutex_lock(&bus_freq_mutex); + + if (mode == BUS_FREQ_ULTRA_LOW) { + dev_dbg(busfreq_dev, + "This mode cannot be released!\n"); + mutex_unlock(&bus_freq_mutex); + return; + } + + if (mode == BUS_FREQ_HIGH) { + if (high_bus_count == 0) { + dev_err(busfreq_dev, "high bus count mismatch!\n"); + dump_stack(); + mutex_unlock(&bus_freq_mutex); + return; + } + high_bus_count--; + } else if (mode == BUS_FREQ_MED) { + if (med_bus_count == 0) { + dev_err(busfreq_dev, "med bus count mismatch!\n"); + dump_stack(); + mutex_unlock(&bus_freq_mutex); + return; + } + med_bus_count--; + } else if (mode == BUS_FREQ_AUDIO) { + if (audio_bus_count == 0) { + dev_err(busfreq_dev, "audio bus count mismatch!\n"); + dump_stack(); + mutex_unlock(&bus_freq_mutex); + return; + } + audio_bus_count--; + } else if (mode == BUS_FREQ_LOW) { + if (low_bus_count == 0) { + dev_err(busfreq_dev, "low bus count mismatch!\n"); + dump_stack(); + mutex_unlock(&bus_freq_mutex); + return; + } + low_bus_count--; + } + + if (busfreq_suspended || !bus_freq_scaling_initialized || + !bus_freq_scaling_is_active) { + mutex_unlock(&bus_freq_mutex); + return; + } + + if ((!audio_bus_freq_mode) && (high_bus_count == 0) && + (med_bus_count == 0) && (audio_bus_count != 0)) { + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); + return; + } + if ((!low_bus_freq_mode) && (high_bus_count == 0) && + (med_bus_count == 0) && (audio_bus_count == 0) && + (low_bus_count != 0)) { + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); + return; + } + if ((!ultra_low_bus_freq_mode) && (high_bus_count == 0) && + (med_bus_count == 0) && (audio_bus_count == 0) && + (low_bus_count == 0)) { + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); + return; + } + + mutex_unlock(&bus_freq_mutex); +} +EXPORT_SYMBOL(release_bus_freq); + +int get_bus_freq_mode(void) +{ + return cur_bus_freq_mode; +} +EXPORT_SYMBOL(get_bus_freq_mode); + +static struct map_desc ddr_iram_io_desc __initdata = { + /* .virtual and .pfn are run-time assigned */ + .length = SZ_1M, + .type = MT_MEMORY_RWX_NONCACHED, +}; + +const static char *ddr_freq_iram_match[] __initconst = { + "fsl,ddr-lpm-sram", + NULL +}; + +static int __init imx_dt_find_ddr_sram(unsigned long node, + const char *uname, int depth, void *data) +{ + unsigned long ddr_iram_addr; + const __be32 *prop; + + if (of_flat_dt_match(node, ddr_freq_iram_match)) { + unsigned int len; + + prop = of_get_flat_dt_prop(node, "reg", &len); + if (prop == NULL || len != (sizeof(unsigned long) * 2)) + return -EINVAL; + ddr_iram_addr = be32_to_cpu(prop[0]); + ddr_freq_change_total_size = be32_to_cpu(prop[1]); + ddr_freq_change_iram_phys = ddr_iram_addr; + + /* Make sure ddr_freq_change_iram_phys is 8 byte aligned. */ + if ((uintptr_t)(ddr_freq_change_iram_phys) & (FNCPY_ALIGN - 1)) + ddr_freq_change_iram_phys += FNCPY_ALIGN - + ((uintptr_t)ddr_freq_change_iram_phys % + (FNCPY_ALIGN)); + } + return 0; +} + +void __init imx_busfreq_map_io(void) +{ + /* + * Get the address of IRAM to be used by the ddr frequency + * change code from the device tree. + */ + WARN_ON(of_scan_flat_dt(imx_dt_find_ddr_sram, NULL)); + if (ddr_freq_change_iram_phys) { + ddr_freq_change_iram_base = IMX_IO_P2V( + ddr_freq_change_iram_phys); + if ((iram_tlb_phys_addr & 0xFFF00000) != + (ddr_freq_change_iram_phys & 0xFFF00000)) { + /* We need to create a 1M page table entry. */ + ddr_iram_io_desc.virtual = IMX_IO_P2V( + ddr_freq_change_iram_phys & 0xFFF00000); + ddr_iram_io_desc.pfn = __phys_to_pfn( + ddr_freq_change_iram_phys & 0xFFF00000); + iotable_init(&ddr_iram_io_desc, 1); + } + memset((void *)ddr_freq_change_iram_base, 0, + ddr_freq_change_total_size); + } +} + +static void bus_freq_daemon_handler(struct work_struct *work) +{ + mutex_lock(&bus_freq_mutex); + if ((!low_bus_freq_mode) && (!ultra_low_bus_freq_mode) + && (high_bus_count == 0) && + (med_bus_count == 0) && (audio_bus_count == 0)) + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); +} + +static ssize_t bus_freq_scaling_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (bus_freq_scaling_is_active) + return sprintf(buf, "Bus frequency scaling is enabled\n"); + else + return sprintf(buf, "Bus frequency scaling is disabled\n"); +} + +static ssize_t bus_freq_scaling_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + if (strncmp(buf, "1", 1) == 0) { + bus_freq_scaling_is_active = 1; + set_high_bus_freq(1); + /* + * We set bus freq to highest at the beginning, + * so we use this daemon thread to make sure system + * can enter low bus mode if + * there is no high bus request pending + */ + schedule_delayed_work(&bus_freq_daemon, + usecs_to_jiffies(5000000)); + } else if (strncmp(buf, "0", 1) == 0) { + if (bus_freq_scaling_is_active) + set_high_bus_freq(1); + bus_freq_scaling_is_active = 0; + } + return size; +} + +static int bus_freq_pm_notify(struct notifier_block *nb, unsigned long event, + void *dummy) +{ + mutex_lock(&bus_freq_mutex); + + if (event == PM_SUSPEND_PREPARE) { + high_bus_count++; + set_high_bus_freq(1); + busfreq_suspended = 1; + } else if (event == PM_POST_SUSPEND) { + busfreq_suspended = 0; + high_bus_count--; + schedule_delayed_work(&bus_freq_daemon, + usecs_to_jiffies(5000000)); + } + + mutex_unlock(&bus_freq_mutex); + + return NOTIFY_OK; +} + +static int busfreq_reboot_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + /* System is rebooting. Set the system into high_bus_freq_mode. */ + request_bus_freq(BUS_FREQ_HIGH); + + return 0; +} + +static struct notifier_block imx_bus_freq_pm_notifier = { + .notifier_call = bus_freq_pm_notify, +}; + +static struct notifier_block imx_busfreq_reboot_notifier = { + .notifier_call = busfreq_reboot_notifier_event, +}; + + +static DEVICE_ATTR(enable, 0644, bus_freq_scaling_enable_show, + bus_freq_scaling_enable_store); + +/*! + * This is the probe routine for the bus frequency driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + * + */ + +static int busfreq_probe(struct platform_device *pdev) +{ + u32 err; + + busfreq_dev = &pdev->dev; + + /* Return if no IRAM space is allocated for ddr freq change code. */ + if (!ddr_freq_change_iram_base) + return -ENOMEM; + + if (cpu_is_imx7d()) { + osc_clk = devm_clk_get(&pdev->dev, "osc"); + axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel"); + ahb_sel_clk = devm_clk_get(&pdev->dev, "ahb_sel"); + pfd0_392m = devm_clk_get(&pdev->dev, "pfd0_392m"); + dram_root = devm_clk_get(&pdev->dev, "dram_root"); + dram_alt_sel = devm_clk_get(&pdev->dev, "dram_alt_sel"); + pll_dram = devm_clk_get(&pdev->dev, "pll_dram"); + dram_alt_root = devm_clk_get(&pdev->dev, "dram_alt_root"); + pfd1_332m = devm_clk_get(&pdev->dev, "pfd1_332m"); + pfd2_270m = devm_clk_get(&pdev->dev, "pfd2_270m"); + ahb_clk = devm_clk_get(&pdev->dev, "ahb"); + axi_clk = devm_clk_get(&pdev->dev, "axi"); + if (IS_ERR(osc_clk) || IS_ERR(axi_sel_clk) || IS_ERR(ahb_clk) + || IS_ERR(pfd0_392m) || IS_ERR(dram_root) + || IS_ERR(dram_alt_sel) || IS_ERR(pll_dram) + || IS_ERR(dram_alt_root) || IS_ERR(pfd1_332m) + || IS_ERR(ahb_clk) || IS_ERR(axi_clk) + || IS_ERR(pfd2_270m)) { + dev_err(busfreq_dev, + "%s: failed to get busfreq clk\n", __func__); + return -EINVAL; + } + } + + err = sysfs_create_file(&busfreq_dev->kobj, &dev_attr_enable.attr); + if (err) { + dev_err(busfreq_dev, + "Unable to register sysdev entry for BUSFREQ"); + return err; + } + + if (of_property_read_u32(pdev->dev.of_node, "fsl,max_ddr_freq", + &ddr_normal_rate)) { + dev_err(busfreq_dev, "max_ddr_freq entry missing\n"); + return -EINVAL; + } + + high_bus_freq_mode = 1; + med_bus_freq_mode = 0; + low_bus_freq_mode = 0; + audio_bus_freq_mode = 0; + ultra_low_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_HIGH; + + bus_freq_scaling_is_active = 1; + bus_freq_scaling_initialized = 1; + + ddr_low_rate = LPAPM_CLK; + + INIT_DELAYED_WORK(&low_bus_freq_handler, reduce_bus_freq_handler); + INIT_DELAYED_WORK(&bus_freq_daemon, bus_freq_daemon_handler); + register_pm_notifier(&imx_bus_freq_pm_notifier); + register_reboot_notifier(&imx_busfreq_reboot_notifier); + + /* enter low bus mode if no high speed device enabled */ + schedule_delayed_work(&bus_freq_daemon, + msecs_to_jiffies(20000)); + + /* + * Need to make sure to an entry for the ddr freq change code + * address in the IRAM page table. + * This is only required if the DDR freq code and suspend/idle + * code are in different OCRAM spaces. + */ + if ((iram_tlb_phys_addr & 0xFFF00000) != + (ddr_freq_change_iram_phys & 0xFFF00000)) { + unsigned long i; + + /* + * Make sure the ddr_iram virtual address has a mapping + * in the IRAM page table. + */ + i = ((IMX_IO_P2V(ddr_freq_change_iram_phys) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + i) = + (ddr_freq_change_iram_phys & 0xFFF00000) | + TT_ATTRIB_NON_CACHEABLE_1M; + } + + if (cpu_is_imx7d()) { + ddr_type = imx_ddrc_get_ddr_type(); + /* reduce ddr3 normal rate to 400M due to CKE issue on TO1.1 */ + if (imx_get_soc_revision() == IMX_CHIP_REVISION_1_1 && + ddr_type == IMX_DDR_TYPE_DDR3) { + ddr_normal_rate = 400000000; + pr_info("ddr3 normal rate changed to 400MHz for TO1.1.\n"); + } + busfreq_func.init = &init_ddrc_ddr_settings; + busfreq_func.update = &update_ddr_freq_imx_smp; + } + + if (busfreq_func.init) + err = busfreq_func.init(pdev); + else + err = -EINVAL; + + if (err) { + dev_err(busfreq_dev, "Busfreq init of ddr controller failed\n"); + return err; + } + + return 0; +} + +static const struct of_device_id imx_busfreq_ids[] = { + { .compatible = "fsl,imx_busfreq", }, + { /* sentinel */ } +}; + +static struct platform_driver busfreq_driver = { + .driver = { + .name = "imx_busfreq", + .owner = THIS_MODULE, + .of_match_table = imx_busfreq_ids, + }, + .probe = busfreq_probe, +}; + +/*! + * Initialise the busfreq_driver. + * + * @return The function always returns 0. + */ + +static int __init busfreq_init(void) +{ +#ifndef CONFIG_MX6_VPU_352M + if (platform_driver_register(&busfreq_driver) != 0) + return -ENODEV; + + pr_info("Bus freq driver module loaded\n"); +#endif + return 0; +} + +static void __exit busfreq_cleanup(void) +{ + sysfs_remove_file(&busfreq_dev->kobj, &dev_attr_enable.attr); + + /* Unregister the device structure */ + platform_driver_unregister(&busfreq_driver); + bus_freq_scaling_initialized = 0; +} + +module_init(busfreq_init); +module_exit(busfreq_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("BusFreq driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-imx/busfreq_ddr3.c b/arch/arm/mach-imx/busfreq_ddr3.c new file mode 100644 index 000000000000..3bd03dab05e6 --- /dev/null +++ b/arch/arm/mach-imx/busfreq_ddr3.c @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file busfreq_ddr3.c + * + * @brief iMX6 DDR3 frequency change specific file. + * + * @ingroup PM + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hardware.h" +#include "common.h" + +#define SMP_WFE_CODE_SIZE 0x400 + +#define MIN_DLL_ON_FREQ 333000000 +#define MAX_DLL_OFF_FREQ 125000000 +#define MMDC0_MPMUR0 0x8b8 +#define MMDC0_MPMUR0_OFFSET 16 +#define MMDC0_MPMUR0_MASK 0x3ff + +/* + * This structure is for passing necessary data for low level ocram + * busfreq code(arch/arm/mach-imx/ddr3_freq_imx6.S), if this struct + * definition is changed, the offset definition in + * arch/arm/mach-imx/ddr3_freq_imx6.S must be also changed accordingly, + * otherwise, the busfreq change function will be broken! + * + * This structure will be placed in front of the asm code on ocram. + */ +struct imx6_busfreq_info { + u32 freq; + void *ddr_settings; + u32 dll_off; + void *iomux_offsets; + u32 mu_delay_val; +} __aligned(8); + +/* DDR settings */ +static unsigned long (*iram_iomux_settings)[2]; + +static void __iomem *gic_dist_base; + +static int curr_ddr_rate; + +void (*imx7d_change_ddr_freq)(u32 freq) = NULL; +extern void imx7d_ddr3_freq_change(u32 freq); +extern void imx_lpddr3_freq_change(u32 freq); + +extern unsigned int ddr_normal_rate; +extern int low_bus_freq_mode; +extern int audio_bus_freq_mode; + +extern unsigned long save_ttbr1(void); +extern void restore_ttbr1(unsigned long ttbr1); +extern unsigned long ddr_freq_change_iram_base; + +extern unsigned long ddr_freq_change_total_size; +extern unsigned long iram_tlb_phys_addr; + +#ifdef CONFIG_SMP +volatile u32 *wait_for_ddr_freq_update; +static unsigned int online_cpus; +static u32 *irqs_used; + +void (*wfe_change_ddr_freq)(u32 cpuid, u32 *ddr_freq_change_done); +void (*imx7_wfe_change_ddr_freq)(u32 cpuid, u32 ocram_base); +extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done); +extern void imx7_smp_wfe(u32 cpuid, u32 ocram_base); +extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start"); +extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end"); +extern void __iomem *imx_scu_base; +#endif + +int can_change_ddr_freq(void) +{ + return 1; +} + +#ifdef CONFIG_SMP +/* + * each active core apart from the one changing + * the DDR frequency will execute this function. + * the rest of the cores have to remain in WFE + * state until the frequency is changed. + */ +static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id) +{ + u32 me; + + me = smp_processor_id(); +#ifdef CONFIG_LOCAL_TIMERS + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, + &me); +#endif + if (cpu_is_imx7d()) + imx7_wfe_change_ddr_freq(0x8 * me, + (u32)ddr_freq_change_iram_base); + else + wfe_change_ddr_freq(0xff << (me * 8), + (u32 *)&iram_iomux_settings[0][1]); +#ifdef CONFIG_LOCAL_TIMERS + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, + &me); +#endif + + return IRQ_HANDLED; +} +#endif + +/* change the DDR frequency. */ +int update_ddr_freq_imx_smp(int ddr_rate) +{ + int me = 0; + unsigned long ttbr1; +#ifdef CONFIG_SMP + unsigned int reg = 0; + int cpu = 0; +#endif + + if (!can_change_ddr_freq()) + return -1; + + if (ddr_rate == curr_ddr_rate) + return 0; + + printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate); + + /* ensure that all Cores are in WFE. */ + local_irq_disable(); + +#ifdef CONFIG_SMP + me = smp_processor_id(); + + /* Make sure all the online cores are active */ + while (1) { + bool not_exited_busfreq = false; + u32 reg = 0; + + for_each_online_cpu(cpu) { + if (cpu_is_imx7d()) + reg = *(wait_for_ddr_freq_update + 1); + + if (reg & (0x02 << (cpu * 8))) + not_exited_busfreq = true; + } + if (!not_exited_busfreq) + break; + } + + wmb(); + *wait_for_ddr_freq_update = 1; + dsb(); + if (cpu_is_imx7d()) + online_cpus = *(wait_for_ddr_freq_update + 1); + for_each_online_cpu(cpu) { + *((char *)(&online_cpus) + (u8)cpu) = 0x02; + if (cpu != me) { + /* set the interrupt to be pending in the GIC. */ + reg = 1 << (irqs_used[cpu] % 32); + writel_relaxed(reg, gic_dist_base + GIC_DIST_PENDING_SET + + (irqs_used[cpu] / 32) * 4); + } + } + /* Wait for the other active CPUs to idle */ + while (1) { + u32 reg = 0; + + if (cpu_is_imx7d()) + reg = *(wait_for_ddr_freq_update + 1); + reg |= (0x02 << (me * 8)); + if (reg == online_cpus) + break; + } +#endif + + /* Ensure iram_tlb_phys_addr is flushed to DDR. */ + __cpuc_flush_dcache_area(&iram_tlb_phys_addr, + sizeof(iram_tlb_phys_addr)); + + ttbr1 = save_ttbr1(); + /* Now we can change the DDR frequency. */ + if (cpu_is_imx7d()) + imx7d_change_ddr_freq(ddr_rate); + restore_ttbr1(ttbr1); + curr_ddr_rate = ddr_rate; + +#ifdef CONFIG_SMP + wmb(); + /* DDR frequency change is done . */ + *wait_for_ddr_freq_update = 0; + dsb(); + + /* wake up all the cores. */ + sev(); +#endif + + local_irq_enable(); + + printk(KERN_DEBUG "Bus freq set to %d done! cpu=%d\n", ddr_rate, me); + + return 0; +} + +int init_ddrc_ddr_settings(struct platform_device *busfreq_pdev) +{ + int ddr_type = imx_ddrc_get_ddr_type(); +#ifdef CONFIG_SMP + struct device_node *node; + u32 cpu; + struct device *dev = &busfreq_pdev->dev; + int err; + struct irq_data *d; + + node = of_find_compatible_node(NULL, NULL, "arm,cortex-a7-gic"); + if (!node) { + printk(KERN_ERR "failed to find imx7d-a7-gic device tree data!\n"); + return -EINVAL; + } + gic_dist_base = of_iomap(node, 0); + WARN(!gic_dist_base, "unable to map gic dist registers\n"); + + irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(), + GFP_KERNEL); + for_each_online_cpu(cpu) { + int irq; + /* + * set up a reserved interrupt to get all + * the active cores into a WFE state + * before changing the DDR frequency. + */ + irq = platform_get_irq(busfreq_pdev, cpu); + err = request_irq(irq, wait_in_wfe_irq, + IRQF_PERCPU, "ddrc", NULL); + if (err) { + dev_err(dev, + "Busfreq:request_irq failed %d, err = %d\n", + irq, err); + return err; + } + err = irq_set_affinity(irq, cpumask_of(cpu)); + if (err) { + dev_err(dev, + "Busfreq: Cannot set irq affinity irq=%d\n", + irq); + return err; + } + d = irq_get_irq_data(irq); + irqs_used[cpu] = d->hwirq + 32; + } + + /* Store the variable used to communicate between cores */ + wait_for_ddr_freq_update = (u32 *)ddr_freq_change_iram_base; + imx7_wfe_change_ddr_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base + 0x8, + &imx7_smp_wfe, SMP_WFE_CODE_SIZE - 0x8); +#endif + if (ddr_type == IMX_DDR_TYPE_DDR3) + imx7d_change_ddr_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base + SMP_WFE_CODE_SIZE, + &imx7d_ddr3_freq_change, + MX7_BUSFREQ_OCRAM_SIZE - SMP_WFE_CODE_SIZE); + else if (ddr_type == IMX_DDR_TYPE_LPDDR3 + || ddr_type == IMX_DDR_TYPE_LPDDR2) + imx7d_change_ddr_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base + + SMP_WFE_CODE_SIZE, + &imx_lpddr3_freq_change, + MX7_BUSFREQ_OCRAM_SIZE - SMP_WFE_CODE_SIZE); + + curr_ddr_rate = ddr_normal_rate; + + return 0; +} diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c new file mode 100644 index 000000000000..2ef1806bbc35 --- /dev/null +++ b/arch/arm/mach-imx/busfreq_lpddr2.c @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2017 NXP. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file busfreq_lpddr2.c + * + * @brief iMX6 LPDDR2 frequency change specific file. + * + * @ingroup PM + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "hardware.h" + +static struct device *busfreq_dev; +static int curr_ddr_rate; +static DEFINE_SPINLOCK(freq_lock); + +void (*mx6_change_lpddr2_freq)(u32 ddr_freq, int bus_freq_mode) = NULL; + +extern unsigned int ddr_normal_rate; +extern void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode); +extern void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode); +extern void imx6sll_lpddr2_freq_change(u32 freq, int bus_freq_mode); +extern unsigned long save_ttbr1(void); +extern void restore_ttbr1(unsigned long ttbr1); +extern void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings); +extern unsigned long ddr_freq_change_iram_base; +extern unsigned long imx6_lpddr2_freq_change_start asm("imx6_lpddr2_freq_change_start"); +extern unsigned long imx6_lpddr2_freq_change_end asm("imx6_lpddr2_freq_change_end"); +extern unsigned long mx6q_lpddr2_freq_change_start asm("mx6q_lpddr2_freq_change_start"); +extern unsigned long mx6q_lpddr2_freq_change_end asm("mx6q_lpddr2_freq_change_end"); +extern unsigned long iram_tlb_phys_addr; + +struct mmdc_settings_info { + u32 size; + void *settings; + int freq; +} __aligned(8); +static struct mmdc_settings_info *mmdc_settings_info; +void (*mx6_change_lpddr2_freq_smp)(u32 ddr_freq, struct mmdc_settings_info + *mmdc_settings_info) = NULL; + +static int mmdc_settings_size; +static unsigned long (*mmdc_settings)[2]; +static unsigned long (*iram_mmdc_settings)[2]; +static unsigned long *iram_settings_size; +static unsigned long *iram_ddr_freq_chage; +unsigned long mmdc_timing_settings[][2] = { + {0x0C, 0x0}, /* mmdc_mdcfg0 */ + {0x10, 0x0}, /* mmdc_mdcfg1 */ + {0x14, 0x0}, /* mmdc_mdcfg2 */ + {0x18, 0x0}, /* mmdc_mdmisc */ + {0x38, 0x0}, /* mmdc_mdcfg3lp */ +}; + +#ifdef CONFIG_SMP +volatile u32 *wait_for_lpddr2_freq_update; +static unsigned int online_cpus; +static u32 *irqs_used; +void (*wfe_change_lpddr2_freq)(u32 cpuid, u32 *ddr_freq_change_done); +extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done); +extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start"); +extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end"); +extern void __iomem *imx_scu_base; +static void __iomem *gic_dist_base; +#endif + +#ifdef CONFIG_SMP +static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id) +{ + u32 me; + + me = smp_processor_id(); +#ifdef CONFIG_LOCAL_TIMERS + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &me); +#endif + wfe_change_lpddr2_freq(0xff << (me * 8), + (u32 *)ddr_freq_change_iram_base); +#ifdef CONFIG_LOCAL_TIMERS + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &me); +#endif + return IRQ_HANDLED; +} +#endif + +/* change the DDR frequency. */ +int update_lpddr2_freq(int ddr_rate) +{ + unsigned long ttbr1, flags; + int mode = get_bus_freq_mode(); + + if (ddr_rate == curr_ddr_rate) + return 0; + + printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate); + + spin_lock_irqsave(&freq_lock, flags); + /* + * Flush the TLB, to ensure no TLB maintenance occurs + * when DDR is in self-refresh. + */ + ttbr1 = save_ttbr1(); + + /* Now change DDR frequency. */ + if (cpu_is_imx6sl()) + mx6_change_lpddr2_freq(ddr_rate, + (mode == BUS_FREQ_LOW || mode == BUS_FREQ_ULTRA_LOW) ? 1 : 0); + else + mx6_change_lpddr2_freq(ddr_rate, + (mode == BUS_FREQ_LOW || mode == BUS_FREQ_AUDIO) ? 1 : 0); + + restore_ttbr1(ttbr1); + + curr_ddr_rate = ddr_rate; + spin_unlock_irqrestore(&freq_lock, flags); + + printk(KERN_DEBUG "\nBus freq set to %d done...\n", ddr_rate); + + return 0; +} + +int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) +{ + unsigned long ddr_code_size; + busfreq_dev = &busfreq_pdev->dev; + + ddr_code_size = SZ_4K; + + if (cpu_is_imx6sl()) + mx6_change_lpddr2_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base, + &mx6_lpddr2_freq_change, ddr_code_size); + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) + mx6_change_lpddr2_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base, + &imx6_up_lpddr2_freq_change, ddr_code_size); + if (cpu_is_imx6sll()) + mx6_change_lpddr2_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base, + &imx6sll_lpddr2_freq_change, ddr_code_size); + + curr_ddr_rate = ddr_normal_rate; + + return 0; +} + +int update_lpddr2_freq_smp(int ddr_rate) +{ + unsigned long ttbr1; + int i, me = 0; +#ifdef CONFIG_SMP + int cpu = 0; + u32 reg = 0; +#endif + + if (ddr_rate == curr_ddr_rate) + return 0; + + printk(KERN_DEBUG "Bus freq set to %d start...\n", ddr_rate); + + for (i=0; i < mmdc_settings_size; i++) { + iram_mmdc_settings[i][0] = mmdc_settings[i][0]; + iram_mmdc_settings[i][1] = mmdc_settings[i][1]; + } + + mmdc_settings_info->size = mmdc_settings_size; + mmdc_settings_info->settings = iram_mmdc_settings; + mmdc_settings_info->freq = curr_ddr_rate; + + /* ensure that all Cores are in WFE. */ + local_irq_disable(); + +#ifdef CONFIG_SMP + me = smp_processor_id(); + + /* Make sure all the online cores are active */ + while (1) { + bool not_exited_busfreq = false; + for_each_online_cpu(cpu) { + reg = __raw_readl(imx_scu_base + 0x08); + if (reg & (0x02 << (cpu * 8))) + not_exited_busfreq = true; + } + if (!not_exited_busfreq) + break; + } + + wmb(); + *wait_for_lpddr2_freq_update = 1; + dsb(); + online_cpus = readl_relaxed(imx_scu_base + 0x08); + for_each_online_cpu(cpu) { + *((char *)(&online_cpus) + (u8)cpu) = 0x02; + if (cpu != me) { + reg = 1 << (irqs_used[cpu] % 32); + writel_relaxed(reg, gic_dist_base + GIC_DIST_PENDING_SET + + (irqs_used[cpu] / 32) * 4); + } + } + + /* Wait for the other active CPUs to idle */ + while (1) { + reg = 0; + reg = readl_relaxed(imx_scu_base + 0x08); + reg |= (0x02 << (me * 8)); + if (reg == online_cpus) + break; + } +#endif + + /* Ensure iram_tlb_phys_addr is flushed to DDR. */ + __cpuc_flush_dcache_area(&iram_tlb_phys_addr, + sizeof(iram_tlb_phys_addr)); + outer_clean_range(__pa(&iram_tlb_phys_addr), + __pa(&iram_tlb_phys_addr + 1)); + /* + * Flush the TLB, to ensure no TLB maintenance occurs + * when DDR is in self-refresh. + */ + ttbr1 = save_ttbr1(); + + curr_ddr_rate = ddr_rate; + + /* Now change DDR frequency. */ + mx6_change_lpddr2_freq_smp(ddr_rate, mmdc_settings_info); + + restore_ttbr1(ttbr1); + +#ifdef CONFIG_SMP + wmb(); + /* DDR frequency change is done . */ + *wait_for_lpddr2_freq_update = 0; + dsb(); + /* wake up all the cores. */ + sev(); +#endif + + local_irq_enable(); + + printk(KERN_DEBUG "Bus freq set to %d done! cpu=%d\n", ddr_rate, me); + + return 0; +} + +int init_mmdc_lpddr2_settings_mx6q(struct platform_device *busfreq_pdev) +{ + struct device *dev = &busfreq_pdev->dev; + unsigned long ddr_code_size = 0; + unsigned long wfe_code_size = 0; + struct device_node *node; + void __iomem *mmdc_base; + int i; +#ifdef CONFIG_SMP + struct irq_data *d; + u32 cpu; + int err; +#endif + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc"); + if (!node) { + printk(KERN_ERR "failed to find mmdc device tree data!\n"); + return -EINVAL; + } + + mmdc_base = of_iomap(node, 0); + if (!mmdc_base) { + dev_err(dev, "unable to map mmdc registers\n"); + return -EINVAL; + } + + mmdc_settings_size = ARRAY_SIZE(mmdc_timing_settings); + mmdc_settings = kmalloc((mmdc_settings_size * 8), GFP_KERNEL); + memcpy(mmdc_settings, mmdc_timing_settings, + sizeof(mmdc_timing_settings)); + +#ifdef CONFIG_SMP + node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-gic"); + if (!node) { + printk(KERN_ERR "failed to find imx6q-a9-gic device tree data!\n"); + return -EINVAL; + } + + gic_dist_base = of_iomap(node, 0); + WARN(!gic_dist_base, "unable to map gic dist registers\n"); + + irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(), + GFP_KERNEL); + + for_each_online_cpu(cpu) { + int irq = platform_get_irq(busfreq_pdev, cpu); + err = request_irq(irq, wait_in_wfe_irq, IRQF_PERCPU, + "mmdc_1", NULL); + if (err) { + dev_err(dev, + "Busfreq:request_irq failed %d, err = %d\n", + irq, err); + return err; + } + err = irq_set_affinity(irq, cpumask_of(cpu)); + if (err) { + dev_err(dev, + "Busfreq: Cannot set irq affinity irq=%d,\n", + irq); + return err; + } + d = irq_get_irq_data(irq); + irqs_used[cpu] = d->hwirq + 32; + } + + /* Stoange_iram_basee the variable used to communicate between cores in + * a non-cacheable IRAM area */ + wait_for_lpddr2_freq_update = (u32 *)ddr_freq_change_iram_base; + wfe_code_size = (&wfe_smp_freq_change_end - &wfe_smp_freq_change_start) *4; + + wfe_change_lpddr2_freq = (void *)fncpy((void *)ddr_freq_change_iram_base + 0x8, + &wfe_smp_freq_change, wfe_code_size); +#endif + iram_settings_size = (void *)ddr_freq_change_iram_base + wfe_code_size + 0x8; + iram_mmdc_settings = (void *)iram_settings_size + sizeof(*mmdc_settings_info); + iram_ddr_freq_chage = (void *)iram_mmdc_settings + (mmdc_settings_size * 8) + 0x8; + mmdc_settings_info = (struct mmdc_settings_info *)iram_settings_size; + + ddr_code_size = (&mx6q_lpddr2_freq_change_end -&mx6q_lpddr2_freq_change_start) *4; + + mx6_change_lpddr2_freq_smp = (void *)fncpy(iram_ddr_freq_chage, + &mx6q_lpddr2_freq_change, ddr_code_size); + + /* save initial mmdc boot timing settings */ + for (i=0; i < mmdc_settings_size; i++) + mmdc_settings[i][1] = readl_relaxed(mmdc_base + + mmdc_settings[i][0]); + + curr_ddr_rate = ddr_normal_rate; + + return 0; +} diff --git a/arch/arm/mach-imx/common.c b/arch/arm/mach-imx/common.c new file mode 100644 index 000000000000..cf57b55a369b --- /dev/null +++ b/arch/arm/mach-imx/common.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include +#include +#include +#include +#include + +#include "hardware.h" + +unsigned long iram_tlb_base_addr; +unsigned long iram_tlb_phys_addr; + +unsigned long save_ttbr1(void) +{ + unsigned long lttbr1; + asm volatile( + ".align 4\n" + "mrc p15, 0, %0, c2, c0, 1\n" + : "=r" (lttbr1) + ); + return lttbr1; +} + +void restore_ttbr1(unsigned long ttbr1) +{ + asm volatile( + ".align 4\n" + "mcr p15, 0, %0, c2, c0, 1\n" + : : "r" (ttbr1) + ); +} + +#define OCOTP_MAC_OFF (cpu_is_imx7d() ? 0x640 : 0x620) +#define OCOTP_MACn(n) (OCOTP_MAC_OFF + (n) * 0x10) +void __init imx6_enet_mac_init(const char *enet_compat, const char *ocotp_compat) +{ + struct device_node *ocotp_np, *enet_np, *from = NULL; + void __iomem *base; + struct property *newmac; + u32 macaddr_low; + u32 macaddr_high = 0; + u32 macaddr1_high = 0; + u8 *macaddr; + int i, id; + + for (i = 0; i < 2; i++) { + enet_np = of_find_compatible_node(from, NULL, enet_compat); + if (!enet_np) + return; + + from = enet_np; + + if (of_get_mac_address(enet_np)) + goto put_enet_node; + + id = of_alias_get_id(enet_np, "ethernet"); + if (id < 0) + id = i; + + ocotp_np = of_find_compatible_node(NULL, NULL, ocotp_compat); + if (!ocotp_np) { + pr_warn("failed to find ocotp node\n"); + goto put_enet_node; + } + + base = of_iomap(ocotp_np, 0); + if (!base) { + pr_warn("failed to map ocotp\n"); + goto put_ocotp_node; + } + + macaddr_low = readl_relaxed(base + OCOTP_MACn(1)); + if (id) + macaddr1_high = readl_relaxed(base + OCOTP_MACn(2)); + else + macaddr_high = readl_relaxed(base + OCOTP_MACn(0)); + + newmac = kzalloc(sizeof(*newmac) + 6, GFP_KERNEL); + if (!newmac) + goto put_ocotp_node; + + newmac->value = newmac + 1; + newmac->length = 6; + newmac->name = kstrdup("local-mac-address", GFP_KERNEL); + if (!newmac->name) { + kfree(newmac); + goto put_ocotp_node; + } + + macaddr = newmac->value; + if (id) { + macaddr[5] = (macaddr_low >> 16) & 0xff; + macaddr[4] = (macaddr_low >> 24) & 0xff; + macaddr[3] = macaddr1_high & 0xff; + macaddr[2] = (macaddr1_high >> 8) & 0xff; + macaddr[1] = (macaddr1_high >> 16) & 0xff; + macaddr[0] = (macaddr1_high >> 24) & 0xff; + } else { + macaddr[5] = macaddr_high & 0xff; + macaddr[4] = (macaddr_high >> 8) & 0xff; + macaddr[3] = (macaddr_high >> 16) & 0xff; + macaddr[2] = (macaddr_high >> 24) & 0xff; + macaddr[1] = macaddr_low & 0xff; + macaddr[0] = (macaddr_low >> 8) & 0xff; + } + + of_update_property(enet_np, newmac); + +put_ocotp_node: + of_node_put(ocotp_np); +put_enet_node: + of_node_put(enet_np); + } +} + +#ifndef CONFIG_HAVE_IMX_GPC +int imx_gpc_mf_request_on(unsigned int irq, unsigned int on) { return 0; } +EXPORT_SYMBOL_GPL(imx_gpc_mf_request_on); +#endif + +#if !defined(CONFIG_SOC_IMX6SL) +u32 imx6_lpddr2_freq_change_start, imx6_lpddr2_freq_change_end; +void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode) {} +#endif + +#if !defined(CONFIG_SOC_IMX6SLL) +void imx6sll_lpddr2_freq_change(u32 freq, int bus_freq_mode) {} +#endif + +#if !defined(CONFIG_SOC_IMX6SX) && !defined(CONFIG_SOC_IMX6UL) +u32 imx6_up_ddr3_freq_change_start, imx6_up_ddr3_freq_change_end; +struct imx6_busfreq_info { +} __aligned(8); +void imx6_up_ddr3_freq_change(struct imx6_busfreq_info *busfreq_info) {} +void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode) {} +#endif + +#if !defined(CONFIG_SOC_IMX6ULL) +u32 mx6ull_lpm_wfi_start, mx6ull_lpm_wfi_end; +void imx6ull_low_power_idle(void) {} +#endif + +#if !defined(CONFIG_SOC_IMX6Q) +u32 mx6_ddr3_freq_change_start, mx6_ddr3_freq_change_end; +u32 mx6q_lpddr2_freq_change_start, mx6q_lpddr2_freq_change_end; +u32 wfe_smp_freq_change_start, wfe_smp_freq_change_end; +void mx6_ddr3_freq_change(u32 freq, void *ddr_settings, + bool dll_mode, void *iomux_offsets) {} +void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings) {} +void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done) {} +#endif + +#if !defined(CONFIG_SOC_IMX7D) +void imx7_smp_wfe(u32 cpuid, u32 ocram_base) {} +void imx7d_ddr3_freq_change(u32 freq) {} +#endif + diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 912aeceb4ff8..60add3295015 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -104,6 +104,8 @@ void imx6_set_int_mem_clk_lpm(bool enable); void imx6sl_set_wait_clk(bool enter); int imx_mmdc_get_ddr_type(void); int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode); +void imx_busfreq_map_io(void); +void imx7_pm_map_io(void); void imx_cpu_die(unsigned int cpu); int imx_cpu_kill(unsigned int cpu); @@ -120,6 +122,12 @@ static const u32 imx53_suspend_sz; static inline void imx6_suspend(void __iomem *ocram_vbase) {} #endif +#ifdef CONFIG_HAVE_IMX_DDRC +int imx_ddrc_get_ddr_type(void); +#else +static inline int imx_ddrc_get_ddr_type(void) { return 0; } +#endif + void imx6_pm_ccm_init(const char *ccm_compat); void imx6q_pm_init(void); void imx6dl_pm_init(void); diff --git a/arch/arm/mach-imx/ddr3_freq_imx7d.S b/arch/arm/mach-imx/ddr3_freq_imx7d.S new file mode 100644 index 000000000000..9342e0d83f5e --- /dev/null +++ b/arch/arm/mach-imx/ddr3_freq_imx7d.S @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "hardware.h" + +#define DDRC_MSTR 0x0 +#define DDRC_STAT 0x4 +#define DDRC_MRCTRL0 0x10 +#define DDRC_MRCTRL1 0x14 +#define DDRC_MRSTAT 0x18 +#define DDRC_PWRCTL 0x30 +#define DDRC_RFSHCTL3 0x60 +#define DDRC_RFSHTMG 0x64 +#define DDRC_DBG1 0x304 +#define DDRC_SWCTL 0x320 +#define DDRC_SWSTAT 0x324 +#define DDRC_PSTAT 0x3fc +#define DDRC_PCTRL_0 0x490 +#define DDRC_ZQCTL0 0x180 +#define DDRC_DFIMISC 0x1b0 +#define DDRC_DBGCAM 0x308 +#define DDRPHY_LP_CON0 0x18 +#define IOMUXC_GPR8 0x20 +#define DDRPHY_MDLL_CON0 0xb0 +#define DDRPHY_MDLL_CON1 0xb4 +#define DDRPHY_OFFSETD_CON0 0x50 +#define DDRPHY_OFFSETR_CON0 0x20 +#define DDRPHY_OFFSETR_CON1 0x24 +#define DDRPHY_OFFSETR_CON2 0x28 +#define DDRPHY_OFFSETW_CON0 0x30 +#define DDRPHY_OFFSETW_CON1 0x34 +#define DDRPHY_OFFSETW_CON2 0x38 +#define DDRPHY_CA_WLDSKEW_CON0 0x6c +#define DDRPHY_CA_DSKEW_CON0 0x7c +#define DDRPHY_CA_DSKEW_CON1 0x80 +#define DDRPHY_CA_DSKEW_CON2 0x84 + +#define ANADIG_DIGPROG 0x800 + + .align 3 + + .macro switch_to_below_100m + + ldr r7, =0x2 + str r7, [r4, #DDRC_DBG1] + + ldr r6, =0x36000000 +1: + ldr r7, [r4, #DDRC_DBGCAM] + and r7, r7, r6 + cmp r7, r6 + bne 1b + + ldr r6, =0x1 +2: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 2b + + ldr r7, =0x10f0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x0 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800010f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r6, =0x1 +3: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 3b + + ldr r7, =0x20f0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x8 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800020f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r6, =0x1 +4: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 4b + + ldr r7, =0x10f0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x1 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800010f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r7, =0x20 + str r7, [r4, #DDRC_PWRCTL] + + ldr r6, =0x23 +5: + ldr r7, [r4, #DDRC_STAT] + and r7, r7, r6 + cmp r7, r6 + bne 5b + + ldr r7, =0x0 + str r7, [r4, #DDRC_SWCTL] + + ldr r7, =0x03048001 + str r7, [r4, #DDRC_MSTR] + + ldr r7, =0x1 + str r7, [r4, #DDRC_SWCTL] + + ldr r6, =0x1 +6: + ldr r7, [r4, #DDRC_SWSTAT] + and r7, r7, r6 + cmp r7, r6 + bne 6b + + ldr r7, =0x10010100 + str r7, [r5, #0x4] + + ldr r6, =24000000 + cmp r0, r6 + beq 25f + + ldr r7, =0x000B000D + str r7,[r4, #DDRC_RFSHTMG] + b 7f + +25: + ldr r7, =0x00030004 + str r7,[r4, #DDRC_RFSHTMG] + + /* dram alt sel set to OSC */ + ldr r7, =0x10000000 + ldr r8, =0xa080 + str r7, [r2, r8] + /* dram root set to from dram alt, div by 1 */ + ldr r7, =0x11000000 + ldr r8, =0x9880 + str r7, [r2, r8] + b 8f +7: + /* dram alt sel set to pfd0_392m */ + ldr r7, =0x15000000 + ldr r8, =0xa080 + str r7, [r2, r8] + /* dram root set to from dram alt, div by 4 */ + ldr r7, =0x11000003 + ldr r8, =0x9880 + str r7, [r2, r8] +8: + ldr r7, =0x202ffd0 + str r7, [r5, #DDRPHY_MDLL_CON0] + + ldr r7, =0x1000007f + str r7, [r5, #DDRPHY_OFFSETD_CON0] + + ldr r7, =0x7f7f7f7f + str r7, [r5, #DDRPHY_OFFSETR_CON0] + str r7, [r5, #DDRPHY_OFFSETR_CON1] + ldr r7, =0x7f + str r7, [r5, #DDRPHY_OFFSETR_CON2] + + ldr r7, =0x7f7f7f7f + str r7, [r5, #DDRPHY_OFFSETW_CON0] + str r7, [r5, #DDRPHY_OFFSETW_CON1] + ldr r7, =0x7f + str r7, [r5, #DDRPHY_OFFSETW_CON2] + + ldr r7, [r9, #ANADIG_DIGPROG] + and r7, r7, #0x11 + cmp r7, #0x11 + bne 20f + + ldr r7, =0x0 + str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0] + ldr r7, =0x60606060 + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + ldr r7, =0x00006060 + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] + b 21f +20: + ldr r7, =0x0 + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] +21: + ldr r7, =0x1100007f + str r7, [r5, #DDRPHY_OFFSETD_CON0] + ldr r7, =0x1000007f + str r7, [r5, #DDRPHY_OFFSETD_CON0] + + ldr r7, =0x0 + str r7, [r4, #DDRC_PWRCTL] + + ldr r6, =0x1 +9: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 9b + + ldr r7, =0xf0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x820 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800000f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r6, =0x1 +10: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 10b + + ldr r7, =0x800020 + str r7, [r4, #DDRC_ZQCTL0] + + ldr r7, =0x0 + str r7, [r4, #DDRC_DBG1] + + /* enable auto self-refresh */ + ldr r7, [r4, #DDRC_PWRCTL] + orr r7, r7, #(1 << 0) + str r7, [r4, #DDRC_PWRCTL] + + .endm + + .macro switch_to_533m + + ldr r7, =0x2 + str r7, [r4, #DDRC_DBG1] + + ldr r7, =0x78 + str r7, [r3, #IOMUXC_GPR8] + orr r7, r7, #0x100 + str r7, [r3, #IOMUXC_GPR8] + + ldr r6, =0x30000000 +11: + ldr r7, [r4, #DDRC_DBGCAM] + and r7, r7, r6 + cmp r7, r6 + bne 11b + + ldr r6, =0x1 +12: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 12b + + ldr r7, =0x10f0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x1 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800010f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r7, =0x20 + str r7, [r4, #DDRC_PWRCTL] + + ldr r6, =0x23 +13: + ldr r7, [r4, #DDRC_STAT] + and r7, r7, r6 + cmp r7, r6 + bne 13b + + ldr r7, =0x03040001 + str r7, [r4, #DDRC_MSTR] + + ldr r7, =0x40800020 + str r7, [r4, #DDRC_ZQCTL0] + + + ldr r7, =0x10210100 + str r7, [r5, #0x4] + + ldr r7, =0x00040046 + str r7, [r4, #DDRC_RFSHTMG] + + /* dram root set to from dram main, div by 2 */ + ldr r7, =0x10000001 + ldr r8, =0x9880 + str r7, [r2, r8] + + ldr r7, =0x1010007e + str r7, [r5, #DDRPHY_MDLL_CON0] + + ldr r7, =0x10000008 + str r7, [r5, #DDRPHY_OFFSETD_CON0] + + ldr r7, =0x08080808 + str r7, [r5, #DDRPHY_OFFSETR_CON0] + str r7, [r5, #DDRPHY_OFFSETR_CON1] + ldr r7, =0x8 + str r7, [r5, #DDRPHY_OFFSETR_CON2] + + ldr r7, =0x08080808 + str r7, [r5, #DDRPHY_OFFSETW_CON0] + str r7, [r5, #DDRPHY_OFFSETW_CON1] + ldr r7, =0x8 + str r7, [r5, #DDRPHY_OFFSETW_CON2] + + ldr r7, [r9, #ANADIG_DIGPROG] + and r7, r7, #0x11 + cmp r7, #0x11 + bne 22f + + ldr r7, =0x40404040 + str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0] + ldr r7, =0x18181818 + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + ldr r7, =0x40401818 + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] + b 23f +22: + ldr r7, =0x0 + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] +23: + ldr r7, =0x11000008 + str r7, [r5, #DDRPHY_OFFSETD_CON0] + ldr r7, =0x10000008 + str r7, [r5, #DDRPHY_OFFSETD_CON0] + + ldr r6, =0x4 +14: + ldr r7, [r5, #DDRPHY_MDLL_CON1] + and r7, r7, r6 + cmp r7, r6 + bne 14b + + ldr r7, =0x1 + str r7, [r4, #DDRC_RFSHCTL3] + ldr r7, =0x3 + str r7, [r4, #DDRC_RFSHCTL3] + + ldr r7, =0x0 + str r7, [r4, #DDRC_PWRCTL] + + ldr r6, =0x1 +15: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 15b + + ldr r7, =0x10f0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x0 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800010f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r6, =0x1 +16: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 16b + + ldr r7, =0xf0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x930 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800000f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r7, =0x0 + str r7, [r4, #DDRC_RFSHCTL3] + ldr r7, =0x2 + str r7, [r4, #DDRC_RFSHCTL3] + + ldr r6, =0x1 +17: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 17b + + ldr r7, =0xf0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x930 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800000f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r6, =0x1 +18: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 18b + + ldr r7, =0x20f0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x408 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800020f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r6, =0x1 +19: + ldr r7, [r4, #DDRC_MRSTAT] + and r7, r7, r6 + cmp r7, r6 + beq 19b + + ldr r7, =0x10f0 + str r7, [r4, #DDRC_MRCTRL0] + ldr r7, =0x4 + str r7, [r4, #DDRC_MRCTRL1] + ldr r7, =0x800010f0 + str r7, [r4, #DDRC_MRCTRL0] + + ldr r7, =0x0 + str r7, [r4, #DDRC_DBG1] + + /* enable auto self-refresh */ + ldr r7, [r4, #DDRC_PWRCTL] + orr r7, r7, #(1 << 0) + str r7, [r4, #DDRC_PWRCTL] + + .endm + +ENTRY(imx7d_ddr3_freq_change) + push {r2 - r9} + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + dsb + isb + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + ldr r2, =IMX_IO_P2V(MX7D_CCM_BASE_ADDR) + ldr r3, =IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR) + ldr r4, =IMX_IO_P2V(MX7D_DDRC_BASE_ADDR) + ldr r5, =IMX_IO_P2V(MX7D_DDRC_PHY_BASE_ADDR) + ldr r9, =IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR) + + ldr r6, =100000000 + cmp r0, r6 + bgt set_to_533m + +set_to_below_100m: + switch_to_below_100m + b done + +set_to_533m: + switch_to_533m + b done + +done: + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + dsb + isb + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + /* Restore registers */ + pop {r2 - r9} + mov pc, lr + .ltorg +ENDPROC(imx7d_ddr3_freq_change) diff --git a/arch/arm/mach-imx/ddrc.c b/arch/arm/mach-imx/ddrc.c new file mode 100644 index 000000000000..9c7f627d465e --- /dev/null +++ b/arch/arm/mach-imx/ddrc.c @@ -0,0 +1,86 @@ +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include + +#include "hardware.h" + +#define DDRC_MSTR 0x0 +#define BM_DDRC_MSTR_DDR3 0x1 +#define BM_DDRC_MSTR_LPDDR2 0x4 +#define BM_DDRC_MSTR_LPDDR3 0x8 + +static int ddr_type; + +static int imx_ddrc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + void __iomem *ddrc_base, *reg; + u32 val; + + ddrc_base = of_iomap(np, 0); + WARN_ON(!ddrc_base); + + reg = ddrc_base + DDRC_MSTR; + /* Get ddr type */ + val = readl_relaxed(reg); + val &= (BM_DDRC_MSTR_DDR3 | BM_DDRC_MSTR_LPDDR2 + | BM_DDRC_MSTR_LPDDR3); + + switch (val) { + case BM_DDRC_MSTR_DDR3: + pr_info("DDR type is DDR3!\n"); + ddr_type = IMX_DDR_TYPE_DDR3; + break; + case BM_DDRC_MSTR_LPDDR2: + pr_info("DDR type is LPDDR2!\n"); + ddr_type = IMX_DDR_TYPE_LPDDR2; + break; + case BM_DDRC_MSTR_LPDDR3: + pr_info("DDR type is LPDDR3!\n"); + ddr_type = IMX_DDR_TYPE_LPDDR3; + break; + default: + break; + } + + return 0; +} + +int imx_ddrc_get_ddr_type(void) +{ + return ddr_type; +} + +static struct of_device_id imx_ddrc_dt_ids[] = { + { .compatible = "fsl,imx7-ddrc", }, + { /* sentinel */ } +}; + +static struct platform_driver imx_ddrc_driver = { + .driver = { + .name = "imx-ddrc", + .owner = THIS_MODULE, + .of_match_table = imx_ddrc_dt_ids, + }, + .probe = imx_ddrc_probe, +}; + +static int __init imx_ddrc_init(void) +{ + return platform_driver_register(&imx_ddrc_driver); +} +postcore_initcall(imx_ddrc_init); diff --git a/arch/arm/mach-imx/hardware.h b/arch/arm/mach-imx/hardware.h index 92c5a9c9f94b..ce8e8a39f051 100644 --- a/arch/arm/mach-imx/hardware.h +++ b/arch/arm/mach-imx/hardware.h @@ -81,13 +81,16 @@ * CCM 0x020c4000+0x004000 -> 0xf42c4000+0x004000 * ANATOP 0x020c8000+0x004000 -> 0xf42c8000+0x004000 * UART4 0x021f0000+0x004000 -> 0xf42f0000+0x004000 + * mx7d: + * CCM 0x30380000+0x010000 -> 0xf5380000+0x010000 + * ANATOP 0x30360000+0x010000 -> 0xf5360000+0x010000 + * UART1 0x30860000+0x010000 -> 0xf5860000+0x010000 */ #define IMX_IO_P2V(x) ( \ - (((x) & 0x80000000) >> 7) | \ (0xf4000000 + \ - (((x) & 0x50000000) >> 6) + \ - (((x) & 0x0b000000) >> 4) + \ - (((x) & 0x000fffff)))) + (((x) & 0x50000000) >> 4) + \ + (((x) & 0x0a000000) >> 4) + \ + (((x) & 0x00ffffff)))) #define IMX_IO_ADDRESS(x) IOMEM(IMX_IO_P2V(x)) @@ -99,6 +102,7 @@ #include "mx2x.h" #include "mx21.h" #include "mx27.h" +#include "mx7.h" #define imx_map_entry(soc, name, _type) { \ .virtual = soc ## _IO_P2V(soc ## _ ## name ## _BASE_ADDR), \ diff --git a/arch/arm/mach-imx/lpddr3_freq_imx.S b/arch/arm/mach-imx/lpddr3_freq_imx.S new file mode 100644 index 000000000000..80fb1184fa54 --- /dev/null +++ b/arch/arm/mach-imx/lpddr3_freq_imx.S @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "hardware.h" + +#define DDRC_MSTR 0x0 +#define DDRC_STAT 0x4 +#define DDRC_PWRCTL 0x30 +#define DDRC_RFSHTMG 0x64 +#define DDRC_DBG1 0x304 +#define DDRC_PSTAT 0x3fc +#define DDRC_PCTRL_0 0x490 +#define DDRC_DFIMISC 0x1b0 +#define DDRC_DBGCAM 0x308 +#define DDRC_SWCTL 0x320 +#define DDRC_SWSTAT 0x324 +#define DDRPHY_LP_CON0 0x18 +#define IOMUXC_GPR8 0x20 +#define DDRPHY_PHY_CON1 0x4 +#define DDRPHY_MDLL_CON0 0xb0 +#define DDRPHY_MDLL_CON1 0xb4 +#define DDRPHY_OFFSETD_CON0 0x50 +#define DDRPHY_OFFSETR_CON0 0x20 +#define DDRPHY_OFFSETR_CON1 0x24 +#define DDRPHY_OFFSETR_CON2 0x28 +#define DDRPHY_OFFSETW_CON0 0x30 +#define DDRPHY_OFFSETW_CON1 0x34 +#define DDRPHY_OFFSETW_CON2 0x38 +#define DDRPHY_RFSHTMG 0x64 +#define DDRPHY_CA_WLDSKEW_CON0 0x6c +#define DDRPHY_CA_DSKEW_CON0 0x7c +#define DDRPHY_CA_DSKEW_CON1 0x80 +#define DDRPHY_CA_DSKEW_CON2 0x84 + +#define ANADIG_DIGPROG 0x800 + + .align 3 + + .macro ddrc_prepare + + /* disable port */ + ldr r7, =0x0 + str r7, [r4, #DDRC_PCTRL_0] + + /* wait port busy done */ + ldr r6, =0x10001 +1: + ldr r7, [r4, #DDRC_PSTAT] + and r7, r7, r6 + cmp r7, #0 + bne 1b + + ldr r7, =0x20 + str r7, [r4, #DDRC_PWRCTL] + + ldr r6, =0x23 +2: + ldr r7, [r4, #DDRC_STAT] + and r7, r7, r6 + cmp r7, r6 + bne 2b + + ldr r7, =0x1 + str r7, [r4, #DDRC_DBG1] + + ldr r6, =0x30000000 +3: + ldr r7, [r4, #DDRC_DBGCAM] + and r7, r7, r6 + cmp r7, r6 + bne 3b + + ldr r7, =0x0 + str r7, [r4, #DDRC_SWCTL] + + ldr r7, =0x0 + str r7, [r4, #DDRC_DFIMISC] + + ldr r7, =0x1 + str r7, [r4, #DDRC_SWCTL] + + ldr r6, =0x1 +4: + ldr r7, [r4, #DDRC_SWSTAT] + and r7, r7, r6 + cmp r7, r6 + bne 4b + + .endm + + .macro ddrc_done + + ldr r7, =0x0 + str r7, [r4, #DDRC_PWRCTL] + + ldr r6, =0x3 +5: + ldr r7, [r4, #DDRC_STAT] + and r7, r7, r6 + cmp r7, r6 + beq 5b + + ldr r7, =0x0 + str r7, [r4, #DDRC_DBG1] + + ldr r7, =0x1 + str r7, [r4, #DDRC_PCTRL_0] + + /* enable auto self-refresh */ + ldr r7, [r4, #DDRC_PWRCTL] + orr r7, r7, #(1 << 0) + str r7, [r4, #DDRC_PWRCTL] + + .endm + + .macro switch_to_below_100m + + /* LPDDR2 and LPDDR3 has different setting */ + ldr r8, [r4, #DDRC_MSTR] + ands r8, r8, #0x4 + bne 9f + + /* LPDDR3 */ + ldr r7, =0x00000100 + str r7, [r5, #DDRPHY_PHY_CON1] + b 10f +9: + /* LPDDR2 */ + ldr r7, =0x10010100 + str r7, [r5, #DDRPHY_PHY_CON1] +10: + ldr r6, =24000000 + cmp r0, r6 + beq 16f + + ldr r7, =0x0005000B + str r7, [r4, #DDRC_RFSHTMG] + b 6f +16: + ldr r7, =0x00010003 + str r7, [r4, #DDRC_RFSHTMG] + + /* dram alt sel set to OSC */ + ldr r7, =0x10000000 + ldr r8, =0xa080 + str r7, [r2, r8] + /* dram root set to from dram alt, div by 1 */ + ldr r7, =0x11000000 + ldr r8, =0x9880 + str r7, [r2, r8] + b 7f + +6: + /* dram alt sel set to pfd0_392m */ + ldr r7, =0x15000000 + ldr r8, =0xa080 + str r7, [r2, r8] + /* dram root set to from dram alt, div by 4 */ + ldr r7, =0x11000003 + ldr r8, =0x9880 + str r7, [r2, r8] +7: + ldr r7, =0x202ffd0 + str r7, [r5, #DDRPHY_MDLL_CON0] + + ldr r7, =0x7f + str r7, [r5, #DDRPHY_OFFSETD_CON0] + + ldr r7, =0x7f7f7f7f + str r7, [r5, #DDRPHY_OFFSETR_CON0] + str r7, [r5, #DDRPHY_OFFSETR_CON1] + ldr r7, =0x7f + str r7, [r5, #DDRPHY_OFFSETR_CON2] + + ldr r7, =0x7f7f7f7f + str r7, [r5, #DDRPHY_OFFSETW_CON0] + str r7, [r5, #DDRPHY_OFFSETW_CON1] + ldr r7, =0x7f + str r7, [r5, #DDRPHY_OFFSETW_CON2] + + ldr r7, [r9, #ANADIG_DIGPROG] + and r7, r7, #0x11 + cmp r7, #0x11 + bne 11f + + ldr r7, =0x0 + str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0] + ldr r7, =0x60606060 + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + ldr r7, =0x00006060 + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] + b 12f +11: + ldr r7, =0x0 + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] +12: + ldr r7, =0x100007f + str r7, [r5, #DDRPHY_OFFSETD_CON0] + ldr r7, =0x7f + str r7, [r5, #DDRPHY_OFFSETD_CON0] + + .endm + + .macro switch_to_533m + + ldr r7, =0x10210100 + str r7, [r5, #DDRPHY_PHY_CON1] + + ldr r7, =0x00200038 + str r7, [r4, #DDRC_RFSHTMG] + + /* dram root set to from dram main, div by 2 */ + ldr r7, =0x10000001 + ldr r8, =0x9880 + str r7, [r2, r8] + + ldr r7, =0x1010007e + str r7, [r5, #DDRPHY_MDLL_CON0] + + ldr r7, =0x10000008 + str r7, [r5, #DDRPHY_OFFSETD_CON0] + + ldr r7, =0x08080808 + str r7, [r5, #DDRPHY_OFFSETR_CON0] + str r7, [r5, #DDRPHY_OFFSETR_CON1] + ldr r7, =0x8 + str r7, [r5, #DDRPHY_OFFSETR_CON2] + + ldr r7, =0x08080808 + str r7, [r5, #DDRPHY_OFFSETW_CON0] + str r7, [r5, #DDRPHY_OFFSETW_CON1] + ldr r7, =0x8 + str r7, [r5, #DDRPHY_OFFSETW_CON2] + + /* LPDDR2 and LPDDR3 has different setting */ + ldr r8, [r4, #DDRC_MSTR] + ands r8, r8, #0x4 + beq 15f + + ldr r7, [r9, #ANADIG_DIGPROG] + and r7, r7, #0x11 + cmp r7, #0x11 + bne 14f + + ldr r7, =0x08080808 + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + ldr r7, =0x0a0a0808 + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] + ldr r7, =0x0a0a0a0a + str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0] + b 14f +15: + ldr r7, [r9, #ANADIG_DIGPROG] + and r7, r7, #0x11 + cmp r7, #0x11 + bne 13f + + ldr r7, =0x1c1c1c1c + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + ldr r7, =0x30301c1c + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] + ldr r7, =0x30303030 + str r7, [r5, #DDRPHY_CA_WLDSKEW_CON0] + b 14f +13: + ldr r7, =0x08080808 + str r7, [r5, #DDRPHY_CA_DSKEW_CON0] + str r7, [r5, #DDRPHY_CA_DSKEW_CON1] + ldr r7, =0x0808 + str r7, [r5, #DDRPHY_CA_DSKEW_CON2] +14: + ldr r7, =0x11000008 + str r7, [r5, #DDRPHY_OFFSETD_CON0] + ldr r7, =0x10000008 + str r7, [r5, #DDRPHY_OFFSETD_CON0] + + ldr r6, =0x4 +8: + ldr r7, [r5, #DDRPHY_MDLL_CON1] + and r7, r7, r6 + cmp r7, r6 + bne 8b + + .endm + +ENTRY(imx_lpddr3_freq_change) + push {r2 - r9} + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Disable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + ldr r2, =IMX_IO_P2V(MX7D_CCM_BASE_ADDR) + ldr r3, =IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR) + ldr r4, =IMX_IO_P2V(MX7D_DDRC_BASE_ADDR) + ldr r5, =IMX_IO_P2V(MX7D_DDRC_PHY_BASE_ADDR) + ldr r9, =IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR) + + ddrc_prepare + + ldr r6, =100000000 + cmp r0, r6 + bgt set_to_533m + +set_to_below_100m: + switch_to_below_100m + b done + +set_to_533m: + switch_to_533m + b done + +done: + ddrc_done + + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + /* Restore registers */ + pop {r2 - r9} + mov pc, lr +ENDPROC(imx_lpddr3_freq_change) diff --git a/arch/arm/mach-imx/mach-imx7d.c b/arch/arm/mach-imx/mach-imx7d.c index ebb27592a9f7..6baead31ec29 100644 --- a/arch/arm/mach-imx/mach-imx7d.c +++ b/arch/arm/mach-imx/mach-imx7d.c @@ -101,6 +101,13 @@ static void __init imx7d_init_irq(void) irqchip_init(); } +static void __init imx7d_map_io(void) +{ + debug_ll_io_init(); + imx7_pm_map_io(); + imx_busfreq_map_io(); +} + static const char *const imx7d_dt_compat[] __initconst = { "fsl,imx7d", "fsl,imx7s", @@ -108,6 +115,7 @@ static const char *const imx7d_dt_compat[] __initconst = { }; DT_MACHINE_START(IMX7D, "Freescale i.MX7 Dual (Device Tree)") + .map_io = imx7d_map_io, .init_irq = imx7d_init_irq, .init_machine = imx7d_init_machine, .init_late = imx7d_init_late, diff --git a/arch/arm/mach-imx/mx7.h b/arch/arm/mach-imx/mx7.h new file mode 100644 index 000000000000..afbeaef12d07 --- /dev/null +++ b/arch/arm/mach-imx/mx7.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * * This program is free software; you can redistribute it and/or modify + * * it under the terms of the GNU General Public License version 2 as + * * published by the Free Software Foundation. + * */ + +#ifndef __ASM_ARCH_MX7_IOMAP_H__ +#define __ASM_ARCH_MX7_IOMAP_H__ + +#define MX7D_IO_P2V(x) IMX_IO_P2V(x) +#define MX7D_IO_ADDRESS(x) IOMEM(MX7D_IO_P2V(x)) + +#define MX7D_LPSR_BASE_ADDR 0x30270000 +#define MX7D_LPSR_SIZE 0x10000 +#define MX7D_CCM_BASE_ADDR 0x30380000 +#define MX7D_CCM_SIZE 0x10000 +#define MX7D_IOMUXC_BASE_ADDR 0x30330000 +#define MX7D_IOMUXC_SIZE 0x10000 +#define MX7D_IOMUXC_GPR_BASE_ADDR 0x30340000 +#define MX7D_IOMUXC_GPR_SIZE 0x10000 +#define MX7D_ANATOP_BASE_ADDR 0x30360000 +#define MX7D_ANATOP_SIZE 0x10000 +#define MX7D_SNVS_BASE_ADDR 0x30370000 +#define MX7D_SNVS_SIZE 0x10000 +#define MX7D_GPC_BASE_ADDR 0x303a0000 +#define MX7D_GPC_SIZE 0x10000 +#define MX7D_SRC_BASE_ADDR 0x30390000 +#define MX7D_SRC_SIZE 0x10000 +#define MX7D_DDRC_BASE_ADDR 0x307a0000 +#define MX7D_DDRC_SIZE 0x10000 +#define MX7D_DDRC_PHY_BASE_ADDR 0x30790000 +#define MX7D_DDRC_PHY_SIZE 0x10000 +#define MX7D_AIPS1_BASE_ADDR 0x30000000 +#define MX7D_AIPS1_SIZE 0x400000 +#define MX7D_AIPS2_BASE_ADDR 0x30400000 +#define MX7D_AIPS2_SIZE 0x400000 +#define MX7D_AIPS3_BASE_ADDR 0x30900000 +#define MX7D_AIPS3_SIZE 0x300000 +#define MX7D_GIC_BASE_ADDR 0x31000000 +#define MX7D_GIC_SIZE 0x100000 + +#define TT_ATTRIB_NON_CACHEABLE_1M 0x802 +#define MX7_IRAM_TLB_SIZE 0x4000 +#define MX7_SUSPEND_OCRAM_SIZE 0x1000 +#define MX7_CPUIDLE_OCRAM_ADDR_OFFSET 0x1000 +#define MX7_CPUIDLE_OCRAM_SIZE 0x1000 +#define MX7_BUSFREQ_OCRAM_ADDR_OFFSET 0x2000 +#define MX7_BUSFREQ_OCRAM_SIZE 0x1000 + +#endif diff --git a/arch/arm/mach-imx/mxc.h b/arch/arm/mach-imx/mxc.h index 2bfd2d59b4a6..33d560c6f0b1 100644 --- a/arch/arm/mach-imx/mxc.h +++ b/arch/arm/mach-imx/mxc.h @@ -33,7 +33,9 @@ #define MXC_CPU_IMX7D 0x72 #define MXC_CPU_IMX7ULP 0xff +#define IMX_DDR_TYPE_DDR3 0 #define IMX_DDR_TYPE_LPDDR2 1 +#define IMX_DDR_TYPE_LPDDR3 2 #ifndef __ASSEMBLY__ extern unsigned int __mxc_cpu_type; diff --git a/arch/arm/mach-imx/pm-imx7.c b/arch/arm/mach-imx/pm-imx7.c new file mode 100644 index 000000000000..a1e3084798f5 --- /dev/null +++ b/arch/arm/mach-imx/pm-imx7.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "hardware.h" + +#define M4_OCRAMS_RESERVED_SIZE 0xc + +extern unsigned long iram_tlb_base_addr; +extern unsigned long iram_tlb_phys_addr; + +static struct map_desc imx7_pm_io_desc[] __initdata = { + imx_map_entry(MX7D, AIPS1, MT_DEVICE), + imx_map_entry(MX7D, AIPS2, MT_DEVICE), + imx_map_entry(MX7D, AIPS3, MT_DEVICE), +}; + +static const char * const low_power_ocram_match[] __initconst = { + "fsl,lpm-sram", + NULL +}; + +static struct map_desc iram_tlb_io_desc __initdata = { + /* .virtual and .pfn are run-time assigned */ + .length = SZ_1M, + .type = MT_MEMORY_RWX_NONCACHED, +}; + +static int __init imx7_dt_find_lpsram(unsigned long node, const char *uname, + int depth, void *data) +{ + unsigned long lpram_addr; + const __be32 *prop = of_get_flat_dt_prop(node, "reg", NULL); + + if (of_flat_dt_match(node, low_power_ocram_match)) { + if (!prop) + return -EINVAL; + + lpram_addr = be32_to_cpup(prop); + + /* We need to create a 1M page table entry. */ + iram_tlb_io_desc.virtual = IMX_IO_P2V(lpram_addr & 0xFFF00000); + iram_tlb_io_desc.pfn = __phys_to_pfn(lpram_addr & 0xFFF00000); + iram_tlb_phys_addr = lpram_addr; + iram_tlb_base_addr = IMX_IO_P2V(lpram_addr); + iotable_init(&iram_tlb_io_desc, 1); + } + + return 0; +} + +void __init imx7_pm_map_io(void) +{ + unsigned long i, j; + + iotable_init(imx7_pm_io_desc, ARRAY_SIZE(imx7_pm_io_desc)); + /* + * Get the address of IRAM or OCRAM to be used by the low + * power code from the device tree. + */ + WARN_ON(of_scan_flat_dt(imx7_dt_find_lpsram, NULL)); + + /* Return if no IRAM space is allocated for suspend/resume code. */ + if (!iram_tlb_base_addr) { + pr_warn("No valid ocram available for suspend/resume!\n"); + return; + } + + /* TODO: Handle M4 in TEE? */ + /* Set all entries to 0 except first 3 words reserved for M4. */ + memset((void *)(iram_tlb_base_addr + M4_OCRAMS_RESERVED_SIZE), + 0, MX7_IRAM_TLB_SIZE - M4_OCRAMS_RESERVED_SIZE); + + /* + * Make sure the IRAM virtual address has a mapping in the IRAM + * page table. + * + * Only use the top 12 bits [31-20] when storing the physical + * address in the page table as only these bits are required + * for 1M mapping. + */ + j = ((iram_tlb_base_addr >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + (iram_tlb_phys_addr & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; + + /* + * Make sure the AIPS1 virtual address has a mapping in the + * IRAM page table. + */ + for (i = 0; i < 4; i++) { + j = ((IMX_IO_P2V(MX7D_AIPS1_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + ((MX7D_AIPS1_BASE_ADDR + i * 0x100000) & 0xFFF00000) | + TT_ATTRIB_NON_CACHEABLE_1M; + } + + /* + * Make sure the AIPS2 virtual address has a mapping in the + * IRAM page table. + */ + for (i = 0; i < 4; i++) { + j = ((IMX_IO_P2V(MX7D_AIPS2_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + ((MX7D_AIPS2_BASE_ADDR + i * 0x100000) & 0xFFF00000) | + TT_ATTRIB_NON_CACHEABLE_1M; + } + + /* + * Make sure the AIPS3 virtual address has a mapping + * in the IRAM page table. + */ + for (i = 0; i < 4; i++) { + j = ((IMX_IO_P2V(MX7D_AIPS3_BASE_ADDR + i * 0x100000) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + ((MX7D_AIPS3_BASE_ADDR + i * 0x100000) & 0xFFF00000) | + TT_ATTRIB_NON_CACHEABLE_1M; + } + + /* + * Make sure the GIC virtual address has a mapping in the + * IRAM page table. + */ + j = ((IMX_IO_P2V(MX7D_GIC_BASE_ADDR) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + (MX7D_GIC_BASE_ADDR & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; +} diff --git a/arch/arm/mach-imx/smp_wfe.S b/arch/arm/mach-imx/smp_wfe.S new file mode 100644 index 000000000000..08894bb39c4d --- /dev/null +++ b/arch/arm/mach-imx/smp_wfe.S @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "hardware.h" + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + .endm + +#ifdef CONFIG_SMP + .align 3 + +ENTRY(imx7_smp_wfe) + push {r4 - r11, lr} + + dsb + isb + + disable_l1_dcache + + isb + + /* Turn off SMP bit. */ + mrc p15, 0, r8, c1, c0, 1 + bic r8, r8, #0x40 + mcr p15, 0, r8, c1, c0, 1 + + isb + /* Set flag of entering WFE. */ + mov r7, #0xff + lsl r7, r7, r0 + mov r6, #SCU_PM_DORMANT + lsl r6, r6, r0 + ldr r8, [r1, #0x4] + bic r8, r8, r7 + orr r6, r6, r8 + str r6, [r1, #0x4] + +go_back_wfe: + wfe + + /* Offset 0x0 stores busfeq done flag */ + ldr r6, [r1] + cmp r6, #1 + beq go_back_wfe + + /* Turn ON SMP bit. */ + mrc p15, 0, r8, c1, c0, 1 + orr r8, r8, #0x40 + mcr p15, 0, r8, c1, c0, 1 + + isb + /* Enable L1 data cache. */ + mrc p15, 0, r8, c1, c0, 0 + orr r8, r8, #0x4 + mcr p15, 0, r8, c1, c0, 0 + isb + + /* Set flag of exiting WFE. */ + mov r7, #0xff + lsl r7, r7, r0 + mov r6, #SCU_PM_NORMAL + lsl r6, r6, r0 + ldr r8, [r1, #0x4] + bic r8, r8, r7 + orr r6, r6, r8 + str r6, [r1, #0x4] + + /* Pop all saved registers. */ + pop {r4 - r11, lr} + mov pc, lr + .ltorg +ENDPROC(imx7_smp_wfe) +#endif From ee23850e61bcb7f8da0d96c428e42c56b1320ab5 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 17 Apr 2019 10:11:31 +0800 Subject: [PATCH 04/81] ARM: imx: add i.MX6Q bus-freq support Add i.MX6Q bus-freq support. Signed-off-by: Anson Huang --- arch/arm/mach-imx/Makefile | 6 +- arch/arm/mach-imx/busfreq-imx.c | 150 ++++ arch/arm/mach-imx/busfreq_ddr3.c | 236 +++++- arch/arm/mach-imx/busfreq_lpddr2.c | 23 +- arch/arm/mach-imx/common.h | 1 + arch/arm/mach-imx/ddr3_freq_imx6.S | 1103 +++++++++++++++++++++++++ arch/arm/mach-imx/hardware.h | 1 + arch/arm/mach-imx/lpddr2_freq_imx6q.S | 765 +++++++++++++++++ arch/arm/mach-imx/mach-imx6q.c | 2 + arch/arm/mach-imx/mx6.h | 51 ++ arch/arm/mach-imx/mxc.h | 12 + arch/arm/mach-imx/platsmp.c | 2 +- arch/arm/mach-imx/pm-imx6.c | 136 +++ arch/arm/mach-imx/smp_wfe_imx6.S | 186 +++++ 14 files changed, 2652 insertions(+), 22 deletions(-) create mode 100644 arch/arm/mach-imx/ddr3_freq_imx6.S create mode 100644 arch/arm/mach-imx/lpddr2_freq_imx6q.S create mode 100644 arch/arm/mach-imx/mx6.h create mode 100644 arch/arm/mach-imx/smp_wfe_imx6.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 8703165f4d64..a3db687d8c07 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -78,7 +78,8 @@ 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_IMX6Q) += mach-imx6q.o ddr3_freq_imx6.o smp_wfe_imx6.o busfreq_lpddr2.o \ + lpddr2_freq_imx6q.o obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o @@ -90,8 +91,11 @@ obj-$(CONFIG_SOC_IMX7ULP) += mach-imx7ulp.o pm-imx7ulp.o obj-y += busfreq-imx.o busfreq_ddr3.o AFLAGS_smp_wfe.o :=-Wa,-march=armv7-a +AFLAGS_smp_wfe_imx6.o :=-Wa,-march=armv7-a AFLAGS_ddr3_freq_imx7d.o :=-Wa,-march=armv7-a AFLAGS_lpddr3_freq_imx.o :=-Wa,-march=armv7-a +AFLAGS_ddr3_freq_imx6.o :=-Wa,-march=armv7-a +AFLAGS_lpddr2_freq_imx6q.o :=-Wa,-march=armv7-a ifeq ($(CONFIG_SUSPEND),y) AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index 976ba3ca7f29..5a43b1521917 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -108,6 +108,16 @@ static struct clk *pll_dram; static struct clk *ahb_sel_clk; static struct clk *axi_clk; +static struct clk *pll3_clk; +static struct clk *pll2_400_clk; +static struct clk *periph_clk2_sel_clk; +static struct clk *periph_pre_clk; +static struct clk *pll2_200_clk; +static struct clk *periph_clk; +static struct clk *mmdc_clk; +static struct clk *periph_clk2_clk; +static struct clk *pll2_bus_clk; + static struct delayed_work low_bus_freq_handler; static struct delayed_work bus_freq_daemon; @@ -143,6 +153,92 @@ int unregister_busfreq_notifier(struct notifier_block *nb) } EXPORT_SYMBOL(unregister_busfreq_notifier); +static void enter_lpm_imx6_smp(void) +{ + if (audio_bus_count) { + /* Need to ensure that PLL2_PFD_400M is kept ON. */ + clk_prepare_enable(pll2_400_clk); + if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) + busfreq_func.update(LOW_AUDIO_CLK); + else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2) + busfreq_func.update(HIGH_AUDIO_CLK); + /* Make sure periph clk's parent also got updated */ + clk_set_parent(periph_clk2_sel_clk, pll3_clk); + if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) + clk_set_parent(periph_pre_clk, pll2_200_clk); + else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2) + clk_set_parent(periph_pre_clk, pll2_400_clk); + clk_set_parent(periph_clk, periph_pre_clk); + + /* + * As periph_pre_clk's parent is not changed from + * high mode to audio mode on lpddr2, the clk framework + * will not update its children's freq, but we + * change the mmdc_ch0_axi podf in asm code, so here + * need to update mmdc rate to make sure clk + * tree is right, although it will not do any + * change to hardware. Calling get_rate will only call + * the .rate_recalc which is all we need. + */ + if (high_bus_freq_mode && mmdc_clk) + if (ddr_type == IMX_DDR_TYPE_LPDDR2) + clk_get_rate(mmdc_clk); + + audio_bus_freq_mode = 1; + low_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_AUDIO; + } else { + busfreq_func.update(LPAPM_CLK); + + /* Make sure periph clk's parent also got updated */ + clk_set_parent(periph_clk2_sel_clk, osc_clk); + /* Set periph_clk parent to OSC via periph_clk2_sel */ + clk_set_parent(periph_clk, periph_clk2_clk); + if (audio_bus_freq_mode) + clk_disable_unprepare(pll2_400_clk); + low_bus_freq_mode = 1; + audio_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_LOW; + } +} + +static void exit_lpm_imx6_smp(void) +{ + struct clk *periph_clk_parent; + + if (cpu_is_imx6q() && ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) + periph_clk_parent = pll2_bus_clk; + else + periph_clk_parent = pll2_400_clk; + + clk_prepare_enable(pll2_400_clk); + + busfreq_func.update(ddr_normal_rate); + + /* Make sure periph clk's parent also got updated */ + clk_set_parent(periph_clk2_sel_clk, pll3_clk); + clk_set_parent(periph_pre_clk, periph_clk_parent); + clk_set_parent(periph_clk, periph_pre_clk); + + /* + * As periph_pre_clk's parent is not changed from + * high mode to audio mode on lpddr2, the clk framework + * will not update its children's freq, but we + * change the mmdc_ch0_axi podf in asm code, so here + * need to update mmdc rate to make sure clk + * tree is right, although it will not do any + * change to hardware. Calling get_rate will only call + * the .rate_recalc which is all we need. + */ + if (audio_bus_freq_mode && mmdc_clk) + if (ddr_type == IMX_DDR_TYPE_LPDDR2) + clk_get_rate(mmdc_clk); + + clk_disable_unprepare(pll2_400_clk); + if (audio_bus_freq_mode) + clk_disable_unprepare(pll2_400_clk); +} + static void enter_lpm_imx7d(void) { /* @@ -200,6 +296,9 @@ static void exit_lpm_imx7d(void) static void reduce_bus_freq(void) { + if (cpu_is_imx6()) + clk_prepare_enable(pll3_clk); + if (audio_bus_count && (low_bus_freq_mode || ultra_low_bus_freq_mode)) busfreq_notify(LOW_BUSFREQ_EXIT); else if (!audio_bus_count) @@ -207,10 +306,15 @@ static void reduce_bus_freq(void) if (cpu_is_imx7d()) enter_lpm_imx7d(); + else if (cpu_is_imx6q()) + enter_lpm_imx6_smp(); med_bus_freq_mode = 0; high_bus_freq_mode = 0; + if (cpu_is_imx6()) + clk_disable_unprepare(pll3_clk); + if (audio_bus_freq_mode) dev_dbg(busfreq_dev, "Bus freq set to audio mode. Count: high %d, med %d, audio %d\n", @@ -297,8 +401,13 @@ static int set_high_bus_freq(int high_bus_freq) if (low_bus_freq_mode || ultra_low_bus_freq_mode) busfreq_notify(LOW_BUSFREQ_EXIT); + if (cpu_is_imx6()) + clk_prepare_enable(pll3_clk); + if (cpu_is_imx7d()) exit_lpm_imx7d(); + else if (cpu_is_imx6q()) + exit_lpm_imx6_smp(); high_bus_freq_mode = 1; med_bus_freq_mode = 0; @@ -306,6 +415,9 @@ static int set_high_bus_freq(int high_bus_freq) audio_bus_freq_mode = 0; cur_bus_freq_mode = BUS_FREQ_HIGH; + if (cpu_is_imx6()) + clk_disable_unprepare(pll3_clk); + if (high_bus_freq_mode) dev_dbg(busfreq_dev, "Bus freq set to high mode. Count: high %d, med %d, audio %d\n", @@ -612,6 +724,35 @@ static int busfreq_probe(struct platform_device *pdev) if (!ddr_freq_change_iram_base) return -ENOMEM; + if (cpu_is_imx6()) { + osc_clk = devm_clk_get(&pdev->dev, "osc"); + pll2_400_clk = devm_clk_get(&pdev->dev, "pll2_pfd2_396m"); + pll2_200_clk = devm_clk_get(&pdev->dev, "pll2_198m"); + pll2_bus_clk = devm_clk_get(&pdev->dev, "pll2_bus"); + pll3_clk = devm_clk_get(&pdev->dev, "pll3_usb_otg"); + periph_clk = devm_clk_get(&pdev->dev, "periph"); + periph_pre_clk = devm_clk_get(&pdev->dev, "periph_pre"); + periph_clk2_clk = devm_clk_get(&pdev->dev, "periph_clk2"); + periph_clk2_sel_clk = devm_clk_get(&pdev->dev, + "periph_clk2_sel"); + if (IS_ERR(osc_clk) || IS_ERR(pll2_400_clk) + || IS_ERR(pll2_200_clk) || IS_ERR(pll2_bus_clk) + || IS_ERR(pll3_clk) || IS_ERR(periph_clk) + || IS_ERR(periph_pre_clk) || IS_ERR(periph_clk2_clk) + || IS_ERR(periph_clk2_sel_clk)) { + dev_err(busfreq_dev, + "%s: failed to get busfreq clk\n", __func__); + return -EINVAL; + } + } + + if (cpu_is_imx6q()) { + mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); + if (IS_ERR(mmdc_clk)) { + mmdc_clk = NULL; + } + } + if (cpu_is_imx7d()) { osc_clk = devm_clk_get(&pdev->dev, "osc"); axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel"); @@ -701,6 +842,15 @@ static int busfreq_probe(struct platform_device *pdev) } busfreq_func.init = &init_ddrc_ddr_settings; busfreq_func.update = &update_ddr_freq_imx_smp; + } else if (cpu_is_imx6q()) { + ddr_type = imx_mmdc_get_ddr_type(); + if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) { + busfreq_func.init = &init_mmdc_ddr3_settings_imx6_smp; + busfreq_func.update = &update_ddr_freq_imx_smp; + } else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2) { + busfreq_func.init = &init_mmdc_lpddr2_settings_mx6q; + busfreq_func.update = &update_lpddr2_freq_smp; + } } if (busfreq_func.init) diff --git a/arch/arm/mach-imx/busfreq_ddr3.c b/arch/arm/mach-imx/busfreq_ddr3.c index 3bd03dab05e6..612dd0be512f 100644 --- a/arch/arm/mach-imx/busfreq_ddr3.c +++ b/arch/arm/mach-imx/busfreq_ddr3.c @@ -73,19 +73,30 @@ struct imx6_busfreq_info { } __aligned(8); /* DDR settings */ +static unsigned long (*iram_ddr_settings)[2]; +static unsigned long (*normal_mmdc_settings)[2]; static unsigned long (*iram_iomux_settings)[2]; +static void __iomem *mmdc_base; +static void __iomem *iomux_base; static void __iomem *gic_dist_base; +static int ddr_settings_size; +static int iomux_settings_size; static int curr_ddr_rate; void (*imx7d_change_ddr_freq)(u32 freq) = NULL; extern void imx7d_ddr3_freq_change(u32 freq); extern void imx_lpddr3_freq_change(u32 freq); +void (*mx6_change_ddr_freq)(u32 freq, void *ddr_settings, + bool dll_mode, void *iomux_offsets) = NULL; + extern unsigned int ddr_normal_rate; extern int low_bus_freq_mode; extern int audio_bus_freq_mode; +extern void mx6_ddr3_freq_change(u32 freq, void *ddr_settings, + bool dll_mode, void *iomux_offsets); extern unsigned long save_ttbr1(void); extern void restore_ttbr1(unsigned long ttbr1); @@ -94,7 +105,10 @@ extern unsigned long ddr_freq_change_iram_base; extern unsigned long ddr_freq_change_total_size; extern unsigned long iram_tlb_phys_addr; +extern unsigned long mx6_ddr3_freq_change_start asm("mx6_ddr3_freq_change_start"); +extern unsigned long mx6_ddr3_freq_change_end asm("mx6_ddr3_freq_change_end"); #ifdef CONFIG_SMP +static unsigned long wfe_freq_change_iram_base; volatile u32 *wait_for_ddr_freq_update; static unsigned int online_cpus; static u32 *irqs_used; @@ -105,9 +119,41 @@ extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done); extern void imx7_smp_wfe(u32 cpuid, u32 ocram_base); extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start"); extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end"); -extern void __iomem *imx_scu_base; +extern void __iomem *scu_base; #endif +unsigned long ddr3_dll_mx6q[][2] = { + {0x0c, 0x0}, + {0x10, 0x0}, + {0x1C, 0x04088032}, + {0x1C, 0x0408803a}, + {0x1C, 0x08408030}, + {0x1C, 0x08408038}, + {0x818, 0x0}, + {0x18, 0x0}, +}; + +unsigned long ddr3_calibration[][2] = { + {0x83c, 0x0}, + {0x840, 0x0}, + {0x483c, 0x0}, + {0x4840, 0x0}, + {0x848, 0x0}, + {0x4848, 0x0}, + {0x850, 0x0}, + {0x4850, 0x0}, +}; + +unsigned long iomux_offsets_mx6q[][2] = { + {0x5A8, 0x0}, + {0x5B0, 0x0}, + {0x524, 0x0}, + {0x51C, 0x0}, + {0x518, 0x0}, + {0x50C, 0x0}, + {0x5B8, 0x0}, + {0x5C0, 0x0}, +}; int can_change_ddr_freq(void) { return 1; @@ -149,10 +195,13 @@ int update_ddr_freq_imx_smp(int ddr_rate) { int me = 0; unsigned long ttbr1; + bool dll_off = false; + int i; #ifdef CONFIG_SMP unsigned int reg = 0; int cpu = 0; #endif + int mode = get_bus_freq_mode(); if (!can_change_ddr_freq()) return -1; @@ -162,6 +211,22 @@ int update_ddr_freq_imx_smp(int ddr_rate) printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate); + if (cpu_is_imx6()) { + if ((mode == BUS_FREQ_LOW) || (mode == BUS_FREQ_AUDIO)) + dll_off = true; + + iram_ddr_settings[0][0] = ddr_settings_size; + iram_iomux_settings[0][0] = iomux_settings_size; + if (ddr_rate == ddr_normal_rate) { + for (i = 0; i < iram_ddr_settings[0][0]; i++) { + iram_ddr_settings[i + 1][0] = + normal_mmdc_settings[i][0]; + iram_ddr_settings[i + 1][1] = + normal_mmdc_settings[i][1]; + } + } + } + /* ensure that all Cores are in WFE. */ local_irq_disable(); @@ -176,6 +241,8 @@ int update_ddr_freq_imx_smp(int ddr_rate) for_each_online_cpu(cpu) { if (cpu_is_imx7d()) reg = *(wait_for_ddr_freq_update + 1); + else if (cpu_is_imx6()) + reg = __raw_readl(scu_base + 0x08); if (reg & (0x02 << (cpu * 8))) not_exited_busfreq = true; @@ -189,6 +256,8 @@ int update_ddr_freq_imx_smp(int ddr_rate) dsb(); if (cpu_is_imx7d()) online_cpus = *(wait_for_ddr_freq_update + 1); + else if (cpu_is_imx6()) + online_cpus = readl_relaxed(scu_base + 0x08); for_each_online_cpu(cpu) { *((char *)(&online_cpus) + (u8)cpu) = 0x02; if (cpu != me) { @@ -204,6 +273,8 @@ int update_ddr_freq_imx_smp(int ddr_rate) if (cpu_is_imx7d()) reg = *(wait_for_ddr_freq_update + 1); + else if (cpu_is_imx6()) + reg = readl_relaxed(scu_base + 0x08); reg |= (0x02 << (me * 8)); if (reg == online_cpus) break; @@ -213,11 +284,17 @@ int update_ddr_freq_imx_smp(int ddr_rate) /* Ensure iram_tlb_phys_addr is flushed to DDR. */ __cpuc_flush_dcache_area(&iram_tlb_phys_addr, sizeof(iram_tlb_phys_addr)); + if (cpu_is_imx6()) + outer_clean_range(__pa(&iram_tlb_phys_addr), + __pa(&iram_tlb_phys_addr + 1)); ttbr1 = save_ttbr1(); /* Now we can change the DDR frequency. */ if (cpu_is_imx7d()) imx7d_change_ddr_freq(ddr_rate); + else if (cpu_is_imx6()) + mx6_change_ddr_freq(ddr_rate, iram_ddr_settings, + dll_off, iram_iomux_settings); restore_ttbr1(ttbr1); curr_ddr_rate = ddr_rate; @@ -308,3 +385,160 @@ int init_ddrc_ddr_settings(struct platform_device *busfreq_pdev) return 0; } + +int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *busfreq_pdev) +{ + int i; + struct device_node *node; + unsigned long ddr_code_size; + unsigned long wfe_code_size = 0; +#ifdef CONFIG_SMP + u32 cpu; + struct device *dev = &busfreq_pdev->dev; + int err; + struct irq_data *d; +#endif + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc-combine"); + if (!node) { + printk(KERN_ERR "failed to find imx6q-mmdc device tree data!\n"); + return -EINVAL; + } + mmdc_base = of_iomap(node, 0); + WARN(!mmdc_base, "unable to map mmdc registers\n"); + + node = NULL; + if (cpu_is_imx6q()) + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc"); + if (!node) { + printk(KERN_ERR "failed to find imx6q-iomux device tree data!\n"); + return -EINVAL; + } + iomux_base = of_iomap(node, 0); + WARN(!iomux_base, "unable to map iomux registers\n"); + + node = NULL; + node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-gic"); + if (!node) { + printk(KERN_ERR "failed to find imx6q-a9-gic device tree data!\n"); + return -EINVAL; + } + gic_dist_base = of_iomap(node, 0); + WARN(!gic_dist_base, "unable to map gic dist registers\n"); + + if (cpu_is_imx6q()) + ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6q) + + ARRAY_SIZE(ddr3_calibration); + + normal_mmdc_settings = kmalloc((ddr_settings_size * 8), GFP_KERNEL); + if (cpu_is_imx6q()) { + memcpy(normal_mmdc_settings, ddr3_dll_mx6q, + sizeof(ddr3_dll_mx6q)); + memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6q)), + ddr3_calibration, sizeof(ddr3_calibration)); + } + /* store the original DDR settings at boot. */ + for (i = 0; i < ddr_settings_size; i++) { + /* + * writes via command mode register cannot be read back. + * hence hardcode them in the initial static array. + * this may require modification on a per customer basis. + */ + if (normal_mmdc_settings[i][0] != 0x1C) + normal_mmdc_settings[i][1] = + readl_relaxed(mmdc_base + + normal_mmdc_settings[i][0]); + } + +#ifdef CONFIG_SMP + irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(), + GFP_KERNEL); + + for_each_online_cpu(cpu) { + int irq; + + /* + * set up a reserved interrupt to get all + * the active cores into a WFE state + * before changing the DDR frequency. + */ + irq = platform_get_irq(busfreq_pdev, cpu); + err = request_irq(irq, wait_in_wfe_irq, + IRQF_PERCPU, "mmdc_1", NULL); + if (err) { + dev_err(dev, + "Busfreq:request_irq failed %d, err = %d\n", + irq, err); + return err; + } + err = irq_set_affinity(irq, cpumask_of(cpu)); + if (err) { + dev_err(dev, + "Busfreq: Cannot set irq affinity irq=%d,\n", + irq); + return err; + } + d = irq_get_irq_data(irq); + irqs_used[cpu] = d->hwirq + 32; + } +#endif + iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6q); + + ddr_code_size = (&mx6_ddr3_freq_change_end - + &mx6_ddr3_freq_change_start) * 4; + + mx6_change_ddr_freq = (void *)fncpy((void *)ddr_freq_change_iram_base, + &mx6_ddr3_freq_change, ddr_code_size); + + /* + * Store the size of the array in iRAM also, + * increase the size by 8 bytes. + */ + iram_iomux_settings = (void *)(ddr_freq_change_iram_base + + ddr_code_size); + iram_ddr_settings = iram_iomux_settings + (iomux_settings_size * 8) + 8; +#ifdef CONFIG_SMP + wfe_freq_change_iram_base = (unsigned long)((u32 *)iram_ddr_settings + + (ddr_settings_size * 8) + 8); + + if (wfe_freq_change_iram_base & (FNCPY_ALIGN - 1)) + wfe_freq_change_iram_base += FNCPY_ALIGN - + ((uintptr_t)wfe_freq_change_iram_base % (FNCPY_ALIGN)); + + wfe_code_size = (&wfe_smp_freq_change_end - + &wfe_smp_freq_change_start) *4; + + wfe_change_ddr_freq = (void *)fncpy((void *)wfe_freq_change_iram_base, + &wfe_smp_freq_change, wfe_code_size); + + /* + * Store the variable used to communicate + * between cores in a non-cacheable IRAM area + */ + wait_for_ddr_freq_update = (u32 *)&iram_iomux_settings[0][1]; +#endif + + if ((ddr_code_size + wfe_code_size + (iomux_settings_size + + ddr_settings_size) * 8 + 16) + > ddr_freq_change_total_size) { + printk(KERN_ERR "Not enough memory for DDR Freq scale.\n"); + return EINVAL; + } + + if (cpu_is_imx6q()) { + /* store the IOMUX settings at boot. */ + for (i = 0; i < iomux_settings_size; i++) { + iomux_offsets_mx6q[i][1] = + readl_relaxed(iomux_base + + iomux_offsets_mx6q[i][0]); + iram_iomux_settings[i + 1][0] = + iomux_offsets_mx6q[i][0]; + iram_iomux_settings[i + 1][1] = + iomux_offsets_mx6q[i][1]; + } + } + + curr_ddr_rate = ddr_normal_rate; + + return 0; +} diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c index 2ef1806bbc35..1d2eda2389b9 100644 --- a/arch/arm/mach-imx/busfreq_lpddr2.c +++ b/arch/arm/mach-imx/busfreq_lpddr2.c @@ -56,8 +56,6 @@ void (*mx6_change_lpddr2_freq)(u32 ddr_freq, int bus_freq_mode) = NULL; extern unsigned int ddr_normal_rate; extern void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode); -extern void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode); -extern void imx6sll_lpddr2_freq_change(u32 freq, int bus_freq_mode); extern unsigned long save_ttbr1(void); extern void restore_ttbr1(unsigned long ttbr1); extern void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings); @@ -98,7 +96,7 @@ void (*wfe_change_lpddr2_freq)(u32 cpuid, u32 *ddr_freq_change_done); extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done); extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start"); extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end"); -extern void __iomem *imx_scu_base; +extern void __iomem *scu_base; static void __iomem *gic_dist_base; #endif @@ -163,19 +161,6 @@ int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) ddr_code_size = SZ_4K; - if (cpu_is_imx6sl()) - mx6_change_lpddr2_freq = (void *)fncpy( - (void *)ddr_freq_change_iram_base, - &mx6_lpddr2_freq_change, ddr_code_size); - if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) - mx6_change_lpddr2_freq = (void *)fncpy( - (void *)ddr_freq_change_iram_base, - &imx6_up_lpddr2_freq_change, ddr_code_size); - if (cpu_is_imx6sll()) - mx6_change_lpddr2_freq = (void *)fncpy( - (void *)ddr_freq_change_iram_base, - &imx6sll_lpddr2_freq_change, ddr_code_size); - curr_ddr_rate = ddr_normal_rate; return 0; @@ -214,7 +199,7 @@ int update_lpddr2_freq_smp(int ddr_rate) while (1) { bool not_exited_busfreq = false; for_each_online_cpu(cpu) { - reg = __raw_readl(imx_scu_base + 0x08); + reg = __raw_readl(scu_base + 0x08); if (reg & (0x02 << (cpu * 8))) not_exited_busfreq = true; } @@ -225,7 +210,7 @@ int update_lpddr2_freq_smp(int ddr_rate) wmb(); *wait_for_lpddr2_freq_update = 1; dsb(); - online_cpus = readl_relaxed(imx_scu_base + 0x08); + online_cpus = readl_relaxed(scu_base + 0x08); for_each_online_cpu(cpu) { *((char *)(&online_cpus) + (u8)cpu) = 0x02; if (cpu != me) { @@ -238,7 +223,7 @@ int update_lpddr2_freq_smp(int ddr_rate) /* Wait for the other active CPUs to idle */ while (1) { reg = 0; - reg = readl_relaxed(imx_scu_base + 0x08); + reg = readl_relaxed(scu_base + 0x08); reg |= (0x02 << (me * 8)); if (reg == online_cpus) break; diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 60add3295015..b6395f68d483 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -106,6 +106,7 @@ int imx_mmdc_get_ddr_type(void); int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode); void imx_busfreq_map_io(void); void imx7_pm_map_io(void); +void imx6_pm_map_io(void); void imx_cpu_die(unsigned int cpu); int imx_cpu_kill(unsigned int cpu); diff --git a/arch/arm/mach-imx/ddr3_freq_imx6.S b/arch/arm/mach-imx/ddr3_freq_imx6.S new file mode 100644 index 000000000000..e6f7f74f7d32 --- /dev/null +++ b/arch/arm/mach-imx/ddr3_freq_imx6.S @@ -0,0 +1,1103 @@ +/* + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "hardware.h" + +#define MMDC0_MDPDC 0x4 +#define MMDC0_MDCF0 0x0c +#define MMDC0_MDCF1 0x10 +#define MMDC0_MDMISC 0x18 +#define MMDC0_MDSCR 0x1c +#define MMDC0_MAARCR 0x400 +#define MMDC0_MAPSR 0x404 +#define MMDC0_MADPCR0 0x410 +#define MMDC0_MPZQHWCTRL 0x800 +#define MMDC1_MPZQHWCTRL 0x4800 +#define MMDC0_MPODTCTRL 0x818 +#define MMDC1_MPODTCTRL 0x4818 +#define MMDC0_MPDGCTRL0 0x83c +#define MMDC1_MPDGCTRL0 0x483c +#define MMDC0_MPMUR0 0x8b8 +#define MMDC1_MPMUR0 0x48b8 + +#define CCM_CBCDR 0x14 +#define CCM_CBCMR 0x18 +#define CCM_CSCMR1 0x1c +#define CCM_CDHIPR 0x48 + +#define L2_CACHE_SYNC 0x730 +#define PL310_AUX_CTRL 0x104 +#define PL310_DCACHE_LOCKDOWN_BASE 0x900 +#define PL310_AUX_16WAY_BIT 0x10000 +#define PL310_LOCKDOWN_NBREGS 8 +#define PL310_LOCKDOWN_SZREG 4 +#define PL310_8WAYS_MASK 0x00FF +#define PL310_16WAYS_UPPERMASK 0xFF00 + +#define IMX6QP_REVISION_ID 0x630100 +#define ANADIG_DIGPROG 0x260 + +.extern iram_tlb_phys_addr + +.globl mx6_ddr3_freq_change_start +.globl mx6_ddr3_freq_change_end + + .align 3 + + .macro is_mx6qp + + /* check if the SOC is i.MX6QP */ + ldr r0, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR) + ldr r1, [r0, #ANADIG_DIGPROG] + ldr r2, =IMX6QP_REVISION_ID + cmp r1, r2 + + .endm + + .macro switch_to_528MHz + + /* check if periph_clk_sel is already set */ + ldr r0, [r6, #CCM_CBCDR] + and r0, r0, #(1 << 25) + cmp r0, #(1 << 25) + beq set_ahb_podf_before_switch + + /* change periph_clk to be sourced from pll3_clk. */ + ldr r0, [r6, #CCM_CBCMR] + bic r0, r0, #(3 << 12) + str r0, [r6, #CCM_CBCMR] + + ldr r0, [r6, #CCM_CBCDR] + bic r0, r0, #(0x38 << 20) + str r0, [r6, #CCM_CBCDR] + + /* + * set the AHB dividers before the switch, + * don't change AXI clock divider, + * set the MMDC_DIV=1, AXI_DIV = 2, AHB_DIV=4, + */ + ldr r0, [r6, #CCM_CBCDR] + ldr r2, =0x3f1f00 + bic r0, r0, r2 + orr r0, r0, #0xd00 + orr r0, r0, #(1 << 16) + str r0, [r6, #CCM_CBCDR] + +wait_div_update528: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne wait_div_update528 + + /* now switch periph_clk to pll3_main_clk. */ + ldr r0, [r6, #CCM_CBCDR] + orr r0, r0, #(1 << 25) + str r0, [r6, #CCM_CBCDR] + +periph_clk_switch3: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne periph_clk_switch3 + + b switch_pre_periph_clk_528 + +set_ahb_podf_before_switch: + /* + * set the MMDC_DIV=1, AXI_DIV = 2, AHB_DIV=4, + */ + ldr r0, [r6, #CCM_CBCDR] + ldr r2, =0x3f1f00 + bic r0, r0, r2 + orr r0, r0, #0xd00 + orr r0, r0, #(1 << 16) + str r0, [r6, #CCM_CBCDR] + +wait_div_update528_1: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne wait_div_update528_1 + +switch_pre_periph_clk_528: + + /* now switch pre_periph_clk to PLL2_528MHz. */ + ldr r0, [r6, #CCM_CBCMR] + bic r0, r0, #(0xc << 16) + str r0, [r6, #CCM_CBCMR] + + /* now switch periph_clk back. */ + ldr r0, [r6, #CCM_CBCDR] + bic r0, r0, #(1 << 25) + str r0, [r6, #CCM_CBCDR] + +periph_clk_switch4: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne periph_clk_switch4 + + .endm + + .macro switch_to_400MHz + + /* check if periph_clk_sel is already set. */ + ldr r0, [r6, #CCM_CBCDR] + and r0, r0, #(1 << 25) + cmp r0, #(1 << 25) + beq set_ahb_podf_before_switch1 + + /* change periph_clk to be sourced from pll3_clk. */ + ldr r0, [r6, #CCM_CBCMR] + bic r0, r0, #(3 << 12) + str r0, [r6, #CCM_CBCMR] + + ldr r0, [r6, #CCM_CBCDR] + bic r0, r0, #(0x38 << 24) + str r0, [r6, #CCM_CBCDR] + + /* now switch periph_clk to pll3_main_clk. */ + ldr r0, [r6, #CCM_CBCDR] + orr r0, r0, #(1 << 25) + str r0, [r6, #CCM_CBCDR] + +periph_clk_switch5: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne periph_clk_switch5 + + b switch_pre_periph_clk_400 + +set_ahb_podf_before_switch1: + /* + * set the MMDC_DIV=1, AXI_DIV = 2, AHB_DIV=4, + */ + ldr r0, [r6, #CCM_CBCDR] + ldr r2, =0x3f1f00 + bic r0, r0, r2 + orr r0, r0, #(0x9 << 8) + orr r0, r0, #(1 << 16) + str r0, [r6, #CCM_CBCDR] + +wait_div_update400_1: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne wait_div_update400_1 + +switch_pre_periph_clk_400: + + /* now switch pre_periph_clk to PFD_400MHz. */ + ldr r0, [r6, #CCM_CBCMR] + bic r0, r0, #(0xc << 16) + orr r0, r0, #(0x4 << 16) + str r0, [r6, #CCM_CBCMR] + + /* now switch periph_clk back. */ + ldr r0, [r6, #CCM_CBCDR] + bic r0, r0, #(1 << 25) + str r0, [r6, #CCM_CBCDR] + +periph_clk_switch6: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne periph_clk_switch6 + + /* + * change AHB divider so that we are at 400/3=133MHz. + * don't change AXI clock divider. + * set the MMDC_DIV=1, AXI_DIV=2, AHB_DIV=3, + */ + ldr r0, [r6, #CCM_CBCDR] + ldr r2, =0x3f1f00 + bic r0, r0, r2 + orr r0, r0, #(0x9 << 8) + orr r0, r0, #(1 << 16) + str r0, [r6, #CCM_CBCDR] + +wait_div_update400_2: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne wait_div_update400_2 + + .endm + + .macro switch_to_50MHz + + /* check if periph_clk_sel is already set. */ + ldr r0, [r6, #CCM_CBCDR] + and r0, r0, #(1 << 25) + cmp r0, #(1 << 25) + beq switch_pre_periph_clk_50 + + /* + * set the periph_clk to be sourced from PLL2_PFD_200M + * change periph_clk to be sourced from pll3_clk. + * ensure PLL3 is the source and set the divider to 1. + */ + ldr r0, [r6, #CCM_CBCMR] + bic r0, r0, #(0x3 << 12) + str r0, [r6, #CCM_CBCMR] + + ldr r0, [r6, #CCM_CBCDR] + bic r0, r0, #(0x38 << 24) + str r0, [r6, #CCM_CBCDR] + + /* now switch periph_clk to pll3_main_clk. */ + ldr r0, [r6, #CCM_CBCDR] + orr r0, r0, #(1 << 25) + str r0, [r6, #CCM_CBCDR] + +periph_clk_switch_50: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne periph_clk_switch_50 + +switch_pre_periph_clk_50: + + /* now switch pre_periph_clk to PFD_200MHz. */ + ldr r0, [r6, #CCM_CBCMR] + orr r0, r0, #(0xc << 16) + str r0, [r6, #CCM_CBCMR] + + /* + * set the MMDC_DIV=4, AXI_DIV = 4, AHB_DIV=8, + */ + ldr r0, [r6, #CCM_CBCDR] + ldr r2, =0x3f1f00 + bic r0, r0, r2 + orr r0, r0, #(0x18 << 16) + orr r0, r0, #(0x3 << 16) + + /* + * if changing AHB divider remember to change + * the IPGPER divider too below. + */ + orr r0, r0, #0x1d00 + str r0, [r6, #CCM_CBCDR] + +wait_div_update_50: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne wait_div_update_50 + + /* now switch periph_clk back. */ + ldr r0, [r6, #CCM_CBCDR] + bic r0, r0, #(1 << 25) + str r0, [r6, #CCM_CBCDR] + +periph_clk_switch2: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne periph_clk_switch2 + + .endm + + .macro switch_to_24MHz + /* + * change the freq now try setting DDR to 24MHz. + * source it from the periph_clk2 ensure the + * periph_clk2 is sourced from 24MHz and the + * divider is 1. + */ + + ldr r0, [r6, #CCM_CBCMR] + bic r0, r0, #(0x3 << 12) + orr r0, r0, #(1 << 12) + str r0, [r6, #CCM_CBCMR] + + ldr r0, [r6, #CCM_CBCDR] + bic r0, r0, #(0x38 << 24) + str r0, [r6, #CCM_CBCDR] + + /* now switch periph_clk to 24MHz. */ + ldr r0, [r6, #CCM_CBCDR] + orr r0, r0, #(1 << 25) + str r0, [r6, #CCM_CBCDR] + +periph_clk_switch1: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne periph_clk_switch1 + + /* change all the dividers to 1. */ + ldr r0, [r6, #CCM_CBCDR] + ldr r2, =0x3f1f00 + bic r0, r0, r2 + orr r0, r0, #(1 << 8) + str r0, [r6, #CCM_CBCDR] + + /* Wait for the divider to change. */ +wait_div_update: + ldr r0, [r6, #CCM_CDHIPR] + cmp r0, #0 + bne wait_div_update + + .endm + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r11, lr} + + ldr r7, =v7_flush_kern_cache_all + mov lr, pc + mov pc, r7 + pop {r0 - r11, lr} + + /* disable d-cache */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + dsb + isb + + push {r0 - r11, lr} + + ldr r7, =v7_flush_kern_cache_all + mov lr, pc + mov pc, r7 + pop {r0 - r11, lr} + + .endm + +/* + * mx6_ddr3_freq_change + * + * idle the processor (eg, wait for interrupt). + * make sure DDR is in self-refresh. + * IRQs are already disabled. + */ +ENTRY(mx6_ddr3_freq_change) + +mx6_ddr3_freq_change_start: + stmfd sp!, {r4-r12} + + /* + * r5 -> mmdc_base + * r6 -> ccm_base + * r7 -> iomux_base + * r12 -> l2_base + */ + mov r4, r0 + mov r8, r1 + mov r9, r2 + mov r11, r3 + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* + * Need to flush and disable L1 before + * disabling L2, we need data to + * coherent. Flushing L1 pushes + * everyhting to L2. We sync L2 later, but + * it can still have dirty lines. + * While exiting, we need to enable L2 first + * and then L1. + */ + disable_l1_dcache + +#ifdef CONFIG_CACHE_L2X0 + /* + * Make sure the L2 buffers are drained. + * Sync operation on L2 drains the buffers. + */ + ldr r12, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + + /* Wait for background operations to complete. */ +wait_for_l2_to_idle: + ldr r1, [r12, #L2_CACHE_SYNC] + cmp r1, #0x0 + bne wait_for_l2_to_idle + + mov r1, #0x0 + str r1, [r12, #L2_CACHE_SYNC] + + dsb + isb + + ldr r1, [r12, #PL310_AUX_CTRL] + tst r1, #PL310_AUX_16WAY_BIT + mov r1, #PL310_8WAYS_MASK + orrne r1, #PL310_16WAYS_UPPERMASK + mov r6, #PL310_LOCKDOWN_NBREGS + add r5, r12, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r1, [r5], #PL310_LOCKDOWN_SZREG + str r1, [r5], #PL310_LOCKDOWN_SZREG + subs r6, r6, #1 + bne 1b +#endif + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + + /* Now switch the TTBR. */ + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + + ldr r5, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR) + ldr r6, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR) + ldr r7, =IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR) + + /* Read the Original MU delay value */ + ldr r1, [r5, #MMDC0_MPMUR0] + mov r10, r1, lsr #16 + ldr r1, =0x3ff + and r10, r10, r1 + + /* disable automatic power saving. */ + ldr r0, [r5, #MMDC0_MAPSR] + orr r0, r0, #0x01 + str r0, [r5, #MMDC0_MAPSR] + + /* disable MMDC power down timer. */ + ldr r0, [r5, #MMDC0_MDPDC] + bic r0, r0, #(0xff << 8) + str r0, [r5, #MMDC0_MDPDC] + + /* delay for a while */ + ldr r1, =4 +delay1: + ldr r2, =0 +cont1: + ldr r0, [r5, r2] + add r2, r2, #4 + cmp r2, #16 + bne cont1 + sub r1, r1, #1 + cmp r1, #0 + bgt delay1 + + /* set CON_REG */ + ldr r0, =0x8000 + str r0, [r5, #MMDC0_MDSCR] +poll_conreq_set_1: + ldr r0, [r5, #MMDC0_MDSCR] + and r0, r0, #(0x4 << 12) + cmp r0, #(0x4 << 12) + bne poll_conreq_set_1 + + /* + * if requested frequency is great than + * 300MHz, skip setting bypass adopt mode. + */ + ldr r1, =300000000 + cmp r4, r1 + bge 1f + + is_mx6qp + bne 1f + /* Switch to adopt mode, set MMDC0_MAARCR bit25~26 to 2b'01 */ + ldr r0, [r5, #MMDC0_MAARCR] + bic r0, r0, #(0x3 << 25) + orr r0, #(0x01 << 25) + str r0 , [r5, #MMDC0_MAARCR] +1: + ldr r0, =0x00008050 + str r0, [r5, #MMDC0_MDSCR] + ldr r0, =0x00008058 + str r0, [r5, #MMDC0_MDSCR] + + /* + * if requested frequency is greater than + * 300MHz go to DLL on mode. + */ + ldr r1, =300000000 + cmp r4, r1 + bge dll_on_mode + +dll_off_mode: + + /* if DLL is currently on, turn it off. */ + cmp r9, #1 + beq continue_dll_off_1 + + ldr r0, =0x00018031 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x00018039 + str r0, [r5, #MMDC0_MDSCR] + + ldr r1, =10 +delay1a: + ldr r2, =0 +cont1a: + ldr r0, [r5, r2] + add r2, r2, #4 + cmp r2, #16 + bne cont1a + sub r1, r1, #1 + cmp r1, #0 + bgt delay1a + +continue_dll_off_1: + /* set DVFS - enter self refresh mode */ + ldr r0, [r5, #MMDC0_MAPSR] + orr r0, r0, #(1 << 21) + str r0, [r5, #MMDC0_MAPSR] + + /* de-assert con_req */ + mov r0, #0x0 + str r0, [r5, #MMDC0_MDSCR] + +poll_dvfs_set_1: + ldr r0, [r5, #MMDC0_MAPSR] + and r0, r0, #(1 << 25) + cmp r0, #(1 << 25) + bne poll_dvfs_set_1 + + ldr r1, =24000000 + cmp r4, r1 + beq switch_freq_24 + + switch_to_50MHz + b continue_dll_off_2 + +switch_freq_24: + switch_to_24MHz + +continue_dll_off_2: + + /* set SBS - block ddr accesses */ + ldr r0, [r5, #MMDC0_MADPCR0] + orr r0, r0, #(1 << 8) + str r0, [r5, #MMDC0_MADPCR0] + + /* clear DVFS - exit from self refresh mode */ + ldr r0, [r5, #MMDC0_MAPSR] + bic r0, r0, #(1 << 21) + str r0, [r5, #MMDC0_MAPSR] + +poll_dvfs_clear_1: + ldr r0, [r5, #MMDC0_MAPSR] + and r0, r0, #(1 << 25) + cmp r0, #(1 << 25) + beq poll_dvfs_clear_1 + + /* if DLL was previously on, continue DLL off routine. */ + cmp r9, #1 + beq continue_dll_off_3 + + ldr r0, =0x00018031 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x00018039 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x08208030 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x08208038 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x00088032 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x0008803A + str r0, [r5, #MMDC0_MDSCR] + + /* delay for a while. */ + ldr r1, =4 +delay_1: + ldr r2, =0 +cont_1: + ldr r0, [r5, r2] + add r2, r2, #4 + cmp r2, #16 + bne cont_1 + sub r1, r1, #1 + cmp r1, #0 + bgt delay_1 + + ldr r0, [r5, #MMDC0_MDCF0] + bic r0, r0, #0xf + orr r0, r0, #0x3 + str r0, [r5, #MMDC0_MDCF0] + + ldr r0, [r5, #MMDC0_MDCF1] + bic r0, r0, #0x7 + orr r0, r0, #0x4 + str r0, [r5, #MMDC0_MDCF1] + + ldr r0, [r5, #MMDC0_MDMISC] + bic r0, r0, #(0x3 << 16) /* walat = 0x1 */ + orr r0, r0, #(0x1 << 16) + bic r0, r0, #(0x7 << 6) /* ralat = 0x2 */ + orr r0, r0, #(0x2 << 6) + str r0, [r5, #MMDC0_MDMISC] + + /* enable dqs pull down in the IOMUX. */ + ldr r1, [r11] + add r11, r11, #8 + ldr r2, =0x3028 +update_iomux: + ldr r0, [r11, #0x0] + ldr r3, [r7, r0] + bic r3, r3, r2 + orr r3, r3, #(0x3 << 12) + orr r3, r3, #0x28 + str r3, [r7, r0] + add r11, r11, #8 + sub r1, r1, #1 + cmp r1, #0 + bgt update_iomux + + /* ODT disabled. */ + ldr r0, =0x0 + ldr r2, =MMDC0_MPODTCTRL + str r0, [r5, r2] + ldr r2, =MMDC1_MPODTCTRL + str r0, [r5, r2] + + /* DQS gating disabled. */ + ldr r2, =MMDC0_MPDGCTRL0 + ldr r0, [r5, r2] + orr r0, r0, #(1 << 29) + str r0, [r5, r2] + + ldr r2, =MMDC1_MPDGCTRL0 + ldr r0, [r5, r2] + orr r0, r0, #(0x1 << 29) + str r0, [r5, r2] + + /* Add workaround for ERR005778.*/ + /* double the original MU_UNIT_DEL_NUM. */ + lsl r10, r10, #1 + + /* Bypass the automatic MU by setting the mu_byp_en */ + ldr r2, [r5, #MMDC0_MPMUR0] + orr r2, r2, #0x400 + orr r2, r2, r10 + str r2, [r5, #MMDC0_MPMUR0] + ldr r0, =MMDC1_MPMUR0 + str r2, [r5, r0] + + /* Now perform a force measure */ + ldr r0, [r5, #MMDC0_MPMUR0] + orr r0, r0, #0x800 + str r0, [r5, #MMDC0_MPMUR0] + ldr r2, =MMDC1_MPMUR0 + str r0, [r5, r2] + /* Wait for FRC_MSR to clear. */ +1: + ldr r0, [r5, #MMDC0_MPMUR0] + and r0, r0, #0x800 + ldr r1, [r5, r2] + and r1, r1, #0x800 + orr r0, r0, r1 + cmp r0, #0x0 + bne 1b + +continue_dll_off_3: + /* clear SBS - unblock accesses to DDR. */ + ldr r0, [r5, #MMDC0_MADPCR0] + bic r0, r0, #(0x1 << 8) + str r0, [r5, #MMDC0_MADPCR0] + + mov r0, #0x0 + str r0, [r5, #MMDC0_MDSCR] +poll_conreq_clear_1: + ldr r0, [r5, #MMDC0_MDSCR] + and r0, r0, #(0x4 << 12) + cmp r0, #(0x4 << 12) + beq poll_conreq_clear_1 + + b done + +dll_on_mode: + /* assert DVFS - enter self refresh mode. */ + ldr r0, [r5, #MMDC0_MAPSR] + orr r0, r0, #(1 << 21) + str r0, [r5, #MMDC0_MAPSR] + + /* de-assert CON_REQ. */ + mov r0, #0x0 + str r0, [r5, #MMDC0_MDSCR] + + /* poll DVFS ack. */ +poll_dvfs_set_2: + ldr r0, [r5, #MMDC0_MAPSR] + and r0, r0, #(1 << 25) + cmp r0, #(1 << 25) + bne poll_dvfs_set_2 + + ldr r1, =528000000 + cmp r4, r1 + beq switch_freq_528 + + switch_to_400MHz + + b continue_dll_on + +switch_freq_528: + switch_to_528MHz + +continue_dll_on: + + /* set SBS step-by-step mode. */ + ldr r0, [r5, #MMDC0_MADPCR0] + orr r0, r0, #( 1 << 8) + str r0, [r5, #MMDC0_MADPCR0] + + /* clear DVFS - exit self refresh mode. */ + ldr r0, [r5, #MMDC0_MAPSR] + bic r0, r0, #(1 << 21) + str r0, [r5, #MMDC0_MAPSR] + +poll_dvfs_clear_2: + ldr r0, [r5, #MMDC0_MAPSR] + and r0, r0, #(1 << 25) + cmp r0, #(1 << 25) + beq poll_dvfs_clear_2 + + /* if DLL is currently off, turn it back on. */ + cmp r9, #0 + beq update_calibration_only + + /* issue zq calibration command */ + ldr r0, [r5, #MMDC0_MPZQHWCTRL] + orr r0, r0, #0x3 + str r0, [r5, #MMDC0_MPZQHWCTRL] + ldr r2, =MMDC1_MPZQHWCTRL + str r0, [r5, r2] + + /* enable DQS gating. */ + ldr r2, =MMDC0_MPDGCTRL0 + ldr r0, [r5, r2] + bic r0, r0, #(1 << 29) + str r0, [r5, r2] + + ldr r2, =MMDC1_MPDGCTRL0 + ldr r0, [r5, r2] + bic r0, r0, #(1 << 29) + str r0, [r5, r2] + + /* force measure. */ + ldr r0, =0x00000800 + str r0, [r5, #MMDC0_MPMUR0] + ldr r2, =MMDC1_MPMUR0 + str r0, [r5, r2] + + /* Wait for FRC_MSR to clear. */ +1: + ldr r0, [r5, #MMDC0_MPMUR0] + and r0, r0, #0x800 + ldr r1, [r5, r2] + and r1, r1, #0x800 + orr r0, r0, r1 + cmp r0, #0x0 + bne 1b + + /* disable dqs pull down in the IOMUX. */ + ldr r1, [r11] + add r11, r11, #8 +update_iomux1: + ldr r0, [r11, #0x0] + ldr r3, [r11, #0x4] + str r3, [r7, r0] + add r11, r11, #8 + sub r1, r1, #1 + cmp r1, #0 + bgt update_iomux1 + + /* config MMDC timings to 528MHz. */ + ldr r9, [r8] + add r8, r8, #8 + ldr r0, [r8, #0x0] + ldr r3, [r8, #0x4] + str r3, [r5, r0] + add r8, r8, #8 + + ldr r0, [r8, #0x0] + ldr r3, [r8, #0x4] + str r3, [r5, r0] + add r8, r8, #8 + + /* configure ddr devices to dll on, odt. */ + ldr r0, =0x00048031 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x00048039 + str r0, [r5, #MMDC0_MDSCR] + + /* delay for while. */ + ldr r1, =4 +delay7: + ldr r2, =0 +cont7: + ldr r0, [r5, r2] + add r2, r2, #4 + cmp r2, #16 + bne cont7 + sub r1, r1, #1 + cmp r1, #0 + bgt delay7 + + /* reset dll. */ + ldr r0, =0x09408030 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x09408038 + str r0, [r5, #MMDC0_MDSCR] + + /* delay for while. */ + ldr r1, =100 +delay8: + ldr r2, =0 +cont8: + ldr r0, [r5, r2] + add r2, r2, #4 + cmp r2, #16 + bne cont8 + sub r1, r1, #1 + cmp r1, #0 + bgt delay8 + + ldr r0, [r8, #0x0] + ldr r3, [r8, #0x4] + str r3, [r5, r0] + add r8, r8, #8 + + ldr r0, [r8, #0x0] + ldr r3, [r8, #0x4] + str r3, [r5, r0] + add r8, r8, #8 + + ldr r0, =0x00428031 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x00428039 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, [r8, #0x0] + ldr r3, [r8, #0x4] + str r3, [r5, r0] + add r8, r8, #8 + + ldr r0, [r8, #0x0] + ldr r3, [r8, #0x4] + str r3, [r5, r0] + add r8, r8, #8 + + /* issue a zq command. */ + ldr r0, =0x04008040 + str r0, [r5, #MMDC0_MDSCR] + + ldr r0, =0x04008048 + str r0, [r5, #MMDC0_MDSCR] + + /* MMDC ODT enable. */ + ldr r0, [r8, #0x0] + ldr r3, [r8, #0x4] + str r3, [r5, r0] + add r8, r8, #8 + + ldr r2, =0x4818 + str r3, [r5, r2] + + /* delay for while. */ + ldr r1, =40 +delay15: + ldr r2, =0 +cont15: + ldr r0, [r5, r2] + add r2, r2, #4 + cmp r2, #16 + bne cont15 + sub r1, r1, #1 + cmp r1, #0 + bgt delay15 + + /* enable MMDC power down timer. */ + ldr r0, [r5, #MMDC0_MDPDC] + orr r0, r0, #(0x55 << 8) + str r0, [r5, #MMDC0_MDPDC] + + b update_calibration + +update_calibration_only: + ldr r1, [r8] + sub r1, r1, #7 + add r8, r8, #64 + b update_calib + +update_calibration: + /* write the new calibration values. */ + mov r1, r9 + sub r1, r1, #7 + +update_calib: + ldr r0, [r8, #0x0] + ldr r3, [r8, #0x4] + str r3, [r5, r0] + add r8, r8, #8 + sub r1, r1, #1 + cmp r1, #0 + bgt update_calib + + /* perform a force measurement. */ + ldr r0, =0x800 + str r0, [r5, #MMDC0_MPMUR0] + ldr r2, =MMDC1_MPMUR0 + str r0, [r5, r2] + + /* Wait for FRC_MSR to clear. */ +1: + ldr r0, [r5, #MMDC0_MPMUR0] + and r0, r0, #0x800 + ldr r1, [r5, r2] + and r1, r1, #0x800 + orr r0, r0, r1 + cmp r0, #0x0 + bne 1b + + /* clear SBS - unblock DDR accesses. */ + ldr r0, [r5, #MMDC0_MADPCR0] + bic r0, r0, #(1 << 8) + str r0, [r5, #MMDC0_MADPCR0] + + is_mx6qp + bne 3f + /* + * Switch back to adopt_bp mode, set MMDC0_MAARCR + * bit25~26 to 2b'10. + */ + ldr r0, [r5, #MMDC0_MAARCR] + bic r0, r0, #(0x3 << 25) + orr r0, r0, #(0x2 << 25) + str r0, [r5, #MMDC0_MAARCR] +3: + mov r0, #0x0 + str r0, [r5, #MMDC0_MDSCR] +poll_conreq_clear_2: + ldr r0, [r5, #MMDC0_MDSCR] + and r0, r0, #(0x4 << 12) + cmp r0, #(0x4 << 12) + beq poll_conreq_clear_2 + +done: + /* MMDC0_MAPSR adopt power down enable. */ + ldr r0, [r5, #MMDC0_MAPSR] + bic r0, r0, #0x01 + str r0, [r5, #MMDC0_MAPSR] + +#ifdef CONFIG_CACHE_L2X0 + ldr r1, [r12, #PL310_AUX_CTRL] + tst r1, #PL310_AUX_16WAY_BIT + mov r6, #PL310_LOCKDOWN_NBREGS + mov r1, #0x00 /* 8 ways mask */ + orrne r1, #0x0000 /* 16 ways mask */ + add r5, r12, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r1, [r5], #PL310_LOCKDOWN_SZREG + str r1, [r5], #PL310_LOCKDOWN_SZREG + subs r6, r6, #1 + bne 1b + + isb + dsb +#endif + + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + isb + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + isb + dsb + + /* restore registers */ + ldmfd sp!, {r4-r12} + mov pc, lr + + /* + * Add ltorg here to ensure that all + * literals are stored here and are + * within the text space. + */ + .ltorg +mx6_ddr3_freq_change_end: diff --git a/arch/arm/mach-imx/hardware.h b/arch/arm/mach-imx/hardware.h index ce8e8a39f051..468fe53600f5 100644 --- a/arch/arm/mach-imx/hardware.h +++ b/arch/arm/mach-imx/hardware.h @@ -102,6 +102,7 @@ #include "mx2x.h" #include "mx21.h" #include "mx27.h" +#include "mx6.h" #include "mx7.h" #define imx_map_entry(soc, name, _type) { \ diff --git a/arch/arm/mach-imx/lpddr2_freq_imx6q.S b/arch/arm/mach-imx/lpddr2_freq_imx6q.S new file mode 100644 index 000000000000..6c9aac07df16 --- /dev/null +++ b/arch/arm/mach-imx/lpddr2_freq_imx6q.S @@ -0,0 +1,765 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "hardware.h" + +#define CCM_CBCDR 0x14 +#define CCM_CBCMR 0x18 +#define CCM_CSCMR1 0x1c +#define CCM_CDHIPR 0x48 + +.globl mx6q_lpddr2_freq_change_start +.globl mx6q_lpddr2_freq_change_end + + .macro wait_for_ccm_handshake + /* wait for div update */ +1: + ldr r9, [r2, #CCM_CDHIPR] + cmp r9, #0 + bne 1b + + .endm + + .macro set_mmdc_misc_ralat_2_cycles + + /* Set MMDCx_MISC[RALAT] = 2 cycles */ + ldr r6, [r8, #0x18] + bic r6, r6, #(0x7 << 6) + orr r6, r6, #(0x2 << 6) + str r6, [r8, #0x18] + + /* Check if lpddr2 channel 1 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq 1f + + ldr r6, [r4, #0x18] + bic r6, r6, #(0x7 << 6) + orr r6, r6, #(0x2 << 6) + str r6, [r4, #0x18] +1: + .endm + + .macro switch_to_400MHz + /* set the MMDC_DIV=1, AXI_DIV=2, AHB_DIV=3 */ + ldr r9, [r2, #CCM_CBCDR] + ldr r6, =0x3f1f00 + bic r9, r9, r6 + orr r9, r9, #(0x9 << 8) + orr r9, r9, #(1 << 16) + str r9, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* check periph_clk_sel */ + ldr r9, [r2, #CCM_CBCDR] + and r9, r9, #(1 << 25) + cmp r9, #(1 << 25) + bne skip_periph_clk_switch_400m + + /* now switch periph_clk back. */ + ldr r9, [r2, #CCM_CBCDR] + bic r9, r9, #(1 << 25) + str r9, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + +skip_periph_clk_switch_400m: + + .endm + + .macro switch_to_100MHz + /* set the MMDC_DIV=4, AXI_DIV=8, AHB_DIV=8 */ + ldr r9, [r2, #CCM_CBCDR] + ldr r6, =0x3f1f00 + bic r9, r9, r6 + orr r9, r9, #(0x1F << 16) + orr r9, r9, #(0x1D << 8) + str r9, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* check if periph_clk_sel is already set. */ + ldr r9, [r2, #CCM_CBCDR] + and r9, r9, #(1 << 25) + cmp r9, #(1 << 25) + bne skip_periph_clk_switch_100m + + /* now switch periph_clk back. */ + ldr r9, [r2, #CCM_CBCDR] + bic r9, r9, #(1 << 25) + str r9, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + +skip_periph_clk_switch_100m: + + .endm + + .macro switch_to_24MHz + /* + * change the freq now try setting DDR to 24MHz. + * source it from the periph_clk2 ensure the + * periph_clk2 is sourced from 24MHz and the + * divider is 1. + */ + + ldr r9, [r2, #CCM_CBCMR] + bic r9, r9, #(0x3 << 12) + orr r9, r9, #(1 << 12) + str r9, [r2, #CCM_CBCMR] + + ldr r9, [r2, #CCM_CBCDR] + bic r9, r9, #(0x7 << 27) + str r9, [r2, #CCM_CBCDR] + + /* now switch periph_clk to 24MHz. */ + ldr r9, [r2, #CCM_CBCDR] + orr r9, r9, #(1 << 25) + str r9, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* change all the dividers to 1. */ + ldr r9, [r2, #CCM_CBCDR] + ldr r6, =0x3f1f00 + bic r9, r9, r6 + orr r9, r9, #(1 << 8) + str r9, [r2, #CCM_CBCDR] + + /* Wait for the divider to change. */ + wait_for_ccm_handshake + + .endm + + .macro switch_to_24MHZ_from_pll2 + /* Change DDR freq settings from pll2_pfd2 (div 2) */ + + ldr r9, [r2, #CCM_CBCMR] + bic r9, r9, #(0x3 << 18) + orr r9, r9, #(0x3 << 18) + str r9, [r2, #CCM_CBCMR] + + ldr r9, [r2, #CCM_CBCDR] + bic r9, r9, #(1 << 25) + str r9, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + ldr r9, [r2, #CCM_CBCDR] + ldr r6, =0x3f1f00 + bic r9, r9, r6 + orr r9, r9, #(1 << 8) + orr r9, r9, #(0x7 << 19) + str r9, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro set_timings_below_100MHz_operation + set_mmdc_misc_ralat_2_cycles + + /* Adjust LPDDR2 timings for 24Mhz operation */ + ldr r5, =0x03162073 + str r5, [r8, #0xC] /* MMDC0_MDCFG0 */ + ldr r7, =0x00020482 + str r7, [r8, #0x10] /* MMDC0_MDCFG1 */ + ldr r9, =0x00000049 + str r9, [r8, #0x14] /* MMDC0_MDCFG2 */ + ldr r10, =0x00020333 + str r10, [r8, #0x38] /* MMDC0_MDCFG3LP */ + + /* Check if lpddr2 channel 1 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq skip_below_100Mhz_ch1_timings + + str r5, [r4, #0xC] /* MMDC1_MDCFG0 */ + str r7, [r4, #0x10] /* MMDC1_MDCFG1 */ + str r9, [r4, #0x14] /* MMDC1_MDCFG2 */ + str r10, [r4, #0x38] /* MMDC1_MDCFG3LP */ + +skip_below_100Mhz_ch1_timings: + + .endm + + .macro restore_mmdc_settings_info + /* restore timing from mmdc_settings_info */ + ldr r6, [r1, #0x0] + ldr r7, [r1, #0x4] +1: + ldr r9, [r7], #0x4 + ldr r10, [r7], #0x4 + str r10, [r8, r9] + subs r6, r6, #0x1 + bne 1b + + /* Check if lpddr2 channel 1 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq 3f + + ldr r6, [r1, #0x0] + ldr r7, [r1, #0x4] +2: + ldr r9, [r7], #0x4 + ldr r10, [r7], #0x4 + str r10, [r4, r9] + subs r6, r6, #0x1 + bne 2b +3: + + .endm + + .macro mmdc_clk_lower_equal_100MHz + + ldr r10, =100000000 + cmp r0, r10 + beq set_timmings_100MHz + set_timings_below_100MHz_operation + b common_to_lower_equal_100MHz + +set_timmings_100MHz: + restore_mmdc_settings_info + set_mmdc_misc_ralat_2_cycles + +common_to_lower_equal_100MHz: + + /* if MMDC is not in 400MHz mode, skip double mu count */ + ldr r5, [r1, #0x8] + ldr r6, =400000000 + cmp r5, r6 + bne skip_lower_force_measure_ch1 + + /* + * Prior to reducing the DDR frequency (at 528/400 MHz), + * read the Measure unit count bits (MU_UNIT_DEL_NUM) + */ + ldr r5, =0x8B8 + ldr r6, [r8, r5] + /* Original MU unit count */ + mov r6, r6, LSR #16 + ldr r9, =0x3FF + and r6, r6, r9 + /* Original MU unit count * 2 */ + mov r7, r6, LSL #1 + /* + * Bypass the automatic measure unit when below 100 MHz + * by setting the Measure unit bypass enable bit (MU_BYP_EN) + */ + ldr r6, [r8, r5] + orr r6, r6, #0x400 + str r6, [r8, r5] + /* + * Double the measure count value read in step 1 and program it in the + * measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit + * Register for the reduced frequency operation below 100 MHz + */ + ldr r6, [r8, r5] + ldr r9, =0x3FF + bic r6, r6, r9 + orr r6, r6, r7 + str r6, [r8, r5] + /* Now perform a Force Measurement. */ + ldr r6, [r8, r5] + orr r6, r6, #0x800 + str r6, [r8, r5] + /* Wait for FRC_MSR to clear. */ +force_measure: + ldr r6, [r8, r5] + and r6, r6, #0x800 + cmp r6, #0x0 + bne force_measure + + /* Check if lpddr2 channel 2 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq skip_lower_force_measure_ch1 + + ldr r5, =0x8B8 + ldr r6, [r4, r5] + /* Original MU unit count */ + mov r6, r6, LSR #16 + ldr r9, =0x3FF + and r6, r6, r9 + /* Original MU unit count * 2 */ + mov r7, r6, LSL #1 + /* + * Bypass the automatic measure unit when below 100 MHz + * by setting the Measure unit bypass enable bit (MU_BYP_EN) + */ + ldr r6, [r4, r5] + orr r6, r6, #0x400 + str r6, [r4, r5] + /* + * Double the measure count value read in step 1 and program it in the + * measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit + * Register for the reduced frequency operation below 100 MHz + */ + ldr r6, [r4, r5] + ldr r9, =0x3FF + bic r6, r6, r9 + orr r6, r6, r7 + str r6, [r4, r5] + /* Now perform a Force Measurement. */ + ldr r6, [r4, r5] + orr r6, r6, #0x800 + str r6, [r4, r5] + /* Wait for FRC_MSR to clear. */ +force_measure_ch1: + ldr r6, [r4, r5] + and r6, r6, #0x800 + cmp r6, #0x0 + bne force_measure_ch1 + +skip_lower_force_measure_ch1: + + .endm + + .macro mmdc_clk_above_100MHz + + restore_mmdc_settings_info + + /* Make sure that the PHY measurement unit is NOT in bypass mode */ + ldr r5, =0x8B8 + ldr r6, [r8, r5] + bic r6, r6, #0x400 + str r6, [r8, r5] + /* Now perform a Force Measurement. */ + ldr r6, [r8, r5] + orr r6, r6, #0x800 + str r6, [r8, r5] + /* Wait for FRC_MSR to clear. */ +force_measure1: + ldr r6, [r8, r5] + and r6, r6, #0x800 + cmp r6, #0x0 + bne force_measure1 + + /* Check if lpddr2 channel 2 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq skip_above_force_measure_ch1 + + ldr r5, =0x8B8 + ldr r6, [r4, r5] + bic r6, r6, #0x400 + str r6, [r4, r5] + /* Now perform a Force Measurement. */ + ldr r6, [r4, r5] + orr r6, r6, #0x800 + str r6, [r4, r5] + /* Wait for FRC_MSR to clear. */ +force_measure1_ch1: + ldr r6, [r4, r5] + and r6, r6, #0x800 + cmp r6, #0x0 + bne force_measure1_ch1 + +skip_above_force_measure_ch1: + + .endm + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r11, lr} + + ldr r7, =v7_flush_kern_cache_all + mov lr, pc + mov pc, r7 + pop {r0 - r11, lr} + + /* disable d-cache */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + dsb + isb + + push {r0 - r11, lr} + + ldr r7, =v7_flush_kern_cache_all + mov lr, pc + mov pc, r7 + pop {r0 - r11, lr} + + .endm + +/* + * mx6_lpddr2_freq_change + * + * Make sure DDR is in self-refresh. + * IRQs are already disabled. + * r0 : DDR freq. + * r1 : mmdc_settings_info + */ + .align 3 +ENTRY(mx6q_lpddr2_freq_change) +mx6q_lpddr2_freq_change_start: + push {r2-r10} + + /* + * Need to flush and disable L1 before + * disabling L2, we need data to + * coherent. Flushing L1 pushes + * everyhting to L2. We sync L2 later, but + * it can still have dirty lines. + * While exiting, we need to enable L2 first + * and then L1. + */ + disable_l1_dcache + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + +#ifdef CONFIG_CACHE_L2X0 + /* + * Need to make sure the buffers in L2 are drained. + * Performing a sync operation does this. + */ + ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + + /* Wait for background operations to complete. */ +wait_for_l2_to_idle: + ldr r6, [r7, #0x730] + cmp r6, #0x0 + bne wait_for_l2_to_idle + + mov r6, #0x0 + str r6, [r7, #0x730] + + /* + * The second dsb might be needed to keep cache sync (device write) + * ordering with the memory accesses before it. + */ + dsb + isb + + /* Disable L2. */ + str r6, [r7, #0x100] +#endif + + ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR) + ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR) + ldr r8, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR) + ldr r4, =IMX_IO_P2V(MX6Q_MMDC_P1_BASE_ADDR) + + /* Disable Automatic power savings. */ + ldr r6, [r8, #0x404] + orr r6, r6, #0x01 + str r6, [r8, #0x404] + + /* MMDC0_MDPDC disable power down timer */ + ldr r6, [r8, #0x4] + bic r6, r6, #0xff00 + str r6, [r8, #0x4] + + /* Check if lpddr2 channel 2 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq skip_psd_ch1 + + ldr r6, [r4, #0x404] + orr r6, r6, #0x01 + str r6, [r4, #0x404] + + ldr r6, [r4, #0x4] + bic r6, r6, #0xff00 + str r6, [r4, #0x4] + +skip_psd_ch1: + /* Delay for a while */ + ldr r10, =10 +delay1: + ldr r7, =0 +cont1: + ldr r6, [r8, r7] + add r7, r7, #4 + cmp r7, #16 + bne cont1 + sub r10, r10, #1 + cmp r10, #0 + bgt delay1 + + /* Make the DDR explicitly enter self-refresh. */ + ldr r6, [r8, #0x404] + orr r6, r6, #0x200000 + str r6, [r8, #0x404] + +poll_dvfs_set_1: + ldr r6, [r8, #0x404] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + bne poll_dvfs_set_1 + + /* set SBS step-by-step mode */ + ldr r6, [r8, #0x410] + orr r6, r6, #0x100 + str r6, [r8, #0x410] + + /* Check if lpddr2 channel 2 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq skip_sbs_ch1 + + ldr r6, [r4, #0x404] + orr r6, r6, #0x200000 + str r6, [r4, #0x404] + +poll_dvfs_set_2: + ldr r6, [r4, #0x404] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + bne poll_dvfs_set_2 + + ldr r6, [r4, #0x410] + orr r6, r6, #0x100 + str r6, [r4, #0x410] + +skip_sbs_ch1: + ldr r10, =100000000 + cmp r0, r10 + bgt set_ddr_mu_above_100 + mmdc_clk_lower_equal_100MHz + +set_ddr_mu_above_100: + ldr r10, =24000000 + cmp r0, r10 + beq set_to_24MHz + + ldr r10, =100000000 + cmp r0, r10 + beq set_to_100MHz + + ldr r10, =400000000 + cmp r0, r10 + switch_to_400MHz + b done + +set_to_24MHz: +/* + switch_to_24MHZ_from_pll2 +*/ + switch_to_24MHz + b done + +set_to_100MHz: + switch_to_100MHz + +done: + + ldr r10,=100000000 + cmp r0, r10 + ble skip_mmdc_clk_check + mmdc_clk_above_100MHz + +skip_mmdc_clk_check: + + /* clear DVFS - exit from self refresh mode */ + ldr r6, [r8, #0x404] + bic r6, r6, #0x200000 + str r6, [r8, #0x404] + +poll_dvfs_clear_1: + ldr r6, [r8, #0x404] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + beq poll_dvfs_clear_1 + + /* Enable Automatic power savings. */ + ldr r6, [r8, #0x404] + bic r6, r6, #0x01 + str r6, [r8, #0x404] + + /* Check if lpddr2 channel 2 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq skip_enable_psd_ch1 + + ldr r6, [r4, #0x404] + bic r6, r6, #0x200000 + str r6, [r4, #0x404] + +poll_dvfs_clear_2: + ldr r6, [r4, #0x404] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + beq poll_dvfs_clear_2 + + ldr r6, [r4, #0x404] + bic r6, r6, #0x01 + str r6, [r4, #0x404] + +skip_enable_psd_ch1: + ldr r10, =24000000 + cmp r0, r10 + beq skip_power_down + + /* Enable MMDC power down timer. */ + ldr r6, [r8, #0x4] + orr r6, r6, #0x5500 + str r6, [r8, #0x4] + + /* Check if lpddr2 channel 2 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq skip_power_down + + ldr r6, [r4, #0x4] + orr r6, r6, #0x5500 + str r6, [r4, #0x4] + +skip_power_down: + /* clear SBS - unblock DDR accesses */ + ldr r6, [r8, #0x410] + bic r6, r6, #0x100 + str r6, [r8, #0x410] + + /* Check if lpddr2 channel 2 is enabled */ + ldr r6, [r8, #0x18] + ands r6, r6, #(1 << 2) + beq skip_disable_sbs_ch1 + + ldr r6, [r4, #0x410] + bic r6, r6, #0x100 + str r6, [r4, #0x410] + +skip_disable_sbs_ch1: +#ifdef CONFIG_CACHE_L2X0 + /* Enable L2. */ + ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + ldr r6, =0x1 + str r6, [r7, #0x100] +#endif + + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + pop {r2-r10} + + /* Restore registers */ + mov pc, lr + + /* + * Add ltorg here to ensure that all + * literals are stored here and are + * within the text space. + */ + .ltorg +mx6q_lpddr2_freq_change_end: diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c index edd26e0ffeec..54c89bdad39f 100644 --- a/arch/arm/mach-imx/mach-imx6q.c +++ b/arch/arm/mach-imx/mach-imx6q.c @@ -300,6 +300,8 @@ static void __init imx6q_map_io(void) { debug_ll_io_init(); imx_scu_map_io(); + imx6_pm_map_io(); + imx_busfreq_map_io(); } static void __init imx6q_init_irq(void) diff --git a/arch/arm/mach-imx/mx6.h b/arch/arm/mach-imx/mx6.h new file mode 100644 index 000000000000..06b8135a9954 --- /dev/null +++ b/arch/arm/mach-imx/mx6.h @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * * This program is free software; you can redistribute it and/or modify + * * it under the terms of the GNU General Public License version 2 as + * * published by the Free Software Foundation. + * */ + +#ifndef __ASM_ARCH_MXC_IOMAP_H__ +#define __ASM_ARCH_MXC_IOMAP_H__ + +#define MX6Q_IO_P2V(x) IMX_IO_P2V(x) +#define MX6Q_IO_ADDRESS(x) IOMEM(MX6Q_IO_P2V(x)) + +#define MX6Q_L2_BASE_ADDR 0x00a02000 +#define MX6Q_L2_SIZE 0x1000 +#define MX6Q_IOMUXC_BASE_ADDR 0x020e0000 +#define MX6Q_IOMUXC_SIZE 0x4000 +#define MX6Q_SRC_BASE_ADDR 0x020d8000 +#define MX6Q_SRC_SIZE 0x4000 +#define MX6Q_CCM_BASE_ADDR 0x020c4000 +#define MX6Q_CCM_SIZE 0x4000 +#define MX6Q_ANATOP_BASE_ADDR 0x020c8000 +#define MX6Q_ANATOP_SIZE 0x1000 +#define MX6Q_GPC_BASE_ADDR 0x020dc000 +#define MX6Q_GPC_SIZE 0x4000 +#define MX6Q_SEMA4_BASE_ADDR 0x02290000 +#define MX6Q_SEMA4_SIZE 0x4000 +#define MX6Q_MMDC_P0_BASE_ADDR 0x021b0000 +#define MX6Q_MMDC_P0_SIZE 0x4000 +#define MX6Q_MMDC_P1_BASE_ADDR 0x021b4000 +#define MX6Q_MMDC_P1_SIZE 0x4000 +#define MX6Q_AIPS1_BASE_ADDR 0x02000000 +#define MX6Q_AIPS1_SIZE 0x100000 +#define MX6Q_AIPS2_BASE_ADDR 0x02100000 +#define MX6Q_AIPS2_SIZE 0x100000 +#define MX6Q_AIPS3_BASE_ADDR 0x02200000 +#define MX6Q_AIPS3_SIZE 0x100000 + +#define MX6SX_IRAM_TLB_BASE_ADDR 0x008f8000 +#define MX6Q_IRAM_TLB_BASE_ADDR 0x00900000 +#define MX6Q_IRAM_TLB_SIZE 0x4000 +#define TT_ATTRIB_NON_CACHEABLE_1M 0x802 +#define MX6_SUSPEND_IRAM_DATA_SIZE 256 +#define MX6SL_WFI_IRAM_DATA_SIZE 100 + +#define MX6_SUSPEND_IRAM_ADDR_OFFSET 0 +#define MX6_CPUIDLE_IRAM_ADDR_OFFSET 0x1000 +#endif diff --git a/arch/arm/mach-imx/mxc.h b/arch/arm/mach-imx/mxc.h index 33d560c6f0b1..848d96b2dd77 100644 --- a/arch/arm/mach-imx/mxc.h +++ b/arch/arm/mach-imx/mxc.h @@ -87,6 +87,18 @@ static inline bool cpu_is_imx6q(void) return __mxc_cpu_type == MXC_CPU_IMX6Q; } +static inline bool cpu_is_imx6(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX6Q || + __mxc_cpu_type == MXC_CPU_IMX6DL || + __mxc_cpu_type == MXC_CPU_IMX6SL || + __mxc_cpu_type == MXC_CPU_IMX6SX || + __mxc_cpu_type == MXC_CPU_IMX6UL || + __mxc_cpu_type == MXC_CPU_IMX6ULL || + __mxc_cpu_type == MXC_CPU_IMX6SLL || + __mxc_cpu_type == MXC_CPU_IMX6ULZ; +} + static inline bool cpu_is_imx7d(void) { return __mxc_cpu_type == MXC_CPU_IMX7D; diff --git a/arch/arm/mach-imx/platsmp.c b/arch/arm/mach-imx/platsmp.c index 2aa26928221d..7be31e7e91b3 100644 --- a/arch/arm/mach-imx/platsmp.c +++ b/arch/arm/mach-imx/platsmp.c @@ -18,7 +18,7 @@ #include "hardware.h" u32 g_diag_reg; -static void __iomem *scu_base; +void __iomem *scu_base; static struct map_desc scu_io_desc __initdata = { /* .virtual and .pfn are run-time assigned */ diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c index 1c0ecad3620e..e36d0acfda8a 100644 --- a/arch/arm/mach-imx/pm-imx6.c +++ b/arch/arm/mach-imx/pm-imx6.c @@ -13,11 +13,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -58,6 +60,9 @@ #define MX6Q_SUSPEND_OCRAM_SIZE 0x1000 #define MX6_MAX_MMDC_IO_NUM 33 +extern unsigned long iram_tlb_base_addr; +extern unsigned long iram_tlb_phys_addr; + static void __iomem *ccm_base; static void __iomem *suspend_ocram_base; static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase); @@ -206,6 +211,37 @@ static const struct imx6_pm_socdata imx6ul_pm_data __initconst = { .mmdc_io_offset = imx6ul_mmdc_io_offset, }; +static struct map_desc iram_tlb_io_desc __initdata = { + /* .virtual and .pfn are run-time assigned */ + .length = SZ_1M, + .type = MT_MEMORY_RWX_NONCACHED, +}; + +/* + * AIPS1 and AIPS2 is not used, because it will trigger a BUG_ON if + * lowlevel debug and earlyprintk are configured. + * + * it is because there is a vm conflict because UART1 is mapped early if + * AIPS1 is mapped using 1M size. + * + * Thus no use AIPS1 and AIPS2 to avoid kernel BUG_ON. + */ +static struct map_desc imx6_pm_io_desc[] __initdata = { + imx_map_entry(MX6Q, MMDC_P0, MT_DEVICE), + imx_map_entry(MX6Q, MMDC_P1, MT_DEVICE), + imx_map_entry(MX6Q, SRC, MT_DEVICE), + imx_map_entry(MX6Q, IOMUXC, MT_DEVICE), + imx_map_entry(MX6Q, CCM, MT_DEVICE), + imx_map_entry(MX6Q, ANATOP, MT_DEVICE), + imx_map_entry(MX6Q, GPC, MT_DEVICE), + imx_map_entry(MX6Q, L2, MT_DEVICE), +}; + +static const char * const low_power_ocram_match[] __initconst = { + "fsl,lpm-sram", + NULL +}; + /* * This structure is for passing necessary data for low level ocram * suspend code(arch/arm/mach-imx/suspend-imx6.S), if this struct @@ -433,6 +469,106 @@ static const struct platform_suspend_ops imx6q_pm_ops = { .valid = imx6q_pm_valid, }; +static int __init imx6_dt_find_lpsram(unsigned long node, const char *uname, + int depth, void *data) +{ + unsigned long lpram_addr; + const __be32 *prop = of_get_flat_dt_prop(node, "reg", NULL); + + if (of_flat_dt_match(node, low_power_ocram_match)) { + if (!prop) + return -EINVAL; + + lpram_addr = be32_to_cpup(prop); + + /* We need to create a 1M page table entry. */ + iram_tlb_io_desc.virtual = IMX_IO_P2V(lpram_addr & 0xFFF00000); + iram_tlb_io_desc.pfn = __phys_to_pfn(lpram_addr & 0xFFF00000); + iram_tlb_phys_addr = lpram_addr; + iram_tlb_base_addr = IMX_IO_P2V(lpram_addr); + + iotable_init(&iram_tlb_io_desc, 1); + } + + return 0; +} + +void __init imx6_pm_map_io(void) +{ + unsigned long i; + + iotable_init(imx6_pm_io_desc, ARRAY_SIZE(imx6_pm_io_desc)); + + /* + * Get the address of IRAM or OCRAM to be used by the low + * power code from the device tree. + */ + WARN_ON(of_scan_flat_dt(imx6_dt_find_lpsram, NULL)); + + /* + * We moved suspend/resume and lowpower idle to TEE, + * But busfreq now still in Linux, this table is still needed + * If we later decide to move busfreq to TEE, we could drop this. + */ + /* Return if no IRAM space is allocated for suspend/resume code. */ + if (!iram_tlb_base_addr) { + pr_warn("No IRAM/OCRAM memory allocated for suspend/resume \ + code. Please ensure device tree has an entry for \ + fsl,lpm-sram.\n"); + return; + } + + /* Set all entries to 0. */ + memset((void *)iram_tlb_base_addr, 0, MX6Q_IRAM_TLB_SIZE); + + /* + * Make sure the IRAM virtual address has a mapping in the IRAM + * page table. + * + * Only use the top 11 bits [31-20] when storing the physical + * address in the page table as only these bits are required + * for 1M mapping. + */ + i = ((iram_tlb_base_addr >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + i) = + (iram_tlb_phys_addr & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; + + /* + * Make sure the AIPS1 virtual address has a mapping in the + * IRAM page table. + */ + i = ((IMX_IO_P2V(MX6Q_AIPS1_BASE_ADDR) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + i) = + (MX6Q_AIPS1_BASE_ADDR & 0xFFF00000) | + TT_ATTRIB_NON_CACHEABLE_1M; + + /* + * Make sure the AIPS2 virtual address has a mapping in the + * IRAM page table. + */ + i = ((IMX_IO_P2V(MX6Q_AIPS2_BASE_ADDR) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + i) = + (MX6Q_AIPS2_BASE_ADDR & 0xFFF00000) | + TT_ATTRIB_NON_CACHEABLE_1M; + + /* + * Make sure the AIPS3 virtual address has a mapping + * in the IRAM page table. + */ + i = ((IMX_IO_P2V(MX6Q_AIPS3_BASE_ADDR) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + i) = + (MX6Q_AIPS3_BASE_ADDR & 0xFFF00000) | + TT_ATTRIB_NON_CACHEABLE_1M; + + /* + * Make sure the L2 controller virtual address has a mapping + * in the IRAM page table. + */ + i = ((IMX_IO_P2V(MX6Q_L2_BASE_ADDR) >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + i) = + (MX6Q_L2_BASE_ADDR & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; +} + static int __init imx6_pm_get_base(struct imx6_pm_base *base, const char *compat) { diff --git a/arch/arm/mach-imx/smp_wfe_imx6.S b/arch/arm/mach-imx/smp_wfe_imx6.S new file mode 100644 index 000000000000..791e93ce98da --- /dev/null +++ b/arch/arm/mach-imx/smp_wfe_imx6.S @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include "hardware.h" + +#ifdef CONFIG_SMP +.extern scu_base +#endif + +.globl wfe_smp_freq_change_start +.globl wfe_smp_freq_change_end + +#ifdef CONFIG_SMP + + .align 3 + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r11, lr} + + ldr r7, =v7_flush_kern_cache_all + mov lr, pc + mov pc, r7 + pop {r0 - r11, lr} + + /* disable d-cache */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + dsb + isb + + push {r0 - r11, lr} + + ldr r7, =v7_flush_kern_cache_all + mov lr, pc + mov pc, r7 + pop {r0 - r11, lr} + + .endm + +ENTRY(wfe_smp_freq_change) +wfe_smp_freq_change_start: + push {r4 - r11, lr} + + mov r6, r0 + mov r7, r1 + + dsb + isb + + disable_l1_dcache + + isb + + /* Turn off SMP bit. */ + mrc p15, 0, r8, c1, c0, 1 + bic r8, r8, #0x40 + mcr p15, 0, r8, c1, c0, 1 + + isb + + /* Inform the SCU we are going to enter WFE. */ + push {r0 - r11, lr} + + ldr r0,=scu_base + ldr r0, [r0] + mov r1, #SCU_PM_DORMANT + ldr r3, =scu_power_mode + mov lr, pc + mov pc, r3 + + pop {r0 - r11, lr} + +go_back_wfe: + wfe + + ldr r3, [r7] + cmp r3, #1 + beq go_back_wfe + + /* Turn ON SMP bit. */ + mrc p15, 0, r8, c1, c0, 1 + orr r8, r8, #0x40 + mcr p15, 0, r8, c1, c0, 1 + + isb + /* Enable L1 data cache. */ + mrc p15, 0, r8, c1, c0, 0 + orr r8, r8, #0x4 + mcr p15, 0, r8, c1, c0, 0 + isb + + /* Inform the SCU we have exited WFE. */ + push {r0 - r11, lr} + + ldr r0,=scu_base + ldr r0, [r0] + mov r1, #SCU_PM_NORMAL + ldr r3, =scu_power_mode + mov lr, pc + mov pc, r3 + + pop {r0 - r11, lr} + + /* Pop all saved registers. */ + pop {r4 - r11, lr} + mov pc, lr + .ltorg +wfe_smp_freq_change_end: +ENDPROC(wfe_smp_freq_change) + +#ifdef CONFIG_OPTEE +/** + * @brief Switch CPU in WFE mode while bus frequency change + * on-going + * + * @param[in] r0 CPU in WFE Status + * @param[in] r1 Bus frequency change status + */ + +.globl imx_smp_wfe_optee_end + +ENTRY(imx_smp_wfe_optee) + push {r4-r11, lr} + + dsb + isb + + disable_l1_dcache + isb + + /* Set flag CPU entering WFE. */ + mov r4, #1 + str r4, [r0] + + dsb + isb + +1: + wfe + + /* Check if busfreq is done, else loop */ + ldr r4, [r1] + cmp r4, #1 + beq 1b + + /* Enable L1 data cache. */ + mrc p15, 0, r4, c1, c0, 0 + orr r4, r4, #0x4 + mcr p15, 0, r4, c1, c0, 0 + isb + + /* Set flag CPU exiting WFE. */ + mov r4, #0 + str r4, [r0] + + /* Pop all saved registers. */ + pop {r4-r11, lr} + mov pc, lr + .ltorg +imx_smp_wfe_optee_end: +ENDPROC(imx_smp_wfe_optee) +#endif +#endif From 1b8714b0de9acf294fe4c203c20684d1c7f60c1a Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 17 Apr 2019 10:39:55 +0800 Subject: [PATCH 05/81] ARM: imx: add i.MX6DL bus-freq support This patch adds i.MX6DL bus-freq support. Signed-off-by: Anson Huang --- arch/arm/mach-imx/busfreq-imx.c | 26 +++++++++++++++--- arch/arm/mach-imx/busfreq_ddr3.c | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index 5a43b1521917..62b36e94a12c 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -118,6 +118,8 @@ static struct clk *mmdc_clk; static struct clk *periph_clk2_clk; static struct clk *pll2_bus_clk; +static struct clk *pll3_pfd1_540m_clk; + static struct delayed_work low_bus_freq_handler; static struct delayed_work bus_freq_daemon; @@ -155,6 +157,10 @@ EXPORT_SYMBOL(unregister_busfreq_notifier); static void enter_lpm_imx6_smp(void) { + if (cpu_is_imx6dl()) + /* Set axi to periph_clk */ + clk_set_parent(axi_sel_clk, periph_clk); + if (audio_bus_count) { /* Need to ensure that PLL2_PFD_400M is kept ON. */ clk_prepare_enable(pll2_400_clk); @@ -219,7 +225,10 @@ static void exit_lpm_imx6_smp(void) clk_set_parent(periph_clk2_sel_clk, pll3_clk); clk_set_parent(periph_pre_clk, periph_clk_parent); clk_set_parent(periph_clk, periph_pre_clk); - + if (cpu_is_imx6dl()) { + /* Set axi to pll3_pfd1_540m */ + clk_set_parent(axi_sel_clk, pll3_pfd1_540m_clk); + } /* * As periph_pre_clk's parent is not changed from * high mode to audio mode on lpddr2, the clk framework @@ -306,7 +315,7 @@ static void reduce_bus_freq(void) if (cpu_is_imx7d()) enter_lpm_imx7d(); - else if (cpu_is_imx6q()) + else if (cpu_is_imx6q() || cpu_is_imx6dl()) enter_lpm_imx6_smp(); med_bus_freq_mode = 0; @@ -406,7 +415,7 @@ static int set_high_bus_freq(int high_bus_freq) if (cpu_is_imx7d()) exit_lpm_imx7d(); - else if (cpu_is_imx6q()) + else if (cpu_is_imx6q() || cpu_is_imx6dl()) exit_lpm_imx6_smp(); high_bus_freq_mode = 1; @@ -746,6 +755,15 @@ static int busfreq_probe(struct platform_device *pdev) } } + if (cpu_is_imx6dl()) { + axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel"); + pll3_pfd1_540m_clk = devm_clk_get(&pdev->dev, "pll3_pfd1_540m"); + if (IS_ERR(axi_sel_clk) || IS_ERR(pll3_pfd1_540m_clk)) { + dev_err(busfreq_dev, + "%s: failed to get busfreq clk\n", __func__); + return -EINVAL; + } + } if (cpu_is_imx6q()) { mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); if (IS_ERR(mmdc_clk)) { @@ -842,7 +860,7 @@ static int busfreq_probe(struct platform_device *pdev) } busfreq_func.init = &init_ddrc_ddr_settings; busfreq_func.update = &update_ddr_freq_imx_smp; - } else if (cpu_is_imx6q()) { + } else if (cpu_is_imx6q() || cpu_is_imx6dl()) { ddr_type = imx_mmdc_get_ddr_type(); if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) { busfreq_func.init = &init_mmdc_ddr3_settings_imx6_smp; diff --git a/arch/arm/mach-imx/busfreq_ddr3.c b/arch/arm/mach-imx/busfreq_ddr3.c index 612dd0be512f..53c9d3b3462c 100644 --- a/arch/arm/mach-imx/busfreq_ddr3.c +++ b/arch/arm/mach-imx/busfreq_ddr3.c @@ -154,6 +154,29 @@ unsigned long iomux_offsets_mx6q[][2] = { {0x5B8, 0x0}, {0x5C0, 0x0}, }; + +unsigned long ddr3_dll_mx6dl[][2] = { + {0x0c, 0x0}, + {0x10, 0x0}, + {0x1C, 0x04008032}, + {0x1C, 0x0400803a}, + {0x1C, 0x07208030}, + {0x1C, 0x07208038}, + {0x818, 0x0}, + {0x18, 0x0}, +}; + +unsigned long iomux_offsets_mx6dl[][2] = { + {0x4BC, 0x0}, + {0x4C0, 0x0}, + {0x4C4, 0x0}, + {0x4C8, 0x0}, + {0x4CC, 0x0}, + {0x4D0, 0x0}, + {0x4D4, 0x0}, + {0x4D8, 0x0}, +}; + int can_change_ddr_freq(void) { return 1; @@ -410,6 +433,9 @@ int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *busfreq_pdev) node = NULL; if (cpu_is_imx6q()) node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc"); + if (cpu_is_imx6dl()) + node = of_find_compatible_node(NULL, NULL, + "fsl,imx6dl-iomuxc"); if (!node) { printk(KERN_ERR "failed to find imx6q-iomux device tree data!\n"); return -EINVAL; @@ -429,6 +455,9 @@ int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *busfreq_pdev) if (cpu_is_imx6q()) ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6q) + ARRAY_SIZE(ddr3_calibration); + if (cpu_is_imx6dl()) + ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6dl) + + ARRAY_SIZE(ddr3_calibration); normal_mmdc_settings = kmalloc((ddr_settings_size * 8), GFP_KERNEL); if (cpu_is_imx6q()) { @@ -437,6 +466,12 @@ int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *busfreq_pdev) memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6q)), ddr3_calibration, sizeof(ddr3_calibration)); } + if (cpu_is_imx6dl()) { + memcpy(normal_mmdc_settings, ddr3_dll_mx6dl, + sizeof(ddr3_dll_mx6dl)); + memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6dl)), + ddr3_calibration, sizeof(ddr3_calibration)); + } /* store the original DDR settings at boot. */ for (i = 0; i < ddr_settings_size; i++) { /* @@ -538,6 +573,18 @@ int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *busfreq_pdev) } } + if (cpu_is_imx6dl()) { + for (i = 0; i < iomux_settings_size; i++) { + iomux_offsets_mx6dl[i][1] = + readl_relaxed(iomux_base + + iomux_offsets_mx6dl[i][0]); + iram_iomux_settings[i + 1][0] = + iomux_offsets_mx6dl[i][0]; + iram_iomux_settings[i + 1][1] = + iomux_offsets_mx6dl[i][1]; + } + } + curr_ddr_rate = ddr_normal_rate; return 0; From 1f6b6e8a89af685a4cf24e9ee96aa53ddd98063a Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 17 Apr 2019 13:40:49 +0800 Subject: [PATCH 06/81] ARM: imx: add i.MX6SX bus-freq support This patch adds i.MX6SX bus-freq support. Signed-off-by: Anson Huang --- arch/arm/mach-imx/Makefile | 8 +- arch/arm/mach-imx/busfreq-imx.c | 149 +++++ arch/arm/mach-imx/busfreq_ddr3.c | 160 ++++++ arch/arm/mach-imx/busfreq_lpddr2.c | 5 + arch/arm/mach-imx/ddr3_freq_imx6sx.S | 764 +++++++++++++++++++++++++ arch/arm/mach-imx/lpddr2_freq_imx6sx.S | 492 ++++++++++++++++ arch/arm/mach-imx/mach-imx6sx.c | 8 + arch/arm/mach-imx/mxc.h | 1 + 8 files changed, 1584 insertions(+), 3 deletions(-) create mode 100644 arch/arm/mach-imx/ddr3_freq_imx6sx.S create mode 100644 arch/arm/mach-imx/lpddr2_freq_imx6sx.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index a3db687d8c07..c24cdd8af09a 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -78,24 +78,26 @@ 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 ddr3_freq_imx6.o smp_wfe_imx6.o busfreq_lpddr2.o \ +obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o ddr3_freq_imx6.o smp_wfe_imx6.o \ lpddr2_freq_imx6q.o obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o -obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o +obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o ddr3_freq_imx6sx.o smp_wfe_imx6.o lpddr2_freq_imx6sx.o obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o obj-$(CONFIG_SOC_IMX7D_CA7) += mach-imx7d.o pm-imx7.o ddr3_freq_imx7d.o smp_wfe.o \ lpddr3_freq_imx.o obj-$(CONFIG_SOC_IMX7D_CM4) += mach-imx7d-cm4.o obj-$(CONFIG_SOC_IMX7ULP) += mach-imx7ulp.o pm-imx7ulp.o -obj-y += busfreq-imx.o busfreq_ddr3.o +obj-y += busfreq-imx.o busfreq_ddr3.o busfreq_lpddr2.o AFLAGS_smp_wfe.o :=-Wa,-march=armv7-a AFLAGS_smp_wfe_imx6.o :=-Wa,-march=armv7-a AFLAGS_ddr3_freq_imx7d.o :=-Wa,-march=armv7-a AFLAGS_lpddr3_freq_imx.o :=-Wa,-march=armv7-a AFLAGS_ddr3_freq_imx6.o :=-Wa,-march=armv7-a AFLAGS_lpddr2_freq_imx6q.o :=-Wa,-march=armv7-a +AFLAGS_lpddr2_freq_imx6sx.o :=-Wa,-march=armv7-a +AFLAGS_ddr3_freq_imx6sx.o :=-Wa,-march=armv7-a ifeq ($(CONFIG_SUSPEND),y) AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index 62b36e94a12c..0dea0fff3665 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -120,6 +120,12 @@ static struct clk *pll2_bus_clk; static struct clk *pll3_pfd1_540m_clk; +static struct clk *ocram_clk; +static struct clk *periph2_clk; +static struct clk *periph2_pre_clk; +static struct clk *periph2_clk2_clk; +static struct clk *periph2_clk2_sel_clk; + static struct delayed_work low_bus_freq_handler; static struct delayed_work bus_freq_daemon; @@ -155,6 +161,65 @@ int unregister_busfreq_notifier(struct notifier_block *nb) } EXPORT_SYMBOL(unregister_busfreq_notifier); +/* + * enter_lpm_imx6_up and exit_lpm_imx6_up is used by + * i.MX6SX/i.MX6UL for entering and exiting lpm mode. + */ +static void enter_lpm_imx6_up(void) +{ + /* set periph_clk2 to source from OSC for periph */ + clk_set_parent(periph_clk2_sel_clk, osc_clk); + clk_set_parent(periph_clk, periph_clk2_clk); + /* set ahb/ocram to 24MHz */ + clk_set_rate(ahb_clk, LPAPM_CLK); + clk_set_rate(ocram_clk, LPAPM_CLK); + + if (audio_bus_count) { + /* Need to ensure that PLL2_PFD_400M is kept ON. */ + clk_prepare_enable(pll2_400_clk); + if (ddr_type == IMX_DDR_TYPE_DDR3) + busfreq_func.update(LOW_AUDIO_CLK); + else if (ddr_type == IMX_DDR_TYPE_LPDDR2 || + ddr_type == IMX_MMDC_DDR_TYPE_LPDDR3) + busfreq_func.update(HIGH_AUDIO_CLK); + clk_set_parent(periph2_clk2_sel_clk, pll3_clk); + clk_set_parent(periph2_pre_clk, pll2_400_clk); + clk_set_parent(periph2_clk, periph2_pre_clk); + /* + * As periph2_clk's parent is not changed from + * high mode to audio mode, so clk framework + * will not update its children's freq, but we + * change the mmdc's podf in asm code, so here + * need to update mmdc rate to make sure clk + * tree is right, although it will not do any + * change to hardware. + */ + if (high_bus_freq_mode) { + if (ddr_type == IMX_DDR_TYPE_DDR3) + clk_set_rate(mmdc_clk, LOW_AUDIO_CLK); + else if (ddr_type == IMX_DDR_TYPE_LPDDR2 || + ddr_type == IMX_MMDC_DDR_TYPE_LPDDR3) + clk_set_rate(mmdc_clk, HIGH_AUDIO_CLK); + } + + audio_bus_freq_mode = 1; + low_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_AUDIO; + } else { + busfreq_func.update(LPAPM_CLK); + + clk_set_parent(periph2_clk2_sel_clk, osc_clk); + clk_set_parent(periph2_clk, periph2_clk2_clk); + + if (audio_bus_freq_mode) + clk_disable_unprepare(pll2_400_clk); + + low_bus_freq_mode = 1; + audio_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_LOW; + } +} + static void enter_lpm_imx6_smp(void) { if (cpu_is_imx6dl()) @@ -208,6 +273,48 @@ static void enter_lpm_imx6_smp(void) } } +static void exit_lpm_imx6_up(void) +{ + clk_prepare_enable(pll2_400_clk); + + /* + * lower ahb/ocram's freq first to avoid too high + * freq during parent switch from OSC to pll3. + */ + clk_set_rate(ahb_clk, LPAPM_CLK / 3); + + clk_set_rate(ocram_clk, LPAPM_CLK / 2); + /* set periph clk to from pll2_400 */ + clk_set_parent(periph_pre_clk, pll2_400_clk); + clk_set_parent(periph_clk, periph_pre_clk); + /* set periph_clk2 to pll3 */ + clk_set_parent(periph_clk2_sel_clk, pll3_clk); + + busfreq_func.update(ddr_normal_rate); + + /* correct parent info after ddr freq change in asm code */ + clk_set_parent(periph2_pre_clk, pll2_400_clk); + clk_set_parent(periph2_clk, periph2_pre_clk); + clk_set_parent(periph2_clk2_sel_clk, pll3_clk); + + /* + * As periph2_clk's parent is not changed from + * audio mode to high mode, so clk framework + * will not update its children's freq, but we + * change the mmdc's podf in asm code, so here + * need to update mmdc rate to make sure clk + * tree is right, although it will not do any + * change to hardware. + */ + if (audio_bus_freq_mode) + clk_set_rate(mmdc_clk, ddr_normal_rate); + + clk_disable_unprepare(pll2_400_clk); + + if (audio_bus_freq_mode) + clk_disable_unprepare(pll2_400_clk); +} + static void exit_lpm_imx6_smp(void) { struct clk *periph_clk_parent; @@ -315,6 +422,8 @@ static void reduce_bus_freq(void) if (cpu_is_imx7d()) enter_lpm_imx7d(); + else if (cpu_is_imx6sx()) + enter_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) enter_lpm_imx6_smp(); @@ -415,6 +524,8 @@ static int set_high_bus_freq(int high_bus_freq) if (cpu_is_imx7d()) exit_lpm_imx7d(); + else if (cpu_is_imx6sx()) + exit_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) exit_lpm_imx6_smp(); @@ -764,6 +875,34 @@ static int busfreq_probe(struct platform_device *pdev) return -EINVAL; } } + + if (cpu_is_imx6sx()) { + ahb_clk = devm_clk_get(&pdev->dev, "ahb"); + ocram_clk = devm_clk_get(&pdev->dev, "ocram"); + periph2_clk = devm_clk_get(&pdev->dev, "periph2"); + periph2_pre_clk = devm_clk_get(&pdev->dev, "periph2_pre"); + periph2_clk2_clk = devm_clk_get(&pdev->dev, "periph2_clk2"); + periph2_clk2_sel_clk = + devm_clk_get(&pdev->dev, "periph2_clk2_sel"); + if (IS_ERR(ahb_clk) || IS_ERR(ocram_clk) + || IS_ERR(periph2_clk) || IS_ERR(periph2_pre_clk) + || IS_ERR(periph2_clk2_clk) + || IS_ERR(periph2_clk2_sel_clk)) { + dev_err(busfreq_dev, + "%s: failed to get busfreq clk for imx6ul/sx/sl.\n", __func__); + return -EINVAL; + } + } + + if (cpu_is_imx6sx()) { + mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); + if (IS_ERR(mmdc_clk)) { + dev_err(busfreq_dev, + "%s: failed to get mmdc clk for imx6sx/ul.\n", __func__); + return -EINVAL; + } + } + if (cpu_is_imx6q()) { mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); if (IS_ERR(mmdc_clk)) { @@ -860,6 +999,16 @@ static int busfreq_probe(struct platform_device *pdev) } busfreq_func.init = &init_ddrc_ddr_settings; busfreq_func.update = &update_ddr_freq_imx_smp; + } else if (cpu_is_imx6sx()) { + ddr_type = imx_mmdc_get_ddr_type(); + if (ddr_type == IMX_DDR_TYPE_DDR3) { + busfreq_func.init = &init_mmdc_ddr3_settings_imx6_up; + busfreq_func.update = &update_ddr_freq_imx6_up; + } else if (ddr_type == IMX_DDR_TYPE_LPDDR2 || + ddr_type == IMX_MMDC_DDR_TYPE_LPDDR3) { + busfreq_func.init = &init_mmdc_lpddr2_settings; + busfreq_func.update = &update_lpddr2_freq; + } } else if (cpu_is_imx6q() || cpu_is_imx6dl()) { ddr_type = imx_mmdc_get_ddr_type(); if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) { diff --git a/arch/arm/mach-imx/busfreq_ddr3.c b/arch/arm/mach-imx/busfreq_ddr3.c index 53c9d3b3462c..3aa28f9e4853 100644 --- a/arch/arm/mach-imx/busfreq_ddr3.c +++ b/arch/arm/mach-imx/busfreq_ddr3.c @@ -72,6 +72,8 @@ struct imx6_busfreq_info { u32 mu_delay_val; } __aligned(8); +static struct imx6_busfreq_info *imx6_busfreq_info; + /* DDR settings */ static unsigned long (*iram_ddr_settings)[2]; static unsigned long (*normal_mmdc_settings)[2]; @@ -85,6 +87,8 @@ static int ddr_settings_size; static int iomux_settings_size; static int curr_ddr_rate; +void (*imx6_up_change_ddr_freq)(struct imx6_busfreq_info *busfreq_info); +extern void imx6_up_ddr3_freq_change(struct imx6_busfreq_info *busfreq_info); void (*imx7d_change_ddr_freq)(u32 freq) = NULL; extern void imx7d_ddr3_freq_change(u32 freq); extern void imx_lpddr3_freq_change(u32 freq); @@ -107,6 +111,9 @@ extern unsigned long iram_tlb_phys_addr; extern unsigned long mx6_ddr3_freq_change_start asm("mx6_ddr3_freq_change_start"); extern unsigned long mx6_ddr3_freq_change_end asm("mx6_ddr3_freq_change_end"); +extern unsigned long imx6_up_ddr3_freq_change_start asm("imx6_up_ddr3_freq_change_start"); +extern unsigned long imx6_up_ddr3_freq_change_end asm("imx6_up_ddr3_freq_change_end"); + #ifdef CONFIG_SMP static unsigned long wfe_freq_change_iram_base; volatile u32 *wait_for_ddr_freq_update; @@ -122,6 +129,30 @@ extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end"); extern void __iomem *scu_base; #endif +unsigned long ddr3_dll_mx6sx[][2] = { + {0x0c, 0x0}, + {0x10, 0x0}, + {0x1C, 0x04008032}, + {0x1C, 0x00048031}, + {0x1C, 0x05208030}, + {0x1C, 0x04008040}, + {0x818, 0x0}, + {0x18, 0x0}, +}; + +unsigned long ddr3_calibration_mx6sx[][2] = { + {0x83c, 0x0}, + {0x840, 0x0}, + {0x848, 0x0}, + {0x850, 0x0}, +}; + +unsigned long iomux_offsets_mx6sx[][2] = { + {0x330, 0x0}, + {0x334, 0x0}, + {0x338, 0x0}, + {0x33c, 0x0}, +}; unsigned long ddr3_dll_mx6q[][2] = { {0x0c, 0x0}, {0x10, 0x0}, @@ -338,6 +369,52 @@ int update_ddr_freq_imx_smp(int ddr_rate) return 0; } +/* Used by i.MX6SX/i.MX6UL for updating the ddr frequency */ +int update_ddr_freq_imx6_up(int ddr_rate) +{ + int i; + bool dll_off = false; + unsigned long ttbr1; + int mode = get_bus_freq_mode(); + + if (ddr_rate == curr_ddr_rate) + return 0; + + printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate); + + if ((mode == BUS_FREQ_LOW) || (mode == BUS_FREQ_AUDIO)) + dll_off = true; + + imx6_busfreq_info->dll_off = dll_off; + iram_ddr_settings[0][0] = ddr_settings_size; + iram_iomux_settings[0][0] = iomux_settings_size; + for (i = 0; i < iram_ddr_settings[0][0]; i++) { + iram_ddr_settings[i + 1][0] = + normal_mmdc_settings[i][0]; + iram_ddr_settings[i + 1][1] = + normal_mmdc_settings[i][1]; + } + + local_irq_disable(); + + ttbr1 = save_ttbr1(); + imx6_busfreq_info->freq = ddr_rate; + imx6_busfreq_info->ddr_settings = iram_ddr_settings; + imx6_busfreq_info->iomux_offsets = iram_iomux_settings; + imx6_busfreq_info->mu_delay_val = ((readl_relaxed(mmdc_base + MMDC0_MPMUR0) + >> MMDC0_MPMUR0_OFFSET) & MMDC0_MPMUR0_MASK); + + imx6_up_change_ddr_freq(imx6_busfreq_info); + restore_ttbr1(ttbr1); + curr_ddr_rate = ddr_rate; + + local_irq_enable(); + + printk(KERN_DEBUG "Bus freq set to %d done!\n", ddr_rate); + + return 0; +} + int init_ddrc_ddr_settings(struct platform_device *busfreq_pdev) { int ddr_type = imx_ddrc_get_ddr_type(); @@ -409,6 +486,89 @@ int init_ddrc_ddr_settings(struct platform_device *busfreq_pdev) return 0; } +/* Used by i.MX6SX/i.MX6UL for mmdc setting init. */ +int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev) +{ + int i; + struct device_node *node; + unsigned long ddr_code_size; + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc"); + if (!node) { + printk(KERN_ERR "failed to find mmdc device tree data!\n"); + return -EINVAL; + } + mmdc_base = of_iomap(node, 0); + WARN(!mmdc_base, "unable to map mmdc registers\n"); + + if (cpu_is_imx6sx()) + node = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-iomuxc"); + if (!node) { + printk(KERN_ERR "failed to find iomuxc device tree data!\n"); + return -EINVAL; + } + iomux_base = of_iomap(node, 0); + WARN(!iomux_base, "unable to map iomux registers\n"); + + ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6sx) + + ARRAY_SIZE(ddr3_calibration_mx6sx); + + normal_mmdc_settings = kmalloc((ddr_settings_size * 8), GFP_KERNEL); + memcpy(normal_mmdc_settings, ddr3_dll_mx6sx, + sizeof(ddr3_dll_mx6sx)); + memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6sx)), + ddr3_calibration_mx6sx, sizeof(ddr3_calibration_mx6sx)); + + /* store the original DDR settings at boot. */ + for (i = 0; i < ddr_settings_size; i++) { + /* + * writes via command mode register cannot be read back. + * hence hardcode them in the initial static array. + * this may require modification on a per customer basis. + */ + if (normal_mmdc_settings[i][0] != 0x1C) + normal_mmdc_settings[i][1] = + readl_relaxed(mmdc_base + + normal_mmdc_settings[i][0]); + } + + iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6sx); + + ddr_code_size = (&imx6_up_ddr3_freq_change_end -&imx6_up_ddr3_freq_change_start) *4 + + sizeof(*imx6_busfreq_info); + imx6_busfreq_info = (struct imx6_busfreq_info *)ddr_freq_change_iram_base; + + imx6_up_change_ddr_freq = (void *)fncpy((void *)ddr_freq_change_iram_base + sizeof(*imx6_busfreq_info), + &imx6_up_ddr3_freq_change, ddr_code_size - sizeof(*imx6_busfreq_info)); + + /* + * Store the size of the array in iRAM also, + * increase the size by 8 bytes. + */ + iram_iomux_settings = (void *)(ddr_freq_change_iram_base + ddr_code_size); + iram_ddr_settings = iram_iomux_settings + (iomux_settings_size * 8) + 8; + + if ((ddr_code_size + (iomux_settings_size + ddr_settings_size) * 8 + 16) + > ddr_freq_change_total_size) { + printk(KERN_ERR "Not enough memory allocated for DDR Frequency change code.\n"); + return EINVAL; + } + + for (i = 0; i < iomux_settings_size; i++) { + iomux_offsets_mx6sx[i][1] = + readl_relaxed(iomux_base + + iomux_offsets_mx6sx[i][0]); + iram_iomux_settings[i + 1][0] = + iomux_offsets_mx6sx[i][0]; + iram_iomux_settings[i + 1][1] = + iomux_offsets_mx6sx[i][1]; + } + + curr_ddr_rate = ddr_normal_rate; + + return 0; +} + int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *busfreq_pdev) { int i; diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c index 1d2eda2389b9..72c947370f51 100644 --- a/arch/arm/mach-imx/busfreq_lpddr2.c +++ b/arch/arm/mach-imx/busfreq_lpddr2.c @@ -56,6 +56,7 @@ void (*mx6_change_lpddr2_freq)(u32 ddr_freq, int bus_freq_mode) = NULL; extern unsigned int ddr_normal_rate; extern void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode); +extern void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode); extern unsigned long save_ttbr1(void); extern void restore_ttbr1(unsigned long ttbr1); extern void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings); @@ -161,6 +162,10 @@ int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) ddr_code_size = SZ_4K; + if (cpu_is_imx6sx()) + mx6_change_lpddr2_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base, + &imx6_up_lpddr2_freq_change, ddr_code_size); curr_ddr_rate = ddr_normal_rate; return 0; diff --git a/arch/arm/mach-imx/ddr3_freq_imx6sx.S b/arch/arm/mach-imx/ddr3_freq_imx6sx.S new file mode 100644 index 000000000000..1ac0d017bdf9 --- /dev/null +++ b/arch/arm/mach-imx/ddr3_freq_imx6sx.S @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "hardware.h" + +.globl imx6_up_ddr3_freq_change_start +.globl imx6_up_ddr3_freq_change_end + +#define MMDC0_MDPDC 0x4 +#define MMDC0_MDCF0 0xc +#define MMDC0_MDCF1 0x10 +#define MMDC0_MDMISC 0x18 +#define MMDC0_MDSCR 0x1c +#define MMDC0_MAPSR 0x404 +#define MMDC0_MADPCR0 0x410 +#define MMDC0_MPZQHWCTRL 0x800 +#define MMDC0_MPODTCTRL 0x818 +#define MMDC0_MPDGCTRL0 0x83c +#define MMDC0_MPMUR0 0x8b8 + +#define CCM_CBCDR 0x14 +#define CCM_CBCMR 0x18 +#define CCM_CSCMR1 0x1c +#define CCM_CDHIPR 0x48 + +#define L2_CACHE_SYNC 0x730 +#define PL310_AUX_CTRL 0x104 +#define PL310_DCACHE_LOCKDOWN_BASE 0x900 +#define PL310_AUX_16WAY_BIT 0x10000 +#define PL310_LOCKDOWN_NBREGS 8 +#define PL310_LOCKDOWN_SZREG 4 +#define PL310_8WAYS_MASK 0x00FF +#define PL310_16WAYS_UPPERMASK 0xFF00 + +#define BUSFREQ_INFO_FREQ_OFFSET 0x0 +#define BUSFREQ_INFO_DDR_SETTINGS_OFFSET 0x4 +#define BUSFREQ_INFO_DLL_OFF_OFFSET 0x8 +#define BUSFREQ_INFO_IOMUX_OFFSETS_OFFSET 0xc +#define BUSFREQ_INFO_MU_DELAY_OFFSET 0x10 + +.extern iram_tlb_phys_addr + + .align 3 + + /* Check if the cpu is cortex-a7 */ + .macro is_ca7 + + /* Read the primary cpu number is MPIDR */ + mrc p15, 0, r7, c0, c0, 0 + ldr r8, =0xfff0 + and r7, r7, r8 + ldr r8, =0xc070 + cmp r7, r8 + + .endm + + .macro do_delay + +1: + ldr r9, =0 +2: + ldr r10, [r4, r9] + add r9, r9, #4 + cmp r9, #16 + bne 2b + sub r8, r8, #1 + cmp r8, #0 + bgt 1b + + .endm + + .macro wait_for_ccm_handshake + +3: + ldr r8, [r5, #CCM_CDHIPR] + cmp r8, #0 + bne 3b + + .endm + + .macro switch_to_400MHz + + /* check whether periph2_clk is already from top path */ + ldr r8, [r5, #CCM_CBCDR] + ands r8, #(1 << 26) + beq skip_periph2_clk2_switch_400m + + /* now switch periph2_clk back. */ + ldr r8, [r5, #CCM_CBCDR] + bic r8, r8, #(1 << 26) + str r8, [r5, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* + * on i.MX6SX, pre_periph2_clk will be always from + * pll2_pfd2, so no need to set pre_periph2_clk + * parent, just set the mmdc divider directly. + */ +skip_periph2_clk2_switch_400m: + + /* fabric_mmdc_podf to 0 */ + ldr r8, [r5, #CCM_CBCDR] + bic r8, r8, #(0x7 << 3) + str r8, [r5, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro switch_to_50MHz + + /* check whether periph2_clk is already from top path */ + ldr r8, [r5, #CCM_CBCDR] + ands r8, #(1 << 26) + beq skip_periph2_clk2_switch_50m + + /* now switch periph2_clk back. */ + ldr r8, [r5, #CCM_CBCDR] + bic r8, r8, #(1 << 26) + str r8, [r5, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* + * on i.MX6SX, pre_periph2_clk will be always from + * pll2_pfd2, so no need to set pre_periph2_clk + * parent, just set the mmdc divider directly. + */ +skip_periph2_clk2_switch_50m: + + /* fabric_mmdc_podf to 7 so that mmdc is 400 / 8 = 50MHz */ + ldr r8, [r5, #CCM_CBCDR] + orr r8, r8, #(0x7 << 3) + str r8, [r5, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro switch_to_24MHz + + /* periph2_clk2 sel to OSC_CLK */ + ldr r8, [r5, #CCM_CBCMR] + orr r8, r8, #(1 << 20) + str r8, [r5, #CCM_CBCMR] + + /* periph2_clk2_podf to 0 */ + ldr r8, [r5, #CCM_CBCDR] + bic r8, r8, #0x7 + str r8, [r5, #CCM_CBCDR] + + /* periph2_clk sel to periph2_clk2 */ + ldr r8, [r5, #CCM_CBCDR] + orr r8, r8, #(0x1 << 26) + str r8, [r5, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* fabric_mmdc_podf to 0 */ + ldr r8, [r5, #CCM_CBCDR] + bic r8, r8, #(0x7 << 3) + str r8, [r5, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + +/* + * imx6_up_ddr3_freq_change + * Below code can be used by i.MX6SX and i.MX6UL. + * + * idle the processor (eg, wait for interrupt). + * make sure DDR is in self-refresh. + * IRQs are already disabled. + */ +ENTRY(imx6_up_ddr3_freq_change) + +imx6_up_ddr3_freq_change_start: + stmfd sp!, {r4 - r11} + + ldr r1, [r0, #BUSFREQ_INFO_DDR_SETTINGS_OFFSET] + ldr r2, [r0, #BUSFREQ_INFO_DLL_OFF_OFFSET] + ldr r3, [r0, #BUSFREQ_INFO_IOMUX_OFFSETS_OFFSET] + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Disable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + ldr r4, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR) + ldr r5, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR) + ldr r6, =IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR) + + is_ca7 + beq skip_disable_l2 + +#ifdef CONFIG_CACHE_L2X0 + /* + * make sure the L2 buffers are drained, + * sync operation on L2 drains the buffers. + */ + ldr r8, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + + /* Wait for background operations to complete. */ +wait_for_l2_to_idle: + ldr r7, [r8, #0x730] + cmp r7, #0x0 + bne wait_for_l2_to_idle + + mov r7, #0x0 + str r7, [r8, #L2_CACHE_SYNC] + + /* Lock L2. */ + + ldr r9, [r8, #PL310_AUX_CTRL] + tst r9, #PL310_AUX_16WAY_BIT + mov r9, #PL310_8WAYS_MASK + orrne r9, #PL310_16WAYS_UPPERMASK + mov r10, #PL310_LOCKDOWN_NBREGS + add r11, r8, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r9, [r11], #PL310_LOCKDOWN_SZREG + str r9, [r11], #PL310_LOCKDOWN_SZREG + subs r10, r10, #1 + bne 1b + + /* + * The second dsb might be needed to keep cache sync (device write) + * ordering with the memory accesses before it. + */ + dsb + isb +#endif + +skip_disable_l2: + /* disable automatic power saving. */ + ldr r8, [r4, #MMDC0_MAPSR] + orr r8, r8, #0x1 + str r8, [r4, #MMDC0_MAPSR] + + /* disable MMDC power down timer. */ + ldr r8, [r4, #MMDC0_MDPDC] + bic r8, r8, #(0xff << 8) + str r8, [r4, #MMDC0_MDPDC] + + /* delay for a while */ + ldr r8, =4 + do_delay + + /* set CON_REG */ + ldr r8, =0x8000 + str r8, [r4, #MMDC0_MDSCR] +poll_conreq_set_1: + ldr r8, [r4, #MMDC0_MDSCR] + and r8, r8, #(0x4 << 12) + cmp r8, #(0x4 << 12) + bne poll_conreq_set_1 + + /* + * if requested frequency is greater than + * 300MHz go to DLL on mode. + */ + ldr r8, [r0, #BUSFREQ_INFO_FREQ_OFFSET] + ldr r9, =300000000 + cmp r8, r9 + bge dll_on_mode + +dll_off_mode: + /* if DLL is currently on, turn it off. */ + cmp r2, #1 + beq continue_dll_off_1 + + ldr r8, =0x00018031 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x00018039 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =10 + do_delay + +continue_dll_off_1: + /* set DVFS - enter self refresh mode */ + ldr r8, [r4, #MMDC0_MAPSR] + orr r8, r8, #(1 << 21) + str r8, [r4, #MMDC0_MAPSR] + + /* de-assert con_req */ + mov r8, #0x0 + str r8, [r4, #MMDC0_MDSCR] + +poll_dvfs_set_1: + ldr r8, [r4, #MMDC0_MAPSR] + and r8, r8, #(1 << 25) + cmp r8, #(1 << 25) + bne poll_dvfs_set_1 + + ldr r8, [r0, #BUSFREQ_INFO_FREQ_OFFSET] + ldr r9, =24000000 + cmp r8, r9 + beq switch_freq_24 + + switch_to_50MHz + b continue_dll_off_2 + +switch_freq_24: + switch_to_24MHz + +continue_dll_off_2: + /* set SBS - block ddr accesses */ + ldr r8, [r4, #MMDC0_MADPCR0] + orr r8, r8, #(1 << 8) + str r8, [r4, #MMDC0_MADPCR0] + + /* clear DVFS - exit from self refresh mode */ + ldr r8, [r4, #MMDC0_MAPSR] + bic r8, r8, #(1 << 21) + str r8, [r4, #MMDC0_MAPSR] + +poll_dvfs_clear_1: + ldr r8, [r4, #MMDC0_MAPSR] + and r8, r8, #(1 << 25) + cmp r8, #(1 << 25) + beq poll_dvfs_clear_1 + + /* if DLL was previously on, continue DLL off routine. */ + cmp r2, #1 + beq continue_dll_off_3 + + ldr r8, =0x00018031 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x00018039 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x04208030 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x04208038 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x00088032 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x0008803A + str r8, [r4, #MMDC0_MDSCR] + + /* delay for a while. */ + ldr r8, =4 + do_delay + + ldr r8, [r4, #MMDC0_MDCF0] + bic r8, r8, #0xf + orr r8, r8, #0x3 + str r8, [r4, #MMDC0_MDCF0] + + ldr r8, [r4, #MMDC0_MDCF1] + bic r8, r8, #0x7 + orr r8, r8, #0x4 + str r8, [r4, #MMDC0_MDCF1] + + ldr r8, [r4, #MMDC0_MDMISC] + bic r8, r8, #(0x3 << 16) /* walat = 0x1 */ + orr r8, r8, #(0x1 << 16) + bic r8, r8, #(0x7 << 6) /* ralat = 0x2 */ + orr r8, r8, #(0x2 << 6) + str r8, [r4, #MMDC0_MDMISC] + + /* enable dqs pull down in the IOMUX. */ + ldr r8, [r3] + add r3, r3, #8 + ldr r9, =0x3028 +update_iomux: + ldr r10, [r3] + ldr r11, [r6, r10] + bic r11, r11, r9 + orr r11, r11, #(0x3 << 12) + orr r11, r11, #0x28 + str r11, [r6, r10] + add r3, r3, #8 + sub r8, r8, #1 + cmp r8, #0 + bgt update_iomux + + /* ODT disabled. */ + ldr r8, =0x0 + str r8, [r4, #MMDC0_MPODTCTRL] + + /* DQS gating disabled. */ + ldr r8, [r4, #MMDC0_MPDGCTRL0] + orr r8, r8, #(1 << 29) + str r8, [r4, #MMDC0_MPDGCTRL0] + + /* Add workaround for ERR005778.*/ + /* double the original MU_UNIT_DEL_NUM. */ + ldr r8, [r0, #BUSFREQ_INFO_MU_DELAY_OFFSET] + lsl r8, r8, #1 + + /* Bypass the automatic MU by setting the mu_byp_en */ + ldr r10, [r4, #MMDC0_MPMUR0] + orr r10, r10, #0x400 + /* Set the MU_BYP_VAL */ + orr r10, r10, r8 + str r10, [r4, #MMDC0_MPMUR0] + + /* Now perform a force measure */ + ldr r8, [r4, #MMDC0_MPMUR0] + orr r8, r8, #0x800 + str r8, [r4, #MMDC0_MPMUR0] + /* Wait for FRC_MSR to clear. */ +1: + ldr r8, [r4, #MMDC0_MPMUR0] + and r8, r8, #0x800 + cmp r8, #0x0 + bne 1b + +continue_dll_off_3: + /* clear SBS - unblock accesses to DDR. */ + ldr r8, [r4, #MMDC0_MADPCR0] + bic r8, r8, #(0x1 << 8) + str r8, [r4, #MMDC0_MADPCR0] + + mov r8, #0x0 + str r8, [r4, #MMDC0_MDSCR] +poll_conreq_clear_1: + ldr r8, [r4, #MMDC0_MDSCR] + and r8, r8, #(0x4 << 12) + cmp r8, #(0x4 << 12) + beq poll_conreq_clear_1 + + b done + +dll_on_mode: + /* assert DVFS - enter self refresh mode. */ + ldr r8, [r4, #MMDC0_MAPSR] + orr r8, r8, #(1 << 21) + str r8, [r4, #MMDC0_MAPSR] + + /* de-assert CON_REQ. */ + mov r8, #0x0 + str r8, [r4, #MMDC0_MDSCR] + + /* poll DVFS ack. */ +poll_dvfs_set_2: + ldr r8, [r4, #MMDC0_MAPSR] + and r8, r8, #(1 << 25) + cmp r8, #(1 << 25) + bne poll_dvfs_set_2 + + switch_to_400MHz + + /* set SBS step-by-step mode. */ + ldr r8, [r4, #MMDC0_MADPCR0] + orr r8, r8, #(1 << 8) + str r8, [r4, #MMDC0_MADPCR0] + + /* clear DVFS - exit self refresh mode. */ + ldr r8, [r4, #MMDC0_MAPSR] + bic r8, r8, #(1 << 21) + str r8, [r4, #MMDC0_MAPSR] + +poll_dvfs_clear_2: + ldr r8, [r4, #MMDC0_MAPSR] + ands r8, r8, #(1 << 25) + bne poll_dvfs_clear_2 + + /* if DLL is currently off, turn it back on. */ + cmp r2, #0 + beq update_calibration_only + + /* issue zq calibration command */ + ldr r8, [r4, #MMDC0_MPZQHWCTRL] + orr r8, r8, #0x3 + str r8, [r4, #MMDC0_MPZQHWCTRL] + + /* enable DQS gating. */ + ldr r10, =MMDC0_MPDGCTRL0 + ldr r8, [r4, r10] + bic r8, r8, #(1 << 29) + str r8, [r4, r10] + + /* Now perform a force measure */ + ldr r8, =0x00000800 + str r8, [r4, #MMDC0_MPMUR0] + /* Wait for FRC_MSR to clear. */ +1: + ldr r8, [r4, #MMDC0_MPMUR0] + and r8, r8, #0x800 + cmp r8, #0x0 + bne 1b + + /* disable dqs pull down in the IOMUX. */ + ldr r8, [r3] + add r3, r3, #8 +update_iomux1: + ldr r10, [r3, #0x0] + ldr r11, [r3, #0x4] + str r11, [r6, r10] + add r3, r3, #8 + sub r8, r8, #1 + cmp r8, #0 + bgt update_iomux1 + + /* config MMDC timings to 400MHz. */ + ldr r1, [r0, #BUSFREQ_INFO_DDR_SETTINGS_OFFSET] + ldr r7, [r1] + add r1, r1, #8 + ldr r10, [r1, #0x0] + ldr r11, [r1, #0x4] + str r11, [r4, r10] + add r1, r1, #8 + + ldr r10, [r1, #0x0] + ldr r11, [r1, #0x4] + str r11, [r4, r10] + add r1, r1, #8 + + /* configure ddr devices to dll on, odt. */ + ldr r8, =0x00028031 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x00028039 + str r8, [r4, #MMDC0_MDSCR] + + /* delay for while. */ + ldr r8, =4 + do_delay + + /* reset dll. */ + ldr r8, =0x09208030 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x09208038 + str r8, [r4, #MMDC0_MDSCR] + + /* delay for while. */ + ldr r8, =100 + do_delay + + ldr r10, [r1, #0x0] + ldr r11, [r1, #0x4] + str r11, [r4, r10] + add r1, r1, #8 + + ldr r10, [r1, #0x0] + ldr r11, [r1, #0x4] + str r11, [r4, r10] + add r1, r1, #8 + + ldr r8, =0x00428031 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x00428039 + str r8, [r4, #MMDC0_MDSCR] + + ldr r10, [r1, #0x0] + ldr r11, [r1, #0x4] + str r11, [r4, r10] + add r1, r1, #8 + + ldr r10, [r1, #0x0] + ldr r11, [r1, #0x4] + str r11, [r4, r10] + add r1, r1, #8 + + /* issue a zq command. */ + ldr r8, =0x04008040 + str r8, [r4, #MMDC0_MDSCR] + + ldr r8, =0x04008048 + str r8, [r4, #MMDC0_MDSCR] + + /* MMDC ODT enable. */ + ldr r10, [r1, #0x0] + ldr r11, [r1, #0x4] + str r11, [r4, r10] + add r1, r1, #8 + + /* delay for while. */ + ldr r8, =40 + do_delay + + /* enable MMDC power down timer. */ + ldr r8, [r4, #MMDC0_MDPDC] + orr r8, r8, #(0x55 << 8) + str r8, [r4, #MMDC0_MDPDC] + + b update_calibration + +update_calibration_only: + ldr r8, [r1] + sub r8, r8, #7 + add r1, r1, #64 + b update_calib + +update_calibration: + /* write the new calibration values. */ + mov r8, r7 + sub r8, r8, #7 + +update_calib: + ldr r10, [r1, #0x0] + ldr r11, [r1, #0x4] + str r11, [r4, r10] + add r1, r1, #8 + sub r8, r8, #1 + cmp r8, #0 + bgt update_calib + + /* perform a force measurement. */ + ldr r8, =0x800 + str r8, [r4, #MMDC0_MPMUR0] + /* Wait for FRC_MSR to clear. */ +1: + ldr r8, [r4, #MMDC0_MPMUR0] + and r8, r8, #0x800 + cmp r8, #0x0 + bne 1b + + /* clear SBS - unblock DDR accesses. */ + ldr r8, [r4, #MMDC0_MADPCR0] + bic r8, r8, #(1 << 8) + str r8, [r4, #MMDC0_MADPCR0] + + mov r8, #0x0 + str r8, [r4, #MMDC0_MDSCR] +poll_conreq_clear_2: + ldr r8, [r4, #MMDC0_MDSCR] + and r8, r8, #(0x4 << 12) + cmp r8, #(0x4 << 12) + beq poll_conreq_clear_2 + +done: + + /* MMDC0_MAPSR adopt power down enable. */ + ldr r8, [r4, #MMDC0_MAPSR] + bic r8, r8, #0x01 + str r8, [r4, #MMDC0_MAPSR] + + is_ca7 + beq skip_enable_l2 + +#ifdef CONFIG_CACHE_L2X0 + /* Unlock L2. */ + ldr r8, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + ldr r9, [r8, #PL310_AUX_CTRL] + tst r9, #PL310_AUX_16WAY_BIT + mov r10, #PL310_LOCKDOWN_NBREGS + mov r9, #0x00 /* 8 ways mask */ + orrne r9, #0x0000 /* 16 ways mask */ + add r11, r8, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r9, [r11], #PL310_LOCKDOWN_SZREG + str r9, [r11], #PL310_LOCKDOWN_SZREG + subs r10, r10, #1 + bne 1b + +#endif + +skip_enable_l2: + /* Enable L1 data cache. */ + mrc p15, 0, r7, c1, c0, 0 + orr r7, r7, #0x4 + mcr p15, 0, r7, c1, c0, 0 + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r7, c1, c0, 0 + orr r7, r7, #0x800 + mcr p15, 0, r7, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r7, =0x0 + mcr p15, 0, r7, c7, c1, 6 + + /* restore registers */ + ldmfd sp!, {r4 - r11} + mov pc, lr + + /* + * Add ltorg here to ensure that all + * literals are stored here and are + * within the text space. + */ + .ltorg +imx6_up_ddr3_freq_change_end: +ENDPROC(imx6_up_ddr3_freq_change) diff --git a/arch/arm/mach-imx/lpddr2_freq_imx6sx.S b/arch/arm/mach-imx/lpddr2_freq_imx6sx.S new file mode 100644 index 000000000000..ba3488cad9d4 --- /dev/null +++ b/arch/arm/mach-imx/lpddr2_freq_imx6sx.S @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "hardware.h" + +#define CCM_CBCDR 0x14 +#define CCM_CBCMR 0x18 +#define CCM_CSCMR1 0x1c +#define CCM_CDHIPR 0x48 + +#define L2_CACHE_SYNC 0x730 +#define PL310_AUX_CTRL 0x104 +#define PL310_DCACHE_LOCKDOWN_BASE 0x900 +#define PL310_AUX_16WAY_BIT 0x10000 +#define PL310_LOCKDOWN_NBREGS 8 +#define PL310_LOCKDOWN_SZREG 4 +#define PL310_8WAYS_MASK 0x00FF +#define PL310_16WAYS_UPPERMASK 0xFF00 + +#define MMDC0_MDPDC 0x4 +#define MMDC0_MAPSR 0x404 +#define MMDC0_MADPCR0 0x410 + +#define HIGH_BUS_MODE 0x0 + + /* Check if the cpu is cortex-a7 */ + .macro is_ca7 + + /* Read the primary cpu number is MPIDR */ + mrc p15, 0, r6, c0, c0, 0 + ldr r7, =0xfff0 + and r6, r6, r7 + ldr r7, =0xc070 + cmp r6, r7 + + .endm + + .macro wait_for_ccm_handshake + +1: + ldr r8, [r2, #CCM_CDHIPR] + cmp r8, #0 + bne 1b + + .endm + + .macro switch_to_24MHz + + /* periph2_clk2 sel to OSC_CLK */ + ldr r8, [r2, #CCM_CBCMR] + orr r8, r8, #(1 << 20) + str r8, [r2, #CCM_CBCMR] + + /* periph2_clk2_podf to 0 */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #0x7 + str r8, [r2, #CCM_CBCDR] + + /* periph2_clk sel to periph2_clk2 */ + ldr r8, [r2, #CCM_CBCDR] + orr r8, r8, #(0x1 << 26) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* fabric_mmdc_podf to 0 */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(0x7 << 3) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro switch_to_100MHz + + /* check whether periph2_clk is from top path */ + ldr r8, [r2, #CCM_CBCDR] + ands r8, #(1 << 26) + beq skip_periph2_clk2_switch_100m + + /* now switch periph2_clk back. */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(1 << 26) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* + * on i.MX6SX, pre_periph2_clk will be always from + * pll2_pfd2, so no need to set pre_periph2_clk + * parent, just set the mmdc divider directly. + */ +skip_periph2_clk2_switch_100m: + + /* fabric_mmdc_podf to 3 so that mmdc is 400 / 4 = 100MHz */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(0x7 << 3) + orr r8, r8, #(0x3 << 3) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro switch_to_400MHz + + /* check whether periph2_clk is from top path */ + ldr r8, [r2, #CCM_CBCDR] + ands r8, #(1 << 26) + beq skip_periph2_clk2_switch_400m + + /* now switch periph2_clk back. */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(1 << 26) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* + * on i.MX6SX, pre_periph2_clk will be always from + * pll2_pfd2, so no need to set pre_periph2_clk + * parent, just set the mmdc divider directly. + */ +skip_periph2_clk2_switch_400m: + + /* fabric_mmdc_podf to 0 */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(0x7 << 3) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro mmdc_clk_lower_100MHz + /* if MMDC is not in 400MHz mode, skip double mu count */ + cmp r1, #HIGH_BUS_MODE + bne 1f + + /* + * Prior to reducing the DDR frequency (at 528/400 MHz), + * read the Measure unit count bits (MU_UNIT_DEL_NUM) + */ + ldr r8, =0x8B8 + ldr r6, [r5, r8] + /* Original MU unit count */ + mov r6, r6, LSR #16 + ldr r4, =0x3FF + and r6, r6, r4 + /* Original MU unit count * 2 */ + mov r7, r6, LSL #1 + /* + * Bypass the automatic measure unit when below 100 MHz + * by setting the Measure unit bypass enable bit (MU_BYP_EN) + */ + ldr r6, [r5, r8] + orr r6, r6, #0x400 + str r6, [r5, r8] + /* + * Double the measure count value read in step 1 and program it in the + * measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit + * Register for the reduced frequency operation below 100 MHz + */ + ldr r6, [r5, r8] + ldr r4, =0x3FF + bic r6, r6, r4 + orr r6, r6, r7 + str r6, [r5, r8] + + /* For freq lower than 100MHz, need to set RALAT to 2 */ + ldr r6, [r5, #0x18] + bic r6, r6, #(0x7 << 6) + orr r6, r6, #(0x2 << 6) + str r6, [r5, #0x18] +1: + .endm + + .macro mmdc_clk_above_100MHz + + /* Make sure that the PHY measurement unit is NOT in bypass mode */ + ldr r8, =0x8B8 + ldr r6, [r5, r8] + bic r6, r6, #0x400 + str r6, [r5, r8] + /* Now perform a Force Measurement. */ + ldr r6, [r5, r8] + orr r6, r6, #0x800 + str r6, [r5, r8] + /* Wait for FRC_MSR to clear. */ +force_measure1: + ldr r6, [r5, r8] + and r6, r6, #0x800 + cmp r6, #0x0 + bne force_measure1 + + /* For freq higher than 100MHz, need to set RALAT to 5 */ + ldr r6, [r5, #0x18] + bic r6, r6, #(0x7 << 6) + orr r6, r6, #(0x5 << 6) + str r6, [r5, #0x18] + + .endm + + .align 3 +/* + * Below code can be used by i.MX6SX and i.MX6UL when changing the + * frequency of MMDC. the MMDC is the same on these two SOCs. + */ +ENTRY(imx6_up_lpddr2_freq_change) + + push {r2 - r8} + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Disable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + is_ca7 + beq skip_disable_l2 + +#ifdef CONFIG_CACHE_L2X0 + /* + * Need to make sure the buffers in L2 are drained. + * Performing a sync operation does this. + */ + ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + mov r6, #0x0 + str r6, [r7, #L2_CACHE_SYNC] + + /* + * The second dsb might be needed to keep cache sync (device write) + * ordering with the memory accesses before it. + */ + dsb + isb + + ldr r3, [r7, #PL310_AUX_CTRL] + tst r3, #PL310_AUX_16WAY_BIT + mov r3, #PL310_8WAYS_MASK + orrne r3, #PL310_16WAYS_UPPERMASK + mov r6, #PL310_LOCKDOWN_NBREGS + add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r3, [r5], #PL310_LOCKDOWN_SZREG + str r3, [r5], #PL310_LOCKDOWN_SZREG + subs r6, r6, #1 + bne 1b +#endif + +skip_disable_l2: + ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR) + ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR) + ldr r5, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR) + + /* Disable Automatic power savings. */ + ldr r6, [r5, #MMDC0_MAPSR] + orr r6, r6, #0x1 + str r6, [r5, #MMDC0_MAPSR] + + /* MMDC0_MDPDC disable power down timer */ + ldr r6, [r5, #MMDC0_MDPDC] + bic r6, r6, #0xff00 + str r6, [r5, #MMDC0_MDPDC] + + /* Delay for a while */ + ldr r8, =10 +delay: + ldr r7, =0 +cont: + ldr r6, [r5, r7] + add r7, r7, #4 + cmp r7, #16 + bne cont + sub r8, r8, #1 + cmp r8, #0 + bgt delay + + /* Make the DDR explicitly enter self-refresh. */ + ldr r6, [r5, #MMDC0_MAPSR] + orr r6, r6, #0x200000 + str r6, [r5, #MMDC0_MAPSR] + +poll_dvfs_set_1: + ldr r6, [r5, #MMDC0_MAPSR] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + bne poll_dvfs_set_1 + + /* set SBS step-by-step mode */ + ldr r6, [r5, #MMDC0_MADPCR0] + orr r6, r6, #0x100 + str r6, [r5, #MMDC0_MADPCR0] + + ldr r6, =100000000 + cmp r0, r6 + bgt set_ddr_mu_above_100 + mmdc_clk_lower_100MHz + +set_ddr_mu_above_100: + ldr r6, =24000000 + cmp r0, r6 + beq set_to_24MHz + + ldr r6, =100000000 + cmp r0, r6 + beq set_to_100MHz + + switch_to_400MHz + + mmdc_clk_above_100MHz + + b done + +set_to_24MHz: + switch_to_24MHz + b done +set_to_100MHz: + switch_to_100MHz +done: + /* clear DVFS - exit from self refresh mode */ + ldr r6, [r5, #MMDC0_MAPSR] + bic r6, r6, #0x200000 + str r6, [r5, #MMDC0_MAPSR] + +poll_dvfs_clear_1: + ldr r6, [r5, #MMDC0_MAPSR] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + beq poll_dvfs_clear_1 + + /* Enable Automatic power savings. */ + ldr r6, [r5, #MMDC0_MAPSR] + bic r6, r6, #0x1 + str r6, [r5, #MMDC0_MAPSR] + + ldr r6, =24000000 + cmp r0, r6 + beq skip_power_down + + /* Enable MMDC power down timer. */ + ldr r6, [r5, #MMDC0_MDPDC] + orr r6, r6, #0x5500 + str r6, [r5, #MMDC0_MDPDC] + +skip_power_down: + /* clear SBS - unblock DDR accesses */ + ldr r6, [r5, #MMDC0_MADPCR0] + bic r6, r6, #0x100 + str r6, [r5, #MMDC0_MADPCR0] + + is_ca7 + beq skip_enable_l2 + +#ifdef CONFIG_CACHE_L2X0 + ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + ldr r3, [r7, #PL310_AUX_CTRL] + tst r3, #PL310_AUX_16WAY_BIT + mov r6, #PL310_LOCKDOWN_NBREGS + mov r3, #0x00 /* 8 ways mask */ + orrne r3, #0x0000 /* 16 ways mask */ + add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r3, [r5], #PL310_LOCKDOWN_SZREG + str r3, [r5], #PL310_LOCKDOWN_SZREG + subs r6, r6, #1 + bne 1b +#endif + +skip_enable_l2: + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + /* Restore registers */ + pop {r2 - r8} + mov pc, lr diff --git a/arch/arm/mach-imx/mach-imx6sx.c b/arch/arm/mach-imx/mach-imx6sx.c index d5310bf307ff..35ef73346047 100644 --- a/arch/arm/mach-imx/mach-imx6sx.c +++ b/arch/arm/mach-imx/mach-imx6sx.c @@ -86,6 +86,13 @@ static void __init imx6sx_init_irq(void) imx6_pm_ccm_init("fsl,imx6sx-ccm"); } +static void __init imx6sx_map_io(void) +{ + debug_ll_io_init(); + imx6_pm_map_io(); + imx_busfreq_map_io(); +} + static void __init imx6sx_init_late(void) { imx6sx_cpuidle_init(); @@ -102,6 +109,7 @@ static const char * const imx6sx_dt_compat[] __initconst = { DT_MACHINE_START(IMX6SX, "Freescale i.MX6 SoloX (Device Tree)") .l2c_aux_val = 0, .l2c_aux_mask = ~0, + .map_io = imx6sx_map_io, .init_irq = imx6sx_init_irq, .init_machine = imx6sx_init_machine, .dt_compat = imx6sx_dt_compat, diff --git a/arch/arm/mach-imx/mxc.h b/arch/arm/mach-imx/mxc.h index 848d96b2dd77..fc22dcd0a591 100644 --- a/arch/arm/mach-imx/mxc.h +++ b/arch/arm/mach-imx/mxc.h @@ -36,6 +36,7 @@ #define IMX_DDR_TYPE_DDR3 0 #define IMX_DDR_TYPE_LPDDR2 1 #define IMX_DDR_TYPE_LPDDR3 2 +#define IMX_MMDC_DDR_TYPE_LPDDR3 3 #ifndef __ASSEMBLY__ extern unsigned int __mxc_cpu_type; From 4d546d4a1e15addd03ab1d48a315abbb7d07f4e2 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Thu, 18 Apr 2019 12:54:29 +0800 Subject: [PATCH 07/81] ARM: imx: add suspend/resume with FastMix OFF support This patch adds suspend/resume with FastMix OFF support. Signed-off-by: Anson Huang --- arch/arm/mach-imx/Kconfig | 7 + arch/arm/mach-imx/Makefile | 1 + arch/arm/mach-imx/anatop.c | 153 ++++-- arch/arm/mach-imx/common.h | 31 ++ arch/arm/mach-imx/gpc.c | 286 +++++++++- arch/arm/mach-imx/mmdc.c | 6 + arch/arm/mach-imx/mu.c | 434 ++++++++++++++++ arch/arm/mach-imx/mxc.h | 8 + arch/arm/mach-imx/pm-imx6.c | 867 +++++++++++++++++++++++++------ arch/arm/mach-imx/src.c | 6 + arch/arm/mach-imx/suspend-imx6.S | 527 ++++++++++++++++--- 11 files changed, 2072 insertions(+), 254 deletions(-) create mode 100644 arch/arm/mach-imx/mu.c diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index 29b15098f26b..d4c5cd99ca18 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -50,6 +50,9 @@ config HAVE_IMX_DDRC config HAVE_IMX_BUSFREQ bool +config HAVE_IMX_MU + bool + config HAVE_IMX_SRC def_bool y if SMP select ARCH_HAS_RESET_CONTROLLER @@ -519,6 +522,8 @@ config SOC_IMX6SX bool "i.MX6 SoloX support" select PINCTRL_IMX6SX select SOC_IMX6 + select HAVE_IMX_MU + select KEYBOARD_SNVS_PWRKEY help This enables support for Freescale i.MX6 SoloX processor. @@ -554,6 +559,8 @@ config SOC_IMX7D_CA7 select HAVE_IMX_SRC select IMX_GPCV2 select HAVE_IMX_DDRC + select HAVE_IMX_MU + select KEYBOARD_SNVS_PWRKEY config SOC_IMX7D_CM4 bool diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index c24cdd8af09a..4e8c2e8f9a73 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o obj-$(CONFIG_HAVE_IMX_MMDC) += mmdc.o obj-$(CONFIG_HAVE_IMX_SRC) += src.o obj-$(CONFIG_HAVE_IMX_DDRC) += ddrc.o +obj-$(CONFIG_HAVE_IMX_MU) += mu.o ifneq ($(CONFIG_SOC_IMX6)$(CONFIG_SOC_LS1021A),) AFLAGS_headsmp.o :=-Wa,-march=armv7-a obj-$(CONFIG_SMP) += headsmp.o platsmp.o diff --git a/arch/arm/mach-imx/anatop.c b/arch/arm/mach-imx/anatop.c index 777d8c255501..c674ab94cc3f 100644 --- a/arch/arm/mach-imx/anatop.c +++ b/arch/arm/mach-imx/anatop.c @@ -4,6 +4,7 @@ * Copyright 2017-2018 NXP. */ +#include #include #include #include @@ -16,38 +17,63 @@ #define REG_SET 0x4 #define REG_CLR 0x8 +#define ANADIG_ARM_PLL 0x60 +#define ANADIG_DDR_PLL 0x70 +#define ANADIG_SYS_PLL 0xb0 +#define ANADIG_ENET_PLL 0xe0 +#define ANADIG_AUDIO_PLL 0xf0 +#define ANADIG_VIDEO_PLL 0x130 + #define ANADIG_REG_2P5 0x130 #define ANADIG_REG_CORE 0x140 #define ANADIG_ANA_MISC0 0x150 #define ANADIG_USB1_CHRG_DETECT 0x1b0 #define ANADIG_USB2_CHRG_DETECT 0x210 +#define ANADIG_ANA_MISC2 0x170 #define ANADIG_DIGPROG 0x260 #define ANADIG_DIGPROG_IMX6SL 0x280 #define ANADIG_DIGPROG_IMX7D 0x800 -#define SRC_SBMR2 0x1c - #define BM_ANADIG_REG_2P5_ENABLE_WEAK_LINREG 0x40000 #define BM_ANADIG_REG_2P5_ENABLE_PULLDOWN 0x8 #define BM_ANADIG_REG_CORE_FET_ODRIVE 0x20000000 +#define BM_ANADIG_REG_CORE_REG1 (0x1f << 9) +#define BM_ANADIG_REG_CORE_REG2 (0x1f << 18) +#define BP_ANADIG_REG_CORE_REG2 (18) #define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG 0x1000 +#define BM_ANADIG_ANA_MISC0_V2_STOP_MODE_CONFIG 0x800 +#define BM_ANADIG_ANA_MISC0_V3_STOP_MODE_CONFIG 0xc00 +#define BM_ANADIG_ANA_MISC2_REG1_STEP_TIME (0x3 << 26) +#define BP_ANADIG_ANA_MISC2_REG1_STEP_TIME (26) /* Below MISC0_DISCON_HIGH_SNVS is only for i.MX6SL */ #define BM_ANADIG_ANA_MISC0_DISCON_HIGH_SNVS 0x2000 +/* Since i.MX6SX, DISCON_HIGH_SNVS is changed to bit 12 */ +#define BM_ANADIG_ANA_MISC0_V2_DISCON_HIGH_SNVS 0x1000 #define BM_ANADIG_USB_CHRG_DETECT_CHK_CHRG_B 0x80000 #define BM_ANADIG_USB_CHRG_DETECT_EN_B 0x100000 +#define LDO_RAMP_UP_UNIT_IN_CYCLES 64 /* 64 cycles per step */ +#define LDO_RAMP_UP_FREQ_IN_MHZ 24 /* cycle based on 24M OSC */ + static struct regmap *anatop; static void imx_anatop_enable_weak2p5(bool enable) { - u32 reg, val; + u32 reg, val, mask; regmap_read(anatop, ANADIG_ANA_MISC0, &val); + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() + || cpu_is_imx6sll()) + mask = BM_ANADIG_ANA_MISC0_V3_STOP_MODE_CONFIG; + else if (cpu_is_imx6sl()) + mask = BM_ANADIG_ANA_MISC0_V2_STOP_MODE_CONFIG; + else + mask = BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG; + /* can only be enabled when stop_mode_config is clear. */ reg = ANADIG_REG_2P5; - reg += (enable && (val & BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG) == 0) ? - REG_SET : REG_CLR; + reg += (enable && (val & mask) == 0) ? REG_SET : REG_CLR; regmap_write(anatop, reg, BM_ANADIG_REG_2P5_ENABLE_WEAK_LINREG); } @@ -65,35 +91,89 @@ static inline void imx_anatop_enable_2p5_pulldown(bool enable) static inline void imx_anatop_disconnect_high_snvs(bool enable) { - regmap_write(anatop, ANADIG_ANA_MISC0 + (enable ? REG_SET : REG_CLR), - BM_ANADIG_ANA_MISC0_DISCON_HIGH_SNVS); + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6sll()) + regmap_write(anatop, ANADIG_ANA_MISC0 + + (enable ? REG_SET : REG_CLR), + BM_ANADIG_ANA_MISC0_V2_DISCON_HIGH_SNVS); + else + regmap_write(anatop, ANADIG_ANA_MISC0 + + (enable ? REG_SET : REG_CLR), + BM_ANADIG_ANA_MISC0_DISCON_HIGH_SNVS); +} + +static void imx_anatop_disable_pu(bool off) +{ + u32 val, soc, delay; + if (off) { + regmap_read(anatop, ANADIG_REG_CORE, &val); + val &= ~BM_ANADIG_REG_CORE_REG1; + regmap_write(anatop, ANADIG_REG_CORE, val); + } else { + /* track vddpu with vddsoc */ + regmap_read(anatop, ANADIG_REG_CORE, &val); + soc = val & BM_ANADIG_REG_CORE_REG2; + val &= ~BM_ANADIG_REG_CORE_REG1; + val |= soc >> 9; + regmap_write(anatop, ANADIG_REG_CORE, val); + /* wait PU LDO ramp */ + regmap_read(anatop, ANADIG_ANA_MISC2, &val); + val &= BM_ANADIG_ANA_MISC2_REG1_STEP_TIME; + val >>= BP_ANADIG_ANA_MISC2_REG1_STEP_TIME; + delay = (soc >> BP_ANADIG_REG_CORE_REG2) * + (LDO_RAMP_UP_UNIT_IN_CYCLES << val) / + LDO_RAMP_UP_FREQ_IN_MHZ + 1; + udelay(delay); + } } void imx_anatop_pre_suspend(void) { - if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2) - imx_anatop_enable_2p5_pulldown(true); - else - imx_anatop_enable_weak2p5(true); + if (cpu_is_imx7d()) { + /* PLL and PFDs overwrite set */ + regmap_write(anatop, ANADIG_ARM_PLL + REG_SET, 1 << 20); + regmap_write(anatop, ANADIG_DDR_PLL + REG_SET, 1 << 19); + regmap_write(anatop, ANADIG_SYS_PLL + REG_SET, 0x1ff << 17); + regmap_write(anatop, ANADIG_ENET_PLL + REG_SET, 1 << 13); + regmap_write(anatop, ANADIG_AUDIO_PLL + REG_SET, 1 << 24); + regmap_write(anatop, ANADIG_VIDEO_PLL + REG_SET, 1 << 24); + return; + } + + if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) + imx_anatop_disable_pu(true); + imx_anatop_enable_weak2p5(true); imx_anatop_enable_fet_odrive(true); - if (cpu_is_imx6sl()) + if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() || + cpu_is_imx6ull() || cpu_is_imx6sll()) imx_anatop_disconnect_high_snvs(true); } void imx_anatop_post_resume(void) { - if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2) - imx_anatop_enable_2p5_pulldown(false); - else - imx_anatop_enable_weak2p5(false); + if (cpu_is_imx7d()) { + /* PLL and PFDs overwrite clear */ + regmap_write(anatop, ANADIG_ARM_PLL + REG_CLR, 1 << 20); + regmap_write(anatop, ANADIG_DDR_PLL + REG_CLR, 1 << 19); + regmap_write(anatop, ANADIG_SYS_PLL + REG_CLR, 0x1ff << 17); + regmap_write(anatop, ANADIG_ENET_PLL + REG_CLR, 1 << 13); + regmap_write(anatop, ANADIG_AUDIO_PLL + REG_CLR, 1 << 24); + regmap_write(anatop, ANADIG_VIDEO_PLL + REG_CLR, 1 << 24); + return; + } + + if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) + imx_anatop_disable_pu(false); + + imx_anatop_enable_weak2p5(false); imx_anatop_enable_fet_odrive(false); - if (cpu_is_imx6sl()) + if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() || + cpu_is_imx6ull() || cpu_is_imx6sll()) imx_anatop_disconnect_high_snvs(false); - } static void imx_anatop_usb_chrg_detect_disable(void) @@ -110,10 +190,11 @@ void __init imx_init_revision_from_anatop(void) { struct device_node *np; void __iomem *anatop_base; + void __iomem *src_base; unsigned int revision; - u32 digprog; + u32 digprog, sbmr2 = 0; u16 offset = ANADIG_DIGPROG; - u8 major_part, minor_part; + u16 major_part, minor_part; np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop"); anatop_base = of_iomap(np, 0); @@ -125,6 +206,20 @@ void __init imx_init_revision_from_anatop(void) digprog = readl_relaxed(anatop_base + offset); iounmap(anatop_base); + if ((digprog >> 16) == MXC_CPU_IMX6ULL) { + np = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-src"); + if (np) { + src_base = of_iomap(np, 0); + WARN_ON(!src_base); + sbmr2 = readl_relaxed(src_base + 0x1c); + iounmap(src_base); + } + if (sbmr2 & (1 << 6)) { + digprog &= ~(0xff << 16); + digprog |= (MXC_CPU_IMX6ULZ << 16); + } + } + /* * On i.MX7D digprog value match linux version format, so * it needn't map again and we can use register value directly. @@ -144,24 +239,6 @@ void __init imx_init_revision_from_anatop(void) major_part = (digprog >> 8) & 0xf; minor_part = digprog & 0xf; revision = ((major_part + 1) << 4) | minor_part; - - if ((digprog >> 16) == MXC_CPU_IMX6ULL) { - void __iomem *src_base; - u32 sbmr2; - - np = of_find_compatible_node(NULL, NULL, - "fsl,imx6ul-src"); - src_base = of_iomap(np, 0); - WARN_ON(!src_base); - sbmr2 = readl_relaxed(src_base + SRC_SBMR2); - iounmap(src_base); - - /* src_sbmr2 bit 6 is to identify if it is i.MX6ULZ */ - if (sbmr2 & (1 << 6)) { - digprog &= ~(0xff << 16); - digprog |= (MXC_CPU_IMX6ULZ << 16); - } - } } mxc_set_cpu_type(digprog >> 16 & 0xff); diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index b6395f68d483..49f3456aa323 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -8,6 +8,7 @@ #define __ASM_ARCH_MXC_COMMON_H__ #include +#include struct irq_data; struct platform_device; @@ -89,6 +90,29 @@ void imx_smp_prepare(void); static inline void imx_scu_map_io(void) {} static inline void imx_smp_prepare(void) {} #endif +void imx6sx_set_m4_highfreq(bool high_freq); +void imx_mu_enable_m4_irqs_in_gic(bool enable); +#ifdef CONFIG_HAVE_IMX_GPC +void imx_gpc_add_m4_wake_up_irq(u32 irq, bool enable); +unsigned int imx_gpc_is_m4_sleeping(void); +#else +static inline void imx_gpc_add_m4_wake_up_irq(u32 irq, bool enable) {} +static inline unsigned int imx_gpc_is_m4_sleeping(void) { return 0; } +#endif +#ifdef CONFIG_HAVE_IMX_GPCV2 +int imx_gpcv2_mf_power_on(unsigned int irq, unsigned int on); +void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn); +void imx_gpcv2_add_m4_wake_up_irq(u32 hwirq, bool enable); +#else +static inline int imx_gpcv2_mf_power_on(unsigned int irq, unsigned int on) { return 0; } +static inline void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn) {} +static inline void imx_gpcv2_add_m4_wake_up_irq(u32 hwirq, bool enable) {} +#endif +void imx_gpc_hold_m4_in_sleep(void); +void imx_gpc_release_m4_in_sleep(void); +bool imx_mu_is_m4_in_low_freq(void); +bool imx_mu_is_m4_in_stop(void); +void imx_mu_set_m4_run_mode(void); void imx_src_init(void); void imx_gpc_pre_suspend(bool arm_power_off); void imx_gpc_post_resume(void); @@ -96,13 +120,20 @@ void imx_gpc_mask_all(void); void imx_gpc_restore_all(void); void imx_gpc_hwirq_mask(unsigned int hwirq); void imx_gpc_hwirq_unmask(unsigned int hwirq); +unsigned int imx_gpc_is_mf_mix_off(void); void imx_anatop_init(void); void imx_anatop_pre_suspend(void); void imx_anatop_post_resume(void); int imx6_set_lpm(enum mxc_cpu_pwr_mode mode); void imx6_set_int_mem_clk_lpm(bool enable); void imx6sl_set_wait_clk(bool enter); +#ifdef CONFIG_HAVE_IMX_MMDC int imx_mmdc_get_ddr_type(void); +int imx_mmdc_get_lpddr2_2ch_mode(void); +#else +static inline int imx_mmdc_get_ddr_type(void) { return 0; } +static inline int imx_mmdc_get_lpddr2_2ch_mode(void) { return 0; } +#endif int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode); void imx_busfreq_map_io(void); void imx7_pm_map_io(void); diff --git a/arch/arm/mach-imx/gpc.c b/arch/arm/mach-imx/gpc.c index b5b557fe2c49..71484c457f14 100644 --- a/arch/arm/mach-imx/gpc.c +++ b/arch/arm/mach-imx/gpc.c @@ -14,22 +14,157 @@ #include "common.h" #include "hardware.h" -#define GPC_CNTR 0x0 +#define GPC_CNTR 0x000 +#define GPC_CNTR_L2_PGE 22 + #define GPC_IMR1 0x008 +#define GPC_PGC_MF_PDN 0x220 #define GPC_PGC_CPU_PDN 0x2a0 #define GPC_PGC_CPU_PUPSCR 0x2a4 #define GPC_PGC_CPU_PDNSCR 0x2a8 #define GPC_PGC_SW2ISO_SHIFT 0x8 #define GPC_PGC_SW_SHIFT 0x0 +#define GPC_M4_LPSR 0x2c +#define GPC_M4_LPSR_M4_SLEEPING_SHIFT 4 +#define GPC_M4_LPSR_M4_SLEEPING_MASK 0x1 +#define GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_MASK 0x1 +#define GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_SHIFT 0 +#define GPC_M4_LPSR_M4_SLEEP_HOLD_ACK_MASK 0x1 +#define GPC_M4_LPSR_M4_SLEEP_HOLD_ACK_SHIFT 1 -#define GPC_CNTR_L2_PGE_SHIFT 22 +#define GPC_PGC_CPU_SW_SHIFT 0 +#define GPC_PGC_CPU_SW_MASK 0x3f +#define GPC_PGC_CPU_SW2ISO_SHIFT 8 +#define GPC_PGC_CPU_SW2ISO_MASK 0x3f #define IMR_NUM 4 #define GPC_MAX_IRQS (IMR_NUM * 32) +/* for irq #74 and #75 */ +#define GPC_USB_VBUS_WAKEUP_IRQ_MASK 0xc00 + +/* for irq #150 and #151 */ +#define GPC_ENET_WAKEUP_IRQ_MASK 0xC00000 + static void __iomem *gpc_base; static u32 gpc_wake_irqs[IMR_NUM]; static u32 gpc_saved_imrs[IMR_NUM]; +static u32 gpc_mf_irqs[IMR_NUM]; +static u32 gpc_mf_request_on[IMR_NUM]; +static DEFINE_SPINLOCK(gpc_lock); + +/* implemented in drivers/soc/imx/gpc.c */ +extern void _imx6_pm_pu_power_off(void); +extern void _imx6_pm_pu_power_on(void); + +void imx_gpc_add_m4_wake_up_irq(u32 hwirq, bool enable) +{ + unsigned int idx = hwirq / 32; + unsigned long flags; + u32 mask; + + /* Sanity check for SPI irq */ + if (hwirq < 32) + return; + + mask = 1 << hwirq % 32; + spin_lock_irqsave(&gpc_lock, flags); + gpc_wake_irqs[idx] = enable ? gpc_wake_irqs[idx] | mask : + gpc_wake_irqs[idx] & ~mask; + spin_unlock_irqrestore(&gpc_lock, flags); +} + +void imx_gpc_hold_m4_in_sleep(void) +{ + int val; + unsigned long timeout = jiffies + msecs_to_jiffies(500); + + /* wait M4 in wfi before asserting hold request */ + while (!imx_gpc_is_m4_sleeping()) + if (time_after(jiffies, timeout)) + pr_err("M4 is NOT in expected sleep!\n"); + + val = readl_relaxed(gpc_base + GPC_M4_LPSR); + val &= ~(GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_MASK << + GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_SHIFT); + writel_relaxed(val, gpc_base + GPC_M4_LPSR); + + timeout = jiffies + msecs_to_jiffies(500); + while (readl_relaxed(gpc_base + GPC_M4_LPSR) + & (GPC_M4_LPSR_M4_SLEEP_HOLD_ACK_MASK << + GPC_M4_LPSR_M4_SLEEP_HOLD_ACK_SHIFT)) + if (time_after(jiffies, timeout)) + pr_err("Wait M4 hold ack timeout!\n"); +} + +void imx_gpc_release_m4_in_sleep(void) +{ + int val; + + val = readl_relaxed(gpc_base + GPC_M4_LPSR); + val |= GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_MASK << + GPC_M4_LPSR_M4_SLEEP_HOLD_REQ_SHIFT; + writel_relaxed(val, gpc_base + GPC_M4_LPSR); +} + +unsigned int imx_gpc_is_m4_sleeping(void) +{ + if (readl_relaxed(gpc_base + GPC_M4_LPSR) & + (GPC_M4_LPSR_M4_SLEEPING_MASK << + GPC_M4_LPSR_M4_SLEEPING_SHIFT)) + return 1; + + return 0; +} + +bool imx_gpc_usb_wakeup_enabled(void) +{ + if (!(cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() + || cpu_is_imx6sll())) + return false; + + /* + * for SoC later than i.MX6SX, USB vbus wakeup + * only needs weak 2P5 on, stop_mode_config is + * NOT needed, so we check if is USB vbus wakeup + * is enabled(assume irq #74 and #75) to decide + * if to keep weak 2P5 on. + */ + if (gpc_wake_irqs[1] & GPC_USB_VBUS_WAKEUP_IRQ_MASK) + return true; + + return false; +} + +bool imx_gpc_enet_wakeup_enabled(void) +{ + if (!cpu_is_imx6q()) + return false; + + if (gpc_wake_irqs[3] & GPC_ENET_WAKEUP_IRQ_MASK) + return true; + + return false; +} + +unsigned int imx_gpc_is_mf_mix_off(void) +{ + return readl_relaxed(gpc_base + GPC_PGC_MF_PDN); +} + +static void imx_gpc_mf_mix_off(void) +{ + int i; + + for (i = 0; i < IMR_NUM; i++) + if (((gpc_wake_irqs[i] | gpc_mf_request_on[i]) & + gpc_mf_irqs[i]) != 0) + return; + + pr_info("Turn off M/F mix!\n"); + /* turn off mega/fast mix */ + writel_relaxed(0x1, gpc_base + GPC_PGC_MF_PDN); +} void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw) { @@ -53,9 +188,9 @@ void imx_gpc_set_l2_mem_power_in_lpm(bool power_off) u32 val; val = readl_relaxed(gpc_base + GPC_CNTR); - val &= ~(1 << GPC_CNTR_L2_PGE_SHIFT); + val &= ~(1 << GPC_CNTR_L2_PGE); if (power_off) - val |= 1 << GPC_CNTR_L2_PGE_SHIFT; + val |= 1 << GPC_CNTR_L2_PGE; writel_relaxed(val, gpc_base + GPC_CNTR); } @@ -64,6 +199,14 @@ void imx_gpc_pre_suspend(bool arm_power_off) void __iomem *reg_imr1 = gpc_base + GPC_IMR1; int i; + if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) + _imx6_pm_pu_power_off(); + + /* power down the mega-fast power domain */ + if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() + || cpu_is_imx6sll()) && arm_power_off) + imx_gpc_mf_mix_off(); + /* Tell GPC to power off ARM core when suspend */ if (arm_power_off) imx_gpc_set_arm_power_in_lpm(arm_power_off); @@ -79,8 +222,15 @@ void imx_gpc_post_resume(void) void __iomem *reg_imr1 = gpc_base + GPC_IMR1; int i; + if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) + _imx6_pm_pu_power_on(); + /* Keep ARM core powered on for other low-power modes */ imx_gpc_set_arm_power_in_lpm(false); + /* Keep M/F mix powered on for other low-power modes */ + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() + || cpu_is_imx6sll()) + writel_relaxed(0x0, gpc_base + GPC_PGC_MF_PDN); for (i = 0; i < IMR_NUM; i++) writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); @@ -89,11 +239,14 @@ void imx_gpc_post_resume(void) static int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on) { unsigned int idx = d->hwirq / 32; + unsigned long flags; u32 mask; mask = 1 << d->hwirq % 32; + spin_lock_irqsave(&gpc_lock, flags); gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask : gpc_wake_irqs[idx] & ~mask; + spin_unlock_irqrestore(&gpc_lock, flags); /* * Do *not* call into the parent, as the GIC doesn't have any @@ -225,11 +378,78 @@ static const struct irq_domain_ops imx_gpc_domain_ops = { .free = irq_domain_free_irqs_common, }; +int imx_gpc_mf_power_on(unsigned int irq, unsigned int on) +{ + struct irq_desc *d = irq_to_desc(irq); + unsigned int idx = d->irq_data.hwirq / 32; + unsigned long flags; + u32 mask; + + mask = 1 << (d->irq_data.hwirq % 32); + spin_lock_irqsave(&gpc_lock, flags); + gpc_mf_request_on[idx] = on ? gpc_mf_request_on[idx] | mask : + gpc_mf_request_on[idx] & ~mask; + spin_unlock_irqrestore(&gpc_lock, flags); + + return 0; +} + +int imx_gpc_mf_request_on(unsigned int irq, unsigned int on) +{ + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() + || cpu_is_imx6sll()) + return imx_gpc_mf_power_on(irq, on); + else if (cpu_is_imx7d()) + return imx_gpcv2_mf_power_on(irq, on); + else + return 0; +} +EXPORT_SYMBOL_GPL(imx_gpc_mf_request_on); + +void imx_gpc_switch_pupscr_clk(bool flag) +{ + static u32 pupscr_sw2iso, pupscr_sw; + u32 ratio, pupscr = readl_relaxed(gpc_base + GPC_PGC_CPU_PUPSCR); + + if (flag) { + /* save the init clock setting IPG/2048 for IPG@66Mhz */ + pupscr_sw2iso = (pupscr >> GPC_PGC_CPU_SW2ISO_SHIFT) & + GPC_PGC_CPU_SW2ISO_MASK; + pupscr_sw = (pupscr >> GPC_PGC_CPU_SW_SHIFT) & + GPC_PGC_CPU_SW_MASK; + /* + * i.MX6UL TO1.0 ARM power up uses IPG/2048 as clock source, + * from TO1.1, PGC_CPU_PUPSCR bit [5] is re-defined to switch + * clock to IPG/32, enable this bit to speed up the ARM power + * up process in low power idle case(IPG@1.5Mhz). So the sw and + * sw2iso need to be adjusted as below: + * sw_new(sw2iso_new) = (2048 * 1.5 / 66 * 32) * sw(sw2iso) + */ + ratio = 3072 / (66 * 32); + pupscr &= ~(GPC_PGC_CPU_SW_MASK << GPC_PGC_CPU_SW_SHIFT | + GPC_PGC_CPU_SW2ISO_MASK << GPC_PGC_CPU_SW2ISO_SHIFT); + pupscr |= (ratio * pupscr_sw + 1) << GPC_PGC_CPU_SW_SHIFT | + 1 << 5 | (ratio * pupscr_sw2iso + 1) << + GPC_PGC_CPU_SW2ISO_SHIFT; + writel_relaxed(pupscr, gpc_base + GPC_PGC_CPU_PUPSCR); + } else { + /* restore back after exit from low power idle */ + pupscr &= ~(GPC_PGC_CPU_SW_MASK << GPC_PGC_CPU_SW_SHIFT | + GPC_PGC_CPU_SW2ISO_MASK << GPC_PGC_CPU_SW2ISO_SHIFT); + pupscr |= pupscr_sw << GPC_PGC_CPU_SW_SHIFT | + pupscr_sw2iso << GPC_PGC_CPU_SW2ISO_SHIFT; + writel_relaxed(pupscr, gpc_base + GPC_PGC_CPU_PUPSCR); + } +} + static int __init imx_gpc_init(struct device_node *node, struct device_node *parent) { struct irq_domain *parent_domain, *domain; int i; + u32 val; + u32 cpu_pupscr_sw2iso, cpu_pupscr_sw; + u32 cpu_pdnscr_iso2sw, cpu_pdnscr_iso; if (!parent) { pr_err("%pOF: no parent, giving up\n", node); @@ -258,12 +478,70 @@ static int __init imx_gpc_init(struct device_node *node, for (i = 0; i < IMR_NUM; i++) writel_relaxed(~0, gpc_base + GPC_IMR1 + i * 4); + /* Read supported wakeup source in M/F domain */ + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() + || cpu_is_imx6sll()) { + of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 0, + &gpc_mf_irqs[0]); + of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 1, + &gpc_mf_irqs[1]); + of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 2, + &gpc_mf_irqs[2]); + of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 3, + &gpc_mf_irqs[3]); + if (!(gpc_mf_irqs[0] | gpc_mf_irqs[1] | + gpc_mf_irqs[2] | gpc_mf_irqs[3])) + pr_info("No wakeup source in Mega/Fast domain found!\n"); + } + + /* clear the L2_PGE bit on i.MX6SLL */ + if (cpu_is_imx6sll()) { + val = readl_relaxed(gpc_base + GPC_CNTR); + val &= ~(1 << GPC_CNTR_L2_PGE); + writel_relaxed(val, gpc_base + GPC_CNTR); + } + /* * Clear the OF_POPULATED flag set in of_irq_init so that * later the GPC power domain driver will not be skipped. */ of_node_clear_flag(node, OF_POPULATED); + /* + * If there are CPU isolation timing settings in dts, + * update them according to dts, otherwise, keep them + * with default value in registers. + */ + cpu_pupscr_sw2iso = cpu_pupscr_sw = + cpu_pdnscr_iso2sw = cpu_pdnscr_iso = 0; + + /* Read CPU isolation setting for GPC */ + of_property_read_u32(node, "fsl,cpu_pupscr_sw2iso", &cpu_pupscr_sw2iso); + of_property_read_u32(node, "fsl,cpu_pupscr_sw", &cpu_pupscr_sw); + of_property_read_u32(node, "fsl,cpu_pdnscr_iso2sw", &cpu_pdnscr_iso2sw); + of_property_read_u32(node, "fsl,cpu_pdnscr_iso", &cpu_pdnscr_iso); + + /* Return if no property found in dtb */ + if ((cpu_pupscr_sw2iso | cpu_pupscr_sw + | cpu_pdnscr_iso2sw | cpu_pdnscr_iso) == 0) + return 0; + + /* Update CPU PUPSCR timing if it is defined in dts */ + val = readl_relaxed(gpc_base + GPC_PGC_CPU_PUPSCR); + val &= ~(GPC_PGC_CPU_SW2ISO_MASK << GPC_PGC_CPU_SW2ISO_SHIFT); + val &= ~(GPC_PGC_CPU_SW_MASK << GPC_PGC_CPU_SW_SHIFT); + val |= cpu_pupscr_sw2iso << GPC_PGC_CPU_SW2ISO_SHIFT; + val |= cpu_pupscr_sw << GPC_PGC_CPU_SW_SHIFT; + writel_relaxed(val, gpc_base + GPC_PGC_CPU_PUPSCR); + + /* Update CPU PDNSCR timing if it is defined in dts */ + val = readl_relaxed(gpc_base + GPC_PGC_CPU_PDNSCR); + val &= ~(GPC_PGC_CPU_SW2ISO_MASK << GPC_PGC_CPU_SW2ISO_SHIFT); + val &= ~(GPC_PGC_CPU_SW_MASK << GPC_PGC_CPU_SW_SHIFT); + val |= cpu_pdnscr_iso2sw << GPC_PGC_CPU_SW2ISO_SHIFT; + val |= cpu_pdnscr_iso << GPC_PGC_CPU_SW_SHIFT; + writel_relaxed(val, gpc_base + GPC_PGC_CPU_PDNSCR); + return 0; } IRQCHIP_DECLARE(imx_gpc, "fsl,imx6q-gpc", imx_gpc_init); diff --git a/arch/arm/mach-imx/mmdc.c b/arch/arm/mach-imx/mmdc.c index 0dfd0ae7a63d..8599936fc001 100644 --- a/arch/arm/mach-imx/mmdc.c +++ b/arch/arm/mach-imx/mmdc.c @@ -59,6 +59,7 @@ #define to_mmdc_pmu(p) container_of(p, struct mmdc_pmu, pmu) static int ddr_type; +static int lpddr2_2ch_mode; struct fsl_mmdc_devtype_data { unsigned int flags; @@ -575,6 +576,11 @@ int imx_mmdc_get_ddr_type(void) return ddr_type; } +int imx_mmdc_get_lpddr2_2ch_mode(void) +{ + return lpddr2_2ch_mode; +} + static struct platform_driver imx_mmdc_driver = { .driver = { .name = "imx-mmdc", diff --git a/arch/arm/mach-imx/mu.c b/arch/arm/mach-imx/mu.c new file mode 100644 index 000000000000..4ab7ef2f9d62 --- /dev/null +++ b/arch/arm/mach-imx/mu.c @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "hardware.h" + +#define MU_ATR0_OFFSET 0x0 +#define MU_ARR0_OFFSET 0x10 +#define MU_ARR1_OFFSET 0x14 +#define MU_ASR 0x20 +#define MU_ACR 0x24 +#define MX7ULP_MU_TR0 0x20 +#define MX7ULP_MU_RR0 0x40 +#define MX7ULP_MU_RR1 0x44 +#define MX7ULP_MU_SR 0x60 +#define MX7ULP_MU_CR 0x64 + +#define MU_LPM_HANDSHAKE_INDEX 0 +#define MU_RPMSG_HANDSHAKE_INDEX 1 +#define MU_LPM_BUS_HIGH_READY_FOR_M4 0xFFFF6666 +#define MU_LPM_M4_FREQ_CHANGE_READY 0xFFFF7777 +#define MU_LPM_M4_REQUEST_HIGH_BUS 0x2222CCCC +#define MU_LPM_M4_RELEASE_HIGH_BUS 0x2222BBBB +#define MU_LPM_M4_WAKEUP_SRC_VAL 0x55555000 +#define MU_LPM_M4_WAKEUP_SRC_MASK 0xFFFFF000 +#define MU_LPM_M4_WAKEUP_IRQ_MASK 0xFF0 +#define MU_LPM_M4_WAKEUP_IRQ_SHIFT 0x4 +#define MU_LPM_M4_WAKEUP_ENABLE_MASK 0xF +#define MU_LPM_M4_WAKEUP_ENABLE_SHIFT 0x0 + +#define MU_LPM_M4_RUN_MODE 0x5A5A0001 +#define MU_LPM_M4_WAIT_MODE 0x5A5A0002 +#define MU_LPM_M4_STOP_MODE 0x5A5A0003 + +#define MAX_NUM 10 /* enlarge it if overflow happen */ + +static void __iomem *mu_base; +static u32 m4_message[MAX_NUM]; +static u32 in_idx, out_idx; +static struct delayed_work mu_work; +static u32 m4_wake_irqs[4]; +static bool m4_freq_low; +struct irq_domain *domain; +static bool m4_in_stop; +static struct clk *clk; +static DEFINE_SPINLOCK(mu_lock); + +void imx_mu_set_m4_run_mode(void) +{ + m4_in_stop = false; +} + +bool imx_mu_is_m4_in_stop(void) +{ + return m4_in_stop; +} + +bool imx_mu_is_m4_in_low_freq(void) +{ + return m4_freq_low; +} + +void imx_mu_enable_m4_irqs_in_gic(bool enable) +{ + int i, j; + + for (i = 0; i < 4; i++) { + if (m4_wake_irqs[i] == 0) + continue; + for (j = 0; j < 32; j++) { + if (m4_wake_irqs[i] & (1 << j)) { + if (enable) + enable_irq(irq_find_mapping( + domain, i * 32 + j)); + else + disable_irq(irq_find_mapping( + domain, i * 32 + j)); + } + } + } +} + +static irqreturn_t mcc_m4_dummy_isr(int irq, void *param) +{ + return IRQ_HANDLED; +} + +static int imx_mu_send_message(unsigned int index, unsigned int data) +{ + u32 val, ep; + int i, te_flag = 0; + unsigned long timeout = jiffies + msecs_to_jiffies(500); + + /* wait for transfer buffer empty, and no event pending */ + do { + if (cpu_is_imx7ulp()) + val = readl_relaxed(mu_base + MX7ULP_MU_SR); + else + val = readl_relaxed(mu_base + MU_ASR); + ep = val & BIT(4); + if (time_after(jiffies, timeout)) { + pr_err("Waiting MU transmit buffer empty timeout!\n"); + return -EIO; + } + } while (((val & (1 << (20 + 3 - index))) == 0) || (ep == BIT(4))); + + if (cpu_is_imx7ulp()) + writel_relaxed(data, mu_base + index * 0x4 + MX7ULP_MU_TR0); + else + writel_relaxed(data, mu_base + index * 0x4 + MU_ATR0_OFFSET); + + /* + * make a double check that TEn is not empty after write + */ + if (cpu_is_imx7ulp()) + val = readl_relaxed(mu_base + MX7ULP_MU_SR); + else + val = readl_relaxed(mu_base + MU_ASR); + ep = val & BIT(4); + if (((val & (1 << (20 + (3 - index)))) == 0) || (ep == BIT(4))) + return 0; + else + te_flag = 1; + + /* + * Make sure that TEn flag is changed, after the ATRn is filled up. + */ + for (i = 0; i < 100; i++) { + if (cpu_is_imx7ulp()) + val = readl_relaxed(mu_base + MX7ULP_MU_SR); + else + val = readl_relaxed(mu_base + MU_ASR); + ep = val & BIT(4); + if (((val & (1 << (20 + 3 - index))) == 0) || (ep == BIT(4))) { + /* + * BUG here. TEn flag is changes, after the + * ATRn is filled with MSG for a while. + */ + te_flag = 0; + break; + } else if (time_after(jiffies, timeout)) { + /* Can't see TEn 1->0, maybe already handled! */ + te_flag = 1; + break; + } + } + if (te_flag == 0) + pr_info("BUG: TEn is not changed immediately" + "when ATRn is filled up.\n"); + + return 0; +} + +static void mu_work_handler(struct work_struct *work) +{ + int ret; + u32 irq, enable, idx, mask, virq; + struct of_phandle_args args; + u32 message; + unsigned long flags; + + spin_lock_irqsave(&mu_lock, flags); + message = m4_message[out_idx % MAX_NUM]; + spin_unlock_irqrestore(&mu_lock, flags); + + pr_debug("receive M4 message 0x%x\n", message); + + switch (message) { + case MU_LPM_M4_RUN_MODE: + case MU_LPM_M4_WAIT_MODE: + m4_in_stop = false; + break; + case MU_LPM_M4_STOP_MODE: + m4_in_stop = true; + break; + case MU_LPM_M4_REQUEST_HIGH_BUS: + request_bus_freq(BUS_FREQ_HIGH); +#ifdef CONFIG_SOC_IMX6SX + if (cpu_is_imx6sx()) + imx6sx_set_m4_highfreq(true); +#endif + imx_mu_send_message(MU_LPM_HANDSHAKE_INDEX, + MU_LPM_BUS_HIGH_READY_FOR_M4); + m4_freq_low = false; + break; + case MU_LPM_M4_RELEASE_HIGH_BUS: + release_bus_freq(BUS_FREQ_HIGH); +#ifdef CONFIG_SOC_IMX6SX + if (cpu_is_imx6sx()) { + imx6sx_set_m4_highfreq(false); + imx_mu_send_message(MU_LPM_HANDSHAKE_INDEX, + MU_LPM_M4_FREQ_CHANGE_READY); + } +#endif + m4_freq_low = true; + break; + default: + if ((message & MU_LPM_M4_WAKEUP_SRC_MASK) == + MU_LPM_M4_WAKEUP_SRC_VAL) { + irq = (message & MU_LPM_M4_WAKEUP_IRQ_MASK) >> + MU_LPM_M4_WAKEUP_IRQ_SHIFT; + + enable = (message & MU_LPM_M4_WAKEUP_ENABLE_MASK) >> + MU_LPM_M4_WAKEUP_ENABLE_SHIFT; + + /* to hwirq start from 0 */ + irq -= 32; + + idx = irq / 32; + mask = 1 << irq % 32; + + args.np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-gpc"); + args.args_count = 3; + args.args[0] = 0; + args.args[1] = irq; + args.args[2] = IRQ_TYPE_LEVEL_HIGH; + + virq = irq_create_of_mapping(&args); + + if (enable && can_request_irq(virq, 0)) { + ret = request_irq(virq, mcc_m4_dummy_isr, + IRQF_NO_SUSPEND, "imx-m4-dummy", NULL); + if (ret) { + pr_err("%s: register interrupt %d failed, rc %d\n", + __func__, virq, ret); + break; + } + disable_irq(virq); + m4_wake_irqs[idx] = m4_wake_irqs[idx] | mask; + } + imx_gpc_add_m4_wake_up_irq(irq, enable); + } + break; + } + + spin_lock_irqsave(&mu_lock, flags); + m4_message[out_idx % MAX_NUM] = 0; + out_idx++; + spin_unlock_irqrestore(&mu_lock, flags); + + /* enable RIE3 interrupt */ + if (cpu_is_imx7ulp()) + writel_relaxed(readl_relaxed(mu_base + MX7ULP_MU_CR) | BIT(27), + mu_base + MX7ULP_MU_CR); + else + writel_relaxed(readl_relaxed(mu_base + MU_ACR) | BIT(27), + mu_base + MU_ACR); +} + +int imx_mu_lpm_ready(bool ready) +{ + u32 val; + + if (cpu_is_imx7ulp()) { + val = readl_relaxed(mu_base + MX7ULP_MU_CR); + if (ready) + writel_relaxed(val | BIT(0), mu_base + MX7ULP_MU_CR); + else + writel_relaxed(val & ~BIT(0), mu_base + MX7ULP_MU_CR); + } else { + val = readl_relaxed(mu_base + MU_ACR); + if (ready) + writel_relaxed(val | BIT(0), mu_base + MU_ACR); + else + writel_relaxed(val & ~BIT(0), mu_base + MU_ACR); + } + return 0; +} + +static irqreturn_t imx_mu_isr(int irq, void *param) +{ + u32 irqs; + unsigned long flags; + + if (cpu_is_imx7ulp()) + irqs = readl_relaxed(mu_base + MX7ULP_MU_SR); + else + irqs = readl_relaxed(mu_base + MU_ASR); + + if (irqs & (1 << 27)) { + spin_lock_irqsave(&mu_lock, flags); + /* get message from receive buffer */ + if (cpu_is_imx7ulp()) + m4_message[in_idx % MAX_NUM] = readl_relaxed(mu_base + + MX7ULP_MU_RR0); + else + m4_message[in_idx % MAX_NUM] = readl_relaxed(mu_base + + MU_ARR0_OFFSET); + /* disable RIE3 interrupt */ + if (cpu_is_imx7ulp()) + writel_relaxed(readl_relaxed(mu_base + MX7ULP_MU_CR) + & (~BIT(27)), mu_base + MX7ULP_MU_CR); + else + writel_relaxed(readl_relaxed(mu_base + MU_ACR) + & (~BIT(27)), mu_base + MU_ACR); + in_idx++; + if (in_idx == out_idx) { + spin_unlock_irqrestore(&mu_lock, flags); + pr_err("MU overflow!\n"); + return IRQ_HANDLED; + } + spin_unlock_irqrestore(&mu_lock, flags); + + schedule_delayed_work(&mu_work, 0); + } + + return IRQ_HANDLED; +} + +static int imx_mu_probe(struct platform_device *pdev) +{ + int ret; + u32 irq; + struct device_node *np; + struct device *dev = &pdev->dev; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-mu"); + mu_base = of_iomap(np, 0); + WARN_ON(!mu_base); + + ret = of_device_is_compatible(np, "fsl,imx7ulp-mu"); + if (ret) + irq = platform_get_irq(pdev, 1); + else + irq = platform_get_irq(pdev, 0); + ret = request_irq(irq, imx_mu_isr, + IRQF_EARLY_RESUME | IRQF_SHARED, "imx-mu", dev); + if (ret) { + pr_err("%s: register interrupt %d failed, rc %d\n", + __func__, irq, ret); + return ret; + } + + ret = of_device_is_compatible(np, "fsl,imx7d-mu"); + if (ret) { + clk = devm_clk_get(&pdev->dev, "mu"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, + "mu clock source missing or invalid\n"); + return PTR_ERR(clk); + } else { + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, + "unable to enable mu clock\n"); + return ret; + } + } + + /* MU always as a wakeup source for low power mode */ + imx_gpcv2_add_m4_wake_up_irq(irq_to_desc(irq)->irq_data.hwirq, + true); + } else { + /* MU always as a wakeup source for low power mode */ + imx_gpc_add_m4_wake_up_irq(irq_to_desc(irq)->irq_data.hwirq, true); + } + + INIT_DELAYED_WORK(&mu_work, mu_work_handler); + /* bit0 of MX7ULP_MU_CR used to let m4 to know MU is ready now */ + if (cpu_is_imx7ulp()) + writel_relaxed(readl_relaxed(mu_base + MX7ULP_MU_CR) | + BIT(0) | BIT(26) | BIT(27), mu_base + MX7ULP_MU_CR); + else + writel_relaxed(readl_relaxed(mu_base + MU_ACR) | + BIT(26) | BIT(27), mu_base + MU_ACR); + + pr_info("MU is ready for cross core communication!\n"); + + return 0; +} + +static const struct of_device_id imx_mu_ids[] = { + { .compatible = "fsl,imx6sx-mu" }, + { .compatible = "fsl,imx7d-mu" }, + { .compatible = "fsl,imx7ulp-mu" }, + { } +}; + +#ifdef CONFIG_PM_SLEEP +static int mu_suspend(struct device *dev) +{ + return 0; +} + +static int mu_resume(struct device *dev) +{ + if (!cpu_is_imx7ulp()) + return 0; + + writel_relaxed(readl_relaxed(mu_base + MX7ULP_MU_CR) | + BIT(0) | BIT(26) | BIT(27), mu_base + MX7ULP_MU_CR); + + return 0; +} +#endif +static const struct dev_pm_ops mu_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(mu_suspend, mu_resume) +}; + +static struct platform_driver imx_mu_driver = { + .driver = { + .name = "imx-mu", + .owner = THIS_MODULE, + .pm = &mu_pm_ops, + .of_match_table = imx_mu_ids, + }, + .probe = imx_mu_probe, +}; + +static int __init imx_mu_init(void) +{ + return platform_driver_register(&imx_mu_driver); +} +subsys_initcall(imx_mu_init); diff --git a/arch/arm/mach-imx/mxc.h b/arch/arm/mach-imx/mxc.h index fc22dcd0a591..c311f9d48de2 100644 --- a/arch/arm/mach-imx/mxc.h +++ b/arch/arm/mach-imx/mxc.h @@ -38,6 +38,9 @@ #define IMX_DDR_TYPE_LPDDR3 2 #define IMX_MMDC_DDR_TYPE_LPDDR3 3 +#define IMX_LPDDR2_1CH_MODE 0 +#define IMX_LPDDR2_2CH_MODE 1 + #ifndef __ASSEMBLY__ extern unsigned int __mxc_cpu_type; @@ -105,6 +108,11 @@ static inline bool cpu_is_imx7d(void) return __mxc_cpu_type == MXC_CPU_IMX7D; } +static inline bool cpu_is_imx7ulp(void) +{ + return __mxc_cpu_type == MXC_CPU_IMX7ULP; +} + struct cpu_op { u32 cpu_rate; }; diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c index e36d0acfda8a..6681973ee9b5 100644 --- a/arch/arm/mach-imx/pm-imx6.c +++ b/arch/arm/mach-imx/pm-imx6.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright 2011-2014 Freescale Semiconductor, Inc. + * Copyright 2011-2016 Freescale Semiconductor, Inc. * Copyright 2011 Linaro Ltd. */ @@ -15,7 +15,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -24,6 +26,8 @@ #include #include +#include + #include "common.h" #include "hardware.h" @@ -56,16 +60,215 @@ #define CGPR 0x64 #define BM_CGPR_INT_MEM_CLK_LPM (0x1 << 17) +#define CCGR4 0x78 +#define CCGR6 0x80 #define MX6Q_SUSPEND_OCRAM_SIZE 0x1000 -#define MX6_MAX_MMDC_IO_NUM 33 +#define MX6_MAX_MMDC_IO_NUM 36 +#define MX6_MAX_MMDC_NUM 36 + +#define ROMC_ROMPATCH0D 0xf0 +#define ROMC_ROMPATCHCNTL 0xf4 +#define ROMC_ROMPATCHENL 0xfc +#define ROMC_ROMPATCH0A 0x100 +#define BM_ROMPATCHCNTL_0D (0x1 << 0) +#define BM_ROMPATCHCNTL_DIS (0x1 << 29) +#define BM_ROMPATCHENL_0D (0x1 << 0) +#define ROM_ADDR_FOR_INTERNAL_RAM_BASE 0x10d7c + +#define UART_UCR1 0x80 +#define UART_UCR2 0x84 +#define UART_UCR3 0x88 +#define UART_UCR4 0x8c +#define UART_UFCR 0x90 +#define UART_UESC 0x9c +#define UART_UTIM 0xa0 +#define UART_UBIR 0xa4 +#define UART_UBMR 0xa8 +#define UART_UBRC 0xac +#define UART_UTS 0xb4 + +#define IOMUXC_GPR5_CLOCK_AFCG_X_BYPASS_MASK 0xf800 extern unsigned long iram_tlb_base_addr; extern unsigned long iram_tlb_phys_addr; +/* QSPI register layout */ +#define QSPI_MCR 0x00 +#define QSPI_IPCR 0x08 +#define QSPI_BUF0CR 0x10 +#define QSPI_BUF1CR 0x14 +#define QSPI_BUF2CR 0x18 +#define QSPI_BUF3CR 0x1c +#define QSPI_BFGENCR 0x20 +#define QSPI_BUF0IND 0x30 +#define QSPI_BUF1IND 0x34 +#define QSPI_BUF2IND 0x38 +#define QSPI_SFAR 0x100 +#define QSPI_SMPR 0x108 +#define QSPI_RBSR 0x10c +#define QSPI_RBCT 0x110 +#define QSPI_TBSR 0x150 +#define QSPI_TBDR 0x154 +#define QSPI_SFA1AD 0x180 +#define QSPI_SFA2AD 0x184 +#define QSPI_SFB1AD 0x188 +#define QSPI_SFB2AD 0x18c +#define QSPI_RBDR_BASE 0x200 +#define QSPI_LUTKEY 0x300 +#define QSPI_LCKCR 0x304 +#define QSPI_LUT_BASE 0x310 + +#define QSPI_RBDR_(x) (QSPI_RBDR_BASE + (x) * 4) +#define QSPI_LUT(x) (QSPI_LUT_BASE + (x) * 4) + +#define QSPI_LUTKEY_VALUE 0x5AF05AF0 +#define QSPI_LCKER_LOCK 0x1 +#define QSPI_LCKER_UNLOCK 0x2 + +enum qspi_regs_valuetype { + QSPI_PREDEFINED, + QSPI_RETRIEVED, +}; + +struct qspi_regs { + int offset; + unsigned int value; + enum qspi_regs_valuetype valuetype; +}; + +struct qspi_regs qspi_regs_imx6sx[] = { + {QSPI_IPCR, 0, QSPI_RETRIEVED}, + {QSPI_BUF0CR, 0, QSPI_RETRIEVED}, + {QSPI_BUF1CR, 0, QSPI_RETRIEVED}, + {QSPI_BUF2CR, 0, QSPI_RETRIEVED}, + {QSPI_BUF3CR, 0, QSPI_RETRIEVED}, + {QSPI_BFGENCR, 0, QSPI_RETRIEVED}, + {QSPI_BUF0IND, 0, QSPI_RETRIEVED}, + {QSPI_BUF1IND, 0, QSPI_RETRIEVED}, + {QSPI_BUF2IND, 0, QSPI_RETRIEVED}, + {QSPI_SFAR, 0, QSPI_RETRIEVED}, + {QSPI_SMPR, 0, QSPI_RETRIEVED}, + {QSPI_RBSR, 0, QSPI_RETRIEVED}, + {QSPI_RBCT, 0, QSPI_RETRIEVED}, + {QSPI_TBSR, 0, QSPI_RETRIEVED}, + {QSPI_TBDR, 0, QSPI_RETRIEVED}, + {QSPI_SFA1AD, 0, QSPI_RETRIEVED}, + {QSPI_SFA2AD, 0, QSPI_RETRIEVED}, + {QSPI_SFB1AD, 0, QSPI_RETRIEVED}, + {QSPI_SFB2AD, 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(0), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(1), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(2), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(3), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(4), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(5), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(6), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(7), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(8), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(9), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(10), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(11), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(12), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(13), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(14), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(15), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(16), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(17), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(18), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(19), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(20), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(21), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(22), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(23), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(24), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(25), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(26), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(27), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(28), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(29), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(30), 0, QSPI_RETRIEVED}, + {QSPI_RBDR_(31), 0, QSPI_RETRIEVED}, + {QSPI_LUTKEY, QSPI_LUTKEY_VALUE, QSPI_PREDEFINED}, + {QSPI_LCKCR, QSPI_LCKER_UNLOCK, QSPI_PREDEFINED}, + {QSPI_LUT(0), 0, QSPI_RETRIEVED}, + {QSPI_LUT(1), 0, QSPI_RETRIEVED}, + {QSPI_LUT(2), 0, QSPI_RETRIEVED}, + {QSPI_LUT(3), 0, QSPI_RETRIEVED}, + {QSPI_LUT(4), 0, QSPI_RETRIEVED}, + {QSPI_LUT(5), 0, QSPI_RETRIEVED}, + {QSPI_LUT(6), 0, QSPI_RETRIEVED}, + {QSPI_LUT(7), 0, QSPI_RETRIEVED}, + {QSPI_LUT(8), 0, QSPI_RETRIEVED}, + {QSPI_LUT(9), 0, QSPI_RETRIEVED}, + {QSPI_LUT(10), 0, QSPI_RETRIEVED}, + {QSPI_LUT(11), 0, QSPI_RETRIEVED}, + {QSPI_LUT(12), 0, QSPI_RETRIEVED}, + {QSPI_LUT(13), 0, QSPI_RETRIEVED}, + {QSPI_LUT(14), 0, QSPI_RETRIEVED}, + {QSPI_LUT(15), 0, QSPI_RETRIEVED}, + {QSPI_LUT(16), 0, QSPI_RETRIEVED}, + {QSPI_LUT(17), 0, QSPI_RETRIEVED}, + {QSPI_LUT(18), 0, QSPI_RETRIEVED}, + {QSPI_LUT(19), 0, QSPI_RETRIEVED}, + {QSPI_LUT(20), 0, QSPI_RETRIEVED}, + {QSPI_LUT(21), 0, QSPI_RETRIEVED}, + {QSPI_LUT(22), 0, QSPI_RETRIEVED}, + {QSPI_LUT(23), 0, QSPI_RETRIEVED}, + {QSPI_LUT(24), 0, QSPI_RETRIEVED}, + {QSPI_LUT(25), 0, QSPI_RETRIEVED}, + {QSPI_LUT(26), 0, QSPI_RETRIEVED}, + {QSPI_LUT(27), 0, QSPI_RETRIEVED}, + {QSPI_LUT(28), 0, QSPI_RETRIEVED}, + {QSPI_LUT(29), 0, QSPI_RETRIEVED}, + {QSPI_LUT(30), 0, QSPI_RETRIEVED}, + {QSPI_LUT(31), 0, QSPI_RETRIEVED}, + {QSPI_LUT(32), 0, QSPI_RETRIEVED}, + {QSPI_LUT(33), 0, QSPI_RETRIEVED}, + {QSPI_LUT(34), 0, QSPI_RETRIEVED}, + {QSPI_LUT(35), 0, QSPI_RETRIEVED}, + {QSPI_LUT(36), 0, QSPI_RETRIEVED}, + {QSPI_LUT(37), 0, QSPI_RETRIEVED}, + {QSPI_LUT(38), 0, QSPI_RETRIEVED}, + {QSPI_LUT(39), 0, QSPI_RETRIEVED}, + {QSPI_LUT(40), 0, QSPI_RETRIEVED}, + {QSPI_LUT(41), 0, QSPI_RETRIEVED}, + {QSPI_LUT(42), 0, QSPI_RETRIEVED}, + {QSPI_LUT(43), 0, QSPI_RETRIEVED}, + {QSPI_LUT(44), 0, QSPI_RETRIEVED}, + {QSPI_LUT(45), 0, QSPI_RETRIEVED}, + {QSPI_LUT(46), 0, QSPI_RETRIEVED}, + {QSPI_LUT(47), 0, QSPI_RETRIEVED}, + {QSPI_LUT(48), 0, QSPI_RETRIEVED}, + {QSPI_LUT(49), 0, QSPI_RETRIEVED}, + {QSPI_LUT(50), 0, QSPI_RETRIEVED}, + {QSPI_LUT(51), 0, QSPI_RETRIEVED}, + {QSPI_LUT(52), 0, QSPI_RETRIEVED}, + {QSPI_LUT(53), 0, QSPI_RETRIEVED}, + {QSPI_LUT(54), 0, QSPI_RETRIEVED}, + {QSPI_LUT(55), 0, QSPI_RETRIEVED}, + {QSPI_LUT(56), 0, QSPI_RETRIEVED}, + {QSPI_LUT(57), 0, QSPI_RETRIEVED}, + {QSPI_LUT(58), 0, QSPI_RETRIEVED}, + {QSPI_LUT(59), 0, QSPI_RETRIEVED}, + {QSPI_LUT(60), 0, QSPI_RETRIEVED}, + {QSPI_LUT(61), 0, QSPI_RETRIEVED}, + {QSPI_LUT(62), 0, QSPI_RETRIEVED}, + {QSPI_LUT(63), 0, QSPI_RETRIEVED}, + {QSPI_LUTKEY, QSPI_LUTKEY_VALUE, QSPI_PREDEFINED}, + {QSPI_LCKCR, QSPI_LCKER_LOCK, QSPI_PREDEFINED}, + {QSPI_MCR, 0, QSPI_RETRIEVED}, +}; + +static unsigned int *ocram_saved_in_ddr; +static void __iomem *ocram_base; +static void __iomem *console_base; +static void __iomem *qspi_base; +static unsigned int ocram_size; static void __iomem *ccm_base; static void __iomem *suspend_ocram_base; static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase); +struct regmap *romcp; /* * suspend ocram space layout: @@ -95,6 +298,8 @@ struct imx6_pm_socdata { const char *pl310_compat; const u32 mmdc_io_num; const u32 *mmdc_io_offset; + const u32 mmdc_num; + const u32 *mmdc_offset; }; static const u32 imx6q_mmdc_io_offset[] __initconst = { @@ -109,6 +314,18 @@ static const u32 imx6q_mmdc_io_offset[] __initconst = { 0x74c, /* GPR_ADDS */ }; +static const u32 imx6q_mmdc_io_lpddr2_offset[] __initconst = { + 0x5ac, 0x5b4, 0x528, 0x520, /* DQM0 ~ DQM3 */ + 0x514, 0x510, 0x5bc, 0x5c4, /* DQM4 ~ DQM7 */ + 0x784, 0x788, 0x794, 0x79c, /* GPR_B0DS ~ GPR_B3DS */ + 0x7a0, 0x7a4, 0x7a8, 0x748, /* GPR_B4DS ~ GPR_B7DS */ + 0x56c, 0x578, 0x588, 0x594, /* CAS, RAS, SDCLK_0, SDCLK_1 */ + 0x5a8, 0x5b0, 0x524, 0x51c, /* SDQS0 ~ SDQS3 */ + 0x518, 0x50c, 0x5b8, 0x5c0, /* SDQS4 ~ SDQS7 */ + 0x59c, 0x5a0, 0x750, 0x774, /* SODT0, SODT1, MODE_CTL, MODE */ + 0x74c, 0x590, 0x598, 0x57c, /* GRP_ADDS, SDCKE0, SDCKE1, RESET */ +}; + static const u32 imx6dl_mmdc_io_offset[] __initconst = { 0x470, 0x474, 0x478, 0x47c, /* DQM0 ~ DQM3 */ 0x480, 0x484, 0x488, 0x48c, /* DQM4 ~ DQM7 */ @@ -129,11 +346,25 @@ static const u32 imx6sl_mmdc_io_offset[] __initconst = { 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_lpddr2_offset[] __initconst = { + 0x2ec, 0x2f0, 0x2f4, 0x2f8, /* DQM0 ~ DQM3 */ + 0x300, 0x2fc, 0x32c, 0x5f4, /* CAS, RAS, SDCLK_0, GPR_ADDS */ + 0x60c, 0x610, 0x61c, 0x620, /* GPR_B0DS ~ GPR_B3DS */ + 0x310, 0x314, 0x5f8, 0x608, /* SODT0, SODT1, MODE_CTL, MODE */ + 0x330, 0x334, 0x338, 0x33c, /* SDQS0 ~ SDQS3 */ + 0x324, 0x328, 0x340, /* DRAM_SDCKE0 ~ 1, DRAM_RESET */ +}; + +static const u32 imx6sx_mmdc_lpddr2_offset[] __initconst = { + 0x01c, 0x85c, 0x800, 0x890, + 0x8b8, 0x81c, 0x820, 0x824, + 0x828, 0x82c, 0x830, 0x834, + 0x838, 0x848, 0x850, 0x8c0, + 0x83c, 0x840, 0x8b8, 0x00c, + 0x004, 0x010, 0x014, 0x018, + 0x02c, 0x030, 0x038, 0x008, + 0x040, 0x000, 0x020, 0x818, + 0x800, 0x004, 0x01c, }; static const u32 imx6sx_mmdc_io_offset[] __initconst = { @@ -144,6 +375,16 @@ static const u32 imx6sx_mmdc_io_offset[] __initconst = { 0x330, 0x334, 0x338, 0x33c, /* SDQS0 ~ SDQS3 */ }; +static const u32 imx6sx_mmdc_offset[] __initconst = { + 0x800, 0x80c, 0x810, 0x83c, + 0x840, 0x848, 0x850, 0x81c, + 0x820, 0x824, 0x828, 0x8b8, + 0x004, 0x008, 0x00c, 0x010, + 0x014, 0x018, 0x01c, 0x02c, + 0x030, 0x040, 0x000, 0x01c, + 0x020, 0x818, 0x01c, +}; + 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 */ @@ -151,6 +392,53 @@ static const u32 imx6ul_mmdc_io_offset[] __initconst = { 0x494, 0x4b0, /* MODE_CTL, MODE, */ }; +static const u32 imx6ul_mmdc_offset[] __initconst = { + 0x01c, 0x800, 0x80c, 0x83c, + 0x848, 0x850, 0x81c, 0x820, + 0x82c, 0x830, 0x8c0, 0x8b8, + 0x004, 0x008, 0x00c, 0x010, + 0x014, 0x018, 0x01c, 0x02c, + 0x030, 0x040, 0x000, 0x01c, + 0x020, 0x818, 0x01c, +}; + +static const u32 imx6ul_mmdc_io_lpddr2_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, 0x274, 0x278, /* MODE_CTL, MODE, SDCKE0, SDCKE1 */ + 0x288, /* DRAM_RESET */ +}; + +static const u32 imx6ul_mmdc_lpddr2_offset[] __initconst = { + 0x01c, 0x85c, 0x800, 0x890, + 0x8b8, 0x81c, 0x820, 0x82c, + 0x830, 0x83c, 0x848, 0x850, + 0x8c0, 0x8b8, 0x004, 0x008, + 0x00c, 0x010, 0x038, 0x014, + 0x018, 0x01c, 0x02c, 0x030, + 0x040, 0x000, 0x020, 0x818, + 0x800, 0x004, 0x01c, +}; + +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 imx6sll_mmdc_lpddr3_offset[] __initconst = { + 0x01c, 0x85c, 0x800, 0x890, + 0x8b8, 0x81c, 0x820, 0x82c, + 0x830, 0x83c, 0x848, 0x850, + 0x8c0, 0x8b8, 0x004, 0x008, + 0x00c, 0x010, 0x038, 0x014, + 0x018, 0x01c, 0x02c, 0x030, + 0x040, 0x000, 0x020, 0x818, + 0x800, 0x004, 0x01c, +}; + static const struct imx6_pm_socdata imx6q_pm_data __initconst = { .mmdc_compat = "fsl,imx6q-mmdc", .src_compat = "fsl,imx6q-src", @@ -159,6 +447,19 @@ static const struct imx6_pm_socdata imx6q_pm_data __initconst = { .pl310_compat = "arm,pl310-cache", .mmdc_io_num = ARRAY_SIZE(imx6q_mmdc_io_offset), .mmdc_io_offset = imx6q_mmdc_io_offset, + .mmdc_num = 0, + .mmdc_offset = NULL, +}; + +static const struct imx6_pm_socdata imx6q_lpddr2_pm_data __initconst = { + .mmdc_compat = "fsl,imx6q-mmdc", + .src_compat = "fsl,imx6q-src", + .iomuxc_compat = "fsl,imx6q-iomuxc", + .gpc_compat = "fsl,imx6q-gpc", + .mmdc_io_num = ARRAY_SIZE(imx6q_mmdc_io_lpddr2_offset), + .mmdc_io_offset = imx6q_mmdc_io_lpddr2_offset, + .mmdc_num = 0, + .mmdc_offset = NULL, }; static const struct imx6_pm_socdata imx6dl_pm_data __initconst = { @@ -169,6 +470,8 @@ static const struct imx6_pm_socdata imx6dl_pm_data __initconst = { .pl310_compat = "arm,pl310-cache", .mmdc_io_num = ARRAY_SIZE(imx6dl_mmdc_io_offset), .mmdc_io_offset = imx6dl_mmdc_io_offset, + .mmdc_num = 0, + .mmdc_offset = NULL, }; static const struct imx6_pm_socdata imx6sl_pm_data __initconst = { @@ -179,16 +482,8 @@ static const struct imx6_pm_socdata imx6sl_pm_data __initconst = { .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, + .mmdc_num = 0, + .mmdc_offset = NULL, }; static const struct imx6_pm_socdata imx6sx_pm_data __initconst = { @@ -199,6 +494,19 @@ static const struct imx6_pm_socdata imx6sx_pm_data __initconst = { .pl310_compat = "arm,pl310-cache", .mmdc_io_num = ARRAY_SIZE(imx6sx_mmdc_io_offset), .mmdc_io_offset = imx6sx_mmdc_io_offset, + .mmdc_num = ARRAY_SIZE(imx6sx_mmdc_offset), + .mmdc_offset = imx6sx_mmdc_offset, +}; + +static const struct imx6_pm_socdata imx6sx_lpddr2_pm_data __initconst = { + .mmdc_compat = "fsl,imx6sx-mmdc", + .src_compat = "fsl,imx6sx-src", + .iomuxc_compat = "fsl,imx6sx-iomuxc", + .gpc_compat = "fsl,imx6sx-gpc", + .mmdc_io_num = ARRAY_SIZE(imx6sx_mmdc_io_lpddr2_offset), + .mmdc_io_offset = imx6sx_mmdc_io_lpddr2_offset, + .mmdc_num = ARRAY_SIZE(imx6sx_mmdc_lpddr2_offset), + .mmdc_offset = imx6sx_mmdc_lpddr2_offset, }; static const struct imx6_pm_socdata imx6ul_pm_data __initconst = { @@ -209,6 +517,30 @@ static const struct imx6_pm_socdata imx6ul_pm_data __initconst = { .pl310_compat = NULL, .mmdc_io_num = ARRAY_SIZE(imx6ul_mmdc_io_offset), .mmdc_io_offset = imx6ul_mmdc_io_offset, + .mmdc_num = ARRAY_SIZE(imx6ul_mmdc_offset), + .mmdc_offset = imx6ul_mmdc_offset, +}; + +static const struct imx6_pm_socdata imx6ul_lpddr2_pm_data __initconst = { + .mmdc_compat = "fsl,imx6ul-mmdc", + .src_compat = "fsl,imx6ul-src", + .iomuxc_compat = "fsl,imx6ul-iomuxc", + .gpc_compat = "fsl,imx6ul-gpc", + .mmdc_io_num = ARRAY_SIZE(imx6ul_mmdc_io_lpddr2_offset), + .mmdc_io_offset = imx6ul_mmdc_io_lpddr2_offset, + .mmdc_num = ARRAY_SIZE(imx6ul_mmdc_lpddr2_offset), + .mmdc_offset = imx6ul_mmdc_lpddr2_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", + .mmdc_io_num = ARRAY_SIZE(imx6sll_mmdc_io_offset), + .mmdc_io_offset = imx6sll_mmdc_io_offset, + .mmdc_num = ARRAY_SIZE(imx6sll_mmdc_lpddr3_offset), + .mmdc_offset = imx6sll_mmdc_lpddr3_offset, }; static struct map_desc iram_tlb_io_desc __initdata = { @@ -254,14 +586,19 @@ struct imx6_cpu_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 mmdc0_base; + struct imx6_pm_base mmdc1_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; + struct imx6_pm_base anatop_base; + u32 ttbr1; /* Store TTBR1 */ 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 */ + u32 mmdc_io_val[MX6_MAX_MMDC_IO_NUM][3]; /* To save offset, value, low power settings */ + u32 mmdc_num; /* Number of MMDC registers which need saved/restored. */ + u32 mmdc_val[MX6_MAX_MMDC_NUM][2]; } __aligned(8); void imx6_set_int_mem_clk_lpm(bool enable) @@ -340,11 +677,18 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode) val |= 0x2 << BP_CLPCR_LPM; val &= ~BM_CLPCR_VSTBY; val &= ~BM_CLPCR_SBYOS; - if (cpu_is_imx6sl()) + if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6sll()) 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()) + cpu_is_imx6ull() || cpu_is_imx6sll()) val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS; + else if (cpu_is_imx6q() && + imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2 && + imx_mmdc_get_lpddr2_2ch_mode() == IMX_LPDDR2_2CH_MODE) { + /* keep handshake enabled for lpddr2 2ch-mode */ + val &= ~BM_CLPCR_BYP_MMDC_CH0_LPM_HS; + val &= ~BM_CLPCR_BYP_MMDC_CH1_LPM_HS; + } else val |= BM_CLPCR_BYP_MMDC_CH1_LPM_HS; break; @@ -358,11 +702,18 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode) val |= 0x3 << BP_CLPCR_STBY_COUNT; val |= BM_CLPCR_VSTBY; val |= BM_CLPCR_SBYOS; - if (cpu_is_imx6sl() || cpu_is_imx6sx()) + if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6sll()) 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()) + cpu_is_imx6ull() || cpu_is_imx6sll()) val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS; + else if (cpu_is_imx6q() && + imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2 && + imx_mmdc_get_lpddr2_2ch_mode() == IMX_LPDDR2_2CH_MODE) { + /* keep handshake enabled for lpddr2 2ch-mode */ + val &= ~BM_CLPCR_BYP_MMDC_CH0_LPM_HS; + val &= ~BM_CLPCR_BYP_MMDC_CH1_LPM_HS; + } else val |= BM_CLPCR_BYP_MMDC_CH1_LPM_HS; break; @@ -393,8 +744,18 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode) return 0; } +#define MX6Q_SUSPEND_PARAM \ + ((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + static int imx6q_suspend_finish(unsigned long val) { + if (psci_ops.cpu_suspend) { + return psci_ops.cpu_suspend(MX6Q_SUSPEND_PARAM, + __pa(cpu_resume)); + } + if (!imx6_suspend_in_ocram_fn) { cpu_do_idle(); } else { @@ -413,19 +774,104 @@ static int imx6q_suspend_finish(unsigned long val) return 0; } +static void imx6_console_save(unsigned int *regs) +{ + if (!console_base) + return; + + regs[0] = readl_relaxed(console_base + UART_UCR1); + regs[1] = readl_relaxed(console_base + UART_UCR2); + regs[2] = readl_relaxed(console_base + UART_UCR3); + regs[3] = readl_relaxed(console_base + UART_UCR4); + regs[4] = readl_relaxed(console_base + UART_UFCR); + regs[5] = readl_relaxed(console_base + UART_UESC); + regs[6] = readl_relaxed(console_base + UART_UTIM); + regs[7] = readl_relaxed(console_base + UART_UBIR); + regs[8] = readl_relaxed(console_base + UART_UBMR); + regs[9] = readl_relaxed(console_base + UART_UTS); +} + +static void imx6_console_restore(unsigned int *regs) +{ + if (!console_base) + return; + + writel_relaxed(regs[4], console_base + UART_UFCR); + writel_relaxed(regs[5], console_base + UART_UESC); + writel_relaxed(regs[6], console_base + UART_UTIM); + writel_relaxed(regs[7], console_base + UART_UBIR); + writel_relaxed(regs[8], console_base + UART_UBMR); + writel_relaxed(regs[9], console_base + UART_UTS); + writel_relaxed(regs[0], console_base + UART_UCR1); + writel_relaxed(regs[1] | 0x1, console_base + UART_UCR2); + writel_relaxed(regs[2], console_base + UART_UCR3); + writel_relaxed(regs[3], console_base + UART_UCR4); +} + +static void imx6_qspi_save(struct qspi_regs *pregs, int reg_num) +{ + int i; + + if (!qspi_base) + return; + + for (i = 0; i < reg_num; i++) { + if (QSPI_RETRIEVED == pregs[i].valuetype) + pregs[i].value = readl_relaxed(qspi_base + + pregs[i].offset); + } +} + +static void imx6_qspi_restore(struct qspi_regs *pregs, int reg_num) +{ + int i; + + if (!qspi_base) + return; + + for (i = 0; i < reg_num; i++) + writel_relaxed(pregs[i].value, qspi_base + pregs[i].offset); +} + static int imx6q_pm_enter(suspend_state_t state) { + unsigned int console_saved_reg[10] = {0}; + static unsigned int ccm_ccgr4, ccm_ccgr6; + +#ifdef CONFIG_SOC_IMX6SX + if (imx_src_is_m4_enabled()) { + if (imx_gpc_is_m4_sleeping() && imx_mu_is_m4_in_low_freq()) { + imx_gpc_hold_m4_in_sleep(); + imx_mu_enable_m4_irqs_in_gic(true); + } else { + pr_info("M4 is busy, enter WAIT mode instead of STOP!\n"); + imx6_set_lpm(WAIT_UNCLOCKED); + imx6_set_int_mem_clk_lpm(true); + imx_gpc_pre_suspend(false); + /* Zzz ... */ + cpu_do_idle(); + imx_gpc_post_resume(); + imx6_set_lpm(WAIT_CLOCKED); + + return 0; + } + } +#endif switch (state) { case PM_SUSPEND_STANDBY: imx6_set_lpm(STOP_POWER_ON); imx6_set_int_mem_clk_lpm(true); imx_gpc_pre_suspend(false); +#ifdef CONFIG_SOC_IMX6SL if (cpu_is_imx6sl()) imx6sl_set_wait_clk(true); +#endif /* Zzz ... */ cpu_do_idle(); +#ifdef CONFIG_SOC_IMX6SL if (cpu_is_imx6sl()) imx6sl_set_wait_clk(false); +#endif imx_gpc_post_resume(); imx6_set_lpm(WAIT_CLOCKED); break; @@ -441,8 +887,50 @@ static int imx6q_pm_enter(suspend_state_t state) imx6_enable_rbc(true); imx_gpc_pre_suspend(true); imx_anatop_pre_suspend(); + if ((cpu_is_imx6ull() || cpu_is_imx6sll()) && + imx_gpc_is_mf_mix_off()) + imx6_console_save(console_saved_reg); + if (cpu_is_imx6sx() && imx_gpc_is_mf_mix_off()) { + ccm_ccgr4 = readl_relaxed(ccm_base + CCGR4); + ccm_ccgr6 = readl_relaxed(ccm_base + CCGR6); + /* + * i.MX6SX RDC needs PCIe and eim clk to be enabled + * if Mega/Fast off, it is better to check cpu type + * and whether Mega/Fast is off in this suspend flow, + * but we need to add cpu type check for 3 places which + * will increase code size, so here we just do it + * for all cases, as when STOP mode is entered, CCM + * hardware will gate all clocks, so it will NOT impact + * any function or power. + */ + writel_relaxed(ccm_ccgr4 | (0x3 << 0), ccm_base + + CCGR4); + writel_relaxed(ccm_ccgr6 | (0x3 << 10), ccm_base + + CCGR6); + memcpy(ocram_saved_in_ddr, ocram_base, ocram_size); + imx6_console_save(console_saved_reg); + if (imx_src_is_m4_enabled()) + imx6_qspi_save(qspi_regs_imx6sx, + sizeof(qspi_regs_imx6sx) / + sizeof(struct qspi_regs)); + } + /* Zzz ... */ cpu_suspend(0, imx6q_suspend_finish); + + if (cpu_is_imx6sx() && imx_gpc_is_mf_mix_off()) { + writel_relaxed(ccm_ccgr4, ccm_base + CCGR4); + writel_relaxed(ccm_ccgr6, ccm_base + CCGR6); + memcpy(ocram_base, ocram_saved_in_ddr, ocram_size); + imx6_console_restore(console_saved_reg); + if (imx_src_is_m4_enabled()) + imx6_qspi_restore(qspi_regs_imx6sx, + sizeof(qspi_regs_imx6sx) / + sizeof(struct qspi_regs)); + } + if ((cpu_is_imx6ull() || cpu_is_imx6sll()) && + imx_gpc_is_mf_mix_off()) + imx6_console_restore(console_saved_reg); if (cpu_is_imx6q() || cpu_is_imx6dl()) imx_smp_prepare(); imx_anatop_post_resume(); @@ -456,6 +944,13 @@ static int imx6q_pm_enter(suspend_state_t state) return -EINVAL; } +#ifdef CONFIG_SOC_IMX6SX + if (imx_src_is_m4_enabled()) { + imx_mu_enable_m4_irqs_in_gic(false); + imx_gpc_release_m4_in_sleep(); + } +#endif + return 0; } @@ -569,41 +1064,14 @@ void __init imx6_pm_map_io(void) (MX6Q_L2_BASE_ADDR & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; } -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; + unsigned long iram_paddr; int i, ret = 0; const u32 *mmdc_offset_array; + const u32 *mmdc_io_offset_array; suspend_set_ops(&imx6q_pm_ops); @@ -612,41 +1080,30 @@ static int __init imx6q_suspend_init(const struct imx6_pm_socdata *socdata) 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; + if (psci_ops.cpu_suspend) { + /* TODO: seems not needed */ + /* of_node_put(node); */ + return ret; } - pdev = of_find_device_by_node(node); - if (!pdev) { - pr_warn("%s: failed to find ocram device!\n", __func__); - ret = -ENODEV; - goto put_node; - } + /* + * 16KB is allocated for IRAM TLB, but only up 8k is for kernel TLB, + * The lower 8K is not used, so use the lower 8K for IRAM code and + * pm_info. + * + */ + iram_paddr = iram_tlb_phys_addr + MX6_SUSPEND_IRAM_ADDR_OFFSET; - ocram_pool = gen_pool_get(&pdev->dev, NULL); - if (!ocram_pool) { - pr_warn("%s: ocram pool unavailable!\n", __func__); - ret = -ENODEV; - goto put_node; - } + /* Make sure iram_paddr is 8 byte aligned. */ + if ((uintptr_t)(iram_paddr) & (FNCPY_ALIGN - 1)) + iram_paddr += FNCPY_ALIGN - iram_paddr % (FNCPY_ALIGN); - 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_node; - } - - ocram_pbase = gen_pool_virt_to_phys(ocram_pool, ocram_base); - - suspend_ocram_base = __arm_ioremap_exec(ocram_pbase, - MX6Q_SUSPEND_OCRAM_SIZE, false); + /* Get the virtual address of the suspend code. */ + suspend_ocram_base = (void *)IMX_IO_P2V(iram_paddr); memset(suspend_ocram_base, 0, sizeof(*pm_info)); pm_info = suspend_ocram_base; - pm_info->pbase = ocram_pbase; + pm_info->pbase = iram_paddr; pm_info->resume_addr = __pa_symbol(v7_cpu_resume); pm_info->pm_info_size = sizeof(*pm_info); @@ -654,53 +1111,120 @@ static int __init imx6q_suspend_init(const struct imx6_pm_socdata *socdata) * ccm physical address is not used by asm code currently, * so get ccm virtual address directly. */ - pm_info->ccm_base.vbase = ccm_base; + pm_info->ccm_base.pbase = MX6Q_CCM_BASE_ADDR; + pm_info->ccm_base.vbase = (void __iomem *) + IMX_IO_P2V(MX6Q_CCM_BASE_ADDR); - 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_node; - } + pm_info->mmdc0_base.pbase = MX6Q_MMDC_P0_BASE_ADDR; + pm_info->mmdc0_base.vbase = (void __iomem *) + IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR); - 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; - } + pm_info->mmdc1_base.pbase = MX6Q_MMDC_P1_BASE_ADDR; + pm_info->mmdc1_base.vbase = (void __iomem *) + IMX_IO_P2V(MX6Q_MMDC_P1_BASE_ADDR); - 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; - } + pm_info->src_base.pbase = MX6Q_SRC_BASE_ADDR; + pm_info->src_base.vbase = (void __iomem *) + IMX_IO_P2V(MX6Q_SRC_BASE_ADDR); - 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; - } + pm_info->iomuxc_base.pbase = MX6Q_IOMUXC_BASE_ADDR; + pm_info->iomuxc_base.vbase = (void __iomem *) + IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR); - 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->gpc_base.pbase = MX6Q_GPC_BASE_ADDR; + pm_info->gpc_base.vbase = (void __iomem *) + IMX_IO_P2V(MX6Q_GPC_BASE_ADDR); + + pm_info->l2_base.pbase = MX6Q_L2_BASE_ADDR; + pm_info->l2_base.vbase = (void __iomem *) + IMX_IO_P2V(MX6Q_L2_BASE_ADDR); + + pm_info->anatop_base.pbase = MX6Q_ANATOP_BASE_ADDR; + pm_info->anatop_base.vbase = (void __iomem *) + IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR); 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; + mmdc_io_offset_array = socdata->mmdc_io_offset; + pm_info->mmdc_num = socdata->mmdc_num; + mmdc_offset_array = socdata->mmdc_offset; for (i = 0; i < pm_info->mmdc_io_num; i++) { pm_info->mmdc_io_val[i][0] = - mmdc_offset_array[i]; + mmdc_io_offset_array[i]; pm_info->mmdc_io_val[i][1] = readl_relaxed(pm_info->iomuxc_base.vbase + + mmdc_io_offset_array[i]); + pm_info->mmdc_io_val[i][2] = 0; + } + + /* i.MX6SLL has no DRAM RESET pin */ + if (cpu_is_imx6sll()) { + pm_info->mmdc_io_val[pm_info->mmdc_io_num - 2][2] = 0x1000; + pm_info->mmdc_io_val[pm_info->mmdc_io_num - 1][2] = 0x1000; + } else { + if (pm_info->ddr_type == IMX_DDR_TYPE_LPDDR2) { + /* for LPDDR2, CKE0/1 and RESET pin need special setting */ + pm_info->mmdc_io_val[pm_info->mmdc_io_num - 3][2] = 0x1000; + pm_info->mmdc_io_val[pm_info->mmdc_io_num - 2][2] = 0x1000; + pm_info->mmdc_io_val[pm_info->mmdc_io_num - 1][2] = 0x80000; + } + } + + /* initialize MMDC settings */ + for (i = 0; i < pm_info->mmdc_num; i++) { + pm_info->mmdc_val[i][0] = + mmdc_offset_array[i]; + pm_info->mmdc_val[i][1] = + readl_relaxed(pm_info->mmdc0_base.vbase + mmdc_offset_array[i]); } + if (cpu_is_imx6sll() && pm_info->ddr_type == IMX_MMDC_DDR_TYPE_LPDDR3) { + pm_info->mmdc_val[0][1] = 0x8000; + pm_info->mmdc_val[2][1] = 0xa1390003; + pm_info->mmdc_val[3][1] = 0x400000; + pm_info->mmdc_val[4][1] = 0x800; + pm_info->mmdc_val[13][1] = 0x800; + pm_info->mmdc_val[14][1] = 0x20052; + pm_info->mmdc_val[20][1] = 0x201718; + pm_info->mmdc_val[21][1] = 0x8000; + pm_info->mmdc_val[28][1] = 0xa1310003; + } + + /* need to overwrite the value for some mmdc registers */ + if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) && + pm_info->ddr_type != IMX_DDR_TYPE_LPDDR2) { + pm_info->mmdc_val[20][1] = (pm_info->mmdc_val[20][1] + & 0xffff0000) | 0x0202; + pm_info->mmdc_val[23][1] = 0x8033; + } + + if (cpu_is_imx6sx() && + pm_info->ddr_type == IMX_DDR_TYPE_LPDDR2) { + pm_info->mmdc_val[0][1] = 0x8000; + pm_info->mmdc_val[2][1] = 0xa1390003; + pm_info->mmdc_val[3][1] = 0x380000; + pm_info->mmdc_val[4][1] = 0x800; + pm_info->mmdc_val[18][1] = 0x800; + pm_info->mmdc_val[20][1] = 0x20024; + pm_info->mmdc_val[23][1] = 0x1748; + pm_info->mmdc_val[32][1] = 0xa1310003; + } + + if ((cpu_is_imx6ul() || cpu_is_imx6ull()) && + pm_info->ddr_type == IMX_DDR_TYPE_LPDDR2) { + pm_info->mmdc_val[0][1] = 0x8000; + pm_info->mmdc_val[2][1] = 0xa1390003; + pm_info->mmdc_val[3][1] = 0x470000; + pm_info->mmdc_val[4][1] = 0x800; + pm_info->mmdc_val[13][1] = 0x800; + pm_info->mmdc_val[14][1] = 0x20012; + pm_info->mmdc_val[20][1] = 0x1748; + pm_info->mmdc_val[21][1] = 0x8000; + pm_info->mmdc_val[28][1] = 0xa1310003; + } + imx6_suspend_in_ocram_fn = fncpy( suspend_ocram_base + sizeof(*pm_info), &imx6_suspend, @@ -708,14 +1232,6 @@ static int __init imx6q_suspend_init(const struct imx6_pm_socdata *socdata) goto put_node; -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_node: of_node_put(node); @@ -750,28 +1266,6 @@ static void __init imx6_pm_common_init(const struct imx6_pm_socdata IMX6Q_GPR1_GINT); } -static void imx6_pm_stby_poweroff(void) -{ - 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; @@ -788,14 +1282,14 @@ void __init imx6_pm_ccm_init(const char *ccm_compat) 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(); } void __init imx6q_pm_init(void) { - imx6_pm_common_init(&imx6q_pm_data); + if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2) + imx6_pm_common_init(&imx6q_lpddr2_pm_data); + else + imx6_pm_common_init(&imx6q_pm_data); } void __init imx6dl_pm_init(void) @@ -805,25 +1299,96 @@ void __init imx6dl_pm_init(void) void __init imx6sl_pm_init(void) { + struct device_node *np; struct regmap *gpr; - if (cpu_is_imx6sl()) { - imx6_pm_common_init(&imx6sl_pm_data); - } else { + if (cpu_is_imx6sll()) { imx6_pm_common_init(&imx6sll_pm_data); + np = of_find_node_by_path( + "/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000"); + if (np) + console_base = of_iomap(np, 0); + /* i.MX6SLL has bus auto clock gating function */ 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); + IOMUXC_GPR5_CLOCK_AFCG_X_BYPASS_MASK, 0); + return; } + + imx6_pm_common_init(&imx6sl_pm_data); } void __init imx6sx_pm_init(void) { - imx6_pm_common_init(&imx6sx_pm_data); + struct device_node *np; + struct resource res; + + if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2) + imx6_pm_common_init(&imx6sx_lpddr2_pm_data); + else + imx6_pm_common_init(&imx6sx_pm_data); + if (imx_get_soc_revision() < IMX_CHIP_REVISION_1_2) { + /* + * As there is a 16K OCRAM(start from 0x8f8000) + * dedicated for low power function on i.MX6SX, + * but ROM did NOT do the ocram address change + * accordingly, so we need to add a data patch + * to workaround this issue, otherwise, system + * will fail to resume from DSM mode. TO1.2 fixes + * this issue. + */ + romcp = syscon_regmap_lookup_by_compatible( + "fsl,imx6sx-romcp"); + if (IS_ERR(romcp)) { + pr_err("failed to find fsl,imx6sx-romcp regmap\n"); + return; + } + regmap_write(romcp, ROMC_ROMPATCH0D, iram_tlb_phys_addr); + regmap_update_bits(romcp, ROMC_ROMPATCHCNTL, + BM_ROMPATCHCNTL_0D, BM_ROMPATCHCNTL_0D); + regmap_update_bits(romcp, ROMC_ROMPATCHENL, + BM_ROMPATCHENL_0D, BM_ROMPATCHENL_0D); + regmap_write(romcp, ROMC_ROMPATCH0A, + ROM_ADDR_FOR_INTERNAL_RAM_BASE); + regmap_update_bits(romcp, ROMC_ROMPATCHCNTL, + BM_ROMPATCHCNTL_DIS, ~BM_ROMPATCHCNTL_DIS); + } + + np = of_find_compatible_node(NULL, NULL, "fsl,mega-fast-sram"); + ocram_base = of_iomap(np, 0); + WARN_ON(!ocram_base); + WARN_ON(of_address_to_resource(np, 0, &res)); + ocram_size = resource_size(&res); + ocram_saved_in_ddr = kzalloc(ocram_size, GFP_KERNEL); + WARN_ON(!ocram_saved_in_ddr); + + np = of_find_node_by_path( + "/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000"); + if (np) + console_base = of_iomap(np, 0); + if (imx_src_is_m4_enabled()) { + np = of_find_compatible_node(NULL, NULL, + "fsl,imx6sx-qspi-m4-restore"); + if (np) + qspi_base = of_iomap(np, 0); + WARN_ON(!qspi_base); + } } void __init imx6ul_pm_init(void) { - imx6_pm_common_init(&imx6ul_pm_data); + struct device_node *np; + + if (imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2) + imx6_pm_common_init(&imx6ul_lpddr2_pm_data); + else + imx6_pm_common_init(&imx6ul_pm_data); + + if (cpu_is_imx6ull()) { + np = of_find_node_by_path( + "/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000"); + if (np) + console_base = of_iomap(np, 0); + } } diff --git a/arch/arm/mach-imx/src.c b/arch/arm/mach-imx/src.c index 0beea6d0217f..3969578ea9d3 100644 --- a/arch/arm/mach-imx/src.c +++ b/arch/arm/mach-imx/src.c @@ -26,6 +26,7 @@ static void __iomem *src_base; static DEFINE_SPINLOCK(scr_lock); +static bool m4_is_enabled; static const int sw_reset_bits[5] = { BP_SRC_SCR_SW_GPU_RST, @@ -35,6 +36,11 @@ static const int sw_reset_bits[5] = { BP_SRC_SCR_SW_IPU2_RST }; +bool imx_src_is_m4_enabled(void) +{ + return m4_is_enabled; +} + static int imx_src_reset_module(struct reset_controller_dev *rcdev, unsigned long sw_reset_idx) { diff --git a/arch/arm/mach-imx/suspend-imx6.S b/arch/arm/mach-imx/suspend-imx6.S index 062391ff13da..391a7f92b6ff 100644 --- a/arch/arm/mach-imx/suspend-imx6.S +++ b/arch/arm/mach-imx/suspend-imx6.S @@ -41,23 +41,32 @@ #define PM_INFO_RESUME_ADDR_OFFSET 0x4 #define PM_INFO_DDR_TYPE_OFFSET 0x8 #define PM_INFO_PM_INFO_SIZE_OFFSET 0xC -#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 -#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 -#define PM_INFO_MX6Q_SRC_P_OFFSET 0x18 -#define PM_INFO_MX6Q_SRC_V_OFFSET 0x1C -#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x20 -#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x24 -#define PM_INFO_MX6Q_CCM_P_OFFSET 0x28 -#define PM_INFO_MX6Q_CCM_V_OFFSET 0x2C -#define PM_INFO_MX6Q_GPC_P_OFFSET 0x30 -#define PM_INFO_MX6Q_GPC_V_OFFSET 0x34 -#define PM_INFO_MX6Q_L2_P_OFFSET 0x38 -#define PM_INFO_MX6Q_L2_V_OFFSET 0x3C -#define PM_INFO_MMDC_IO_NUM_OFFSET 0x40 -#define PM_INFO_MMDC_IO_VAL_OFFSET 0x44 +#define PM_INFO_MX6Q_MMDC0_P_OFFSET 0x10 +#define PM_INFO_MX6Q_MMDC0_V_OFFSET 0x14 +#define PM_INFO_MX6Q_MMDC1_P_OFFSET 0x18 +#define PM_INFO_MX6Q_MMDC1_V_OFFSET 0x1C +#define PM_INFO_MX6Q_SRC_P_OFFSET 0x20 +#define PM_INFO_MX6Q_SRC_V_OFFSET 0x24 +#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x28 +#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x2C +#define PM_INFO_MX6Q_CCM_P_OFFSET 0x30 +#define PM_INFO_MX6Q_CCM_V_OFFSET 0x34 +#define PM_INFO_MX6Q_GPC_P_OFFSET 0x38 +#define PM_INFO_MX6Q_GPC_V_OFFSET 0x3C +#define PM_INFO_MX6Q_L2_P_OFFSET 0x40 +#define PM_INFO_MX6Q_L2_V_OFFSET 0x44 +#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x48 +#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x4C +#define PM_INFO_MX6Q_TTBR1_V_OFFSET 0x50 +#define PM_INFO_MMDC_IO_NUM_OFFSET 0x54 +#define PM_INFO_MMDC_IO_VAL_OFFSET 0x58 +/* below offsets depends on MX6_MAX_MMDC_IO_NUM(36) definition */ +#define PM_INFO_MMDC_NUM_OFFSET 0x208 +#define PM_INFO_MMDC_VAL_OFFSET 0x20C #define MX6Q_SRC_GPR1 0x20 #define MX6Q_SRC_GPR2 0x24 +#define MX6Q_MMDC_MISC 0x18 #define MX6Q_MMDC_MAPSR 0x404 #define MX6Q_MMDC_MPDGCTRL0 0x83c #define MX6Q_GPC_IMR1 0x08 @@ -65,9 +74,49 @@ #define MX6Q_GPC_IMR3 0x10 #define MX6Q_GPC_IMR4 0x14 #define MX6Q_CCM_CCR 0x0 +#define MX6Q_ANATOP_CORE 0x140 .align 3 + /* Check if the cpu is cortex-a7 */ + .macro is_cortex_a7 + + /* Read the primary cpu number is MPIDR */ + mrc p15, 0, r5, c0, c0, 0 + ldr r6, =0xfff0 + and r5, r5, r6 + ldr r6, =0xc070 + cmp r5, r6 + + .endm + + .macro disable_l1_cache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 -r10, lr} + ldr r7, = v7_flush_dcache_all + mov lr, pc + mov pc , r7 + pop {r0 -r10, lr} + + .endm + .macro sync_l2_cache /* sync L2 cache to drain L2's buffers to DRAM. */ @@ -86,29 +135,8 @@ .endm - .macro resume_mmdc - - /* restore MMDC IO */ - cmp r5, #0x0 - ldreq r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] - ldrne r11, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] - - ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] - ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET - add r7, r7, r0 -1: - ldr r8, [r7], #0x4 - ldr r9, [r7], #0x4 - str r9, [r11, r8] - subs r6, r6, #0x1 - bne 1b - - cmp r5, #0x0 - ldreq r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] - ldrne r11, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] - - cmp r3, #IMX_DDR_TYPE_LPDDR2 - bne 4f + /* r11 must be MMDC base address */ + .macro reset_read_fifo /* reset read FIFO, RST_RD_FIFO */ ldr r7, =MX6Q_MMDC_MPDGCTRL0 @@ -128,23 +156,294 @@ ldr r6, [r11, r7] ands r6, r6, #(1 << 31) bne 3b + + /* check if lppdr2 2 channel mode is enabled */ + ldr r7, =MX6Q_MMDC_MISC + ldr r6, [r11, r7] + ands r6, r6, #(1 << 2) + beq 6f + + ldr r7, =MX6Q_MMDC_MPDGCTRL0 + ldr r6, [r12, r7] + orr r6, r6, #(1 << 31) + str r6, [r12, r7] 4: + ldr r6, [r12, r7] + ands r6, r6, #(1 << 31) + bne 4b + + ldr r6, [r12, r7] + orr r6, r6, #(1 << 31) + str r6, [r12, r7] +5: + ldr r6, [r12, r7] + ands r6, r6, #(1 << 31) + bne 5b + +6: + .endm + + /* r11 must be MMDC base address */ + .macro mmdc_out_and_auto_self_refresh + /* let DDR out of self-refresh */ ldr r7, [r11, #MX6Q_MMDC_MAPSR] bic r7, r7, #(1 << 21) str r7, [r11, #MX6Q_MMDC_MAPSR] -5: +7: ldr r7, [r11, #MX6Q_MMDC_MAPSR] ands r7, r7, #(1 << 25) - bne 5b + bne 7b /* enable DDR auto power saving */ ldr r7, [r11, #MX6Q_MMDC_MAPSR] bic r7, r7, #0x1 str r7, [r11, #MX6Q_MMDC_MAPSR] + /* check if lppdr2 2 channel mode is enabled */ + ldr r7, =MX6Q_MMDC_MISC + ldr r6, [r11, r7] + ands r6, r6, #(1 << 2) + beq 9f + + ldr r7, [r12, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r12, #MX6Q_MMDC_MAPSR] +8: + ldr r7, [r12, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne 8b + + ldr r7, [r12, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r12, #MX6Q_MMDC_MAPSR] +9: .endm + /* r10 must be iomuxc base address */ + .macro resume_iomuxc_gpr + + add r10, r10, #0x4000 + /* IOMUXC GPR DRAM_RESET_BYPASS */ + ldr r4, [r10, #0x8] + bic r4, r4, #(0x1 << 27) + str r4, [r10, #0x8] + /* IOMUXC GPR DRAM_CKE_BYPASS */ + ldr r4, [r10, #0x8] + bic r4, r4, #(0x1 << 31) + str r4, [r10, #0x8] + + .endm + + .macro resume_io + + /* restore MMDC IO */ + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] + + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +10: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x8 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 10b + + cmp r5, #0x0 + /* Here only MMDC0 is set */ + ldreq r11, [r0, #PM_INFO_MX6Q_MMDC0_V_OFFSET] + ldrne r11, [r0, #PM_INFO_MX6Q_MMDC0_P_OFFSET] + ldreq r12, [r0, #PM_INFO_MX6Q_MMDC1_V_OFFSET] + ldrne r12, [r0, #PM_INFO_MX6Q_MMDC1_P_OFFSET] + + reset_read_fifo + mmdc_out_and_auto_self_refresh + + .endm + + .macro resume_mmdc_io + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] + ldreq r11, [r0, #PM_INFO_MX6Q_MMDC0_V_OFFSET] + ldrne r11, [r0, #PM_INFO_MX6Q_MMDC0_P_OFFSET] + + /* resume mmdc iomuxc settings */ + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +11: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x8 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 11b + + /* check whether we need to restore MMDC */ + cmp r5, #0x0 + beq 12f + + /* check whether last suspend is with M/F mix off */ + ldr r9, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET] + ldr r6, [r9, #0x220] + cmp r6, #0x0 + bne 13f +12: + resume_iomuxc_gpr + reset_read_fifo + + b 17f +13: + /* restore MMDC settings */ + ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_VAL_OFFSET + add r7, r7, r0 +14: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r11, r8] + subs r6, r6, #0x1 + bne 14b + + /* let DDR enter self-refresh */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 20) + str r7, [r11, #MX6Q_MMDC_MAPSR] +15: + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + beq 15b + + resume_iomuxc_gpr + reset_read_fifo + + /* let DDR out of self-refresh */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 20) + str r7, [r11, #MX6Q_MMDC_MAPSR] +16: + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + bne 16b + + /* kick off MMDC */ + ldr r4, =0x0 + str r4, [r11, #0x1c] + +17: + mmdc_out_and_auto_self_refresh + + .endm + + .macro store_ttbr1 + + /* Store TTBR1 to pm_info->ttbr1 */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_MX6Q_TTBR1_V_OFFSET] + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + ldr r6, =iram_tlb_phys_addr + ldr r6, [r6] + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r6, c2, c0, 1 + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Disable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + is_cortex_a7 + beq 17f + +#ifdef CONFIG_CACHE_L2X0 + ldr r8, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] + mov r6, #0x0 + str r6, [r8, #0x100] + + dsb + isb +#endif +17: + .endm + + .macro restore_ttbr1 + + is_cortex_a7 + beq 18f + +#ifdef CONFIG_CACHE_L2X0 + /* Enable L2. */ + ldr r8, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] + ldr r7, =0x1 + str r7, [r8, #0x100] +#endif + +18: + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + /* Restore TTBCR */ + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Restore TTBR1, get the origin ttbr1 from pm info */ + ldr r7, [r0, #PM_INFO_MX6Q_TTBR1_V_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + + ENTRY(imx6_suspend) ldr r1, [r0, #PM_INFO_PBASE_OFFSET] ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET] @@ -179,10 +478,25 @@ ENTRY(imx6_suspend) str r9, [r11, #MX6Q_SRC_GPR1] str r1, [r11, #MX6Q_SRC_GPR2] + /* + * Check if the cpu is Cortex-A7, for Cortex-A7 + * the cache implementation is not the same as + * Cortex-A9, so the cache maintenance operation + * is different. + */ + is_cortex_a7 + beq a7_dache_flush + /* need to sync L2 cache before DSM. */ sync_l2_cache + b ttbr_store +a7_dache_flush: + disable_l1_cache +ttbr_store: + store_ttbr1 - ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + ldr r11, [r0, #PM_INFO_MX6Q_MMDC0_V_OFFSET] + ldr r12, [r0, #PM_INFO_MX6Q_MMDC1_V_OFFSET] /* * put DDR explicitly into self-refresh and * disable automatic power savings. @@ -201,31 +515,59 @@ poll_dvfs_set: ands r7, r7, #(1 << 25) beq poll_dvfs_set + /* check if lppdr2 2 channel mode is enabled */ + ldr r7, =MX6Q_MMDC_MISC + ldr r6, [r11, r7] + ands r6, r6, #(1 << 2) + beq skip_self_refresh_ch1 + + ldr r7, [r12, #MX6Q_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r12, #MX6Q_MMDC_MAPSR] + + ldr r7, [r12, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 21) + str r7, [r12, #MX6Q_MMDC_MAPSR] + +poll_dvfs_set_ch1: + ldr r7, [r12, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + beq poll_dvfs_set_ch1 + +skip_self_refresh_ch1: + /* use r11 to store the IO address */ ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] - ldr r6, =0x0 - ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET add r8, r8, r0 - /* LPDDR2's last 3 IOs need special setting */ - cmp r3, #IMX_DDR_TYPE_LPDDR2 - subeq r7, r7, #0x3 set_mmdc_io_lpm: - ldr r9, [r8], #0x8 - str r6, [r11, r9] - subs r7, r7, #0x1 + ldr r7, [r8], #0x8 + ldr r9, [r8], #0x4 + str r9, [r11, r7] + subs r6, r6, #0x1 bne set_mmdc_io_lpm - cmp r3, #IMX_DDR_TYPE_LPDDR2 - bne set_mmdc_io_lpm_done - ldr r6, =0x1000 - ldr r9, [r8], #0x8 - str r6, [r11, r9] - ldr r9, [r8], #0x8 - str r6, [r11, r9] - ldr r6, =0x80000 - ldr r9, [r8] - str r6, [r11, r9] -set_mmdc_io_lpm_done: + /* check whether it supports Mega/Fast off */ + ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET] + cmp r6, #0x0 + beq set_mmdc_lpm_done + + /* IOMUXC GPR DRAM_RESET */ + add r11, r11, #0x4000 + ldr r6, [r11, #0x8] + orr r6, r6, #(0x1 << 28) + str r6, [r11, #0x8] + + /* IOMUXC GPR DRAM_RESET_BYPASS */ + ldr r6, [r11, #0x8] + orr r6, r6, #(0x1 << 27) + str r6, [r11, #0x8] + + /* IOMUXC GPR DRAM_CKE_BYPASS */ + ldr r6, [r11, #0x8] + orr r6, r6, #(0x1 << 31) + str r6, [r11, #0x8] +set_mmdc_lpm_done: /* * mask all GPC interrupts before @@ -285,6 +627,27 @@ rbc_loop: subs r6, r6, #0x1 bne rbc_loop + /* + * ERR005852 Analog: Transition from Deep Sleep Mode to + * LDO Bypass Mode may cause the slow response of the + * VDDARM_CAP output. + * + * Software workaround: + * if internal ldo(VDDARM) bypassed, switch to analog bypass + * mode (0x1E), prio to entering DSM, and then, revert to the + * normal bypass mode, when exiting from DSM. + */ + ldr r11, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldr r10, [r11, #MX6Q_ANATOP_CORE] + and r10, r10, #0x1f + cmp r10, #0x1f + bne ldo_check_done1 +ldo_analog_bypass: + ldr r10, [r11, #MX6Q_ANATOP_CORE] + bic r10, r10, #0x1f + orr r10, r10, #0x1e + str r10, [r11, #MX6Q_ANATOP_CORE] +ldo_check_done1: /* Zzz, enter stop mode */ wfi nop @@ -297,8 +660,28 @@ rbc_loop: * wakeup source, system should auto * resume, we need to restore MMDC IO first */ + /* restore it with 0x1f if use ldo bypass mode.*/ + ldr r10, [r11, #MX6Q_ANATOP_CORE] + and r10, r10, #0x1f + cmp r10, #0x1e + bne ldo_check_done2 +ldo_bypass_restore: + ldr r10, [r11, #MX6Q_ANATOP_CORE] + orr r10, r10, #0x1f + str r10, [r11, #MX6Q_ANATOP_CORE] +ldo_check_done2: mov r5, #0x0 - resume_mmdc + /* check whether it supports Mega/Fast off */ + ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET] + cmp r6, #0x0 + beq only_resume_io + resume_mmdc_io + b resume_mmdc_done +only_resume_io: + resume_io +resume_mmdc_done: + + restore_ttbr1 /* return to suspend finish */ ret lr @@ -313,6 +696,16 @@ resume: mcr p15, 0, r6, c1, c0, 0 isb + /* restore it with 0x1f if use ldo bypass mode.*/ + ldr r11, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + ldr r7, [r11, #MX6Q_ANATOP_CORE] + and r7, r7, #0x1f + cmp r7, #0x1e + bne ldo_check_done3 + ldr r7, [r11, #MX6Q_ANATOP_CORE] + orr r7, r7, #0x1f + str r7, [r11, #MX6Q_ANATOP_CORE] +ldo_check_done3: /* get physical resume address from pm_info. */ ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] /* clear core0's entry and parameter */ @@ -323,7 +716,16 @@ resume: ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] mov r5, #0x1 - resume_mmdc + /* check whether it supports Mega/Fast off */ + ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET] + cmp r6, #0x0 + beq dsm_only_resume_io + resume_mmdc_io + b dsm_resume_mmdc_done +dsm_only_resume_io: + ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] + resume_io +dsm_resume_mmdc_done: ret lr ENDPROC(imx6_suspend) @@ -336,8 +738,11 @@ ENDPROC(imx6_suspend) ENTRY(v7_cpu_resume) bl v7_invalidate_l1 + is_cortex_a7 + beq done #ifdef CONFIG_CACHE_L2X0 bl l2c310_early_resume #endif +done: b cpu_resume ENDPROC(v7_cpu_resume) From a70f422f264c07b825ea632afab7933e1fa4c7ba Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Thu, 18 Apr 2019 16:01:53 +0800 Subject: [PATCH 08/81] ARM: imx: add i.MX6SX low power idle support This patch adds i.MX6SX low power idle support. Signed-off-by: Anson Huang [ Aisheng: Fix rebase conflict ] Signed-off-by: Dong Aisheng --- arch/arm/mach-imx/Makefile | 3 +- arch/arm/mach-imx/common.h | 2 + arch/arm/mach-imx/cpuidle-imx6sx.c | 256 ++++++- arch/arm/mach-imx/imx6sx_low_power_idle.S | 887 ++++++++++++++++++++++ 4 files changed, 1106 insertions(+), 42 deletions(-) create mode 100644 arch/arm/mach-imx/imx6sx_low_power_idle.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 4e8c2e8f9a73..1c2b9421065c 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -26,8 +26,9 @@ 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_IMX6SLL) += cpuidle-imx6sx.o imx6sx_low_power_idle.o obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o +AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6sx.o obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o endif diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 49f3456aa323..54f4c667db81 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -127,6 +127,7 @@ void imx_anatop_post_resume(void); int imx6_set_lpm(enum mxc_cpu_pwr_mode mode); void imx6_set_int_mem_clk_lpm(bool enable); void imx6sl_set_wait_clk(bool enter); +void imx6sx_low_power_idle(void); #ifdef CONFIG_HAVE_IMX_MMDC int imx_mmdc_get_ddr_type(void); int imx_mmdc_get_lpddr2_2ch_mode(void); @@ -191,4 +192,5 @@ static inline void imx_init_l2cache(void) {} extern const struct smp_operations imx_smp_ops; extern const struct smp_operations ls1021a_smp_ops; +extern bool uart_from_osc; #endif diff --git a/arch/arm/mach-imx/cpuidle-imx6sx.c b/arch/arm/mach-imx/cpuidle-imx6sx.c index 74ea1720e3d8..692775a223a2 100644 --- a/arch/arm/mach-imx/cpuidle-imx6sx.c +++ b/arch/arm/mach-imx/cpuidle-imx6sx.c @@ -1,20 +1,97 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. */ +#include #include #include +#include +#include +#include #include +#include #include #include +#include +#include +#include #include +#include + +#include #include "common.h" #include "cpuidle.h" #include "hardware.h" -static int imx6sx_idle_finish(unsigned long val) +#define MX6_MAX_MMDC_IO_NUM 19 + +#define PMU_LOW_PWR_CTRL 0x270 +#define XTALOSC24M_OSC_CONFIG0 0x2a0 +#define XTALOSC24M_OSC_CONFIG1 0x2b0 +#define XTALOSC24M_OSC_CONFIG2 0x2c0 +#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT 24 +#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK 0xf +#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT 16 +#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK 0xf +#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT 12 +#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT 4 +#define XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT 1 +#define XTALOSC24M_OSC_CONFIG0_START_SHIFT 0 +#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT 20 +#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT 0 +#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK 0xfff +#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT 0 + +extern unsigned long iram_tlb_phys_addr; + +static void __iomem *wfi_iram_base; +#ifdef CONFIG_CPU_FREQ +static void __iomem *wfi_iram_base_phys; +extern unsigned long mx6sx_lpm_wfi_start asm("mx6sx_lpm_wfi_start"); +extern unsigned long mx6sx_lpm_wfi_end asm("mx6sx_lpm_wfi_end"); +#endif + +struct imx6_pm_base { + phys_addr_t pbase; + void __iomem *vbase; +}; + +static const u32 imx6sx_mmdc_io_offset[] __initconst = { + 0x2ec, 0x2f0, 0x2f4, 0x2f8, /* DQM0 ~ DQM3 */ + 0x330, 0x334, 0x338, 0x33c, /* SDQS0 ~ SDQS3 */ + 0x60c, 0x610, 0x61c, 0x620, /* B0DS ~ B3DS */ + 0x5f8, 0x608, 0x310, 0x314, /* CTL, MODE, SODT0, SODT1 */ + 0x300, 0x2fc, 0x32c, /* CAS, RAS, SDCLK_0 */ +}; + +struct imx6_cpuidle_pm_info { + phys_addr_t pbase; /* The physical address of pm_info. */ + phys_addr_t resume_addr; /* The physical resume address for asm code */ + u32 pm_info_size; /* Size of pm_info. */ + u32 ttbr; + struct imx6_pm_base mmdc_base; + struct imx6_pm_base iomuxc_base; + struct imx6_pm_base ccm_base; + struct imx6_pm_base gpc_base; + struct imx6_pm_base l2_base; + struct imx6_pm_base anatop_base; + struct imx6_pm_base src_base; + struct imx6_pm_base sema4_base; + u32 saved_diagnostic; /* To save disagnostic register */ + u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */ + u32 mmdc_io_val[MX6_MAX_MMDC_IO_NUM][2]; /* To save offset and value */ +} __aligned(8); + +static void (*imx6sx_wfi_in_iram_fn)(void __iomem *iram_vbase); + +#define MX6SX_POWERDWN_IDLE_PARAM \ + ((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +static int imx6_idle_finish(unsigned long val) { /* * for Cortex-A7 which has an internal L2 @@ -25,7 +102,11 @@ static int imx6sx_idle_finish(unsigned long val) * just call flush_cache_all() is fine. */ flush_cache_all(); - cpu_do_idle(); + if (psci_ops.cpu_suspend) + psci_ops.cpu_suspend(MX6SX_POWERDWN_IDLE_PARAM, + __pa(cpu_resume)); + else + imx6sx_wfi_in_iram_fn(wfi_iram_base); return 0; } @@ -33,29 +114,22 @@ static int imx6sx_idle_finish(unsigned long val) static int imx6sx_enter_wait(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) { + int mode = get_bus_freq_mode(); + imx6_set_lpm(WAIT_UNCLOCKED); - - switch (index) { - case 1: + if ((index == 1) || ((mode != BUS_FREQ_LOW) && index == 2)) { + index = 1; cpu_do_idle(); - break; - case 2: - imx6_enable_rbc(true); - imx_gpc_set_arm_power_in_lpm(true); - imx_set_cpu_jump(0, v7_cpu_resume); - /* Need to notify there is a cpu pm operation. */ - cpu_pm_enter(); - cpu_cluster_pm_enter(); + } else { + /* Need to notify there is a cpu pm operation. */ + cpu_pm_enter(); + cpu_cluster_pm_enter(); - cpu_suspend(0, imx6sx_idle_finish); + cpu_suspend(0, imx6_idle_finish); - cpu_cluster_pm_exit(); - cpu_pm_exit(); - imx_gpc_set_arm_power_in_lpm(false); - imx6_enable_rbc(false); - break; - default: - break; + cpu_cluster_pm_exit(); + cpu_pm_exit(); + imx6_enable_rbc(false); } imx6_set_lpm(WAIT_CLOCKED); @@ -69,25 +143,23 @@ static struct cpuidle_driver imx6sx_cpuidle_driver = { .states = { /* WFI */ ARM_CPUIDLE_WFI_STATE, - /* WAIT */ + /* WAIT MODE */ { .exit_latency = 50, .target_residency = 75, - .flags = CPUIDLE_FLAG_TIMER_STOP, .enter = imx6sx_enter_wait, .name = "WAIT", .desc = "Clock off", }, - /* WAIT + ARM power off */ + /* LOW POWER IDLE */ { /* - * ARM gating 31us * 5 + RBC clear 65us - * and some margin for SW execution, here set it - * to 300us. + * RBC 130us + ARM gating 93us + RBC clear 65us + * + PLL2 relock 450us and some margin, here set + * it to 800us. */ - .exit_latency = 300, - .target_residency = 500, - .flags = CPUIDLE_FLAG_TIMER_STOP, + .exit_latency = 800, + .target_residency = 1000, .enter = imx6sx_enter_wait, .name = "LOW-POWER-IDLE", .desc = "ARM power off", @@ -99,17 +171,119 @@ static struct cpuidle_driver imx6sx_cpuidle_driver = { int __init imx6sx_cpuidle_init(void) { + void __iomem *anatop_base = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR); + u32 val; +#ifdef CONFIG_CPU_FREQ + struct imx6_cpuidle_pm_info *cpuidle_pm_info; + int i; + const u32 *mmdc_offset_array; + u32 wfi_code_size; + + wfi_iram_base_phys = (void *)(iram_tlb_phys_addr + MX6_CPUIDLE_IRAM_ADDR_OFFSET); + + /* Make sure wfi_iram_base is 8 byte aligned. */ + if ((uintptr_t)(wfi_iram_base_phys) & (FNCPY_ALIGN - 1)) + wfi_iram_base_phys += FNCPY_ALIGN - ((uintptr_t)wfi_iram_base_phys % (FNCPY_ALIGN)); + + wfi_iram_base = (void *)IMX_IO_P2V((unsigned long) wfi_iram_base_phys); + + cpuidle_pm_info = wfi_iram_base; + cpuidle_pm_info->pbase = (phys_addr_t) wfi_iram_base_phys; + cpuidle_pm_info->pm_info_size = sizeof(*cpuidle_pm_info); + cpuidle_pm_info->resume_addr = virt_to_phys(v7_cpu_resume); + cpuidle_pm_info->mmdc_io_num = ARRAY_SIZE(imx6sx_mmdc_io_offset); + mmdc_offset_array = imx6sx_mmdc_io_offset; + + cpuidle_pm_info->mmdc_base.pbase = MX6Q_MMDC_P0_BASE_ADDR; + cpuidle_pm_info->mmdc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR); + + cpuidle_pm_info->ccm_base.pbase = MX6Q_CCM_BASE_ADDR; + cpuidle_pm_info->ccm_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_CCM_BASE_ADDR); + + cpuidle_pm_info->anatop_base.pbase = MX6Q_ANATOP_BASE_ADDR; + cpuidle_pm_info->anatop_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR); + + cpuidle_pm_info->gpc_base.pbase = MX6Q_GPC_BASE_ADDR; + cpuidle_pm_info->gpc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_GPC_BASE_ADDR); + + cpuidle_pm_info->iomuxc_base.pbase = MX6Q_IOMUXC_BASE_ADDR; + cpuidle_pm_info->iomuxc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR); + + cpuidle_pm_info->l2_base.pbase = MX6Q_L2_BASE_ADDR; + cpuidle_pm_info->l2_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_L2_BASE_ADDR); + + cpuidle_pm_info->src_base.pbase = MX6Q_SRC_BASE_ADDR; + cpuidle_pm_info->src_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_SRC_BASE_ADDR); + + cpuidle_pm_info->sema4_base.pbase = MX6Q_SEMA4_BASE_ADDR; + cpuidle_pm_info->sema4_base.vbase = + (void __iomem *)IMX_IO_P2V(MX6Q_SEMA4_BASE_ADDR); + + /* only save mmdc io offset, settings will be saved in asm code */ + for (i = 0; i < cpuidle_pm_info->mmdc_io_num; i++) + cpuidle_pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i]; + + /* code size should include cpuidle_pm_info size */ + wfi_code_size = (&mx6sx_lpm_wfi_end -&mx6sx_lpm_wfi_start) *4 + sizeof(*cpuidle_pm_info); + imx6sx_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info), + &imx6sx_low_power_idle, wfi_code_size); +#endif + imx6_set_int_mem_clk_lpm(true); - imx6_enable_rbc(false); - imx_gpc_set_l2_mem_power_in_lpm(false); - /* - * set ARM power up/down timing to the fastest, - * sw2iso and sw can be set to one 32K cycle = 31us - * except for power up sw2iso which need to be - * larger than LDO ramp up time. - */ - imx_gpc_set_arm_power_up_timing(cpu_is_imx6sx() ? 0xf : 0x2, 1); - imx_gpc_set_arm_power_down_timing(1, 1); + + if (imx_get_soc_revision() >= IMX_CHIP_REVISION_1_2) { + /* + * enable RC-OSC here, as it needs at least 4ms for RC-OSC to + * be stable, low power idle flow can NOT endure this big + * latency, so we make RC-OSC self-tuning enabled here. + */ + val = readl_relaxed(anatop_base + PMU_LOW_PWR_CTRL); + val |= 0x1; + writel_relaxed(val, anatop_base + PMU_LOW_PWR_CTRL); + /* + * config RC-OSC freq + * tune_enable = 1;tune_start = 1;hyst_plus = 0;hyst_minus = 0; + * osc_prog = 0xa7; + */ + writel_relaxed( + 0x4 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT | + 0xa7 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT | + 0x1 << XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT | + 0x1 << XTALOSC24M_OSC_CONFIG0_START_SHIFT, + anatop_base + XTALOSC24M_OSC_CONFIG0); + /* set count_trg = 0x2dc */ + writel_relaxed( + 0x40 << XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT | + 0x2dc << XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT, + anatop_base + XTALOSC24M_OSC_CONFIG1); + /* wait 4ms according to hardware design */ + msleep(4); + /* + * now add some hysteresis, hyst_plus=3, hyst_minus=3 + * (the minimum hysteresis that looks good is 2) + */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG0); + val &= ~((XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK << + XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) | + (XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK << + XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT)); + val |= (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) | + (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT); + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG0); + /* set the count_1m_trg = 0x2d7 */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG2); + val &= ~(XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK << + XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT); + val |= 0x2d7 << XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT; + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG2); + /* + * hardware design require to write XTALOSC24M_OSC_CONFIG0 or + * XTALOSC24M_OSC_CONFIG1 to + * make XTALOSC24M_OSC_CONFIG2 write work + */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG1); + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG1); + } return cpuidle_register(&imx6sx_cpuidle_driver, NULL); } diff --git a/arch/arm/mach-imx/imx6sx_low_power_idle.S b/arch/arm/mach-imx/imx6sx_low_power_idle.S new file mode 100644 index 000000000000..7ddda1cd1a8f --- /dev/null +++ b/arch/arm/mach-imx/imx6sx_low_power_idle.S @@ -0,0 +1,887 @@ +/* + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#define PM_INFO_PBASE_OFFSET 0x0 +#define PM_INFO_RESUME_ADDR_OFFSET 0x4 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0x8 +#define PM_INFO_PM_INFO_TTBR_OFFSET 0xc +#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 +#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 +#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18 +#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c +#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20 +#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24 +#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28 +#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c +#define PM_INFO_MX6Q_L2_P_OFFSET 0x30 +#define PM_INFO_MX6Q_L2_V_OFFSET 0x34 +#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x38 +#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x3c +#define PM_INFO_MX6Q_SRC_P_OFFSET 0x40 +#define PM_INFO_MX6Q_SRC_V_OFFSET 0x44 +#define PM_INFO_MX6Q_SEMA4_P_OFFSET 0x48 +#define PM_INFO_MX6Q_SEMA4_V_OFFSET 0x4c +#define PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET 0x50 +#define PM_INFO_MMDC_IO_NUM_OFFSET 0x54 +#define PM_INFO_MMDC_IO_VAL_OFFSET 0x58 + +#define MX6Q_MMDC_MAPSR 0x404 +#define MX6Q_MMDC_MPDGCTRL0 0x83c +#define MX6Q_SRC_GPR1 0x20 +#define MX6Q_SRC_GPR2 0x24 +#define MX6Q_GPC_IMR1 0x08 +#define MX6Q_GPC_IMR2 0x0c +#define MX6Q_GPC_IMR3 0x10 +#define MX6Q_GPC_IMR4 0x14 +#define MX6Q_CCM_CCR 0x0 + +.globl mx6sx_lpm_wfi_start +.globl mx6sx_lpm_wfi_end + + .macro pll_do_wait_lock +1: + ldr r7, [r10, r8] + ands r7, #0x80000000 + beq 1b + + .endm + + .macro ccm_do_wait +2: + ldr r7, [r10, #0x48] + cmp r7, #0x0 + bne 2b + + .endm + + .macro ccm_enter_idle + + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + + /* set ahb to 3MHz */ + ldr r7, [r10, #0x14] + orr r7, r7, #0x1c00 + str r7, [r10, #0x14] + + /* set perclk to 6MHz */ + ldr r7, [r10, #0x1c] + bic r7, r7, #0x3f + orr r7, r7, #0x3 + str r7, [r10, #0x1c] + + /* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */ + ldr r7, [r10, #0x14] + orr r7, r7, #0x2 + orr r7, r7, #(0x7 << 3) + str r7, [r10, #0x14] + + ccm_do_wait + + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + + /* set pll1_sw to from pll1 main */ + ldr r7, [r10, #0xc] + bic r7, r7, #0x4 + str r7, [r10, #0xc] + + /* set step from osc */ + ldr r7, [r10, #0xc] + bic r7, r7, #0x100 + str r7, [r10, #0xc] + + /* set pll1_sw to from step */ + ldr r7, [r10, #0xc] + orr r7, r7, #0x4 + str r7, [r10, #0xc] + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* Disable PLL1 bypass output */ + ldr r7, [r10] + bic r7, r7, #0x12000 + str r7, [r10] + + /* + * disable pll2, suppose when system enter low + * power idle mode, only 396MHz pfd needs pll2, + * now we switch arm clock to OSC, we can disable + * pll2 now, gate pll2_pfd2 first. + */ + ldr r7, [r10, #0x100] + orr r7, #0x800000 + str r7, [r10, #0x100] + + ldr r7, [r10, #0x30] + orr r7, r7, #0x1000 + bic r7, r7, #0x2000 + str r7, [r10, #0x30] + + .endm + + .macro ccm_exit_idle + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* enable pll2 and pll2_pfd2 */ + ldr r7, [r10, #0x30] + bic r7, r7, #0x1000 + orr r7, r7, #0x2000 + str r7, [r10, #0x30] + + ldr r8, =0x30 + pll_do_wait_lock + + ldr r7, [r10, #0x100] + bic r7, #0x800000 + str r7, [r10, #0x100] + + /* enable PLL1 bypass output */ + ldr r7, [r10] + orr r7, r7, #0x12000 + str r7, [r10] + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET] + + /* set perclk back to 24MHz */ + ldr r7, [r10, #0x1c] + bic r7, r7, #0x3f + str r7, [r10, #0x1c] + + /* set mmdc back to 24MHz */ + ldr r7, [r10, #0x14] + bic r7, r7, #0x7 + bic r7, r7, #(0x7 << 3) + str r7, [r10, #0x14] + + /* set ahb div back to 24MHz */ + ldr r7, [r10, #0x14] + bic r7, r7, #0x1c00 + str r7, [r10, #0x14] + + ccm_do_wait + + /* set pll1_sw to from pll1 main */ + ldr r7, [r10, #0xc] + bic r7, r7, #0x4 + str r7, [r10, #0xc] + + /* set step from pll2_pfd2 */ + ldr r7, [r10, #0xc] + orr r7, r7, #0x100 + str r7, [r10, #0xc] + + /* set pll1_sw to from step */ + ldr r7, [r10, #0xc] + orr r7, r7, #0x4 + str r7, [r10, #0xc] + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + .endm + + .macro anatop_enter_idle + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* + * check whether any PLL is enabled, as only when + * there is no PLLs enabled, 2P5 and 1P1 can be + * off and only enable weak ones. + */ + + /* arm pll1 */ + ldr r7, [r10, #0] + ands r7, r7, #(1 << 31) + bne 10f + + /* sys pll2 */ + ldr r7, [r10, #0x30] + ands r7, r7, #(1 << 31) + bne 10f + + /* usb pll3 */ + ldr r7, [r10, #0x10] + ands r7, r7, #(1 << 31) + bne 10f + + /* audio pll4 */ + ldr r7, [r10, #0x70] + ands r7, r7, #(1 << 31) + bne 10f + + /* vidio pll5 */ + ldr r7, [r10, #0xa0] + ands r7, r7, #(1 << 31) + bne 10f + + /* enet pll6 */ + ldr r7, [r10, #0xe0] + ands r7, r7, #(1 << 31) + bne 10f + + /* usb host pll7 */ + ldr r7, [r10, #0x20] + ands r7, r7, #(1 << 31) + bne 10f + + /* enable weak 2P5 and turn off regular 2P5 */ + ldr r7, [r10, #0x130] + orr r7, r7, #0x40000 + str r7, [r10, #0x130] + bic r7, r7, #0x1 + str r7, [r10, #0x130] + + /* enable weak 1p1 and turn off regular 1P1 */ + ldr r7, [r10, #0x110] + orr r7, r7, #0x40000 + str r7, [r10, #0x110] + bic r7, r7, #0x1 + str r7, [r10, #0x110] + + /* check whether ARM LDO is bypassed */ + ldr r7, [r10, #0x140] + and r7, r7, #0x1f + cmp r7, #0x1f + bne 10f + + /* low power band gap enable */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x20 + str r7, [r10, #0x270] + + /* turn off the bias current from the regular bandgap */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x80 + str r7, [r10, #0x270] + + /* + * clear the REFTOP_SELFBIASOFF, + * self-bias circuit of the band gap. + * Per RM, should be cleared when + * band gap is powered down. + */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x8 + str r7, [r10, #0x150] + + /* turn off regular bandgap */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x1 + str r7, [r10, #0x150] + + /* only switch to RC-OSC clk after TO1.2 */ + ldr r7, [r10, #0x260] + and r7, r7, #0x3 + cmp r7, #0x2 + blt 10f + + /* switch to RC-OSC */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x10 + str r7, [r10, #0x270] + + /* turn off XTAL-OSC */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x40000000 + str r7, [r10, #0x150] +10: + /* lower OSC current by 37.5% */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x6000 + str r7, [r10, #0x150] + + .endm + + .macro anatop_exit_idle + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* increase OSC current to normal */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x6000 + str r7, [r10, #0x150] + + /* only switch to RC-OSC after TO1.2 */ + ldr r7, [r10, #0x260] + and r7, r7, #0x3 + cmp r7, #0x2 + blt 15f + + /* turn on XTAL-OSC and detector */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x40000000 + orr r7, r7, #0x10000 + str r7, [r10, #0x150] + + /* wait for XTAL stable */ +14: + ldr r7, [r10, #0x150] + ands r7, r7, #0x8000 + beq 14b + + /* switch to XTAL-OSC */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x10 + str r7, [r10, #0x270] + + /* turn off XTAL-OSC detector */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x10000 + str r7, [r10, #0x150] +15: + /* check whether we need to enable 2P5/1P1 */ + ldr r7, [r10, #0x110] + ands r7, r7, #0x40000 + beq 11f + + /* check whether ARM LDO is bypassed */ + ldr r7, [r10, #0x140] + and r7, r7, #0x1f + cmp r7, #0x1f + bne 12f + + /* turn on regular bandgap and wait for stable */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x1 + str r7, [r10, #0x150] +13: + ldr r7, [r10, #0x150] + ands r7, #0x80 + beq 13b + + /* + * set the REFTOP_SELFBIASOFF, + * self-bias circuit of the band gap. + */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x8 + str r7, [r10, #0x150] + + /* turn on the bias current from the regular bandgap */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x80 + str r7, [r10, #0x270] + + /* low power band gap disable */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x20 + str r7, [r10, #0x270] +12: + /* enable regular 2P5 and turn off weak 2P5 */ + ldr r7, [r10, #0x130] + orr r7, r7, #0x1 + str r7, [r10, #0x130] + + /* Ensure the 2P5 is up. */ +3: + ldr r7, [r10, #0x130] + ands r7, r7, #0x20000 + beq 3b + ldr r7, [r10, #0x130] + bic r7, r7, #0x40000 + str r7, [r10, #0x130] + + /* enable regular 1p1 and turn off weak 1P1 */ + ldr r7, [r10, #0x110] + orr r7, r7, #0x1 + str r7, [r10, #0x110] +4: + ldr r7, [r10, #0x110] + ands r7, r7, #0x20000 + beq 4b + ldr r7, [r10, #0x110] + bic r7, r7, #0x40000 + str r7, [r10, #0x110] +11: + .endm + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + .endm + + .macro mmdc_enter_dvfs_mode + + /* disable automatic power savings. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + /* disable power down timer */ + ldr r7, [r10, #0x4] + bic r7, r7, #0xff00 + str r7, [r10, #0x4] + + /* make the DDR explicitly enter self-refresh. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +5: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + beq 5b + + .endm + + .macro resume_mmdc + + /* restore MMDC IO */ + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] + + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +6: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 6b + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] + + /* reset read FIFO, RST_RD_FIFO */ + ldr r7, =MX6Q_MMDC_MPDGCTRL0 + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +7: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 7b + + /* reset FIFO a second time */ + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +8: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 8b + + /* let DDR out of self-refresh */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +9: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne 9b + + /* enable power down timer */ + ldr r7, [r10, #0x4] + orr r7, r7, #0x5500 + str r7, [r10, #0x4] + + /* enable DDR auto power saving */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + .endm + + .macro sema4_lock + + /* lock share memory sema4 */ + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_SEMA4_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_SEMA4_P_OFFSET] + ldrb r6, =0x1 +16: + ldrb r7, [r10, #0x6] + cmp r7, #0x0 + bne 16b + strb r6, [r10, #0x6] + + .endm + + .macro sema4_unlock + + /* unlock share memory sema4 */ + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_SEMA4_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_SEMA4_P_OFFSET] + ldrb r6, =0x0 + strb r6, [r10, #0x6] + + .endm + + .macro tlb_set_to_ocram + + /* save ttbr */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + .endm + + .macro tlb_back_to_ddr + + /* Restore the TTBCR */ + + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* restore ttbr */ + ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + +.extern iram_tlb_phys_addr + +/* imx6sx_low_power_idle */ + + .align 3 +ENTRY(imx6sx_low_power_idle) +mx6sx_lpm_wfi_start: + push {r4 - r10} + + /* get necessary info from pm_info */ + ldr r1, [r0, #PM_INFO_PBASE_OFFSET] + ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r5, =imx6sx_low_power_idle + ldr r6, =wakeup + sub r6, r6, r5 + add r8, r1, r2 + add r3, r8, r6 + + /* store physical resume addr and pm_info address. */ + ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] + str r3, [r10, #0x20] + str r1, [r10, #0x24] + + /* save disagnostic register */ + mrc p15, 0, r7, c15, c0, 1 + str r7, [r0, #PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET] + + /* set ARM power to be gated */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r7, =0x1 + str r7, [r10, #0x2a0] + + disable_l1_dcache + +#ifdef CONFIG_CACHE_L2X0 + /* sync L2 */ + ldr r10, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] + + /* Wait for background operations to complete. */ +wait_for_l2_to_idle: + ldr r7, [r10, #0x730] + cmp r7, #0x0 + bne wait_for_l2_to_idle + + mov r7, #0x0 + str r7, [r10, #0x730] + /* disable L2 */ + str r7, [r10, #0x100] + + dsb + isb +#endif + + tlb_set_to_ocram + + /* make sure MMDC in self-refresh */ + ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + mmdc_enter_dvfs_mode + + /* save DDR IO settings */ + ldr r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldr r6, =0x0 + ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET + add r8, r8, r0 +save_and_set_mmdc_io_lpm: + ldr r9, [r8], #0x4 + ldr r5, [r10, r9] + str r6, [r10, r9] + str r5, [r8], #0x4 + subs r7, r7, #0x1 + bne save_and_set_mmdc_io_lpm + + mov r5, #0x0 + sema4_lock + ccm_enter_idle + anatop_enter_idle + sema4_unlock + + /* + * mask all GPC interrupts before + * enabling the RBC counters to + * avoid the counter starting too + * early if an interupt is already + * pending. + */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r4, [r10, #MX6Q_GPC_IMR1] + ldr r5, [r10, #MX6Q_GPC_IMR2] + ldr r6, [r10, #MX6Q_GPC_IMR3] + ldr r7, [r10, #MX6Q_GPC_IMR4] + + ldr r3, =0xffffffff + str r3, [r10, #MX6Q_GPC_IMR1] + str r3, [r10, #MX6Q_GPC_IMR2] + str r3, [r10, #MX6Q_GPC_IMR3] + str r3, [r10, #MX6Q_GPC_IMR4] + + /* + * enable the RBC bypass counter here + * to hold off the interrupts. RBC counter + * = 4 (120us). With this setting, the latency + * from wakeup interrupt to ARM power up + * is ~130uS. + */ + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldr r3, [r10, #MX6Q_CCM_CCR] + bic r3, r3, #(0x3f << 21) + orr r3, r3, #(0x4 << 21) + str r3, [r10, #MX6Q_CCM_CCR] + + /* enable the counter. */ + ldr r3, [r10, #MX6Q_CCM_CCR] + orr r3, r3, #(0x1 << 27) + str r3, [r10, #MX6Q_CCM_CCR] + + /* unmask all the GPC interrupts. */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + str r4, [r10, #MX6Q_GPC_IMR1] + str r5, [r10, #MX6Q_GPC_IMR2] + str r6, [r10, #MX6Q_GPC_IMR3] + str r7, [r10, #MX6Q_GPC_IMR4] + + /* + * now delay for a short while (3usec) + * ARM is at 24MHz at this point + * so a short loop should be enough. + * this delay is required to ensure that + * the RBC counter can start counting in + * case an interrupt is already pending + * or in case an interrupt arrives just + * as ARM is about to assert DSM_request. + */ + ldr r4, =50 +rbc_loop: + subs r4, r4, #0x1 + bne rbc_loop + + wfi + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + mov r5, #0x0 + sema4_lock + anatop_exit_idle + ccm_exit_idle + sema4_unlock + resume_mmdc + + /* clear ARM power gate setting */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r7, =0x0 + str r7, [r10, #0x2a0] + + /* enable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + orr r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + +#ifdef CONFIG_CACHE_L2X0 + ldr r10, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] + mov r7, #0x1 + /* enable L2 */ + str r7, [r10, #0x100] +#endif + + tlb_back_to_ddr + + /* Restore registers */ + pop {r4 - r10} + mov pc, lr + +wakeup: + + /* invalidate L1 I-cache first */ + mov r1, #0x0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 6 + /* enable the Icache and branch prediction */ + mov r1, #0x1800 + mcr p15, 0, r1, c1, c0, 0 + isb + /* restore disagnostic register */ + ldr r7, [r0, #PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET] + mcr p15, 0, r7, c15, c0, 1 + + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + /* clear core0's entry and parameter */ + ldr r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] + mov r7, #0x0 + str r7, [r10, #MX6Q_SRC_GPR1] + str r7, [r10, #MX6Q_SRC_GPR2] + + /* clear ARM power gate setting */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET] + ldr r7, =0x0 + str r7, [r10, #0x2a0] + + mov r5, #0x1 + sema4_lock + anatop_exit_idle + ccm_exit_idle + sema4_unlock + resume_mmdc + + /* Restore registers */ + mov pc, lr + .ltorg +mx6sx_lpm_wfi_end: From e12ffa41c1dcc70bc0e8815e03440ec727daeeb8 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Fri, 19 Apr 2019 13:04:34 +0800 Subject: [PATCH 09/81] ARM: imx: add i.MX6SX AMP system support This patch adds i.MX6SX A9-M4 AMP system support. Signed-off-by: Anson Huang --- arch/arm/mach-imx/Kconfig | 11 +++++++++++ arch/arm/mach-imx/busfreq-imx.c | 34 +++++++++++++++++++++++++++++++++ arch/arm/mach-imx/src.c | 9 +++++++++ 3 files changed, 54 insertions(+) diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index d4c5cd99ca18..a266b30d033f 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -43,6 +43,9 @@ config HAVE_IMX_GPC config HAVE_IMX_MMDC bool +config HAVE_IMX_AMP + bool + config HAVE_IMX_DDRC bool select HAVE_IMX_BUSFREQ @@ -53,6 +56,10 @@ config HAVE_IMX_BUSFREQ config HAVE_IMX_MU bool +config HAVE_IMX_RPMSG + select RPMSG_VIRTIO + bool + config HAVE_IMX_SRC def_bool y if SMP select ARCH_HAS_RESET_CONTROLLER @@ -521,8 +528,12 @@ config SOC_IMX6SLL config SOC_IMX6SX bool "i.MX6 SoloX support" select PINCTRL_IMX6SX + select HAVE_IMX_AMP select SOC_IMX6 select HAVE_IMX_MU + select HAVE_IMX_RPMSG + select RPMSG + select IMX_SEMA4 select KEYBOARD_SNVS_PWRKEY help diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index 0dea0fff3665..36fcc56334ff 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -108,6 +108,7 @@ static struct clk *pll_dram; static struct clk *ahb_sel_clk; static struct clk *axi_clk; +static struct clk *m4_clk; static struct clk *pll3_clk; static struct clk *pll2_400_clk; static struct clk *periph_clk2_sel_clk; @@ -131,6 +132,16 @@ static struct delayed_work bus_freq_daemon; static RAW_NOTIFIER_HEAD(busfreq_notifier_chain); +static bool check_m4_sleep(void) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(500); + + while (imx_gpc_is_m4_sleeping() == 0) + if (time_after(jiffies, timeout)) + return false; + return true; +} + static bool busfreq_notified_low = false; static int busfreq_notify(enum busfreq_event event) @@ -167,6 +178,10 @@ EXPORT_SYMBOL(unregister_busfreq_notifier); */ static void enter_lpm_imx6_up(void) { + if (cpu_is_imx6sx() && imx_src_is_m4_enabled()) + if (!check_m4_sleep()) + pr_err("M4 is NOT in sleep!!!\n"); + /* set periph_clk2 to source from OSC for periph */ clk_set_parent(periph_clk2_sel_clk, osc_clk); clk_set_parent(periph_clk, periph_clk2_clk); @@ -910,6 +925,13 @@ static int busfreq_probe(struct platform_device *pdev) } } + if (cpu_is_imx6sx()) { + m4_clk = devm_clk_get(&pdev->dev, "m4"); + if (IS_ERR(m4_clk)) { + dev_err(busfreq_dev, "%s: failed to get m4 clk.\n", __func__); + return -EINVAL; + } + } if (cpu_is_imx7d()) { osc_clk = devm_clk_get(&pdev->dev, "osc"); axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel"); @@ -1025,6 +1047,18 @@ static int busfreq_probe(struct platform_device *pdev) else err = -EINVAL; + if (!err) { + if (cpu_is_imx6sx()) { + /* + * If M4 is enabled and rate > 24MHz, + * add high bus count + */ + if (imx_src_is_m4_enabled() && + (clk_get_rate(m4_clk) > LPAPM_CLK)) + high_bus_count++; + } + } + if (err) { dev_err(busfreq_dev, "Busfreq init of ddr controller failed\n"); return err; diff --git a/arch/arm/mach-imx/src.c b/arch/arm/mach-imx/src.c index 3969578ea9d3..60dad38d02f7 100644 --- a/arch/arm/mach-imx/src.c +++ b/arch/arm/mach-imx/src.c @@ -12,6 +12,7 @@ #include #include #include "common.h" +#include "hardware.h" #define SRC_SCR 0x000 #define SRC_GPR1 0x020 @@ -136,6 +137,14 @@ void __init imx_src_init(void) */ spin_lock(&scr_lock); val = readl_relaxed(src_base + SRC_SCR); + + /* bit 4 is m4c_non_sclr_rst on i.MX6SX */ + if (cpu_is_imx6sx() && ((val & + (1 << BP_SRC_SCR_SW_OPEN_VG_RST)) == 0)) + m4_is_enabled = true; + else + m4_is_enabled = false; + val &= ~(1 << BP_SRC_SCR_WARM_RESET_ENABLE); writel_relaxed(val, src_base + SRC_SCR); spin_unlock(&scr_lock); From b199c5a4709cd5681c7b740829c77780730e6898 Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Fri, 19 Apr 2019 16:51:04 +0800 Subject: [PATCH 10/81] soc: imx: fix build error of missing imx_src_is_m4_enabled drivers/clk/imx/clk-gate2.o: In function `clk_gate2_do_shared_clks': /home/b29396/Work/linux/dash-linux-devel/build_v8/../drivers/clk/imx/clk-gate2.c:61: undefined reference to `imx_src_is_m4_enabled' drivers/clk/imx/clk-pfd.o: In function `clk_pfd_do_shared_clks': /home/b29396/Work/linux/dash-linux-devel/build_v8/../drivers/clk/imx/clk-pfd.c:55: undefined reference to `imx_src_is_m4_enabled' /home/b29396/Work/linux/dash-linux-devel/build_v8/../drivers/clk/imx/clk-pfd.c:55: undefined reference to `imx_src_is_m4_enabled' drivers/clk/imx/clk-pllv3.o: In function `clk_pllv3_do_shared_clks': /home/b29396/Work/linux/dash-linux-devel/build_v8/../drivers/clk/imx/clk-pllv3.c:109: undefined reference to `imx_src_is_m4_enabled' /home/b29396/Work/linux/dash-linux-devel/Makefile:1047: recipe for target 'vmlinux' failed make[1]: *** [vmlinux] Error 1 make[1]: Leaving directory '/home/b29396/Work/linux/dash-linux-devel/build_v8' Makefile:179: recipe for target 'sub-make' failed make: *** [sub-make] Error 2 Signed-off-by: Dong Aisheng --- drivers/soc/imx/soc-imx8.c | 28 ++++++++++++++++++++++++++++ include/soc/imx/soc.h | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 include/soc/imx/soc.h diff --git a/drivers/soc/imx/soc-imx8.c b/drivers/soc/imx/soc-imx8.c index b9831576dd25..416e099dd4eb 100644 --- a/drivers/soc/imx/soc-imx8.c +++ b/drivers/soc/imx/soc-imx8.c @@ -3,6 +3,7 @@ * Copyright 2019 NXP. */ +#include #include #include #include @@ -11,6 +12,8 @@ #include #include +#include + #define REV_B1 0x21 #define IMX8MQ_SW_INFO_B1 0x40 @@ -198,3 +201,28 @@ free_soc: return ret; } device_initcall(imx8_soc_init); + +#define FSL_SIP_SRC 0xc2000005 +#define FSL_SIP_SRC_M4_START 0x00 +#define FSL_SIP_SRC_M4_STARTED 0x01 + +/* To indicate M4 enabled or not on i.MX8MQ */ +static bool m4_is_enabled; +bool imx_src_is_m4_enabled(void) +{ + return m4_is_enabled; +} + +int check_m4_enabled(void) +{ + struct arm_smccc_res res; + + arm_smccc_smc(FSL_SIP_SRC, FSL_SIP_SRC_M4_STARTED, 0, + 0, 0, 0, 0, 0, &res); + m4_is_enabled = !!res.a0; + + if (m4_is_enabled) + printk("M4 is started\n"); + + return 0; +} diff --git a/include/soc/imx/soc.h b/include/soc/imx/soc.h new file mode 100644 index 000000000000..78e05523154a --- /dev/null +++ b/include/soc/imx/soc.h @@ -0,0 +1,20 @@ +/* + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __SOC_IMX8_SOC_H__ +#define __SOC_IMX8_SOC_H__ + +int check_m4_enabled(void); + +#endif From ee891b4c23718a6d802a9a641db965054d1043b3 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Mon, 22 Apr 2019 09:25:24 +0800 Subject: [PATCH 11/81] ARM: imx: add i.MX7D power management support This patch adds i.MX7D power management support, including low power idle, suspend with FastMix off, A7-M4 AMP power management support. Signed-off-by: Anson Huang [ Aisheng: remove duplicated imx7d_init_late definition ] Signed-off-by: Dong Aisheng --- arch/arm/mach-imx/Kconfig | 5 + arch/arm/mach-imx/Makefile | 6 +- arch/arm/mach-imx/busfreq-imx.c | 9 + arch/arm/mach-imx/common.h | 20 + arch/arm/mach-imx/cpuidle-imx7d.c | 390 ++++++++ arch/arm/mach-imx/cpuidle.h | 10 + arch/arm/mach-imx/gpcv2.c | 851 +++++++++++++++++ arch/arm/mach-imx/headsmp.S | 11 + arch/arm/mach-imx/imx7d_low_power_idle.S | 788 ++++++++++++++++ arch/arm/mach-imx/mach-imx7d.c | 26 + arch/arm/mach-imx/platsmp.c | 30 +- arch/arm/mach-imx/pm-imx7.c | 1079 ++++++++++++++++++++++ arch/arm/mach-imx/src.c | 76 +- arch/arm/mach-imx/suspend-imx7.S | 714 ++++++++++++++ 14 files changed, 3997 insertions(+), 18 deletions(-) create mode 100644 arch/arm/mach-imx/cpuidle-imx7d.c create mode 100644 arch/arm/mach-imx/gpcv2.c create mode 100644 arch/arm/mach-imx/imx7d_low_power_idle.S create mode 100644 arch/arm/mach-imx/suspend-imx7.S diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index a266b30d033f..57a2eb8e908a 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -40,6 +40,10 @@ config HAVE_IMX_GPC bool select PM_GENERIC_DOMAINS if PM +config HAVE_IMX_GPCV2 + bool + select PM_GENERIC_DOMAINS if PM + config HAVE_IMX_MMDC bool @@ -571,6 +575,7 @@ config SOC_IMX7D_CA7 select IMX_GPCV2 select HAVE_IMX_DDRC select HAVE_IMX_MU + select HAVE_IMX_GPCV2 select KEYBOARD_SNVS_PWRKEY config SOC_IMX7D_CM4 diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 1c2b9421065c..84e8112afb2b 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -31,6 +31,8 @@ obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6sx.o obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o +AFLAGS_imx7d_low_power_idle.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_IMX7D_CA7) += cpuidle-imx7d.o imx7d_low_power_idle.o endif ifdef CONFIG_SND_SOC_IMX_PCM_FIQ @@ -71,6 +73,7 @@ obj-$(CONFIG_MACH_IMX35_DT) += imx35-dt.o obj-$(CONFIG_HAVE_IMX_ANATOP) += anatop.o obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o +obj-$(CONFIG_HAVE_IMX_GPCV2) += gpcv2.o obj-$(CONFIG_HAVE_IMX_MMDC) += mmdc.o obj-$(CONFIG_HAVE_IMX_SRC) += src.o obj-$(CONFIG_HAVE_IMX_DDRC) += ddrc.o @@ -87,7 +90,7 @@ obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o ddr3_freq_imx6sx.o smp_wfe_imx6.o lpddr2_freq_imx6sx.o obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o obj-$(CONFIG_SOC_IMX7D_CA7) += mach-imx7d.o pm-imx7.o ddr3_freq_imx7d.o smp_wfe.o \ - lpddr3_freq_imx.o + lpddr3_freq_imx.o suspend-imx7.o obj-$(CONFIG_SOC_IMX7D_CM4) += mach-imx7d-cm4.o obj-$(CONFIG_SOC_IMX7ULP) += mach-imx7ulp.o pm-imx7ulp.o @@ -103,6 +106,7 @@ AFLAGS_ddr3_freq_imx6sx.o :=-Wa,-march=armv7-a ifeq ($(CONFIG_SUSPEND),y) AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a +AFLAGS_suspend-imx7.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o obj-$(CONFIG_SOC_IMX53) += suspend-imx53.o endif diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index 36fcc56334ff..c4aae89ced16 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -804,12 +804,16 @@ static int bus_freq_pm_notify(struct notifier_block *nb, unsigned long event, mutex_lock(&bus_freq_mutex); if (event == PM_SUSPEND_PREPARE) { + if (cpu_is_imx7d() && imx_src_is_m4_enabled()) + imx_mu_lpm_ready(false); high_bus_count++; set_high_bus_freq(1); busfreq_suspended = 1; } else if (event == PM_POST_SUSPEND) { busfreq_suspended = 0; high_bus_count--; + if (cpu_is_imx7d() && imx_src_is_m4_enabled()) + imx_mu_lpm_ready(true); schedule_delayed_work(&bus_freq_daemon, usecs_to_jiffies(5000000)); } @@ -1057,6 +1061,11 @@ static int busfreq_probe(struct platform_device *pdev) (clk_get_rate(m4_clk) > LPAPM_CLK)) high_bus_count++; } + + if (cpu_is_imx7d() && imx_src_is_m4_enabled()) { + high_bus_count++; + imx_mu_lpm_ready(true); + } } if (err) { diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 54f4c667db81..19a3c0243b77 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -57,9 +57,19 @@ void imx_gpc_set_arm_power_in_lpm(bool power_off); void imx_gpc_set_l2_mem_power_in_lpm(bool power_off); void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw); void imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw); +void imx_gpcv2_pre_suspend(bool arm_power_off); +void imx_gpcv2_post_resume(void); +unsigned int imx_gpcv2_is_mf_mix_off(void); +void imx_gpcv2_enable_wakeup_for_m4(void); +void imx_gpcv2_disable_wakeup_for_m4(void); void imx25_pm_init(void); void imx27_pm_init(void); void imx5_pmu_init(void); +#ifdef CONFIG_HAVE_IMX_MU +int imx_mu_lpm_ready(bool ready); +#else +static inline int imx_mu_lpm_ready(bool ready) { return 0; } +#endif enum mxc_cpu_pwr_mode { WAIT_CLOCKED, /* wfi only */ @@ -110,6 +120,10 @@ static inline void imx_gpcv2_add_m4_wake_up_irq(u32 hwirq, bool enable) {} #endif void imx_gpc_hold_m4_in_sleep(void); void imx_gpc_release_m4_in_sleep(void); +void __init imx_gpcv2_check_dt(void); +void imx_gpcv2_set_lpm_mode(enum mxc_cpu_pwr_mode mode); +void imx_gpcv2_set_cpu_power_gate_in_idle(bool pdn); +void imx_gpcv2_enable_rbc(bool enable); bool imx_mu_is_m4_in_low_freq(void); bool imx_mu_is_m4_in_stop(void); void imx_mu_set_m4_run_mode(void); @@ -128,6 +142,7 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode); void imx6_set_int_mem_clk_lpm(bool enable); void imx6sl_set_wait_clk(bool enter); void imx6sx_low_power_idle(void); +void imx7d_low_power_idle(void); #ifdef CONFIG_HAVE_IMX_MMDC int imx_mmdc_get_ddr_type(void); int imx_mmdc_get_lpddr2_2ch_mode(void); @@ -145,14 +160,18 @@ int imx_cpu_kill(unsigned int cpu); #ifdef CONFIG_SUSPEND void v7_cpu_resume(void); +void ca7_cpu_resume(void); void imx53_suspend(void __iomem *ocram_vbase); extern const u32 imx53_suspend_sz; void imx6_suspend(void __iomem *ocram_vbase); +void imx7_suspend(void __iomem *ocram_vbase); #else static inline void v7_cpu_resume(void) {} +static inline void ca7_cpu_resume(void) {} static inline void imx53_suspend(void __iomem *ocram_vbase) {} static const u32 imx53_suspend_sz; static inline void imx6_suspend(void __iomem *ocram_vbase) {} +static inline void imx7_suspend(void __iomem *ocram_vbase) {} #endif #ifdef CONFIG_HAVE_IMX_DDRC @@ -167,6 +186,7 @@ void imx6dl_pm_init(void); void imx6sl_pm_init(void); void imx6sx_pm_init(void); void imx6ul_pm_init(void); +void imx7d_pm_init(void); void imx7ulp_pm_init(void); #ifdef CONFIG_PM diff --git a/arch/arm/mach-imx/cpuidle-imx7d.c b/arch/arm/mach-imx/cpuidle-imx7d.c new file mode 100644 index 000000000000..8e1e61c5503b --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx7d.c @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +#define XTALOSC24M_OSC_CONFIG0 0x10 +#define XTALOSC24M_OSC_CONFIG1 0x20 +#define XTALOSC24M_OSC_CONFIG2 0x30 +#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT 24 +#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK 0xf +#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT 16 +#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK 0xf +#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT 12 +#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT 4 +#define XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT 1 +#define XTALOSC24M_OSC_CONFIG0_START_SHIFT 0 +#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT 20 +#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT 0 +#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK 0xfff +#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT 0 + +#define XTALOSC_CTRL_24M 0x0 +#define XTALOSC_CTRL_24M_RC_OSC_EN_SHIFT 13 +#define REG_SET 0x4 + +static void __iomem *wfi_iram_base; +static void __iomem *wfi_iram_base_phys; +extern unsigned long iram_tlb_phys_addr; + +struct imx7_pm_base { + phys_addr_t pbase; + void __iomem *vbase; +}; + +struct imx7_cpuidle_pm_info { + phys_addr_t vbase; /* The virtual address of pm_info. */ + phys_addr_t pbase; /* The physical address of pm_info. */ + phys_addr_t resume_addr; /* The physical resume address for asm code */ + u32 pm_info_size; + u32 ttbr; + u32 num_online_cpus; + u32 num_lpi_cpus; + atomic_t val; + atomic_t flag0; + atomic_t flag1; + struct imx7_pm_base ddrc_base; + struct imx7_pm_base ccm_base; + struct imx7_pm_base anatop_base; + struct imx7_pm_base src_base; + struct imx7_pm_base iomuxc_gpr_base; + struct imx7_pm_base gpc_base; + struct imx7_pm_base gic_dist_base; +} __aligned(8); + +static atomic_t master_lpi = ATOMIC_INIT(0); +static atomic_t master_wait = ATOMIC_INIT(0); + +static void (*imx7d_wfi_in_iram_fn)(void __iomem *iram_vbase); +static struct imx7_cpuidle_pm_info *cpuidle_pm_info; + +#define MX7D_POWERDWN_IDLE_PARAM \ + ((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +#define MX7D_STANDBY_IDLE_PARAM \ + ((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_STANDBY << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +/* Mapped for the kernel, unlike cpuidle_pm_info->gic_dist_base.vbase */ +static void __iomem *imx7d_cpuidle_gic_base; + +static void imx_pen_lock(int cpu) +{ + if (cpu == 0) { + atomic_set(&cpuidle_pm_info->flag0, 1); + dsb(); + atomic_set(&cpuidle_pm_info->val, cpu); + do { + dsb(); + } while (atomic_read(&cpuidle_pm_info->flag1) == 1 + && atomic_read(&cpuidle_pm_info->val) == cpu) + ; + } else { + atomic_set(&cpuidle_pm_info->flag1, 1); + dsb(); + atomic_set(&cpuidle_pm_info->val, cpu); + do { + dsb(); + } while (atomic_read(&cpuidle_pm_info->flag0) == 1 + && atomic_read(&cpuidle_pm_info->val) == cpu) + ; + } +} + +static void imx_pen_unlock(int cpu) +{ + dsb(); + if (cpu == 0) + atomic_set(&cpuidle_pm_info->flag0, 0); + else + atomic_set(&cpuidle_pm_info->flag1, 0); +} + +static int imx7d_idle_finish(unsigned long val) +{ + if (psci_ops.cpu_suspend) + psci_ops.cpu_suspend(MX7D_POWERDWN_IDLE_PARAM, __pa(cpu_resume)); + else + imx7d_wfi_in_iram_fn(wfi_iram_base); + + return 0; +} + +static bool imx7d_gic_sgis_pending(void) +{ + void __iomem *sgip_base = imx7d_cpuidle_gic_base + 0x1f20; + + return (readl_relaxed(sgip_base + 0x0) | + readl_relaxed(sgip_base + 0x4) | + readl_relaxed(sgip_base + 0x8) | + readl_relaxed(sgip_base + 0xc)); +} + +static DEFINE_SPINLOCK(psci_lock); +static int imx7d_enter_low_power_idle(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int mode = get_bus_freq_mode(); + + + if ((index == 1) || ((mode != BUS_FREQ_LOW) && index == 2)) { + index = 1; + if (atomic_inc_return(&master_wait) == num_online_cpus()) + imx_gpcv2_set_lpm_mode(WAIT_UNCLOCKED); + + cpu_do_idle(); + + atomic_dec(&master_wait); + imx_gpcv2_set_lpm_mode(WAIT_CLOCKED); + } else { + if (psci_ops.cpu_suspend) { + cpu_pm_enter(); + spin_lock(&psci_lock); + if (atomic_inc_return(&master_lpi) == num_online_cpus()) { + if (imx7d_gic_sgis_pending()) { + atomic_dec(&master_lpi); + index = -1; + goto psci_skip_lpi_flow; + } + + imx_gpcv2_set_lpm_mode(WAIT_UNCLOCKED); + imx_gpcv2_set_cpu_power_gate_in_idle(true); + + cpu_cluster_pm_enter(); + } + spin_unlock(&psci_lock); + + cpu_suspend(0, imx7d_idle_finish); + + spin_lock(&psci_lock); + if (atomic_read(&master_lpi) == num_online_cpus()) { + cpu_cluster_pm_exit(); + imx_gpcv2_set_cpu_power_gate_in_idle(false); + imx_gpcv2_set_lpm_mode(WAIT_CLOCKED); + } + + atomic_dec(&master_lpi); +psci_skip_lpi_flow: + spin_unlock(&psci_lock); + cpu_pm_exit(); + } else { + imx_pen_lock(dev->cpu); + cpuidle_pm_info->num_online_cpus = num_online_cpus(); + ++cpuidle_pm_info->num_lpi_cpus; + cpu_pm_enter(); + if (cpuidle_pm_info->num_lpi_cpus == + cpuidle_pm_info->num_online_cpus) { + /* + * GPC will not wake on SGIs so check for them + * manually here. At this point we know the other cpu + * is in wfi or waiting for the lock and can't send + * any additional IPIs. + */ + if (imx7d_gic_sgis_pending()) { + index = -1; + goto skip_lpi_flow; + } + imx_gpcv2_set_lpm_mode(WAIT_UNCLOCKED); + imx_gpcv2_set_cpu_power_gate_in_idle(true); + cpu_cluster_pm_enter(); + } else { + imx_set_cpu_jump(dev->cpu, ca7_cpu_resume); + } + + cpu_suspend(0, imx7d_idle_finish); + + if (cpuidle_pm_info->num_lpi_cpus == + cpuidle_pm_info->num_online_cpus) { + cpu_cluster_pm_exit(); + imx_gpcv2_set_cpu_power_gate_in_idle(false); + imx_gpcv2_set_lpm_mode(WAIT_CLOCKED); + } + +skip_lpi_flow: + cpu_pm_exit(); + --cpuidle_pm_info->num_lpi_cpus; + imx_pen_unlock(dev->cpu); + } + } + + return index; +} + +static struct cpuidle_driver imx7d_cpuidle_driver = { + .name = "imx7d_cpuidle", + .owner = THIS_MODULE, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT MODE */ + { + .exit_latency = 50, + .target_residency = 75, + .flags = CPUIDLE_FLAG_TIMER_STOP, + .enter = imx7d_enter_low_power_idle, + .name = "WAIT", + .desc = "Clock off", + }, + /* LOW POWER IDLE */ + { + .exit_latency = 10000, + .target_residency = 20000, + .flags = CPUIDLE_FLAG_TIMER_STOP, + .enter = imx7d_enter_low_power_idle, + .name = "LOW-POWER-IDLE", + .desc = "ARM power off", + }, + }, + .state_count = 3, + .safe_state_index = 0, +}; + +int imx7d_enable_rcosc(void) +{ + void __iomem *anatop_base = + (void __iomem *)IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR); + u32 val; + + imx_gpcv2_set_lpm_mode(WAIT_CLOCKED); + /* set RC-OSC freq and turn it on */ + writel_relaxed(0x1 << XTALOSC_CTRL_24M_RC_OSC_EN_SHIFT, + anatop_base + XTALOSC_CTRL_24M + REG_SET); + /* + * config RC-OSC freq + * tune_enable = 1;tune_start = 1;hyst_plus = 0;hyst_minus = 0; + * osc_prog = 0xa7; + */ + writel_relaxed( + 0x4 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT | + 0xa7 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT | + 0x1 << XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT | + 0x1 << XTALOSC24M_OSC_CONFIG0_START_SHIFT, + anatop_base + XTALOSC24M_OSC_CONFIG0); + /* set count_trg = 0x2dc */ + writel_relaxed( + 0x40 << XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT | + 0x2dc << XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT, + anatop_base + XTALOSC24M_OSC_CONFIG1); + /* wait at least 4ms according to hardware design */ + mdelay(6); + /* + * now add some hysteresis, hyst_plus=3, hyst_minus=3 + * (the minimum hysteresis that looks good is 2) + */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG0); + val &= ~((XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK << + XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) | + (XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK << + XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT)); + val |= (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) | + (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT); + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG0); + /* set the count_1m_trg = 0x2d7 */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG2); + val &= ~(XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK << + XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT); + val |= 0x2d7 << XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT; + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG2); + /* + * hardware design require to write XTALOSC24M_OSC_CONFIG0 or + * XTALOSC24M_OSC_CONFIG1 to + * make XTALOSC24M_OSC_CONFIG2 write work + */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG1); + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG1); + + return 0; +} + +int __init imx7d_cpuidle_init(void) +{ + wfi_iram_base_phys = (void *)(iram_tlb_phys_addr + + MX7_CPUIDLE_OCRAM_ADDR_OFFSET); + + /* Make sure wfi_iram_base is 8 byte aligned. */ + if ((uintptr_t)(wfi_iram_base_phys) & (FNCPY_ALIGN - 1)) + wfi_iram_base_phys += FNCPY_ALIGN - + ((uintptr_t)wfi_iram_base_phys % (FNCPY_ALIGN)); + + wfi_iram_base = (void *)IMX_IO_P2V((unsigned long) wfi_iram_base_phys); + + cpuidle_pm_info = wfi_iram_base; + cpuidle_pm_info->vbase = (phys_addr_t) wfi_iram_base; + cpuidle_pm_info->pbase = (phys_addr_t) wfi_iram_base_phys; + cpuidle_pm_info->pm_info_size = sizeof(*cpuidle_pm_info); + cpuidle_pm_info->resume_addr = virt_to_phys(ca7_cpu_resume); + cpuidle_pm_info->num_online_cpus = num_online_cpus(); + + cpuidle_pm_info->ddrc_base.pbase = MX7D_DDRC_BASE_ADDR; + cpuidle_pm_info->ddrc_base.vbase = + (void __iomem *)IMX_IO_P2V(MX7D_DDRC_BASE_ADDR); + + cpuidle_pm_info->ccm_base.pbase = MX7D_CCM_BASE_ADDR; + cpuidle_pm_info->ccm_base.vbase = + (void __iomem *)IMX_IO_P2V(MX7D_CCM_BASE_ADDR); + + cpuidle_pm_info->anatop_base.pbase = MX7D_ANATOP_BASE_ADDR; + cpuidle_pm_info->anatop_base.vbase = + (void __iomem *)IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR); + + cpuidle_pm_info->src_base.pbase = MX7D_SRC_BASE_ADDR; + cpuidle_pm_info->src_base.vbase = + (void __iomem *)IMX_IO_P2V(MX7D_SRC_BASE_ADDR); + + cpuidle_pm_info->iomuxc_gpr_base.pbase = MX7D_IOMUXC_GPR_BASE_ADDR; + cpuidle_pm_info->iomuxc_gpr_base.vbase = + (void __iomem *)IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR); + + cpuidle_pm_info->gpc_base.pbase = MX7D_GPC_BASE_ADDR; + cpuidle_pm_info->gpc_base.vbase = + (void __iomem *)IMX_IO_P2V(MX7D_GPC_BASE_ADDR); + + cpuidle_pm_info->gic_dist_base.pbase = MX7D_GIC_BASE_ADDR; + cpuidle_pm_info->gic_dist_base.vbase = + (void __iomem *)IMX_IO_P2V(MX7D_GIC_BASE_ADDR); + + imx7d_cpuidle_gic_base = ioremap(MX7D_GIC_BASE_ADDR, MX7D_GIC_SIZE); + + imx7d_enable_rcosc(); + + /* code size should include cpuidle_pm_info size */ + if (!psci_ops.cpu_suspend) { + imx7d_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + + sizeof(*cpuidle_pm_info), + &imx7d_low_power_idle, + MX7_CPUIDLE_OCRAM_SIZE - sizeof(*cpuidle_pm_info)); + } + + return cpuidle_register(&imx7d_cpuidle_driver, NULL); +} diff --git a/arch/arm/mach-imx/cpuidle.h b/arch/arm/mach-imx/cpuidle.h index ce552c096cae..1d3c93f3f6b9 100644 --- a/arch/arm/mach-imx/cpuidle.h +++ b/arch/arm/mach-imx/cpuidle.h @@ -9,6 +9,8 @@ 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 imx7d_cpuidle_init(void); +extern int imx7d_enable_rcosc(void); extern int imx7ulp_cpuidle_init(void); #else static inline int imx5_cpuidle_init(void) @@ -27,6 +29,14 @@ static inline int imx6sx_cpuidle_init(void) { return 0; } +static inline int imx7d_cpuidle_init(void) +{ + return 0; +} +static inline int imx7d_enable_rcosc(void) +{ + return 0; +} static inline int imx7ulp_cpuidle_init(void) { return 0; diff --git a/arch/arm/mach-imx/gpcv2.c b/arch/arm/mach-imx/gpcv2.c new file mode 100644 index 000000000000..3e8ab91cb97f --- /dev/null +++ b/arch/arm/mach-imx/gpcv2.c @@ -0,0 +1,851 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "hardware.h" + +#define IMR_NUM 4 +#define GPC_MAX_IRQS (IMR_NUM * 32) +#define GPC_LPCR_A7_BSC 0x0 +#define GPC_LPCR_A7_AD 0x4 +#define GPC_LPCR_M4 0x8 +#define GPC_SLPCR 0x14 +#define GPC_MLPCR 0x20 +#define GPC_PGC_ACK_SEL_A7 0x24 +#define GPC_MISC 0x2c +#define GPC_IMR1_CORE0 0x30 +#define GPC_IMR1_CORE1 0x40 +#define GPC_IMR1_M4 0x50 +#define GPC_SLOT0_CFG 0xb0 +#define GPC_PGC_CPU_MAPPING 0xec +#define GPC_CPU_PGC_SW_PUP_REQ 0xf0 +#define GPC_PU_PGC_SW_PUP_REQ 0xf8 +#define GPC_CPU_PGC_SW_PDN_REQ 0xfc +#define GPC_PU_PGC_SW_PDN_REQ 0x104 +#define GPC_GTOR 0x124 +#define GPC_PGC_C0 0x800 +#define GPC_PGC_C0_PUPSCR 0x804 +#define GPC_PGC_SCU_TIMING 0x890 +#define GPC_PGC_C1 0x840 +#define GPC_PGC_C1_PUPSCR 0x844 +#define GPC_PGC_SCU 0x880 +#define GPC_PGC_FM 0xa00 + +#define BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP 0x70000000 +#define BM_LPCR_A7_BSC_CPU_CLK_ON_LPM 0x4000 +#define BM_LPCR_A7_BSC_LPM1 0xc +#define BM_LPCR_A7_BSC_LPM0 0x3 +#define BP_LPCR_A7_BSC_LPM1 2 +#define BP_LPCR_A7_BSC_LPM0 0 +#define BM_LPCR_M4_MASK_DSM_TRIGGER 0x80000000 +#define BM_SLPCR_EN_DSM 0x80000000 +#define BM_SLPCR_RBC_EN 0x40000000 +#define BM_SLPCR_REG_BYPASS_COUNT 0x3f000000 +#define BM_SLPCR_VSTBY 0x4 +#define BM_SLPCR_SBYOS 0x2 +#define BM_SLPCR_BYPASS_PMIC_READY 0x1 +#define BM_SLPCR_EN_A7_FASTWUP_WAIT_MODE 0x10000 +#define BM_LPCR_A7_AD_L2PGE 0x10000 +#define BM_LPCR_A7_AD_EN_C1_PUP 0x800 +#define BM_LPCR_A7_AD_EN_C1_IRQ_PUP 0x400 +#define BM_LPCR_A7_AD_EN_C0_PUP 0x200 +#define BM_LPCR_A7_AD_EN_C0_IRQ_PUP 0x100 +#define BM_LPCR_A7_AD_EN_PLAT_PDN 0x10 +#define BM_LPCR_A7_AD_EN_C1_PDN 0x8 +#define BM_LPCR_A7_AD_EN_C1_WFI_PDN 0x4 +#define BM_LPCR_A7_AD_EN_C0_PDN 0x2 +#define BM_LPCR_A7_AD_EN_C0_WFI_PDN 0x1 + +#define BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7 0x2 +#define BM_GPC_PGC_PCG 0x1 +#define BM_GPC_PGC_CORE_PUPSCR 0x7fff80 + +#define BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK 0x80000000 +#define BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK 0x8000 +#define BM_GPC_MLPCR_MEMLP_CTL_DIS 0x1 + +#define BP_LPCR_A7_BSC_IRQ_SRC 28 + +#define MAX_SLOT_NUMBER 10 +#define A7_LPM_WAIT 0x5 +#define A7_LPM_STOP 0xa + +enum imx_gpc_slot { + CORE0_A7, + CORE1_A7, + SCU_A7, + FAST_MEGA_MIX, + MIPI_PHY, + PCIE_PHY, + USB_OTG1_PHY, + USB_OTG2_PHY, + USB_HSIC_PHY, + CORE0_M4, +}; + +static void __iomem *gpc_base; +static u32 gpcv2_wake_irqs[IMR_NUM]; +static u32 gpcv2_saved_imrs[IMR_NUM]; +static u32 gpcv2_saved_imrs_m4[IMR_NUM]; +static u32 gpcv2_mf_irqs[IMR_NUM]; +static u32 gpcv2_mf_request_on[IMR_NUM]; +static DEFINE_SPINLOCK(gpcv2_lock); + +void imx_gpcv2_add_m4_wake_up_irq(u32 hwirq, bool enable) +{ + unsigned int idx = hwirq / 32; + unsigned long flags; + u32 mask; + + /* Sanity check for SPI irq */ + if (hwirq < 32) + return; + + mask = 1 << hwirq % 32; + spin_lock_irqsave(&gpcv2_lock, flags); + gpcv2_wake_irqs[idx] = enable ? gpcv2_wake_irqs[idx] | mask : + gpcv2_wake_irqs[idx] & ~mask; + spin_unlock_irqrestore(&gpcv2_lock, flags); +} + +static int imx_gpcv2_irq_set_wake(struct irq_data *d, unsigned int on) +{ + unsigned int idx = d->hwirq / 32; + unsigned long flags; + u32 mask; + + BUG_ON(idx >= IMR_NUM); + + mask = 1 << d->hwirq % 32; + spin_lock_irqsave(&gpcv2_lock, flags); + gpcv2_wake_irqs[idx] = on ? gpcv2_wake_irqs[idx] | mask : + gpcv2_wake_irqs[idx] & ~mask; + spin_unlock_irqrestore(&gpcv2_lock, flags); + + return 0; +} + +void imx_gpcv2_mask_all(void) +{ + void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0; + int i; + + for (i = 0; i < IMR_NUM; i++) { + gpcv2_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); + writel_relaxed(~0, reg_imr1 + i * 4); + } +} + +void imx_gpcv2_restore_all(void) +{ + void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0; + int i; + + for (i = 0; i < IMR_NUM; i++) + writel_relaxed(gpcv2_saved_imrs[i], reg_imr1 + i * 4); +} + +void imx_gpcv2_hwirq_unmask(unsigned int hwirq) +{ + void __iomem *reg; + u32 val; + + reg = gpc_base + GPC_IMR1_CORE0 + (hwirq / 32) * 4; + val = readl_relaxed(reg); + val &= ~(1 << hwirq % 32); + writel_relaxed(val, reg); +} + +void imx_gpcv2_hwirq_mask(unsigned int hwirq) +{ + void __iomem *reg; + u32 val; + + reg = gpc_base + GPC_IMR1_CORE0 + (hwirq / 32) * 4; + val = readl_relaxed(reg); + val |= 1 << (hwirq % 32); + writel_relaxed(val, reg); +} + +static void imx_gpcv2_irq_unmask(struct irq_data *d) +{ + imx_gpcv2_hwirq_unmask(d->hwirq); + irq_chip_unmask_parent(d); +} + +static void imx_gpcv2_irq_mask(struct irq_data *d) +{ + imx_gpcv2_hwirq_mask(d->hwirq); + irq_chip_mask_parent(d); +} + +void imx_gpcv2_set_slot_ack(u32 index, enum imx_gpc_slot m_core, + bool mode, bool ack) +{ + u32 val; + + if (index >= MAX_SLOT_NUMBER) + pr_err("Invalid slot index!\n"); + /* set slot */ + writel_relaxed(readl_relaxed(gpc_base + GPC_SLOT0_CFG + index * 4) | + ((mode + 1) << (m_core * 2)), + gpc_base + GPC_SLOT0_CFG + index * 4); + + if (ack) { + /* set ack */ + val = readl_relaxed(gpc_base + GPC_PGC_ACK_SEL_A7); + /* clear dummy ack */ + val &= ~(1 << (15 + (mode ? 16 : 0))); + val |= 1 << (m_core + (mode ? 16 : 0)); + writel_relaxed(val, gpc_base + GPC_PGC_ACK_SEL_A7); + } +} + +void imx_gpcv2_set_lpm_mode(enum mxc_cpu_pwr_mode mode) +{ + unsigned long flags; + u32 val1, val2; + + spin_lock_irqsave(&gpcv2_lock, flags); + + val1 = readl_relaxed(gpc_base + GPC_LPCR_A7_BSC); + val2 = readl_relaxed(gpc_base + GPC_SLPCR); + + /* all cores' LPM settings must be same */ + val1 &= ~(BM_LPCR_A7_BSC_LPM0 | BM_LPCR_A7_BSC_LPM1); + + val1 |= BM_LPCR_A7_BSC_CPU_CLK_ON_LPM; + + val2 &= ~(BM_SLPCR_EN_DSM | BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN | + BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY); + /* + * GPC: When improper low-power sequence is used, + * the SoC enters low power mode before the ARM core executes WFI. + * + * Software workaround: + * 1) Software should trigger IRQ #32 (IOMUX) to be always pending + * by setting IOMUX_GPR1_IRQ. + * 2) Software should then unmask IRQ #32 in GPC before setting GPC + * Low-Power mode. + * 3) Software should mask IRQ #32 right after GPC Low-Power mode + * is set. + */ + switch (mode) { + case WAIT_CLOCKED: + imx_gpcv2_hwirq_unmask(0); + break; + case WAIT_UNCLOCKED: + val1 |= A7_LPM_WAIT << BP_LPCR_A7_BSC_LPM0; + val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM; + imx_gpcv2_hwirq_mask(0); + break; + case STOP_POWER_ON: + val1 |= A7_LPM_STOP << BP_LPCR_A7_BSC_LPM0; + val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM; + val2 |= BM_SLPCR_EN_DSM; + val2 |= BM_SLPCR_RBC_EN; + val2 |= BM_SLPCR_BYPASS_PMIC_READY; + imx_gpcv2_hwirq_mask(0); + break; + case STOP_POWER_OFF: + val1 |= A7_LPM_STOP << BP_LPCR_A7_BSC_LPM0; + val1 &= ~BM_LPCR_A7_BSC_CPU_CLK_ON_LPM; + val2 |= BM_SLPCR_EN_DSM; + val2 |= BM_SLPCR_RBC_EN; + val2 |= BM_SLPCR_SBYOS; + val2 |= BM_SLPCR_VSTBY; + val2 |= BM_SLPCR_BYPASS_PMIC_READY; + imx_gpcv2_hwirq_mask(0); + break; + default: + return; + } + writel_relaxed(val1, gpc_base + GPC_LPCR_A7_BSC); + writel_relaxed(val2, gpc_base + GPC_SLPCR); + + spin_unlock_irqrestore(&gpcv2_lock, flags); +} + +void imx_gpcv2_set_plat_power_gate_by_lpm(bool pdn) +{ + u32 val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD); + + val &= ~(BM_LPCR_A7_AD_EN_PLAT_PDN | BM_LPCR_A7_AD_L2PGE); + if (pdn) + val |= BM_LPCR_A7_AD_EN_PLAT_PDN | BM_LPCR_A7_AD_L2PGE; + + writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD); +} + +void imx_gpcv2_set_m_core_pgc(bool enable, u32 offset) +{ + u32 val = readl_relaxed(gpc_base + offset) & (~BM_GPC_PGC_PCG); + + if (enable) + val |= BM_GPC_PGC_PCG; + + writel_relaxed(val, gpc_base + offset); +} + +void imx_gpcv2_set_core1_pdn_pup_by_software(bool pdn) +{ + u32 val = readl_relaxed(gpc_base + (pdn ? + GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ)); + + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C1); + val |= BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7; + writel_relaxed(val, gpc_base + (pdn ? + GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ)); + + while ((readl_relaxed(gpc_base + (pdn ? + GPC_CPU_PGC_SW_PDN_REQ : GPC_CPU_PGC_SW_PUP_REQ)) & + BM_CPU_PGC_SW_PDN_PUP_REQ_CORE1_A7) != 0) + ; + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C1); +} + +void imx_gpcv2_set_cpu_power_gate_by_wfi(u32 cpu, bool pdn) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&gpcv2_lock, flags); + val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD); + + if (cpu == 0) { + if (pdn) { + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C0); + val |= BM_LPCR_A7_AD_EN_C0_WFI_PDN | + BM_LPCR_A7_AD_EN_C0_IRQ_PUP; + } else { + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C0); + val &= ~(BM_LPCR_A7_AD_EN_C0_WFI_PDN | + BM_LPCR_A7_AD_EN_C0_IRQ_PUP); + } + } + if (cpu == 1) { + if (pdn) { + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C1); + val |= BM_LPCR_A7_AD_EN_C1_WFI_PDN | + BM_LPCR_A7_AD_EN_C1_IRQ_PUP; + } else { + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C1); + val &= ~(BM_LPCR_A7_AD_EN_C1_WFI_PDN | + BM_LPCR_A7_AD_EN_C1_IRQ_PUP); + } + } + writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD); + spin_unlock_irqrestore(&gpcv2_lock, flags); +} + +void imx_gpcv2_set_cpu_power_gate_by_lpm(u32 cpu, bool pdn) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&gpcv2_lock, flags); + + val = readl_relaxed(gpc_base + GPC_LPCR_A7_AD); + if (cpu == 0) { + if (pdn) + val |= BM_LPCR_A7_AD_EN_C0_PDN | + BM_LPCR_A7_AD_EN_C0_PUP; + else + val &= ~(BM_LPCR_A7_AD_EN_C0_PDN | + BM_LPCR_A7_AD_EN_C0_PUP); + } + if (cpu == 1) { + if (pdn) + val |= BM_LPCR_A7_AD_EN_C1_PDN | + BM_LPCR_A7_AD_EN_C1_PUP; + else + val &= ~(BM_LPCR_A7_AD_EN_C1_PDN | + BM_LPCR_A7_AD_EN_C1_PUP); + } + + writel_relaxed(val, gpc_base + GPC_LPCR_A7_AD); + spin_unlock_irqrestore(&gpcv2_lock, flags); +} + +void imx_gpcv2_set_cpu_power_gate_in_idle(bool pdn) +{ + unsigned long flags; + u32 cpu; + + for_each_possible_cpu(cpu) + imx_gpcv2_set_cpu_power_gate_by_lpm(cpu, pdn); + + spin_lock_irqsave(&gpcv2_lock, flags); + + imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_C0); + if (num_online_cpus() > 1) + imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_C1); + imx_gpcv2_set_m_core_pgc(pdn, GPC_PGC_SCU); + imx_gpcv2_set_plat_power_gate_by_lpm(pdn); + + if (pdn) { + imx_gpcv2_set_slot_ack(0, CORE0_A7, false, false); + if (num_online_cpus() > 1) + imx_gpcv2_set_slot_ack(2, CORE1_A7, false, false); + imx_gpcv2_set_slot_ack(3, SCU_A7, false, true); + imx_gpcv2_set_slot_ack(6, SCU_A7, true, false); + if (num_online_cpus() > 1) + imx_gpcv2_set_slot_ack(6, CORE1_A7, true, false); + imx_gpcv2_set_slot_ack(6, CORE0_A7, true, true); + } else { + writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 0 * 0x4); + writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 2 * 0x4); + writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 3 * 0x4); + writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 6 * 0x4); + writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 7 * 0x4); + writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + 8 * 0x4); + writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK | + BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK, + gpc_base + GPC_PGC_ACK_SEL_A7); + imx_gpcv2_enable_rbc(false); + } + spin_unlock_irqrestore(&gpcv2_lock, flags); +} + +void imx_gpcv2_set_mix_phy_gate_by_lpm(u32 pdn_index, u32 pup_index) +{ + /* set power down slot */ + writel_relaxed(1 << (FAST_MEGA_MIX * 2), + gpc_base + GPC_SLOT0_CFG + pdn_index * 4); + + /* set power up slot */ + writel_relaxed(1 << (FAST_MEGA_MIX * 2 + 1), + gpc_base + GPC_SLOT0_CFG + pup_index * 4); +} + +unsigned int imx_gpcv2_is_mf_mix_off(void) +{ + return readl_relaxed(gpc_base + GPC_PGC_FM); +} + +static void imx_gpcv2_mf_mix_off(void) +{ + int i; + + for (i = 0; i < IMR_NUM; i++) + if (((gpcv2_wake_irqs[i] | gpcv2_mf_request_on[i]) & + gpcv2_mf_irqs[i]) != 0) + return; + + pr_info("Turn off Mega/Fast mix in DSM\n"); + imx_gpcv2_set_slot_ack(1, FAST_MEGA_MIX, false, false); + imx_gpcv2_set_slot_ack(5, FAST_MEGA_MIX, true, false); + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_FM); +} + +int imx_gpcv2_mf_power_on(unsigned int irq, unsigned int on) +{ + struct irq_desc *desc = irq_to_desc(irq); + unsigned long hwirq = desc->irq_data.hwirq; + unsigned int idx = hwirq / 32; + unsigned long flags; + u32 mask = 1 << (hwirq % 32); + + BUG_ON(idx >= IMR_NUM); + + spin_lock_irqsave(&gpcv2_lock, flags); + gpcv2_mf_request_on[idx] = on ? gpcv2_mf_request_on[idx] | mask : + gpcv2_mf_request_on[idx] & ~mask; + spin_unlock_irqrestore(&gpcv2_lock, flags); + + return 0; +} + +void imx_gpcv2_enable_rbc(bool enable) +{ + u32 val; + + /* + * need to mask all interrupts in GPC before + * operating RBC configurations + */ + imx_gpcv2_mask_all(); + + /* configure RBC enable bit */ + val = readl_relaxed(gpc_base + GPC_SLPCR); + val &= ~BM_SLPCR_RBC_EN; + val |= enable ? BM_SLPCR_RBC_EN : 0; + writel_relaxed(val, gpc_base + GPC_SLPCR); + + /* configure RBC count */ + val = readl_relaxed(gpc_base + GPC_SLPCR); + val &= ~BM_SLPCR_REG_BYPASS_COUNT; + val |= enable ? BM_SLPCR_REG_BYPASS_COUNT : 0; + writel(val, gpc_base + GPC_SLPCR); + + /* + * need to delay at least 2 cycles of CKIL(32K) + * due to hardware design requirement, which is + * ~61us, here we use 65us for safe + */ + udelay(65); + + /* restore GPC interrupt mask settings */ + imx_gpcv2_restore_all(); +} + + +void imx_gpcv2_pre_suspend(bool arm_power_off) +{ + void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0; + int i; + + if (arm_power_off) { + imx_gpcv2_set_lpm_mode(STOP_POWER_OFF); + /* enable core0 power down/up with low power mode */ + imx_gpcv2_set_cpu_power_gate_by_lpm(0, true); + /* enable plat power down with low power mode */ + imx_gpcv2_set_plat_power_gate_by_lpm(true); + + /* + * To avoid confuse, we use slot 0~4 for power down, + * slot 5~9 for power up. + * + * Power down slot sequence: + * Slot0 -> CORE0 + * Slot1 -> Mega/Fast MIX + * Slot2 -> SCU + * + * Power up slot sequence: + * Slot5 -> Mega/Fast MIX + * Slot6 -> SCU + * Slot7 -> CORE0 + */ + imx_gpcv2_set_slot_ack(0, CORE0_A7, false, false); + imx_gpcv2_set_slot_ack(2, SCU_A7, false, true); + + if ((!imx_src_is_m4_enabled()) || + (imx_src_is_m4_enabled() && imx_mu_is_m4_in_stop())) + imx_gpcv2_mf_mix_off();; + + imx_gpcv2_set_slot_ack(6, SCU_A7, true, false); + imx_gpcv2_set_slot_ack(6, CORE0_A7, true, true); + + /* enable core0, scu */ + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_C0); + imx_gpcv2_set_m_core_pgc(true, GPC_PGC_SCU); + } else { + imx_gpcv2_set_lpm_mode(STOP_POWER_ON); + } + + for (i = 0; i < IMR_NUM; i++) { + gpcv2_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); + writel_relaxed(~gpcv2_wake_irqs[i], reg_imr1 + i * 4); + } +} + +void imx_gpcv2_enable_wakeup_for_m4(void) +{ + void __iomem *reg_imr2 = gpc_base + GPC_IMR1_M4; + u32 i; + + for (i = 0; i < IMR_NUM; i++) { + gpcv2_saved_imrs_m4[i] = readl_relaxed(reg_imr2 + i * 4); + writel_relaxed(~gpcv2_wake_irqs[i], reg_imr2 + i * 4); + } +} + +void imx_gpcv2_disable_wakeup_for_m4(void) +{ + void __iomem *reg_imr2 = gpc_base + GPC_IMR1_M4; + u32 i; + + for (i = 0; i < IMR_NUM; i++) + writel_relaxed(gpcv2_saved_imrs_m4[i], reg_imr2 + i * 4); +} + +void imx_gpcv2_post_resume(void) +{ + void __iomem *reg_imr1 = gpc_base + GPC_IMR1_CORE0; + int i, val; + + /* only external IRQs to wake up LPM and core 0/1 */ + val = readl_relaxed(gpc_base + GPC_LPCR_A7_BSC); + val |= BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP; + writel_relaxed(val, gpc_base + GPC_LPCR_A7_BSC); + /* mask m4 dsm trigger if M4 NOT enabled */ + if (!imx_src_is_m4_enabled()) + writel_relaxed(readl_relaxed(gpc_base + GPC_LPCR_M4) | + BM_LPCR_M4_MASK_DSM_TRIGGER, gpc_base + GPC_LPCR_M4); + /* set mega/fast mix in A7 domain */ + writel_relaxed(0x1, gpc_base + GPC_PGC_CPU_MAPPING); + /* set SCU timing */ + writel_relaxed((0x59 << 10) | 0x5B | (0x2 << 20), + gpc_base + GPC_PGC_SCU_TIMING); + + /* set C0/C1 power up timming per design requirement */ + val = readl_relaxed(gpc_base + GPC_PGC_C0_PUPSCR); + val &= ~BM_GPC_PGC_CORE_PUPSCR; + val |= (0x1A << 7); + writel_relaxed(val, gpc_base + GPC_PGC_C0_PUPSCR); + + val = readl_relaxed(gpc_base + GPC_PGC_C1_PUPSCR); + val &= ~BM_GPC_PGC_CORE_PUPSCR; + val |= (0x1A << 7); + writel_relaxed(val, gpc_base + GPC_PGC_C1_PUPSCR); + + val = readl_relaxed(gpc_base + GPC_SLPCR); + val &= ~(BM_SLPCR_EN_DSM); + if (!imx_src_is_m4_enabled()) + val &= ~(BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN | + BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY); + val |= BM_SLPCR_EN_A7_FASTWUP_WAIT_MODE; + writel_relaxed(val, gpc_base + GPC_SLPCR); + + if (imx_get_soc_revision() == IMX_CHIP_REVISION_1_0) { + /* disable memory low power mode */ + val = readl_relaxed(gpc_base + GPC_MLPCR); + val |= BM_GPC_MLPCR_MEMLP_CTL_DIS; + writel_relaxed(val, gpc_base + GPC_MLPCR); + } + + for (i = 0; i < IMR_NUM; i++) + writel_relaxed(gpcv2_saved_imrs[i], reg_imr1 + i * 4); + + imx_gpcv2_set_lpm_mode(WAIT_CLOCKED); + imx_gpcv2_set_cpu_power_gate_by_lpm(0, false); + imx_gpcv2_set_plat_power_gate_by_lpm(false); + + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_C0); + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_SCU); + imx_gpcv2_set_m_core_pgc(false, GPC_PGC_FM); + for (i = 0; i < MAX_SLOT_NUMBER; i++){ + if (i == 1 || i == 5) /* skip slts m4 uses */ + continue; + writel_relaxed(0x0, gpc_base + GPC_SLOT0_CFG + i * 0x4); + } + writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK | + BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK, + gpc_base + GPC_PGC_ACK_SEL_A7); + + /* disable RBC */ + imx_gpcv2_enable_rbc(false); +} + +static struct irq_chip imx_gpcv2_chip = { + .name = "GPCV2", + .irq_eoi = irq_chip_eoi_parent, + .irq_mask = imx_gpcv2_irq_mask, + .irq_unmask = imx_gpcv2_irq_unmask, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_wake = imx_gpcv2_irq_set_wake, +#ifdef CONFIG_SMP + .irq_set_affinity = irq_chip_set_affinity_parent, +#endif +}; + +static int imx_gpcv2_domain_xlate(struct irq_domain *domain, + struct device_node *controller, + const u32 *intspec, + unsigned int intsize, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (irq_domain_get_of_node(domain) != controller) + return -EINVAL; /* Shouldn't happen, really... */ + if (intsize != 3) + return -EINVAL; /* Not GIC compliant */ + if (intspec[0] != 0) + return -EINVAL; /* No PPI should point to this domain */ + + *out_hwirq = intspec[1]; + *out_type = intspec[2]; + return 0; +} + +static int imx_gpcv2_domain_alloc(struct irq_domain *domain, + unsigned int irq, + unsigned int nr_irqs, void *data) +{ + struct irq_fwspec *fwspec = data; + struct irq_fwspec parent_fwspec; + irq_hw_number_t hwirq; + int i; + + if (fwspec->param_count != 3) + return -EINVAL; /* Not GIC compliant */ + if (fwspec->param[0] != 0) + return -EINVAL; /* No PPI should point to this domain */ + + hwirq = fwspec->param[1]; + if (hwirq >= GPC_MAX_IRQS) + return -EINVAL; /* Can't deal with this */ + + for (i = 0; i < nr_irqs; i++) + irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i, + &imx_gpcv2_chip, NULL); + + parent_fwspec.fwnode = domain->parent->fwnode; + parent_fwspec.param_count = 3; + parent_fwspec.param[0] = 0; + parent_fwspec.param[1] = hwirq; + parent_fwspec.param[2] = fwspec->param[2]; + + return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs, + &parent_fwspec); +} + +static struct irq_domain_ops imx_gpcv2_domain_ops = { + .xlate = imx_gpcv2_domain_xlate, + .alloc = imx_gpcv2_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +static int __init imx_gpcv2_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *parent_domain, *domain; + int i, val; + + if (!parent) { + pr_err("%s: no parent, giving up\n", node->full_name); + return -ENODEV; + } + + parent_domain = irq_find_host(parent); + if (!parent_domain) { + pr_err("%s: unable to obtain parent domain\n", node->full_name); + return -ENXIO; + } + + gpc_base = of_iomap(node, 0); + if (WARN_ON(!gpc_base)) + return -ENOMEM; + + domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS, + node, &imx_gpcv2_domain_ops, + NULL); + if (!domain) { + iounmap(gpc_base); + return -ENOMEM; + } + + /* Initially mask all interrupts */ + for (i = 0; i < IMR_NUM; i++) { + writel_relaxed(~0, gpc_base + GPC_IMR1_CORE0 + i * 4); + writel_relaxed(~0, gpc_base + GPC_IMR1_CORE1 + i * 4); + } + /* + * Due to hardware design requirement, need to make sure GPR + * interrupt(#32) is unmasked during RUN mode to avoid entering + * DSM by mistake. + */ + writel_relaxed(~0x1, gpc_base + GPC_IMR1_CORE0); + + /* Read supported wakeup source in M/F domain */ + if (cpu_is_imx7d()) { + of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 0, + &gpcv2_mf_irqs[0]); + of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 1, + &gpcv2_mf_irqs[1]); + of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 2, + &gpcv2_mf_irqs[2]); + of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 3, + &gpcv2_mf_irqs[3]); + if (!(gpcv2_mf_irqs[0] | gpcv2_mf_irqs[1] | + gpcv2_mf_irqs[2] | gpcv2_mf_irqs[3])) + pr_info("No wakeup source in Mega/Fast domain found!\n"); + } + + /* only external IRQs to wake up LPM and core 0/1 */ + val = readl_relaxed(gpc_base + GPC_LPCR_A7_BSC); + val |= BM_LPCR_A7_BSC_IRQ_SRC_A7_WAKEUP; + writel_relaxed(val, gpc_base + GPC_LPCR_A7_BSC); + /* mask m4 dsm trigger if M4 NOT enabled */ + if (!imx_src_is_m4_enabled()) + writel_relaxed(readl_relaxed(gpc_base + GPC_LPCR_M4) | + BM_LPCR_M4_MASK_DSM_TRIGGER, gpc_base + GPC_LPCR_M4); + /* set mega/fast mix in A7 domain */ + writel_relaxed(0x1, gpc_base + GPC_PGC_CPU_MAPPING); + /* set SCU timing */ + writel_relaxed((0x59 << 10) | 0x5B | (0x2 << 20), + gpc_base + GPC_PGC_SCU_TIMING); + + /* set C0/C1 power up timming per design requirement */ + val = readl_relaxed(gpc_base + GPC_PGC_C0_PUPSCR); + val &= ~BM_GPC_PGC_CORE_PUPSCR; + val |= (0x1A << 7); + writel_relaxed(val, gpc_base + GPC_PGC_C0_PUPSCR); + + val = readl_relaxed(gpc_base + GPC_PGC_C1_PUPSCR); + val &= ~BM_GPC_PGC_CORE_PUPSCR; + val |= (0x1A << 7); + writel_relaxed(val, gpc_base + GPC_PGC_C1_PUPSCR); + + writel_relaxed(BM_GPC_PGC_ACK_SEL_A7_DUMMY_PUP_ACK | + BM_GPC_PGC_ACK_SEL_A7_DUMMY_PDN_ACK, + gpc_base + GPC_PGC_ACK_SEL_A7); + + val = readl_relaxed(gpc_base + GPC_SLPCR); + val &= ~(BM_SLPCR_EN_DSM); + if (!imx_src_is_m4_enabled()) + val &= ~(BM_SLPCR_VSTBY | BM_SLPCR_RBC_EN | + BM_SLPCR_SBYOS | BM_SLPCR_BYPASS_PMIC_READY); + val |= BM_SLPCR_EN_A7_FASTWUP_WAIT_MODE; + writel_relaxed(val, gpc_base + GPC_SLPCR); + + if (imx_get_soc_revision() == IMX_CHIP_REVISION_1_0) { + /* disable memory low power mode */ + val = readl_relaxed(gpc_base + GPC_MLPCR); + val |= BM_GPC_MLPCR_MEMLP_CTL_DIS; + writel_relaxed(val, gpc_base + GPC_MLPCR); + } + + /* disable RBC */ + imx_gpcv2_enable_rbc(false); + + /* + * Clear the OF_POPULATED flag set in of_irq_init so that + * later the GPC power domain driver will not be skipped. + */ + of_node_clear_flag(node, OF_POPULATED); + + return 0; +} + +/* + * We cannot use the IRQCHIP_DECLARE macro that lives in + * drivers/irqchip, so we're forced to roll our own. Not very nice. + */ +OF_DECLARE_2(irqchip, imx_gpcv2, "fsl,imx7d-gpc", imx_gpcv2_init); + +void __init imx_gpcv2_check_dt(void) +{ + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7d-gpc"); + if (WARN_ON(!np)) + return; + + if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) { + pr_warn("Outdated DT detected, suspend/resume will NOT work\n"); + + /* map GPC, so that at least CPUidle and WARs keep working */ + gpc_base = of_iomap(np, 0); + } +} diff --git a/arch/arm/mach-imx/headsmp.S b/arch/arm/mach-imx/headsmp.S index 766dbdb2ae27..c19cfc3166ae 100644 --- a/arch/arm/mach-imx/headsmp.S +++ b/arch/arm/mach-imx/headsmp.S @@ -21,6 +21,17 @@ diag_reg_offset: ENTRY(v7_secondary_startup) ARM_BE8(setend be) @ go BE8 if entered LE + mrc p15, 0, r0, c0, c0, 0 + ldr r1, =0xf00 + orr r1, r1, #0xff + mov r0, r0, lsr #4 + and r0, r0, r1 + /* 0xc07 is cortex A7's ID */ + ldr r1, =0xc00 + orr r1, r1, #0x7 + cmp r0, r1 + beq secondary_startup + set_diag_reg b secondary_startup ENDPROC(v7_secondary_startup) diff --git a/arch/arm/mach-imx/imx7d_low_power_idle.S b/arch/arm/mach-imx/imx7d_low_power_idle.S new file mode 100644 index 000000000000..85c229700ec4 --- /dev/null +++ b/arch/arm/mach-imx/imx7d_low_power_idle.S @@ -0,0 +1,788 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#define PM_INFO_VBASE_OFFSET 0x0 +#define PM_INFO_PBASE_OFFSET 0x4 +#define PM_INFO_RESUME_ADDR_OFFSET 0x8 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0xc +#define PM_INFO_PM_INFO_TTBR_OFFSET 0x10 +#define PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET 0x14 +#define PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET 0x18 +#define PM_INFO_VAL_OFFSET 0x1c +#define PM_INFO_FLAG0_OFFSET 0x20 +#define PM_INFO_FLAG1_OFFSET 0x24 +#define PM_INFO_MX7D_DDRC_P_OFFSET 0x28 +#define PM_INFO_MX7D_DDRC_V_OFFSET 0x2c +#define PM_INFO_MX7D_CCM_P_OFFSET 0x30 +#define PM_INFO_MX7D_CCM_V_OFFSET 0x34 +#define PM_INFO_MX7D_ANATOP_P_OFFSET 0x38 +#define PM_INFO_MX7D_ANATOP_V_OFFSET 0x3c +#define PM_INFO_MX7D_SRC_P_OFFSET 0x40 +#define PM_INFO_MX7D_SRC_V_OFFSET 0x44 +#define PM_INFO_MX7D_IOMUXC_GPR_P_OFFSET 0x48 +#define PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET 0x4c +#define PM_INFO_MX7D_GPC_P_OFFSET 0x50 +#define PM_INFO_MX7D_GPC_V_OFFSET 0x54 +#define PM_INFO_MX7D_GIC_DIST_P_OFFSET 0x58 +#define PM_INFO_MX7D_GIC_DIST_V_OFFSET 0x5c + +#define MX7D_SRC_GPR1 0x74 +#define MX7D_SRC_GPR2 0x78 +#define MX7D_SRC_GPR3 0x7c +#define MX7D_SRC_GPR4 0x80 +#define MX7D_GPC_IMR1 0x30 +#define MX7D_GPC_IMR2 0x34 +#define MX7D_GPC_IMR3 0x38 +#define MX7D_GPC_IMR4 0x3c +#define DDRC_STAT 0x4 +#define DDRC_PWRCTL 0x30 +#define DDRC_DBG1 0x304 +#define DDRC_DBGCAM 0x308 +#define DDRC_PSTAT 0x3fc +#define DDRC_PCTRL_0 0x490 + +/* + * imx_pen_lock + * + * The reference link of Peterson's algorithm: + * http://en.wikipedia.org/wiki/Peterson's_algorithm + * + * val1 = r1 = !turn (inverted from Peterson's algorithm) + * on cpu 0: + * r2 = flag[0] (in flag0) + * r3 = flag[1] (in flag1) + * on cpu1: + * r2 = flag[1] (in flag1) + * r3 = flag[0] (in flag0) + * + */ + .macro imx_pen_lock + + mov r8, r0 + mrc p15, 0, r5, c0, c0, 5 + and r5, r5, #3 + add r6, r8, #PM_INFO_VAL_OFFSET + cmp r5, #0 + addeq r7, r8, #PM_INFO_FLAG0_OFFSET + addeq r8, r8, #PM_INFO_FLAG1_OFFSET + addne r7, r8, #PM_INFO_FLAG1_OFFSET + addne r8, r8, #PM_INFO_FLAG0_OFFSET + + mov r9, #1 + str r9, [r7] + dsb + str r5, [r6] +1: + dsb + ldr r9, [r8] + cmp r9, #1 + ldreq r9, [r6] + cmpeq r9, r5 + beq 1b + + .endm + + .macro imx_pen_unlock + + dsb + mrc p15, 0, r6, c0, c0, 5 + and r6, r6, #3 + cmp r6, #0 + addeq r7, r0, #PM_INFO_FLAG0_OFFSET + addne r7, r0, #PM_INFO_FLAG1_OFFSET + mov r9, #0 + str r9, [r7] + + .endm + + .macro disable_l1_dcache + + push {r0 - r12, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r12, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 - r12, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r12, lr} + +#ifdef CONFIG_SMP + clrex + + /* Turn off SMP bit. */ + mrc p15, 0, r8, c1, c0, 1 + bic r8, r8, #0x40 + mcr p15, 0, r8, c1, c0, 1 + + isb + dsb +#endif + + .endm + + .macro tlb_set_to_ocram + + /* save ttbr */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + .endm + + .macro tlb_back_to_ddr + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* restore ttbr */ + ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + + /* r10 must be DDRC base address */ + .macro ddrc_enter_self_refresh + + ldr r10, [r0, #PM_INFO_MX7D_DDRC_V_OFFSET] + + /* disable port */ + ldr r7, =0x0 + str r7, [r10, #DDRC_PCTRL_0] + + /* let DDR out of self-refresh */ + ldr r7, =0x0 + str r7, [r10, #DDRC_PWRCTL] + + /* wait rw port_busy clear */ + ldr r6, =(0x1 << 16) + orr r6, r6, #0x1 +2: + ldr r7, [r10, #DDRC_PSTAT] + ands r7, r7, r6 + bne 2b + + ldr r7, =0x1 + str r7, [r10, #DDRC_DBG1] + + ldr r6, =0x36000000 +11: + ldr r7, [r10, #DDRC_DBGCAM] + and r7, r7, r6 + cmp r7, r6 + bne 11b + + /* enter self-refresh bit 5 */ + ldr r7, =(0x1 << 5) + str r7, [r10, #DDRC_PWRCTL] + + /* wait until self-refresh mode entered */ +3: + ldr r7, [r10, #DDRC_STAT] + and r7, r7, #0x3 + cmp r7, #0x3 + bne 3b +4: + ldr r7, [r10, #DDRC_STAT] + ands r7, r7, #0x20 + beq 4b + + /* disable dram clk */ + ldr r7, [r10, #DDRC_PWRCTL] + orr r7, r7, #(1 << 3) + str r7, [r10, #DDRC_PWRCTL] + + /* + * TO1.1 adds feature of DDR pads power down, + * although TO1.0 has no such function, but it is + * NOT harmful to program GPR registers for TO1.0, + * it can avoid the logic of version check in idle + * thread. + */ + ldr r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET] + ldr r7, =0xf0000 + str r7, [r10] + + /* delay 20us, measured by gpio */ + ldr r7, =20 +12: + subs r7, r7, #0x1 + bne 12b + + .endm + + /* r10 must be DDRC base address */ + .macro ddrc_exit_self_refresh + + cmp r5, #0x1 + ldreq r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_P_OFFSET] + ldrne r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET] + + ldr r7, =0x0 + str r7, [r10] + + ldr r7, =20 +13: + subs r7, r7, #0x1 + bne 13b + + cmp r5, #0x1 + ldreq r10, [r0, #PM_INFO_MX7D_DDRC_P_OFFSET] + ldrne r10, [r0, #PM_INFO_MX7D_DDRC_V_OFFSET] + + ldr r7, =0x0 + str r7, [r10, #DDRC_DBG1] + + ldr r6, =0x30000000 +14: + ldr r7, [r10, #DDRC_DBGCAM] + and r7, r7, r6 + cmp r7, r6 + bne 14b + + /* let DDR out of self-refresh */ + ldr r7, =0x0 + str r7, [r10, #DDRC_PWRCTL] + + /* wait until self-refresh mode exited */ +5: + ldr r7, [r10, #DDRC_STAT] + and r7, r7, #0x3 + cmp r7, #0x3 + beq 5b + + /* enable auto self-refresh */ + ldr r7, [r10, #DDRC_PWRCTL] + orr r7, r7, #(1 << 0) + str r7, [r10, #DDRC_PWRCTL] + + ldr r7, =0x1 + str r7, [r10, #DDRC_PCTRL_0] + + .endm + + .macro pll_do_wait_lock +6: + ldr r7, [r10, r8] + ands r7, #0x80000000 + beq 6b + + .endm + + .macro ccm_enter_idle + + ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] + + /* ungate pfd1 332m for lower axi */ + ldr r7, =0x8000 + str r7, [r10, #0xc8] + + ldr r10, [r0, #PM_INFO_MX7D_CCM_V_OFFSET] + + /* switch ARM CLK to OSC */ + ldr r8, =0x8000 + ldr r7, [r10, r8] + bic r7, r7, #0x7000000 + str r7, [r10, r8] + + /* lower AXI clk from 24MHz to 3MHz */ + ldr r8, =0x8800 + ldr r7, [r10, r8] + orr r7, r7, #0x7 + str r7, [r10, r8] + + /* lower AHB clk from 24MHz to 3MHz */ + ldr r8, =0x9000 + ldr r7, [r10, r8] + orr r7, r7, #0x7 + str r7, [r10, r8] + + /* gate dram clk */ + ldr r8, =0x9880 + ldr r7, [r10, r8] + bic r7, r7, #0x10000000 + str r7, [r10, r8] + + ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] + + /* gate pfd1 332m */ + ldr r7, =0x8000 + str r7, [r10, #0xc4] + + /* gate system pll pfd div 1 */ + ldr r7, =0x10 + str r7, [r10, #0xb4] + /* power down ARM, 480 and DRAM PLL */ + ldr r7, =0x1000 + str r7, [r10, #0x64] + str r7, [r10, #0xb4] + ldr r7, =0x100000 + str r7, [r10, #0x74] + + .endm + + .macro ccm_exit_idle + + cmp r5, #0x1 + ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET] + ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] + + /* power up ARM, 480 and DRAM PLL */ + ldr r7, =0x1000 + str r7, [r10, #0x68] + ldr r8, =0x60 + pll_do_wait_lock + + ldr r7, =0x1000 + str r7, [r10, #0xb8] + ldr r8, =0xb0 + pll_do_wait_lock + + ldr r7, =0x100000 + str r7, [r10, #0x78] + ldr r8, =0x70 + pll_do_wait_lock + + /* ungate pfd1 332m for lower axi */ + ldr r7, =0x8000 + str r7, [r10, #0xc8] + + /* ungate system pll pfd div 1 */ + ldr r7, =0x10 + str r7, [r10, #0xb8] + + cmp r5, #0x1 + ldreq r10, [r0, #PM_INFO_MX7D_CCM_P_OFFSET] + ldrne r10, [r0, #PM_INFO_MX7D_CCM_V_OFFSET] + + /* switch ARM CLK to PLL */ + ldr r8, =0x8000 + ldr r7, [r10, r8] + orr r7, r7, #0x1000000 + str r7, [r10, r8] + + /* restore AXI clk from 3MHz to 24MHz */ + ldr r8, =0x8800 + ldr r7, [r10, r8] + bic r7, r7, #0x7 + str r7, [r10, r8] + + /* restore AHB clk from 3MHz to 24MHz */ + ldr r8, =0x9000 + ldr r7, [r10, r8] + bic r7, r7, #0x7 + str r7, [r10, r8] + + /* ungate dram clk */ + ldr r8, =0x9880 + ldr r7, [r10, r8] + orr r7, r7, #0x10000000 + str r7, [r10, r8] + + cmp r5, #0x1 + ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET] + ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] + + /* gate pfd1 332m for lower axi */ + ldr r7, =0x8000 + str r7, [r10, #0xc4] + + .endm + + .macro anatop_enter_idle + + ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] + + /* XTAL to RC-OSC switch */ + ldr r7, [r10] + orr r7, r7, #0x1000 + str r7, [r10] + /* power down XTAL */ + ldr r7, [r10] + orr r7, r7, #0x1 + str r7, [r10] + + /* enable weak 1P0A */ + ldr r7, [r10, #0x200] + orr r7, r7, #0x40000 + str r7, [r10, #0x200] + + /* disable LDO 1P0A */ + ldr r7, [r10, #0x200] + bic r7, r7, #0x1 + str r7, [r10, #0x200] + + /* disable LDO 1P0D */ + ldr r7, [r10, #0x210] + bic r7, r7, #0x1 + str r7, [r10, #0x210] + + /* disable LDO 1P2 */ + ldr r7, [r10, #0x220] + bic r7, r7, #0x1 + str r7, [r10, #0x220] + + /* switch to low power bandgap */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x400 + str r7, [r10, #0x270] + /* power down normal bandgap */ + orr r7, r7, #0x1 + str r7, [r10, #0x270] + + .endm + + .macro anatop_exit_idle + + cmp r5, #0x1 + ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET] + ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] + + /* power on normal bandgap */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x1 + str r7, [r10, #0x270] + /* switch to normal bandgap */ + bic r7, r7, #0x400 + str r7, [r10, #0x270] + + /* enable LDO 1P2 */ + ldr r7, [r10, #0x220] + orr r7, r7, #0x1 + str r7, [r10, #0x220] +7: + ldr r7, [r10, #0x220] + ands r7, #0x20000 + beq 7b + + /* enable LDO 1P0D */ + ldr r7, [r10, #0x210] + orr r7, r7, #0x1 + str r7, [r10, #0x210] +8: + ldr r7, [r10, #0x210] + ands r7, #0x20000 + beq 8b + + /* enable LDO 1P0A */ + ldr r7, [r10, #0x200] + orr r7, r7, #0x1 + str r7, [r10, #0x200] +9: + ldr r7, [r10, #0x200] + ands r7, #0x20000 + beq 9b + /* disable weak 1P0A */ + ldr r7, [r10, #0x200] + bic r7, r7, #0x40000 + str r7, [r10, #0x200] + + /* power up XTAL and wait */ + ldr r7, [r10] + bic r7, r7, #0x1 + str r7, [r10] +10: + ldr r7, [r10] + ands r7, r7, #0x4 + beq 10b + /* RC-OSC to XTAL switch */ + ldr r7, [r10] + bic r7, r7, #0x1000 + str r7, [r10] + + .endm + +.extern iram_tlb_phys_addr + + .align 3 +ENTRY(imx7d_low_power_idle) + push {r0 - r12} + + /* get necessary info from pm_info */ + ldr r1, [r0, #PM_INFO_PBASE_OFFSET] + ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r5, =imx7d_low_power_idle + ldr r6, =wakeup + sub r6, r6, r5 + add r8, r1, r2 + add r3, r8, r6 + + /* r11 is cpu id */ + mrc p15, 0, r11, c0, c0, 5 + and r11, r11, #3 + cmp r11, #0x0 + ldreq r6, =MX7D_SRC_GPR1 + ldreq r7, =MX7D_SRC_GPR2 + ldrne r6, =MX7D_SRC_GPR3 + ldrne r7, =MX7D_SRC_GPR4 + /* store physical resume addr and pm_info address. */ + ldr r10, [r0, #PM_INFO_MX7D_SRC_V_OFFSET] + str r3, [r10, r6] + str r1, [r10, r7] + + disable_l1_dcache + + tlb_set_to_ocram + + /* check last to sleep */ + ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET] + ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET] + cmp r6, r7 + bne lpi_enter_done + + ddrc_enter_self_refresh + ccm_enter_idle + anatop_enter_idle + + ldr r10, [r0, #PM_INFO_MX7D_GIC_DIST_V_OFFSET] + ldr r7, =0x0 + ldr r8, =0x1000 + str r7, [r10, r8] + + ldr r10, [r0, #PM_INFO_MX7D_GPC_V_OFFSET] + ldr r4, [r10, #MX7D_GPC_IMR1] + ldr r5, [r10, #MX7D_GPC_IMR2] + ldr r6, [r10, #MX7D_GPC_IMR3] + ldr r7, [r10, #MX7D_GPC_IMR4] + + ldr r8, =0xffffffff + str r8, [r10, #MX7D_GPC_IMR1] + str r8, [r10, #MX7D_GPC_IMR2] + str r8, [r10, #MX7D_GPC_IMR3] + str r8, [r10, #MX7D_GPC_IMR4] + + /* + * enable the RBC bypass counter here + * to hold off the interrupts. RBC counter + * = 8 (240us). With this setting, the latency + * from wakeup interrupt to ARM power up + * is ~250uS. + */ + ldr r8, [r10, #0x14] + bic r8, r8, #(0x3f << 24) + orr r8, r8, #(0x8 << 24) + str r8, [r10, #0x14] + + /* enable the counter. */ + ldr r8, [r10, #0x14] + orr r8, r8, #(0x1 << 30) + str r8, [r10, #0x14] + + /* unmask all the GPC interrupts. */ + str r4, [r10, #MX7D_GPC_IMR1] + str r5, [r10, #MX7D_GPC_IMR2] + str r6, [r10, #MX7D_GPC_IMR3] + str r7, [r10, #MX7D_GPC_IMR4] + + /* + * now delay for a short while (30usec) + * ARM is at 24MHz at this point + * so a short loop should be enough. + * this delay is required to ensure that + * the RBC counter can start counting in + * case an interrupt is already pending + * or in case an interrupt arrives just + * as ARM is about to assert DSM_request. + */ + ldr r4, =5 +rbc_loop: + subs r4, r4, #0x1 + bne rbc_loop + +lpi_enter_done: + + imx_pen_unlock + + wfi + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + imx_pen_lock + + /* check first to wake */ + ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET] + ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET] + cmp r6, r7 + bne skip_lpi_flow + + ldr r5, =0x0 + anatop_exit_idle + ccm_exit_idle + ddrc_exit_self_refresh + + ldr r10, [r0, #PM_INFO_MX7D_GIC_DIST_V_OFFSET] + ldr r7, =0x1 + ldr r8, =0x1000 + str r7, [r10, r8] + +skip_lpi_flow: + tlb_back_to_ddr + +#ifdef CONFIG_SMP + /* Turn on SMP bit. */ + mrc p15, 0, r7, c1, c0, 1 + orr r7, r7, #0x40 + mcr p15, 0, r7, c1, c0, 1 + + isb +#endif + + /* enable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + orr r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + /* Restore registers */ + pop {r0 - r12} + mov pc, lr + +wakeup: + + /* invalidate L1 I-cache first */ + mov r1, #0x0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 6 + /* enable the Icache and branch prediction */ + mov r1, #0x1800 + mcr p15, 0, r1, c1, c0, 0 + isb + + imx_pen_lock + + /* check first to wake */ + ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET] + ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET] + cmp r6, r7 + bne wakeup_skip_lpi_flow + + ldr r5, =0x1 + anatop_exit_idle + ccm_exit_idle + ddrc_exit_self_refresh + +wakeup_skip_lpi_flow: + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + + /* Restore registers */ + mov pc, lr + .ltorg +ENDPROC(imx7d_low_power_idle) diff --git a/arch/arm/mach-imx/mach-imx7d.c b/arch/arm/mach-imx/mach-imx7d.c index 6baead31ec29..c303ae3b0dfa 100644 --- a/arch/arm/mach-imx/mach-imx7d.c +++ b/arch/arm/mach-imx/mach-imx7d.c @@ -13,6 +13,13 @@ #include #include "common.h" +#include "cpuidle.h" + +static struct property device_disabled = { + .name = "status", + .length = sizeof("disabled"), + .value = "disabled", +}; static int ar8031_phy_fixup(struct phy_device *dev) { @@ -76,6 +83,17 @@ static inline void imx7d_enet_init(void) imx7d_enet_clk_sel(); } +static inline void imx7d_disable_arm_arch_timer(void) +{ + struct device_node *node; + + node = of_find_compatible_node(NULL, NULL, "arm,armv7-timer"); + if (node) { + pr_info("disable arm arch timer for nosmp!\n"); + of_add_property(node, &device_disabled); + } +} + static void __init imx7d_init_machine(void) { struct device *parent; @@ -85,20 +103,27 @@ static void __init imx7d_init_machine(void) pr_warn("failed to initialize soc device\n"); imx_anatop_init(); + of_platform_default_populate(NULL, NULL, parent); + imx7d_pm_init(); imx7d_enet_init(); } static void __init imx7d_init_late(void) { + imx7d_cpuidle_init(); if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT)) platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0); } static void __init imx7d_init_irq(void) { + imx_gpcv2_check_dt(); imx_init_revision_from_anatop(); imx_src_init(); irqchip_init(); +#ifndef CONFIG_SMP + imx7d_disable_arm_arch_timer(); +#endif } static void __init imx7d_map_io(void) @@ -116,6 +141,7 @@ static const char *const imx7d_dt_compat[] __initconst = { DT_MACHINE_START(IMX7D, "Freescale i.MX7 Dual (Device Tree)") .map_io = imx7d_map_io, + .smp = smp_ops(imx_smp_ops), .init_irq = imx7d_init_irq, .init_machine = imx7d_init_machine, .init_late = imx7d_init_late, diff --git a/arch/arm/mach-imx/platsmp.c b/arch/arm/mach-imx/platsmp.c index 7be31e7e91b3..90dccaa66c89 100644 --- a/arch/arm/mach-imx/platsmp.c +++ b/arch/arm/mach-imx/platsmp.c @@ -47,15 +47,39 @@ static int imx_boot_secondary(unsigned int cpu, struct task_struct *idle) return 0; } +#define MXC_ARCH_CA7 0xc07 +static unsigned long __mxc_arch_type; + +static inline bool arm_is_ca7(void) +{ + return __mxc_arch_type == MXC_ARCH_CA7; +} /* * Initialise the CPU possible map early - this describes the CPUs * which may be present or become present in the system. */ static void __init imx_smp_init_cpus(void) { + unsigned long arch_type; int i, ncores; - ncores = scu_get_core_count(scu_base); + asm volatile( + ".align 4\n" + "mrc p15, 0, %0, c0, c0, 0\n" + : "=r" (arch_type) + ); + /* MIDR[15:4] defines ARCH type */ + __mxc_arch_type = (arch_type >> 4) & 0xfff; + + if (arm_is_ca7()) { + unsigned long val; + + /* CA7 core number, [25:24] of CP15 L2CTLR */ + asm volatile("mrc p15, 1, %0, c9, c0, 2" : "=r" (val)); + ncores = ((val >> 24) & 0x3) + 1; + } else { + ncores = scu_get_core_count(scu_base); + } for (i = ncores; i < NR_CPUS; i++) set_cpu_possible(i, false); @@ -63,11 +87,15 @@ static void __init imx_smp_init_cpus(void) void imx_smp_prepare(void) { + if (arm_is_ca7()) + return; scu_enable(scu_base); } static void __init imx_smp_prepare_cpus(unsigned int max_cpus) { + if (arm_is_ca7()) + return; imx_smp_prepare(); /* diff --git a/arch/arm/mach-imx/pm-imx7.c b/arch/arm/mach-imx/pm-imx7.c index a1e3084798f5..e9f4d86f21aa 100644 --- a/arch/arm/mach-imx/pm-imx7.c +++ b/arch/arm/mach-imx/pm-imx7.c @@ -10,27 +10,373 @@ */ #include +#include #include #include +#include #include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include #include #include +#include +#include #include +#include #include #include +#include #include +#include + #include "common.h" #include "hardware.h" +#include "cpuidle.h" +#define MX7_SUSPEND_OCRAM_SIZE 0x1000 +#define MX7_MAX_DDRC_NUM 32 +#define MX7_MAX_DDRC_PHY_NUM 16 + +#define MX7_SUSPEND_IRAM_ADDR_OFFSET 0 +#define READ_DATA_FROM_HARDWARE 0 + +#define UART_UCR1 0x80 +#define UART_UCR2 0x84 +#define UART_UCR3 0x88 +#define UART_UCR4 0x8c +#define UART_UFCR 0x90 +#define UART_UESC 0x9c +#define UART_UTIM 0xa0 +#define UART_UBIR 0xa4 +#define UART_UBMR 0xa8 +#define UART_UBRC 0xac +#define UART_UTS 0xb4 + +#define MAX_IOMUXC_GPR 23 +#define MAX_UART_IO 4 +#define MAX_CCM_LPCG 167 +#define MAX_GPT 3 +#define MAX_GPIO_ROW 7 +#define MAX_GPIO_COL 8 + +#define UART_RX_IO 0x128 +#define UART_RX_PAD 0x398 +#define UART_TX_IO 0x12c +#define UART_TX_PAD 0x39c + +#define GPT_CR 0x0 +#define GPT_PR 0x4 +#define GPT_IR 0xc + +#define CCM_LPCG_START 0x4040 +#define CCM_LPCG_STEP 0x10 +#define CCM_EIM_LPCG 0x4160 +#define CCM_PXP_LPCG 0x44c0 +#define CCM_PCIE_LPCG 0x4600 + +#define BM_CCM_ROOT_POST_PODF 0x3f +#define BM_CCM_ROOT_PRE_PODF 0x70000 +#define BM_CCM_ROOT_MUX 0x7000000 +#define BM_CCM_ROOT_ENABLE 0x10000000 + +#define BM_SYS_COUNTER_CNTCR_FCR1 0x200 +#define BM_SYS_COUNTER_CNTCR_FCR0 0x100 + +#define PFD_A_OFFSET 0xc0 +#define PFD_B_OFFSET 0xd0 + +#define PLL_ARM_OFFSET 0x60 +#define PLL_DDR_OFFSET 0x70 +#define PLL_DDR_SS_OFFSET 0x80 +#define PLL_DDR_NUM_OFFSET 0x90 +#define PLL_DDR_DENOM_OFFSET 0xa0 +#define PLL_480_OFFSET 0xb0 +#define PLL_ENET_OFFSET 0xe0 +#define PLL_AUDIO_OFFSET 0xf0 +#define PLL_AUDIO_SS_OFFSET 0x100 +#define PLL_AUDIO_NUM_OFFSET 0x110 +#define PLL_AUDIO_DENOM_OFFSET 0x120 +#define PLL_VIDEO_OFFSET 0x130 +#define PLL_VIDEO_SS_OFFSET 0x140 +#define PLL_VIDEO_NUM_OFFSET 0x150 +#define PLL_VIDEO_DENOM_OFFSET 0x160 + +#define REG_SET 0x4 +#define REG_CLR 0x8 + +#define GPIO_DR 0x0 +#define GPIO_GDIR 0x4 +#define GPIO_ICR1 0xc +#define GPIO_ICR2 0x10 +#define GPIO_IMR 0x14 +#define GPIO_EDGE 0x1c + +#define M4RCR 0x0C +#define M4_SP_OFF 0x00 +#define M4_PC_OFF 0x04 +#define M4_RCR_HALT 0xAB +#define M4_RCR_GO 0xAA #define M4_OCRAMS_RESERVED_SIZE 0xc extern unsigned long iram_tlb_base_addr; extern unsigned long iram_tlb_phys_addr; +static unsigned int *ocram_saved_in_ddr; +static void __iomem *ocram_base; +static unsigned int ocram_size; +static unsigned int *lpm_ocram_saved_in_ddr; +static void __iomem *lpm_ocram_base; + +static unsigned int *lpm_m4tcm_saved_in_ddr; +static void __iomem *lpm_m4tcm_base; +static void __iomem *m4_bootrom_base; + +static unsigned int lpm_ocram_size; +static void __iomem *ccm_base; +static void __iomem *lpsr_base; +static void __iomem *console_base; +static void __iomem *suspend_ocram_base; +static void __iomem *iomuxc_base; +static void __iomem *gpt1_base; +static void __iomem *system_counter_ctrl_base; +static void __iomem *system_counter_cmp_base; +static void __iomem *gpio1_base; +static void (*imx7_suspend_in_ocram_fn)(void __iomem *ocram_vbase); +struct imx7_cpu_pm_info *pm_info; +static bool lpsr_enabled; +static u32 iomuxc_gpr[MAX_IOMUXC_GPR]; +static u32 uart1_io[MAX_UART_IO]; +static u32 ccm_lpcg[MAX_CCM_LPCG]; +static u32 ccm_root[][2] = { + {0x8000, 0}, {0x8080, 0}, {0x8100, 0}, {0x8800, 0}, + {0x8880, 0}, {0x8900, 0}, {0x8980, 0}, {0x9000, 0}, + {0x9800, 0}, {0x9880, 0}, {0xa000, 0}, {0xa080, 0}, + {0xa100, 0}, {0xa180, 0}, {0xa200, 0}, {0xa280, 0}, + {0xa300, 0}, {0xa380, 0}, {0xa400, 0}, {0xa480, 0}, + {0xa500, 0}, {0xa580, 0}, {0xa600, 0}, {0xa680, 0}, + {0xa700, 0}, {0xa780, 0}, {0xa800, 0}, {0xa880, 0}, + {0xa900, 0}, {0xa980, 0}, {0xaa00, 0}, {0xaa80, 0}, + {0xab00, 0}, {0xab80, 0}, {0xac00, 0}, {0xac80, 0}, + {0xad00, 0}, {0xad80, 0}, {0xae00, 0}, {0xae80, 0}, + {0xaf00, 0}, {0xaf80, 0}, {0xb000, 0}, {0xb080, 0}, + {0xb100, 0}, {0xb180, 0}, {0xb200, 0}, {0xb280, 0}, + {0xb300, 0}, {0xb380, 0}, {0xb400, 0}, {0xb480, 0}, + {0xb500, 0}, {0xb580, 0}, {0xb600, 0}, {0xb680, 0}, + {0xb700, 0}, {0xb780, 0}, {0xb800, 0}, {0xb880, 0}, + {0xb900, 0}, {0xb980, 0}, {0xba00, 0}, {0xba80, 0}, + {0xbb00, 0}, {0xbb80, 0}, {0xbc00, 0}, {0xbc80, 0}, + {0xbd00, 0}, {0xbd80, 0}, {0xbe00, 0}, +}; +static u32 pfd_a, pfd_b; +static u32 pll[15]; +static u32 gpt1_regs[MAX_GPT]; +static u32 sys_ctrl_reg, sys_cmp_reg; +static u32 gpio_reg[MAX_GPIO_ROW][MAX_GPIO_COL]; +/* + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx7_suspend code + * PM_INFO structure(imx7_cpu_pm_info) + * ======================== low address ======================= + */ + +struct imx7_pm_base { + phys_addr_t pbase; + void __iomem *vbase; +}; + +struct imx7_pm_socdata { + u32 ddr_type; + const char *ddrc_compat; + const char *src_compat; + const char *iomuxc_compat; + const char *gpc_compat; + const u32 ddrc_num; + const u32 (*ddrc_offset)[2]; + const u32 ddrc_phy_num; + const u32 (*ddrc_phy_offset)[2]; +}; + +static const u32 imx7d_ddrc_lpddr3_setting[][2] __initconst = { + { 0x0, READ_DATA_FROM_HARDWARE }, + { 0x1a0, READ_DATA_FROM_HARDWARE }, + { 0x1a4, READ_DATA_FROM_HARDWARE }, + { 0x1a8, READ_DATA_FROM_HARDWARE }, + { 0x64, READ_DATA_FROM_HARDWARE }, + { 0xd0, READ_DATA_FROM_HARDWARE }, + { 0xdc, READ_DATA_FROM_HARDWARE }, + { 0xe0, READ_DATA_FROM_HARDWARE }, + { 0xe4, READ_DATA_FROM_HARDWARE }, + { 0xf4, READ_DATA_FROM_HARDWARE }, + { 0x100, READ_DATA_FROM_HARDWARE }, + { 0x104, READ_DATA_FROM_HARDWARE }, + { 0x108, READ_DATA_FROM_HARDWARE }, + { 0x10c, READ_DATA_FROM_HARDWARE }, + { 0x110, READ_DATA_FROM_HARDWARE }, + { 0x114, READ_DATA_FROM_HARDWARE }, + { 0x118, READ_DATA_FROM_HARDWARE }, + { 0x120, READ_DATA_FROM_HARDWARE }, + { 0x11c, READ_DATA_FROM_HARDWARE }, + { 0x180, READ_DATA_FROM_HARDWARE }, + { 0x184, READ_DATA_FROM_HARDWARE }, + { 0x190, READ_DATA_FROM_HARDWARE }, + { 0x194, READ_DATA_FROM_HARDWARE }, + { 0x200, READ_DATA_FROM_HARDWARE }, + { 0x204, READ_DATA_FROM_HARDWARE }, + { 0x210, READ_DATA_FROM_HARDWARE }, + { 0x214, READ_DATA_FROM_HARDWARE }, + { 0x218, READ_DATA_FROM_HARDWARE }, + { 0x240, READ_DATA_FROM_HARDWARE }, + { 0x244, READ_DATA_FROM_HARDWARE }, +}; + +static const u32 imx7d_ddrc_phy_lpddr3_setting[][2] __initconst = { + { 0x0, READ_DATA_FROM_HARDWARE }, + { 0x4, READ_DATA_FROM_HARDWARE }, + { 0x8, READ_DATA_FROM_HARDWARE }, + { 0x10, READ_DATA_FROM_HARDWARE }, + { 0xb0, READ_DATA_FROM_HARDWARE }, + { 0x1c, READ_DATA_FROM_HARDWARE }, + { 0x9c, READ_DATA_FROM_HARDWARE }, + { 0x7c, READ_DATA_FROM_HARDWARE }, + { 0x80, READ_DATA_FROM_HARDWARE }, + { 0x84, READ_DATA_FROM_HARDWARE }, + { 0x88, READ_DATA_FROM_HARDWARE }, + { 0x6c, READ_DATA_FROM_HARDWARE }, + { 0x20, READ_DATA_FROM_HARDWARE }, + { 0x30, READ_DATA_FROM_HARDWARE }, + { 0x50, 0x01000008 }, + { 0x50, 0x00000008 }, + { 0xc0, 0x0e487304 }, + { 0xc0, 0x0e4c7304 }, + { 0xc0, 0x0e4c7306 }, + { 0xc0, 0x0e487304 }, +}; + +static const u32 imx7d_ddrc_ddr3_setting[][2] __initconst = { + { 0x0, READ_DATA_FROM_HARDWARE }, + { 0x1a0, READ_DATA_FROM_HARDWARE }, + { 0x1a4, READ_DATA_FROM_HARDWARE }, + { 0x1a8, READ_DATA_FROM_HARDWARE }, + { 0x64, READ_DATA_FROM_HARDWARE }, + { 0x490, READ_DATA_FROM_HARDWARE }, + { 0xd0, READ_DATA_FROM_HARDWARE }, + { 0xd4, READ_DATA_FROM_HARDWARE }, + { 0xdc, READ_DATA_FROM_HARDWARE }, + { 0xe0, READ_DATA_FROM_HARDWARE }, + { 0xe4, READ_DATA_FROM_HARDWARE }, + { 0xf4, READ_DATA_FROM_HARDWARE }, + { 0x100, READ_DATA_FROM_HARDWARE }, + { 0x104, READ_DATA_FROM_HARDWARE }, + { 0x108, READ_DATA_FROM_HARDWARE }, + { 0x10c, READ_DATA_FROM_HARDWARE }, + { 0x110, READ_DATA_FROM_HARDWARE }, + { 0x114, READ_DATA_FROM_HARDWARE }, + { 0x120, READ_DATA_FROM_HARDWARE }, + { 0x180, READ_DATA_FROM_HARDWARE }, + { 0x190, READ_DATA_FROM_HARDWARE }, + { 0x194, READ_DATA_FROM_HARDWARE }, + { 0x200, READ_DATA_FROM_HARDWARE }, + { 0x204, READ_DATA_FROM_HARDWARE }, + { 0x210, READ_DATA_FROM_HARDWARE }, + { 0x214, READ_DATA_FROM_HARDWARE }, + { 0x218, READ_DATA_FROM_HARDWARE }, + { 0x240, READ_DATA_FROM_HARDWARE }, + { 0x244, READ_DATA_FROM_HARDWARE }, +}; + +static const u32 imx7d_ddrc_phy_ddr3_setting[][2] __initconst = { + { 0x0, READ_DATA_FROM_HARDWARE }, + { 0x4, READ_DATA_FROM_HARDWARE }, + { 0x10, READ_DATA_FROM_HARDWARE }, + { 0xb0, READ_DATA_FROM_HARDWARE }, + { 0x9c, READ_DATA_FROM_HARDWARE }, + { 0x7c, READ_DATA_FROM_HARDWARE }, + { 0x80, READ_DATA_FROM_HARDWARE }, + { 0x84, READ_DATA_FROM_HARDWARE }, + { 0x88, READ_DATA_FROM_HARDWARE }, + { 0x6c, READ_DATA_FROM_HARDWARE }, + { 0x20, READ_DATA_FROM_HARDWARE }, + { 0x30, READ_DATA_FROM_HARDWARE }, + { 0x50, 0x01000010 }, + { 0x50, 0x00000010 }, + { 0xc0, 0x0e407304 }, + { 0xc0, 0x0e447304 }, + { 0xc0, 0x0e447306 }, + { 0xc0, 0x0e407304 }, +}; + +static const struct imx7_pm_socdata imx7d_pm_data_lpddr3 __initconst = { + .ddrc_compat = "fsl,imx7d-ddrc", + .src_compat = "fsl,imx7d-src", + .iomuxc_compat = "fsl,imx7d-iomuxc", + .gpc_compat = "fsl,imx7d-gpc", + .ddrc_num = ARRAY_SIZE(imx7d_ddrc_lpddr3_setting), + .ddrc_offset = imx7d_ddrc_lpddr3_setting, + .ddrc_phy_num = ARRAY_SIZE(imx7d_ddrc_phy_lpddr3_setting), + .ddrc_phy_offset = imx7d_ddrc_phy_lpddr3_setting, +}; + +static const struct imx7_pm_socdata imx7d_pm_data_ddr3 __initconst = { + .ddrc_compat = "fsl,imx7d-ddrc", + .src_compat = "fsl,imx7d-src", + .iomuxc_compat = "fsl,imx7d-iomuxc", + .gpc_compat = "fsl,imx7d-gpc", + .ddrc_num = ARRAY_SIZE(imx7d_ddrc_ddr3_setting), + .ddrc_offset = imx7d_ddrc_ddr3_setting, + .ddrc_phy_num = ARRAY_SIZE(imx7d_ddrc_phy_ddr3_setting), + .ddrc_phy_offset = imx7d_ddrc_phy_ddr3_setting, +}; + +/* + * This structure is for passing necessary data for low level ocram + * suspend code(arch/arm/mach-imx/suspend-imx7.S), if this struct + * definition is changed, the offset definition in + * arch/arm/mach-imx/suspend-imx7.S must be also changed accordingly, + * otherwise, the suspend to ocram function will be broken! + */ +struct imx7_cpu_pm_info { + u32 m4_reserve0; + u32 m4_reserve1; + u32 m4_reserve2; + phys_addr_t pbase; /* The physical address of pm_info. */ + phys_addr_t resume_addr; /* The physical resume address for asm code */ + u32 ddr_type; + u32 pm_info_size; /* Size of pm_info. */ + struct imx7_pm_base ddrc_base; + struct imx7_pm_base ddrc_phy_base; + struct imx7_pm_base src_base; + struct imx7_pm_base iomuxc_gpr_base; + struct imx7_pm_base ccm_base; + struct imx7_pm_base gpc_base; + struct imx7_pm_base snvs_base; + struct imx7_pm_base anatop_base; + struct imx7_pm_base lpsr_base; + struct imx7_pm_base gic_base; + u32 ttbr1; /* Store TTBR1 */ + u32 ddrc_num; /* Number of DDRC which need saved/restored. */ + u32 ddrc_val[MX7_MAX_DDRC_NUM][2]; /* To save offset and value */ + u32 ddrc_phy_num; /* Number of DDRC which need saved/restored. */ + u32 ddrc_phy_val[MX7_MAX_DDRC_NUM][2]; /* To save offset and value */ +} __aligned(8); + static struct map_desc imx7_pm_io_desc[] __initdata = { imx_map_entry(MX7D, AIPS1, MT_DEVICE), imx_map_entry(MX7D, AIPS2, MT_DEVICE), @@ -42,6 +388,485 @@ static const char * const low_power_ocram_match[] __initconst = { NULL }; +static void imx7_gpio_save(void) +{ + u32 i; + + for (i = 0; i < 7; i++) { + gpio_reg[i][0] = readl_relaxed(gpio1_base + + (i << 16) + GPIO_DR); + gpio_reg[i][1] = readl_relaxed(gpio1_base + + (i << 16) + GPIO_GDIR); + gpio_reg[i][3] = readl_relaxed(gpio1_base + + (i << 16) + GPIO_ICR1); + gpio_reg[i][4] = readl_relaxed(gpio1_base + + (i << 16) + GPIO_ICR2); + gpio_reg[i][5] = readl_relaxed(gpio1_base + + (i << 16) + GPIO_IMR); + gpio_reg[i][7] = readl_relaxed(gpio1_base + + (i << 16) + GPIO_EDGE); + } +} + +static void imx7_gpio_restore(void) +{ + u32 i, val; + + for (i = 0; i < 7; i++) { + writel_relaxed(gpio_reg[i][1], gpio1_base + + (i << 16) + GPIO_GDIR); + writel_relaxed(gpio_reg[i][3], gpio1_base + + (i << 16) + GPIO_ICR1); + writel_relaxed(gpio_reg[i][4], gpio1_base + + (i << 16) + GPIO_ICR2); + writel_relaxed(gpio_reg[i][5], gpio1_base + + (i << 16) + GPIO_IMR); + writel_relaxed(gpio_reg[i][7], gpio1_base + + (i << 16) + GPIO_EDGE); + /* only restore output gpio value */ + val = readl_relaxed(gpio1_base + (i << 16) + GPIO_DR) | + (gpio_reg[i][0] & gpio_reg[i][1]); + writel_relaxed(val, gpio1_base + (i << 16) + GPIO_DR); + } +} + +static void imx7_ccm_save(void) +{ + u32 i; + + for (i = 0; i < MAX_CCM_LPCG; i++) + ccm_lpcg[i] = readl_relaxed(pm_info->ccm_base.vbase + + i * CCM_LPCG_STEP + CCM_LPCG_START); + pfd_a = readl_relaxed(pm_info->anatop_base.vbase + PFD_A_OFFSET); + pfd_b = readl_relaxed(pm_info->anatop_base.vbase + PFD_B_OFFSET); + + pll[0] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_ARM_OFFSET); + pll[1] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_DDR_OFFSET); + pll[2] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_DDR_SS_OFFSET); + pll[3] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_DDR_NUM_OFFSET); + pll[4] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_DDR_DENOM_OFFSET); + pll[5] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_480_OFFSET); + pll[6] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_ENET_OFFSET); + pll[7] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_AUDIO_OFFSET); + pll[8] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_AUDIO_SS_OFFSET); + pll[9] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_AUDIO_NUM_OFFSET); + pll[10] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_AUDIO_DENOM_OFFSET); + pll[11] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_VIDEO_OFFSET); + pll[12] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_VIDEO_SS_OFFSET); + pll[13] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_VIDEO_NUM_OFFSET); + pll[14] = readl_relaxed(pm_info->anatop_base.vbase + + PLL_VIDEO_DENOM_OFFSET); + + /* enable all PLLs/PFDs for saving CCM root */ + writel_relaxed(0x1c000070, pm_info->anatop_base.vbase + + PLL_480_OFFSET + 0x8); + writel_relaxed(0x80808080, pm_info->anatop_base.vbase + + PFD_A_OFFSET + 0x8); + writel_relaxed(0x80808080, pm_info->anatop_base.vbase + + PFD_B_OFFSET + 0x8); + writel_relaxed(0x1fc0, pm_info->anatop_base.vbase + + PLL_ENET_OFFSET + 0x4); + writel_relaxed(0x12000, pm_info->anatop_base.vbase + + PLL_AUDIO_OFFSET); + writel_relaxed(0x12000, pm_info->anatop_base.vbase + + PLL_VIDEO_OFFSET); + + for (i = 0; i < sizeof(ccm_root) / 8; i++) + ccm_root[i][1] = readl_relaxed( + pm_info->ccm_base.vbase + ccm_root[i][0]); +} + +static void imx7_ccm_restore(void) +{ + u32 i, val; + + /* enable all PLLs/PFDs for restoring CCM root */ + writel_relaxed(0x1c000070, pm_info->anatop_base.vbase + + PLL_480_OFFSET + REG_CLR); + writel_relaxed(0x80808080, pm_info->anatop_base.vbase + + PFD_A_OFFSET + REG_CLR); + writel_relaxed(0x80808080, pm_info->anatop_base.vbase + + PFD_B_OFFSET + REG_CLR); + writel_relaxed(0x1fc0, pm_info->anatop_base.vbase + + PLL_ENET_OFFSET + REG_SET); + writel_relaxed(0x12000, pm_info->anatop_base.vbase + + PLL_AUDIO_OFFSET); + writel_relaxed(0x12000, pm_info->anatop_base.vbase + + PLL_VIDEO_OFFSET); + + for (i = 0; i < sizeof(ccm_root) / 8; i++) { + val = readl_relaxed(pm_info->ccm_base.vbase + ccm_root[i][0]); + /* restore post podf */ + val &= ~BM_CCM_ROOT_POST_PODF; + val |= ccm_root[i][1] & BM_CCM_ROOT_POST_PODF; + writel_relaxed(val, pm_info->ccm_base.vbase + ccm_root[i][0]); + /* resotre pre podf */ + val &= ~BM_CCM_ROOT_PRE_PODF; + val |= ccm_root[i][1] & BM_CCM_ROOT_PRE_PODF; + writel_relaxed(val, pm_info->ccm_base.vbase + ccm_root[i][0]); + /* restore mux */ + val &= ~BM_CCM_ROOT_MUX; + val |= ccm_root[i][1] & BM_CCM_ROOT_MUX; + writel_relaxed(val, pm_info->ccm_base.vbase + ccm_root[i][0]); + /* restore enable */ + val &= ~BM_CCM_ROOT_ENABLE; + val |= ccm_root[i][1] & BM_CCM_ROOT_ENABLE; + writel_relaxed(val, pm_info->ccm_base.vbase + ccm_root[i][0]); + } + + /* restore PLLs */ + writel_relaxed(pll[0], pm_info->anatop_base.vbase + + PLL_ARM_OFFSET); + writel_relaxed(pll[1], pm_info->anatop_base.vbase + + PLL_DDR_OFFSET); + writel_relaxed(pll[2], pm_info->anatop_base.vbase + + PLL_DDR_SS_OFFSET); + writel_relaxed(pll[3], pm_info->anatop_base.vbase + + PLL_DDR_NUM_OFFSET); + writel_relaxed(pll[4], pm_info->anatop_base.vbase + + PLL_DDR_DENOM_OFFSET); + writel_relaxed(pll[5], pm_info->anatop_base.vbase + + PLL_480_OFFSET); + writel_relaxed(pll[6], pm_info->anatop_base.vbase + + PLL_ENET_OFFSET); + writel_relaxed(pll[7], pm_info->anatop_base.vbase + + PLL_AUDIO_OFFSET); + writel_relaxed(pll[8], pm_info->anatop_base.vbase + + PLL_AUDIO_SS_OFFSET); + writel_relaxed(pll[9], pm_info->anatop_base.vbase + + PLL_AUDIO_NUM_OFFSET); + writel_relaxed(pll[10], pm_info->anatop_base.vbase + + PLL_AUDIO_DENOM_OFFSET); + writel_relaxed(pll[11], pm_info->anatop_base.vbase + + PLL_VIDEO_OFFSET); + writel_relaxed(pll[12], pm_info->anatop_base.vbase + + PLL_VIDEO_SS_OFFSET); + writel_relaxed(pll[13], pm_info->anatop_base.vbase + + PLL_VIDEO_NUM_OFFSET); + writel_relaxed(pll[14], pm_info->anatop_base.vbase + + PLL_VIDEO_DENOM_OFFSET); + + for (i = 0; i < MAX_CCM_LPCG; i++) + writel_relaxed(ccm_lpcg[i], pm_info->ccm_base.vbase + + i * CCM_LPCG_STEP + CCM_LPCG_START); + /* restore PFDs */ + writel_relaxed(pfd_a & 0x80808080, + pm_info->anatop_base.vbase + PFD_A_OFFSET + REG_SET); + writel_relaxed(pfd_a, pm_info->anatop_base.vbase + PFD_A_OFFSET); + + writel_relaxed(pfd_b & 0x80808080, + pm_info->anatop_base.vbase + PFD_B_OFFSET + REG_SET); + writel_relaxed(pfd_b, pm_info->anatop_base.vbase + PFD_B_OFFSET); +} + +static void imx7_sys_counter_save(void) +{ + sys_ctrl_reg = readl_relaxed(system_counter_ctrl_base); + sys_cmp_reg = readl_relaxed(system_counter_cmp_base); +} + +static void imx7_sys_counter_restore(void) +{ + writel_relaxed(sys_ctrl_reg, system_counter_ctrl_base); + writel_relaxed(sys_cmp_reg, system_counter_cmp_base); +} + +static void imx7_gpt_save(void) +{ + gpt1_regs[0] = readl_relaxed(gpt1_base + GPT_CR); + gpt1_regs[1] = readl_relaxed(gpt1_base + GPT_PR); + gpt1_regs[2] = readl_relaxed(gpt1_base + GPT_IR); +} + +static void imx7_gpt_restore(void) +{ + writel_relaxed(gpt1_regs[0], gpt1_base + GPT_CR); + writel_relaxed(gpt1_regs[1], gpt1_base + GPT_PR); + writel_relaxed(gpt1_regs[2], gpt1_base + GPT_IR); +} + +static void imx7_iomuxc_gpr_save(void) +{ + u32 i; + + for (i = 0; i < MAX_IOMUXC_GPR; i++) + iomuxc_gpr[i] = readl_relaxed( + pm_info->iomuxc_gpr_base.vbase + i * 4); +} + +static void imx7_iomuxc_gpr_restore(void) +{ + u32 i; + + for (i = 0; i < MAX_IOMUXC_GPR; i++) + writel_relaxed(iomuxc_gpr[i], + pm_info->iomuxc_gpr_base.vbase + i * 4); +} + +static void imx7_console_save(unsigned int *regs) +{ + if (!console_base) + return; + + regs[0] = readl_relaxed(console_base + UART_UCR1); + regs[1] = readl_relaxed(console_base + UART_UCR2); + regs[2] = readl_relaxed(console_base + UART_UCR3); + regs[3] = readl_relaxed(console_base + UART_UCR4); + regs[4] = readl_relaxed(console_base + UART_UFCR); + regs[5] = readl_relaxed(console_base + UART_UESC); + regs[6] = readl_relaxed(console_base + UART_UTIM); + regs[7] = readl_relaxed(console_base + UART_UBIR); + regs[8] = readl_relaxed(console_base + UART_UBMR); + regs[9] = readl_relaxed(console_base + UART_UTS); +} + +static void imx7_console_io_save(void) +{ + /* save uart1 io, driver resume is too late */ + uart1_io[0] = readl_relaxed(iomuxc_base + UART_RX_IO); + uart1_io[1] = readl_relaxed(iomuxc_base + UART_RX_PAD); + uart1_io[2] = readl_relaxed(iomuxc_base + UART_TX_IO); + uart1_io[3] = readl_relaxed(iomuxc_base + UART_TX_PAD); +} + +static void imx7_console_restore(unsigned int *regs) +{ + if (!console_base) + return; + + writel_relaxed(regs[4], console_base + UART_UFCR); + writel_relaxed(regs[5], console_base + UART_UESC); + writel_relaxed(regs[6], console_base + UART_UTIM); + writel_relaxed(regs[7], console_base + UART_UBIR); + writel_relaxed(regs[8], console_base + UART_UBMR); + writel_relaxed(regs[9], console_base + UART_UTS); + writel_relaxed(regs[0], console_base + UART_UCR1); + writel_relaxed(regs[1] | 0x1, console_base + UART_UCR2); + writel_relaxed(regs[2], console_base + UART_UCR3); + writel_relaxed(regs[3], console_base + UART_UCR4); +} + +static void imx7_console_io_restore(void) +{ + /* restore uart1 io */ + writel_relaxed(uart1_io[0], iomuxc_base + UART_RX_IO); + writel_relaxed(uart1_io[1], iomuxc_base + UART_RX_PAD); + writel_relaxed(uart1_io[2], iomuxc_base + UART_TX_IO); + writel_relaxed(uart1_io[3], iomuxc_base + UART_TX_PAD); +} + +#define MX7D_SUSPEND_POWERDWN_PARAM \ + ((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +#define MX7D_SUSPEND_STANDBY_PARAM \ + ((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_STANDBY << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +static int imx7_suspend_finish(unsigned long val) +{ + u32 state; + + if (val == 0) + state = MX7D_SUSPEND_POWERDWN_PARAM; + else + state = MX7D_SUSPEND_STANDBY_PARAM; + + if (psci_ops.cpu_suspend) { + return psci_ops.cpu_suspend(state, __pa(cpu_resume)); + } + + if (!imx7_suspend_in_ocram_fn) { + cpu_do_idle(); + } else { + /* + * call low level suspend function in ocram, + * as we need to float DDR IO. + */ + local_flush_tlb_all(); + imx7_suspend_in_ocram_fn(suspend_ocram_base); + } + + return 0; +} + +static void imx7_pm_set_lpsr_resume_addr(unsigned long addr) +{ + writel_relaxed(addr, pm_info->lpsr_base.vbase); +} + +static int imx7_pm_is_resume_from_lpsr(void) +{ + return readl_relaxed(lpsr_base); +} + +static int imx7_pm_enter(suspend_state_t state) +{ + unsigned int console_saved_reg[10] = {0}; + u32 val; + + if (!iram_tlb_base_addr) { + pr_warn("No IRAM/OCRAM memory allocated for suspend/resume \ + code. Please ensure device tree has an entry for \ + fsl,lpm-sram.\n"); + return -EINVAL; + } + + /* + * arm_arch_timer driver requires system counter to be + * a clock source with CLOCK_SOURCE_SUSPEND_NONSTOP flag + * set, which means hardware system counter needs to keep + * running during suspend, as the base clock for system + * counter is 24MHz which will be disabled in STOP mode, + * so we need to switch system counter's clock to alternate + * (lower) clock, it is based on 32K, from block guide, there + * is no special flow needs to be followed, system counter + * hardware will handle the clock transition. + */ + val = readl_relaxed(system_counter_ctrl_base); + val &= ~BM_SYS_COUNTER_CNTCR_FCR0; + val |= BM_SYS_COUNTER_CNTCR_FCR1; + writel_relaxed(val, system_counter_ctrl_base); + + switch (state) { + case PM_SUSPEND_STANDBY: + imx_anatop_pre_suspend(); + imx_gpcv2_pre_suspend(false); + + /* Zzz ... */ + if (psci_ops.cpu_suspend) + cpu_suspend(1, imx7_suspend_finish); + else + imx7_suspend_in_ocram_fn(suspend_ocram_base); + + imx_anatop_post_resume(); + imx_gpcv2_post_resume(); + break; + case PM_SUSPEND_MEM: + imx_anatop_pre_suspend(); + imx_gpcv2_pre_suspend(true); + if (imx_gpcv2_is_mf_mix_off()) { + /* + * per design requirement, EXSC for PCIe/EIM/PXP + * will need clock to recover RDC setting on + * resume, so enable PCIe/EIM LPCG for RDC + * recovery when M/F mix off + */ + writel_relaxed(0x3, pm_info->ccm_base.vbase + + CCM_EIM_LPCG); + writel_relaxed(0x3, pm_info->ccm_base.vbase + + CCM_PXP_LPCG); + writel_relaxed(0x3, pm_info->ccm_base.vbase + + CCM_PCIE_LPCG); + /* stop m4 if mix will also be shutdown */ + if (imx_src_is_m4_enabled() && imx_mu_is_m4_in_stop()) { + writel(M4_RCR_HALT, + pm_info->src_base.vbase + M4RCR); + imx_gpcv2_enable_wakeup_for_m4(); + } + imx7_console_save(console_saved_reg); + memcpy(ocram_saved_in_ddr, ocram_base, ocram_size); + if (lpsr_enabled) { + imx7_pm_set_lpsr_resume_addr(pm_info->resume_addr); + imx7_console_io_save(); + memcpy(lpm_ocram_saved_in_ddr, lpm_ocram_base, + lpm_ocram_size); + imx7_iomuxc_gpr_save(); + imx7_ccm_save(); + imx7_gpt_save(); + imx7_sys_counter_save(); + imx7_gpio_save(); + } + } + + /* Zzz ... */ + cpu_suspend(0, imx7_suspend_finish); + + if (imx7_pm_is_resume_from_lpsr()) { + imx7_console_io_restore(); + memcpy(lpm_ocram_base, lpm_ocram_saved_in_ddr, + lpm_ocram_size); + imx7_iomuxc_gpr_restore(); + imx7_ccm_restore(); + imx7_gpt_restore(); + imx7_sys_counter_restore(); + imx7_gpio_restore(); + imx7d_enable_rcosc(); + } + if (imx_gpcv2_is_mf_mix_off() || + imx7_pm_is_resume_from_lpsr()) { + writel_relaxed(0x0, pm_info->ccm_base.vbase + + CCM_EIM_LPCG); + writel_relaxed(0x0, pm_info->ccm_base.vbase + + CCM_PXP_LPCG); + writel_relaxed(0x0, pm_info->ccm_base.vbase + + CCM_PCIE_LPCG); + memcpy(ocram_base, ocram_saved_in_ddr, ocram_size); + imx7_console_restore(console_saved_reg); + if (imx_src_is_m4_enabled() && imx_mu_is_m4_in_stop()) { + imx_gpcv2_disable_wakeup_for_m4(); + /* restore M4 image */ + memcpy(lpm_m4tcm_base, + lpm_m4tcm_saved_in_ddr, SZ_32K); + /* kick m4 to enable */ + writel(M4_RCR_GO, + pm_info->src_base.vbase + M4RCR); + /* offset high bus count for m4 image */ + request_bus_freq(BUS_FREQ_HIGH); + /* restore M4 to run mode */ + imx_mu_set_m4_run_mode(); + /* gpc wakeup */ + } + } + /* clear LPSR resume address */ + imx7_pm_set_lpsr_resume_addr(0); + imx_anatop_post_resume(); + imx_gpcv2_post_resume(); + break; + default: + return -EINVAL; + } + + /* restore system counter's clock to base clock */ + val = readl_relaxed(system_counter_ctrl_base); + val &= ~BM_SYS_COUNTER_CNTCR_FCR1; + val |= BM_SYS_COUNTER_CNTCR_FCR0; + writel_relaxed(val, system_counter_ctrl_base); + + return 0; +} + +static int imx7_pm_valid(suspend_state_t state) +{ + return state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM; +} + +static const struct platform_suspend_ops imx7_pm_ops = { + .enter = imx7_pm_enter, + .valid = imx7_pm_valid, +}; + +void __init imx7_pm_set_ccm_base(void __iomem *base) +{ + ccm_base = base; +} + static struct map_desc iram_tlb_io_desc __initdata = { /* .virtual and .pfn are run-time assigned */ .length = SZ_1M, @@ -146,3 +971,257 @@ void __init imx7_pm_map_io(void) *((unsigned long *)iram_tlb_base_addr + j) = (MX7D_GIC_BASE_ADDR & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M; } + +static int __init imx7_suspend_init(const struct imx7_pm_socdata *socdata) +{ + struct device_node *node; + int i, ret = 0; + const u32 (*ddrc_offset_array)[2]; + const u32 (*ddrc_phy_offset_array)[2]; + unsigned long iram_paddr; + + suspend_set_ops(&imx7_pm_ops); + + if (!socdata) { + pr_warn("%s: invalid argument!\n", __func__); + return -EINVAL; + } + + /* + * 16KB is allocated for IRAM TLB, but only up 8k is for kernel TLB, + * The lower 8K is not used, so use the lower 8K for IRAM code and + * pm_info. + * + */ + iram_paddr = iram_tlb_phys_addr + MX7_SUSPEND_IRAM_ADDR_OFFSET; + + /* Make sure iram_paddr is 8 byte aligned. */ + if ((uintptr_t)(iram_paddr) & (FNCPY_ALIGN - 1)) + iram_paddr += FNCPY_ALIGN - iram_paddr % (FNCPY_ALIGN); + + /* Get the virtual address of the suspend code. */ + suspend_ocram_base = (void *)IMX_IO_P2V(iram_paddr); + + if (psci_ops.cpu_suspend) { + pm_info = kmalloc(sizeof(*pm_info), GFP_KERNEL); + if (!pm_info) + return -ENOMEM; + } else { + pm_info = suspend_ocram_base; + } + /* pbase points to iram_paddr. */ + pm_info->pbase = iram_paddr; + pm_info->resume_addr = virt_to_phys(ca7_cpu_resume); + pm_info->pm_info_size = sizeof(*pm_info); + + /* + * ccm physical address is not used by asm code currently, + * so get ccm virtual address directly, as we already have + * it from ccm driver. + */ + pm_info->ccm_base.pbase = MX7D_CCM_BASE_ADDR; + pm_info->ccm_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_CCM_BASE_ADDR); + + pm_info->ddrc_base.pbase = MX7D_DDRC_BASE_ADDR; + pm_info->ddrc_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_DDRC_BASE_ADDR); + + pm_info->ddrc_phy_base.pbase = MX7D_DDRC_PHY_BASE_ADDR; + pm_info->ddrc_phy_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_DDRC_PHY_BASE_ADDR); + + pm_info->src_base.pbase = MX7D_SRC_BASE_ADDR; + pm_info->src_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_SRC_BASE_ADDR); + + pm_info->iomuxc_gpr_base.pbase = MX7D_IOMUXC_GPR_BASE_ADDR; + pm_info->iomuxc_gpr_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_IOMUXC_GPR_BASE_ADDR); + + pm_info->gpc_base.pbase = MX7D_GPC_BASE_ADDR; + pm_info->gpc_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_GPC_BASE_ADDR); + + pm_info->anatop_base.pbase = MX7D_ANATOP_BASE_ADDR; + pm_info->anatop_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_ANATOP_BASE_ADDR); + + pm_info->snvs_base.pbase = MX7D_SNVS_BASE_ADDR; + pm_info->snvs_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_SNVS_BASE_ADDR); + + pm_info->lpsr_base.pbase = MX7D_LPSR_BASE_ADDR; + lpsr_base = pm_info->lpsr_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_LPSR_BASE_ADDR); + + pm_info->gic_base.pbase = MX7D_GIC_BASE_ADDR; + pm_info->gic_base.vbase = (void __iomem *) + IMX_IO_P2V(MX7D_GIC_BASE_ADDR); + + pm_info->ddrc_num = socdata->ddrc_num; + ddrc_offset_array = socdata->ddrc_offset; + pm_info->ddrc_phy_num = socdata->ddrc_phy_num; + ddrc_phy_offset_array = socdata->ddrc_phy_offset; + + /* initialize DDRC settings */ + for (i = 0; i < pm_info->ddrc_num; i++) { + pm_info->ddrc_val[i][0] = ddrc_offset_array[i][0]; + if (ddrc_offset_array[i][1] == READ_DATA_FROM_HARDWARE) + pm_info->ddrc_val[i][1] = + readl_relaxed(pm_info->ddrc_base.vbase + + ddrc_offset_array[i][0]); + else + pm_info->ddrc_val[i][1] = ddrc_offset_array[i][1]; + + if (pm_info->ddrc_val[i][0] == 0xd0) + pm_info->ddrc_val[i][1] |= 0xc0000000; + } + + /* initialize DDRC PHY settings */ + for (i = 0; i < pm_info->ddrc_phy_num; i++) { + pm_info->ddrc_phy_val[i][0] = + ddrc_phy_offset_array[i][0]; + if (ddrc_phy_offset_array[i][1] == READ_DATA_FROM_HARDWARE) + pm_info->ddrc_phy_val[i][1] = + readl_relaxed(pm_info->ddrc_phy_base.vbase + + ddrc_phy_offset_array[i][0]); + else + pm_info->ddrc_phy_val[i][1] = + ddrc_phy_offset_array[i][1]; + } + + if (psci_ops.cpu_suspend) + goto put_node; + + imx7_suspend_in_ocram_fn = fncpy( + suspend_ocram_base + sizeof(*pm_info), + &imx7_suspend, + MX7_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); + + goto put_node; + +put_node: + of_node_put(node); + + return ret; +} + +static void __init imx7_pm_common_init(const struct imx7_pm_socdata + *socdata) +{ + int ret; + struct regmap *gpr; + + if (IS_ENABLED(CONFIG_SUSPEND)) { + ret = imx7_suspend_init(socdata); + if (ret) + pr_warn("%s: No DDR LPM support with suspend %d!\n", + __func__, ret); + } + + /* + * Force IOMUXC irq pending, so that the interrupt to GPC can be + * used to deassert dsm_request signal when the signal gets + * asserted unexpectedly. + */ + gpr = syscon_regmap_lookup_by_compatible("fsl,imx7d-iomuxc-gpr"); + if (!IS_ERR(gpr)) + regmap_update_bits(gpr, IOMUXC_GPR1, IMX7D_GPR1_IRQ_MASK, + IMX7D_GPR1_IRQ_MASK); +} + +void __init imx7d_pm_init(void) +{ + struct device_node *np; + struct resource res; + if (imx_src_is_m4_enabled()) { + /* map the 32K of M4 TCM */ + np = of_find_node_by_path( + "/tcml@007f8000"); + if (np) + lpm_m4tcm_base = of_iomap(np, 0); + WARN_ON(!lpm_m4tcm_base); + + /* map the m4 bootrom from dtb */ + np = of_find_node_by_path( + "/soc/sram@00180000"); + if (np) + m4_bootrom_base = of_iomap(np, 0); + WARN_ON(!m4_bootrom_base); + + lpm_m4tcm_saved_in_ddr = kzalloc(SZ_32K, GFP_KERNEL); + WARN_ON(!lpm_m4tcm_saved_in_ddr); + + /* save M4 Image to DDR */ + memcpy(lpm_m4tcm_saved_in_ddr, lpm_m4tcm_base, SZ_32K); + } + np = of_find_compatible_node(NULL, NULL, "fsl,lpm-sram"); + if (of_get_property(np, "fsl,enable-lpsr", NULL)) + lpsr_enabled = true; + + if (psci_ops.cpu_suspend) + lpsr_enabled = false; + + if (lpsr_enabled) { + pr_info("LPSR mode enabled, DSM will go into LPSR mode!\n"); + lpm_ocram_base = of_iomap(np, 0); + WARN_ON(!lpm_ocram_base); + WARN_ON(of_address_to_resource(np, 0, &res)); + lpm_ocram_size = resource_size(&res); + lpm_ocram_saved_in_ddr = kzalloc(lpm_ocram_size, GFP_KERNEL); + WARN_ON(!lpm_ocram_saved_in_ddr); + + np = of_find_node_by_path( + "/soc/aips-bus@30000000/iomuxc@30330000"); + if (np) + iomuxc_base = of_iomap(np, 0); + WARN_ON(!iomuxc_base); + + np = of_find_node_by_path( + "/soc/aips-bus@30000000/gpt@302d0000"); + if (np) + gpt1_base = of_iomap(np, 0); + WARN_ON(!gpt1_base); + + np = of_find_node_by_path( + "/soc/aips-bus@30400000/system-counter-cmp@306b0000"); + if (np) + system_counter_cmp_base = of_iomap(np, 0); + WARN_ON(!system_counter_cmp_base); + + np = of_find_node_by_path( + "/soc/aips-bus@30000000/gpio@30200000"); + if (np) + gpio1_base = of_iomap(np, 0); + WARN_ON(!gpio1_base); + } + + np = of_find_node_by_path( + "/soc/aips-bus@30400000/system-counter-ctrl@306c0000"); + if (np) + system_counter_ctrl_base = of_iomap(np, 0); + WARN_ON(!system_counter_ctrl_base); + + if (imx_ddrc_get_ddr_type() == IMX_DDR_TYPE_LPDDR3 + || imx_ddrc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2) + imx7_pm_common_init(&imx7d_pm_data_lpddr3); + else if (imx_ddrc_get_ddr_type() == IMX_DDR_TYPE_DDR3) + imx7_pm_common_init(&imx7d_pm_data_ddr3); + + np = of_find_compatible_node(NULL, NULL, "fsl,mega-fast-sram"); + ocram_base = of_iomap(np, 0); + WARN_ON(!ocram_base); + WARN_ON(of_address_to_resource(np, 0, &res)); + ocram_size = resource_size(&res); + ocram_saved_in_ddr = kzalloc(ocram_size, GFP_KERNEL); + WARN_ON(!ocram_saved_in_ddr); + + np = of_find_node_by_path( + "/soc/aips-bus@30800000/serial@30860000"); + if (np) + console_base = of_iomap(np, 0); + + /* clear LPSR resume address first */ + imx7_pm_set_lpsr_resume_addr(0); +} diff --git a/arch/arm/mach-imx/src.c b/arch/arm/mach-imx/src.c index 60dad38d02f7..399771e2912e 100644 --- a/arch/arm/mach-imx/src.c +++ b/arch/arm/mach-imx/src.c @@ -24,9 +24,17 @@ #define BP_SRC_SCR_SW_IPU2_RST 12 #define BP_SRC_SCR_CORE1_RST 14 #define BP_SRC_SCR_CORE1_ENABLE 22 +/* below is for i.MX7D */ +#define SRC_GPR1_V2 0x074 +#define SRC_A7RCR0 0x004 +#define SRC_A7RCR1 0x008 +#define SRC_M4RCR 0x00C + +#define BP_SRC_A7RCR0_A7_CORE_RESET0 0 +#define BP_SRC_A7RCR1_A7_CORE1_ENABLE 1 static void __iomem *src_base; -static DEFINE_SPINLOCK(scr_lock); +static DEFINE_SPINLOCK(src_lock); static bool m4_is_enabled; static const int sw_reset_bits[5] = { @@ -58,11 +66,11 @@ static int imx_src_reset_module(struct reset_controller_dev *rcdev, bit = 1 << sw_reset_bits[sw_reset_idx]; - spin_lock_irqsave(&scr_lock, flags); + spin_lock_irqsave(&src_lock, flags); val = readl_relaxed(src_base + SRC_SCR); val |= bit; writel_relaxed(val, src_base + SRC_SCR); - spin_unlock_irqrestore(&scr_lock, flags); + spin_unlock_irqrestore(&src_lock, flags); timeout = jiffies + msecs_to_jiffies(1000); while (readl(src_base + SRC_SCR) & bit) { @@ -88,32 +96,59 @@ void imx_enable_cpu(int cpu, bool enable) u32 mask, val; cpu = cpu_logical_map(cpu); - mask = 1 << (BP_SRC_SCR_CORE1_ENABLE + cpu - 1); - spin_lock(&scr_lock); - val = readl_relaxed(src_base + SRC_SCR); - val = enable ? val | mask : val & ~mask; - val |= 1 << (BP_SRC_SCR_CORE1_RST + cpu - 1); - writel_relaxed(val, src_base + SRC_SCR); - spin_unlock(&scr_lock); + spin_lock(&src_lock); + if (cpu_is_imx7d()) { + /* enable core */ + if (enable) + imx_gpcv2_set_core1_pdn_pup_by_software(false); + + mask = 1 << (BP_SRC_A7RCR1_A7_CORE1_ENABLE + cpu - 1); + val = readl_relaxed(src_base + SRC_A7RCR1); + val = enable ? val | mask : val & ~mask; + writel_relaxed(val, src_base + SRC_A7RCR1); + } else { + mask = 1 << (BP_SRC_SCR_CORE1_ENABLE + cpu - 1); + val = readl_relaxed(src_base + SRC_SCR); + val = enable ? val | mask : val & ~mask; + val |= 1 << (BP_SRC_SCR_CORE1_RST + cpu - 1); + writel_relaxed(val, src_base + SRC_SCR); + } + spin_unlock(&src_lock); } void imx_set_cpu_jump(int cpu, void *jump_addr) { + spin_lock(&src_lock); cpu = cpu_logical_map(cpu); - writel_relaxed(__pa_symbol(jump_addr), - src_base + SRC_GPR1 + cpu * 8); + if (cpu_is_imx7d()) + writel_relaxed(__pa_symbol(jump_addr), + src_base + SRC_GPR1_V2 + cpu * 8); + else + writel_relaxed(__pa_symbol(jump_addr), + src_base + SRC_GPR1 + cpu * 8); + spin_unlock(&src_lock); } u32 imx_get_cpu_arg(int cpu) { cpu = cpu_logical_map(cpu); - return readl_relaxed(src_base + SRC_GPR1 + cpu * 8 + 4); + if (cpu_is_imx7d()) + return readl_relaxed(src_base + SRC_GPR1_V2 + + cpu * 8 + 4); + else + return readl_relaxed(src_base + SRC_GPR1 + + cpu * 8 + 4); } void imx_set_cpu_arg(int cpu, u32 arg) { cpu = cpu_logical_map(cpu); - writel_relaxed(arg, src_base + SRC_GPR1 + cpu * 8 + 4); + if (cpu_is_imx7d()) + writel_relaxed(arg, src_base + SRC_GPR1_V2 + + cpu * 8 + 4); + else + writel_relaxed(arg, src_base + SRC_GPR1 + + cpu * 8 + 4); } void __init imx_src_init(void) @@ -127,6 +162,15 @@ void __init imx_src_init(void) src_base = of_iomap(np, 0); WARN_ON(!src_base); + if (cpu_is_imx7d()) { + val = readl_relaxed(src_base + SRC_M4RCR); + if (((val & BIT(3)) == BIT(3)) && !(val & BIT(0))) + m4_is_enabled = true; + else + m4_is_enabled = false; + return; + } + imx_reset_controller.of_node = np; if (IS_ENABLED(CONFIG_RESET_CONTROLLER)) reset_controller_register(&imx_reset_controller); @@ -135,7 +179,7 @@ void __init imx_src_init(void) * force warm reset sources to generate cold reset * for a more reliable restart */ - spin_lock(&scr_lock); + spin_lock(&src_lock); val = readl_relaxed(src_base + SRC_SCR); /* bit 4 is m4c_non_sclr_rst on i.MX6SX */ @@ -147,5 +191,5 @@ void __init imx_src_init(void) val &= ~(1 << BP_SRC_SCR_WARM_RESET_ENABLE); writel_relaxed(val, src_base + SRC_SCR); - spin_unlock(&scr_lock); + spin_unlock(&src_lock); } diff --git a/arch/arm/mach-imx/suspend-imx7.S b/arch/arm/mach-imx/suspend-imx7.S new file mode 100644 index 000000000000..5f4e31152a69 --- /dev/null +++ b/arch/arm/mach-imx/suspend-imx7.S @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include "hardware.h" + +/* + * ==================== low level suspend ==================== + * + * Better to follow below rules to use ARM registers: + * r0: pm_info structure address; + * r1 ~ r4: for saving pm_info members; + * r5 ~ r10: free registers; + * r11: io base address. + * + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx7_suspend code + * PM_INFO structure(imx7_cpu_pm_info) + * ======================== low address ======================= + */ + +/* + * Below offsets are based on struct imx7_cpu_pm_info + * which defined in arch/arm/mach-imx/pm-imx7.c, this + * structure contains necessary pm info for low level + * suspend related code. + */ +#define PM_INFO_M4_RESERVE0_OFFSET 0x0 +#define PM_INFO_M4_RESERVE1_OFFSET 0x4 +#define PM_INFO_M4_RESERVE2_OFFSET 0x8 +#define PM_INFO_PBASE_OFFSET 0xc +#define PM_INFO_RESUME_ADDR_OFFSET 0x10 +#define PM_INFO_DDR_TYPE_OFFSET 0x14 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0x18 +#define PM_INFO_MX7_DDRC_P_OFFSET 0x1c +#define PM_INFO_MX7_DDRC_V_OFFSET 0x20 +#define PM_INFO_MX7_DDRC_PHY_P_OFFSET 0x24 +#define PM_INFO_MX7_DDRC_PHY_V_OFFSET 0x28 +#define PM_INFO_MX7_SRC_P_OFFSET 0x2c +#define PM_INFO_MX7_SRC_V_OFFSET 0x30 +#define PM_INFO_MX7_IOMUXC_GPR_P_OFFSET 0x34 +#define PM_INFO_MX7_IOMUXC_GPR_V_OFFSET 0x38 +#define PM_INFO_MX7_CCM_P_OFFSET 0x3c +#define PM_INFO_MX7_CCM_V_OFFSET 0x40 +#define PM_INFO_MX7_GPC_P_OFFSET 0x44 +#define PM_INFO_MX7_GPC_V_OFFSET 0x48 +#define PM_INFO_MX7_SNVS_P_OFFSET 0x4c +#define PM_INFO_MX7_SNVS_V_OFFSET 0x50 +#define PM_INFO_MX7_ANATOP_P_OFFSET 0x54 +#define PM_INFO_MX7_ANATOP_V_OFFSET 0x58 +#define PM_INFO_MX7_LPSR_P_OFFSET 0x5c +#define PM_INFO_MX7_LPSR_V_OFFSET 0x60 +#define PM_INFO_MX7_GIC_DIST_P_OFFSET 0x64 +#define PM_INFO_MX7_GIC_DIST_V_OFFSET 0x68 +#define PM_INFO_MX7_TTBR1_V_OFFSET 0x6c +#define PM_INFO_DDRC_REG_NUM_OFFSET 0x70 +#define PM_INFO_DDRC_REG_OFFSET 0x74 +#define PM_INFO_DDRC_VALUE_OFFSET 0x78 +#define PM_INFO_DDRC_PHY_REG_NUM_OFFSET 0x174 +#define PM_INFO_DDRC_PHY_REG_OFFSET 0x178 +#define PM_INFO_DDRC_PHY_VALUE_OFFSET 0x17c + +#define MX7_SRC_GPR1 0x74 +#define MX7_SRC_GPR2 0x78 +#define GPC_PGC_C0 0x800 +#define GPC_PGC_FM 0xa00 +#define ANADIG_SNVS_MISC_CTRL 0x380 +#define ANADIG_SNVS_MISC_CTRL_SET 0x384 +#define ANADIG_SNVS_MISC_CTRL_CLR 0x388 +#define ANADIG_DIGPROG 0x800 +#define DDRC_STAT 0x4 +#define DDRC_PWRCTL 0x30 +#define DDRC_PSTAT 0x3fc +#define DDRC_PCTRL_0 0x490 +#define DDRC_DFIMISC 0x1b0 +#define DDRC_SWCTL 0x320 +#define DDRC_SWSTAT 0x324 +#define DDRPHY_LP_CON0 0x18 + +#define CCM_SNVS_LPCG 0x250 +#define MX7D_GPC_IMR1 0x30 +#define MX7D_GPC_IMR2 0x34 +#define MX7D_GPC_IMR3 0x38 +#define MX7D_GPC_IMR4 0x3c + + .align 3 + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + .endm + + .macro store_ttbr1 + + /* Store TTBR1 to pm_info->ttbr1 */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_MX7_TTBR1_V_OFFSET] + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + ldr r6, =iram_tlb_phys_addr + ldr r6, [r6] + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r6, c2, c0, 1 + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + .endm + + .macro restore_ttbr1 + + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + /* Restore TTBCR */ + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Restore TTBR1, get the origin ttbr1 from pm info */ + ldr r7, [r0, #PM_INFO_MX7_TTBR1_V_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + + .macro ddrc_enter_self_refresh + + ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET] + + /* let DDR out of self-refresh */ + ldr r7, =0x0 + str r7, [r11, #DDRC_PWRCTL] + + /* wait rw port_busy clear */ + ldr r6, =(0x1 << 16) + orr r6, r6, #0x1 +1: + ldr r7, [r11, #DDRC_PSTAT] + ands r7, r7, r6 + bne 1b + + /* enter self-refresh bit 5 */ + ldr r7, =(0x1 << 5) + str r7, [r11, #DDRC_PWRCTL] + + /* wait until self-refresh mode entered */ +2: + ldr r7, [r11, #DDRC_STAT] + and r7, r7, #0x3 + cmp r7, #0x3 + bne 2b +3: + ldr r7, [r11, #DDRC_STAT] + ands r7, r7, #0x20 + beq 3b + + /* disable dram clk */ + ldr r7, [r11, #DDRC_PWRCTL] + orr r7, r7, #(1 << 3) + str r7, [r11, #DDRC_PWRCTL] + + .endm + + .macro ddrc_exit_self_refresh + + cmp r5, #0x0 + ldreq r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET] + ldrne r11, [r0, #PM_INFO_MX7_DDRC_P_OFFSET] + + /* let DDR out of self-refresh */ + ldr r7, =0x0 + str r7, [r11, #DDRC_PWRCTL] + + /* wait until self-refresh mode entered */ +4: + ldr r7, [r11, #DDRC_STAT] + and r7, r7, #0x3 + cmp r7, #0x3 + beq 4b + + /* enable auto self-refresh */ + ldr r7, [r11, #DDRC_PWRCTL] + orr r7, r7, #(1 << 0) + str r7, [r11, #DDRC_PWRCTL] + + .endm + + .macro wait_delay +5: + subs r6, r6, #0x1 + bne 5b + + .endm + + .macro ddr_enter_retention + + ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET] + + /* let DDR out of self-refresh */ + ldr r7, =0x0 + str r7, [r11, #DDRC_PCTRL_0] + + /* wait rw port_busy clear */ + ldr r6, =(0x1 << 16) + orr r6, r6, #0x1 +6: + ldr r7, [r11, #DDRC_PSTAT] + ands r7, r7, r6 + bne 6b + + ldr r11, [r0, #PM_INFO_MX7_DDRC_V_OFFSET] + /* enter self-refresh bit 5 */ + ldr r7, =(0x1 << 5) + str r7, [r11, #DDRC_PWRCTL] + + /* wait until self-refresh mode entered */ +7: + ldr r7, [r11, #DDRC_STAT] + and r7, r7, #0x3 + cmp r7, #0x3 + bne 7b +8: + ldr r7, [r11, #DDRC_STAT] + ands r7, r7, #0x20 + beq 8b + + /* disable dram clk */ + ldr r7, =(0x1 << 5) + orr r7, r7, #(1 << 3) + str r7, [r11, #DDRC_PWRCTL] + + ldr r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET] + ldr r7, [r11, #ANADIG_DIGPROG] + and r7, r7, #0xff + cmp r7, #0x11 + bne 10f + + /* TO 1.1 */ + ldr r11, [r0, #PM_INFO_MX7_IOMUXC_GPR_V_OFFSET] + ldr r7, =0x38000000 + str r7, [r11] + + /* LPSR mode need to use TO1.0 flow as IOMUX lost power */ + ldr r10, [r0, #PM_INFO_MX7_LPSR_V_OFFSET] + ldr r7, [r10] + cmp r7, #0x0 + beq 11f +10: + /* reset ddr_phy */ + ldr r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET] + ldr r7, =0x0 + str r7, [r11, #ANADIG_SNVS_MISC_CTRL] + + /* delay 7 us */ + ldr r6, =6000 + wait_delay + + ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET] + ldr r6, =0x1000 + ldr r7, [r11, r6] + orr r7, r7, #0x1 + str r7, [r11, r6] +11: + /* turn off ddr power */ + ldr r11, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET] + ldr r7, =(0x1 << 29) + str r7, [r11, #ANADIG_SNVS_MISC_CTRL_SET] + + ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET] + ldr r6, =0x1000 + ldr r7, [r11, r6] + orr r7, r7, #0x1 + str r7, [r11, r6] + + .endm + + .macro ddr_exit_retention + + cmp r5, #0x0 + ldreq r1, [r0, #PM_INFO_MX7_ANATOP_V_OFFSET] + ldrne r1, [r0, #PM_INFO_MX7_ANATOP_P_OFFSET] + ldreq r2, [r0, #PM_INFO_MX7_SRC_V_OFFSET] + ldrne r2, [r0, #PM_INFO_MX7_SRC_P_OFFSET] + ldreq r3, [r0, #PM_INFO_MX7_DDRC_V_OFFSET] + ldrne r3, [r0, #PM_INFO_MX7_DDRC_P_OFFSET] + ldreq r4, [r0, #PM_INFO_MX7_DDRC_PHY_V_OFFSET] + ldrne r4, [r0, #PM_INFO_MX7_DDRC_PHY_P_OFFSET] + ldreq r10, [r0, #PM_INFO_MX7_CCM_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX7_CCM_P_OFFSET] + ldreq r11, [r0, #PM_INFO_MX7_IOMUXC_GPR_V_OFFSET] + ldrne r11, [r0, #PM_INFO_MX7_IOMUXC_GPR_P_OFFSET] + + /* turn on ddr power */ + ldr r7, =(0x1 << 29) + str r7, [r1, #ANADIG_SNVS_MISC_CTRL_CLR] + + ldr r6, =50 + wait_delay + + /* clear ddr_phy reset */ + ldr r6, =0x1000 + ldr r7, [r2, r6] + orr r7, r7, #0x3 + str r7, [r2, r6] + ldr r7, [r2, r6] + bic r7, r7, #0x1 + str r7, [r2, r6] +13: + ldr r6, [r0, #PM_INFO_DDRC_REG_NUM_OFFSET] + ldr r7, =PM_INFO_DDRC_REG_OFFSET + add r7, r7, r0 +14: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r3, r8] + subs r6, r6, #0x1 + bne 14b + ldr r7, =0x20 + str r7, [r3, #DDRC_PWRCTL] + ldr r7, =0x0 + str r7, [r3, #DDRC_DFIMISC] + + /* do PHY, clear ddr_phy reset */ + ldr r6, =0x1000 + ldr r7, [r2, r6] + bic r7, r7, #0x2 + str r7, [r2, r6] + + ldr r7, [r1, #ANADIG_DIGPROG] + and r7, r7, #0xff + cmp r7, #0x11 + bne 12f + + /* + * TKT262940: + * System hang when press RST for DDR PAD is + * in retention mode, fixed on TO1.1 + */ + ldr r7, [r11] + bic r7, r7, #(1 << 27) + str r7, [r11] + ldr r7, [r11] + bic r7, r7, #(1 << 29) + str r7, [r11] +12: + ldr r7, =(0x1 << 30) + str r7, [r1, #ANADIG_SNVS_MISC_CTRL_SET] + + /* need to delay ~5mS */ + ldr r6, =0x100000 + wait_delay + + ldr r6, [r0, #PM_INFO_DDRC_PHY_REG_NUM_OFFSET] + ldr r7, =PM_INFO_DDRC_PHY_REG_OFFSET + add r7, r7, r0 + +15: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r4, r8] + subs r6, r6, #0x1 + bne 15b + + ldr r7, =0x0 + add r9, r10, #0x4000 + str r7, [r9, #0x130] + + ldr r7, =0x170 + orr r7, r7, #0x8 + str r7, [r11, #0x20] + + ldr r7, =0x2 + add r9, r10, #0x4000 + str r7, [r9, #0x130] + + ldr r7, =0xf + str r7, [r4, #DDRPHY_LP_CON0] + + /* wait until self-refresh mode entered */ +16: + ldr r7, [r3, #DDRC_STAT] + and r7, r7, #0x3 + cmp r7, #0x3 + bne 16b + ldr r7, =0x0 + str r7, [r3, #DDRC_SWCTL] + ldr r7, =0x1 + str r7, [r3, #DDRC_DFIMISC] + ldr r7, =0x1 + str r7, [r3, #DDRC_SWCTL] +17: + ldr r7, [r3, #DDRC_SWSTAT] + and r7, r7, #0x1 + cmp r7, #0x1 + bne 17b +18: + ldr r7, [r3, #DDRC_STAT] + and r7, r7, #0x20 + cmp r7, #0x20 + bne 18b + + /* let DDR out of self-refresh */ + ldr r7, =0x0 + str r7, [r3, #DDRC_PWRCTL] +19: + ldr r7, [r3, #DDRC_STAT] + and r7, r7, #0x30 + cmp r7, #0x0 + bne 19b + +20: + ldr r7, [r3, #DDRC_STAT] + and r7, r7, #0x3 + cmp r7, #0x1 + bne 20b + + /* enable port */ + ldr r7, =0x1 + str r7, [r3, #DDRC_PCTRL_0] + + /* enable auto self-refresh */ + ldr r7, [r3, #DDRC_PWRCTL] + orr r7, r7, #(1 << 0) + str r7, [r3, #DDRC_PWRCTL] + + .endm + +ENTRY(imx7_suspend) + push {r4-r12} + + /* make sure SNVS clk is enabled */ + ldr r11, [r0, #PM_INFO_MX7_CCM_V_OFFSET] + add r11, r11, #0x4000 + ldr r7, =0x3 + str r7, [r11, #CCM_SNVS_LPCG] + + /* check whether it is a standby mode */ + ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET] + ldr r7, [r11, #GPC_PGC_C0] + cmp r7, #0 + beq ddr_only_self_refresh + + /* + * The value of r0 is mapped the same in origin table and IRAM table, + * thus no need to care r0 here. + */ + ldr r1, [r0, #PM_INFO_PBASE_OFFSET] + ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] + ldr r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r6, =imx7_suspend + ldr r7, =resume + sub r7, r7, r6 + add r8, r1, r4 + add r9, r8, r7 + + ldr r11, [r0, #PM_INFO_MX7_SRC_V_OFFSET] + /* store physical resume addr and pm_info address. */ + str r9, [r11, #MX7_SRC_GPR1] + str r1, [r11, #MX7_SRC_GPR2] + + disable_l1_dcache + + store_ttbr1 + + ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET] + ldr r7, [r11, #GPC_PGC_FM] + cmp r7, #0 + beq ddr_only_self_refresh + + ddr_enter_retention + /* enter LPSR mode if resume addr is valid */ + ldr r11, [r0, #PM_INFO_MX7_LPSR_V_OFFSET] + ldr r7, [r11] + cmp r7, #0x0 + beq ddr_retention_enter_out + + /* disable STOP mode before entering LPSR */ + ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET] + ldr r7, [r11] + bic r7, #0xf + str r7, [r11] + + /* shut down vddsoc to enter lpsr mode */ + ldr r11, [r0, #PM_INFO_MX7_SNVS_V_OFFSET] + ldr r7, [r11, #0x38] + orr r7, r7, #0x60 + str r7, [r11, #0x38] +wait_shutdown: + wfi + nop + nop + nop + nop + b wait_shutdown + +ddr_only_self_refresh: + ddrc_enter_self_refresh + b wfi +ddr_retention_enter_out: + + ldr r11, [r0, #PM_INFO_MX7_GIC_DIST_V_OFFSET] + ldr r7, =0x0 + ldr r8, =0x1000 + str r7, [r11, r8] + + ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET] + ldr r4, [r11, #MX7D_GPC_IMR1] + ldr r5, [r11, #MX7D_GPC_IMR2] + ldr r6, [r11, #MX7D_GPC_IMR3] + ldr r7, [r11, #MX7D_GPC_IMR4] + + ldr r8, =0xffffffff + str r8, [r11, #MX7D_GPC_IMR1] + str r8, [r11, #MX7D_GPC_IMR2] + str r8, [r11, #MX7D_GPC_IMR3] + str r8, [r11, #MX7D_GPC_IMR4] + + /* + * enable the RBC bypass counter here + * to hold off the interrupts. RBC counter + * = 8 (240us). With this setting, the latency + * from wakeup interrupt to ARM power up + * is ~250uS. + */ + ldr r8, [r11, #0x14] + bic r8, r8, #(0x3f << 24) + orr r8, r8, #(0x8 << 24) + str r8, [r11, #0x14] + + /* enable the counter. */ + ldr r8, [r11, #0x14] + orr r8, r8, #(0x1 << 30) + str r8, [r11, #0x14] + + /* unmask all the GPC interrupts. */ + str r4, [r11, #MX7D_GPC_IMR1] + str r5, [r11, #MX7D_GPC_IMR2] + str r6, [r11, #MX7D_GPC_IMR3] + str r7, [r11, #MX7D_GPC_IMR4] + + /* + * now delay for a short while (3usec) + * ARM is at 1GHz at this point + * so a short loop should be enough. + * this delay is required to ensure that + * the RBC counter can start counting in + * case an interrupt is already pending + * or in case an interrupt arrives just + * as ARM is about to assert DSM_request. + */ + ldr r7, =2000 +rbc_loop: + subs r7, r7, #0x1 + bne rbc_loop +wfi: + /* Zzz, enter stop mode */ + wfi + nop + nop + nop + nop + + mov r5, #0x0 + + ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET] + ldr r7, [r11, #GPC_PGC_FM] + cmp r7, #0 + beq wfi_ddr_self_refresh_out + + ddr_exit_retention + b wfi_ddr_retention_out +wfi_ddr_self_refresh_out: + ddrc_exit_self_refresh +wfi_ddr_retention_out: + + /* check whether it is a standby mode */ + ldr r11, [r0, #PM_INFO_MX7_GPC_V_OFFSET] + ldr r7, [r11, #GPC_PGC_C0] + cmp r7, #0 + beq standby_out + + ldr r11, [r0, #PM_INFO_MX7_GIC_DIST_V_OFFSET] + ldr r7, =0x1 + ldr r8, =0x1000 + str r7, [r11, r8] + + restore_ttbr1 +standby_out: + pop {r4-r12} + /* return to suspend finish */ + mov pc, lr + +resume: + /* invalidate L1 I-cache first */ + mov r6, #0x0 + mcr p15, 0, r6, c7, c5, 0 + mcr p15, 0, r6, c7, c5, 6 + /* enable the Icache and branch prediction */ + mov r6, #0x1800 + mcr p15, 0, r6, c1, c0, 0 + isb + + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + /* clear core0's entry and parameter */ + ldr r11, [r0, #PM_INFO_MX7_SRC_P_OFFSET] + mov r7, #0x0 + str r7, [r11, #MX7_SRC_GPR1] + str r7, [r11, #MX7_SRC_GPR2] + + mov r5, #0x1 + + ldr r11, [r0, #PM_INFO_MX7_GPC_P_OFFSET] + ldr r7, [r11, #GPC_PGC_FM] + cmp r7, #0 + beq dsm_ddr_self_refresh_out + + ddr_exit_retention + b dsm_ddr_retention_out +dsm_ddr_self_refresh_out: + ddrc_exit_self_refresh +dsm_ddr_retention_out: + + mov pc, lr +ENDPROC(imx7_suspend) + +ENTRY(ca7_cpu_resume) + bl v7_invalidate_l1 + b cpu_resume +ENDPROC(ca7_cpu_resume) From 95508eed54eee2a6c7315373ff82e80b90cda981 Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Mon, 29 Apr 2019 16:21:39 +0800 Subject: [PATCH 12/81] arm: imx: remove RPMSG from Kconfig There's already an rpmsg driver under drivers/rpmsg Remove this duplicated one. Signed-off-by: Dong Aisheng --- arch/arm/mach-imx/Kconfig | 6 ------ 1 file changed, 6 deletions(-) diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index 57a2eb8e908a..9b424cbe5b55 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -60,10 +60,6 @@ config HAVE_IMX_BUSFREQ config HAVE_IMX_MU bool -config HAVE_IMX_RPMSG - select RPMSG_VIRTIO - bool - config HAVE_IMX_SRC def_bool y if SMP select ARCH_HAS_RESET_CONTROLLER @@ -535,8 +531,6 @@ config SOC_IMX6SX select HAVE_IMX_AMP select SOC_IMX6 select HAVE_IMX_MU - select HAVE_IMX_RPMSG - select RPMSG select IMX_SEMA4 select KEYBOARD_SNVS_PWRKEY From 1bc81e83d4faa75735dda1e7d28dbcdf40c092d4 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Sun, 28 Apr 2019 17:35:44 +0800 Subject: [PATCH 13/81] arm: imx: Add map io for imx6sl Add the map io for i.MX6SL to support suspend/resume, busfreq and low power idle. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/mach-imx6sl.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm/mach-imx/mach-imx6sl.c b/arch/arm/mach-imx/mach-imx6sl.c index e00818abe54d..c4d6a1ec069a 100644 --- a/arch/arm/mach-imx/mach-imx6sl.c +++ b/arch/arm/mach-imx/mach-imx6sl.c @@ -73,6 +73,14 @@ static void __init imx6sl_init_irq(void) imx6_pm_ccm_init("fsl,imx6sll-ccm"); } +static void __init imx6sl_map_io(void) +{ + imx6_pm_map_io(); +#ifdef CONFIG_CPU_FREQ + imx_busfreq_map_io(); +#endif +} + static const char * const imx6sl_dt_compat[] __initconst = { "fsl,imx6sl", "fsl,imx6sll", @@ -82,6 +90,7 @@ static const char * const imx6sl_dt_compat[] __initconst = { DT_MACHINE_START(IMX6SL, "Freescale i.MX6 SoloLite (Device Tree)") .l2c_aux_val = 0, .l2c_aux_mask = ~0, + .map_io = imx6sl_map_io, .init_irq = imx6sl_init_irq, .init_machine = imx6sl_init_machine, .init_late = imx6sl_init_late, From 55ec6efecdf8c0700f90e2c3b7f8fd71aa8e567e Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Mon, 29 Apr 2019 10:25:50 +0800 Subject: [PATCH 14/81] arm: imx: Add busfreq support imx6sl Add busfreq support for i.MX6SL. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/Makefile | 3 +- arch/arm/mach-imx/busfreq-imx.c | 230 +++++++++- arch/arm/mach-imx/busfreq_lpddr2.c | 4 + arch/arm/mach-imx/lpddr2_freq_imx6.S | 618 +++++++++++++++++++++++++++ 4 files changed, 853 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-imx/lpddr2_freq_imx6.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 84e8112afb2b..998b07de577d 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -85,7 +85,7 @@ obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o endif obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o ddr3_freq_imx6.o smp_wfe_imx6.o \ lpddr2_freq_imx6q.o -obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o +obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o lpddr2_freq_imx6.o obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o ddr3_freq_imx6sx.o smp_wfe_imx6.o lpddr2_freq_imx6sx.o obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o @@ -100,6 +100,7 @@ AFLAGS_smp_wfe_imx6.o :=-Wa,-march=armv7-a AFLAGS_ddr3_freq_imx7d.o :=-Wa,-march=armv7-a AFLAGS_lpddr3_freq_imx.o :=-Wa,-march=armv7-a AFLAGS_ddr3_freq_imx6.o :=-Wa,-march=armv7-a +AFLAGS_lpddr2_freq_imx6.o :=-Wa,-march=armv7-a AFLAGS_lpddr2_freq_imx6q.o :=-Wa,-march=armv7-a AFLAGS_lpddr2_freq_imx6sx.o :=-Wa,-march=armv7-a AFLAGS_ddr3_freq_imx6sx.o :=-Wa,-march=armv7-a diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index c4aae89ced16..f948ca65ffb3 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -66,6 +66,7 @@ static int bus_freq_scaling_is_active; static int high_bus_count, med_bus_count, audio_bus_count, low_bus_count; static unsigned int ddr_low_rate; static int cur_bus_freq_mode; +static u32 org_arm_rate; extern unsigned long iram_tlb_phys_addr; extern int unsigned long iram_tlb_base_addr; @@ -119,6 +120,17 @@ static struct clk *mmdc_clk; static struct clk *periph_clk2_clk; static struct clk *pll2_bus_clk; +static struct clk *pll2_bypass_src_clk; +static struct clk *pll2_bypass_clk; +static struct clk *pll2_clk; +static struct clk *arm_clk; +static struct clk *step_clk; +static struct clk *pll1_clk; +static struct clk *pll1_bypass_src_clk; +static struct clk *pll1_bypass_clk; +static struct clk *pll1_sys_clk; +static struct clk *pll1_sw_clk; + static struct clk *pll3_pfd1_540m_clk; static struct clk *ocram_clk; @@ -370,6 +382,186 @@ static void exit_lpm_imx6_smp(void) clk_disable_unprepare(pll2_400_clk); } +static void enter_lpm_imx6sl(void) +{ + if (high_bus_freq_mode) { + /* Set periph_clk to be sourced from OSC_CLK */ + clk_set_parent(periph_clk2_sel_clk, osc_clk); + clk_set_parent(periph_clk, periph_clk2_clk); + /* Ensure AHB/AXI clks are at 24MHz. */ + clk_set_rate(ahb_clk, LPAPM_CLK); + clk_set_rate(ocram_clk, LPAPM_CLK); + } + if (audio_bus_count) { + /* Set AHB to 8MHz to lower pwer.*/ + clk_set_rate(ahb_clk, LPAPM_CLK / 3); + + /* Set up DDR to 100MHz. */ + busfreq_func.update(HIGH_AUDIO_CLK); + + /* Fix the clock tree in kernel */ + clk_set_parent(periph2_pre_clk, pll2_200_clk); + clk_set_parent(periph2_clk, periph2_pre_clk); + + if (low_bus_freq_mode || ultra_low_bus_freq_mode) { + /* + * Fix the clock tree in kernel, make sure + * pll2_bypass is updated as it is + * sourced from PLL2. + */ + clk_set_parent(pll2_bypass_clk, pll2_clk); + /* + * Swtich ARM to run off PLL2_PFD2_400MHz + * since DDR is anyway at 100MHz. + */ + clk_set_parent(step_clk, pll2_400_clk); + clk_set_parent(pll1_sw_clk, step_clk); + + /* + * Need to ensure that PLL1 is bypassed and enabled + * before ARM-PODF is set. + */ + clk_set_parent(pll1_bypass_clk, pll1_bypass_src_clk); + + /* + * Ensure that the clock will be + * at original speed. + */ + clk_set_rate(arm_clk, org_arm_rate); + } + low_bus_freq_mode = 0; + ultra_low_bus_freq_mode = 0; + audio_bus_freq_mode = 1; + cur_bus_freq_mode = BUS_FREQ_AUDIO; + } else { + u32 arm_div, pll1_rate; + org_arm_rate = clk_get_rate(arm_clk); + if (org_arm_rate == 0) { + WARN_ON(1); + return; + } + if (low_bus_freq_mode && low_bus_count == 0) { + /* + * We are already in DDR @ 24MHz state, but + * no one but ARM needs the DDR. In this case, + * we can lower the DDR freq to 1MHz when ARM + * enters WFI in this state. Keep track of this state. + */ + ultra_low_bus_freq_mode = 1; + low_bus_freq_mode = 0; + audio_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_ULTRA_LOW; + } else { + if (!ultra_low_bus_freq_mode && !low_bus_freq_mode) { + /* + * Anyway, make sure the AHB is running at 24MHz + * in low_bus_freq_mode. + */ + if (audio_bus_freq_mode) + clk_set_rate(ahb_clk, LPAPM_CLK); + /* + * Set DDR to 24MHz. + * Since we are going to bypass PLL2, + * we need to move ARM clk off PLL2_PFD2 + * to PLL1. Make sure the PLL1 is running + * at the lowest possible freq. + * To work well with CPUFREQ we want to ensure that + * the CPU freq does not change, so attempt to + * get a freq as close to 396MHz as possible. + */ + clk_set_rate(pll1_clk, + clk_round_rate(pll1_clk, (org_arm_rate * 2))); + pll1_rate = clk_get_rate(pll1_clk); + arm_div = pll1_rate / org_arm_rate; + if (pll1_rate / arm_div > org_arm_rate) + arm_div++; + /* + * Need to ensure that PLL1 is bypassed and enabled + * before ARM-PODF is set. + */ + clk_set_parent(pll1_bypass_clk, pll1_clk); + /* + * Ensure ARM CLK is lower before + * changing the parent. + */ + clk_set_rate(arm_clk, org_arm_rate / arm_div); + /* Now set the ARM clk parent to PLL1_SYS. */ + clk_set_parent(pll1_sw_clk, pll1_sys_clk); + + /* + * Set STEP_CLK back to OSC to save power and + * also to maintain the parent.The WFI iram code + * will switch step_clk to osc, but the clock API + * is not aware of the change and when a new request + * to change the step_clk parent to pll2_pfd2_400M + * is requested sometime later, the change is ignored. + */ + clk_set_parent(step_clk, osc_clk); + + /* Now set DDR to 24MHz. */ + busfreq_func.update(LPAPM_CLK); + + /* + * Fix the clock tree in kernel. + * Make sure PLL2 rate is updated as it gets + * bypassed in the DDR freq change code. + */ + clk_set_parent(pll2_bypass_clk, pll2_bypass_src_clk); + clk_set_parent(periph2_clk2_sel_clk, pll2_bus_clk); + clk_set_parent(periph2_clk, periph2_clk2_clk); + } + if (low_bus_count == 0) { + ultra_low_bus_freq_mode = 1; + low_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_ULTRA_LOW; + } else { + ultra_low_bus_freq_mode = 0; + low_bus_freq_mode = 1; + cur_bus_freq_mode = BUS_FREQ_LOW; + } + audio_bus_freq_mode = 0; + } + } +} + +static void exit_lpm_imx6sl(void) +{ + /* Change DDR freq in IRAM. */ + busfreq_func.update(ddr_normal_rate); + + /* + * Fix the clock tree in kernel. + * Make sure PLL2 rate is updated as it gets + * un-bypassed in the DDR freq change code. + */ + clk_set_parent(pll2_bypass_clk, pll2_clk); + clk_set_parent(periph2_pre_clk, pll2_400_clk); + clk_set_parent(periph2_clk, periph2_pre_clk); + + /* Ensure that periph_clk is sourced from PLL2_400. */ + clk_set_parent(periph_pre_clk, pll2_400_clk); + /* + * Before switching the perhiph_clk, ensure that the + * AHB/AXI will not be too fast. + */ + clk_set_rate(ahb_clk, LPAPM_CLK / 3); + clk_set_rate(ocram_clk, LPAPM_CLK / 2); + clk_set_parent(periph_clk, periph_pre_clk); + + if (low_bus_freq_mode || ultra_low_bus_freq_mode) { + /* Move ARM from PLL1_SW_CLK to PLL2_400. */ + clk_set_parent(step_clk, pll2_400_clk); + clk_set_parent(pll1_sw_clk, step_clk); + /* + * Need to ensure that PLL1 is bypassed and enabled + * before ARM-PODF is set. + */ + clk_set_parent(pll1_bypass_clk, pll1_bypass_src_clk); + clk_set_rate(arm_clk, org_arm_rate); + ultra_low_bus_freq_mode = 0; + } +} + static void enter_lpm_imx7d(void) { /* @@ -441,6 +633,8 @@ static void reduce_bus_freq(void) enter_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) enter_lpm_imx6_smp(); + else if (cpu_is_imx6sl()) + enter_lpm_imx6sl(); med_bus_freq_mode = 0; high_bus_freq_mode = 0; @@ -543,6 +737,8 @@ static int set_high_bus_freq(int high_bus_freq) exit_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) exit_lpm_imx6_smp(); + else if (cpu_is_imx6sl()) + exit_lpm_imx6sl(); high_bus_freq_mode = 1; med_bus_freq_mode = 0; @@ -895,7 +1091,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx()) { + if (cpu_is_imx6sx() || cpu_is_imx6sl()) { ahb_clk = devm_clk_get(&pdev->dev, "ahb"); ocram_clk = devm_clk_get(&pdev->dev, "ocram"); periph2_clk = devm_clk_get(&pdev->dev, "periph2"); @@ -936,6 +1132,35 @@ static int busfreq_probe(struct platform_device *pdev) return -EINVAL; } } + + if (cpu_is_imx6sl()) { + pll2_bypass_src_clk = devm_clk_get(&pdev->dev, "pll2_bypass_src"); + pll2_bypass_clk = devm_clk_get(&pdev->dev, "pll2_bypass"); + pll2_clk = devm_clk_get(&pdev->dev, "pll2"); + if (IS_ERR(pll2_bypass_src_clk) || IS_ERR(pll2_bypass_clk) + || IS_ERR(pll2_clk)) { + dev_err(busfreq_dev, + "%s failed to get busfreq clk for imx6sl.\n", __func__); + return -EINVAL; + } + } + + if (cpu_is_imx6sl()) { + arm_clk = devm_clk_get(&pdev->dev, "arm"); + step_clk = devm_clk_get(&pdev->dev, "step"); + pll1_clk = devm_clk_get(&pdev->dev, "pll1"); + pll1_bypass_src_clk = devm_clk_get(&pdev->dev, "pll1_bypass_src"); + pll1_bypass_clk = devm_clk_get(&pdev->dev, "pll1_bypass"); + pll1_sys_clk = devm_clk_get(&pdev->dev, "pll1_sys"); + pll1_sw_clk = devm_clk_get(&pdev->dev, "pll1_sw"); + if (IS_ERR(arm_clk) || IS_ERR(step_clk) || IS_ERR(pll1_clk) + || IS_ERR(pll1_bypass_src_clk) || IS_ERR(pll1_bypass_clk) + || IS_ERR(pll1_sys_clk) || IS_ERR(pll1_sw_clk)) { + dev_err(busfreq_dev, "%s failed to get busfreq clk for imx6ull/sl.\n", __func__); + return -EINVAL; + } + } + if (cpu_is_imx7d()) { osc_clk = devm_clk_get(&pdev->dev, "osc"); axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel"); @@ -1044,6 +1269,9 @@ static int busfreq_probe(struct platform_device *pdev) busfreq_func.init = &init_mmdc_lpddr2_settings_mx6q; busfreq_func.update = &update_lpddr2_freq_smp; } + } else if (cpu_is_imx6sl()) { + busfreq_func.init = &init_mmdc_lpddr2_settings; + busfreq_func.update = &update_lpddr2_freq; } if (busfreq_func.init) diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c index 72c947370f51..8c7793c01f3a 100644 --- a/arch/arm/mach-imx/busfreq_lpddr2.c +++ b/arch/arm/mach-imx/busfreq_lpddr2.c @@ -162,6 +162,10 @@ int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) ddr_code_size = SZ_4K; + if (cpu_is_imx6sl()) + mx6_change_lpddr2_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base, + &mx6_lpddr2_freq_change, ddr_code_size); if (cpu_is_imx6sx()) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, diff --git a/arch/arm/mach-imx/lpddr2_freq_imx6.S b/arch/arm/mach-imx/lpddr2_freq_imx6.S new file mode 100644 index 000000000000..21179fb2cb72 --- /dev/null +++ b/arch/arm/mach-imx/lpddr2_freq_imx6.S @@ -0,0 +1,618 @@ +/* + * Copyright (C) 2012-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "hardware.h" + +#define PL310_AUX_CTRL 0x104 +#define PL310_DCACHE_LOCKDOWN_BASE 0x900 +#define PL310_AUX_16WAY_BIT 0x10000 +#define PL310_LOCKDOWN_NBREGS 8 +#define PL310_LOCKDOWN_SZREG 4 +#define PL310_8WAYS_MASK 0x00FF +#define PL310_16WAYS_UPPERMASK 0xFF00 + +.globl imx6_lpddr2_freq_change_start +.globl imx6_lpddr2_freq_change_end + + .macro mx6sl_switch_to_24MHz + + /* + * Set MMDC clock to be sourced from PLL3. + * Ensure first periph2_clk2 is sourced from PLL3. + * Set the PERIPH2_CLK2_PODF to divide by 2. + */ + ldr r6, [r2, #0x14] + bic r6, r6, #0x7 + orr r6, r6, #0x1 + str r6, [r2, #0x14] + + /* Select PLL3 to source MMDC. */ + ldr r6, [r2, #0x18] + bic r6, r6, #0x100000 + str r6, [r2, #0x18] + + /* Swtich periph2_clk_sel to run from PLL3. */ + ldr r6, [r2, #0x14] + orr r6, r6, #0x4000000 + str r6, [r2, #0x14] + +periph2_clk_switch1: + ldr r6, [r2, #0x48] + cmp r6, #0 + bne periph2_clk_switch1 + + /* + * Need to clock gate the 528 PFDs before + * powering down PLL2. + * Only the PLL2_PFD2_400M should be ON + * at this time, so only clock gate that one. + */ + ldr r6, [r3, #0x100] + orr r6, r6, #0x800000 + str r6, [r3, #0x100] + + /* + * Set PLL2 to bypass state. We should be here + * only if MMDC is not sourced from PLL2. + */ + ldr r6, [r3, #0x30] + orr r6, r6, #0x10000 + str r6, [r3, #0x30] + + ldr r6, [r3, #0x30] + orr r6, r6, #0x1000 + str r6, [r3, #0x30] + + /* Ensure pre_periph2_clk_mux is set to pll2 */ + ldr r6, [r2, #0x18] + bic r6, r6, #0x600000 + str r6, [r2, #0x18] + + /* Set MMDC clock to be sourced from the bypassed PLL2. */ + ldr r6, [r2, #0x14] + bic r6, r6, #0x4000000 + str r6, [r2, #0x14] + +periph2_clk_switch2: + ldr r6, [r2, #0x48] + cmp r6, #0 + bne periph2_clk_switch2 + + /* + * Now move MMDC back to periph2_clk2 source. + * after selecting PLL2 as the option. + * Select PLL2 as the source. + */ + ldr r6, [r2, #0x18] + orr r6, r6, #0x100000 + str r6, [r2, #0x18] + + /* set periph2_clk2_podf to divide by 1. */ + ldr r6, [r2, #0x14] + bic r6, r6, #0x7 + str r6, [r2, #0x14] + + /* Now move periph2_clk to periph2_clk2 source */ + ldr r6, [r2, #0x14] + orr r6, r6, #0x4000000 + str r6, [r2, #0x14] + +periph2_clk_switch3: + ldr r6, [r2, #0x48] + cmp r6, #0 + bne periph2_clk_switch3 + + /* Now set the MMDC PODF back to 1.*/ + ldr r6, [r2, #0x14] + bic r6, r6, #0x38 + str r6, [r2, #0x14] + +mmdc_podf0: + ldr r6, [r2, #0x48] + cmp r6, #0 + bne mmdc_podf0 + + .endm + + .macro ddr_switch_400MHz + + /* Set MMDC divider first, in case PLL3 is at 480MHz. */ + ldr r6, [r3, #0x10] + and r6, r6, #0x10000 + cmp r6, #0x10000 + beq pll3_in_bypass + + /* Set MMDC divder to divide by 2. */ + ldr r6, [r2, #0x14] + bic r6, r6, #0x38 + orr r6, r6, #0x8 + str r6, [r2, #0x14] + +mmdc_podf: + ldr r6, [r2, #0x48] + cmp r6, #0 + bne mmdc_podf + +pll3_in_bypass: + /* + * Check if we are switching between + * 400Mhz <-> 100MHz.If so, we should + * try to source MMDC from PLL2_200M. + */ + cmp r1, #0 + beq not_low_bus_freq + + /* Ensure that MMDC is sourced from PLL2 mux first. */ + ldr r6, [r2, #0x14] + bic r6, r6, #0x4000000 + str r6, [r2, #0x14] + +periph2_clk_switch4: + ldr r6, [r2, #0x48] + cmp r6, #0 + bne periph2_clk_switch4 + +not_low_bus_freq: + /* Now ensure periph2_clk2_sel mux is set to PLL3 */ + ldr r6, [r2, #0x18] + bic r6, r6, #0x100000 + str r6, [r2, #0x18] + + /* Now switch MMDC to PLL3. */ + ldr r6, [r2, #0x14] + orr r6, r6, #0x4000000 + str r6, [r2, #0x14] + +periph2_clk_switch5: + ldr r6, [r2, #0x48] + cmp r6, #0 + bne periph2_clk_switch5 + + /* + * Check if PLL2 is already unlocked. + * If so do nothing with PLL2. + */ + cmp r1, #0 + beq pll2_already_on + + /* Now power up PLL2 and unbypass it. */ + ldr r6, [r3, #0x30] + bic r6, r6, #0x1000 + str r6, [r3, #0x30] + + /* Make sure PLL2 has locked.*/ +wait_for_pll_lock: + ldr r6, [r3, #0x30] + and r6, r6, #0x80000000 + cmp r6, #0x80000000 + bne wait_for_pll_lock + + ldr r6, [r3, #0x30] + bic r6, r6, #0x10000 + str r6, [r3, #0x30] + + /* + * Need to enable the 528 PFDs after + * powering up PLL2. + * Only the PLL2_PFD2_400M should be ON + * as it feeds the MMDC. Rest should have + * been managed by clock code. + */ + ldr r6, [r3, #0x100] + bic r6, r6, #0x800000 + str r6, [r3, #0x100] + +pll2_already_on: + /* + * Now switch MMDC clk back to pll2_mux option. + * Ensure pre_periph2_clk2 is set to pll2_pfd_400M. + * If switching to audio DDR freq, set the + * pre_periph2_clk2 to PLL2_PFD_200M + */ + ldr r6, =400000000 + cmp r6, r0 + bne use_pll2_pfd_200M + + ldr r6, [r2, #0x18] + bic r6, r6, #0x600000 + orr r6, r6, #0x200000 + str r6, [r2, #0x18] + ldr r6, =400000000 + b cont2 + +use_pll2_pfd_200M: + ldr r6, [r2, #0x18] + orr r6, r6, #0x600000 + str r6, [r2, #0x18] + ldr r6, =200000000 + +cont2: + ldr r4, [r2, #0x14] + bic r4, r4, #0x4000000 + str r4, [r2, #0x14] + +periph2_clk_switch6: + ldr r4, [r2, #0x48] + cmp r4, #0 + bne periph2_clk_switch6 + +change_divider_only: + /* + * Calculate the MMDC divider + * based on the requested freq. + */ + ldr r4, =0 +Loop2: + sub r6, r6, r0 + cmp r6, r0 + blt Div_Found + add r4, r4, #1 + bgt Loop2 + + /* Shift divider into correct offset. */ + lsl r4, r4, #3 +Div_Found: + /* Set the MMDC PODF. */ + ldr r6, [r2, #0x14] + bic r6, r6, #0x38 + orr r6, r6, r4 + str r6, [r2, #0x14] + +mmdc_podf1: + ldr r6, [r2, #0x48] + cmp r6, #0 + bne mmdc_podf1 + + .endm + + .macro mmdc_clk_lower_100MHz + + /* + * Prior to reducing the DDR frequency (at 528/400 MHz), + * read the Measure unit count bits (MU_UNIT_DEL_NUM) + */ + ldr r5, =0x8B8 + ldr r6, [r8, r5] + /* Original MU unit count */ + mov r6, r6, LSR #16 + ldr r4, =0x3FF + and r6, r6, r4 + /* Original MU unit count * 2 */ + mov r7, r6, LSL #1 + /* + * Bypass the automatic measure unit when below 100 MHz + * by setting the Measure unit bypass enable bit (MU_BYP_EN) + */ + ldr r6, [r8, r5] + orr r6, r6, #0x400 + str r6, [r8, r5] + /* + * Double the measure count value read in step 1 and program it in the + * measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit + * Register for the reduced frequency operation below 100 MHz + */ + ldr r6, [r8, r5] + ldr r4, =0x3FF + bic r6, r6, r4 + orr r6, r6, r7 + str r6, [r8, r5] + + .endm + + .macro mmdc_clk_above_100MHz + + /* Make sure that the PHY measurement unit is NOT in bypass mode */ + ldr r5, =0x8B8 + ldr r6, [r8, r5] + bic r6, r6, #0x400 + str r6, [r8, r5] + /* Now perform a Force Measurement. */ + ldr r6, [r8, r5] + orr r6, r6, #0x800 + str r6, [r8, r5] + /* Wait for FRC_MSR to clear. */ +force_measure1: + ldr r6, [r8, r5] + and r6, r6, #0x800 + cmp r6, #0x0 + bne force_measure1 + .endm + +/* + * mx6_lpddr2_freq_change + * + * Make sure DDR is in self-refresh. + * IRQs are already disabled. + * r0 : DDR freq. + * r1: low_bus_freq_mode flag + */ + .align 3 +ENTRY(mx6_lpddr2_freq_change) +imx6_lpddr2_freq_change_start: + push {r4-r10} + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + dsb + isb + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Disable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + +#ifdef CONFIG_CACHE_L2X0 + /* + * Need to make sure the buffers in L2 are drained. + * Performing a sync operation does this. + */ + ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + + /* Wait for background operations to complete. */ +wait_for_l2_to_idle: + ldr r6, [r7, #0x730] + cmp r6, #0x0 + bne wait_for_l2_to_idle + + mov r6, #0x0 + str r6, [r7, #0x730] + + /* + * The second dsb might be needed to keep cache sync (device write) + * ordering with the memory accesses before it. + */ + dsb + isb + + ldr r3, [r7, #PL310_AUX_CTRL] + tst r3, #PL310_AUX_16WAY_BIT + mov r3, #PL310_8WAYS_MASK + orrne r3, #PL310_16WAYS_UPPERMASK + mov r6, #PL310_LOCKDOWN_NBREGS + add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r3, [r5], #PL310_LOCKDOWN_SZREG + str r3, [r5], #PL310_LOCKDOWN_SZREG + subs r6, r6, #1 + bne 1b +#endif + + ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR) + ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR) + ldr r8, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR) + + /* Disable Automatic power savings. */ + ldr r6, [r8, #0x404] + orr r6, r6, #0x01 + str r6, [r8, #0x404] + + /* MMDC0_MDPDC disable power down timer */ + ldr r6, [r8, #0x4] + bic r6, r6, #0xff00 + str r6, [r8, #0x4] + + /* Delay for a while */ + ldr r10, =10 +delay1: + ldr r7, =0 +cont1: + ldr r6, [r8, r7] + add r7, r7, #4 + cmp r7, #16 + bne cont1 + sub r10, r10, #1 + cmp r10, #0 + bgt delay1 + + /* Make the DDR explicitly enter self-refresh. */ + ldr r6, [r8, #0x404] + orr r6, r6, #0x200000 + str r6, [r8, #0x404] + +poll_dvfs_set_1: + ldr r6, [r8, #0x404] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + bne poll_dvfs_set_1 + + /* set SBS step-by-step mode */ + ldr r6, [r8, #0x410] + orr r6, r6, #0x100 + str r6, [r8, #0x410] + + ldr r10, =100000000 + cmp r0, r10 + bgt set_ddr_mu_above_100 + mmdc_clk_lower_100MHz + +set_ddr_mu_above_100: + ldr r10, =24000000 + cmp r0, r10 + beq set_to_24MHz + + ddr_switch_400MHz + + ldr r10,=100000000 + cmp r0, r10 + blt done + mmdc_clk_above_100MHz + + b done + +set_to_24MHz: + mx6sl_switch_to_24MHz + +done: + /* clear DVFS - exit from self refresh mode */ + ldr r6, [r8, #0x404] + bic r6, r6, #0x200000 + str r6, [r8, #0x404] + +poll_dvfs_clear_1: + ldr r6, [r8, #0x404] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + beq poll_dvfs_clear_1 + + /* Enable Automatic power savings. */ + ldr r6, [r8, #0x404] + bic r6, r6, #0x01 + str r6, [r8, #0x404] + + ldr r10, =24000000 + cmp r0, r10 + beq skip_power_down + + /* Enable MMDC power down timer. */ + ldr r6, [r8, #0x4] + orr r6, r6, #0x5500 + str r6, [r8, #0x4] + +skip_power_down: + /* clear SBS - unblock DDR accesses */ + ldr r6, [r8, #0x410] + bic r6, r6, #0x100 + str r6, [r8, #0x410] + +#ifdef CONFIG_CACHE_L2X0 + ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + ldr r3, [r7, #PL310_AUX_CTRL] + tst r3, #PL310_AUX_16WAY_BIT + mov r6, #PL310_LOCKDOWN_NBREGS + mov r3, #0x00 /* 8 ways mask */ + orrne r3, #0x0000 /* 16 ways mask */ + add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r3, [r5], #PL310_LOCKDOWN_SZREG + str r3, [r5], #PL310_LOCKDOWN_SZREG + subs r6, r6, #1 + bne 1b +#endif + + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + pop {r4-r10} + + /* Restore registers */ + mov pc, lr + + /* + * Add ltorg here to ensure that all + * literals are stored here and are + * within the text space. + */ + .ltorg +imx6_lpddr2_freq_change_end: From 42528edf3f964d961ad9fa858144256afaed1f6f Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Mon, 29 Apr 2019 10:54:42 +0800 Subject: [PATCH 15/81] arm: imx: Add imx6sl low power idle support Add i.MX6SL low power idle support. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/Makefile | 3 +- arch/arm/mach-imx/common.h | 1 + arch/arm/mach-imx/cpuidle-imx6sl.c | 198 +++++- arch/arm/mach-imx/imx6sl_low_power_idle.S | 776 ++++++++++++++++++++++ 4 files changed, 969 insertions(+), 9 deletions(-) create mode 100644 arch/arm/mach-imx/imx6sl_low_power_idle.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 998b07de577d..93f23104c60e 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -25,7 +25,8 @@ obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o ifeq ($(CONFIG_CPU_IDLE),y) obj-$(CONFIG_SOC_IMX5) += cpuidle-imx5.o obj-$(CONFIG_SOC_IMX6Q) += cpuidle-imx6q.o -obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o +AFLAGS_imx6sl_low_power_idle.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o imx6sl_low_power_idle.o obj-$(CONFIG_SOC_IMX6SLL) += cpuidle-imx6sx.o imx6sx_low_power_idle.o obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 19a3c0243b77..811b2f34042b 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -141,6 +141,7 @@ void imx_anatop_post_resume(void); int imx6_set_lpm(enum mxc_cpu_pwr_mode mode); void imx6_set_int_mem_clk_lpm(bool enable); void imx6sl_set_wait_clk(bool enter); +void imx6sl_low_power_idle(void); void imx6sx_low_power_idle(void); void imx7d_low_power_idle(void); #ifdef CONFIG_HAVE_IMX_MMDC diff --git a/arch/arm/mach-imx/cpuidle-imx6sl.c b/arch/arm/mach-imx/cpuidle-imx6sl.c index 4521e5352bf6..e54acc2ac8a1 100644 --- a/arch/arm/mach-imx/cpuidle-imx6sl.c +++ b/arch/arm/mach-imx/cpuidle-imx6sl.c @@ -1,26 +1,104 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. + * Copyright 2017-2018 NXP. */ +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include + +#include #include "common.h" #include "cpuidle.h" +#include "hardware.h" + +#define MAX_MMDC_IO_NUM 19 + +static void __iomem *wfi_iram_base; +extern unsigned long iram_tlb_base_addr; + +#ifdef CONFIG_CPU_FREQ +extern unsigned long mx6sl_lpm_wfi_start asm("mx6sl_lpm_wfi_start"); +extern unsigned long mx6sl_lpm_wfi_end asm("mx6sl_lpm_wfi_end"); +#endif + +struct imx6_cpuidle_pm_info { + u32 pm_info_size; /* Size of pm_info */ + u32 ttbr; + void __iomem *mmdc_base; + void __iomem *iomuxc_base; + void __iomem *ccm_base; + void __iomem *l2_base; + void __iomem *anatop_base; + u32 mmdc_io_num; /*Number of MMDC IOs which need saved/restored. */ + u32 mmdc_io_val[MAX_MMDC_IO_NUM][2]; /* To save offset and value */ +} __aligned(8); + +static const u32 imx6sl_mmdc_io_offset[] __initconst = { + 0x30c, 0x310, 0x314, 0x318, /* DQM0 ~ DQM3 */ + 0x5c4, 0x5cc, 0x5d4, 0x5d8, /* GPR_B0DS ~ GPR_B3DS */ + 0x300, 0x31c, 0x338, 0x5ac, /*CAS, RAS, SDCLK_0, GPR_ADDS */ + 0x33c, 0x340, 0x5b0, 0x5c0, /*SODT0, SODT1, ,MODE_CTL, MODE */ + 0x330, 0x334, 0x320, /*SDCKE0, SDCK1, RESET */ +}; + +static struct regulator *vbus_ldo; +static struct regulator_dev *ldo2p5_dummy_regulator_rdev; +static struct regulator_init_data ldo2p5_dummy_initdata = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, +}; +static int ldo2p5_dummy_enable; + +static void (*imx6sl_wfi_in_iram_fn)(void __iomem *iram_vbase, + int audio_mode, bool vbus_ldo); + +#define MX6SL_POWERDWN_IDLE_PARAM \ + ((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) static int imx6sl_enter_wait(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) { + int mode = get_bus_freq_mode(); + imx6_set_lpm(WAIT_UNCLOCKED); - /* - * Software workaround for ERR005311, see function - * description for details. - */ - imx6sl_set_wait_clk(true); - cpu_do_idle(); - imx6sl_set_wait_clk(false); + + if ((mode == BUS_FREQ_AUDIO) || (mode == BUS_FREQ_ULTRA_LOW)) { + /* + * bit 2 used for low power mode; + * bit 1 used for the ldo2p5_dummmy enable + */ + if (psci_ops.cpu_suspend) { + psci_ops.cpu_suspend((MX6SL_POWERDWN_IDLE_PARAM | ((mode == BUS_FREQ_AUDIO ? 1 : 0) << 2) | + (ldo2p5_dummy_enable ? 1 : 0) << 1), __pa(cpu_resume)); + } else { + imx6sl_wfi_in_iram_fn(wfi_iram_base, (mode == BUS_FREQ_AUDIO) ? 1 : 0, + ldo2p5_dummy_enable); + } + } else { + /* + * Software workaround for ERR005311, see function + * description for details. + */ + imx6sl_set_wait_clk(true); + cpu_do_idle(); + imx6sl_set_wait_clk(false); + } imx6_set_lpm(WAIT_CLOCKED); return index; @@ -48,5 +126,109 @@ static struct cpuidle_driver imx6sl_cpuidle_driver = { int __init imx6sl_cpuidle_init(void) { + +#ifdef CONFIG_CPU_FREQ + struct imx6_cpuidle_pm_info *pm_info; + int i; + const u32 *mmdc_offset_array; + u32 wfi_code_size; + + vbus_ldo = regulator_get(NULL, "ldo2p5-dummy"); + if (IS_ERR(vbus_ldo)) + vbus_ldo = NULL; + + wfi_iram_base = (void *)(iram_tlb_base_addr + MX6_CPUIDLE_IRAM_ADDR_OFFSET); + + /* Make sure wif_iram_base is 8 byte aligned. */ + if ((uintptr_t)(wfi_iram_base) & (FNCPY_ALIGN - 1)) + wfi_iram_base += FNCPY_ALIGN - ((uintptr_t)wfi_iram_base % (FNCPY_ALIGN)); + + pm_info = wfi_iram_base; + pm_info->pm_info_size = sizeof(*pm_info); + pm_info->mmdc_io_num = ARRAY_SIZE(imx6sl_mmdc_io_offset); + mmdc_offset_array = imx6sl_mmdc_io_offset; + pm_info->mmdc_base = (void __iomem *)IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR); + pm_info->ccm_base = (void __iomem *)IMX_IO_P2V(MX6Q_CCM_BASE_ADDR); + pm_info->anatop_base = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR); + pm_info->iomuxc_base = (void __iomem *)IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR); + pm_info->l2_base = (void __iomem *)IMX_IO_P2V(MX6Q_L2_BASE_ADDR); + + /* Only save mmdc io offset, settings will be saved in asm code */ + for (i = 0; i < pm_info->mmdc_io_num; i++) + pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i]; + + /* calculate the wfi code size */ + wfi_code_size = (&mx6sl_lpm_wfi_end -&mx6sl_lpm_wfi_start) *4; + + imx6sl_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*pm_info), + &imx6sl_low_power_idle, wfi_code_size); +#endif + return cpuidle_register(&imx6sl_cpuidle_driver, NULL); } + +static int imx_ldo2p5_dummy_enable(struct regulator_dev *rdev) +{ + ldo2p5_dummy_enable = 1; + return 0; +} + +static int imx_ldo2p5_dummy_disable(struct regulator_dev *rdev) +{ + ldo2p5_dummy_enable = 0; + return 0; +} + +static int imx_ldo2p5_dummy_is_enable(struct regulator_dev *rdev) +{ + return ldo2p5_dummy_enable; +} + +static struct regulator_ops ldo2p5_dummy_ops = { + .enable = imx_ldo2p5_dummy_enable, + .disable = imx_ldo2p5_dummy_disable, + .is_enabled = imx_ldo2p5_dummy_is_enable, +}; + +static struct regulator_desc ldo2p5_dummy_desc = { + .name = "ldo2p5-dummy", + .id = -1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &ldo2p5_dummy_ops, +}; + +static int ldo2p5_dummy_probe(struct platform_device *pdev) +{ + struct regulator_config config = { }; + int ret; + + config.dev = &pdev->dev; + config.init_data = &ldo2p5_dummy_initdata; + config.of_node = pdev->dev.of_node; + + ldo2p5_dummy_regulator_rdev = regulator_register(&ldo2p5_dummy_desc, &config); + if (IS_ERR(ldo2p5_dummy_regulator_rdev)) { + ret = PTR_ERR(ldo2p5_dummy_regulator_rdev); + dev_err(&pdev->dev, "Failed to register dummy ldo2p5 regulator: %d\n", ret); + return ret; + } + return 0; +} + +static const struct of_device_id imx_ldo2p5_dummy_ids[] = { + { .compatible = "fsl,imx6-dummy-ldo2p5", }, + { }, +}; +MODULE_DEVICE_TABLE(ofm, imx_ldo2p5_dummy_ids); + +static struct platform_driver ldo2p5_dummy_driver = { + .probe = ldo2p5_dummy_probe, + .driver = { + .name = "ldo2p5-dummy", + .owner = THIS_MODULE, + .of_match_table = imx_ldo2p5_dummy_ids, + }, +}; + +module_platform_driver(ldo2p5_dummy_driver); diff --git a/arch/arm/mach-imx/imx6sl_low_power_idle.S b/arch/arm/mach-imx/imx6sl_low_power_idle.S new file mode 100644 index 000000000000..978f8d1cc234 --- /dev/null +++ b/arch/arm/mach-imx/imx6sl_low_power_idle.S @@ -0,0 +1,776 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the license, or + * (at your option) any later version. + * + * This program is distributed in teh hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#define PM_INFO_PM_INFO_SIZE_OFFSET 0x0 +#define PM_INFO_TTBR_OFFSET 0x4 +#define PM_INFO_MMDC_V_OFFSET 0x8 +#define PM_INFO_IOMUXC_V_OFFSET 0xc +#define PM_INFO_CCM_V_OFFSET 0x10 +#define PM_INFO_L2_V_OFFSET 0x14 +#define PM_INFO_ANATOP_V_OFFSET 0x18 +#define PM_INFO_IO_NUM_OFFSET 0x1c +#define PM_INFO_IO_VAL_OFFSET 0x20 + +#define MX6Q_MMDC_MAPSR 0x404 +#define MX6Q_MMDC_MPDGCTRL0 0x83c + +.global mx6sl_lpm_wfi_start +.global mx6sl_lpm_wfi_end + + .macro pll_do_wait_lock +1: + ldr r7, [r10, r8] + ands r7, #0x80000000 + beq 1b + + .endm + + .macro ccm_do_wait +2: + ldr r7, [r10, #0x48] + cmp r7, #0x0 + bne 2b + + .endm + + .macro ccm_enter_idle + + ldr r10, [r0, #PM_INFO_CCM_V_OFFSET] + /* + * if in audio_bus_freq_mode, skip to + * audio_mode low power setting. + */ + cmp r1, #0x1 + beq audio_mode + /* + * Now set DDR rate to 1MHz. + * DDR is from bypassed PLL2 on periph2_clk2 path. + * Set the periph2_clk2_podf to divide by 8. + */ + ldr r6, [r10, #0x14] + orr r6, r6, #0x07 + str r6, [r10, #0x14] + + /* Now set MMDC PODF to divide by 3. */ + ldr r6, [r10, #0x14] + bic r6, r6, #0x38 + orr r6, r6, #0x10 + str r6, [r10, #0x14] + + ccm_do_wait + + /* Set the AHB to 3MHz. AXI to 3MHz. */ + ldr r6, [r10, #0x14] + /*r12 stores the origin AHB podf value */ + mov r12, r6 + orr r6, r6, #0x1c00 + orr r6, r6, #0x70000 + str r6, [r10, #0x14] + + ccm_do_wait + + /* Now set ARM to 24MHz. + * Move ARM to be sourced from step_clk + * after setting step_clk to 24MHz. + */ + ldr r6, [r10, #0x0c] + bic r6, r6, #0x100 + str r6, [r10, #0xc] + /*Now pll1_sw_clk to step_clk */ + ldr r6, [r10, #0x0c] + orr r6, r6, #0x4 + str r6, [r10, #0x0c] + + /* Bypass PLL1 and power it down */ + ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET] + ldr r6, =(1 << 16) + orr r6, r6, #0x1000 + str r6, [r10, #0x04] + + /* + * Set the ARM PODF to divide by 8. + * IPG is at 1.5MHz here, we need ARM to + * run at the 12:5 ratio (WAIT mode issue). + */ + ldr r10, [r0, #PM_INFO_CCM_V_OFFSET] + ldr r11, [r10, #0x10] + ldr r6, =0x07 + str r6, [r10, #0x10] + + ccm_do_wait + + b ccm_idle_done + +audio_mode: + /* + * MMDC is sourced from pll2_200M. + * Set the mmdc_podf to div by 8 + */ + ldr r10, [r0, #PM_INFO_CCM_V_OFFSET] + ldr r6, [r10, #0x14] + orr r6, r6, #0x38 + str r6, [r10, #0x14] + + ccm_do_wait + + /* + * ARM is sourced from pll2_pfd2_400M here. + * switch ARM to bypassed PLL1 + */ + ldr r10, [r0, #PM_INFO_CCM_V_OFFSET] + ldr r6, [r10, #0x0c] + bic r6, r6, #0x4 + str r6, [r10, #0xc] + + /* + * set the arm_podf to divide by 3 + * as IPG is at 4MHz, we cannot run + * arm clk above 9.6MHz when system + * enter WAIT mode + */ + ldr r11, [r10, #0x10] + ldr r6, =0x2 + str r6, [r10, #0x10] + + ccm_do_wait + +ccm_idle_done: + + .endm + + .macro ccm_exit_idle + + /* + * If in audio_bus_freq_mode, skip to + * audio_mode ccm restore. + */ + cmp r1, #0x1 + beq audio_ccm_restore + + ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET] + /* Power up PLL1 and un-bypass it. */ + ldr r6, =(1 << 12) + str r6, [r10, #0x08] + + /* Wait for PLL1 to relock */ + ldr r8, =0x0 + pll_do_wait_lock + + ldr r6, =(1 << 16) + str r6, [r10, #0x08] + + ldr r10, [r0, #PM_INFO_CCM_V_OFFSET] + /* Set PLL1_sw_clk back to PLL1 */ + ldr r6, [r10, #0x0c] + bic r6, r6, #0x4 + str r6, [r10, #0x0c] + + /* Restore AHB/AXI back */ + str r12, [r10, #0x14] + + ccm_do_wait + + /* restore mmdc back to 24MHz*/ + ldr r6, [r10, #0x14] + bic r6, r6, #0x3f + str r6, [r10, #0x14] + + ccm_do_wait + b ccm_exit_done + +audio_ccm_restore: + /* move arm clk back to pll2_pfd2_400M */ + ldr r6, [r10, #0xc] + orr r6, r6, #0x4 + str r6, [r10, #0xc] + + /* restore mmdc podf */ + ldr r10, [r0, #PM_INFO_CCM_V_OFFSET] + ldr r6, [r10, #0x14] + bic r6, r6, #0x38 + orr r6, #0x8 + str r6, [r10, #0x14] + + ccm_do_wait + +ccm_exit_done: + + .endm + + .macro check_pll_state + + ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET] + /* + * Check whether any PLL is enabled, as only when + * there is no PLLs enabled, 2p5 can be off and + * only enable the weak one. PLL1 will be powered + * down late, so no need to check PLL1 state. + */ + + /* sys PLL2 */ + ldr r6, [r10, #0x30] + ands r6, r6, #(1 << 31) + bne 1f + + /* usb PLL3 */ + ldr r6, [r10, #0x10] + ands r6, r6, #(1 << 31) + bne 1f + + /* audio PLL4 */ + ldr r6, [r10, #0x70] + ands r6, r6, #(1 << 31) + bne 1f + + /* video PLL5 */ + ldr r6, [r10, #0xa0] + ands r6, r6, #(1 << 31) + bne 1f + + /* enet PLL6 */ + ldr r6, [r10, #0xe0] + ands r6, r6, #(1 << 31) + bne 1f + + /* usb host PLL7 */ + ldr r6, [r10, #0x20] + ands r6, r6, #(1 << 31) + bne 1f + + ldr r4, =0x1 + b check_done +1: + ldr r4, =0x0 + +check_done: + .endm + + .macro anatop_enter_idle + + ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET] + cmp r4, #0x0 + beq anatop_enter_done + + /* Disable 1p1 brown out. */ + ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET] + ldr r6, [r10, #0x110] + bic r6, r6, #0x2 + str r6, [r10, #0x110] + /* + * Set the OSC bias current to -37.5% + * to drop the power on VDDHIGH. + */ + ldr r6, [r10, #0x150] + orr r6, r6, #0xc000 + str r6, [r10, #0x150] + + /* + * if the usb VBUS wakeup is enabled, skip + * disable main 2p5. + */ + cmp r2, #0x1 + beq anatop_enter_done + + /* Enable the week 2p5 */ + ldr r6, [r10, #0x130] + orr r6, r6, #0x40000 + str r6, [r10, #0x130] + + /* Disable main 2p5. */ + ldr r6, [r10, #0x130] + bic r6, r6, #0x1 + str r6, [r10, #0x130] + + /* + * Cannot diable regular bandgap + * in LDO-enable mode. The bandgap + * is required for ARM-LDO to regulate + * the voltage. + */ + ldr r6, [r10, #0x140] + and r6, r6, #0x1f + cmp r6, #0x1f + bne anatop_enter_done + + /* Enable low power bandgap */ + ldr r6, [r10, #0x260] + orr r6, r6, #0x20 + str r6, [r10, #0x260] + + /* + * Turn off the bias current + * from the regular bandgap. + */ + ldr r6, [r10, #0x260] + orr r6, r6, #0x80 + str r6, [r10, #0x260] + + /* + * Clear the REFTTOP+SELFBIASOFF, + * self_bais circuit of the band gap. + * Per RM, should be cleared when + * band gap is powered down. + */ + ldr r6, [r10, #0x150] + bic r6, r6, #0x8 + str r6, [r10, #0x150] + + /* Power down the regular bandgap */ + ldr r6, [r10, #0x150] + orr r6, r6, #0x1 + str r6, [r10, #0x150] +anatop_enter_done: + + .endm + + .macro anatop_exit_idle + + ldr r10, [r0, #PM_INFO_ANATOP_V_OFFSET] + cmp r4, #0x0 + beq skip_anatop_restore + + cmp r2, #0x1 + beq ldo2p5_not_disabled + /* + * Regular bandgap will not be disabled + * in LDO-enabled mode as it is required + * for ARM-LDO to reguulate the voltage. + */ + ldr r6, [r10, #0x140] + and r6, r6, #0x1f + cmp r6, #0x1f + bne skip_bandgap_restore + + /* Power up the regular bandgap */ + ldr r6, [r10, #0x150] + bic r6, r6, #0x1 + str r6, [r10, #0x150] + + /* wait for bandgap stable */ +3: + ldr r6, [r10, #0x150] + and r6, r6, #0x80 + cmp r6, #0x80 + bne 3b + + /* now disable bandgap self-bias circuit */ + ldr r6, [r10, #0x150] + orr r6, r6, #0x8 + str r6, [r10, #0x150] + + /* Turn on the bias current + * from the regular bandgap. + */ + ldr r6, [r10, #0x260] + bic r6, r6, #0x80 + str r6, [r10, #0x260] + + /* Disable the low power bandgap */ + ldr r6, [r10, #0x260] + bic r6, r6, #0x20 + str r6, [r10, #0x260] + +skip_bandgap_restore: + /* Enable main 2p5. */ + ldr r6, [r10, #0x130] + orr r6, r6, #0x1 + str r6, [r10, #0x130] + + /* Ensure the 2p5 is up */ +5: + ldr r6, [r10, #0x130] + and r6, r6, #0x20000 + cmp r6, #0x20000 + bne 5b + + /* Disable the weak 2p5 */ + ldr r6, [r10, #0x130] + bic r6, r6, #0x40000 + str r6, [r10, #0x130] + +ldo2p5_not_disabled: + /* + * Set the OSC bias current to max + * value for normal operation. + */ + ldr r6, [r10, #0x150] + bic r6, r6, #0xc000 + str r6, [r10, #0x150] + + /* Enable 1p1 brown out, */ + ldr r6, [r10, #0x110] + orr r6, r6, #0x2 + str r6, [r10, #0x110] + +skip_anatop_restore: + + .endm + + .macro disable_l1_dcache + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + + dsb + isb + + .endm + + .macro mmdc_enter_dvfs_mode + + /* disable automatic power saving. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + /* disable power down timer */ + ldr r7, [r10, #0x04] + bic r7, r7, #0xff00 + str r7, [r10, #0x04] + + /* Make the DDR explicitly enter self-refresh. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] + +poll_dvfs_set: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + beq poll_dvfs_set + + /* set SBS step-by step mode */ + ldr r7, [r10, #0x410] + orr r7, r7, #0x100 + str r7, [r10, #0x410] + + .endm + + .macro resume_mmdc + /* restore MMDC IO */ + ldr r10, [r0, #PM_INFO_IOMUXC_V_OFFSET] + + ldr r6, [r0, #PM_INFO_IO_NUM_OFFSET] + ldr r7, =PM_INFO_IO_VAL_OFFSET + add r7, r7, r0 +6: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 6b + + /* + * Need to reset the FIFO to avoid MMDC lockup + * caused because of floating/changing the + * configuration of many DDR IO pads. + */ + ldr r10, [r0, #PM_INFO_MMDC_V_OFFSET] + /* reset read FIFO, RST_RD_FIFO */ + ldr r7, =MX6Q_MMDC_MPDGCTRL0 + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +7: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 7b + + /* reset FIFO a second time */ + ldr r7, =MX6Q_MMDC_MPDGCTRL0 + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +8: + ldr r6, [r10, r7] + ands r6, r6, #(1 <<31) + bne 8b + + ldr r10, [r0, #PM_INFO_MMDC_V_OFFSET] + /* Let DDR out of self-refresh */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +9: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne 9b + + /* enable power down timer */ + ldr r7, [r10, #0x04] + orr r7, r7, #0x5500 + str r7, [r10, #0x04] + + /* enable DDR auto power saving */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + /* Clear SBS - unblock DDR accesses */ + ldr r7, [r10, #0x410] + bic r7, r7, #0x100 + str r7, [r10, #0x410] + + .endm + + .macro tlb_set_to_ocram + + /* save ttbr */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_TTBR_OFFSET] + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * we need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to the IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is transslated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Disable Branch Prediction, Z bit in SCTLR */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + dsb + isb + + /* store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + /* Read TTBCR and set PD0=1, N=1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* Flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + .endm + + .macro tlb_back_to_ddr + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N=0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + /* Flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0 ,r6, c1, c0, 0 + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + /* Restore ttbr */ + ldr r7, [r0, #PM_INFO_TTBR_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + +.extern iram_tlb_phys_addr + +/* + * imx6sl_low_power_wfi code + * r0: wfi code base address + * r1: audio_bus_freq mode stat + * r2: vbus_ldo status + * r4: used for store the PLLs state + * r11: used for saving the ARM_PODF origin value + * r12: used for saving AHB_PODF origin value + */ + .align 3 +ENTRY(imx6sl_low_power_idle) + +mx6sl_lpm_wfi_start: + push {r4-r12} + + tlb_set_to_ocram + disable_l1_dcache + +#ifdef CONFIG_CACHE_L2X0 + /* sync L2 */ + ldr r10, [r0, #PM_INFO_L2_V_OFFSET] + /* Wait for background operations to complete. */ +wait_for_l2_idle: + ldr r6, [r10, #0x730] + cmp r6, #0x0 + bne wait_for_l2_idle + + mov r6, #0x0 + str r6, [r10, #0x730] + /* disable L2 */ + str r6, [r10, #0x100] + + dsb + isb +#endif + + /* make sure MMDC in self-refresh */ + ldr r10, [r0, #PM_INFO_MMDC_V_OFFSET] + mmdc_enter_dvfs_mode + /* save DDR IO settings and set to LPM mode*/ + ldr r10, [r0, #PM_INFO_IOMUXC_V_OFFSET] + ldr r6, =0x0 + ldr r7, [r0, #PM_INFO_IO_NUM_OFFSET] + ldr r8, =PM_INFO_IO_VAL_OFFSET + add r8, r8, r0 + + /* imx6sl's last 3 IOs need special setting */ + sub r7, r7, #0x3 +save_and_set_mmdc_io_lpm: + ldr r9, [r8], #0x4 + ldr r5, [r10, r9] + str r6, [r10, r9] + str r5, [r8], #0x4 + subs r7, r7, #0x1 + bne save_and_set_mmdc_io_lpm + ldr r6, =0x1000 + ldr r9, [r8], #0x4 + ldr r5, [r10, r9] + str r5, [r8], #0x4 + str r6, [r10, r9] + ldr r9, [r8], #0x4 + ldr r5, [r10, r9] + str r6, [r10, r9] + str r5, [r8], #0x4 + ldr r6, =0x80000 + ldr r9, [r8], #0x4 + ldr r5, [r10, r9] + str r6, [r10, r9] + str r5, [r8], #0x4 + + + /* check the PLLs lock state */ + check_pll_state + + ccm_enter_idle + /* if in audio low power mode, no + * need to do anatop setting. + */ + cmp r1, #0x1 + beq do_wfi + anatop_enter_idle +do_wfi: + wfi + /* + * Add these nops so that the + * prefetcher will not try to get + * any instrutions from DDR. + * The prefetch depth is about 23 + * on A9, so adding 25 nops. + */ + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + /* + * restore the ARM PODF first to speed + * up the restore procedure + */ + ldr r10, [r0, #PM_INFO_CCM_V_OFFSET] + /* Restore arm_clk_podf */ + str r11, [r10, #0x10] + ccm_do_wait + + /* + * if in audio low power mode, skip + * restore the anatop setting. + */ + cmp r1, #0x1 + beq skip_analog_restore + anatop_exit_idle + +skip_analog_restore: + ccm_exit_idle + resume_mmdc + + /* enable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + orr r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + +#ifdef CONFIG_CACHE_L2X0 + ldr r10, [r0, #PM_INFO_L2_V_OFFSET] + mov r7, #0x1 + /* enable L2 */ + str r7, [r10, #0x100] +#endif + tlb_back_to_ddr + + /* Restore register */ + pop {r4 - r12} + mov pc, lr + + /* + * Add ltorg here to ensure that all + * literals are stored here and are + * within the text space. + */ + .ltorg +mx6sl_lpm_wfi_end: From 21066aba62c0a8d7b1defd364566b172d22bdc7f Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Mon, 29 Apr 2019 14:11:46 +0800 Subject: [PATCH 16/81] arm: imx: Add map io for imx6ul Add the map io on i.MX6UL to support dsm, busfreq & low power idle. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/mach-imx6ul.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/mach-imx/mach-imx6ul.c b/arch/arm/mach-imx/mach-imx6ul.c index 311f5e4ff723..d4e8c1690e29 100644 --- a/arch/arm/mach-imx/mach-imx6ul.c +++ b/arch/arm/mach-imx/mach-imx6ul.c @@ -84,6 +84,12 @@ static void __init imx6ul_init_late(void) platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0); } +static void __init imx6ul_map_io(void) +{ + imx6_pm_map_io(); + imx_busfreq_map_io(); +} + static const char * const imx6ul_dt_compat[] __initconst = { "fsl,imx6ul", "fsl,imx6ull", @@ -91,6 +97,7 @@ static const char * const imx6ul_dt_compat[] __initconst = { }; DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)") + .map_io = imx6ul_map_io, .init_irq = imx6ul_init_irq, .init_machine = imx6ul_init_machine, .init_late = imx6ul_init_late, From b202184ae799e049b5199ab402cf55e2e4e4b25a Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Mon, 29 Apr 2019 14:20:34 +0800 Subject: [PATCH 17/81] arm: imx: Add busfreq support for imx6ul Add busfreq support on i.MX6UL. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/busfreq-imx.c | 21 ++++++++++++------ arch/arm/mach-imx/busfreq_ddr3.c | 35 ++++++++++++++++++++++++------ arch/arm/mach-imx/busfreq_lpddr2.c | 2 +- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index f948ca65ffb3..b96b3fda66c9 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -308,11 +308,18 @@ static void exit_lpm_imx6_up(void) * lower ahb/ocram's freq first to avoid too high * freq during parent switch from OSC to pll3. */ - clk_set_rate(ahb_clk, LPAPM_CLK / 3); + if (cpu_is_imx6ul()) + clk_set_rate(ahb_clk, LPAPM_CLK / 4); + else + clk_set_rate(ahb_clk, LPAPM_CLK / 3); clk_set_rate(ocram_clk, LPAPM_CLK / 2); + /* set periph clk to from pll2_bus on i.MX6UL */ + if (cpu_is_imx6ul()) + clk_set_parent(periph_pre_clk, pll2_bus_clk); /* set periph clk to from pll2_400 */ - clk_set_parent(periph_pre_clk, pll2_400_clk); + else + clk_set_parent(periph_pre_clk, pll2_400_clk); clk_set_parent(periph_clk, periph_pre_clk); /* set periph_clk2 to pll3 */ clk_set_parent(periph_clk2_sel_clk, pll3_clk); @@ -629,7 +636,7 @@ static void reduce_bus_freq(void) if (cpu_is_imx7d()) enter_lpm_imx7d(); - else if (cpu_is_imx6sx()) + else if (cpu_is_imx6sx() || cpu_is_imx6ul()) enter_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) enter_lpm_imx6_smp(); @@ -733,7 +740,7 @@ static int set_high_bus_freq(int high_bus_freq) if (cpu_is_imx7d()) exit_lpm_imx7d(); - else if (cpu_is_imx6sx()) + else if (cpu_is_imx6sx() || cpu_is_imx6ul()) exit_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) exit_lpm_imx6_smp(); @@ -1091,7 +1098,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx() || cpu_is_imx6sl()) { + if (cpu_is_imx6sx() || cpu_is_imx6sl() || cpu_is_imx6ul()) { ahb_clk = devm_clk_get(&pdev->dev, "ahb"); ocram_clk = devm_clk_get(&pdev->dev, "ocram"); periph2_clk = devm_clk_get(&pdev->dev, "periph2"); @@ -1109,7 +1116,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx()) { + if (cpu_is_imx6sx() || cpu_is_imx6ul()) { mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); if (IS_ERR(mmdc_clk)) { dev_err(busfreq_dev, @@ -1250,7 +1257,7 @@ static int busfreq_probe(struct platform_device *pdev) } busfreq_func.init = &init_ddrc_ddr_settings; busfreq_func.update = &update_ddr_freq_imx_smp; - } else if (cpu_is_imx6sx()) { + } else if (cpu_is_imx6sx() || cpu_is_imx6ul()) { ddr_type = imx_mmdc_get_ddr_type(); if (ddr_type == IMX_DDR_TYPE_DDR3) { busfreq_func.init = &init_mmdc_ddr3_settings_imx6_up; diff --git a/arch/arm/mach-imx/busfreq_ddr3.c b/arch/arm/mach-imx/busfreq_ddr3.c index 3aa28f9e4853..3d01dd982d51 100644 --- a/arch/arm/mach-imx/busfreq_ddr3.c +++ b/arch/arm/mach-imx/busfreq_ddr3.c @@ -153,6 +153,12 @@ unsigned long iomux_offsets_mx6sx[][2] = { {0x338, 0x0}, {0x33c, 0x0}, }; + +unsigned long iomux_offsets_mx6ul[][2] = { + {0x280, 0x0}, + {0x284, 0x0}, +}; + unsigned long ddr3_dll_mx6q[][2] = { {0x0c, 0x0}, {0x10, 0x0}, @@ -503,6 +509,8 @@ int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev) if (cpu_is_imx6sx()) node = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-iomuxc"); + else + node = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-iomuxc"); if (!node) { printk(KERN_ERR "failed to find iomuxc device tree data!\n"); return -EINVAL; @@ -532,7 +540,10 @@ int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev) + normal_mmdc_settings[i][0]); } - iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6sx); + if (cpu_is_imx6ul()) + iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6ul); + else + iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6sx); ddr_code_size = (&imx6_up_ddr3_freq_change_end -&imx6_up_ddr3_freq_change_start) *4 + sizeof(*imx6_busfreq_info); @@ -555,13 +566,23 @@ int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev) } for (i = 0; i < iomux_settings_size; i++) { - iomux_offsets_mx6sx[i][1] = + if (cpu_is_imx6ul()) { + iomux_offsets_mx6ul[i][1] = readl_relaxed(iomux_base + - iomux_offsets_mx6sx[i][0]); - iram_iomux_settings[i + 1][0] = - iomux_offsets_mx6sx[i][0]; - iram_iomux_settings[i + 1][1] = - iomux_offsets_mx6sx[i][1]; + iomux_offsets_mx6ul[i][0]); + iram_iomux_settings[i + 1][0] = + iomux_offsets_mx6ul[i][0]; + iram_iomux_settings[i + 1][1] = + iomux_offsets_mx6ul[i][1]; + } else { + iomux_offsets_mx6sx[i][1] = + readl_relaxed(iomux_base + + iomux_offsets_mx6sx[i][0]); + iram_iomux_settings[i + 1][0] = + iomux_offsets_mx6sx[i][0]; + iram_iomux_settings[i + 1][1] = + iomux_offsets_mx6sx[i][1]; + } } curr_ddr_rate = ddr_normal_rate; diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c index 8c7793c01f3a..4793fc980eea 100644 --- a/arch/arm/mach-imx/busfreq_lpddr2.c +++ b/arch/arm/mach-imx/busfreq_lpddr2.c @@ -166,7 +166,7 @@ int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &mx6_lpddr2_freq_change, ddr_code_size); - if (cpu_is_imx6sx()) + if (cpu_is_imx6sx() || cpu_is_imx6ul()) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &imx6_up_lpddr2_freq_change, ddr_code_size); From 9378dc38b8fb064a63cc2f6810b2c8c4604419e7 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Tue, 30 Apr 2019 12:34:39 +0800 Subject: [PATCH 18/81] arm: imx: add low power idle for imx6ul Add the low power idle support for i.MX6UL. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/Makefile | 3 +- arch/arm/mach-imx/common.h | 2 + arch/arm/mach-imx/cpuidle-imx6ul.c | 320 +++++++++ arch/arm/mach-imx/cpuidle.h | 5 + arch/arm/mach-imx/imx6ul_low_power_idle.S | 821 ++++++++++++++++++++++ arch/arm/mach-imx/mach-imx6ul.c | 2 +- 6 files changed, 1151 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-imx/cpuidle-imx6ul.c create mode 100644 arch/arm/mach-imx/imx6ul_low_power_idle.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 93f23104c60e..136768d9b372 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -30,7 +30,8 @@ obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o imx6sl_low_power_idle.o obj-$(CONFIG_SOC_IMX6SLL) += cpuidle-imx6sx.o imx6sx_low_power_idle.o obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a -obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6sx.o +AFLAGS_imx6ul_low_power_idle.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6ul.o imx6ul_low_power_idle.o obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o AFLAGS_imx7d_low_power_idle.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX7D_CA7) += cpuidle-imx7d.o imx7d_low_power_idle.o diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 811b2f34042b..ce52b92856d8 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -130,6 +130,7 @@ void imx_mu_set_m4_run_mode(void); void imx_src_init(void); void imx_gpc_pre_suspend(bool arm_power_off); void imx_gpc_post_resume(void); +void imx_gpc_switch_pupscr_clk(bool flag); void imx_gpc_mask_all(void); void imx_gpc_restore_all(void); void imx_gpc_hwirq_mask(unsigned int hwirq); @@ -143,6 +144,7 @@ void imx6_set_int_mem_clk_lpm(bool enable); void imx6sl_set_wait_clk(bool enter); void imx6sl_low_power_idle(void); void imx6sx_low_power_idle(void); +void imx6ul_low_power_idle(void); void imx7d_low_power_idle(void); #ifdef CONFIG_HAVE_IMX_MMDC int imx_mmdc_get_ddr_type(void); diff --git a/arch/arm/mach-imx/cpuidle-imx6ul.c b/arch/arm/mach-imx/cpuidle-imx6ul.c new file mode 100644 index 000000000000..64e14f69fcca --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx6ul.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +#define MAX_MMDC_IO_NUM 14 + +#define PMU_LOW_PWR_CTRL 0x270 +#define XTALOSC24M_OSC_CONFIG0 0x2a0 +#define XTALOSC24M_OSC_CONFIG1 0x2b0 +#define XTALOSC24M_OSC_CONFIG2 0x2c0 +#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT 24 +#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK 0xf +#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT 16 +#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK 0xf +#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT 12 +#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT 4 +#define XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT 1 +#define XTALOSC24M_OSC_CONFIG0_START_SHIFT 0 +#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT 20 +#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT 0 +#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK 0xfff +#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT 0 + +extern unsigned long iram_tlb_phys_addr; +static void __iomem *wfi_iram_base; + +#ifdef CONFIG_CPU_FREQ +static void __iomem *wfi_iram_base_phys; +extern unsigned long mx6ul_lpm_wfi_start asm("mx6ul_lpm_wfi_start"); +extern unsigned long mx6ul_lpm_wfi_end asm("mx6ul_lpm_wfi_end"); +extern unsigned long mx6ull_lpm_wfi_start asm("mx6ull_lpm_wfi_start"); +extern unsigned long mx6ull_lpm_wfi_end asm("mx6ull_lpm_wfi_end"); +#endif + +struct imx6_pm_base { + phys_addr_t pbase; + void __iomem *vbase; +}; + +struct imx6_cpuidle_pm_info { + phys_addr_t pbase; /* The physical address of pm_info. */ + phys_addr_t resume_addr; /* The physical resume address for asm code */ + u32 pm_info_size; /* Size of pm_info. */ + u32 ttbr; + struct imx6_pm_base mmdc_base; + struct imx6_pm_base iomuxc_base; + struct imx6_pm_base ccm_base; + struct imx6_pm_base gpc_base; + struct imx6_pm_base anatop_base; + struct imx6_pm_base src_base; + u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */ + u32 mmdc_io_val[MAX_MMDC_IO_NUM][2]; /* To save offset and value */ +} __aligned(8); + +static const u32 imx6ul_mmdc_io_offset[] __initconst = { + 0x244, 0x248, 0x24c, 0x250, /* DQM0, DQM1, RAS, CAS */ + 0x27c, 0x498, 0x4a4, 0x490, /* SDCLK0, GPR_B0DS-B1DS, GPR_ADDS */ + 0x280, 0x284, 0x260, 0x264, /* SDQS0~1, SODT0, SODT1 */ + 0x494, 0x4b0, /* MODE_CTL, MODE, */ +}; + +static void (*imx6ul_wfi_in_iram_fn)(void __iomem *iram_vbase); + +#define MX6UL_POWERDWN_IDLE_PARAM \ + ((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +static int imx6ul_idle_finish(unsigned long val) +{ + if (psci_ops.cpu_suspend) + psci_ops.cpu_suspend(MX6UL_POWERDWN_IDLE_PARAM, + __pa(cpu_resume)); + else + imx6ul_wfi_in_iram_fn(wfi_iram_base); + + return 0; +} + +static int imx6ul_enter_wait(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int mode = get_bus_freq_mode(); + + imx6_set_lpm(WAIT_UNCLOCKED); + if ((index == 1) || ((mode != BUS_FREQ_LOW) && index == 2)) { + cpu_do_idle(); + index = 1; + } else { + /* + * i.MX6UL TO1.0 ARM power up uses IPG/2048 as clock source, + * from TO1.1, PGC_CPU_PUPSCR bit [5] is re-defined to switch + * clock to IPG/32, enable this bit to speed up the ARM power + * up process in low power idle case. + */ + if (cpu_is_imx6ul() && imx_get_soc_revision() > + IMX_CHIP_REVISION_1_0) + imx_gpc_switch_pupscr_clk(true); + /* Need to notify there is a cpu pm operation. */ + cpu_pm_enter(); + cpu_cluster_pm_enter(); + + cpu_suspend(0, imx6ul_idle_finish); + + cpu_cluster_pm_exit(); + cpu_pm_exit(); + imx6_enable_rbc(false); + + if (cpu_is_imx6ul() && imx_get_soc_revision() > + IMX_CHIP_REVISION_1_0) + imx_gpc_switch_pupscr_clk(false); + } + + imx6_set_lpm(WAIT_CLOCKED); + + return index; +} + +static struct cpuidle_driver imx6ul_cpuidle_driver_v2 = { + .name = "imx6ul_cpuidle", + .owner = THIS_MODULE, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT */ + { + .exit_latency = 50, + .target_residency = 75, + .enter = imx6ul_enter_wait, + .name = "WAIT", + .desc = "Clock off", + }, + /* LOW POWER IDLE */ + { + /* + * RBC 130us + ARM gating 43us + RBC clear 65us + * + PLL2 relock 450us and some margin, here set + * it to 700us. + */ + .exit_latency = 700, + .target_residency = 1000, + .enter = imx6ul_enter_wait, + .name = "LOW-POWER-IDLE", + .desc = "ARM power off", + } + }, + .state_count = 3, + .safe_state_index = 0, +}; + +static struct cpuidle_driver imx6ul_cpuidle_driver = { + .name = "imx6ul_cpuidle", + .owner = THIS_MODULE, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT */ + { + .exit_latency = 50, + .target_residency = 75, + .enter = imx6ul_enter_wait, + .name = "WAIT", + .desc = "Clock off", + }, + /* LOW POWER IDLE */ + { + /* + * RBC 130us + ARM gating 1370us + RBC clear 65us + * + PLL2 relock 450us and some margin, here set + * it to 2100us. + */ + .exit_latency = 2100, + .target_residency = 2500, + .enter = imx6ul_enter_wait, + .name = "LOW-POWER-IDLE", + .desc = "ARM power off", + } + }, + .state_count = 3, + .safe_state_index = 0, +}; + +int __init imx6ul_cpuidle_init(void) +{ + void __iomem *anatop_base = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR); + u32 val; +#ifdef CONFIG_CPU_FREQ + struct imx6_cpuidle_pm_info *cpuidle_pm_info; + int i; + const u32 *mmdc_offset_array; + u32 wfi_code_size; + + wfi_iram_base_phys = (void *)(iram_tlb_phys_addr + MX6_CPUIDLE_IRAM_ADDR_OFFSET); + + /* Make sure wfi_iram_base is 8 byte aligned. */ + if ((uintptr_t)(wfi_iram_base_phys) & (FNCPY_ALIGN - 1)) + wfi_iram_base_phys += FNCPY_ALIGN - ((uintptr_t)wfi_iram_base_phys % (FNCPY_ALIGN)); + + wfi_iram_base = (void *)IMX_IO_P2V((unsigned long) wfi_iram_base_phys); + + cpuidle_pm_info = wfi_iram_base; + cpuidle_pm_info->pbase = (phys_addr_t) wfi_iram_base_phys; + cpuidle_pm_info->pm_info_size = sizeof(*cpuidle_pm_info); + cpuidle_pm_info->resume_addr = virt_to_phys(v7_cpu_resume); + cpuidle_pm_info->mmdc_io_num = ARRAY_SIZE(imx6ul_mmdc_io_offset); + mmdc_offset_array = imx6ul_mmdc_io_offset; + + cpuidle_pm_info->mmdc_base.pbase = MX6Q_MMDC_P0_BASE_ADDR; + cpuidle_pm_info->mmdc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR); + + cpuidle_pm_info->ccm_base.pbase = MX6Q_CCM_BASE_ADDR; + cpuidle_pm_info->ccm_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_CCM_BASE_ADDR); + + cpuidle_pm_info->anatop_base.pbase = MX6Q_ANATOP_BASE_ADDR; + cpuidle_pm_info->anatop_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR); + + cpuidle_pm_info->gpc_base.pbase = MX6Q_GPC_BASE_ADDR; + cpuidle_pm_info->gpc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_GPC_BASE_ADDR); + + cpuidle_pm_info->iomuxc_base.pbase = MX6Q_IOMUXC_BASE_ADDR; + cpuidle_pm_info->iomuxc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR); + + cpuidle_pm_info->src_base.pbase = MX6Q_SRC_BASE_ADDR; + cpuidle_pm_info->src_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_SRC_BASE_ADDR); + + /* Only save mmdc io offset, settings will be saved in asm code */ + for (i = 0; i < cpuidle_pm_info->mmdc_io_num; i++) + cpuidle_pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i]; + + /* calculate the wfi code size */ + wfi_code_size = (&mx6ul_lpm_wfi_end -&mx6ul_lpm_wfi_start) *4; + + imx6ul_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info), + &imx6ul_low_power_idle, wfi_code_size); +#endif + + imx6_set_int_mem_clk_lpm(true); + + /* + * enable RC-OSC here, as it needs at least 4ms for RC-OSC to + * be stable, low power idle flow can NOT endure this big + * latency, so we make RC-OSC self-tuning enabled here. + */ + val = readl_relaxed(anatop_base + PMU_LOW_PWR_CTRL); + val |= 0x1; + writel_relaxed(val, anatop_base + PMU_LOW_PWR_CTRL); + /* + * config RC-OSC freq + * tune_enable = 1;tune_start = 1;hyst_plus = 0;hyst_minus = 0; + * osc_prog = 0xa7; + */ + writel_relaxed( + 0x4 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT | + 0xa7 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT | + 0x1 << XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT | + 0x1 << XTALOSC24M_OSC_CONFIG0_START_SHIFT, + anatop_base + XTALOSC24M_OSC_CONFIG0); + /* set count_trg = 0x2dc */ + writel_relaxed( + 0x40 << XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT | + 0x2dc << XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT, + anatop_base + XTALOSC24M_OSC_CONFIG1); + /* wait 4ms according to hardware design */ + msleep(4); + /* + * now add some hysteresis, hyst_plus=3, hyst_minus=3 + * (the minimum hysteresis that looks good is 2) + */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG0); + val &= ~((XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK << + XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) | + (XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK << + XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT)); + val |= (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) | + (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT); + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG0); + /* set the count_1m_trg = 0x2d7 */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG2); + val &= ~(XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK << + XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT); + val |= 0x2d7 << XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT; + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG2); + /* + * hardware design require to write XTALOSC24M_OSC_CONFIG0 or + * XTALOSC24M_OSC_CONFIG1 to + * make XTALOSC24M_OSC_CONFIG2 write work + */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG1); + + /* ARM power up time is reduced since TO1.1 */ + if (imx_get_soc_revision() > IMX_CHIP_REVISION_1_0) + return cpuidle_register(&imx6ul_cpuidle_driver_v2, NULL); + else + return cpuidle_register(&imx6ul_cpuidle_driver, NULL); +} diff --git a/arch/arm/mach-imx/cpuidle.h b/arch/arm/mach-imx/cpuidle.h index 1d3c93f3f6b9..d011da14d4c6 100644 --- a/arch/arm/mach-imx/cpuidle.h +++ b/arch/arm/mach-imx/cpuidle.h @@ -9,6 +9,7 @@ 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 imx6ul_cpuidle_init(void); extern int imx7d_cpuidle_init(void); extern int imx7d_enable_rcosc(void); extern int imx7ulp_cpuidle_init(void); @@ -29,6 +30,10 @@ static inline int imx6sx_cpuidle_init(void) { return 0; } +static inline int imx6ul_cpuidle_init(void) +{ + return 0; +} static inline int imx7d_cpuidle_init(void) { return 0; diff --git a/arch/arm/mach-imx/imx6ul_low_power_idle.S b/arch/arm/mach-imx/imx6ul_low_power_idle.S new file mode 100644 index 000000000000..26bb83da1a7d --- /dev/null +++ b/arch/arm/mach-imx/imx6ul_low_power_idle.S @@ -0,0 +1,821 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#define PM_INFO_PBASE_OFFSET 0x0 +#define PM_INFO_RESUME_ADDR_OFFSET 0x4 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0x8 +#define PM_INFO_PM_INFO_TTBR_OFFSET 0xc +#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 +#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 +#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18 +#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c +#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20 +#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24 +#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28 +#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c +#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x30 +#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x34 +#define PM_INFO_MX6Q_SRC_P_OFFSET 0x38 +#define PM_INFO_MX6Q_SRC_V_OFFSET 0x3c +#define PM_INFO_MMDC_IO_NUM_OFFSET 0x40 +#define PM_INFO_MMDC_IO_VAL_OFFSET 0x44 + +#define MX6Q_MMDC_MAPSR 0x404 +#define MX6Q_MMDC_MPDGCTRL0 0x83c +#define MX6Q_SRC_GPR1 0x20 +#define MX6Q_SRC_GPR2 0x24 +#define MX6Q_GPC_IMR1 0x08 +#define MX6Q_GPC_IMR2 0x0c +#define MX6Q_GPC_IMR3 0x10 +#define MX6Q_GPC_IMR4 0x14 +#define MX6Q_CCM_CCR 0x0 + +.globl mx6ul_lpm_wfi_start +.globl mx6ul_lpm_wfi_end + + .macro pll_do_wait_lock +1: + ldr r7, [r10, r8] + ands r7, #0x80000000 + beq 1b + + .endm + + .macro ccm_do_wait +2: + ldr r7, [r10, #0x48] + cmp r7, #0x0 + bne 2b + + .endm + + .macro ccm_enter_idle + + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + + /* set ahb to 3MHz */ + ldr r7, [r10, #0x14] + orr r7, r7, #0x1c00 + str r7, [r10, #0x14] + + /* set perclk to 6MHz */ + ldr r7, [r10, #0x1c] + bic r7, r7, #0x3f + orr r7, r7, #0x3 + str r7, [r10, #0x1c] + + /* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */ + ldr r7, [r10, #0x14] + orr r7, r7, #0x2 + orr r7, r7, #(0x7 << 3) + str r7, [r10, #0x14] + + ccm_do_wait + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* bypass PLL1 output to OSC */ + ldr r7, [r10] + orr r7, r7, #(0x1 << 16) + str r7, [r10] + + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + + /* set pll1_sw to from pll1 main */ + ldr r7, [r10, #0xc] + bic r7, r7, #0x4 + str r7, [r10, #0xc] + + /* set step from osc */ + ldr r7, [r10, #0xc] + bic r7, r7, #0x100 + str r7, [r10, #0xc] + + /* set pll1_sw to from step */ + ldr r7, [r10, #0xc] + orr r7, r7, #0x4 + str r7, [r10, #0xc] + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* Disable PLL1 bypass output */ + ldr r7, [r10] + bic r7, r7, #0x12000 + str r7, [r10] + + /* + * disable pll2, suppose when system enter low + * power idle mode, only 396MHz pfd needs pll2, + * now we switch arm clock to OSC, we can disable + * pll2 now, gate pll2_pfd2 first. + */ + ldr r7, [r10, #0x100] + orr r7, #0x800000 + str r7, [r10, #0x100] + + ldr r7, [r10, #0x30] + orr r7, r7, #0x1000 + bic r7, r7, #0x2000 + str r7, [r10, #0x30] + + .endm + + .macro ccm_exit_idle + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* enable pll2 and pll2_pfd2 */ + ldr r7, [r10, #0x30] + bic r7, r7, #0x1000 + orr r7, r7, #0x2000 + str r7, [r10, #0x30] + + ldr r8, =0x30 + pll_do_wait_lock + + ldr r7, [r10, #0x100] + bic r7, #0x800000 + str r7, [r10, #0x100] + + /* enable PLL1 bypass output */ + ldr r7, [r10] + orr r7, r7, #0x12000 + str r7, [r10] + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET] + + /* set perclk back to 24MHz */ + ldr r7, [r10, #0x1c] + bic r7, r7, #0x3f + str r7, [r10, #0x1c] + + /* set mmdc back to 24MHz */ + ldr r7, [r10, #0x14] + bic r7, r7, #0x7 + bic r7, r7, #(0x7 << 3) + str r7, [r10, #0x14] + + /* set ahb div back to 24MHz */ + ldr r7, [r10, #0x14] + bic r7, r7, #0x1c00 + str r7, [r10, #0x14] + + ccm_do_wait + + /* set pll1_sw to from pll1 main */ + ldr r7, [r10, #0xc] + bic r7, r7, #0x4 + str r7, [r10, #0xc] + + /* set step from pll2_pfd2 */ + ldr r7, [r10, #0xc] + orr r7, r7, #0x100 + str r7, [r10, #0xc] + + /* set pll1_sw to from step */ + ldr r7, [r10, #0xc] + orr r7, r7, #0x4 + str r7, [r10, #0xc] + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* Unbypass PLL1 */ + ldr r7, [r10] + bic r7, r7, #(0x1 << 16) + str r7, [r10] + + .endm + + .macro anatop_enter_idle + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* + * check whether any PLL is enabled, as only when + * there is no PLLs enabled, 2P5 and 1P1 can be + * off and only enable weak ones. + */ + + /* arm pll1 */ + ldr r7, [r10, #0] + ands r7, r7, #(1 << 31) + bne 10f + + /* sys pll2 */ + ldr r7, [r10, #0x30] + ands r7, r7, #(1 << 31) + bne 10f + + /* usb pll3 */ + ldr r7, [r10, #0x10] + ands r7, r7, #(1 << 31) + bne 10f + + /* audio pll4 */ + ldr r7, [r10, #0x70] + ands r7, r7, #(1 << 31) + bne 10f + + /* vidio pll5 */ + ldr r7, [r10, #0xa0] + ands r7, r7, #(1 << 31) + bne 10f + + /* enet pll6 */ + ldr r7, [r10, #0xe0] + ands r7, r7, #(1 << 31) + bne 10f + + /* usb host pll7 */ + ldr r7, [r10, #0x20] + ands r7, r7, #(1 << 31) + bne 10f + + /* enable weak 2P5 and turn off regular 2P5 */ + ldr r7, [r10, #0x130] + orr r7, r7, #0x40000 + str r7, [r10, #0x130] + bic r7, r7, #0x1 + str r7, [r10, #0x130] + + /* enable weak 1p1 and turn off regular 1P1 */ + ldr r7, [r10, #0x110] + orr r7, r7, #0x40000 + str r7, [r10, #0x110] + bic r7, r7, #0x1 + str r7, [r10, #0x110] + + /* check whether ARM LDO is bypassed */ + ldr r7, [r10, #0x140] + and r7, r7, #0x1f + cmp r7, #0x1f + bne 10f + + /* low power band gap enable */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x20 + str r7, [r10, #0x270] + + /* turn off the bias current from the regular bandgap */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x80 + str r7, [r10, #0x270] + + /* + * clear the REFTOP_SELFBIASOFF, + * self-bias circuit of the band gap. + * Per RM, should be cleared when + * band gap is powered down. + */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x8 + str r7, [r10, #0x150] + + /* turn off regular bandgap */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x1 + str r7, [r10, #0x150] + + /* switch to RC-OSC */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x10 + str r7, [r10, #0x270] + + /* turn off XTAL-OSC */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x40000000 + str r7, [r10, #0x150] +10: + /* lower OSC current by 37.5% */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x6000 + str r7, [r10, #0x150] + + /* disconnect vdd_high_in and vdd_snvs_in */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x1000 + str r7, [r10, #0x150] + + .endm + + .macro anatop_exit_idle + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* increase OSC current to normal */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x6000 + str r7, [r10, #0x150] + + /* turn on XTAL-OSC and detector */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x40000000 + orr r7, r7, #0x10000 + str r7, [r10, #0x150] + + /* wait for XTAL stable */ +14: + ldr r7, [r10, #0x150] + ands r7, r7, #0x8000 + beq 14b + + /* switch to XTAL-OSC */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x10 + str r7, [r10, #0x270] + + /* turn off XTAL-OSC detector */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x10000 + str r7, [r10, #0x150] +15: + /* check whether we need to enable 2P5/1P1 */ + ldr r7, [r10, #0x110] + ands r7, r7, #0x40000 + beq 11f + + /* check whether ARM LDO is bypassed */ + ldr r7, [r10, #0x140] + and r7, r7, #0x1f + cmp r7, #0x1f + bne 12f + + /* turn on regular bandgap and wait for stable */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x1 + str r7, [r10, #0x150] +13: + ldr r7, [r10, #0x150] + ands r7, #0x80 + beq 13b + + /* + * set the REFTOP_SELFBIASOFF, + * self-bias circuit of the band gap. + */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x8 + str r7, [r10, #0x150] + + /* turn on the bias current from the regular bandgap */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x80 + str r7, [r10, #0x270] + + /* low power band gap disable */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x20 + str r7, [r10, #0x270] +12: + /* enable regular 2P5 and turn off weak 2P5 */ + ldr r7, [r10, #0x130] + orr r7, r7, #0x1 + str r7, [r10, #0x130] + + /* Ensure the 2P5 is up. */ +3: + ldr r7, [r10, #0x130] + ands r7, r7, #0x20000 + beq 3b + ldr r7, [r10, #0x130] + bic r7, r7, #0x40000 + str r7, [r10, #0x130] + + /* enable regular 1p1 and turn off weak 1P1 */ + ldr r7, [r10, #0x110] + orr r7, r7, #0x1 + str r7, [r10, #0x110] +4: + ldr r7, [r10, #0x110] + ands r7, r7, #0x20000 + beq 4b + ldr r7, [r10, #0x110] + bic r7, r7, #0x40000 + str r7, [r10, #0x110] +11: + .endm + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + .endm + + .macro mmdc_enter_dvfs_mode + + /* disable automatic power savings. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + /* disable power down timer */ + ldr r7, [r10, #0x4] + bic r7, r7, #0xff00 + str r7, [r10, #0x4] + + /* make the DDR explicitly enter self-refresh. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +5: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + beq 5b + + .endm + + .macro resume_mmdc + + /* restore MMDC IO */ + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] + + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +6: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 6b + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] + + /* reset read FIFO, RST_RD_FIFO */ + ldr r7, =MX6Q_MMDC_MPDGCTRL0 + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +7: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 7b + + /* reset FIFO a second time */ + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +8: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 8b + + /* let DDR out of self-refresh */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +9: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne 9b + + /* enable power down timer */ + ldr r7, [r10, #0x4] + orr r7, r7, #0x5500 + str r7, [r10, #0x4] + + /* enable DDR auto power saving */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + .endm + + .macro tlb_set_to_ocram + + /* save ttbr */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + .endm + + .macro tlb_back_to_ddr + + /* Restore the TTBCR */ + + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* restore ttbr */ + ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + +.extern iram_tlb_phys_addr + +/* imx6ul_low_power_idle */ + + .align 3 +ENTRY(imx6ul_low_power_idle) +mx6ul_lpm_wfi_start: + push {r4 - r10} + + /* get necessary info from pm_info */ + ldr r1, [r0, #PM_INFO_PBASE_OFFSET] + ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r5, =imx6ul_low_power_idle + ldr r6, =wakeup + sub r6, r6, r5 + add r8, r1, r2 + add r3, r8, r6 + + /* store physical resume addr and pm_info address. */ + ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] + str r3, [r10, #0x20] + str r1, [r10, #0x24] + + /* set ARM power to be gated */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r7, =0x1 + str r7, [r10, #0x2a0] + + disable_l1_dcache + + tlb_set_to_ocram + + /* make sure MMDC in self-refresh */ + ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + mmdc_enter_dvfs_mode + + /* save DDR IO settings */ + ldr r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldr r6, =0x0 + ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET + add r8, r8, r0 +save_and_set_mmdc_io_lpm: + ldr r9, [r8], #0x4 + ldr r5, [r10, r9] + str r6, [r10, r9] + str r5, [r8], #0x4 + subs r7, r7, #0x1 + bne save_and_set_mmdc_io_lpm + + mov r5, #0x0 + ccm_enter_idle + anatop_enter_idle + + /* + * mask all GPC interrupts before + * enabling the RBC counters to + * avoid the counter starting too + * early if an interupt is already + * pending. + */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r4, [r10, #MX6Q_GPC_IMR1] + ldr r5, [r10, #MX6Q_GPC_IMR2] + ldr r6, [r10, #MX6Q_GPC_IMR3] + ldr r7, [r10, #MX6Q_GPC_IMR4] + + ldr r3, =0xffffffff + str r3, [r10, #MX6Q_GPC_IMR1] + str r3, [r10, #MX6Q_GPC_IMR2] + str r3, [r10, #MX6Q_GPC_IMR3] + str r3, [r10, #MX6Q_GPC_IMR4] + + /* + * enable the RBC bypass counter here + * to hold off the interrupts. RBC counter + * = 4 (120us). With this setting, the latency + * from wakeup interrupt to ARM power up + * is ~130uS. + */ + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldr r3, [r10, #MX6Q_CCM_CCR] + bic r3, r3, #(0x3f << 21) + orr r3, r3, #(0x4 << 21) + str r3, [r10, #MX6Q_CCM_CCR] + + /* enable the counter. */ + ldr r3, [r10, #MX6Q_CCM_CCR] + orr r3, r3, #(0x1 << 27) + str r3, [r10, #MX6Q_CCM_CCR] + + /* unmask all the GPC interrupts. */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + str r4, [r10, #MX6Q_GPC_IMR1] + str r5, [r10, #MX6Q_GPC_IMR2] + str r6, [r10, #MX6Q_GPC_IMR3] + str r7, [r10, #MX6Q_GPC_IMR4] + + /* + * now delay for a short while (3usec) + * ARM is at 24MHz at this point + * so a short loop should be enough. + * this delay is required to ensure that + * the RBC counter can start counting in + * case an interrupt is already pending + * or in case an interrupt arrives just + * as ARM is about to assert DSM_request. + */ + ldr r4, =50 +rbc_loop: + subs r4, r4, #0x1 + bne rbc_loop + + wfi + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + mov r5, #0x0 + anatop_exit_idle + ccm_exit_idle + + /* clear ARM power gate setting */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r7, =0x0 + str r7, [r10, #0x2a0] + + resume_mmdc + /* enable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + orr r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + + tlb_back_to_ddr + + /* Restore registers */ + pop {r4 - r10} + mov pc, lr + +wakeup: + /* invalidate L1 I-cache first */ + mov r1, #0x0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 6 + /* enable the Icache and branch prediction */ + mov r1, #0x1800 + mcr p15, 0, r1, c1, c0, 0 + isb + + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + /* clear core0's entry and parameter */ + ldr r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] + mov r7, #0x0 + str r7, [r10, #MX6Q_SRC_GPR1] + str r7, [r10, #MX6Q_SRC_GPR2] + + /* clear ARM power gate setting */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET] + ldr r7, =0x0 + str r7, [r10, #0x2a0] + + mov r5, #0x1 + anatop_exit_idle + ccm_exit_idle + resume_mmdc + + /* Restore registers */ + mov pc, lr + .ltorg +mx6ul_lpm_wfi_end: diff --git a/arch/arm/mach-imx/mach-imx6ul.c b/arch/arm/mach-imx/mach-imx6ul.c index d4e8c1690e29..f7d35b012899 100644 --- a/arch/arm/mach-imx/mach-imx6ul.c +++ b/arch/arm/mach-imx/mach-imx6ul.c @@ -78,7 +78,7 @@ static void __init imx6ul_init_irq(void) static void __init imx6ul_init_late(void) { - imx6sx_cpuidle_init(); + imx6ul_cpuidle_init(); if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ)) platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0); From 1e1c1396e5a96035efde01d0a1cdad67d4794c00 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Tue, 30 Apr 2019 15:17:13 +0800 Subject: [PATCH 19/81] arm: imx: Add busfreq support for imx6ull Add the busfreq node for i.MX6ULL. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/busfreq-imx.c | 54 +++++++++++++++++++++++++----- arch/arm/mach-imx/busfreq_ddr3.c | 4 +-- arch/arm/mach-imx/busfreq_lpddr2.c | 3 +- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index b96b3fda66c9..e7a7c5167c8e 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -184,6 +184,35 @@ int unregister_busfreq_notifier(struct notifier_block *nb) } EXPORT_SYMBOL(unregister_busfreq_notifier); +static struct clk *origin_step_parent; + +/* + * on i.MX6ULL, when entering low bus mode, the ARM core + * can run at 24MHz to support the low power run mode per + * to design team. + */ +static void imx6ull_lower_cpu_rate(bool enter) +{ + if (enter) { + org_arm_rate = clk_get_rate(arm_clk); + } + + clk_set_parent(pll1_bypass_clk, pll1_bypass_src_clk); + clk_set_parent(pll1_sw_clk, pll1_sys_clk); + + if (enter) { + origin_step_parent = clk_get_parent(step_clk); + clk_set_parent(step_clk, osc_clk); + clk_set_parent(pll1_sw_clk, step_clk); + clk_set_rate(arm_clk, LPAPM_CLK); + } else { + clk_set_parent(step_clk, origin_step_parent); + clk_set_parent(pll1_sw_clk, step_clk); + clk_set_rate(arm_clk, org_arm_rate); + clk_set_parent(pll1_bypass_clk, pll1_clk); + } +} + /* * enter_lpm_imx6_up and exit_lpm_imx6_up is used by * i.MX6SX/i.MX6UL for entering and exiting lpm mode. @@ -229,6 +258,9 @@ static void enter_lpm_imx6_up(void) clk_set_rate(mmdc_clk, HIGH_AUDIO_CLK); } + if (cpu_is_imx6ull() && low_bus_freq_mode) + imx6ull_lower_cpu_rate(false); + audio_bus_freq_mode = 1; low_bus_freq_mode = 0; cur_bus_freq_mode = BUS_FREQ_AUDIO; @@ -241,6 +273,9 @@ static void enter_lpm_imx6_up(void) if (audio_bus_freq_mode) clk_disable_unprepare(pll2_400_clk); + if (cpu_is_imx6ull()) + imx6ull_lower_cpu_rate(true); + low_bus_freq_mode = 1; audio_bus_freq_mode = 0; cur_bus_freq_mode = BUS_FREQ_LOW; @@ -302,20 +337,23 @@ static void enter_lpm_imx6_smp(void) static void exit_lpm_imx6_up(void) { + if (cpu_is_imx6ull()&& low_bus_freq_mode) + imx6ull_lower_cpu_rate(false); + clk_prepare_enable(pll2_400_clk); /* * lower ahb/ocram's freq first to avoid too high * freq during parent switch from OSC to pll3. */ - if (cpu_is_imx6ul()) + if (cpu_is_imx6ul() || cpu_is_imx6ull()) clk_set_rate(ahb_clk, LPAPM_CLK / 4); else clk_set_rate(ahb_clk, LPAPM_CLK / 3); clk_set_rate(ocram_clk, LPAPM_CLK / 2); /* set periph clk to from pll2_bus on i.MX6UL */ - if (cpu_is_imx6ul()) + if (cpu_is_imx6ul() || cpu_is_imx6ull()) clk_set_parent(periph_pre_clk, pll2_bus_clk); /* set periph clk to from pll2_400 */ else @@ -636,7 +674,7 @@ static void reduce_bus_freq(void) if (cpu_is_imx7d()) enter_lpm_imx7d(); - else if (cpu_is_imx6sx() || cpu_is_imx6ul()) + else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) enter_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) enter_lpm_imx6_smp(); @@ -740,7 +778,7 @@ static int set_high_bus_freq(int high_bus_freq) if (cpu_is_imx7d()) exit_lpm_imx7d(); - else if (cpu_is_imx6sx() || cpu_is_imx6ul()) + else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) exit_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) exit_lpm_imx6_smp(); @@ -1098,7 +1136,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx() || cpu_is_imx6sl() || cpu_is_imx6ul()) { + if (cpu_is_imx6sx() || cpu_is_imx6sl() || cpu_is_imx6ul() || cpu_is_imx6ull()) { ahb_clk = devm_clk_get(&pdev->dev, "ahb"); ocram_clk = devm_clk_get(&pdev->dev, "ocram"); periph2_clk = devm_clk_get(&pdev->dev, "periph2"); @@ -1116,7 +1154,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx() || cpu_is_imx6ul()) { + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) { mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); if (IS_ERR(mmdc_clk)) { dev_err(busfreq_dev, @@ -1152,7 +1190,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sl()) { + if (cpu_is_imx6sl() || cpu_is_imx6ull()) { arm_clk = devm_clk_get(&pdev->dev, "arm"); step_clk = devm_clk_get(&pdev->dev, "step"); pll1_clk = devm_clk_get(&pdev->dev, "pll1"); @@ -1257,7 +1295,7 @@ static int busfreq_probe(struct platform_device *pdev) } busfreq_func.init = &init_ddrc_ddr_settings; busfreq_func.update = &update_ddr_freq_imx_smp; - } else if (cpu_is_imx6sx() || cpu_is_imx6ul()) { + } else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) { ddr_type = imx_mmdc_get_ddr_type(); if (ddr_type == IMX_DDR_TYPE_DDR3) { busfreq_func.init = &init_mmdc_ddr3_settings_imx6_up; diff --git a/arch/arm/mach-imx/busfreq_ddr3.c b/arch/arm/mach-imx/busfreq_ddr3.c index 3d01dd982d51..24ce49d2d540 100644 --- a/arch/arm/mach-imx/busfreq_ddr3.c +++ b/arch/arm/mach-imx/busfreq_ddr3.c @@ -540,7 +540,7 @@ int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev) + normal_mmdc_settings[i][0]); } - if (cpu_is_imx6ul()) + if (cpu_is_imx6ul() || cpu_is_imx6ull()) iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6ul); else iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6sx); @@ -566,7 +566,7 @@ int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev) } for (i = 0; i < iomux_settings_size; i++) { - if (cpu_is_imx6ul()) { + if (cpu_is_imx6ul() || cpu_is_imx6ull()) { iomux_offsets_mx6ul[i][1] = readl_relaxed(iomux_base + iomux_offsets_mx6ul[i][0]); diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c index 4793fc980eea..fe2ca65ee872 100644 --- a/arch/arm/mach-imx/busfreq_lpddr2.c +++ b/arch/arm/mach-imx/busfreq_lpddr2.c @@ -166,10 +166,11 @@ int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &mx6_lpddr2_freq_change, ddr_code_size); - if (cpu_is_imx6sx() || cpu_is_imx6ul()) + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &imx6_up_lpddr2_freq_change, ddr_code_size); + curr_ddr_rate = ddr_normal_rate; return 0; From 92685e4a28a7e5169a8eb69413cec8b2eb57259c Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Tue, 30 Apr 2019 16:17:04 +0800 Subject: [PATCH 20/81] arm: imx Add busfreq support for imx6sll Add busfreq support for i.MX6SLL. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/Makefile | 3 +- arch/arm/mach-imx/busfreq-imx.c | 26 +- arch/arm/mach-imx/busfreq_lpddr2.c | 5 + arch/arm/mach-imx/lpddr2_freq_imx6sll.S | 460 ++++++++++++++++++++++++ arch/arm/mach-imx/mach-imx6sl.c | 2 - 5 files changed, 482 insertions(+), 14 deletions(-) create mode 100644 arch/arm/mach-imx/lpddr2_freq_imx6sll.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 136768d9b372..929412af264b 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -88,7 +88,7 @@ endif obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o ddr3_freq_imx6.o smp_wfe_imx6.o \ lpddr2_freq_imx6q.o obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o lpddr2_freq_imx6.o -obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o +obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o lpddr2_freq_imx6sll.o obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o ddr3_freq_imx6sx.o smp_wfe_imx6.o lpddr2_freq_imx6sx.o obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o obj-$(CONFIG_SOC_IMX7D_CA7) += mach-imx7d.o pm-imx7.o ddr3_freq_imx7d.o smp_wfe.o \ @@ -105,6 +105,7 @@ AFLAGS_ddr3_freq_imx6.o :=-Wa,-march=armv7-a AFLAGS_lpddr2_freq_imx6.o :=-Wa,-march=armv7-a AFLAGS_lpddr2_freq_imx6q.o :=-Wa,-march=armv7-a AFLAGS_lpddr2_freq_imx6sx.o :=-Wa,-march=armv7-a +AFLAGS_lpddr2_freq_imx6sll.o :=-Wa,-march=armv7-a AFLAGS_ddr3_freq_imx6sx.o :=-Wa,-march=armv7-a ifeq ($(CONFIG_SUSPEND),y) diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index e7a7c5167c8e..2fa0b2dcdc06 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -258,7 +258,7 @@ static void enter_lpm_imx6_up(void) clk_set_rate(mmdc_clk, HIGH_AUDIO_CLK); } - if (cpu_is_imx6ull() && low_bus_freq_mode) + if ((cpu_is_imx6ull() | cpu_is_imx6sll()) && low_bus_freq_mode) imx6ull_lower_cpu_rate(false); audio_bus_freq_mode = 1; @@ -273,7 +273,7 @@ static void enter_lpm_imx6_up(void) if (audio_bus_freq_mode) clk_disable_unprepare(pll2_400_clk); - if (cpu_is_imx6ull()) + if (cpu_is_imx6ull() | cpu_is_imx6sll()) imx6ull_lower_cpu_rate(true); low_bus_freq_mode = 1; @@ -337,7 +337,7 @@ static void enter_lpm_imx6_smp(void) static void exit_lpm_imx6_up(void) { - if (cpu_is_imx6ull()&& low_bus_freq_mode) + if ((cpu_is_imx6ull() | cpu_is_imx6sll()) && low_bus_freq_mode) imx6ull_lower_cpu_rate(false); clk_prepare_enable(pll2_400_clk); @@ -346,14 +346,14 @@ static void exit_lpm_imx6_up(void) * lower ahb/ocram's freq first to avoid too high * freq during parent switch from OSC to pll3. */ - if (cpu_is_imx6ul() || cpu_is_imx6ull()) + if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) clk_set_rate(ahb_clk, LPAPM_CLK / 4); else clk_set_rate(ahb_clk, LPAPM_CLK / 3); clk_set_rate(ocram_clk, LPAPM_CLK / 2); /* set periph clk to from pll2_bus on i.MX6UL */ - if (cpu_is_imx6ul() || cpu_is_imx6ull()) + if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) clk_set_parent(periph_pre_clk, pll2_bus_clk); /* set periph clk to from pll2_400 */ else @@ -674,7 +674,8 @@ static void reduce_bus_freq(void) if (cpu_is_imx7d()) enter_lpm_imx7d(); - else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) + else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6sll()) enter_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) enter_lpm_imx6_smp(); @@ -778,7 +779,8 @@ static int set_high_bus_freq(int high_bus_freq) if (cpu_is_imx7d()) exit_lpm_imx7d(); - else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) + else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6sll()) exit_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) exit_lpm_imx6_smp(); @@ -1136,7 +1138,8 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx() || cpu_is_imx6sl() || cpu_is_imx6ul() || cpu_is_imx6ull()) { + if (cpu_is_imx6sx() || cpu_is_imx6sl() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6sll()) { ahb_clk = devm_clk_get(&pdev->dev, "ahb"); ocram_clk = devm_clk_get(&pdev->dev, "ocram"); periph2_clk = devm_clk_get(&pdev->dev, "periph2"); @@ -1154,7 +1157,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) { + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) { mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); if (IS_ERR(mmdc_clk)) { dev_err(busfreq_dev, @@ -1190,7 +1193,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sl() || cpu_is_imx6ull()) { + if (cpu_is_imx6sl() || cpu_is_imx6ull() cpu_is_imx6sll()) { arm_clk = devm_clk_get(&pdev->dev, "arm"); step_clk = devm_clk_get(&pdev->dev, "step"); pll1_clk = devm_clk_get(&pdev->dev, "pll1"); @@ -1295,7 +1298,8 @@ static int busfreq_probe(struct platform_device *pdev) } busfreq_func.init = &init_ddrc_ddr_settings; busfreq_func.update = &update_ddr_freq_imx_smp; - } else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) { + } else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6sll()) { ddr_type = imx_mmdc_get_ddr_type(); if (ddr_type == IMX_DDR_TYPE_DDR3) { busfreq_func.init = &init_mmdc_ddr3_settings_imx6_up; diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c index fe2ca65ee872..dc6d4ce9b685 100644 --- a/arch/arm/mach-imx/busfreq_lpddr2.c +++ b/arch/arm/mach-imx/busfreq_lpddr2.c @@ -57,6 +57,7 @@ void (*mx6_change_lpddr2_freq)(u32 ddr_freq, int bus_freq_mode) = NULL; extern unsigned int ddr_normal_rate; extern void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode); extern void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode); +extern void imx6sll_lpddr2_freq_change(u32 freq, int bus_freq_mode); extern unsigned long save_ttbr1(void); extern void restore_ttbr1(unsigned long ttbr1); extern void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings); @@ -170,6 +171,10 @@ int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &imx6_up_lpddr2_freq_change, ddr_code_size); + if (cpu_is_imx6sll()) + mx6_change_lpddr2_freq = (void *)fncpy( + (void *)ddr_freq_change_iram_base, + &imx6sll_lpddr2_freq_change, ddr_code_size); curr_ddr_rate = ddr_normal_rate; diff --git a/arch/arm/mach-imx/lpddr2_freq_imx6sll.S b/arch/arm/mach-imx/lpddr2_freq_imx6sll.S new file mode 100644 index 000000000000..c67d9e2b82ef --- /dev/null +++ b/arch/arm/mach-imx/lpddr2_freq_imx6sll.S @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "hardware.h" + +#define CCM_CBCDR 0x14 +#define CCM_CBCMR 0x18 +#define CCM_CSCMR1 0x1c +#define CCM_CDHIPR 0x48 + +#define L2_CACHE_SYNC 0x730 +#define PL310_AUX_CTRL 0x104 +#define PL310_DCACHE_LOCKDOWN_BASE 0x900 +#define PL310_AUX_16WAY_BIT 0x10000 +#define PL310_LOCKDOWN_NBREGS 8 +#define PL310_LOCKDOWN_SZREG 4 +#define PL310_8WAYS_MASK 0x00FF +#define PL310_16WAYS_UPPERMASK 0xFF00 + +#define MMDC0_MDPDC 0x4 +#define MMDC0_MAPSR 0x404 +#define MMDC0_MADPCR0 0x410 + +#define HIGH_BUS_MODE 0x0 + + .macro wait_for_ccm_handshake + +1: + ldr r8, [r2, #CCM_CDHIPR] + cmp r8, #0 + bne 1b + + .endm + + .macro switch_to_24MHz + + /* periph2_clk2 sel to OSC_CLK */ + ldr r8, [r2, #CCM_CBCMR] + orr r8, r8, #(1 << 20) + str r8, [r2, #CCM_CBCMR] + + /* periph2_clk2_podf to 0 */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #0x7 + str r8, [r2, #CCM_CBCDR] + + /* periph2_clk sel to periph2_clk2 */ + ldr r8, [r2, #CCM_CBCDR] + orr r8, r8, #(0x1 << 26) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* fabric_mmdc_podf to 0 */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(0x7 << 3) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro switch_to_100MHz + + /* check whether periph2_clk is from top path */ + ldr r8, [r2, #CCM_CBCDR] + ands r8, #(1 << 26) + beq skip_periph2_clk2_switch_100m + + /* now switch periph2_clk back. */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(1 << 26) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* + * on i.MX6SLL, pre_periph2_clk will be always from + * pll2_pfd2, so no need to set pre_periph2_clk + * parent, just set the mmdc divider directly. + */ +skip_periph2_clk2_switch_100m: + + /* fabric_mmdc_podf to 3 so that mmdc is 400 / 4 = 100MHz */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(0x7 << 3) + orr r8, r8, #(0x3 << 3) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro switch_to_400MHz + + /* check whether periph2_clk is from top path */ + ldr r8, [r2, #CCM_CBCDR] + ands r8, #(1 << 26) + beq skip_periph2_clk2_switch_400m + + /* now switch periph2_clk back. */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(1 << 26) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + /* + * on i.MX6SLL, pre_periph2_clk will be always from + * pll2_pfd2, so no need to set pre_periph2_clk + * parent, just set the mmdc divider directly. + */ +skip_periph2_clk2_switch_400m: + + /* fabric_mmdc_podf to 0 */ + ldr r8, [r2, #CCM_CBCDR] + bic r8, r8, #(0x7 << 3) + str r8, [r2, #CCM_CBCDR] + + wait_for_ccm_handshake + + .endm + + .macro mmdc_clk_lower_100MHz + /* if MMDC is not in 400MHz mode, skip double mu count */ + cmp r1, #HIGH_BUS_MODE + bne 1f + + /* + * Prior to reducing the DDR frequency (at 528/400 MHz), + * read the Measure unit count bits (MU_UNIT_DEL_NUM) + */ + ldr r8, =0x8B8 + ldr r6, [r5, r8] + /* Original MU unit count */ + mov r6, r6, LSR #16 + ldr r4, =0x3FF + and r6, r6, r4 + /* Original MU unit count * 2 */ + mov r7, r6, LSL #1 + /* + * Bypass the automatic measure unit when below 100 MHz + * by setting the Measure unit bypass enable bit (MU_BYP_EN) + */ + ldr r6, [r5, r8] + orr r6, r6, #0x400 + str r6, [r5, r8] + /* + * Double the measure count value read in step 1 and program it in the + * measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit + * Register for the reduced frequency operation below 100 MHz + */ + ldr r6, [r5, r8] + ldr r4, =0x3FF + bic r6, r6, r4 + orr r6, r6, r7 + str r6, [r5, r8] + + /* For freq lower than 100MHz, need to set RALAT to 2 */ + ldr r6, [r5, #0x18] + bic r6, r6, #(0x7 << 6) + orr r6, r6, #(0x2 << 6) + str r6, [r5, #0x18] +1: + .endm + + .macro mmdc_clk_above_100MHz + + /* Make sure that the PHY measurement unit is NOT in bypass mode */ + ldr r8, =0x8B8 + ldr r6, [r5, r8] + bic r6, r6, #0x400 + str r6, [r5, r8] + /* Now perform a Force Measurement. */ + ldr r6, [r5, r8] + orr r6, r6, #0x800 + str r6, [r5, r8] + /* Wait for FRC_MSR to clear. */ +force_measure1: + ldr r6, [r5, r8] + and r6, r6, #0x800 + cmp r6, #0x0 + bne force_measure1 + + /* For freq higher than 100MHz, need to set RALAT to 5 */ + ldr r6, [r5, #0x18] + bic r6, r6, #(0x7 << 6) + orr r6, r6, #(0x5 << 6) + str r6, [r5, #0x18] + + .endm + + .align 3 +/* + * Below code can be used by i.MX6SLL when changing the + * frequency of MMDC. the MMDC is the same on these two SOCs. + */ +ENTRY(imx6sll_lpddr2_freq_change) + push {r2 - r8} + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Disable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + +#ifdef CONFIG_CACHE_L2X0 + /* + * Need to make sure the buffers in L2 are drained. + * Performing a sync operation does this. + */ + ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + mov r6, #0x0 + str r6, [r7, #L2_CACHE_SYNC] + + /* + * The second dsb might be needed to keep cache sync (device write) + * ordering with the memory accesses before it. + */ + dsb + isb + + ldr r3, [r7, #PL310_AUX_CTRL] + tst r3, #PL310_AUX_16WAY_BIT + mov r3, #PL310_8WAYS_MASK + orrne r3, #PL310_16WAYS_UPPERMASK + mov r6, #PL310_LOCKDOWN_NBREGS + add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r3, [r5], #PL310_LOCKDOWN_SZREG + str r3, [r5], #PL310_LOCKDOWN_SZREG + subs r6, r6, #1 + bne 1b +#endif + + ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR) + ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR) + ldr r5, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR) + + /* Disable Automatic power savings. */ + ldr r6, [r5, #MMDC0_MAPSR] + orr r6, r6, #0x1 + str r6, [r5, #MMDC0_MAPSR] + + /* Delay for a while */ + ldr r8, =10 +delay: + ldr r7, =0 +cont: + ldr r6, [r5, r7] + add r7, r7, #4 + cmp r7, #16 + bne cont + sub r8, r8, #1 + cmp r8, #0 + bgt delay + + /* Make the DDR explicitly enter self-refresh. */ + ldr r6, [r5, #MMDC0_MAPSR] + orr r6, r6, #0x200000 + str r6, [r5, #MMDC0_MAPSR] + +poll_dvfs_set_1: + ldr r6, [r5, #MMDC0_MAPSR] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + bne poll_dvfs_set_1 + + /* set SBS step-by-step mode */ + ldr r6, [r5, #MMDC0_MADPCR0] + orr r6, r6, #0x100 + str r6, [r5, #MMDC0_MADPCR0] + + ldr r6, =100000000 + cmp r0, r6 + bgt set_ddr_mu_above_100 + mmdc_clk_lower_100MHz + +set_ddr_mu_above_100: + ldr r6, =24000000 + cmp r0, r6 + beq set_to_24MHz + + ldr r6, =100000000 + cmp r0, r6 + beq set_to_100MHz + + switch_to_400MHz + + mmdc_clk_above_100MHz + + b done + +set_to_24MHz: + switch_to_24MHz + b done +set_to_100MHz: + switch_to_100MHz +done: + /* clear DVFS - exit from self refresh mode */ + ldr r6, [r5, #MMDC0_MAPSR] + bic r6, r6, #0x200000 + str r6, [r5, #MMDC0_MAPSR] + +poll_dvfs_clear_1: + ldr r6, [r5, #MMDC0_MAPSR] + and r6, r6, #0x2000000 + cmp r6, #0x2000000 + beq poll_dvfs_clear_1 + + /* Enable Automatic power savings. */ + ldr r6, [r5, #MMDC0_MAPSR] + bic r6, r6, #0x1 + str r6, [r5, #MMDC0_MAPSR] + + /* clear SBS - unblock DDR accesses */ + ldr r6, [r5, #MMDC0_MADPCR0] + bic r6, r6, #0x100 + str r6, [r5, #MMDC0_MADPCR0] + + ldr r6, =0xa0000000 + str r6, [r5, #0x83c] + + +#ifdef CONFIG_CACHE_L2X0 + ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR) + ldr r3, [r7, #PL310_AUX_CTRL] + tst r3, #PL310_AUX_16WAY_BIT + mov r6, #PL310_LOCKDOWN_NBREGS + mov r3, #0x00 /* 8 ways mask */ + orrne r3, #0x0000 /* 16 ways mask */ + add r5, r7, #PL310_DCACHE_LOCKDOWN_BASE +1: /* lock Dcache and Icache */ + str r3, [r5], #PL310_LOCKDOWN_SZREG + str r3, [r5], #PL310_LOCKDOWN_SZREG + subs r6, r6, #1 + bne 1b +#endif + + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + /* Restore the TTBCR */ + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + /* Restore registers */ + pop {r2 - r8} + mov pc, lr diff --git a/arch/arm/mach-imx/mach-imx6sl.c b/arch/arm/mach-imx/mach-imx6sl.c index c4d6a1ec069a..a89500d2f86f 100644 --- a/arch/arm/mach-imx/mach-imx6sl.c +++ b/arch/arm/mach-imx/mach-imx6sl.c @@ -40,8 +40,6 @@ static void __init imx6sl_init_late(void) if (IS_ENABLED(CONFIG_SOC_IMX6SL) && cpu_is_imx6sl()) imx6sl_cpuidle_init(); - else if (IS_ENABLED(CONFIG_SOC_IMX6SLL)) - imx6sx_cpuidle_init(); } static void __init imx6sl_init_machine(void) From 9d8cacc99754ae162f89b6506f1a92053a5efec5 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Tue, 30 Apr 2019 16:48:31 +0800 Subject: [PATCH 21/81] arm: imx: Add low power idle support for imx6sll Add low power idle support for i.MX6SLL. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/Makefile | 5 +- arch/arm/mach-imx/common.h | 1 + arch/arm/mach-imx/cpuidle-imx6sll.c | 277 ++++++++ arch/arm/mach-imx/cpuidle.h | 5 + arch/arm/mach-imx/imx6sll_low_power_idle.S | 780 +++++++++++++++++++++ arch/arm/mach-imx/mach-imx6sl.c | 2 + 6 files changed, 1068 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-imx/cpuidle-imx6sll.c create mode 100644 arch/arm/mach-imx/imx6sll_low_power_idle.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 929412af264b..ceb22c96049f 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -27,8 +27,9 @@ obj-$(CONFIG_SOC_IMX5) += cpuidle-imx5.o obj-$(CONFIG_SOC_IMX6Q) += cpuidle-imx6q.o AFLAGS_imx6sl_low_power_idle.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o imx6sl_low_power_idle.o -obj-$(CONFIG_SOC_IMX6SLL) += cpuidle-imx6sx.o imx6sx_low_power_idle.o -obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o +AFLAGS_imx6sll_low_power_idle.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_IMX6SLL) += cpuidle-imx6sll.o imx6sll_low_power_idle.o +obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o imx6sx_low_power_idle.o AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a AFLAGS_imx6ul_low_power_idle.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6ul.o imx6ul_low_power_idle.o diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index ce52b92856d8..eff3a9f2709e 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -143,6 +143,7 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode); void imx6_set_int_mem_clk_lpm(bool enable); void imx6sl_set_wait_clk(bool enter); void imx6sl_low_power_idle(void); +void imx6sll_low_power_idle(void); void imx6sx_low_power_idle(void); void imx6ul_low_power_idle(void); void imx7d_low_power_idle(void); diff --git a/arch/arm/mach-imx/cpuidle-imx6sll.c b/arch/arm/mach-imx/cpuidle-imx6sll.c new file mode 100644 index 000000000000..bb8678ff56f0 --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx6sll.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "cpuidle.h" +#include "hardware.h" + +#define MAX_MMDC_IO_NUM 14 + +#define PMU_LOW_PWR_CTRL 0x270 +#define XTALOSC24M_OSC_CONFIG0 0x2a0 +#define XTALOSC24M_OSC_CONFIG1 0x2b0 +#define XTALOSC24M_OSC_CONFIG2 0x2c0 +#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT 24 +#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK 0xf +#define XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT 16 +#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK 0xf +#define XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT 12 +#define XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT 4 +#define XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT 1 +#define XTALOSC24M_OSC_CONFIG0_START_SHIFT 0 +#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT 20 +#define XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT 0 +#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK 0xfff +#define XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT 0 + +extern unsigned long iram_tlb_phys_addr; +static void __iomem *wfi_iram_base; + +#ifdef CONFIG_CPU_FREQ +static void __iomem *wfi_iram_base_phys; +extern unsigned long mx6sll_lpm_wfi_start asm("mx6sll_lpm_wfi_start"); +extern unsigned long mx6sll_lpm_wfi_end asm("mx6sll_lpm_wfi_end"); +#endif + +struct imx6_pm_base { + phys_addr_t pbase; + void __iomem *vbase; +}; + +struct imx6_cpuidle_pm_info { + phys_addr_t pbase; /* The physical address of pm_info. */ + phys_addr_t resume_addr; /* The physical resume address for asm code */ + u32 pm_info_size; /* Size of pm_info. */ + u32 ttbr; + struct imx6_pm_base mmdc_base; + struct imx6_pm_base iomuxc_base; + struct imx6_pm_base ccm_base; + struct imx6_pm_base gpc_base; + struct imx6_pm_base anatop_base; + struct imx6_pm_base src_base; + struct imx6_pm_base l2_base; + u32 saved_diagnostic; /* To save disagnostic register */ + u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */ + u32 mmdc_io_val[MAX_MMDC_IO_NUM][2]; /* To save offset and value */ +} __aligned(8); + +static const u32 imx6sll_mmdc_io_offset[] __initconst = { + 0x294, 0x298, 0x29c, 0x2a0, /* DQM0, DQM1, RAS, CAS */ + 0x544, 0x54c, 0x554, 0x558, /* GPR_B0DS ~ GPR_B3DS */ + 0x530, 0x540, 0x2ac, 0x52c, /* MODE_CTL, MODE, SDCLK0, GPR_ADDS */ + 0x2a4, 0x2a8, /* SDCKE0, SDCKE1 */ +}; + +static void (*imx6sll_wfi_in_iram_fn)(void __iomem *iram_vbase); + +#define MX6SLL_POWERDWN_IDLE_PARAM \ + ((1 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +static int imx6sll_idle_finish(unsigned long val) +{ + if (psci_ops.cpu_suspend) + psci_ops.cpu_suspend(MX6SLL_POWERDWN_IDLE_PARAM, + __pa(cpu_resume)); + else + imx6sll_wfi_in_iram_fn(wfi_iram_base); + + return 0; +} + +static int imx6sll_enter_wait(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int mode = get_bus_freq_mode(); + + imx6_set_lpm(WAIT_UNCLOCKED); + if ((index == 1) || ((mode != BUS_FREQ_LOW) && index == 2)) { + index = 1; + cpu_do_idle(); + } else { + imx_gpc_switch_pupscr_clk(true); + /* Need to notify there is a cpu pm operation. */ + cpu_pm_enter(); + cpu_cluster_pm_enter(); + + cpu_suspend(0, imx6sll_idle_finish); + + cpu_cluster_pm_exit(); + cpu_pm_exit(); + imx6_enable_rbc(false); + + imx_gpc_switch_pupscr_clk(false); + } + + imx6_set_lpm(WAIT_CLOCKED); + + return index; +} + +static struct cpuidle_driver imx6sll_cpuidle_driver = { + .name = "imx6sll_cpuidle", + .owner = THIS_MODULE, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT */ + { + .exit_latency = 50, + .target_residency = 75, + .enter = imx6sll_enter_wait, + .name = "WAIT", + .desc = "Clock off", + }, + /* LOW POWER IDLE */ + { + /* + * RBC 130us + ARM gating 43us + RBC clear 65us + * + PLL2 relock 450us and some margin, here set + * it to 700us. + */ + .exit_latency = 700, + .target_residency = 1000, + .enter = imx6sll_enter_wait, + .name = "LOW-POWER-IDLE", + .desc = "ARM power off", + } + }, + .state_count = 3, + .safe_state_index = 0, +}; + +int __init imx6sll_cpuidle_init(void) +{ + void __iomem *anatop_base = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR); + u32 val; +#ifdef CONFIG_CPU_FREQ + struct imx6_cpuidle_pm_info *cpuidle_pm_info; + int i; + const u32 *mmdc_offset_array; + u32 wfi_code_size; + + wfi_iram_base_phys = (void *)(iram_tlb_phys_addr + MX6_CPUIDLE_IRAM_ADDR_OFFSET); + + /* Make sure wfi_iram_base is 8 byte aligned. */ + if ((uintptr_t)(wfi_iram_base_phys) & (FNCPY_ALIGN - 1)) + wfi_iram_base_phys += FNCPY_ALIGN - ((uintptr_t)wfi_iram_base_phys % (FNCPY_ALIGN)); + + wfi_iram_base = (void *)IMX_IO_P2V((unsigned long) wfi_iram_base_phys); + + cpuidle_pm_info = wfi_iram_base; + cpuidle_pm_info->pbase = (phys_addr_t) wfi_iram_base_phys; + cpuidle_pm_info->pm_info_size = sizeof(*cpuidle_pm_info); + cpuidle_pm_info->resume_addr = virt_to_phys(v7_cpu_resume); + cpuidle_pm_info->mmdc_io_num = ARRAY_SIZE(imx6sll_mmdc_io_offset); + mmdc_offset_array = imx6sll_mmdc_io_offset; + + cpuidle_pm_info->mmdc_base.pbase = MX6Q_MMDC_P0_BASE_ADDR; + cpuidle_pm_info->mmdc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR); + + cpuidle_pm_info->ccm_base.pbase = MX6Q_CCM_BASE_ADDR; + cpuidle_pm_info->ccm_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_CCM_BASE_ADDR); + + cpuidle_pm_info->anatop_base.pbase = MX6Q_ANATOP_BASE_ADDR; + cpuidle_pm_info->anatop_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR); + + cpuidle_pm_info->gpc_base.pbase = MX6Q_GPC_BASE_ADDR; + cpuidle_pm_info->gpc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_GPC_BASE_ADDR); + + cpuidle_pm_info->iomuxc_base.pbase = MX6Q_IOMUXC_BASE_ADDR; + cpuidle_pm_info->iomuxc_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR); + + cpuidle_pm_info->src_base.pbase = MX6Q_SRC_BASE_ADDR; + cpuidle_pm_info->src_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_SRC_BASE_ADDR); + + cpuidle_pm_info->l2_base.pbase = MX6Q_L2_BASE_ADDR; + cpuidle_pm_info->l2_base.vbase = (void __iomem *)IMX_IO_P2V(MX6Q_L2_BASE_ADDR); + + /* Only save mmdc io offset, settings will be saved in asm code */ + for (i = 0; i < cpuidle_pm_info->mmdc_io_num; i++) + cpuidle_pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i]; + + wfi_code_size = (&mx6sll_lpm_wfi_end -&mx6sll_lpm_wfi_start) *4; + + imx6sll_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info), + &imx6sll_low_power_idle, wfi_code_size); +#endif + + imx6_set_int_mem_clk_lpm(true); + + /* + * enable RC-OSC here, as it needs at least 4ms for RC-OSC to + * be stable, low power idle flow can NOT endure this big + * latency, so we make RC-OSC self-tuning enabled here. + */ + val = readl_relaxed(anatop_base + PMU_LOW_PWR_CTRL); + val |= 0x1; + writel_relaxed(val, anatop_base + PMU_LOW_PWR_CTRL); + /* + * config RC-OSC freq + * tune_enable = 1;tune_start = 1;hyst_plus = 0;hyst_minus = 0; + * osc_prog = 0xa7; + */ + writel_relaxed( + 0x4 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_CUR_SHIFT | + 0xa7 << XTALOSC24M_OSC_CONFIG0_RC_OSC_PROG_SHIFT | + 0x1 << XTALOSC24M_OSC_CONFIG0_ENABLE_SHIFT | + 0x1 << XTALOSC24M_OSC_CONFIG0_START_SHIFT, + anatop_base + XTALOSC24M_OSC_CONFIG0); + /* set count_trg = 0x2dc */ + writel_relaxed( + 0x40 << XTALOSC24M_OSC_CONFIG1_COUNT_RC_CUR_SHIFT | + 0x2dc << XTALOSC24M_OSC_CONFIG1_COUNT_RC_TRG_SHIFT, + anatop_base + XTALOSC24M_OSC_CONFIG1); + /* wait 4ms according to hardware design */ + msleep(4); + /* + * now add some hysteresis, hyst_plus=3, hyst_minus=3 + * (the minimum hysteresis that looks good is 2) + */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG0); + val &= ~((XTALOSC24M_OSC_CONFIG0_HYST_MINUS_MASK << + XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) | + (XTALOSC24M_OSC_CONFIG0_HYST_PLUS_MASK << + XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT)); + val |= (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_MINUS_SHIFT) | + (0x3 << XTALOSC24M_OSC_CONFIG0_HYST_PLUS_SHIFT); + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG0); + /* set the count_1m_trg = 0x2d7 */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG2); + val &= ~(XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_MASK << + XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT); + val |= 0x2d7 << XTALOSC24M_OSC_CONFIG2_COUNT_1M_TRG_SHIFT; + writel_relaxed(val, anatop_base + XTALOSC24M_OSC_CONFIG2); + /* + * hardware design require to write XTALOSC24M_OSC_CONFIG0 or + * XTALOSC24M_OSC_CONFIG1 to + * make XTALOSC24M_OSC_CONFIG2 write work + */ + val = readl_relaxed(anatop_base + XTALOSC24M_OSC_CONFIG1); + + return cpuidle_register(&imx6sll_cpuidle_driver, NULL); +} diff --git a/arch/arm/mach-imx/cpuidle.h b/arch/arm/mach-imx/cpuidle.h index d011da14d4c6..340b0e3af05f 100644 --- a/arch/arm/mach-imx/cpuidle.h +++ b/arch/arm/mach-imx/cpuidle.h @@ -8,6 +8,7 @@ extern int imx5_cpuidle_init(void); extern int imx6q_cpuidle_init(void); extern int imx6sl_cpuidle_init(void); +extern int imx6sll_cpuidle_init(void); extern int imx6sx_cpuidle_init(void); extern int imx6ul_cpuidle_init(void); extern int imx7d_cpuidle_init(void); @@ -26,6 +27,10 @@ static inline int imx6sl_cpuidle_init(void) { return 0; } +static inline int imx6sll_cpuidle_init(void) +{ + return 0; +} static inline int imx6sx_cpuidle_init(void) { return 0; diff --git a/arch/arm/mach-imx/imx6sll_low_power_idle.S b/arch/arm/mach-imx/imx6sll_low_power_idle.S new file mode 100644 index 000000000000..a7e206ecbb42 --- /dev/null +++ b/arch/arm/mach-imx/imx6sll_low_power_idle.S @@ -0,0 +1,780 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#define PM_INFO_PBASE_OFFSET 0x0 +#define PM_INFO_RESUME_ADDR_OFFSET 0x4 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0x8 +#define PM_INFO_PM_INFO_TTBR_OFFSET 0xc +#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 +#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 +#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18 +#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c +#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20 +#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24 +#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28 +#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c +#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x30 +#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x34 +#define PM_INFO_MX6Q_SRC_P_OFFSET 0x38 +#define PM_INFO_MX6Q_SRC_V_OFFSET 0x3c +#define PM_INFO_MX6Q_L2_P_OFFSET 0x40 +#define PM_INFO_MX6Q_L2_V_OFFSET 0x44 +#define PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET 0x48 + +#define PM_INFO_MMDC_IO_NUM_OFFSET 0x4c +#define PM_INFO_MMDC_IO_VAL_OFFSET 0x50 + +#define MX6Q_MMDC_MAPSR 0x404 +#define MX6Q_MMDC_MPDGCTRL0 0x83c +#define MX6Q_SRC_GPR1 0x20 +#define MX6Q_SRC_GPR2 0x24 +#define MX6Q_GPC_IMR1 0x08 +#define MX6Q_GPC_IMR2 0x0c +#define MX6Q_GPC_IMR3 0x10 +#define MX6Q_GPC_IMR4 0x14 +#define MX6Q_CCM_CCR 0x0 + +.globl mx6sll_lpm_wfi_start +.globl mx6sll_lpm_wfi_end + + .macro pll_do_wait_lock +1: + ldr r7, [r10, r8] + ands r7, #0x80000000 + beq 1b + + .endm + + .macro ccm_do_wait +2: + ldr r7, [r10, #0x48] + cmp r7, #0x0 + bne 2b + + .endm + + .macro ccm_enter_idle + + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + + /* set ahb to 3MHz */ + ldr r7, [r10, #0x14] + orr r7, r7, #0x1c00 + str r7, [r10, #0x14] + + /* set perclk to 6MHz */ + ldr r7, [r10, #0x1c] + bic r7, r7, #0x3f + orr r7, r7, #0x3 + str r7, [r10, #0x1c] + + /* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */ + ldr r7, [r10, #0x14] + orr r7, r7, #0x2 + orr r7, r7, #(0x7 << 3) + str r7, [r10, #0x14] + + ccm_do_wait + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* + * disable pll2, suppose when system enter low + * power idle mode, only 396MHz pfd needs pll2, + * now we switch arm clock to OSC, we can disable + * pll2 now, gate pll2_pfd2 first. + */ + ldr r7, [r10, #0x100] + orr r7, #0x800000 + str r7, [r10, #0x100] + + ldr r7, [r10, #0x30] + orr r7, r7, #0x1000 + bic r7, r7, #0x2000 + str r7, [r10, #0x30] + + .endm + + .macro ccm_exit_idle + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* enable pll2 and pll2_pfd2 */ + ldr r7, [r10, #0x30] + bic r7, r7, #0x1000 + orr r7, r7, #0x2000 + str r7, [r10, #0x30] + + ldr r8, =0x30 + pll_do_wait_lock + + ldr r7, [r10, #0x100] + bic r7, #0x800000 + str r7, [r10, #0x100] + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET] + + /* set perclk back to 24MHz */ + ldr r7, [r10, #0x1c] + bic r7, r7, #0x3f + str r7, [r10, #0x1c] + + /* set mmdc back to 24MHz */ + ldr r7, [r10, #0x14] + bic r7, r7, #0x7 + bic r7, r7, #(0x7 << 3) + str r7, [r10, #0x14] + + /* set ahb div back to 24MHz */ + ldr r7, [r10, #0x14] + bic r7, r7, #0x1c00 + str r7, [r10, #0x14] + + ccm_do_wait + + .endm + + .macro anatop_enter_idle + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* + * check whether any PLL is enabled, as only when + * there is no PLLs enabled, 2P5 and 1P1 can be + * off and only enable weak ones. + */ + + /* arm pll1 */ + ldr r7, [r10, #0] + ands r7, r7, #(1 << 31) + bne 10f + + /* sys pll2 */ + ldr r7, [r10, #0x30] + ands r7, r7, #(1 << 31) + bne 10f + + /* usb pll3 */ + ldr r7, [r10, #0x10] + ands r7, r7, #(1 << 31) + bne 10f + + /* audio pll4 */ + ldr r7, [r10, #0x70] + ands r7, r7, #(1 << 31) + bne 10f + + /* vidio pll5 */ + ldr r7, [r10, #0xa0] + ands r7, r7, #(1 << 31) + bne 10f + + /* enet pll6 */ + ldr r7, [r10, #0xe0] + ands r7, r7, #(1 << 31) + bne 10f + + /* usb host pll7 */ + ldr r7, [r10, #0x20] + ands r7, r7, #(1 << 31) + bne 10f + + /* enable weak 2P5 and turn off regular 2P5 */ + ldr r7, [r10, #0x130] + orr r7, r7, #0x40000 + str r7, [r10, #0x130] + bic r7, r7, #0x1 + str r7, [r10, #0x130] + + /* enable weak 1p1 and turn off regular 1P1 */ + ldr r7, [r10, #0x110] + orr r7, r7, #0x40000 + str r7, [r10, #0x110] + bic r7, r7, #0x1 + str r7, [r10, #0x110] + + /* low power band gap enable */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x20 + str r7, [r10, #0x270] + + /* turn off the bias current from the regular bandgap */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x80 + str r7, [r10, #0x270] + + /* + * clear the REFTOP_SELFBIASOFF, + * self-bias circuit of the band gap. + * Per RM, should be cleared when + * band gap is powered down. + */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x8 + str r7, [r10, #0x150] + + /* turn off regular bandgap */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x1 + str r7, [r10, #0x150] + +10: + /* switch to RC-OSC */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x10 + str r7, [r10, #0x270] + + /* turn off XTAL-OSC */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x40000000 + str r7, [r10, #0x150] + + /* lower OSC current by 37.5% */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x6000 + str r7, [r10, #0x150] + + /* disconnect vdd_high_in and vdd_snvs_in */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x1000 + str r7, [r10, #0x150] + + .endm + + .macro anatop_exit_idle + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* increase OSC current to normal */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x6000 + str r7, [r10, #0x150] + + /* turn on XTAL-OSC and detector */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x40000000 + orr r7, r7, #0x10000 + str r7, [r10, #0x150] + + /* wait for XTAL stable */ +14: + ldr r7, [r10, #0x150] + ands r7, r7, #0x8000 + beq 14b + + /* switch to XTAL-OSC */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x10 + str r7, [r10, #0x270] + + /* turn off XTAL-OSC detector */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x10000 + str r7, [r10, #0x150] +15: + /* check whether we need to enable 2P5/1P1 */ + ldr r7, [r10, #0x110] + ands r7, r7, #0x40000 + beq 11f + + /* turn on regular bandgap and wait for stable */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x1 + str r7, [r10, #0x150] +13: + ldr r7, [r10, #0x150] + ands r7, #0x80 + beq 13b + + /* + * set the REFTOP_SELFBIASOFF, + * self-bias circuit of the band gap. + */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x8 + str r7, [r10, #0x150] + + /* turn on the bias current from the regular bandgap */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x80 + str r7, [r10, #0x270] + + /* low power band gap disable */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x20 + str r7, [r10, #0x270] +12: + /* enable regular 2P5 and turn off weak 2P5 */ + ldr r7, [r10, #0x130] + orr r7, r7, #0x1 + str r7, [r10, #0x130] + + /* Ensure the 2P5 is up. */ +3: + ldr r7, [r10, #0x130] + ands r7, r7, #0x20000 + beq 3b + ldr r7, [r10, #0x130] + bic r7, r7, #0x40000 + str r7, [r10, #0x130] + + /* enable regular 1p1 and turn off weak 1P1 */ + ldr r7, [r10, #0x110] + orr r7, r7, #0x1 + str r7, [r10, #0x110] +4: + ldr r7, [r10, #0x110] + ands r7, r7, #0x20000 + beq 4b + ldr r7, [r10, #0x110] + bic r7, r7, #0x40000 + str r7, [r10, #0x110] +11: + .endm + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + .endm + + .macro mmdc_enter_dvfs_mode + + /* disable automatic power savings. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + /* make the DDR explicitly enter self-refresh. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +5: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + beq 5b + + .endm + + .macro resume_mmdc + + /* restore MMDC IO */ + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] + + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +6: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 6b + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] + + /* reset read FIFO, RST_RD_FIFO */ + ldr r7, =MX6Q_MMDC_MPDGCTRL0 + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +7: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 7b + + /* reset FIFO a second time */ + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +8: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 8b + + /* let DDR out of self-refresh */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +9: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne 9b + + /* enable DDR auto power saving */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + .endm + + .macro tlb_set_to_ocram + + /* save ttbr */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + .endm + + .macro tlb_back_to_ddr + + /* Restore the TTBCR */ + + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* restore ttbr */ + ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + +.extern iram_tlb_phys_addr + +/* imx6sx_low_power_idle */ + + .align 3 +ENTRY(imx6sll_low_power_idle) +mx6sll_lpm_wfi_start: + push {r4 - r10} + + /* get necessary info from pm_info */ + ldr r1, [r0, #PM_INFO_PBASE_OFFSET] + ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r5, =imx6sll_low_power_idle + ldr r6, =wakeup + sub r6, r6, r5 + add r8, r1, r2 + add r3, r8, r6 + + /* store physical resume addr and pm_info address. */ + ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] + str r3, [r10, #0x20] + str r1, [r10, #0x24] + + /* save disagnostic register */ + mrc p15, 0, r7, c15, c0, 1 + str r7, [r0, #PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET] + + /* set ARM power to be gated */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r7, =0x1 + str r7, [r10, #0x2a0] + + disable_l1_dcache + +#ifdef CONFIG_CACHE_L2X0 + /* sync L2 */ + ldr r10, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] + + /* Wait for background operations to complete. */ +wait_for_l2_to_idle: + ldr r7, [r10, #0x730] + cmp r7, #0x0 + bne wait_for_l2_to_idle + + mov r7, #0x0 + str r7, [r10, #0x730] + /* disable L2 */ + str r7, [r10, #0x100] + + dsb + isb +#endif + + tlb_set_to_ocram + + /* make sure MMDC in self-refresh */ + ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + mmdc_enter_dvfs_mode + + /* save DDR IO settings */ + ldr r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldr r6, =0x0 + ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET + add r8, r8, r0 +save_and_set_mmdc_io_lpm: + ldr r9, [r8], #0x4 + ldr r5, [r10, r9] + str r6, [r10, r9] + str r5, [r8], #0x4 + subs r7, r7, #0x1 + bne save_and_set_mmdc_io_lpm + + mov r5, #0x0 + ccm_enter_idle + anatop_enter_idle + + /* + * mask all GPC interrupts before + * enabling the RBC counters to + * avoid the counter starting too + * early if an interupt is already + * pending. + */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r4, [r10, #MX6Q_GPC_IMR1] + ldr r5, [r10, #MX6Q_GPC_IMR2] + ldr r6, [r10, #MX6Q_GPC_IMR3] + ldr r7, [r10, #MX6Q_GPC_IMR4] + + ldr r3, =0xffffffff + str r3, [r10, #MX6Q_GPC_IMR1] + str r3, [r10, #MX6Q_GPC_IMR2] + str r3, [r10, #MX6Q_GPC_IMR3] + str r3, [r10, #MX6Q_GPC_IMR4] + + /* + * enable the RBC bypass counter here + * to hold off the interrupts. RBC counter + * = 4 (120us). With this setting, the latency + * from wakeup interrupt to ARM power up + * is ~130uS. + */ + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldr r3, [r10, #MX6Q_CCM_CCR] + bic r3, r3, #(0x3f << 21) + orr r3, r3, #(0x4 << 21) + str r3, [r10, #MX6Q_CCM_CCR] + + /* enable the counter. */ + ldr r3, [r10, #MX6Q_CCM_CCR] + orr r3, r3, #(0x1 << 27) + str r3, [r10, #MX6Q_CCM_CCR] + + /* unmask all the GPC interrupts. */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + str r4, [r10, #MX6Q_GPC_IMR1] + str r5, [r10, #MX6Q_GPC_IMR2] + str r6, [r10, #MX6Q_GPC_IMR3] + str r7, [r10, #MX6Q_GPC_IMR4] + + /* + * now delay for a short while (3usec) + * ARM is at 24MHz at this point + * so a short loop should be enough. + * this delay is required to ensure that + * the RBC counter can start counting in + * case an interrupt is already pending + * or in case an interrupt arrives just + * as ARM is about to assert DSM_request. + */ + ldr r4, =50 +rbc_loop: + subs r4, r4, #0x1 + bne rbc_loop + + wfi + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + mov r5, #0x0 + anatop_exit_idle + ccm_exit_idle + resume_mmdc + + /* clear ARM power gate setting */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r7, =0x0 + str r7, [r10, #0x2a0] + + /* enable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + orr r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + +#ifdef CONFIG_CACHE_L2X0 + ldr r10, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] + mov r7, #0x1 + /* enable L2 */ + str r7, [r10, #0x100] +#endif + + tlb_back_to_ddr + + /* Restore registers */ + pop {r4 - r10} + mov pc, lr + +wakeup: + + /* invalidate L1 I-cache first */ + mov r1, #0x0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 6 + /* enable the Icache and branch prediction */ + mov r1, #0x1800 + mcr p15, 0, r1, c1, c0, 0 + isb + /* restore disagnostic register */ + ldr r7, [r0, #PM_INFO_MX6Q_SAVED_DIAGNOSTIC_OFFSET] + mcr p15, 0, r7, c15, c0, 1 + + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + /* clear core0's entry and parameter */ + ldr r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] + mov r7, #0x0 + str r7, [r10, #MX6Q_SRC_GPR1] + str r7, [r10, #MX6Q_SRC_GPR2] + + /* clear ARM power gate setting */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET] + ldr r7, =0x0 + str r7, [r10, #0x2a0] + + mov r5, #0x1 + anatop_exit_idle + ccm_exit_idle + resume_mmdc + + /* Restore registers */ + mov pc, lr + .ltorg +mx6sll_lpm_wfi_end: diff --git a/arch/arm/mach-imx/mach-imx6sl.c b/arch/arm/mach-imx/mach-imx6sl.c index a89500d2f86f..5c21742ef796 100644 --- a/arch/arm/mach-imx/mach-imx6sl.c +++ b/arch/arm/mach-imx/mach-imx6sl.c @@ -40,6 +40,8 @@ static void __init imx6sl_init_late(void) if (IS_ENABLED(CONFIG_SOC_IMX6SL) && cpu_is_imx6sl()) imx6sl_cpuidle_init(); + else if (IS_ENABLED(CONFIG_SOC_IMX6SLL)) + imx6sll_cpuidle_init(); } static void __init imx6sl_init_machine(void) From e6d26ea8521f9926c5603ebd9ded0aa172c189a2 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Tue, 30 Apr 2019 16:54:49 +0800 Subject: [PATCH 22/81] arm: imx: Add low power idle for imx6ull Add low power idle support for i.MX6ULL. Signed-off-by: Jacky Bai --- arch/arm/mach-imx/Makefile | 3 +- arch/arm/mach-imx/common.c | 5 - arch/arm/mach-imx/common.h | 1 + arch/arm/mach-imx/cpuidle-imx6ul.c | 13 +- arch/arm/mach-imx/imx6ull_low_power_idle.S | 764 +++++++++++++++++++++ 5 files changed, 777 insertions(+), 9 deletions(-) create mode 100644 arch/arm/mach-imx/imx6ull_low_power_idle.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index ceb22c96049f..174ce1cd8007 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -32,7 +32,8 @@ obj-$(CONFIG_SOC_IMX6SLL) += cpuidle-imx6sll.o imx6sll_low_power_idle.o obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o imx6sx_low_power_idle.o AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a AFLAGS_imx6ul_low_power_idle.o :=-Wa,-march=armv7-a -obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6ul.o imx6ul_low_power_idle.o +AFLAGS_imx6ull_low_power_idle.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6ul.o imx6ul_low_power_idle.o imx6ull_low_power_idle.o obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o AFLAGS_imx7d_low_power_idle.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX7D_CA7) += cpuidle-imx7d.o imx7d_low_power_idle.o diff --git a/arch/arm/mach-imx/common.c b/arch/arm/mach-imx/common.c index cf57b55a369b..2f644c6b2c28 100644 --- a/arch/arm/mach-imx/common.c +++ b/arch/arm/mach-imx/common.c @@ -144,11 +144,6 @@ void imx6_up_ddr3_freq_change(struct imx6_busfreq_info *busfreq_info) {} void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode) {} #endif -#if !defined(CONFIG_SOC_IMX6ULL) -u32 mx6ull_lpm_wfi_start, mx6ull_lpm_wfi_end; -void imx6ull_low_power_idle(void) {} -#endif - #if !defined(CONFIG_SOC_IMX6Q) u32 mx6_ddr3_freq_change_start, mx6_ddr3_freq_change_end; u32 mx6q_lpddr2_freq_change_start, mx6q_lpddr2_freq_change_end; diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index eff3a9f2709e..1a2a9676fa41 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -146,6 +146,7 @@ void imx6sl_low_power_idle(void); void imx6sll_low_power_idle(void); void imx6sx_low_power_idle(void); void imx6ul_low_power_idle(void); +void imx6ull_low_power_idle(void); void imx7d_low_power_idle(void); #ifdef CONFIG_HAVE_IMX_MMDC int imx_mmdc_get_ddr_type(void); diff --git a/arch/arm/mach-imx/cpuidle-imx6ul.c b/arch/arm/mach-imx/cpuidle-imx6ul.c index 64e14f69fcca..4f22b8f0d02b 100644 --- a/arch/arm/mach-imx/cpuidle-imx6ul.c +++ b/arch/arm/mach-imx/cpuidle-imx6ul.c @@ -253,10 +253,17 @@ int __init imx6ul_cpuidle_init(void) cpuidle_pm_info->mmdc_io_val[i][0] = mmdc_offset_array[i]; /* calculate the wfi code size */ - wfi_code_size = (&mx6ul_lpm_wfi_end -&mx6ul_lpm_wfi_start) *4; + if (cpu_is_imx6ul()) { + wfi_code_size = (&mx6ul_lpm_wfi_end -&mx6ul_lpm_wfi_start) *4; - imx6ul_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info), - &imx6ul_low_power_idle, wfi_code_size); + imx6ul_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info), + &imx6ul_low_power_idle, wfi_code_size); + } else { + wfi_code_size = (&mx6ull_lpm_wfi_end -&mx6ull_lpm_wfi_start) *4; + + imx6ul_wfi_in_iram_fn = (void *)fncpy(wfi_iram_base + sizeof(*cpuidle_pm_info), + &imx6ull_low_power_idle, wfi_code_size); + } #endif imx6_set_int_mem_clk_lpm(true); diff --git a/arch/arm/mach-imx/imx6ull_low_power_idle.S b/arch/arm/mach-imx/imx6ull_low_power_idle.S new file mode 100644 index 000000000000..76ceac7fae26 --- /dev/null +++ b/arch/arm/mach-imx/imx6ull_low_power_idle.S @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#define PM_INFO_PBASE_OFFSET 0x0 +#define PM_INFO_RESUME_ADDR_OFFSET 0x4 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0x8 +#define PM_INFO_PM_INFO_TTBR_OFFSET 0xc +#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 +#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 +#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18 +#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c +#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20 +#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24 +#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28 +#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c +#define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x30 +#define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x34 +#define PM_INFO_MX6Q_SRC_P_OFFSET 0x38 +#define PM_INFO_MX6Q_SRC_V_OFFSET 0x3c +#define PM_INFO_MMDC_IO_NUM_OFFSET 0x40 +#define PM_INFO_MMDC_IO_VAL_OFFSET 0x44 + +#define MX6Q_MMDC_MAPSR 0x404 +#define MX6Q_MMDC_MPDGCTRL0 0x83c +#define MX6Q_SRC_GPR1 0x20 +#define MX6Q_SRC_GPR2 0x24 +#define MX6Q_GPC_IMR1 0x08 +#define MX6Q_GPC_IMR2 0x0c +#define MX6Q_GPC_IMR3 0x10 +#define MX6Q_GPC_IMR4 0x14 +#define MX6Q_CCM_CCR 0x0 + +.globl mx6ull_lpm_wfi_start +.globl mx6ull_lpm_wfi_end + + .macro pll_do_wait_lock +1: + ldr r7, [r10, r8] + ands r7, #0x80000000 + beq 1b + + .endm + + .macro ccm_do_wait +2: + ldr r7, [r10, #0x48] + cmp r7, #0x0 + bne 2b + + .endm + + .macro ccm_enter_idle + + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + + /* set ahb to 3MHz */ + ldr r7, [r10, #0x14] + orr r7, r7, #0x1c00 + str r7, [r10, #0x14] + + /* set perclk to 6MHz */ + ldr r7, [r10, #0x1c] + bic r7, r7, #0x3f + orr r7, r7, #0x3 + str r7, [r10, #0x1c] + + /* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */ + ldr r7, [r10, #0x14] + orr r7, r7, #0x2 + orr r7, r7, #(0x7 << 3) + str r7, [r10, #0x14] + + ccm_do_wait + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* + * disable pll2, suppose when system enter low + * power idle mode, only 396MHz pfd needs pll2, + * now we switch arm clock to OSC, we can disable + * pll2 now, gate pll2_pfd2 first. + */ + ldr r7, [r10, #0x100] + orr r7, #0x800000 + str r7, [r10, #0x100] + + ldr r7, [r10, #0x30] + orr r7, r7, #0x1000 + bic r7, r7, #0x2000 + str r7, [r10, #0x30] + + .endm + + .macro ccm_exit_idle + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* enable pll2 and pll2_pfd2 */ + ldr r7, [r10, #0x30] + bic r7, r7, #0x1000 + orr r7, r7, #0x2000 + str r7, [r10, #0x30] + + ldr r8, =0x30 + pll_do_wait_lock + + ldr r7, [r10, #0x100] + bic r7, #0x800000 + str r7, [r10, #0x100] + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET] + + /* set perclk back to 24MHz */ + ldr r7, [r10, #0x1c] + bic r7, r7, #0x3f + str r7, [r10, #0x1c] + + /* set mmdc back to 24MHz */ + ldr r7, [r10, #0x14] + bic r7, r7, #0x7 + bic r7, r7, #(0x7 << 3) + str r7, [r10, #0x14] + + /* set ahb div back to 24MHz */ + ldr r7, [r10, #0x14] + bic r7, r7, #0x1c00 + str r7, [r10, #0x14] + + ccm_do_wait + + .endm + + .macro anatop_enter_idle + + ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + + /* + * check whether any PLL is enabled, as only when + * there is no PLLs enabled, 2P5 and 1P1 can be + * off and only enable weak ones. + */ + + /* arm pll1 */ + ldr r7, [r10, #0] + ands r7, r7, #(1 << 31) + bne 10f + + /* sys pll2 */ + ldr r7, [r10, #0x30] + ands r7, r7, #(1 << 31) + bne 10f + + /* usb pll3 */ + ldr r7, [r10, #0x10] + ands r7, r7, #(1 << 31) + bne 10f + + /* audio pll4 */ + ldr r7, [r10, #0x70] + ands r7, r7, #(1 << 31) + bne 10f + + /* vidio pll5 */ + ldr r7, [r10, #0xa0] + ands r7, r7, #(1 << 31) + bne 10f + + /* enet pll6 */ + ldr r7, [r10, #0xe0] + ands r7, r7, #(1 << 31) + bne 10f + + /* usb host pll7 */ + ldr r7, [r10, #0x20] + ands r7, r7, #(1 << 31) + bne 10f + + /* enable weak 2P5 and turn off regular 2P5 */ + ldr r7, [r10, #0x130] + orr r7, r7, #0x40000 + str r7, [r10, #0x130] + bic r7, r7, #0x1 + str r7, [r10, #0x130] + + /* enable weak 1p1 and turn off regular 1P1 */ + ldr r7, [r10, #0x110] + orr r7, r7, #0x40000 + str r7, [r10, #0x110] + bic r7, r7, #0x1 + str r7, [r10, #0x110] + + /* check whether ARM LDO is bypassed */ + ldr r7, [r10, #0x140] + and r7, r7, #0x1f + cmp r7, #0x1f + bne 10f + + /* low power band gap enable */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x20 + str r7, [r10, #0x270] + + /* turn off the bias current from the regular bandgap */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x80 + str r7, [r10, #0x270] + + /* + * clear the REFTOP_SELFBIASOFF, + * self-bias circuit of the band gap. + * Per RM, should be cleared when + * band gap is powered down. + */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x8 + str r7, [r10, #0x150] + + /* turn off regular bandgap */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x1 + str r7, [r10, #0x150] + +10: + /* switch to RC-OSC */ + ldr r7, [r10, #0x270] + orr r7, r7, #0x10 + str r7, [r10, #0x270] + + /* turn off XTAL-OSC */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x40000000 + str r7, [r10, #0x150] + + /* lower OSC current by 37.5% */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x6000 + str r7, [r10, #0x150] + + /* disconnect vdd_high_in and vdd_snvs_in */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x1000 + str r7, [r10, #0x150] + + .endm + + .macro anatop_exit_idle + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] + + /* increase OSC current to normal */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x6000 + str r7, [r10, #0x150] + + /* turn on XTAL-OSC and detector */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x40000000 + orr r7, r7, #0x10000 + str r7, [r10, #0x150] + + /* wait for XTAL stable */ +14: + ldr r7, [r10, #0x150] + ands r7, r7, #0x8000 + beq 14b + + /* switch to XTAL-OSC */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x10 + str r7, [r10, #0x270] + + /* turn off XTAL-OSC detector */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x10000 + str r7, [r10, #0x150] +15: + /* check whether we need to enable 2P5/1P1 */ + ldr r7, [r10, #0x110] + ands r7, r7, #0x40000 + beq 11f + + /* check whether ARM LDO is bypassed */ + ldr r7, [r10, #0x140] + and r7, r7, #0x1f + cmp r7, #0x1f + bne 12f + + /* turn on regular bandgap and wait for stable */ + ldr r7, [r10, #0x150] + bic r7, r7, #0x1 + str r7, [r10, #0x150] +13: + ldr r7, [r10, #0x150] + ands r7, #0x80 + beq 13b + + /* + * set the REFTOP_SELFBIASOFF, + * self-bias circuit of the band gap. + */ + ldr r7, [r10, #0x150] + orr r7, r7, #0x8 + str r7, [r10, #0x150] + + /* turn on the bias current from the regular bandgap */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x80 + str r7, [r10, #0x270] + + /* low power band gap disable */ + ldr r7, [r10, #0x270] + bic r7, r7, #0x20 + str r7, [r10, #0x270] +12: + /* enable regular 2P5 and turn off weak 2P5 */ + ldr r7, [r10, #0x130] + orr r7, r7, #0x1 + str r7, [r10, #0x130] + + /* Ensure the 2P5 is up. */ +3: + ldr r7, [r10, #0x130] + ands r7, r7, #0x20000 + beq 3b + ldr r7, [r10, #0x130] + bic r7, r7, #0x40000 + str r7, [r10, #0x130] + + /* enable regular 1p1 and turn off weak 1P1 */ + ldr r7, [r10, #0x110] + orr r7, r7, #0x1 + str r7, [r10, #0x110] +4: + ldr r7, [r10, #0x110] + ands r7, r7, #0x20000 + beq 4b + ldr r7, [r10, #0x110] + bic r7, r7, #0x40000 + str r7, [r10, #0x110] +11: + .endm + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + .endm + + .macro mmdc_enter_dvfs_mode + + /* disable automatic power savings. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + /* disable power down timer */ + ldr r7, [r10, #0x4] + bic r7, r7, #0xff00 + str r7, [r10, #0x4] + + /* make the DDR explicitly enter self-refresh. */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +5: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + beq 5b + + .endm + + .macro resume_mmdc + + /* restore MMDC IO */ + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] + + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +6: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 6b + + cmp r5, #0x0 + ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] + + /* reset read FIFO, RST_RD_FIFO */ + ldr r7, =MX6Q_MMDC_MPDGCTRL0 + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +7: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 7b + + /* reset FIFO a second time */ + ldr r6, [r10, r7] + orr r6, r6, #(1 << 31) + str r6, [r10, r7] +8: + ldr r6, [r10, r7] + ands r6, r6, #(1 << 31) + bne 8b + + /* let DDR out of self-refresh */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r10, #MX6Q_MMDC_MAPSR] +9: + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne 9b + + /* enable power down timer */ + ldr r7, [r10, #0x4] + orr r7, r7, #0x5500 + str r7, [r10, #0x4] + + /* enable DDR auto power saving */ + ldr r7, [r10, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r10, #MX6Q_MMDC_MAPSR] + + .endm + + .macro tlb_set_to_ocram + + /* save ttbr */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + + /* + * To ensure no page table walks occur in DDR, we + * have a another page table stored in IRAM that only + * contains entries pointing to IRAM, AIPS1 and AIPS2. + * We need to set the TTBR1 to the new IRAM TLB. + * Do the following steps: + * 1. Flush the Branch Target Address Cache (BTAC) + * 2. Set TTBR1 to point to IRAM page table. + * 3. Disable page table walks in TTBR0 (PD0 = 1) + * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 + * and 2-4G is translated by TTBR1. + */ + + ldr r6, =iram_tlb_phys_addr + ldr r7, [r6] + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r7, c2, c0, 1 + + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + .endm + + .macro tlb_back_to_ddr + + /* Restore the TTBCR */ + + dsb + isb + + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + dsb + isb + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* restore ttbr */ + ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + +.extern iram_tlb_phys_addr + +/* imx6ull_low_power_idle */ + + .align 3 +ENTRY(imx6ull_low_power_idle) +mx6ull_lpm_wfi_start: + push {r4 - r10} + + /* get necessary info from pm_info */ + ldr r1, [r0, #PM_INFO_PBASE_OFFSET] + ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r5, =imx6ull_low_power_idle + ldr r6, =wakeup + sub r6, r6, r5 + add r8, r1, r2 + add r3, r8, r6 + + /* store physical resume addr and pm_info address. */ + ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] + str r3, [r10, #0x20] + str r1, [r10, #0x24] + + /* set ARM power to be gated */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r7, =0x1 + str r7, [r10, #0x2a0] + + disable_l1_dcache + + tlb_set_to_ocram + + /* make sure MMDC in self-refresh */ + ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + mmdc_enter_dvfs_mode + + /* save DDR IO settings */ + ldr r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + ldr r6, =0x0 + ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET + add r8, r8, r0 +save_and_set_mmdc_io_lpm: + ldr r9, [r8], #0x4 + ldr r5, [r10, r9] + str r6, [r10, r9] + str r5, [r8], #0x4 + subs r7, r7, #0x1 + bne save_and_set_mmdc_io_lpm + + mov r5, #0x0 + ccm_enter_idle + anatop_enter_idle + + /* + * mask all GPC interrupts before + * enabling the RBC counters to + * avoid the counter starting too + * early if an interupt is already + * pending. + */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r4, [r10, #MX6Q_GPC_IMR1] + ldr r5, [r10, #MX6Q_GPC_IMR2] + ldr r6, [r10, #MX6Q_GPC_IMR3] + ldr r7, [r10, #MX6Q_GPC_IMR4] + + ldr r3, =0xffffffff + str r3, [r10, #MX6Q_GPC_IMR1] + str r3, [r10, #MX6Q_GPC_IMR2] + str r3, [r10, #MX6Q_GPC_IMR3] + str r3, [r10, #MX6Q_GPC_IMR4] + + /* + * enable the RBC bypass counter here + * to hold off the interrupts. RBC counter + * = 4 (120us). With this setting, the latency + * from wakeup interrupt to ARM power up + * is ~130uS. + */ + ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldr r3, [r10, #MX6Q_CCM_CCR] + bic r3, r3, #(0x3f << 21) + orr r3, r3, #(0x4 << 21) + str r3, [r10, #MX6Q_CCM_CCR] + + /* enable the counter. */ + ldr r3, [r10, #MX6Q_CCM_CCR] + orr r3, r3, #(0x1 << 27) + str r3, [r10, #MX6Q_CCM_CCR] + + /* unmask all the GPC interrupts. */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + str r4, [r10, #MX6Q_GPC_IMR1] + str r5, [r10, #MX6Q_GPC_IMR2] + str r6, [r10, #MX6Q_GPC_IMR3] + str r7, [r10, #MX6Q_GPC_IMR4] + + /* + * now delay for a short while (3usec) + * ARM is at 24MHz at this point + * so a short loop should be enough. + * this delay is required to ensure that + * the RBC counter can start counting in + * case an interrupt is already pending + * or in case an interrupt arrives just + * as ARM is about to assert DSM_request. + */ + ldr r4, =50 +rbc_loop: + subs r4, r4, #0x1 + bne rbc_loop + + wfi + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + nop + nop + nop + nop + nop + + mov r5, #0x0 + anatop_exit_idle + ccm_exit_idle + + /* clear ARM power gate setting */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r7, =0x0 + str r7, [r10, #0x2a0] + + resume_mmdc + /* enable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + orr r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + + tlb_back_to_ddr + + /* Restore registers */ + pop {r4 - r10} + mov pc, lr + +wakeup: + /* invalidate L1 I-cache first */ + mov r1, #0x0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 0 + mcr p15, 0, r1, c7, c5, 6 + /* enable the Icache and branch prediction */ + mov r1, #0x1800 + mcr p15, 0, r1, c1, c0, 0 + isb + + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + /* clear core0's entry and parameter */ + ldr r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] + mov r7, #0x0 + str r7, [r10, #MX6Q_SRC_GPR1] + str r7, [r10, #MX6Q_SRC_GPR2] + + /* clear ARM power gate setting */ + ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET] + ldr r7, =0x0 + str r7, [r10, #0x2a0] + + mov r5, #0x1 + anatop_exit_idle + ccm_exit_idle + resume_mmdc + + /* Restore registers */ + mov pc, lr + .ltorg +mx6ull_lpm_wfi_end: From 82836f6a3304fb313e4e5b18d0a00403fb32b082 Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Tue, 7 May 2019 11:06:50 +0800 Subject: [PATCH 23/81] arm: imx: fix busfreq-imx build failure Signed-off-by: Dong Aisheng --- arch/arm/mach-imx/busfreq-imx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index 2fa0b2dcdc06..57fd6de9cf5a 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -1193,7 +1193,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sl() || cpu_is_imx6ull() cpu_is_imx6sll()) { + if (cpu_is_imx6sl() || cpu_is_imx6ull() || cpu_is_imx6sll()) { arm_clk = devm_clk_get(&pdev->dev, "arm"); step_clk = devm_clk_get(&pdev->dev, "step"); pll1_clk = devm_clk_get(&pdev->dev, "pll1"); From 348314ae48959348df563d025787ecf8572f6d28 Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Tue, 7 May 2019 11:23:24 +0800 Subject: [PATCH 24/81] soc: imx8: do not build for arm v6/v7 platforms Because imx_src_is_m4_enabled also defined in v6/v7 platforms code. Signed-off-by: Dong Aisheng --- drivers/soc/imx/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile index 7d8f8a56ca43..bcca6375e785 100644 --- a/drivers/soc/imx/Makefile +++ b/drivers/soc/imx/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o obj-$(CONFIG_IMX_GPCV2_PM_DOMAINS) += gpcv2.o -obj-$(CONFIG_ARCH_MXC) += soc-imx8.o +obj-$(CONFIG_ARM64) += soc-imx8.o obj-$(CONFIG_IMX_SCU_SOC) += soc-imx-scu.o obj-y += mu/ From 92e082d9fb664cd53e231522082ab48fcb32b3f0 Mon Sep 17 00:00:00 2001 From: Bai Ping Date: Mon, 30 Oct 2017 16:27:02 +0800 Subject: [PATCH 25/81] MLK-16689-03 driver: soc: Add busfreq driver for imx8mq Add busfreq driver support on i.MX8MQ. The busfreq driver is mainly used for dynamic DDR frequency change for power saving feature. When there is no peripheral or DMA device has direct access to DDR memory, we can lower the DDR frequency to save power. Currently, we support frequency setpoint for LPDDR4: (1): 3200mts, the DDRC core clock is sourced from 800MHz dram_pll, the DDRC apb clock is 200MHz. (2): 400mts, the DDRC core clock is source from sys1_pll_400m, the DDRC apb clock is is sourced from sys1_pll_40m. (3): 100mts, the DDRC core clock is sourced from sys1_pll_100m, the DDRC apb clock is sourced from sys1_pll_40m. In our busfreq driver, we have three mode supported: * high bus mode <-----> 3200mts; * audio bus mode <-----> 400mts; * low bus mode <-----> 100mts; The actual DDR frequency is done in ARM trusted firmware by calling the SMCC SiP service call. Signed-off-by: Bai Ping Reviewed-by: Anson Huang Use CONFIG_IMX8M_BUSFREQ Signed-off-by: Leonard Crestez --- arch/arm64/Kconfig.platforms | 5 + drivers/soc/imx/Kconfig | 4 + drivers/soc/imx/Makefile | 1 + drivers/soc/imx/busfreq-imx8mq.c | 553 +++++++++++++++++++++++++++++++ 4 files changed, 563 insertions(+) create mode 100644 drivers/soc/imx/busfreq-imx8mq.c diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 16d761475a86..312089d0c9aa 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -171,6 +171,8 @@ config ARCH_MXC select ARM64_ERRATUM_845719 if COMPAT select IMX_GPCV2 select IMX_GPCV2_PM_DOMAINS + select HAVE_IMX_BUSFREQ + select IMX8M_BUSFREQ select PM select PM_GENERIC_DOMAINS select SOC_BUS @@ -179,6 +181,9 @@ config ARCH_MXC This enables support for the ARMv8 based SoCs in the NXP i.MX family. +config HAVE_IMX_BUSFREQ + bool "i.MX8M busfreq" + config ARCH_QCOM bool "Qualcomm Platforms" select GPIOLIB diff --git a/drivers/soc/imx/Kconfig b/drivers/soc/imx/Kconfig index 8aaebf13e2e6..406f00f312ee 100644 --- a/drivers/soc/imx/Kconfig +++ b/drivers/soc/imx/Kconfig @@ -17,4 +17,8 @@ config IMX_SCU_SOC Controller Unit SoC info module, it will provide the SoC info like SoC family, ID and revision etc. +config IMX8M_BUSFREQ + bool "i.MX8M busfreq" + depends on ARCH_MXC && ARM64 + endmenu diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile index bcca6375e785..e860d547aa56 100644 --- a/drivers/soc/imx/Makefile +++ b/drivers/soc/imx/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o obj-$(CONFIG_IMX_GPCV2_PM_DOMAINS) += gpcv2.o +obj-$(CONFIG_IMX8M_BUSFREQ) += busfreq-imx8mq.o obj-$(CONFIG_ARM64) += soc-imx8.o obj-$(CONFIG_IMX_SCU_SOC) += soc-imx-scu.o obj-y += mu/ diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c new file mode 100644 index 000000000000..4a247a54a0e8 --- /dev/null +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -0,0 +1,553 @@ +/* + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FSL_SIP_DDR_DVFS 0xc2000004 + +#define HIGH_FREQ_3200MTS 0x0 +#define AUDIO_FREQ_400MTS 0x1 +#define LOW_BUS_FREQ_100MTS 0x2 +#define WAIT_BUS_FREQ_DONE 0xf + +static struct device *busfreq_dev; +static int low_bus_freq_mode; +static int audio_bus_freq_mode; +static int high_bus_freq_mode; +static int bus_freq_scaling_initialized; +static int bus_freq_scaling_is_active; +static int high_bus_count, audio_bus_count, low_bus_count; +static int cur_bus_freq_mode; +static int busfreq_suspended; +static bool cancel_reduce_bus_freq; + +static struct clk *dram_pll_clk; +static struct clk *sys1_pll_800m; +static struct clk *sys1_pll_400m; +static struct clk *sys1_pll_100m; +static struct clk *sys1_pll_40m; +static struct clk *dram_alt_src; +static struct clk *dram_alt_root; +static struct clk *dram_core_clk; +static struct clk *dram_apb_src; +static struct clk *dram_apb_pre_div; + +static struct delayed_work low_bus_freq_handler; +static struct delayed_work bus_freq_daemon; + +DEFINE_MUTEX(bus_freq_mutex); + +static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id) +{ + struct arm_smccc_res res; + /* call smc trap to ATF */ + arm_smccc_smc(FSL_SIP_DDR_DVFS, WAIT_BUS_FREQ_DONE, 0, + 0, 0, 0, 0, 0, &res); + + return IRQ_HANDLED; +} + +static void update_bus_freq(int target_freq) +{ + struct arm_smccc_res res; + u32 online_cpus = 0; + int cpu = 0; + + local_irq_disable(); + + for_each_online_cpu(cpu) { + online_cpus |= (1 << (cpu * 8)); + } + /* change the ddr freqency */ + arm_smccc_smc(FSL_SIP_DDR_DVFS, target_freq, online_cpus, + 0, 0, 0, 0, 0, &res); + + local_irq_enable(); +} + +static void reduce_bus_freq(void) +{ + high_bus_freq_mode = 0; + + /* prepare the necessary clk before frequency change */ + clk_prepare_enable(sys1_pll_40m); + clk_prepare_enable(dram_alt_root); + + if (audio_bus_count) { + clk_prepare_enable(sys1_pll_400m); + + update_bus_freq(AUDIO_FREQ_400MTS); + + /* correct the clock tree info */ + clk_disable_unprepare(sys1_pll_400m); + clk_set_parent(dram_alt_src, sys1_pll_400m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + + low_bus_freq_mode = 0; + audio_bus_freq_mode = 1; + cur_bus_freq_mode = BUS_FREQ_AUDIO; + } else { + clk_prepare_enable(sys1_pll_100m); + + update_bus_freq(LOW_BUS_FREQ_100MTS); + + /* correct the clock tree info */ + clk_disable_unprepare(sys1_pll_100m); + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + + low_bus_freq_mode = 1; + audio_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_LOW; + } + + clk_disable_unprepare(sys1_pll_40m); + clk_disable_unprepare(dram_alt_root); + + if (audio_bus_freq_mode) + printk(KERN_DEBUG "ddrc freq set to audio mode: 100MHz\n"); + if (low_bus_freq_mode) + printk(KERN_DEBUG "ddrc freq set to low bus mode: 25MHz\n"); +} + +static void reduce_bus_freq_handler(struct work_struct *work) +{ + mutex_lock(&bus_freq_mutex); + + if (!cancel_reduce_bus_freq) + reduce_bus_freq(); + + mutex_unlock(&bus_freq_mutex); +} + +static int set_low_bus_freq(void) +{ + if (busfreq_suspended) + return 0; + + if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active) + return 0; + + cancel_reduce_bus_freq = false; + + /* + * check to see if we need to got from low bus + * freq mode to audio bus freq mode. + * If so, the change needs to be done immediately. + */ + if (audio_bus_count && low_bus_freq_mode) + reduce_bus_freq(); + else + schedule_delayed_work(&low_bus_freq_handler, + usecs_to_jiffies(3000000)); + + return 0; +} + +static inline void cancel_low_bus_freq_handler(void) +{ + cancel_delayed_work(&low_bus_freq_handler); + cancel_reduce_bus_freq = true; +} + +static int set_high_bus_freq(int high_bus_freq) +{ + if (bus_freq_scaling_initialized || bus_freq_scaling_is_active) + cancel_low_bus_freq_handler(); + + if (busfreq_suspended) + return 0; + + if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active) + return 0; + + if (high_bus_freq_mode) + return 0; + + /* enable the clks needed in frequency */ + clk_prepare_enable(sys1_pll_800m); + clk_prepare_enable(dram_pll_clk); + + /* switch the DDR freqeuncy */ + update_bus_freq(0x0); + + /* correct the clock tree info */ + clk_set_parent(dram_apb_src, sys1_pll_800m); + clk_set_rate(dram_apb_pre_div, 200000000); + clk_set_parent(dram_core_clk, dram_pll_clk); + clk_disable_unprepare(sys1_pll_800m); + clk_disable_unprepare(dram_pll_clk); + + high_bus_freq_mode = 1; + audio_bus_freq_mode = 0; + low_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_HIGH; + + if (high_bus_freq_mode) + printk(KERN_DEBUG "ddrc freq set to high mode: 800MHz\n"); + + return 0; +} + +void request_bus_freq(enum bus_freq_mode mode) +{ + mutex_lock(&bus_freq_mutex); + + if (mode == BUS_FREQ_HIGH) + high_bus_count++; + else if (mode == BUS_FREQ_AUDIO) + audio_bus_count++; + else if (mode == BUS_FREQ_LOW) + low_bus_count++; + + if (busfreq_suspended || !bus_freq_scaling_initialized || + !bus_freq_scaling_is_active) { + mutex_unlock(&bus_freq_mutex); + return; + } + + cancel_low_bus_freq_handler(); + + if ((mode == BUS_FREQ_HIGH) && (!high_bus_freq_mode)) { + set_high_bus_freq(1); + mutex_unlock(&bus_freq_mutex); + return; + } + + if ((mode == BUS_FREQ_AUDIO) && (!high_bus_freq_mode) && + (!audio_bus_freq_mode)) { + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); + return; + } + + mutex_unlock(&bus_freq_mutex); +} +EXPORT_SYMBOL(request_bus_freq); + +void release_bus_freq(enum bus_freq_mode mode) +{ + mutex_lock(&bus_freq_mutex); + if (mode == BUS_FREQ_HIGH) { + if (high_bus_count == 0) { + dev_err(busfreq_dev, "high bus count mismatch!\n"); + dump_stack(); + mutex_unlock(&bus_freq_mutex); + return; + } + high_bus_count--; + } else if (mode == BUS_FREQ_AUDIO) { + if (audio_bus_count == 0) { + dev_err(busfreq_dev, "audio bus count mismatch!\n"); + dump_stack(); + mutex_unlock(&bus_freq_mutex); + return; + } + audio_bus_count--; + } else if (mode == BUS_FREQ_LOW) { + if (low_bus_count == 0) { + dev_err(busfreq_dev, "low bus count mismatch!\n"); + dump_stack(); + mutex_unlock(&bus_freq_mutex); + return; + } + low_bus_count--; + } + + if (busfreq_suspended || !bus_freq_scaling_initialized || + !bus_freq_scaling_is_active) { + mutex_unlock(&bus_freq_mutex); + return; + } + + if ((!audio_bus_freq_mode) && (high_bus_count == 0) && + (audio_bus_count != 0)) { + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); + return; + } + + if ((!low_bus_freq_mode) && (high_bus_count == 0) && + (audio_bus_count == 0)) { + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); + } + + mutex_unlock(&bus_freq_mutex); +} +EXPORT_SYMBOL(release_bus_freq); + +int get_bus_freq_mode(void) +{ + return cur_bus_freq_mode; +} +EXPORT_SYMBOL(get_bus_freq_mode); + +static void bus_freq_daemon_handler(struct work_struct *work) +{ + mutex_lock(&bus_freq_mutex); + if ((!low_bus_freq_mode) && (high_bus_count == 0) && + (audio_bus_count == 0)) + set_low_bus_freq(); + mutex_unlock(&bus_freq_mutex); +} + +static ssize_t bus_freq_scaling_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (bus_freq_scaling_is_active) + return sprintf(buf, "Bus frequency scaling is enabled\n"); + else + return sprintf(buf, "Bus frequency scaling is disabled\n"); +} + +static ssize_t bus_freq_scaling_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + if (strncmp(buf, "1", 1) == 0) { + bus_freq_scaling_is_active = 1; + set_high_bus_freq(1); + /* + * We set bus freq to higher at the beginning, + * so we use this daemon thread to make sure system + * can enter low bus mode if there is no high bus request pending + */ + schedule_delayed_work(&bus_freq_daemon, + usecs_to_jiffies(5000000)); + } else if (strncmp(buf, "0", 1) == 0) { + if (bus_freq_scaling_is_active) + set_high_bus_freq(1); + bus_freq_scaling_is_active = 0; + } + return size; +} + +static int bus_freq_pm_notify(struct notifier_block *nb, unsigned long event, + void *dummy) +{ + mutex_lock(&bus_freq_mutex); + + if (event == PM_SUSPEND_PREPARE) { + high_bus_count++; + set_high_bus_freq(1); + busfreq_suspended = 1; + } else if (event == PM_POST_SUSPEND) { + busfreq_suspended = 0; + high_bus_count--; + schedule_delayed_work(&bus_freq_daemon, + usecs_to_jiffies(5000000)); + } + + mutex_unlock(&bus_freq_mutex); + + return NOTIFY_OK; +} + +static int busfreq_reboot_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + /* System is rebooting. Set the system into high_bus_freq_mode. */ + request_bus_freq(BUS_FREQ_HIGH); + + return 0; +} + +static struct notifier_block imx_bus_freq_pm_notifier = { + .notifier_call = bus_freq_pm_notify, +}; + +static struct notifier_block imx_busfreq_reboot_notifier = { + .notifier_call = busfreq_reboot_notifier_event, +}; + +static DEVICE_ATTR(enable, 0644, bus_freq_scaling_enable_show, + bus_freq_scaling_enable_store); + +static int init_busfreq_irq(struct platform_device *busfreq_pdev) +{ + struct device *dev = &busfreq_pdev->dev; + u32 cpu; + int err; + + for_each_online_cpu(cpu) { + int irq; + /* + * set up a reserved interrupt to get all + * the active cores into a WFE state before + * changing the DDR frequency. + */ + irq = platform_get_irq(busfreq_pdev, cpu); + err = request_irq(irq, wait_in_wfe_irq, + IRQF_PERCPU, "ddrc", NULL); + if (err) { + dev_err(dev, "Busfreq request irq failed %d, err = %d\n", + irq, err); + return err; + } + err = irq_set_affinity(irq, cpumask_of(cpu)); + if (err) { + dev_err(dev, "busfreq can't set irq affinity irq = %d\n", irq); + return err; + } + } + + return 0; +} + +static int init_busfreq_clk(struct platform_device *pdev) +{ + dram_pll_clk = devm_clk_get(&pdev->dev, "dram_pll"); + sys1_pll_800m = devm_clk_get(&pdev->dev, "sys1_pll_800m"); + sys1_pll_400m = devm_clk_get(&pdev->dev, "sys1_pll_400m"); + sys1_pll_100m = devm_clk_get(&pdev->dev, "sys1_pll_100m"); + sys1_pll_40m = devm_clk_get(&pdev->dev, "sys1_pll_40m"); + dram_alt_src = devm_clk_get(&pdev->dev, "dram_alt_src"); + dram_alt_root = devm_clk_get(&pdev->dev, "dram_alt_root"); + dram_core_clk = devm_clk_get(&pdev->dev, "dram_core"); + dram_apb_src = devm_clk_get(&pdev->dev, "dram_apb_src"); + dram_apb_pre_div = devm_clk_get(&pdev->dev, "dram_apb_pre_div"); + + if (IS_ERR(dram_pll_clk) || IS_ERR(sys1_pll_400m) || IS_ERR(sys1_pll_100m) || + IS_ERR(sys1_pll_40m) || IS_ERR(dram_alt_src) || IS_ERR(dram_alt_root) || + IS_ERR(dram_core_clk) || IS_ERR(dram_apb_src) || IS_ERR(dram_apb_pre_div)) { + dev_err(&pdev->dev, "failed to get busfreq clk\n"); + return -EINVAL; + } + + return 0; +} + +/*! + * This is the probe routine for the bus frequency driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + * + */ +static int busfreq_probe(struct platform_device *pdev) +{ + int err; + + busfreq_dev = &pdev->dev; + + /* get the clock for DDRC */ + err = init_busfreq_clk(pdev); + if (err) { + dev_err(busfreq_dev, "init clk failed\n"); + return err; + } + + /* init the irq used for ddr frequency change */ + err = init_busfreq_irq(pdev); + if (err) { + dev_err(busfreq_dev, "init busfreq irq failed!\n"); + return err; + } + + /* create the sysfs file */ + err = sysfs_create_file(&busfreq_dev->kobj, &dev_attr_enable.attr); + if (err) { + dev_err(busfreq_dev, + "Unable to register sysdev entry for BUSFREQ"); + return err; + } + + high_bus_freq_mode = 1; + low_bus_freq_mode = 0; + audio_bus_freq_mode = 0; + cur_bus_freq_mode = BUS_FREQ_HIGH; + + bus_freq_scaling_is_active = 1; + bus_freq_scaling_initialized = 1; + + INIT_DELAYED_WORK(&low_bus_freq_handler, reduce_bus_freq_handler); + INIT_DELAYED_WORK(&bus_freq_daemon, bus_freq_daemon_handler); + register_pm_notifier(&imx_bus_freq_pm_notifier); + register_reboot_notifier(&imx_busfreq_reboot_notifier); + + /* enter low bus mode if no high speed device enabled */ + schedule_delayed_work(&bus_freq_daemon, msecs_to_jiffies(10000)); + + return 0; +} + +static const struct of_device_id imx_busfreq_ids[] = { + { .compatible = "fsl,imx_busfreq", }, + { /*sentinel */} +}; + +static struct platform_driver busfreq_driver = { + .driver = { + .name = "imx_busfreq", + .owner = THIS_MODULE, + .of_match_table = imx_busfreq_ids, + }, + .probe = busfreq_probe, +}; + +/*! + * Initialise the busfreq_driver. + * + * @return The function always returns 0. + */ +static int __init busfreq_init(void) +{ + if (platform_driver_register(&busfreq_driver) != 0) + return -ENODEV; + + printk(KERN_INFO "Bus freq driver module loaded\n"); + + return 0; +} + +static void __exit busfreq_cleanup(void) +{ + sysfs_remove_file(&busfreq_dev->kobj, &dev_attr_enable.attr); + + /* Unregister the device structure */ + platform_driver_unregister(&busfreq_driver); + bus_freq_scaling_initialized = 0; +} + +module_init(busfreq_init); +module_exit(busfreq_cleanup); + +MODULE_AUTHOR("NXP Semiconductor, Inc."); +MODULE_DESCRIPTION("Busfreq driver"); +MODULE_LICENSE("GPL"); From 4db8bf6666e77e8ea45689fdc1226325336f1826 Mon Sep 17 00:00:00 2001 From: Bai Ping Date: Wed, 8 Nov 2017 17:58:00 +0800 Subject: [PATCH 26/81] MLK-16804-06 driver: soc: Optimize the DDR frequency in audio playback case If audio device is the only that access to ddr memory, the DDR frequency can be reduce to 25MHz to save power. when DDR run in 25MHz frequency, the memory bandwidth is about 66MB/s, it can meet the performance requirement for audio only case. Signed-off-by: Bai Ping Reviewed-by: Anson Huang (cherry picked from commit 7c2389b6dca053ae4b4a56b3588978909769008c) --- drivers/soc/imx/busfreq-imx8mq.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index 4a247a54a0e8..54421f181fd8 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -103,14 +103,19 @@ static void reduce_bus_freq(void) clk_prepare_enable(sys1_pll_40m); clk_prepare_enable(dram_alt_root); + /* + * below piece of code has some redundant part, keep + * it at present, we may need update the audio freq + * in the future if needed. + */ if (audio_bus_count) { - clk_prepare_enable(sys1_pll_400m); + clk_prepare_enable(sys1_pll_100m); - update_bus_freq(AUDIO_FREQ_400MTS); + update_bus_freq(LOW_BUS_FREQ_100MTS); /* correct the clock tree info */ - clk_disable_unprepare(sys1_pll_400m); - clk_set_parent(dram_alt_src, sys1_pll_400m); + clk_disable_unprepare(sys1_pll_100m); + clk_set_parent(dram_alt_src, sys1_pll_100m); clk_set_parent(dram_core_clk, dram_alt_root); clk_set_parent(dram_apb_src, sys1_pll_40m); clk_set_rate(dram_apb_pre_div, 20000000); @@ -129,6 +134,7 @@ static void reduce_bus_freq(void) clk_set_parent(dram_core_clk, dram_alt_root); clk_set_parent(dram_apb_src, sys1_pll_40m); clk_set_rate(dram_apb_pre_div, 20000000); + clk_prepare_enable(sys1_pll_400m); low_bus_freq_mode = 1; audio_bus_freq_mode = 0; @@ -139,7 +145,7 @@ static void reduce_bus_freq(void) clk_disable_unprepare(dram_alt_root); if (audio_bus_freq_mode) - printk(KERN_DEBUG "ddrc freq set to audio mode: 100MHz\n"); + printk(KERN_DEBUG "ddrc freq set to audio mode: 25MHz\n"); if (low_bus_freq_mode) printk(KERN_DEBUG "ddrc freq set to low bus mode: 25MHz\n"); } From 68449e5c27aadff140a27d21fbe0749c4aded860 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 8 Nov 2017 18:17:22 +0800 Subject: [PATCH 27/81] MLK-16804-08 driver: soc: Reduce NOC/AHB/MAIN_AXI to save SOC power for audio playback reduce the NOC, main AXI and AHB bus clock frequency to save power when DDR enter low frequency mode. VDDSOC is ~195mA during video play, and ~180mA in idle. Signed-off-by: Anson Huang Signed-off-by: Bai Ping (cherry picked from commit e109b34d30f0b4628a41ca9715eea689cc8c2a56) --- drivers/soc/imx/busfreq-imx8mq.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index 54421f181fd8..faacc9b25be7 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -61,6 +61,11 @@ static struct clk *dram_alt_root; static struct clk *dram_core_clk; static struct clk *dram_apb_src; static struct clk *dram_apb_pre_div; +static struct clk *noc_div; +static struct clk *main_axi_src; +static struct clk *ahb_div; +static struct clk *osc_25m; +static struct clk *sys2_pll_333m; static struct delayed_work low_bus_freq_handler; static struct delayed_work bus_freq_daemon; @@ -119,6 +124,10 @@ static void reduce_bus_freq(void) clk_set_parent(dram_core_clk, dram_alt_root); clk_set_parent(dram_apb_src, sys1_pll_40m); clk_set_rate(dram_apb_pre_div, 20000000); + /* reduce the NOC & bus clock */ + clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); + clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); + clk_set_parent(main_axi_src, osc_25m); low_bus_freq_mode = 0; audio_bus_freq_mode = 1; @@ -135,6 +144,10 @@ static void reduce_bus_freq(void) clk_set_parent(dram_apb_src, sys1_pll_40m); clk_set_rate(dram_apb_pre_div, 20000000); clk_prepare_enable(sys1_pll_400m); + /* reduce the NOC & bus clock */ + clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); + clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); + clk_set_parent(main_axi_src, osc_25m); low_bus_freq_mode = 1; audio_bus_freq_mode = 0; @@ -179,7 +192,7 @@ static int set_low_bus_freq(void) reduce_bus_freq(); else schedule_delayed_work(&low_bus_freq_handler, - usecs_to_jiffies(3000000)); + usecs_to_jiffies(1000000)); return 0; } @@ -217,6 +230,9 @@ static int set_high_bus_freq(int high_bus_freq) clk_set_parent(dram_core_clk, dram_pll_clk); clk_disable_unprepare(sys1_pll_800m); clk_disable_unprepare(dram_pll_clk); + clk_set_rate(noc_div, 800000000); + clk_set_rate(ahb_div, 133333333); + clk_set_parent(main_axi_src, sys2_pll_333m); high_bus_freq_mode = 1; audio_bus_freq_mode = 0; @@ -447,10 +463,17 @@ static int init_busfreq_clk(struct platform_device *pdev) dram_core_clk = devm_clk_get(&pdev->dev, "dram_core"); dram_apb_src = devm_clk_get(&pdev->dev, "dram_apb_src"); dram_apb_pre_div = devm_clk_get(&pdev->dev, "dram_apb_pre_div"); + noc_div = devm_clk_get(&pdev->dev, "noc_div"); + ahb_div = devm_clk_get(&pdev->dev, "ahb_div"); + main_axi_src = devm_clk_get(&pdev->dev, "main_axi_src"); + osc_25m = devm_clk_get(&pdev->dev, "osc_25m"); + sys2_pll_333m = devm_clk_get(&pdev->dev, "sys2_pll_333m"); if (IS_ERR(dram_pll_clk) || IS_ERR(sys1_pll_400m) || IS_ERR(sys1_pll_100m) || IS_ERR(sys1_pll_40m) || IS_ERR(dram_alt_src) || IS_ERR(dram_alt_root) || - IS_ERR(dram_core_clk) || IS_ERR(dram_apb_src) || IS_ERR(dram_apb_pre_div)) { + IS_ERR(dram_core_clk) || IS_ERR(dram_apb_src) || IS_ERR(dram_apb_pre_div) + || IS_ERR(noc_div) || IS_ERR(main_axi_src) || IS_ERR(ahb_div) + || IS_ERR(osc_25m) || IS_ERR(sys2_pll_333m)) { dev_err(&pdev->dev, "failed to get busfreq clk\n"); return -EINVAL; } From ac3505d8ee38beb4ee5b144f27ea20e3b0d3ab44 Mon Sep 17 00:00:00 2001 From: Bai Ping Date: Wed, 13 Dec 2017 13:18:42 +0800 Subject: [PATCH 28/81] MLK-17190 driver: soc: Fix audio bus mode clock rate on imx8mq If the system is currently in low bus mode, if the audio device request the audio bus mode, the NOC, AHB and AXI bus clock rate will be set wrongly, then bus will run at very low frequency, then lead to audio playback underrun. Signed-off-by: Bai Ping Tested-by: Anson Huang (cherry picked from commit 3a2a988cc02823297d14aa9001f013adbd15f6e8) --- drivers/soc/imx/busfreq-imx8mq.c | 54 +++++++++++++++++--------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index faacc9b25be7..aa9d43aceb8f 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -114,40 +114,44 @@ static void reduce_bus_freq(void) * in the future if needed. */ if (audio_bus_count) { - clk_prepare_enable(sys1_pll_100m); + if (cur_bus_freq_mode == BUS_FREQ_HIGH) { + clk_prepare_enable(sys1_pll_100m); - update_bus_freq(LOW_BUS_FREQ_100MTS); + update_bus_freq(LOW_BUS_FREQ_100MTS); - /* correct the clock tree info */ - clk_disable_unprepare(sys1_pll_100m); - clk_set_parent(dram_alt_src, sys1_pll_100m); - clk_set_parent(dram_core_clk, dram_alt_root); - clk_set_parent(dram_apb_src, sys1_pll_40m); - clk_set_rate(dram_apb_pre_div, 20000000); - /* reduce the NOC & bus clock */ - clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); - clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); - clk_set_parent(main_axi_src, osc_25m); + /* correct the clock tree info */ + clk_disable_unprepare(sys1_pll_100m); + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + /* reduce the NOC & bus clock */ + clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); + clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); + clk_set_parent(main_axi_src, osc_25m); + } low_bus_freq_mode = 0; audio_bus_freq_mode = 1; cur_bus_freq_mode = BUS_FREQ_AUDIO; } else { - clk_prepare_enable(sys1_pll_100m); + if (cur_bus_freq_mode == BUS_FREQ_HIGH) { + clk_prepare_enable(sys1_pll_100m); - update_bus_freq(LOW_BUS_FREQ_100MTS); + update_bus_freq(LOW_BUS_FREQ_100MTS); - /* correct the clock tree info */ - clk_disable_unprepare(sys1_pll_100m); - clk_set_parent(dram_alt_src, sys1_pll_100m); - clk_set_parent(dram_core_clk, dram_alt_root); - clk_set_parent(dram_apb_src, sys1_pll_40m); - clk_set_rate(dram_apb_pre_div, 20000000); - clk_prepare_enable(sys1_pll_400m); - /* reduce the NOC & bus clock */ - clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); - clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); - clk_set_parent(main_axi_src, osc_25m); + /* correct the clock tree info */ + clk_disable_unprepare(sys1_pll_100m); + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + clk_prepare_enable(sys1_pll_400m); + /* reduce the NOC & bus clock */ + clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); + clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); + clk_set_parent(main_axi_src, osc_25m); + } low_bus_freq_mode = 1; audio_bus_freq_mode = 0; From c74f34652870bab9ccafcdd2008f8658aaad7d62 Mon Sep 17 00:00:00 2001 From: Bai Ping Date: Mon, 8 Jan 2018 16:06:55 +0800 Subject: [PATCH 29/81] MLK-17447 drivers: soc: imx: Fix busfreq mutex unlock twice on imx8mq A 'return' statement is missed before, So the mutex will be unlocked twice, in some corner case, one core will unlock the mutex that locked by anohter core wrongly. Then lead to concurrent access to the DVFS at the same time. Signed-off-by: Bai Ping Reviewed-by: Anson Huang (cherry picked from commit 659615af4d35c7f118b7cf346624d423a3b15797) --- drivers/soc/imx/busfreq-imx8mq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index aa9d43aceb8f..25e416bad74e 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -1,5 +1,5 @@ /* - * Copyright 2017 NXP + * Copyright 2017-2018 NXP * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -331,6 +331,7 @@ void release_bus_freq(enum bus_freq_mode mode) (audio_bus_count == 0)) { set_low_bus_freq(); mutex_unlock(&bus_freq_mutex); + return; } mutex_unlock(&bus_freq_mutex); From b6cb41ac999bcdfc9498a84e9bdd60b656c52a03 Mon Sep 17 00:00:00 2001 From: Bai Ping Date: Mon, 5 Feb 2018 16:32:07 +0800 Subject: [PATCH 30/81] MLK-17590-02 driver: soc: imx: update the busfreq flow on imx8mq Currently, on imx8mq evk board, we only support 3200mts and 667mts frequency setpoints. So the DDR DVFS flow need to be updated accordingly. The dram pll and dram apb clock rate is changed in ATF when doing frequency, in kernel side, we need to call the clk API to update the clock rate info in clock tree. Signed-off-by: Bai Ping Reviewed-by: Anson Huang (cherry picked from commit a69c3794f52d826762642cbdcf978a85784f386a) --- drivers/soc/imx/busfreq-imx8mq.c | 52 ++++++++++---------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index 25e416bad74e..d82640d7406a 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -38,6 +38,7 @@ #define HIGH_FREQ_3200MTS 0x0 #define AUDIO_FREQ_400MTS 0x1 #define LOW_BUS_FREQ_100MTS 0x2 +#define LOW_BUS_FREQ_667MTS 0x1 #define WAIT_BUS_FREQ_DONE 0xf static struct device *busfreq_dev; @@ -104,10 +105,6 @@ static void reduce_bus_freq(void) { high_bus_freq_mode = 0; - /* prepare the necessary clk before frequency change */ - clk_prepare_enable(sys1_pll_40m); - clk_prepare_enable(dram_alt_root); - /* * below piece of code has some redundant part, keep * it at present, we may need update the audio freq @@ -115,16 +112,16 @@ static void reduce_bus_freq(void) */ if (audio_bus_count) { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { - clk_prepare_enable(sys1_pll_100m); - update_bus_freq(LOW_BUS_FREQ_100MTS); + update_bus_freq(LOW_BUS_FREQ_667MTS); - /* correct the clock tree info */ - clk_disable_unprepare(sys1_pll_100m); - clk_set_parent(dram_alt_src, sys1_pll_100m); - clk_set_parent(dram_core_clk, dram_alt_root); - clk_set_parent(dram_apb_src, sys1_pll_40m); - clk_set_rate(dram_apb_pre_div, 20000000); + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to upate the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); /* reduce the NOC & bus clock */ clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); @@ -136,17 +133,11 @@ static void reduce_bus_freq(void) cur_bus_freq_mode = BUS_FREQ_AUDIO; } else { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { - clk_prepare_enable(sys1_pll_100m); - update_bus_freq(LOW_BUS_FREQ_100MTS); + update_bus_freq(LOW_BUS_FREQ_667MTS); - /* correct the clock tree info */ - clk_disable_unprepare(sys1_pll_100m); - clk_set_parent(dram_alt_src, sys1_pll_100m); - clk_set_parent(dram_core_clk, dram_alt_root); - clk_set_parent(dram_apb_src, sys1_pll_40m); - clk_set_rate(dram_apb_pre_div, 20000000); - clk_prepare_enable(sys1_pll_400m); + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); /* reduce the NOC & bus clock */ clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); @@ -158,13 +149,10 @@ static void reduce_bus_freq(void) cur_bus_freq_mode = BUS_FREQ_LOW; } - clk_disable_unprepare(sys1_pll_40m); - clk_disable_unprepare(dram_alt_root); - if (audio_bus_freq_mode) - printk(KERN_DEBUG "ddrc freq set to audio mode: 25MHz\n"); + printk(KERN_DEBUG "ddrc freq set to audio mode: 167MHz\n"); if (low_bus_freq_mode) - printk(KERN_DEBUG "ddrc freq set to low bus mode: 25MHz\n"); + printk(KERN_DEBUG "ddrc freq set to low bus mode: 167MHz\n"); } static void reduce_bus_freq_handler(struct work_struct *work) @@ -221,19 +209,11 @@ static int set_high_bus_freq(int high_bus_freq) if (high_bus_freq_mode) return 0; - /* enable the clks needed in frequency */ - clk_prepare_enable(sys1_pll_800m); - clk_prepare_enable(dram_pll_clk); - /* switch the DDR freqeuncy */ - update_bus_freq(0x0); + update_bus_freq(HIGH_FREQ_3200MTS); - /* correct the clock tree info */ - clk_set_parent(dram_apb_src, sys1_pll_800m); clk_set_rate(dram_apb_pre_div, 200000000); - clk_set_parent(dram_core_clk, dram_pll_clk); - clk_disable_unprepare(sys1_pll_800m); - clk_disable_unprepare(dram_pll_clk); + clk_get_rate(dram_pll_clk); clk_set_rate(noc_div, 800000000); clk_set_rate(ahb_div, 133333333); clk_set_parent(main_axi_src, sys2_pll_333m); From d5f14e4b585fbaff0dbbe57c3326095e9affd76b Mon Sep 17 00:00:00 2001 From: Bai Ping Date: Tue, 17 Jul 2018 13:27:17 +0800 Subject: [PATCH 31/81] MLK-18427-03 driver: soc: add busfreq driver support for imx8mm add busfreq support on i.MX8MM. when system is running at low bus or audio bus mode, the dram & bus clock will be reduced to a lower rate: NOC: 150MHZ, AXI: 24MHz, AXI 20MHZ, DRAM core clock: 25MHz. when system is running at high bus mode, all the bus clock and dram clock will be restore to the highest one. Signed-off-by: Bai Ping Reviewed-by: Anson Huang (cherry picked from commit 4984e653a6e86f7b6e2e6c195be53da8dcb5f8fd) --- drivers/soc/imx/busfreq-imx8mq.c | 145 +++++++++++++++++++++++++------ 1 file changed, 120 insertions(+), 25 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index d82640d7406a..a6b9d0edad83 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -113,17 +113,39 @@ static void reduce_bus_freq(void) if (audio_bus_count) { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { - update_bus_freq(LOW_BUS_FREQ_667MTS); + if (of_machine_is_compatible("fsl,imx8mq")) { + update_bus_freq(LOW_BUS_FREQ_667MTS); + + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to upate the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); + /* reduce the NOC & bus clock */ + clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); + } else { + /* prepare the necessary clk before frequency change */ + clk_prepare_enable(sys1_pll_40m); + clk_prepare_enable(dram_alt_root); + clk_prepare_enable(sys1_pll_100m); + + update_bus_freq(LOW_BUS_FREQ_100MTS); + + /* correct the clock tree info */ + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + clk_disable_unprepare(sys1_pll_100m); + clk_disable_unprepare(sys1_pll_40m); + clk_disable_unprepare(dram_alt_root); + + /* change the NOC rate */ + clk_set_rate(noc_div, clk_get_rate(noc_div) / 5); + } - /* - * the dram_apb and dram_core clk rate is changed - * in ATF side, below two lines of code is just used - * to upate the clock tree info in kernel side. - */ - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); - /* reduce the NOC & bus clock */ - clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); clk_set_parent(main_axi_src, osc_25m); } @@ -134,12 +156,34 @@ static void reduce_bus_freq(void) } else { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { - update_bus_freq(LOW_BUS_FREQ_667MTS); + if (of_machine_is_compatible("fsl,imx8mq")) { + update_bus_freq(LOW_BUS_FREQ_667MTS); + + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); + /* reduce the NOC & bus clock */ + clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); + } else { + /* prepare the necessary clk before frequency change */ + clk_prepare_enable(sys1_pll_40m); + clk_prepare_enable(dram_alt_root); + clk_prepare_enable(sys1_pll_100m); + + update_bus_freq(LOW_BUS_FREQ_100MTS); + + /* correct the clock tree info */ + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + clk_disable_unprepare(sys1_pll_100m); + clk_disable_unprepare(sys1_pll_40m); + clk_disable_unprepare(dram_alt_root); + + /* change the NOC clock rate */ + clk_set_rate(noc_div, clk_get_rate(noc_div) / 5); + } - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); - /* reduce the NOC & bus clock */ - clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); clk_set_parent(main_axi_src, osc_25m); } @@ -150,9 +194,9 @@ static void reduce_bus_freq(void) } if (audio_bus_freq_mode) - printk(KERN_DEBUG "ddrc freq set to audio mode: 167MHz\n"); + printk(KERN_DEBUG "ddrc freq set to audio bus mode\n"); if (low_bus_freq_mode) - printk(KERN_DEBUG "ddrc freq set to low bus mode: 167MHz\n"); + printk(KERN_DEBUG "ddrc freq set to low bus mode\n"); } static void reduce_bus_freq_handler(struct work_struct *work) @@ -209,12 +253,30 @@ static int set_high_bus_freq(int high_bus_freq) if (high_bus_freq_mode) return 0; - /* switch the DDR freqeuncy */ - update_bus_freq(HIGH_FREQ_3200MTS); + if (of_machine_is_compatible("fsl,imx8mq")) { + /* switch the DDR freqeuncy */ + update_bus_freq(HIGH_FREQ_3200MTS); + + clk_set_rate(dram_apb_pre_div, 200000000); + clk_get_rate(dram_pll_clk); + clk_set_rate(noc_div, 800000000); + } else { + /* enable the clks needed in frequency */ + clk_prepare_enable(sys1_pll_800m); + clk_prepare_enable(dram_pll_clk); + + /* switch the DDR freqeuncy */ + update_bus_freq(HIGH_FREQ_3200MTS); + + /* correct the clock tree info */ + clk_set_parent(dram_apb_src, sys1_pll_800m); + clk_set_rate(dram_apb_pre_div, 160000000); + clk_set_parent(dram_core_clk, dram_pll_clk); + clk_disable_unprepare(sys1_pll_800m); + clk_disable_unprepare(dram_pll_clk); + clk_set_rate(noc_div, 750000000); + } - clk_set_rate(dram_apb_pre_div, 200000000); - clk_get_rate(dram_pll_clk); - clk_set_rate(noc_div, 800000000); clk_set_rate(ahb_div, 133333333); clk_set_parent(main_axi_src, sys2_pll_333m); @@ -224,7 +286,7 @@ static int set_high_bus_freq(int high_bus_freq) cur_bus_freq_mode = BUS_FREQ_HIGH; if (high_bus_freq_mode) - printk(KERN_DEBUG "ddrc freq set to high mode: 800MHz\n"); + printk(KERN_DEBUG "ddrc freq set to high bus mode\n"); return 0; } @@ -436,7 +498,7 @@ static int init_busfreq_irq(struct platform_device *busfreq_pdev) return 0; } -static int init_busfreq_clk(struct platform_device *pdev) +static int imx8mq_init_busfreq_clk(struct platform_device *pdev) { dram_pll_clk = devm_clk_get(&pdev->dev, "dram_pll"); sys1_pll_800m = devm_clk_get(&pdev->dev, "sys1_pll_800m"); @@ -466,6 +528,35 @@ static int init_busfreq_clk(struct platform_device *pdev) return 0; } +static int imx8mm_init_busfreq_clk(struct platform_device *pdev) +{ + dram_pll_clk = devm_clk_get(&pdev->dev, "dram_pll"); + dram_alt_src = devm_clk_get(&pdev->dev, "dram_alt_src"); + dram_alt_root = devm_clk_get(&pdev->dev, "dram_alt_root"); + dram_core_clk = devm_clk_get(&pdev->dev, "dram_core"); + dram_apb_src = devm_clk_get(&pdev->dev, "dram_apb_src"); + dram_apb_pre_div = devm_clk_get(&pdev->dev, "dram_apb_pre_div"); + sys1_pll_800m = devm_clk_get(&pdev->dev, "sys_pll1_800m"); + sys1_pll_100m = devm_clk_get(&pdev->dev, "sys_pll1_100m"); + sys1_pll_40m = devm_clk_get(&pdev->dev, "sys_pll1_40m"); + noc_div = devm_clk_get(&pdev->dev, "noc_div"); + ahb_div = devm_clk_get(&pdev->dev, "ahb_div"); + main_axi_src = devm_clk_get(&pdev->dev, "main_axi_src"); + osc_25m = devm_clk_get(&pdev->dev, "osc_24m"); + sys2_pll_333m = devm_clk_get(&pdev->dev, "sys_pll2_333m"); + + if (IS_ERR(dram_pll_clk) || IS_ERR(dram_alt_src) || IS_ERR(dram_alt_root) || + IS_ERR(dram_core_clk) || IS_ERR(dram_apb_src) || IS_ERR(dram_apb_pre_div) || + IS_ERR(sys1_pll_800m) || IS_ERR(sys1_pll_100m) || IS_ERR(sys1_pll_40m) || + IS_ERR(osc_25m) || IS_ERR(noc_div) || IS_ERR(main_axi_src) || IS_ERR(ahb_div) || + IS_ERR(sys2_pll_333m)) { + dev_err(&pdev->dev, "failed to get busfreq clk\n"); + return -EINVAL; + } + + return 0; +} + /*! * This is the probe routine for the bus frequency driver. * @@ -481,7 +572,11 @@ static int busfreq_probe(struct platform_device *pdev) busfreq_dev = &pdev->dev; /* get the clock for DDRC */ - err = init_busfreq_clk(pdev); + if (of_machine_is_compatible("fsl,imx8mq")) + err = imx8mq_init_busfreq_clk(pdev); + else + err = imx8mm_init_busfreq_clk(pdev); + if (err) { dev_err(busfreq_dev, "init clk failed\n"); return err; From 1e853ac3450f87b730f60236793ac0c3ae3c4da8 Mon Sep 17 00:00:00 2001 From: Bai Ping Date: Wed, 31 Oct 2018 09:59:37 +0800 Subject: [PATCH 32/81] MLK-20136-03 driver: soc: imx: add 100mts support for imx8mq low bus mode The 100MTS low bus mode can be only supported by i.MX8MQ Rev2.1 and future TO. So necessary check is added to identify the chip revision when doing busfreq mode switch. Signed-off-by: Bai Ping Reviewed-by: Anson Huang (cherry picked from commit a906afb17d445b40f6c70fa2a2c3b6707ada0e47) --- drivers/soc/imx/busfreq-imx8mq.c | 87 ++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index a6b9d0edad83..1d261d21c601 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -32,6 +32,7 @@ #include #include #include +#include #define FSL_SIP_DDR_DVFS 0xc2000004 @@ -114,15 +115,32 @@ static void reduce_bus_freq(void) if (cur_bus_freq_mode == BUS_FREQ_HIGH) { if (of_machine_is_compatible("fsl,imx8mq")) { - update_bus_freq(LOW_BUS_FREQ_667MTS); + if (imx_get_soc_revision() < IMX_CHIP_REVISION_2_1) { + update_bus_freq(LOW_BUS_FREQ_667MTS); - /* - * the dram_apb and dram_core clk rate is changed - * in ATF side, below two lines of code is just used - * to upate the clock tree info in kernel side. - */ - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to upate the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); + } else { + /* prepare the necessary clk before frequency change */ + clk_prepare_enable(sys1_pll_40m); + clk_prepare_enable(dram_alt_root); + clk_prepare_enable(sys1_pll_100m); + + update_bus_freq(LOW_BUS_FREQ_100MTS); + + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + clk_disable_unprepare(sys1_pll_100m); + clk_disable_unprepare(sys1_pll_40m); + clk_disable_unprepare(dram_alt_root); + } /* reduce the NOC & bus clock */ clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); } else { @@ -155,12 +173,33 @@ static void reduce_bus_freq(void) cur_bus_freq_mode = BUS_FREQ_AUDIO; } else { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { - if (of_machine_is_compatible("fsl,imx8mq")) { - update_bus_freq(LOW_BUS_FREQ_667MTS); + if (imx_get_soc_revision() < IMX_CHIP_REVISION_2_1) { + update_bus_freq(LOW_BUS_FREQ_667MTS); - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to upate the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); + } else { + /* prepare the necessary clk before frequency change */ + clk_prepare_enable(sys1_pll_40m); + clk_prepare_enable(dram_alt_root); + clk_prepare_enable(sys1_pll_100m); + + update_bus_freq(LOW_BUS_FREQ_100MTS); + + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + clk_disable_unprepare(sys1_pll_100m); + clk_disable_unprepare(sys1_pll_40m); + clk_disable_unprepare(dram_alt_root); + } /* reduce the NOC & bus clock */ clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); } else { @@ -254,11 +293,27 @@ static int set_high_bus_freq(int high_bus_freq) return 0; if (of_machine_is_compatible("fsl,imx8mq")) { - /* switch the DDR freqeuncy */ - update_bus_freq(HIGH_FREQ_3200MTS); + if (imx_get_soc_revision() < IMX_CHIP_REVISION_2_1) { + /* switch the DDR freqeuncy */ + update_bus_freq(HIGH_FREQ_3200MTS); - clk_set_rate(dram_apb_pre_div, 200000000); - clk_get_rate(dram_pll_clk); + clk_set_rate(dram_apb_pre_div, 200000000); + clk_get_rate(dram_pll_clk); + } else { + /* enable the clks needed in frequency */ + clk_prepare_enable(sys1_pll_800m); + clk_prepare_enable(dram_pll_clk); + + /* switch the DDR freqeuncy */ + update_bus_freq(HIGH_FREQ_3200MTS); + + /* correct the clock tree info */ + clk_set_parent(dram_apb_src, sys1_pll_800m); + clk_set_rate(dram_apb_pre_div, 160000000); + clk_set_parent(dram_core_clk, dram_pll_clk); + clk_disable_unprepare(sys1_pll_800m); + clk_disable_unprepare(dram_pll_clk); + } clk_set_rate(noc_div, 800000000); } else { /* enable the clks needed in frequency */ From f6b12bccd77f80e6d165cdfa0b40edf3d77cdddc Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Sat, 3 Nov 2018 12:17:31 +0800 Subject: [PATCH 33/81] MLK-20203-4 soc: imx: fix coverity issue This patch fixes coverity issue of "divide by 0". Signed-off-by: Anson Huang Reviewed-by: Bai Ping (cherry picked from commit ed044f6d78156ae603dd732f15c5268d3f545605) --- drivers/soc/imx/busfreq-imx8mq.c | 45 +++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index 1d261d21c601..c734b6de2138 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -104,6 +104,8 @@ static void update_bus_freq(int target_freq) static void reduce_bus_freq(void) { + u32 rate; + high_bus_freq_mode = 0; /* @@ -142,7 +144,12 @@ static void reduce_bus_freq(void) clk_disable_unprepare(dram_alt_root); } /* reduce the NOC & bus clock */ - clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); + rate = clk_get_rate(noc_div); + if (rate == 0) { + WARN_ON(1); + return; + } + clk_set_rate(noc_div, rate / 8); } else { /* prepare the necessary clk before frequency change */ clk_prepare_enable(sys1_pll_40m); @@ -161,10 +168,19 @@ static void reduce_bus_freq(void) clk_disable_unprepare(dram_alt_root); /* change the NOC rate */ - clk_set_rate(noc_div, clk_get_rate(noc_div) / 5); + rate = clk_get_rate(noc_div); + if (rate == 0) { + WARN_ON(1); + return; + } + clk_set_rate(noc_div, rate / 5); } - - clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); + rate = clk_get_rate(ahb_div); + if (rate == 0) { + WARN_ON(1); + return; + } + clk_set_rate(ahb_div, rate / 6); clk_set_parent(main_axi_src, osc_25m); } @@ -201,7 +217,12 @@ static void reduce_bus_freq(void) clk_disable_unprepare(dram_alt_root); } /* reduce the NOC & bus clock */ - clk_set_rate(noc_div, clk_get_rate(noc_div) / 8); + rate = clk_get_rate(noc_div); + if (rate == 0) { + WARN_ON(1); + return; + } + clk_set_rate(noc_div, rate / 8); } else { /* prepare the necessary clk before frequency change */ clk_prepare_enable(sys1_pll_40m); @@ -220,10 +241,20 @@ static void reduce_bus_freq(void) clk_disable_unprepare(dram_alt_root); /* change the NOC clock rate */ - clk_set_rate(noc_div, clk_get_rate(noc_div) / 5); + rate = clk_get_rate(noc_div); + if (rate == 0) { + WARN_ON(1); + return; + } + clk_set_rate(noc_div, rate / 5); } - clk_set_rate(ahb_div, clk_get_rate(ahb_div) / 6); + rate = clk_get_rate(ahb_div); + if (rate == 0) { + WARN_ON(1); + return; + } + clk_set_rate(ahb_div, rate / 6); clk_set_parent(main_axi_src, osc_25m); } From ae8d63a1a1b0e129c19dadfe8b3bc24f8bd73b6c Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Tue, 11 Jun 2019 13:24:40 +0800 Subject: [PATCH 34/81] MLK-21985-9 Revert "MLK-20136-03 driver: soc: imx: add 100mts support for imx8mq low bus mode" This reverts commit 7560cff21b7b92127675d5e955874af2827a9bca. drivers/soc/imx/busfreq-imx8mq.o: In function `reduce_bus_freq': /home/b29396/Work/linux/dash-linux-devel/build_v8/../drivers/soc/imx/busfreq-imx8mq.c:193: undefined reference to `imx_get_soc_revision' /home/b29396/Work/linux/dash-linux-devel/build_v8/../drivers/soc/imx/busfreq-imx8mq.c:120: undefined reference to `imx_get_soc_revision' drivers/soc/imx/busfreq-imx8mq.o: In function `set_high_bus_freq': /home/b29396/Work/linux/dash-linux-devel/build_v8/../drivers/soc/imx/busfreq-imx8mq.c:327: undefined reference to `imx_get_soc_revision' /home/b29396/Work/linux/dash-linux-devel/Makefile:1052: recipe for target 'vmlinux' failed make[1]: *** [vmlinux] Error 1 upstream kernel did not export imx_get_soc_revision for mx8. Need find a better way to support for both mx8m and mx8. --- drivers/soc/imx/busfreq-imx8mq.c | 87 ++++++-------------------------- 1 file changed, 16 insertions(+), 71 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index c734b6de2138..aa4037e223eb 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -32,7 +32,6 @@ #include #include #include -#include #define FSL_SIP_DDR_DVFS 0xc2000004 @@ -117,32 +116,15 @@ static void reduce_bus_freq(void) if (cur_bus_freq_mode == BUS_FREQ_HIGH) { if (of_machine_is_compatible("fsl,imx8mq")) { - if (imx_get_soc_revision() < IMX_CHIP_REVISION_2_1) { - update_bus_freq(LOW_BUS_FREQ_667MTS); + update_bus_freq(LOW_BUS_FREQ_667MTS); - /* - * the dram_apb and dram_core clk rate is changed - * in ATF side, below two lines of code is just used - * to upate the clock tree info in kernel side. - */ - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); - } else { - /* prepare the necessary clk before frequency change */ - clk_prepare_enable(sys1_pll_40m); - clk_prepare_enable(dram_alt_root); - clk_prepare_enable(sys1_pll_100m); - - update_bus_freq(LOW_BUS_FREQ_100MTS); - - clk_set_parent(dram_alt_src, sys1_pll_100m); - clk_set_parent(dram_core_clk, dram_alt_root); - clk_set_parent(dram_apb_src, sys1_pll_40m); - clk_set_rate(dram_apb_pre_div, 20000000); - clk_disable_unprepare(sys1_pll_100m); - clk_disable_unprepare(sys1_pll_40m); - clk_disable_unprepare(dram_alt_root); - } + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to upate the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); /* reduce the NOC & bus clock */ rate = clk_get_rate(noc_div); if (rate == 0) { @@ -189,33 +171,12 @@ static void reduce_bus_freq(void) cur_bus_freq_mode = BUS_FREQ_AUDIO; } else { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { + if (of_machine_is_compatible("fsl,imx8mq")) { - if (imx_get_soc_revision() < IMX_CHIP_REVISION_2_1) { - update_bus_freq(LOW_BUS_FREQ_667MTS); + update_bus_freq(LOW_BUS_FREQ_667MTS); - /* - * the dram_apb and dram_core clk rate is changed - * in ATF side, below two lines of code is just used - * to upate the clock tree info in kernel side. - */ - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); - } else { - /* prepare the necessary clk before frequency change */ - clk_prepare_enable(sys1_pll_40m); - clk_prepare_enable(dram_alt_root); - clk_prepare_enable(sys1_pll_100m); - - update_bus_freq(LOW_BUS_FREQ_100MTS); - - clk_set_parent(dram_alt_src, sys1_pll_100m); - clk_set_parent(dram_core_clk, dram_alt_root); - clk_set_parent(dram_apb_src, sys1_pll_40m); - clk_set_rate(dram_apb_pre_div, 20000000); - clk_disable_unprepare(sys1_pll_100m); - clk_disable_unprepare(sys1_pll_40m); - clk_disable_unprepare(dram_alt_root); - } + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); /* reduce the NOC & bus clock */ rate = clk_get_rate(noc_div); if (rate == 0) { @@ -324,27 +285,11 @@ static int set_high_bus_freq(int high_bus_freq) return 0; if (of_machine_is_compatible("fsl,imx8mq")) { - if (imx_get_soc_revision() < IMX_CHIP_REVISION_2_1) { - /* switch the DDR freqeuncy */ - update_bus_freq(HIGH_FREQ_3200MTS); + /* switch the DDR freqeuncy */ + update_bus_freq(HIGH_FREQ_3200MTS); - clk_set_rate(dram_apb_pre_div, 200000000); - clk_get_rate(dram_pll_clk); - } else { - /* enable the clks needed in frequency */ - clk_prepare_enable(sys1_pll_800m); - clk_prepare_enable(dram_pll_clk); - - /* switch the DDR freqeuncy */ - update_bus_freq(HIGH_FREQ_3200MTS); - - /* correct the clock tree info */ - clk_set_parent(dram_apb_src, sys1_pll_800m); - clk_set_rate(dram_apb_pre_div, 160000000); - clk_set_parent(dram_core_clk, dram_pll_clk); - clk_disable_unprepare(sys1_pll_800m); - clk_disable_unprepare(dram_pll_clk); - } + clk_set_rate(dram_apb_pre_div, 200000000); + clk_get_rate(dram_pll_clk); clk_set_rate(noc_div, 800000000); } else { /* enable the clks needed in frequency */ From e3e8dee20e9a4a9213e899df67e41ced089b1fa1 Mon Sep 17 00:00:00 2001 From: Leonard Crestez Date: Mon, 17 Jun 2019 15:27:54 +0300 Subject: [PATCH 35/81] MLK-20136-03 driver: soc: imx: add 100mts support for imx8mq low bus mode The 100MTS low bus mode can be only supported by i.MX8MQ Rev2.1 and future TO. So necessary check is added to identify the chip revision when doing busfreq mode switch. Signed-off-by: Bai Ping Reviewed-by: Anson Huang Use soc_device_metch instead of global imx_get_soc_revision Signed-off-by: Leonard Crestez --- drivers/soc/imx/busfreq-imx8mq.c | 105 ++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 16 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index aa4037e223eb..624c638b5eb0 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -32,6 +32,7 @@ #include #include #include +#include #define FSL_SIP_DDR_DVFS 0xc2000004 @@ -101,6 +102,24 @@ static void update_bus_freq(int target_freq) local_irq_enable(); } +/* Match B0 or older */ +static const struct soc_device_attribute imx8mq_b0_older_soc_match[] = { + { + .soc_id = "i.MX8MQ", + .revision = "2.0", + }, + { + .soc_id = "i.MX8MQ", + .revision = "1.*", + }, + { /* sentinel */ } +}; + +static inline bool imx8mq_supports_100mts(void) +{ + return !soc_device_match(imx8mq_b0_older_soc_match); +} + static void reduce_bus_freq(void) { u32 rate; @@ -116,15 +135,32 @@ static void reduce_bus_freq(void) if (cur_bus_freq_mode == BUS_FREQ_HIGH) { if (of_machine_is_compatible("fsl,imx8mq")) { - update_bus_freq(LOW_BUS_FREQ_667MTS); + if (!imx8mq_supports_100mts()) { + update_bus_freq(LOW_BUS_FREQ_667MTS); - /* - * the dram_apb and dram_core clk rate is changed - * in ATF side, below two lines of code is just used - * to upate the clock tree info in kernel side. - */ - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to upate the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); + } else { + /* prepare the necessary clk before frequency change */ + clk_prepare_enable(sys1_pll_40m); + clk_prepare_enable(dram_alt_root); + clk_prepare_enable(sys1_pll_100m); + + update_bus_freq(LOW_BUS_FREQ_100MTS); + + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + clk_disable_unprepare(sys1_pll_100m); + clk_disable_unprepare(sys1_pll_40m); + clk_disable_unprepare(dram_alt_root); + } /* reduce the NOC & bus clock */ rate = clk_get_rate(noc_div); if (rate == 0) { @@ -171,12 +207,33 @@ static void reduce_bus_freq(void) cur_bus_freq_mode = BUS_FREQ_AUDIO; } else { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { - if (of_machine_is_compatible("fsl,imx8mq")) { - update_bus_freq(LOW_BUS_FREQ_667MTS); + if (!imx8mq_supports_100mts()) { + update_bus_freq(LOW_BUS_FREQ_667MTS); - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to upate the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); + } else { + /* prepare the necessary clk before frequency change */ + clk_prepare_enable(sys1_pll_40m); + clk_prepare_enable(dram_alt_root); + clk_prepare_enable(sys1_pll_100m); + + update_bus_freq(LOW_BUS_FREQ_100MTS); + + clk_set_parent(dram_alt_src, sys1_pll_100m); + clk_set_parent(dram_core_clk, dram_alt_root); + clk_set_parent(dram_apb_src, sys1_pll_40m); + clk_set_rate(dram_apb_pre_div, 20000000); + clk_disable_unprepare(sys1_pll_100m); + clk_disable_unprepare(sys1_pll_40m); + clk_disable_unprepare(dram_alt_root); + } /* reduce the NOC & bus clock */ rate = clk_get_rate(noc_div); if (rate == 0) { @@ -285,11 +342,27 @@ static int set_high_bus_freq(int high_bus_freq) return 0; if (of_machine_is_compatible("fsl,imx8mq")) { - /* switch the DDR freqeuncy */ - update_bus_freq(HIGH_FREQ_3200MTS); + if (!imx8mq_supports_100mts()) { + /* switch the DDR freqeuncy */ + update_bus_freq(HIGH_FREQ_3200MTS); - clk_set_rate(dram_apb_pre_div, 200000000); - clk_get_rate(dram_pll_clk); + clk_set_rate(dram_apb_pre_div, 200000000); + clk_get_rate(dram_pll_clk); + } else { + /* enable the clks needed in frequency */ + clk_prepare_enable(sys1_pll_800m); + clk_prepare_enable(dram_pll_clk); + + /* switch the DDR freqeuncy */ + update_bus_freq(HIGH_FREQ_3200MTS); + + /* correct the clock tree info */ + clk_set_parent(dram_apb_src, sys1_pll_800m); + clk_set_rate(dram_apb_pre_div, 160000000); + clk_set_parent(dram_core_clk, dram_pll_clk); + clk_disable_unprepare(sys1_pll_800m); + clk_disable_unprepare(dram_pll_clk); + } clk_set_rate(noc_div, 800000000); } else { /* enable the clks needed in frequency */ From 194fc9c9db00ae5123b441638b7724eb152f6bd3 Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Tue, 2 Jul 2019 17:34:52 +0800 Subject: [PATCH 36/81] =?UTF-8?q?Revert=20"of/fdt:=20Fix=20=E2=80=98of=5Ff?= =?UTF-8?q?dt=5Fmatch=E2=80=99=20defined=20but=20not=20used=20compiler=20w?= =?UTF-8?q?arning"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5d9c4e9591dc0cce80dac170cbdc9015e34b074a. Those APIs are used by PM codes --- drivers/of/fdt.c | 99 ++++++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 223d617ecfe1..114269a3227e 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -79,6 +79,38 @@ void __init of_fdt_limit_memory(int limit) } } +/** + * of_fdt_is_compatible - Return true if given node from the given blob has + * compat in its compatible list + * @blob: A device tree blob + * @node: node to test + * @compat: compatible string to compare with compatible list. + * + * On match, returns a non-zero value with smaller values returned for more + * specific compatible values. + */ +static int of_fdt_is_compatible(const void *blob, + unsigned long node, const char *compat) +{ + const char *cp; + int cplen; + unsigned long l, score = 0; + + cp = fdt_getprop(blob, node, "compatible", &cplen); + if (cp == NULL) + return 0; + while (cplen > 0) { + score++; + if (of_compat_cmp(cp, compat, strlen(compat)) == 0) + return score; + l = strlen(cp) + 1; + cp += l; + cplen -= l; + } + + return 0; +} + static bool of_fdt_device_is_available(const void *blob, unsigned long node) { const char *status = fdt_getprop(blob, node, "status", NULL); @@ -92,6 +124,27 @@ static bool of_fdt_device_is_available(const void *blob, unsigned long node) return false; } +/** + * of_fdt_match - Return true if node matches a list of compatible values + */ +static int __init of_fdt_match(const void *blob, unsigned long node, + const char *const *compat) +{ + unsigned int tmp, score = 0; + + if (!compat) + return 0; + + while (*compat) { + tmp = of_fdt_is_compatible(blob, node, *compat); + if (tmp && (score == 0 || (tmp < score))) + score = tmp; + compat++; + } + + return score; +} + static void *unflatten_dt_alloc(void **mem, unsigned long size, unsigned long align) { @@ -713,38 +766,6 @@ const void *__init of_get_flat_dt_prop(unsigned long node, const char *name, return fdt_getprop(initial_boot_params, node, name, size); } -/** - * of_fdt_is_compatible - Return true if given node from the given blob has - * compat in its compatible list - * @blob: A device tree blob - * @node: node to test - * @compat: compatible string to compare with compatible list. - * - * On match, returns a non-zero value with smaller values returned for more - * specific compatible values. - */ -static int of_fdt_is_compatible(const void *blob, - unsigned long node, const char *compat) -{ - const char *cp; - int cplen; - unsigned long l, score = 0; - - cp = fdt_getprop(blob, node, "compatible", &cplen); - if (cp == NULL) - return 0; - while (cplen > 0) { - score++; - if (of_compat_cmp(cp, compat, strlen(compat)) == 0) - return score; - l = strlen(cp) + 1; - cp += l; - cplen -= l; - } - - return 0; -} - /** * of_flat_dt_is_compatible - Return true if given node has compat in compatible list * @node: node to test @@ -760,19 +781,7 @@ int __init of_flat_dt_is_compatible(unsigned long node, const char *compat) */ static int __init of_flat_dt_match(unsigned long node, const char *const *compat) { - unsigned int tmp, score = 0; - - if (!compat) - return 0; - - while (*compat) { - tmp = of_fdt_is_compatible(initial_boot_params, node, *compat); - if (tmp && (score == 0 || (tmp < score))) - score = tmp; - compat++; - } - - return score; + return of_fdt_match(initial_boot_params, node, compat); } /** From c54a998d8d7202d00cda7d133530f0a921032f9c Mon Sep 17 00:00:00 2001 From: Dong Aisheng Date: Tue, 2 Jul 2019 17:35:31 +0800 Subject: [PATCH 37/81] Revert "of/fdt: Remove dead code and mark functions with __init" This reverts commit 9b4d2b635bd0cf8dfc45223f66fd85792fd2dc7b. Those APIs are needed by i.MX PM codes --- drivers/of/fdt.c | 37 ++++++++++++++++++++++++++++++++----- include/linux/of_fdt.h | 11 +++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 114269a3227e..0219e8c01650 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -39,7 +39,7 @@ * memory entries in the /memory node. This function may be called * any time after initial_boot_param is set. */ -void __init of_fdt_limit_memory(int limit) +void of_fdt_limit_memory(int limit) { int memory; int len; @@ -111,6 +111,25 @@ static int of_fdt_is_compatible(const void *blob, return 0; } +/** + * of_fdt_is_big_endian - Return true if given node needs BE MMIO accesses + * @blob: A device tree blob + * @node: node to test + * + * Returns true if the node has a "big-endian" property, or if the kernel + * was compiled for BE *and* the node has a "native-endian" property. + * Returns false otherwise. + */ +bool of_fdt_is_big_endian(const void *blob, unsigned long node) +{ + if (fdt_getprop(blob, node, "big-endian", NULL)) + return true; + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) && + fdt_getprop(blob, node, "native-endian", NULL)) + return true; + return false; +} + static bool of_fdt_device_is_available(const void *blob, unsigned long node) { const char *status = fdt_getprop(blob, node, "status", NULL); @@ -127,8 +146,8 @@ static bool of_fdt_device_is_available(const void *blob, unsigned long node) /** * of_fdt_match - Return true if node matches a list of compatible values */ -static int __init of_fdt_match(const void *blob, unsigned long node, - const char *const *compat) +int of_fdt_match(const void *blob, unsigned long node, + const char *const *compat) { unsigned int tmp, score = 0; @@ -741,7 +760,7 @@ int __init of_scan_flat_dt_subnodes(unsigned long parent, * @return offset of the subnode, or -FDT_ERR_NOTFOUND if there is none */ -int __init of_get_flat_dt_subnode_by_name(unsigned long node, const char *uname) +int of_get_flat_dt_subnode_by_name(unsigned long node, const char *uname) { return fdt_subnode_offset(initial_boot_params, node, uname); } @@ -754,6 +773,14 @@ unsigned long __init of_get_flat_dt_root(void) return 0; } +/** + * of_get_flat_dt_size - Return the total size of the FDT + */ +int __init of_get_flat_dt_size(void) +{ + return fdt_totalsize(initial_boot_params); +} + /** * of_get_flat_dt_prop - Given a node in the flat blob, return the property ptr * @@ -779,7 +806,7 @@ int __init of_flat_dt_is_compatible(unsigned long node, const char *compat) /** * of_flat_dt_match - Return true if node matches a list of compatible values */ -static int __init of_flat_dt_match(unsigned long node, const char *const *compat) +int __init of_flat_dt_match(unsigned long node, const char *const *compat) { return of_fdt_match(initial_boot_params, node, compat); } diff --git a/include/linux/of_fdt.h b/include/linux/of_fdt.h index acf820e88952..a713e5d156d8 100644 --- a/include/linux/of_fdt.h +++ b/include/linux/of_fdt.h @@ -23,6 +23,15 @@ struct device_node; /* For scanning an arbitrary device-tree at any time */ +extern char *of_fdt_get_string(const void *blob, u32 offset); +extern void *of_fdt_get_property(const void *blob, + unsigned long node, + const char *name, + int *size); +extern bool of_fdt_is_big_endian(const void *blob, + unsigned long node); +extern int of_fdt_match(const void *blob, unsigned long node, + const char *const *compat); extern void *of_fdt_unflatten_tree(const unsigned long *blob, struct device_node *dad, struct device_node **mynodes); @@ -55,7 +64,9 @@ extern int of_get_flat_dt_subnode_by_name(unsigned long node, extern const void *of_get_flat_dt_prop(unsigned long node, const char *name, int *size); extern int of_flat_dt_is_compatible(unsigned long node, const char *name); +extern int of_flat_dt_match(unsigned long node, const char *const *matches); extern unsigned long of_get_flat_dt_root(void); +extern int of_get_flat_dt_size(void); extern uint32_t of_get_flat_dt_phandle(unsigned long node); extern int early_init_dt_scan_chosen(unsigned long node, const char *uname, From 8edeb92188dd63191a225d09554c9eede1dfbcc8 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 22 Jul 2019 16:48:35 +0800 Subject: [PATCH 38/81] arm: arch: enable rpmsg for imx amp soc Enable the RPMSG for iMX AMP SOC. Signed-off-by: Richard Zhu --- arch/arm/mach-imx/Kconfig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index 9b424cbe5b55..0195ee967c2e 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -60,6 +60,9 @@ config HAVE_IMX_BUSFREQ config HAVE_IMX_MU bool +config HAVE_IMX_RPMSG + bool + config HAVE_IMX_SRC def_bool y if SMP select ARCH_HAS_RESET_CONTROLLER @@ -531,6 +534,7 @@ config SOC_IMX6SX select HAVE_IMX_AMP select SOC_IMX6 select HAVE_IMX_MU + select HAVE_IMX_RPMSG select IMX_SEMA4 select KEYBOARD_SNVS_PWRKEY @@ -569,6 +573,7 @@ config SOC_IMX7D_CA7 select IMX_GPCV2 select HAVE_IMX_DDRC select HAVE_IMX_MU + select HAVE_IMX_RPMSG select HAVE_IMX_GPCV2 select KEYBOARD_SNVS_PWRKEY From 4a8eb0f5948087f052a3ce2dd7af480c194285b3 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 25 Jul 2019 12:23:00 +0800 Subject: [PATCH 39/81] arm: imx: rename the compatible node of lpm mu driver Because that the mailbox MU driver is used in i.MX RPMSG implementation. There is a confliction between this MU driver and the mailbox MU driver. To back-compaible with LPM of iMX6SX, iMX7D and iMX7ULP. Rename the compatible node of this MU driver. Signed-off-by: Richard Zhu --- arch/arm/mach-imx/mu.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/arch/arm/mach-imx/mu.c b/arch/arm/mach-imx/mu.c index 4ab7ef2f9d62..1a250b07b256 100644 --- a/arch/arm/mach-imx/mu.c +++ b/arch/arm/mach-imx/mu.c @@ -334,24 +334,24 @@ static int imx_mu_probe(struct platform_device *pdev) struct device_node *np; struct device *dev = &pdev->dev; - np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-mu"); + np = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-mu-lp"); mu_base = of_iomap(np, 0); WARN_ON(!mu_base); - ret = of_device_is_compatible(np, "fsl,imx7ulp-mu"); + ret = of_device_is_compatible(np, "fsl,imx7ulp-mu-lp"); if (ret) irq = platform_get_irq(pdev, 1); else irq = platform_get_irq(pdev, 0); ret = request_irq(irq, imx_mu_isr, - IRQF_EARLY_RESUME | IRQF_SHARED, "imx-mu", dev); + IRQF_NO_SUSPEND | IRQF_SHARED, "imx-mu-lp", dev); if (ret) { pr_err("%s: register interrupt %d failed, rc %d\n", __func__, irq, ret); return ret; } - ret = of_device_is_compatible(np, "fsl,imx7d-mu"); + ret = of_device_is_compatible(np, "fsl,imx7d-mu-lp"); if (ret) { clk = devm_clk_get(&pdev->dev, "mu"); if (IS_ERR(clk)) { @@ -390,9 +390,9 @@ static int imx_mu_probe(struct platform_device *pdev) } static const struct of_device_id imx_mu_ids[] = { - { .compatible = "fsl,imx6sx-mu" }, - { .compatible = "fsl,imx7d-mu" }, - { .compatible = "fsl,imx7ulp-mu" }, + { .compatible = "fsl,imx6sx-mu-lp" }, + { .compatible = "fsl,imx7d-mu-lp" }, + { .compatible = "fsl,imx7ulp-mu-lp" }, { } }; @@ -419,7 +419,7 @@ static const struct dev_pm_ops mu_pm_ops = { static struct platform_driver imx_mu_driver = { .driver = { - .name = "imx-mu", + .name = "imx-mu-lp", .owner = THIS_MODULE, .pm = &mu_pm_ops, .of_match_table = imx_mu_ids, From 7d4e79f327aab923a353f836596e7ea18c7ac287 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Tue, 6 Aug 2019 14:51:07 +0800 Subject: [PATCH 40/81] soc: imx: imx-scu: Use SoC name as soc_id Use SoC name as soc_id instead of number passed from SCU firmware. Signed-off-by: Anson Huang --- drivers/soc/imx/soc-imx-scu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/soc/imx/soc-imx-scu.c b/drivers/soc/imx/soc-imx-scu.c index c68882eb80f7..0448c08a4421 100644 --- a/drivers/soc/imx/soc-imx-scu.c +++ b/drivers/soc/imx/soc-imx-scu.c @@ -114,9 +114,10 @@ static int imx_scu_soc_probe(struct platform_device *pdev) /* format soc_id value passed from SCU firmware */ val = id & 0x1f; - soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "0x%x", val); - if (!soc_dev_attr->soc_id) - return -ENOMEM; + if (of_machine_is_compatible("fsl,imx8qm")) + soc_dev_attr->soc_id = "i.MX8QM"; + else if (of_machine_is_compatible("fsl,imx8qxp")) + soc_dev_attr->soc_id = "i.MX8QXP"; /* format revision value passed from SCU firmware */ val = (id >> 5) & 0xf; From a9796ede8aa9adbafa01f7d359882d20f206a005 Mon Sep 17 00:00:00 2001 From: Robin Gong Date: Wed, 7 Aug 2019 17:48:51 +0800 Subject: [PATCH 41/81] ARM: mach-imx: add heartbeat driver for i.mx7ulp Add heartbeat driver for i.mx7ulp. Signed-off-by: Robin Gong --- arch/arm/mach-imx/Makefile | 2 +- arch/arm/mach-imx/pm-rpmsg.c | 352 +++++++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 arch/arm/mach-imx/pm-rpmsg.c diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 174ce1cd8007..b069646ed8c1 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -34,7 +34,7 @@ AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a AFLAGS_imx6ul_low_power_idle.o :=-Wa,-march=armv7-a AFLAGS_imx6ull_low_power_idle.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6ul.o imx6ul_low_power_idle.o imx6ull_low_power_idle.o -obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o +obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o pm-rpmsg.o AFLAGS_imx7d_low_power_idle.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX7D_CA7) += cpuidle-imx7d.o imx7d_low_power_idle.o endif diff --git a/arch/arm/mach-imx/pm-rpmsg.c b/arch/arm/mach-imx/pm-rpmsg.c new file mode 100644 index 000000000000..52c2f8b402bd --- /dev/null +++ b/arch/arm/mach-imx/pm-rpmsg.c @@ -0,0 +1,352 @@ +/* + * Copyright 2017-2018 NXP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +#define RPMSG_TIMEOUT 1000 + +#define PM_RPMSG_TYPE 0 +#define HEATBEAT_RPMSG_TYPE 2 + +enum pm_rpmsg_cmd { + PM_RPMSG_MODE, + PM_RPMSG_HEART_BEAT, + PM_RPMSG_HEART_BEAT_OFF, +}; + +enum pm_rpmsg_power_mode { + PM_RPMSG_HSRUN, + PM_RPMSG_RUN, + PM_RPMSG_VLPR, + PM_RPMSG_WAIT, + PM_RPMSG_VLPS, + PM_RPMSG_VLLS, + PM_RPMSG_REBOOT, + PM_RPMSG_SHUTDOWN, +}; + +struct pm_rpmsg_info { + struct rpmsg_device *rpdev; + struct device *dev; + struct pm_rpmsg_data *msg; + struct pm_qos_request pm_qos_req; + struct notifier_block restart_handler; + struct completion cmd_complete; + bool first_flag; + struct mutex lock; +}; + +static struct pm_rpmsg_info pm_rpmsg; + +static struct delayed_work heart_beat_work; + +static bool heartbeat_off; + +struct pm_rpmsg_data { + struct imx_rpmsg_head header; + u8 data; +} __attribute__ ((packed)); + +static int pm_send_message(struct pm_rpmsg_data *msg, + struct pm_rpmsg_info *info, bool ack) +{ + int err; + + if (!info->rpdev) { + dev_dbg(info->dev, + "rpmsg channel not ready, m4 image ready?\n"); + return -EINVAL; + } + + mutex_lock(&info->lock); + pm_qos_add_request(&info->pm_qos_req, + PM_QOS_CPU_DMA_LATENCY, 0); + + reinit_completion(&info->cmd_complete); + + err = rpmsg_send(info->rpdev->ept, (void *)msg, + sizeof(struct pm_rpmsg_data)); + + if (err) { + dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err); + goto err_out; + } + + if (ack) { + err = wait_for_completion_timeout(&info->cmd_complete, + msecs_to_jiffies(RPMSG_TIMEOUT)); + if (!err) { + dev_err(&info->rpdev->dev, "rpmsg_send timeout!\n"); + err = -ETIMEDOUT; + goto err_out; + } + + if (info->msg->data != 0) { + dev_err(&info->rpdev->dev, "rpmsg not ack %d!\n", + info->msg->data); + err = -EINVAL; + goto err_out; + } + + err = 0; + } + +err_out: + pm_qos_remove_request(&info->pm_qos_req); + mutex_unlock(&info->lock); + + return err; +} + +static int pm_vlls_notify_m4(bool enter) +{ + struct pm_rpmsg_data msg; + + msg.header.cate = IMX_RMPSG_LIFECYCLE; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = PM_RPMSG_TYPE; + msg.header.cmd = PM_RPMSG_MODE; + msg.data = enter ? PM_RPMSG_VLLS : PM_RPMSG_RUN; + + return pm_send_message(&msg, &pm_rpmsg, true); +} + +void pm_shutdown_notify_m4(void) +{ + struct pm_rpmsg_data msg; + + msg.header.cate = IMX_RMPSG_LIFECYCLE; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = PM_RPMSG_TYPE; + msg.header.cmd = PM_RPMSG_MODE; + msg.data = PM_RPMSG_SHUTDOWN; + /* No ACK from M4 */ + pm_send_message(&msg, &pm_rpmsg, false); +} + +void pm_reboot_notify_m4(void) +{ + struct pm_rpmsg_data msg; + + msg.header.cate = IMX_RMPSG_LIFECYCLE; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = PM_RPMSG_TYPE; + msg.header.cmd = PM_RPMSG_MODE; + msg.data = PM_RPMSG_REBOOT; + + pm_send_message(&msg, &pm_rpmsg, true); + +} + +void pm_heartbeat_off_notify_m4(bool enter) +{ + struct pm_rpmsg_data msg; + + msg.header.cate = IMX_RMPSG_LIFECYCLE; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = PM_RPMSG_TYPE; + msg.header.cmd = PM_RPMSG_HEART_BEAT_OFF; + msg.data = enter ? 0 : 1; + + pm_send_message(&msg, &pm_rpmsg, true); +} + +static void pm_heart_beat_work_handler(struct work_struct *work) +{ + struct pm_rpmsg_data msg; + + /* Notify M4 side A7 in RUN mode at boot time */ + if (pm_rpmsg.first_flag) { + pm_vlls_notify_m4(false); + + pm_heartbeat_off_notify_m4(heartbeat_off); + + pm_rpmsg.first_flag = false; + } + + if (!heartbeat_off) { + msg.header.cate = IMX_RMPSG_LIFECYCLE; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = HEATBEAT_RPMSG_TYPE; + msg.header.cmd = PM_RPMSG_HEART_BEAT; + msg.data = 0; + pm_send_message(&msg, &pm_rpmsg, false); + + schedule_delayed_work(&heart_beat_work, + msecs_to_jiffies(30000)); + } +} + +static void pm_poweroff_rpmsg(void) +{ + pm_shutdown_notify_m4(); + pr_emerg("Unable to poweroff system\n"); +} + +static int pm_restart_handler(struct notifier_block *this, unsigned long mode, + void *cmd) +{ + pm_reboot_notify_m4(); + + return NOTIFY_DONE; +} + +static int pm_rpmsg_probe(struct rpmsg_device *rpdev) +{ + int ret; + + pm_rpmsg.rpdev = rpdev; + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + init_completion(&pm_rpmsg.cmd_complete); + mutex_init(&pm_rpmsg.lock); + + INIT_DELAYED_WORK(&heart_beat_work, + pm_heart_beat_work_handler); + + pm_rpmsg.first_flag = true; + schedule_delayed_work(&heart_beat_work, 0); + + pm_rpmsg.restart_handler.notifier_call = pm_restart_handler; + pm_rpmsg.restart_handler.priority = 128; + ret = register_restart_handler(&pm_rpmsg.restart_handler); + if (ret) + dev_err(&rpdev->dev, "cannot register restart handler\n"); + + pm_power_off = pm_poweroff_rpmsg; + + return 0; +} + +static int pm_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct pm_rpmsg_data *msg = (struct pm_rpmsg_data *)data; + + pm_rpmsg.msg = msg; + + complete(&pm_rpmsg.cmd_complete); + + return 0; +} + +static void pm_rpmsg_remove(struct rpmsg_device *rpdev) +{ + dev_info(&rpdev->dev, "pm rpmsg driver is removed\n"); +} + +static struct rpmsg_device_id pm_rpmsg_id_table[] = { + { .name = "rpmsg-life-cycle-channel" }, + { }, +}; + +static struct rpmsg_driver pm_rpmsg_driver = { + .drv.name = "pm_rpmsg", + .drv.owner = THIS_MODULE, + .id_table = pm_rpmsg_id_table, + .probe = pm_rpmsg_probe, + .callback = pm_rpmsg_cb, + .remove = pm_rpmsg_remove, +}; + +#ifdef CONFIG_PM_SLEEP +static int pm_heartbeat_suspend(struct device *dev) +{ + int err; + + err = pm_vlls_notify_m4(true); + if (err) + return err; + + cancel_delayed_work_sync(&heart_beat_work); + + return 0; +} + +static int pm_heartbeat_resume(struct device *dev) +{ + int err; + + err = pm_vlls_notify_m4(false); + if (err) + return err; + + schedule_delayed_work(&heart_beat_work, + msecs_to_jiffies(10000)); + + return 0; +} +#endif + +static int pm_heartbeat_probe(struct platform_device *pdev) +{ + platform_set_drvdata(pdev, &pm_rpmsg); + + return register_rpmsg_driver(&pm_rpmsg_driver); +} + +static const struct of_device_id pm_heartbeat_id[] = { + {"fsl,heartbeat-rpmsg",}, + {}, +}; +MODULE_DEVICE_TABLE(of, pm_heartbeat_id); + +static const struct dev_pm_ops pm_heartbeat_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_heartbeat_suspend, + pm_heartbeat_resume) +}; + +static struct platform_driver pm_heartbeat_driver = { + .driver = { + .name = "heartbeat-rpmsg", + .owner = THIS_MODULE, + .of_match_table = pm_heartbeat_id, + .pm = &pm_heartbeat_ops, + }, + .probe = pm_heartbeat_probe, +}; + +static int __init setup_heartbeat(char *str) +{ + heartbeat_off = true; + + return 1; +}; +__setup("heartbeat_off", setup_heartbeat); + +module_platform_driver(pm_heartbeat_driver); + +MODULE_DESCRIPTION("Freescale PM rpmsg driver"); +MODULE_AUTHOR("Anson Huang "); +MODULE_LICENSE("GPL"); From 32fd54d13fea8e74be8f45bda9a79bd3d11486f0 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Tue, 6 Aug 2019 16:34:48 +0800 Subject: [PATCH 42/81] MLK-22404-04: arm64: Enable IMX8M_PM_DOMAINS config for ARCH_MXC platform The imx8m_pm_domain driver is used by i.MX8M SOC family, so enable the IMX8M_PM_DOMAINS config for ARCH_MXC platform by default. Signed-off-by: Jacky Bai --- arch/arm64/Kconfig.platforms | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 312089d0c9aa..577c8ae2a0c2 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -173,6 +173,7 @@ config ARCH_MXC select IMX_GPCV2_PM_DOMAINS select HAVE_IMX_BUSFREQ select IMX8M_BUSFREQ + select IMX8M_PM_DOMAINS select PM select PM_GENERIC_DOMAINS select SOC_BUS From 6f233c715c2043d90dee86720c1ed84a2b7c504e Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 7 Aug 2019 18:29:17 +0800 Subject: [PATCH 43/81] ARM: imx: Add i.MX7ULP suspend/resume support Add i.MX7ULP suspend/resume support, including standby mode and mem mode, mapped to VLPS and VLLS mode. Signed-off-by: Anson Huang --- arch/arm/mach-imx/Makefile | 2 + arch/arm/mach-imx/common.h | 7 + arch/arm/mach-imx/hardware.h | 1 + arch/arm/mach-imx/mach-imx7ulp.c | 18 + arch/arm/mach-imx/mx7ulp.h | 63 +++ arch/arm/mach-imx/pm-imx7ulp.c | 788 +++++++++++++++++++++++++++- arch/arm/mach-imx/pm-rpmsg.c | 1 + arch/arm/mach-imx/suspend-imx7ulp.S | 625 ++++++++++++++++++++++ 8 files changed, 1486 insertions(+), 19 deletions(-) create mode 100644 arch/arm/mach-imx/mx7ulp.h create mode 100644 arch/arm/mach-imx/suspend-imx7ulp.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index b069646ed8c1..98a07af52779 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -113,8 +113,10 @@ AFLAGS_ddr3_freq_imx6sx.o :=-Wa,-march=armv7-a ifeq ($(CONFIG_SUSPEND),y) AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a AFLAGS_suspend-imx7.o :=-Wa,-march=armv7-a +AFLAGS_suspend-imx7ulp.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o obj-$(CONFIG_SOC_IMX53) += suspend-imx53.o +obj-$(CONFIG_SOC_IMX7ULP) += suspend-imx7ulp.o endif obj-$(CONFIG_SOC_IMX6) += pm-imx6.o diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 1a2a9676fa41..acdde60009f7 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -159,6 +159,9 @@ int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode); void imx_busfreq_map_io(void); void imx7_pm_map_io(void); void imx6_pm_map_io(void); +void imx7ulp_pm_map_io(void); +void imx7ulp_enable_nmi(void); +void imx7ulp_poweroff(void); void imx_cpu_die(unsigned int cpu); int imx_cpu_kill(unsigned int cpu); @@ -170,6 +173,8 @@ void imx53_suspend(void __iomem *ocram_vbase); extern const u32 imx53_suspend_sz; void imx6_suspend(void __iomem *ocram_vbase); void imx7_suspend(void __iomem *ocram_vbase); +void imx7ulp_cpu_resume(void); +void imx7ulp_suspend(void __iomem *ocram_vbase); #else static inline void v7_cpu_resume(void) {} static inline void ca7_cpu_resume(void) {} @@ -177,6 +182,8 @@ static inline void imx53_suspend(void __iomem *ocram_vbase) {} static const u32 imx53_suspend_sz; static inline void imx6_suspend(void __iomem *ocram_vbase) {} static inline void imx7_suspend(void __iomem *ocram_vbase) {} +static inline void imx7ulp_cpu_resume(void) {} +static inline void imx7ulp_suspend(void __iomem *ocram_vbase) {} #endif #ifdef CONFIG_HAVE_IMX_DDRC diff --git a/arch/arm/mach-imx/hardware.h b/arch/arm/mach-imx/hardware.h index 468fe53600f5..eb05e8592729 100644 --- a/arch/arm/mach-imx/hardware.h +++ b/arch/arm/mach-imx/hardware.h @@ -104,6 +104,7 @@ #include "mx27.h" #include "mx6.h" #include "mx7.h" +#include "mx7ulp.h" #define imx_map_entry(soc, name, _type) { \ .virtual = soc ## _IO_P2V(soc ## _ ## name ## _BASE_ADDR), \ diff --git a/arch/arm/mach-imx/mach-imx7ulp.c b/arch/arm/mach-imx/mach-imx7ulp.c index 11ac71aaf965..65d20685a26c 100644 --- a/arch/arm/mach-imx/mach-imx7ulp.c +++ b/arch/arm/mach-imx/mach-imx7ulp.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "common.h" #include "cpuidle.h" @@ -17,6 +18,15 @@ #define SIM_JTAG_ID_REG 0x8c +/* static IO mapping, and ioremap() could always share the same mapping. */ +static struct map_desc mx7ulp_io_desc[] __initdata = { + mx7ulp_aips_map_entry(1, MT_DEVICE), + mx7ulp_aips_map_entry(2, MT_DEVICE), + mx7ulp_aips_map_entry(3, MT_DEVICE), + mx7ulp_aips_map_entry(4, MT_DEVICE), + mx7ulp_aips_map_entry(5, MT_DEVICE), +}; + static void __init imx7ulp_set_revision(void) { struct regmap *sim; @@ -65,12 +75,20 @@ static const char *const imx7ulp_dt_compat[] __initconst = { NULL, }; +static void __init imx7ulp_map_io(void) +{ + iotable_init(mx7ulp_io_desc, ARRAY_SIZE(mx7ulp_io_desc)); + imx7ulp_pm_map_io(); +} + static void __init imx7ulp_init_late(void) { imx7ulp_cpuidle_init(); + imx7ulp_enable_nmi(); } DT_MACHINE_START(IMX7ulp, "Freescale i.MX7ULP (Device Tree)") + .map_io = imx7ulp_map_io, .init_machine = imx7ulp_init_machine, .dt_compat = imx7ulp_dt_compat, .init_late = imx7ulp_init_late, diff --git a/arch/arm/mach-imx/mx7ulp.h b/arch/arm/mach-imx/mx7ulp.h new file mode 100644 index 000000000000..35638dfea68b --- /dev/null +++ b/arch/arm/mach-imx/mx7ulp.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright NXP 2017. + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ASM_ARCH_MX7ULP_IOMAP_H__ +#define __ASM_ARCH_MX7ULP_IOMAP_H__ + +#define MX7ULP_IO_P2V(x) IMX_IO_P2V(x) +#define MX7ULP_IO_ADDRESS(x) IOMEM(MX7ULP_IO_P2V(x)) + +#define MX7ULP_AIPS1_BASE_ADDR 0x40000000 +#define MX7ULP_AIPS1_SIZE 0x100000 +#define MX7ULP_AIPS2_BASE_ADDR 0x40300000 +#define MX7ULP_AIPS2_SIZE 0x100000 +#define MX7ULP_AIPS3_BASE_ADDR 0x40400000 +#define MX7ULP_AIPS3_SIZE 0x100000 +#define MX7ULP_AIPS4_BASE_ADDR 0x40a00000 +#define MX7ULP_AIPS4_SIZE 0x100000 +#define MX7ULP_AIPS5_BASE_ADDR 0x41000000 +#define MX7ULP_AIPS5_SIZE 0x100000 +#define MX7ULP_GPIOC_BASE_ADDR 0x400f0000 +#define MX7ULP_GPIOC_SIZE 0x1000 +#define MX7ULP_PCC3_BASE_ADDR 0x40b30000 +#define MX7ULP_PCC3_SIZE 0x1000 +#define MX7ULP_SCG1_BASE_ADDR 0x403e0000 +#define MX7ULP_SCG1_SIZE 0x1000 +#define MX7ULP_PCC2_BASE_ADDR 0x403f0000 +#define MX7ULP_PCC2_SIZE 0x1000 +#define MX7ULP_SIM_BASE_ADDR 0x410a3000 +#define MX7ULP_SIM_SIZE 0x1000 +#define MX7ULP_PMC1_BASE_ADDR 0x40400000 +#define MX7ULP_PMC1_SIZE 0x1000 +#define MX7ULP_SMC1_BASE_ADDR 0x40410000 +#define MX7ULP_SMC1_SIZE 0x1000 +#define MX7ULP_MMDC_BASE_ADDR 0x40ab0000 +#define MX7ULP_MMDC_SIZE 0x1000 +#define MX7ULP_IOMUXC1_BASE_ADDR 0x40ac0000 +#define MX7ULP_IOMUXC1_BASE__SIZE 0x1000 +#define MX7ULP_MMDC_IO_BASE_ADDR 0x40ad0000 +#define MX7ULP_MMDC_IO_SIZE 0x1000 + +/* below is just used for static mapping of the AIPSx's memory region */ +#define MX7ULP_AIPS_VIRT_BASE(x) (0xf4000000 + ((x) * SZ_1M)) + +#define mx7ulp_aips_map_entry(index, _type) { \ + .virtual = MX7ULP_AIPS_VIRT_BASE(index), \ + .pfn = __phys_to_pfn(MX7ULP_AIPS ## index ## _BASE_ADDR), \ + .length = SZ_1M, \ + .type = _type, \ +} + +#define TT_ATTRIB_NON_CACHEABLE_1M 0x802 +#define MX7ULP_IRAM_TLB_SIZE 0x4000 +#define MX7ULP_SUSPEND_OCRAM_SIZE 0x1000 + +#endif diff --git a/arch/arm/mach-imx/pm-imx7ulp.c b/arch/arm/mach-imx/pm-imx7ulp.c index 7b2f7387e662..c821642317ec 100644 --- a/arch/arm/mach-imx/pm-imx7ulp.c +++ b/arch/arm/mach-imx/pm-imx7ulp.c @@ -5,64 +5,814 @@ * Author: Dong Aisheng */ +#include +#include +#include #include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include "common.h" +#include "hardware.h" + +#define MU_SR 0x60 + +#define PMPROT 0x8 +#define PMCTRL 0x10 +#define PMSTAT 0x18 +#define SRS 0x20 +#define RPC 0x24 +#define SSRS 0x28 +#define SRIE 0x2c +#define SRIF 0x30 +#define CSRE 0x34 +#define MR 0x40 + +#define PMC1_HSRUN 0x4 +#define PMC1_RUN 0x8 +#define PMC1_VLPR 0xc +#define PMC1_STOP 0x10 +#define PMC1_VLPS 0x14 +#define PMC1_LLS 0x18 +#define PMC1_VLLS 0x1c +#define PMC1_STATUS 0x20 +#define PMC1_CTRL 0x24 +#define PMC0_CTRL 0x28 + +#define BM_PMPROT_AHSRUN (1 << 7) +#define BM_PMPROT_AVLP (1 << 5) +#define BM_PMPROT_ALLS (1 << 3) +#define BM_PMPROT_AVLLS (1 << 1) + +#define BM_PMCTRL_STOPA (1 << 24) +#define BM_PMCTRL_PSTOPO (3 << 16) +#define BM_PMCTRL_RUNM (3 << 8) +#define BM_PMCTRL_STOPM (7 << 0) + +#define BM_VLPS_RBBEN (1 << 28) + +#define BM_CTRL_LDOEN (1 << 31) +#define BM_CTRL_LDOOKDIS (1 << 30) + +#define BM_VLLS_MON1P2HVDHP (1 << 5) +#define BM_VLLS_MON1P2LVDHP (1 << 4) -#define SMC_PMCTRL 0x10 -#define BP_PMCTRL_PSTOPO 16 -#define PSTOPO_PSTOP3 0x3 -#define PSTOPO_PSTOP2 0x2 -#define PSTOPO_PSTOP1 0x1 -#define BP_PMCTRL_RUNM 8 -#define RUNM_RUN 0 #define BP_PMCTRL_STOPM 0 -#define STOPM_STOP 0 +#define BP_PMCTRL_PSTOPO 16 -#define BM_PMCTRL_PSTOPO (3 << BP_PMCTRL_PSTOPO) -#define BM_PMCTRL_RUNM (3 << BP_PMCTRL_RUNM) -#define BM_PMCTRL_STOPM (7 << BP_PMCTRL_STOPM) +#define MX7ULP_MAX_MMDC_IO_NUM 64 +#define MX7ULP_MAX_MMDC_NUM 50 +#define MX7ULP_MAX_IOMUX_NUM 116 +#define MX7ULP_MAX_SELECT_INPUT_NUM 78 + +#define IOMUX_START 0x0 +#define SELECT_INPUT_START 0x200 + +#define TPM_SC 0x10 +#define TPM_MOD 0x18 +#define TPM_C0SC 0x20 +#define TPM_C0V 0x24 + +#define PCC2_ENABLE_PCS_FIRC ((1 << 30) | (3 << 24)) +#define PCC2_ENABLE (1 << 30) + +#define LPUART_BAUD 0x10 +#define LPUART_CTRL 0x18 +#define LPUART_FIFO 0x28 +#define LPUART_WATER 0x2c + +#define GPIO_PDOR 0x0 +#define GPIO_PDDR 0x14 + +#define PTC2_LPUART4_TX_OFFSET 0x8 +#define PTC3_LPUART4_RX_OFFSET 0xc +#define PTC2_LPUART4_TX_INPUT_OFFSET 0x248 +#define PTC3_LPUART4_RX_INPUT_OFFSET 0x24c +#define LPUART4_MUX_VALUE (4 << 8) +#define LPUART4_INPUT_VALUE (1) + +#define MU_B_SR_NMIC (1 << 3) + +#define DGO_GPR3 0x60 +#define DGO_GPR4 0x64 + +#define ADDR_1M_MASK 0xFFF00000 static void __iomem *smc1_base; +static void __iomem *pmc0_base; +static void __iomem *pmc1_base; +static void __iomem *tpm5_base; +static void __iomem *lpuart4_base; +static void __iomem *iomuxc1_base; +static void __iomem *pcc2_base; +static void __iomem *pcc3_base; +static void __iomem *mu_base; +static void __iomem *scg1_base; +static void __iomem *gpio_base[4]; +static void __iomem *suspend_ocram_base; +static void (*imx7ulp_suspend_in_ocram_fn)(void __iomem *sram_base); + +static u32 tpm5_regs[4]; +static u32 lpuart4_regs[4]; +static u32 pcc2_regs[24][2] = { + {0x20, 0}, {0x3c, 0}, {0x40, 0}, {0x6c, 0}, + {0x84, 0}, {0x90, 0}, {0x94, 0}, {0x98, 0}, + {0x9c, 0}, {0xa4, 0}, {0xa8, 0}, {0xac, 0}, + {0xb0, 0}, {0xb4, 0}, {0xb8, 0}, {0xc4, 0}, + {0xcc, 0}, {0xd0, 0}, {0xd4, 0}, {0xd8, 0}, + {0xdc, 0}, {0xe0, 0}, {0xf4, 0}, {0x10c, 0}, +}; + +static u32 pcc3_regs[16][2] = { + {0x84, 0}, {0x88, 0}, {0x90, 0}, {0x94, 0}, + {0x98, 0}, {0x9c, 0}, {0xa0, 0}, {0xa4, 0}, + {0xa8, 0}, {0xac, 0}, {0xb8, 0}, {0xbc, 0}, + {0xc0, 0}, {0xc4, 0}, {0x140, 0}, {0x144, 0}, +}; + +static u32 scg1_offset[17] = { + 0x14, 0x30, 0x40, 0x304, + 0x500, 0x504, 0x508, 0x50c, + 0x510, 0x514, 0x600, 0x604, + 0x608, 0x60c, 0x610, 0x614, + 0x104, +}; + +extern unsigned long iram_tlb_base_addr; +extern unsigned long iram_tlb_phys_addr; + +/* + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx7ulp_suspend code + * PM_INFO structure(imx7ulp_cpu_pm_info) + * ======================== low address ======================= + */ +struct imx7ulp_pm_socdata { + u32 ddr_type; + const char *mmdc_compat; + const u32 mmdc_io_num; + const u32 *mmdc_io_offset; + const u32 mmdc_num; + const u32 *mmdc_offset; +}; + +static const u32 imx7ulp_mmdc_io_lpddr3_offset[] __initconst = { + 0x0, 0x4, 0x8, 0xc, + 0x10, 0x14, 0x18, 0x1c, + 0x20, 0x24, 0x28, 0x2c, + 0x30, 0x34, 0x38, 0x3c, + 0x40, 0x44, 0x48, 0x4c, + 0x50, 0x54, 0x58, 0x5c, + 0x60, 0x64, 0x68, 0x6c, + 0x70, 0x74, 0x78, 0x7c, + 0x80, 0x84, 0x88, 0x8c, + 0x90, 0x94, 0x98, 0x9c, + 0xa0, 0xa4, 0xa8, 0xac, + 0xb0, 0xb4, 0xb8, 0xbc, + 0xc0, 0xc4, 0xc8, 0xcc, + 0xd0, 0xd4, 0xd8, 0xdc, + 0xe8, 0xf8, 0xfc, 0x120, + 0x124, +}; + +static const u32 imx7ulp_mmdc_lpddr3_offset[] __initconst = { + 0x01c, 0x800, 0x85c, 0x890, + 0x848, 0x850, 0x81c, 0x820, + 0x824, 0x828, 0x82c, 0x830, + 0x834, 0x838, 0x8c0, 0x8b8, + 0x004, 0x00c, 0x010, 0x038, + 0x014, 0x018, 0x02c, 0x030, + 0x040, 0x000, 0x01c, 0x01c, + 0x01c, 0x01c, 0x01c, 0x01c, + 0x01c, 0x01c, 0x01c, 0x01c, + 0x01c, 0x01c, 0x83c, 0x020, + 0x800, 0x004, 0x404, 0x01c, +}; + +static const u32 imx7ulp_lpddr3_script[] __initconst = { + 0x00008000, 0xA1390003, 0x0D3900A0, 0x00400000, + 0x40404040, 0x40404040, 0x33333333, 0x33333333, + 0x33333333, 0x33333333, 0xf3333333, 0xf3333333, + 0xf3333333, 0xf3333333, 0x24922492, 0x00000800, + 0x00020052, 0x292C42F3, 0x00100A22, 0x00120556, + 0x00C700DB, 0x00211718, 0x0F9F26D2, 0x009F0E10, + 0x0000003F, 0xC3190000, 0x00008050, 0x00008058, + 0x003F8030, 0x003F8038, 0xFF0A8030, 0xFF0A8038, + 0x04028030, 0x04028038, 0x83018030, 0x83018038, + 0x01038030, 0x01038038, 0x20000000, 0x00001800, + 0xA1310000, 0x00020052, 0x00011006, 0x00000000, +}; + +static const struct imx7ulp_pm_socdata imx7ulp_lpddr3_pm_data __initconst = { + .mmdc_compat = "fsl,imx7ulp-mmdc", + .mmdc_io_num = ARRAY_SIZE(imx7ulp_mmdc_io_lpddr3_offset), + .mmdc_io_offset = imx7ulp_mmdc_io_lpddr3_offset, + .mmdc_num = ARRAY_SIZE(imx7ulp_mmdc_lpddr3_offset), + .mmdc_offset = imx7ulp_mmdc_lpddr3_offset, +}; + +/* + * This structure is for passing necessary data for low level ocram + * suspend code(arch/arm/mach-imx/suspend-imx7ulp.S), if this struct + * definition is changed, the offset definition in + * arch/arm/mach-imx/suspend-imx7ulp.S must be also changed accordingly, + * otherwise, the suspend to sram function will be broken! + */ +struct imx7ulp_cpu_pm_info { + u32 m4_reserve0; + u32 m4_reserve1; + u32 m4_reserve2; + phys_addr_t pbase; /* The physical address of pm_info. */ + phys_addr_t resume_addr; /* The physical resume address for asm code */ + u32 pm_info_size; /* Size of pm_info. */ + void __iomem *sim_base; + void __iomem *scg1_base; + void __iomem *mmdc_base; + void __iomem *mmdc_io_base; + void __iomem *smc1_base; + u32 scg1[17]; + u32 ttbr1; /* Store TTBR1 */ + u32 gpio[4][2]; + u32 iomux_num; /* Number of IOs which need saved/restored. */ + u32 iomux_val[MX7ULP_MAX_IOMUX_NUM]; /* To save value */ + u32 select_input_num; /* Number of select input which need saved/restored. */ + u32 select_input_val[MX7ULP_MAX_SELECT_INPUT_NUM]; /* To save value */ + u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */ + u32 mmdc_io_val[MX7ULP_MAX_MMDC_IO_NUM][2]; /* To save offset and value */ + u32 mmdc_num; /* Number of MMDC registers which need saved/restored. */ + u32 mmdc_val[MX7ULP_MAX_MMDC_NUM][2]; +} __aligned(8); + +static struct imx7ulp_cpu_pm_info *pm_info; +static void __iomem *aips1_base; +static void __iomem *aips2_base; +static void __iomem *aips3_base; +static void __iomem *aips4_base; +static void __iomem *aips5_base; + +static const char * const low_power_ocram_match[] __initconst = { + "fsl,lpm-sram", + NULL +}; + +static void imx7ulp_gpio_save(void) +{ + int i; + + for (i = 0; i < 4; i++) { + pm_info->gpio[i][0] = readl_relaxed(gpio_base[i] + GPIO_PDOR); + pm_info->gpio[i][1] = readl_relaxed(gpio_base[i] + GPIO_PDDR); + } +} + +static void imx7ulp_scg1_save(void) +{ + int i; + + for (i = 0; i < 17; i++) + pm_info->scg1[i] = readl_relaxed(scg1_base + scg1_offset[i]); +} + +static void imx7ulp_pcc3_save(void) +{ + int i; + + for (i = 0; i < 16; i++) + pcc3_regs[i][1] = readl_relaxed(pcc3_base + pcc3_regs[i][0]); +} + +static void imx7ulp_pcc3_restore(void) +{ + int i; + + for (i = 0; i < 16; i++) + writel_relaxed(pcc3_regs[i][1], pcc3_base + pcc3_regs[i][0]); +} + +static void imx7ulp_pcc2_save(void) +{ + int i; + + for (i = 0; i < 24; i++) + pcc2_regs[i][1] = readl_relaxed(pcc2_base + pcc2_regs[i][0]); +} + +static void imx7ulp_pcc2_restore(void) +{ + int i; + + for (i = 0; i < 24; i++) + writel_relaxed(pcc2_regs[i][1], pcc2_base + pcc2_regs[i][0]); +} + +static inline void imx7ulp_iomuxc_save(void) +{ + int i; + + pm_info->iomux_num = MX7ULP_MAX_IOMUX_NUM; + pm_info->select_input_num = MX7ULP_MAX_SELECT_INPUT_NUM; + + for (i = 0; i < pm_info->iomux_num; i++) + pm_info->iomux_val[i] = + readl_relaxed(iomuxc1_base + + IOMUX_START + i * 0x4); + for (i = 0; i < pm_info->select_input_num; i++) + pm_info->select_input_val[i] = + readl_relaxed(iomuxc1_base + + SELECT_INPUT_START + i * 0x4); +} + +static void imx7ulp_lpuart_save(void) +{ + lpuart4_regs[0] = readl_relaxed(lpuart4_base + LPUART_BAUD); + lpuart4_regs[1] = readl_relaxed(lpuart4_base + LPUART_FIFO); + lpuart4_regs[2] = readl_relaxed(lpuart4_base + LPUART_WATER); + lpuart4_regs[3] = readl_relaxed(lpuart4_base + LPUART_CTRL); +} + +static void imx7ulp_lpuart_restore(void) +{ + writel_relaxed(LPUART4_MUX_VALUE, + iomuxc1_base + PTC2_LPUART4_TX_OFFSET); + writel_relaxed(LPUART4_MUX_VALUE, + iomuxc1_base + PTC3_LPUART4_RX_OFFSET); + writel_relaxed(LPUART4_INPUT_VALUE, + iomuxc1_base + PTC2_LPUART4_TX_INPUT_OFFSET); + writel_relaxed(LPUART4_INPUT_VALUE, + iomuxc1_base + PTC3_LPUART4_RX_INPUT_OFFSET); + + writel_relaxed(lpuart4_regs[0], lpuart4_base + LPUART_BAUD); + writel_relaxed(lpuart4_regs[1], lpuart4_base + LPUART_FIFO); + writel_relaxed(lpuart4_regs[2], lpuart4_base + LPUART_WATER); + writel_relaxed(lpuart4_regs[3], lpuart4_base + LPUART_CTRL); +} + +static void imx7ulp_tpm_save(void) +{ + tpm5_regs[0] = readl_relaxed(tpm5_base + TPM_SC); + tpm5_regs[1] = readl_relaxed(tpm5_base + TPM_MOD); + tpm5_regs[2] = readl_relaxed(tpm5_base + TPM_C0SC); + tpm5_regs[3] = readl_relaxed(tpm5_base + TPM_C0V); +} + +static void imx7ulp_tpm_restore(void) +{ + writel_relaxed(tpm5_regs[0], tpm5_base + TPM_SC); + writel_relaxed(tpm5_regs[1], tpm5_base + TPM_MOD); + writel_relaxed(tpm5_regs[2], tpm5_base + TPM_C0SC); + writel_relaxed(tpm5_regs[3], tpm5_base + TPM_C0V); +} + +static void imx7ulp_set_dgo(u32 val) +{ + writel_relaxed(val, pm_info->sim_base + DGO_GPR3); + writel_relaxed(val, pm_info->sim_base + DGO_GPR4); +} int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode) { - u32 val = readl_relaxed(smc1_base + SMC_PMCTRL); + u32 val1 = BM_PMPROT_AHSRUN | BM_PMPROT_AVLP | BM_PMPROT_AVLLS; + u32 val2 = readl_relaxed(smc1_base + PMCTRL); + u32 val3 = readl_relaxed(pmc0_base + PMC0_CTRL); - /* clear all */ - val &= ~(BM_PMCTRL_RUNM | BM_PMCTRL_STOPM | BM_PMCTRL_PSTOPO); + val2 &= ~(BM_PMCTRL_RUNM | + BM_PMCTRL_STOPM | BM_PMCTRL_PSTOPO); + val3 |= BM_CTRL_LDOOKDIS; switch (mode) { case ULP_PM_RUN: /* system/bus clock enabled */ - val |= PSTOPO_PSTOP3 << BP_PMCTRL_PSTOPO; + val2 |= 0x3 << BP_PMCTRL_PSTOPO; break; case ULP_PM_WAIT: /* system clock disabled, bus clock enabled */ - val |= PSTOPO_PSTOP2 << BP_PMCTRL_PSTOPO; + val2 |= 0x2 << BP_PMCTRL_PSTOPO; break; case ULP_PM_STOP: /* system/bus clock disabled */ - val |= PSTOPO_PSTOP1 << BP_PMCTRL_PSTOPO; + val2 |= 0x1 << BP_PMCTRL_PSTOPO; + break; + case ULP_PM_VLPS: + val2 |= 0x2 << BP_PMCTRL_STOPM; + break; + case ULP_PM_VLLS: + val2 |= 0x4 << BP_PMCTRL_STOPM; break; default: return -EINVAL; } - writel_relaxed(val, smc1_base + SMC_PMCTRL); + writel_relaxed(val1, smc1_base + PMPROT); + writel_relaxed(val2, smc1_base + PMCTRL); + writel_relaxed(val3, pmc0_base + PMC0_CTRL); return 0; } -void __init imx7ulp_pm_init(void) +#define MX7ULP_SUSPEND_POWERDWN_PARAM \ + ((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +#define MX7ULP_SUSPEND_STANDBY_PARAM \ + ((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_STANDBY << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) + +static int imx7ulp_suspend_finish(unsigned long val) +{ + u32 state; + + if (val == 0) + state = MX7ULP_SUSPEND_POWERDWN_PARAM; + else + state = MX7ULP_SUSPEND_STANDBY_PARAM; + + if (psci_ops.cpu_suspend) + return psci_ops.cpu_suspend(state, __pa(cpu_resume)); + + imx7ulp_suspend_in_ocram_fn(suspend_ocram_base); + + return 0; +} + +static int imx7ulp_pm_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_STANDBY: + if (psci_ops.cpu_suspend) + /* Zzz ... */ + cpu_suspend(1, imx7ulp_suspend_finish); + else { + imx7ulp_set_lpm(ULP_PM_VLPS); + writel_relaxed( + readl_relaxed(pmc1_base + PMC1_VLPS) | BM_VLPS_RBBEN, + pmc1_base + PMC1_VLPS); + + /* Zzz ... */ + cpu_suspend(0, imx7ulp_suspend_finish); + + writel_relaxed( + readl_relaxed(pmc1_base + PMC1_VLPS) & ~BM_VLPS_RBBEN, + pmc1_base + PMC1_VLPS); + imx7ulp_set_lpm(ULP_PM_RUN); + } + break; + case PM_SUSPEND_MEM: + if (psci_ops.cpu_suspend) { + /* Zzz ... */ + cpu_suspend(0, imx7ulp_suspend_finish); + } else { + imx7ulp_gpio_save(); + imx7ulp_scg1_save(); + imx7ulp_pcc2_save(); + imx7ulp_pcc3_save(); + imx7ulp_tpm_save(); + if (!console_suspend_enabled) + imx7ulp_lpuart_save(); + imx7ulp_iomuxc_save(); + imx7ulp_set_lpm(ULP_PM_VLLS); + + /* Zzz ... */ + cpu_suspend(0, imx7ulp_suspend_finish); + + imx7ulp_pcc2_restore(); + imx7ulp_pcc3_restore(); + if (!console_suspend_enabled) + imx7ulp_lpuart_restore(); + imx7ulp_set_dgo(0); + imx7ulp_tpm_restore(); + imx7ulp_set_lpm(ULP_PM_RUN); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Put CA7 into VLLS mode before M4 power off CA7 */ +void imx7ulp_poweroff(void) +{ + imx7ulp_set_lpm(ULP_PM_VLLS); + cpu_suspend(0, imx7ulp_suspend_finish); +} + +static int imx7ulp_pm_valid(suspend_state_t state) +{ + return (state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM); +} + +static const struct platform_suspend_ops imx7ulp_pm_ops = { + .enter = imx7ulp_pm_enter, + .valid = imx7ulp_pm_valid, +}; + +static int __init imx7ulp_suspend_init(void) +{ + int ret = 0; + + suspend_set_ops(&imx7ulp_pm_ops); + + return ret; +} + +static struct map_desc iram_tlb_io_desc __initdata = { + /* .virtual and .pfn are run-time assigned */ + .length = SZ_1M, + .type = MT_MEMORY_RWX_NONCACHED, +}; + +static int __init imx7ulp_dt_find_lpsram(unsigned long node, const char *uname, + int depth, void *data) +{ + unsigned long lpram_addr; + const __be32 *prop = of_get_flat_dt_prop(node, "reg", NULL); + + if (of_flat_dt_match(node, low_power_ocram_match)) { + if (!prop) + return -EINVAL; + + lpram_addr = be32_to_cpup(prop); + + /* We need to create a 1M page table entry. */ + iram_tlb_io_desc.virtual = + IMX_IO_P2V(lpram_addr & ADDR_1M_MASK); + iram_tlb_io_desc.pfn = __phys_to_pfn(lpram_addr & ADDR_1M_MASK); + iram_tlb_phys_addr = lpram_addr; + iram_tlb_base_addr = IMX_IO_P2V(lpram_addr); + iotable_init(&iram_tlb_io_desc, 1); + } + + return 0; +} + +void __init imx7ulp_pm_map_io(void) +{ + /* + * Get the address of IRAM or OCRAM to be used by the low + * power code from the device tree. + */ + WARN_ON(of_scan_flat_dt(imx7ulp_dt_find_lpsram, NULL)); + + /* Return if no IRAM space is allocated for suspend/resume code. */ + if (!iram_tlb_base_addr) { + pr_warn("No valid ocram available for suspend/resume!\n"); + return; + } +} + +void __init imx7ulp_pm_common_init(const struct imx7ulp_pm_socdata + *socdata) { struct device_node *np; + unsigned long sram_paddr = 0; + const u32 *mmdc_offset_array; + const u32 *mmdc_io_offset_array; + unsigned long i, j; + int ret; + + if (psci_ops.cpu_suspend) { + aips1_base = ioremap(MX7ULP_AIPS1_BASE_ADDR, SZ_1M); + aips2_base = ioremap(MX7ULP_AIPS2_BASE_ADDR, SZ_1M); + aips3_base = ioremap(MX7ULP_AIPS3_BASE_ADDR, SZ_1M); + aips4_base = ioremap(MX7ULP_AIPS4_BASE_ADDR, SZ_1M); + aips5_base = ioremap(MX7ULP_AIPS5_BASE_ADDR, SZ_1M); + } else { + /* Set all entries to 0 except first 3 words reserved for M4. */ + memset((void *)iram_tlb_base_addr, 0, MX7ULP_IRAM_TLB_SIZE); + + /* + * Make sure the IRAM virtual address has a mapping in the IRAM + * page table. + * + * Only use the top 12 bits [31-20] when storing the physical + * address in the page table as only these bits are required + * for 1M mapping. + */ + j = ((iram_tlb_base_addr >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + (iram_tlb_phys_addr & ADDR_1M_MASK) | + TT_ATTRIB_NON_CACHEABLE_1M; + /* + * Make sure the AIPS1 virtual address has a mapping in the + * IRAM page table. + */ + aips1_base = ioremap(MX7ULP_AIPS1_BASE_ADDR, SZ_1M); + j = (((u32)aips1_base >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + ((MX7ULP_AIPS1_BASE_ADDR) & ADDR_1M_MASK) | + TT_ATTRIB_NON_CACHEABLE_1M; + /* + * Make sure the AIPS2 virtual address has a mapping in the + * IRAM page table. + */ + aips2_base = ioremap(MX7ULP_AIPS2_BASE_ADDR, SZ_1M); + j = (((u32)aips2_base >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + ((MX7ULP_AIPS2_BASE_ADDR) & ADDR_1M_MASK) | + TT_ATTRIB_NON_CACHEABLE_1M; + /* + * Make sure the AIPS3 virtual address has a mapping in the + * IRAM page table. + */ + aips3_base = ioremap(MX7ULP_AIPS3_BASE_ADDR, SZ_1M); + j = (((u32)aips3_base >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + ((MX7ULP_AIPS3_BASE_ADDR) & ADDR_1M_MASK) | + TT_ATTRIB_NON_CACHEABLE_1M; + /* + * Make sure the AIPS4 virtual address has a mapping in the + * IRAM page table. + */ + aips4_base = ioremap(MX7ULP_AIPS4_BASE_ADDR, SZ_1M); + j = (((u32)aips4_base >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + ((MX7ULP_AIPS4_BASE_ADDR) & ADDR_1M_MASK) | + TT_ATTRIB_NON_CACHEABLE_1M; + /* + * Make sure the AIPS5 virtual address has a mapping in the + * IRAM page table. + */ + aips5_base = ioremap(MX7ULP_AIPS5_BASE_ADDR, SZ_1M); + j = (((u32)aips5_base >> 20) << 2) / 4; + *((unsigned long *)iram_tlb_base_addr + j) = + ((MX7ULP_AIPS5_BASE_ADDR) & ADDR_1M_MASK) | + TT_ATTRIB_NON_CACHEABLE_1M; + } np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-smc1"); smc1_base = of_iomap(np, 0); WARN_ON(!smc1_base); + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-pmc0"); + pmc0_base = of_iomap(np, 0); + WARN_ON(!pmc0_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-pmc1"); + pmc1_base = of_iomap(np, 0); + WARN_ON(!pmc1_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-tpm"); + tpm5_base = of_iomap(np, 0); + WARN_ON(!tpm5_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-lpuart"); + lpuart4_base = of_iomap(np, 0); + WARN_ON(!lpuart4_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-pcc2"); + pcc2_base = of_iomap(np, 0); + WARN_ON(!pcc2_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-pcc3"); + pcc3_base = of_iomap(np, 0); + WARN_ON(!pcc3_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-iomuxc1"); + iomuxc1_base = of_iomap(np, 0); + WARN_ON(!iomuxc1_base); + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-scg1"); + scg1_base = of_iomap(np, 0); + WARN_ON(!scg1_base); + + np = NULL; + for (i = 0; i < 4; i++) { + np = of_find_compatible_node(np, NULL, "fsl,vf610-gpio"); + gpio_base[i] = of_iomap(np, 1); + WARN_ON(!gpio_base[i]); + } + + if (psci_ops.cpu_suspend) { + pm_info = kzalloc(SZ_16K, GFP_KERNEL); + if (!pm_info) + panic("pm info allocation failed\n"); + } else { + /* + * 16KB is allocated for IRAM TLB, but only up 8k is for kernel TLB, + * The lower 8K is not used, so use the lower 8K for IRAM code and + * pm_info. + * + */ + sram_paddr = iram_tlb_phys_addr; + + /* Make sure sram_paddr is 8 byte aligned. */ + if ((uintptr_t)(sram_paddr) & (FNCPY_ALIGN - 1)) + sram_paddr += FNCPY_ALIGN - sram_paddr % (FNCPY_ALIGN); + + /* Get the virtual address of the suspend code. */ + suspend_ocram_base = (void *)IMX_IO_P2V(sram_paddr); + + pm_info = suspend_ocram_base; + } + pm_info->pbase = sram_paddr; + pm_info->resume_addr = virt_to_phys(imx7ulp_cpu_resume); + pm_info->pm_info_size = sizeof(*pm_info); + + pm_info->scg1_base = aips2_base + + (MX7ULP_SCG1_BASE_ADDR & ~ADDR_1M_MASK); + pm_info->smc1_base = aips3_base + + (MX7ULP_SMC1_BASE_ADDR & ~ADDR_1M_MASK); + pm_info->mmdc_base = aips4_base + + (MX7ULP_MMDC_BASE_ADDR & ~ADDR_1M_MASK); + pm_info->mmdc_io_base = aips4_base + + (MX7ULP_MMDC_IO_BASE_ADDR & ~ADDR_1M_MASK); + pm_info->sim_base = aips5_base + + (MX7ULP_SIM_BASE_ADDR & ~ADDR_1M_MASK); + + pm_info->mmdc_io_num = socdata->mmdc_io_num; + mmdc_io_offset_array = socdata->mmdc_io_offset; + pm_info->mmdc_num = socdata->mmdc_num; + mmdc_offset_array = socdata->mmdc_offset; + + for (i = 0; i < pm_info->mmdc_io_num; i++) { + pm_info->mmdc_io_val[i][0] = + mmdc_io_offset_array[i]; + pm_info->mmdc_io_val[i][1] = + readl_relaxed(pm_info->mmdc_io_base + + mmdc_io_offset_array[i]); + } + + /* initialize MMDC settings */ + for (i = 0; i < pm_info->mmdc_num; i++) + pm_info->mmdc_val[i][0] = + mmdc_offset_array[i]; + + for (i = 0; i < pm_info->mmdc_num; i++) + pm_info->mmdc_val[i][1] = imx7ulp_lpddr3_script[i]; + + if (!psci_ops.cpu_suspend) { + imx7ulp_suspend_in_ocram_fn = fncpy( + suspend_ocram_base + sizeof(*pm_info), + &imx7ulp_suspend, + MX7ULP_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); + } + + if (IS_ENABLED(CONFIG_SUSPEND)) { + ret = imx7ulp_suspend_init(); + if (ret) + pr_warn("%s: No DDR LPM support with suspend %d!\n", + __func__, ret); + } +} + +void __init imx7ulp_pm_init(void) +{ + imx7ulp_pm_common_init(&imx7ulp_lpddr3_pm_data); imx7ulp_set_lpm(ULP_PM_RUN); } + +static irqreturn_t imx7ulp_nmi_isr(int irq, void *param) +{ + writel_relaxed(readl_relaxed(mu_base + MU_SR) | MU_B_SR_NMIC, + mu_base + MU_SR); + pm_system_wakeup(); + + return IRQ_HANDLED; +} + +void imx7ulp_enable_nmi(void) +{ + struct device_node *np; + int irq, ret; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx7ulp-nmi"); + mu_base = of_iomap(np, 0); + WARN_ON(!mu_base); + irq = of_irq_get(np, 0); + ret = request_irq(irq, imx7ulp_nmi_isr, + IRQF_NO_SUSPEND, "imx7ulp-nmi", NULL); + if (ret) { + pr_err("%s: register interrupt %d failed, rc %d\n", + __func__, irq, ret); + return; + } +} diff --git a/arch/arm/mach-imx/pm-rpmsg.c b/arch/arm/mach-imx/pm-rpmsg.c index 52c2f8b402bd..4dfe489f5d38 100644 --- a/arch/arm/mach-imx/pm-rpmsg.c +++ b/arch/arm/mach-imx/pm-rpmsg.c @@ -148,6 +148,7 @@ void pm_shutdown_notify_m4(void) msg.data = PM_RPMSG_SHUTDOWN; /* No ACK from M4 */ pm_send_message(&msg, &pm_rpmsg, false); + imx7ulp_poweroff(); } void pm_reboot_notify_m4(void) diff --git a/arch/arm/mach-imx/suspend-imx7ulp.S b/arch/arm/mach-imx/suspend-imx7ulp.S new file mode 100644 index 000000000000..bd45e460f75f --- /dev/null +++ b/arch/arm/mach-imx/suspend-imx7ulp.S @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include "hardware.h" + +/* + * ==================== low level suspend ==================== + * + * Better to follow below rules to use ARM registers: + * r0: pm_info structure address; + * + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * imx7ulp_suspend code + * PM_INFO structure(imx7ulp_cpu_pm_info) + * ======================== low address ======================= + */ + +/* + * Below offsets are based on struct imx7ulp_cpu_pm_info + * which defined in arch/arm/mach-imx/pm-imx7ulp.c, this + * structure contains necessary pm info for low level + * suspend related code. + */ +#define PM_INFO_M4_RESERVE0_OFFSET 0x0 +#define PM_INFO_M4_RESERVE1_OFFSET 0x4 +#define PM_INFO_M4_RESERVE2_OFFSET 0x8 +#define PM_INFO_PBASE_OFFSET 0xc +#define PM_INFO_RESUME_ADDR_OFFSET 0x10 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0x14 +#define PM_INFO_PM_INFO_SIM_VBASE_OFFSET 0x18 +#define PM_INFO_PM_INFO_SCG1_VBASE_OFFSET 0x1c +#define PM_INFO_PM_INFO_MMDC_VBASE_OFFSET 0x20 +#define PM_INFO_PM_INFO_MMDC_IO_VBASE_OFFSET 0x24 +#define PM_INFO_PM_INFO_SMC1_VBASE_OFFSET 0x28 +#define PM_INFO_PM_INFO_SCG1_VAL_OFFSET 0x2c +#define PM_INFO_MX7ULP_TTBR1_V_OFFSET 0x70 +#define PM_INFO_MX7ULP_GPIO_REG_OFFSET 0x74 +#define PM_INFO_IOMUX_NUM_OFFSET 0x94 +#define PM_INFO_IOMUX_VAL_OFFSET 0x98 +#define PM_INFO_SELECT_INPUT_NUM_OFFSET 0x268 +#define PM_INFO_SELECT_INPUT_VAL_OFFSET 0x26c +#define PM_INFO_MMDC_IO_NUM_OFFSET 0x3a4 +#define PM_INFO_MMDC_IO_VAL_OFFSET 0x3a8 +/* below offsets depends on MX7ULP_MAX_MMDC_IO_NUM(36) definition */ +#define PM_INFO_MMDC_NUM_OFFSET 0x5a8 +#define PM_INFO_MMDC_VAL_OFFSET 0x5ac + +#define DGO_CTRL0 0x50 +#define DGO_GPR3 0x60 +#define DGO_GPR4 0x64 + +#define MX7ULP_MMDC_MISC 0x18 +#define MX7ULP_MMDC_MAPSR 0x404 +#define MX7ULP_MMDC_MPDGCTRL0 0x83c + +#define SCG_RCCR 0x14 +#define SCG_DDRCCR 0x30 +#define SCG_NICCCR 0x40 +#define SCG_FIRCDIV 0x304 +#define SCG_APLLCSR 0x500 +#define SCG_APLLDIV 0x504 +#define SCG_APLLCFG 0x508 +#define SCG_APLLPFD 0x50c +#define SCG_APLLNUM 0x510 +#define SCG_APLLDENOM 0x514 +#define SCG_SPLLCSR 0x600 +#define SCG_SPLLDIV 0x604 +#define SCG_SPLLCFG 0x608 +#define SCG_SPLLPFD 0x60c +#define SCG_SPLLNUM 0x610 +#define SCG_SPLLDENOM 0x614 +#define SCG_SOSCDIV 0x104 + +#define PMC1_CTRL 0x24 + +#define GPIO_PDOR 0x0 +#define GPIO_PDDR 0x14 +#define GPIO_PORT_NUM 0x4 +#define GPIO_PORT_OFFSET 0x40 + +#define PMCTRL 0x10 + +#define IOMUX_OFFSET 0x0 +#define SELECT_INPUT_OFFSET 0x200 + + .align 3 + + .macro store_ttbr1 + + /* Store TTBR1 to pm_info->ttbr1 */ + mrc p15, 0, r7, c2, c0, 1 + str r7, [r0, #PM_INFO_MX7ULP_TTBR1_V_OFFSET] + + /* Disable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + bic r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the BTAC. */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + ldr r6, =iram_tlb_phys_addr + ldr r6, [r6] + dsb + isb + + /* Store the IRAM table in TTBR1 */ + mcr p15, 0, r6, c2, c0, 1 + /* Read TTBCR and set PD0=1, N = 1 */ + mrc p15, 0, r6, c2, c0, 2 + orr r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + .endm + + .macro restore_ttbr1 + + /* Enable L1 data cache. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x4 + mcr p15, 0, r6, c1, c0, 0 + + dsb + isb + + /* Restore TTBCR */ + /* Read TTBCR and set PD0=0, N = 0 */ + mrc p15, 0, r6, c2, c0, 2 + bic r6, r6, #0x11 + mcr p15, 0, r6, c2, c0, 2 + dsb + isb + + /* flush the TLB */ + ldr r6, =0x0 + mcr p15, 0, r6, c8, c3, 0 + + /* Enable Branch Prediction, Z bit in SCTLR. */ + mrc p15, 0, r6, c1, c0, 0 + orr r6, r6, #0x800 + mcr p15, 0, r6, c1, c0, 0 + + /* Flush the Branch Target Address Cache (BTAC) */ + ldr r6, =0x0 + mcr p15, 0, r6, c7, c1, 6 + + /* Restore TTBR1, get the origin ttbr1 from pm info */ + ldr r7, [r0, #PM_INFO_MX7ULP_TTBR1_V_OFFSET] + mcr p15, 0, r7, c2, c0, 1 + + .endm + + .macro disable_l1_dcache + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + /* disable d-cache */ + mrc p15, 0, r7, c1, c0, 0 + bic r7, r7, #(1 << 2) + mcr p15, 0, r7, c1, c0, 0 + dsb + isb + + push {r0 - r10, lr} + ldr r7, =v7_flush_dcache_all + mov lr, pc + mov pc, r7 + pop {r0 - r10, lr} + + .endm + + .macro restore_mmdc_settings + + ldr r10, =MX7ULP_MMDC_IO_BASE_ADDR + ldr r11, =MX7ULP_MMDC_BASE_ADDR + + /* resume mmdc iomuxc settings */ + ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +11: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r10, r8] + subs r6, r6, #0x1 + bne 11b + + /* restore MMDC settings */ + ldr r6, [r0, #PM_INFO_MMDC_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_VAL_OFFSET + add r7, r7, r0 +1: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r11, r8] + subs r6, r6, #0x1 + bne 1b + + /* let DDR enter self-refresh */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + orr r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] +2: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + beq 2b + + /* let DDR out of self-refresh */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] +3: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + bne 3b + + /* kick off MMDC */ + ldr r4, =0x0 + str r4, [r11, #0x1c] + + /* let DDR out of self-refresh */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] +4: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + bne 4b + + /* enable DDR auto power saving */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r11, #MX7ULP_MMDC_MAPSR] + + .endm + +ENTRY(imx7ulp_suspend) + push {r4-r12} + + /* + * The value of r0 is mapped the same in origin table and IRAM table, + * thus no need to care r0 here. + */ + ldr r1, [r0, #PM_INFO_PBASE_OFFSET] + ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + ldr r3, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r6, =imx7ulp_suspend + ldr r7, =resume + sub r7, r7, r6 + add r8, r1, r3 + add r9, r8, r7 + + ldr r11, [r0, #PM_INFO_PM_INFO_SIM_VBASE_OFFSET] + /* store physical resume addr and pm_info address. */ + str r9, [r11, #DGO_GPR3] + str r1, [r11, #DGO_GPR4] + ldr r7, [r11, #DGO_CTRL0] + orr r7, r7, #0xc + str r7, [r11, #DGO_CTRL0] +wait_dgo: + ldr r7, [r11, #DGO_CTRL0] + and r7, r7, #0x18000 + cmp r7, #0x18000 + bne wait_dgo + + ldr r7, [r11, #DGO_CTRL0] + orr r7, r7, #0x18000 + bic r7, r7, #0xc + str r7, [r11, #DGO_CTRL0] + + disable_l1_dcache + + store_ttbr1 + + ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_VBASE_OFFSET] + + /* + * put DDR explicitly into self-refresh and + * disable automatic power savings. + */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + orr r7, r7, #0x1 + str r7, [r11, #MX7ULP_MMDC_MAPSR] + + /* make the DDR explicitly enter self-refresh. */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + orr r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] + +poll_dvfs_set: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + beq poll_dvfs_set + + /* put mmdc io into lpm */ + ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_IO_VBASE_OFFSET] + ldr r10, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +mmdc_io_lpm: + ldr r8, [r7], #0x8 + mov r9, #0x0 + str r9, [r11, r8] + subs r10, r10, #0x1 + bne mmdc_io_lpm + + /* switch NIC clock to FIRC */ + ldr r10, [r0, #PM_INFO_PM_INFO_SCG1_VBASE_OFFSET] + ldr r7, [r10, #SCG_NICCCR] + bic r7, #(1 << 28) + str r7, [r10, #SCG_NICCCR] + + /* switch RUN clock to FIRC */ + ldr r7, [r10, #SCG_RCCR] + bic r7, #(0xf << 24) + orr r7, #(0x3 << 24) + str r7, [r10, #SCG_RCCR] + + /* turn off SPLL and SPFD */ + ldr r7, [r10, #SCG_SPLLPFD] + mov r8, r7 + orr r7, r7, #(0x1 << 31) + orr r7, r7, #(0x1 << 23) + orr r7, r7, #(0x1 << 15) + orr r7, r7, #(0x1 << 7) + str r7, [r10, #SCG_SPLLPFD] + + ldr r7, [r10, #SCG_SPLLCSR] + bic r7, r7, #0x1 + str r7, [r10, #SCG_SPLLCSR] + + /* turn off APLL and APFD */ + ldr r7, [r10, #SCG_APLLPFD] + mov r9, r7 + orr r7, r7, #(0x1 << 31) + orr r7, r7, #(0x1 << 23) + orr r7, r7, #(0x1 << 15) + orr r7, r7, #(0x1 << 7) + str r7, [r10, #SCG_APLLPFD] + + ldr r7, [r10, #SCG_APLLCSR] + bic r7, r7, #0x1 + str r7, [r10, #SCG_APLLCSR] + + /* Zzz, enter stop mode */ + wfi + nop + nop + nop + nop + + /* clear core0's entry and parameter */ + ldr r10, [r0, #PM_INFO_PM_INFO_SIM_VBASE_OFFSET] + mov r7, #0x0 + str r7, [r10, #DGO_GPR3] + str r7, [r10, #DGO_GPR4] + + /* enable SPLL and SPFD */ + ldr r10, [r0, #PM_INFO_PM_INFO_SCG1_VBASE_OFFSET] + ldr r7, [r10, #SCG_SPLLCSR] + orr r7, r7, #1 + str r7, [r10, #SCG_SPLLCSR] +wait_spll: + ldr r7, [r10, #SCG_SPLLCSR] + ands r7, r7, #(1 << 24) + beq wait_spll + + str r8, [r10, #SCG_SPLLPFD] + /* switch RUN clock to SPLL */ + ldr r7, [r10, #SCG_RCCR] + bic r7, #(0xf << 24) + orr r7, #(0x6 << 24) + str r7, [r10, #SCG_RCCR] + + /* enable APLL and APFD */ + ldr r7, [r10, #SCG_APLLCSR] + orr r7, r7, #1 + str r7, [r10, #SCG_APLLCSR] +wait_apll: + ldr r7, [r10, #SCG_APLLCSR] + ands r7, r7, #(1 << 24) + beq wait_apll + + str r9, [r10, #SCG_APLLPFD] + + /* switch NIC clock to DDR */ + ldr r7, [r10, #SCG_NICCCR] + orr r7, #(1 << 28) + str r7, [r10, #SCG_NICCCR] + + /* let mmdc io out of lpm */ + ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_IO_VBASE_OFFSET] + ldr r10, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET + add r7, r7, r0 +mmdc_io_exit_lpm: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r11, r8] + subs r10, r10, #0x1 + bne mmdc_io_exit_lpm + + /* let DDR out of self-refresh */ + ldr r11, [r0, #PM_INFO_PM_INFO_MMDC_VBASE_OFFSET] + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #(1 << 20) + str r7, [r11, #MX7ULP_MMDC_MAPSR] +poll_dvfs_clear: + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + ands r7, r7, #(1 << 24) + bne poll_dvfs_clear + + /* enable DDR auto power saving */ + ldr r7, [r11, #MX7ULP_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r11, #MX7ULP_MMDC_MAPSR] + + restore_ttbr1 + pop {r4-r12} + /* return to suspend finish */ + mov pc, lr + +resume: + /* invalidate L1 I-cache first */ + mov r6, #0x0 + mcr p15, 0, r6, c7, c5, 0 + mcr p15, 0, r6, c7, c5, 6 + /* enable the Icache and branch prediction */ + mov r6, #0x1800 + mcr p15, 0, r6, c1, c0, 0 + isb + + ldr r6, =MX7ULP_SIM_BASE_ADDR + ldr r0, [r6, #DGO_GPR4] + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + + ldr r11, =MX7ULP_SCG1_BASE_ADDR + /* enable spll and pfd0 */ + ldr r5, =PM_INFO_PM_INFO_SCG1_VAL_OFFSET + add r6, r5, #48 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLCFG] + + add r6, r5, #56 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLNUM] + + add r6, r5, #60 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLDENOM] + + add r6, r5, #40 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLCSR] +5: + ldr r7, [r11, #SCG_SPLLCSR] + ands r7, r7, #0x1000000 + beq 5b + + add r6, r5, #44 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLDIV] + + add r6, r5, #52 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SPLLPFD] + + add r6, r5, #0 + ldr r7, [r0, r6] + str r7, [r11, #SCG_RCCR] + + /* enable apll and pfd0 */ + add r6, r5, #24 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLCFG] + + add r6, r5, #32 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLNUM] + + add r6, r5, #36 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLDENOM] + + add r6, r5, #16 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLCSR] +6: + ldr r7, [r11, #SCG_APLLCSR] + ands r7, r7, #0x1000000 + beq 6b + + add r6, r5, #20 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLDIV] + + add r6, r5, #28 + ldr r7, [r0, r6] + str r7, [r11, #SCG_APLLPFD] + + /* set ddr ccr */ + add r6, r5, #4 + ldr r7, [r0, r6] + str r7, [r11, #SCG_DDRCCR] + + /* set nic sel */ + add r6, r5, #8 + ldr r7, [r0, r6] + str r7, [r11, #SCG_NICCCR] + + /* set firc div2 to get 48MHz */ + add r6, r5, #12 + ldr r7, [r0, r6] + str r7, [r11, #SCG_FIRCDIV] + + /* restore system OSC div */ + add r6, r5, #64 + ldr r7, [r0, r6] + str r7, [r11, #SCG_SOSCDIV] + + /* enable mmdc clock in pcc3 */ + ldr r11, =MX7ULP_PCC3_BASE_ADDR + ldr r7, [r11, #0xac] + orr r7, r7, #(1 << 30) + str r7, [r11, #0xac] + + /* enable GPIO clock in pcc2 */ + ldr r11, =MX7ULP_PCC2_BASE_ADDR + ldr r7, [r11, #0x3c] + orr r7, r7, #(1 << 30) + str r7, [r11, #0x3c] + + /* restore gpio settings */ + ldr r10, =MX7ULP_GPIOC_BASE_ADDR + ldr r7, =PM_INFO_MX7ULP_GPIO_REG_OFFSET + add r7, r7, r0 + ldr r6, =GPIO_PORT_NUM +12: + ldr r9, [r7], #0x4 + str r9, [r10, #GPIO_PDOR] + ldr r9, [r7], #0x4 + str r9, [r10, #GPIO_PDDR] + add r10, r10, #GPIO_PORT_OFFSET + subs r6, r6, #0x1 + bne 12b + + /* restore iomuxc settings */ + ldr r10, =MX7ULP_IOMUXC1_BASE_ADDR + add r10, r10, #IOMUX_OFFSET + ldr r6, [r0, #PM_INFO_IOMUX_NUM_OFFSET] + ldr r7, =PM_INFO_IOMUX_VAL_OFFSET + add r7, r7, r0 +13: + ldr r9, [r7], #0x4 + str r9, [r10], #0x4 + subs r6, r6, #0x1 + bne 13b + + /* restore select input settings */ + ldr r10, =MX7ULP_IOMUXC1_BASE_ADDR + add r10, r10, #SELECT_INPUT_OFFSET + ldr r6, [r0, #PM_INFO_SELECT_INPUT_NUM_OFFSET] + ldr r7, =PM_INFO_SELECT_INPUT_VAL_OFFSET + add r7, r7, r0 +14: + ldr r9, [r7], #0x4 + str r9, [r10], #0x4 + subs r6, r6, #0x1 + bne 14b + + /* isoack */ + ldr r6, =MX7ULP_PMC1_BASE_ADDR + ldr r7, [r6, #PMC1_CTRL] + orr r7, r7, #(1 << 14) + str r7, [r6, #PMC1_CTRL] + + restore_mmdc_settings + + mov pc, lr +ENDPROC(imx7ulp_suspend) + +ENTRY(imx7ulp_cpu_resume) + bl v7_invalidate_l1 + b cpu_resume +ENDPROC(imx7ulp_cpu_resume) From ec873f0edbf658dd46a163f71b375e20b495a2ea Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Thu, 8 Aug 2019 08:07:28 +0800 Subject: [PATCH 44/81] ARM: imx: Enable i.MX7ULP cpufreq driver Register i.MX7ULP cpufreq platform device to enable cpufreq driver. Signed-off-by: Anson Huang --- arch/arm/mach-imx/mach-imx7ulp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/mach-imx/mach-imx7ulp.c b/arch/arm/mach-imx/mach-imx7ulp.c index 65d20685a26c..419c7905fe05 100644 --- a/arch/arm/mach-imx/mach-imx7ulp.c +++ b/arch/arm/mach-imx/mach-imx7ulp.c @@ -83,6 +83,9 @@ static void __init imx7ulp_map_io(void) static void __init imx7ulp_init_late(void) { + if (IS_ENABLED(CONFIG_ARM_IMX7ULP_CPUFREQ)) + platform_device_register_simple("imx7ulp-cpufreq", -1, NULL, 0); + imx7ulp_cpuidle_init(); imx7ulp_enable_nmi(); } From 05d0ab781f40dac54fead37b8713dd19a8633542 Mon Sep 17 00:00:00 2001 From: Fugang Duan Date: Mon, 12 Aug 2019 15:54:39 +0800 Subject: [PATCH 45/81] MLK-9694 ARM: imx6: init enet MAC address Enet get MAC address order: From module parameters or kernel command line -> device tree -> pfuse -> mac registers set by bootloader -> random mac address. When there have no "fec.macaddr" parameters set in kernel command line, enet driver get MAC address from device tree. And then if the MAC address set in device tree and is valid, enet driver get MAC address from device tree. Otherwise,enet get MAarch/arm/mach-imx /mach-imx6q.c address from pfuse. So, in the condition, update the MAC address (read from pfuse) to device tree. Cherry-pick & Merge patches from: 149ac988a25b8d8eb86d05679cbb7b42819ff7a1 & 3269e5c06bdb2f7ab9bd5afa9bbfe46d872197d3 Signed-off-by: Fugang Duan Signed-off-by: Arulpandiyan Vadivel --- arch/arm/mach-imx/common.h | 1 + arch/arm/mach-imx/mach-imx6sl.c | 8 +++++++- arch/arm/mach-imx/mach-imx6sx.c | 1 + arch/arm/mach-imx/mach-imx7d.c | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index acdde60009f7..1e298e5cca3d 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -142,6 +142,7 @@ void imx_anatop_post_resume(void); int imx6_set_lpm(enum mxc_cpu_pwr_mode mode); void imx6_set_int_mem_clk_lpm(bool enable); void imx6sl_set_wait_clk(bool enter); +void imx6_enet_mac_init(const char *enet_compat, const char *ocotp_compat); void imx6sl_low_power_idle(void); void imx6sll_low_power_idle(void); void imx6sx_low_power_idle(void); diff --git a/arch/arm/mach-imx/mach-imx6sl.c b/arch/arm/mach-imx/mach-imx6sl.c index 5c21742ef796..85fe55cd8e46 100644 --- a/arch/arm/mach-imx/mach-imx6sl.c +++ b/arch/arm/mach-imx/mach-imx6sl.c @@ -16,7 +16,7 @@ #include "cpuidle.h" #include "hardware.h" -static void __init imx6sl_fec_init(void) +static void __init imx6sl_fec_clk_init(void) { struct regmap *gpr; @@ -32,6 +32,12 @@ static void __init imx6sl_fec_init(void) } } +static inline void imx6sl_fec_init(void) +{ + imx6sl_fec_clk_init(); + imx6_enet_mac_init("fsl,imx6sl-fec", "fsl,imx6sl-ocotp"); +} + static void __init imx6sl_init_late(void) { /* imx6sl reuses imx6q cpufreq driver */ diff --git a/arch/arm/mach-imx/mach-imx6sx.c b/arch/arm/mach-imx/mach-imx6sx.c index 35ef73346047..fd382d00d53f 100644 --- a/arch/arm/mach-imx/mach-imx6sx.c +++ b/arch/arm/mach-imx/mach-imx6sx.c @@ -57,6 +57,7 @@ static void __init imx6sx_enet_clk_sel(void) static inline void imx6sx_enet_init(void) { + imx6_enet_mac_init("fsl,imx6sx-fec", "fsl,imx6sx-ocotp"); imx6sx_enet_phy_init(); imx6sx_enet_clk_sel(); } diff --git a/arch/arm/mach-imx/mach-imx7d.c b/arch/arm/mach-imx/mach-imx7d.c index c303ae3b0dfa..ac1ae7ee79ef 100644 --- a/arch/arm/mach-imx/mach-imx7d.c +++ b/arch/arm/mach-imx/mach-imx7d.c @@ -79,6 +79,7 @@ static void __init imx7d_enet_clk_sel(void) static inline void imx7d_enet_init(void) { + imx6_enet_mac_init("fsl,imx7d-fec", "fsl,imx7d-ocotp"); imx7d_enet_phy_init(); imx7d_enet_clk_sel(); } From e67f13383bae696de87174fe35f53cec99dda346 Mon Sep 17 00:00:00 2001 From: Fugang Duan Date: Mon, 21 Dec 2015 17:58:42 +0800 Subject: [PATCH 46/81] MLK-12065 ARM: imx: imx7d: enable enet mdio open drain The management data input/output (MDIO) bus where often high-speed, open-drain operation is required. i.MX7D TO1.0 ENET MDIO pin has no open drain as IC ticket number: TKT252980, i.MX7D TO1.1 fix the issue. Signed-off-by: Fugang Duan (cherry picked from commit: a747abd5f01d278b91d1b6ee6628e1935cb7b23c) Conflicts: arch/arm/mach-imx/mach-imx7d.c Signed-off-by: Arulpandiyan Vadivel --- arch/arm/mach-imx/mach-imx7d.c | 18 ++++++++++++++++++ include/linux/mfd/syscon/imx7-iomuxc-gpr.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/arch/arm/mach-imx/mach-imx7d.c b/arch/arm/mach-imx/mach-imx7d.c index ac1ae7ee79ef..20eed10df965 100644 --- a/arch/arm/mach-imx/mach-imx7d.c +++ b/arch/arm/mach-imx/mach-imx7d.c @@ -64,6 +64,23 @@ static void __init imx7d_enet_phy_init(void) } } +static void __init imx7d_enet_mdio_fixup(void) +{ + struct regmap *gpr; + + /* The management data input/output (MDIO) bus where often high-speed, + * open-drain operation is required. i.MX7D TO1.0 ENET MDIO pin has no + * open drain as IC ticket number: TKT252980, i.MX7D TO1.1 fix the issue. + * GPR1[8:7] are reserved bits at TO1.0, there no need to add version check. + */ + gpr = syscon_regmap_lookup_by_compatible("fsl,imx7d-iomuxc-gpr"); + if (!IS_ERR(gpr)) + regmap_update_bits(gpr, IOMUXC_GPR0, IMX7D_GPR0_ENET_MDIO_OPEN_DRAIN_MASK, + IMX7D_GPR0_ENET_MDIO_OPEN_DRAIN_MASK); + else + pr_err("failed to find fsl,imx7d-iomux-gpr regmap\n"); +} + static void __init imx7d_enet_clk_sel(void) { struct regmap *gpr; @@ -80,6 +97,7 @@ static void __init imx7d_enet_clk_sel(void) static inline void imx7d_enet_init(void) { imx6_enet_mac_init("fsl,imx7d-fec", "fsl,imx7d-ocotp"); + imx7d_enet_mdio_fixup(); imx7d_enet_phy_init(); imx7d_enet_clk_sel(); } diff --git a/include/linux/mfd/syscon/imx7-iomuxc-gpr.h b/include/linux/mfd/syscon/imx7-iomuxc-gpr.h index 3d46907bab89..4b9797c78395 100644 --- a/include/linux/mfd/syscon/imx7-iomuxc-gpr.h +++ b/include/linux/mfd/syscon/imx7-iomuxc-gpr.h @@ -31,6 +31,8 @@ #define IOMUXC_GPR22 0x58 /* For imx7d iomux gpr register field define */ +#define IMX7D_GPR0_ENET_MDIO_OPEN_DRAIN_MASK (0x3 << 7) + #define IMX7D_GPR1_IRQ_MASK (0x1 << 12) #define IMX7D_GPR1_ENET1_TX_CLK_SEL_MASK (0x1 << 13) #define IMX7D_GPR1_ENET2_TX_CLK_SEL_MASK (0x1 << 14) From a3990871b98587f53548e769c23fda5c2644063c Mon Sep 17 00:00:00 2001 From: Fugang Duan Date: Mon, 12 Aug 2019 17:31:46 +0800 Subject: [PATCH 47/81] ARM: imx: correct the enet_clk_ref clock string Correct the clock string "enet_ref" to "enet_clk_ref". Signed-off-by: Fugang Duan --- arch/arm/mach-imx/mach-imx6q.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c index 54c89bdad39f..d21c4798054e 100644 --- a/arch/arm/mach-imx/mach-imx6q.c +++ b/arch/arm/mach-imx/mach-imx6q.c @@ -194,7 +194,7 @@ static void __init imx6q_1588_init(void) goto put_node; } - enet_ref = clk_get_sys(NULL, "enet_ref"); + enet_ref = clk_get_sys(NULL, "enet_clk_ref"); if (IS_ERR(enet_ref)) { pr_warn("%s: failed to get enet clock\n", __func__); goto put_ptp_clk; From c894edb3670ad6bc68a35ba513e33b6e98e64db3 Mon Sep 17 00:00:00 2001 From: Fugang Duan Date: Thu, 25 Dec 2014 17:17:49 +0800 Subject: [PATCH 48/81] MLK-10060 ARM: i.MX6: disable ethernet phy AR8031 EEE mode in default Disable ethernet phy AR8031 EEE mode in default to reduce the IEEE1588 latency. Signed-off-by: Fugang Duan Signed-off-by: Arulpandiyan Vadivel --- arch/arm/mach-imx/mach-imx6q.c | 8 ++++++++ arch/arm/mach-imx/mach-imx6sx.c | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c index d21c4798054e..ef9c1f8f8391 100644 --- a/arch/arm/mach-imx/mach-imx6q.c +++ b/arch/arm/mach-imx/mach-imx6q.c @@ -106,6 +106,14 @@ static int ar8031_phy_fixup(struct phy_device *dev) { u16 val; + /* disable phy AR8031 SmartEEE function. */ + phy_write(dev, 0xd, 0x3); + phy_write(dev, 0xe, 0x805d); + phy_write(dev, 0xd, 0x4003); + val = phy_read(dev, 0xe); + val &= ~(0x1 << 8); + phy_write(dev, 0xe, val); + /* To enable AR8031 output a 125MHz clk from CLK_25M */ phy_write(dev, 0xd, 0x7); phy_write(dev, 0xe, 0x8016); diff --git a/arch/arm/mach-imx/mach-imx6sx.c b/arch/arm/mach-imx/mach-imx6sx.c index fd382d00d53f..a02b403d3ab5 100644 --- a/arch/arm/mach-imx/mach-imx6sx.c +++ b/arch/arm/mach-imx/mach-imx6sx.c @@ -23,6 +23,14 @@ static int ar8031_phy_fixup(struct phy_device *dev) phy_write(dev, 0x1d, 0x1f); phy_write(dev, 0x1e, 0x8); + /* disable phy AR8031 SmartEEE function. */ + phy_write(dev, 0xd, 0x3); + phy_write(dev, 0xe, 0x805d); + phy_write(dev, 0xd, 0x4003); + val = phy_read(dev, 0xe); + val &= ~(0x1 << 8); + phy_write(dev, 0xe, val); + /* introduce tx clock delay */ phy_write(dev, 0x1d, 0x5); val = phy_read(dev, 0x1e); From 05f69cb034d6fe1033d07086647d94b37fddf910 Mon Sep 17 00:00:00 2001 From: Fugang Duan Date: Mon, 12 Aug 2019 17:44:50 +0800 Subject: [PATCH 49/81] MLK-10463-1 ARM: imx: init ENET RGMII tx clock source Init ENET RGMII tx clock source, set GPR5[9] to select clock from internal PLL_enet. And set phy VDDIO to 1.8V that get better signal quality. Signed-off-by: Fugang Duan (cherry picked from commit: d7a171fcf5218166f558428610ca8e9cb9f7e830) Signed-off-by: Arulpandiyan Vadivel --- arch/arm/mach-imx/mach-imx6q.c | 29 ++++++++++++++++++--- include/linux/mfd/syscon/imx6q-iomuxc-gpr.h | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c index ef9c1f8f8391..e33a22896dc3 100644 --- a/arch/arm/mach-imx/mach-imx6q.c +++ b/arch/arm/mach-imx/mach-imx6q.c @@ -106,6 +106,10 @@ static int ar8031_phy_fixup(struct phy_device *dev) { u16 val; + /* Set RGMII IO voltage to 1.8V */ + phy_write(dev, 0x1d, 0x1f); + phy_write(dev, 0x1e, 0x8); + /* disable phy AR8031 SmartEEE function. */ phy_write(dev, 0xd, 0x3); phy_write(dev, 0xe, 0x805d); @@ -231,6 +235,27 @@ put_node: of_node_put(np); } +static void __init imx6q_enet_clk_sel(void) +{ + struct regmap *gpr; + + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (!IS_ERR(gpr)) + regmap_update_bits(gpr, IOMUXC_GPR5, + IMX6Q_GPR5_ENET_TX_CLK_SEL, IMX6Q_GPR5_ENET_TX_CLK_SEL); + else + pr_err("failed to find fsl,imx6q-iomux-gpr regmap\n"); +} + +static inline void imx6q_enet_init(void) +{ + imx6_enet_mac_init("fsl,imx6q-fec", "fsl,imx6q-ocotp"); + imx6q_enet_phy_init(); + imx6q_1588_init(); + if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) + imx6q_enet_clk_sel(); +} + static void __init imx6q_axi_init(void) { struct regmap *gpr; @@ -278,13 +303,11 @@ static void __init imx6q_init_machine(void) if (parent == NULL) pr_warn("failed to initialize soc device\n"); - imx6q_enet_phy_init(); - of_platform_default_populate(NULL, NULL, parent); imx_anatop_init(); + imx6q_enet_init(); cpu_is_imx6q() ? imx6q_pm_init() : imx6dl_pm_init(); - imx6q_1588_init(); imx6q_axi_init(); } diff --git a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h index d4b5e527a7a3..db1c35143ff8 100644 --- a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h +++ b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h @@ -240,6 +240,7 @@ #define IMX6Q_GPR4_IPU_RD_CACHE_CTL BIT(0) #define IMX6Q_GPR5_L2_CLK_STOP BIT(8) +#define IMX6Q_GPR5_ENET_TX_CLK_SEL BIT(9) #define IMX6Q_GPR5_SATA_SW_PD BIT(10) #define IMX6Q_GPR5_SATA_SW_RST BIT(11) From a86c2f7db7cc477985bcdfe369daaa98b61f42a0 Mon Sep 17 00:00:00 2001 From: Fugang Duan Date: Tue, 13 Aug 2019 17:42:24 +0800 Subject: [PATCH 50/81] ARM: imx6ul/ull: init enet MAC address Add enet MAC address read from efuse. Signed-off-by: Fugang Duan --- arch/arm/mach-imx/mach-imx6ul.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-imx/mach-imx6ul.c b/arch/arm/mach-imx/mach-imx6ul.c index f7d35b012899..0f6d18c4b27f 100644 --- a/arch/arm/mach-imx/mach-imx6ul.c +++ b/arch/arm/mach-imx/mach-imx6ul.c @@ -52,6 +52,7 @@ static inline void imx6ul_enet_init(void) { imx6ul_enet_clk_init(); imx6ul_enet_phy_init(); + imx6_enet_mac_init("fsl,imx6ul-fec", "fsl,imx6ul-ocotp"); } static void __init imx6ul_init_machine(void) From 9528afb1991f965697bcb1f8c8b22fc7d678d32c Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Thu, 22 Aug 2019 13:34:28 +0800 Subject: [PATCH 51/81] mfd: imx6qp: Add PRE/PRG mux control register definitions for iomuxc gpr This patch adds macros to define masks, shifts and bits for imx6qp PRE/PRG mux control registers. Signed-off-by: Liu Ying --- include/linux/mfd/syscon/imx6q-iomuxc-gpr.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h index db1c35143ff8..30331a8631a9 100644 --- a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h +++ b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h @@ -243,6 +243,22 @@ #define IMX6Q_GPR5_ENET_TX_CLK_SEL BIT(9) #define IMX6Q_GPR5_SATA_SW_PD BIT(10) #define IMX6Q_GPR5_SATA_SW_RST BIT(11) +#define IMX6Q_GPR5_PRE_PRG_SEL0_MASK (0x3 << 12) +#define IMX6Q_GPR5_PRE_PRG_SEL0_SHIFT 12 +#define IMX6Q_GPR5_PRE_PRG_SEL0_MSB 13 +#define IMX6Q_GPR5_PRE_PRG_SEL0_LSB 12 +#define IMX6Q_GPR5_PRE_PRG_SEL0_PRE1_PRG0_CHAN1 (0x0 << 12) +#define IMX6Q_GPR5_PRE_PRG_SEL0_PRE1_PRG0_CHAN2 (0x1 << 12) +#define IMX6Q_GPR5_PRE_PRG_SEL0_PRE1_PRG1_CHAN1 (0x2 << 12) +#define IMX6Q_GPR5_PRE_PRG_SEL0_PRE1_PRG1_CHAN2 (0x3 << 12) +#define IMX6Q_GPR5_PRE_PRG_SEL1_MASK (0x3 << 14) +#define IMX6Q_GPR5_PRE_PRG_SEL1_SHIFT 14 +#define IMX6Q_GPR5_PRE_PRG_SEL1_MSB 15 +#define IMX6Q_GPR5_PRE_PRG_SEL1_LSB 14 +#define IMX6Q_GPR5_PRE_PRG_SEL1_PRE2_PRG0_CHAN1 (0x0 << 14) +#define IMX6Q_GPR5_PRE_PRG_SEL1_PRE2_PRG0_CHAN2 (0x1 << 14) +#define IMX6Q_GPR5_PRE_PRG_SEL1_PRE2_PRG1_CHAN1 (0x2 << 14) +#define IMX6Q_GPR5_PRE_PRG_SEL1_PRE2_PRG1_CHAN2 (0x3 << 14) #define IMX6Q_GPR6_IPU1_ID00_WR_QOS_MASK (0xf << 0) #define IMX6Q_GPR6_IPU1_ID01_WR_QOS_MASK (0xf << 4) From 8cb7f0e456bb3962691711efad5fbe06cd9a5c2f Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Thu, 22 Aug 2019 14:24:06 +0800 Subject: [PATCH 52/81] mfd: imx6dl: Add LDB/IPU DI mux control register definitions for iomuxc gpr This patch adds macros to define masks and bits for imx6dl LDB/IPU DI mux control registers. Signed-off-by: Liu Ying --- include/linux/mfd/syscon/imx6q-iomuxc-gpr.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h index 30331a8631a9..487fdb7edad3 100644 --- a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h +++ b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h @@ -467,6 +467,16 @@ #define IMX6SX_GPR12_PCIE_RX_EQ_MASK (0x7 << 0) #define IMX6SX_GPR12_PCIE_RX_EQ_2 (0x2 << 0) +/* For imx6dl iomux gpr register field definitions */ +#define IMX6DL_GPR3_LVDS1_MUX_CTL_MASK (0x3 << 8) +#define IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI0 (0x0 << 8) +#define IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI1 (0x1 << 8) +#define IMX6DL_GPR3_LVDS1_MUX_CTL_LCDIF (0x2 << 8) +#define IMX6DL_GPR3_LVDS0_MUX_CTL_MASK (0x3 << 6) +#define IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI0 (0x0 << 6) +#define IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI1 (0x1 << 6) +#define IMX6DL_GPR3_LVDS0_MUX_CTL_LCDIF (0x2 << 6) + /* For imx6ul iomux gpr register field define */ #define IMX6UL_GPR1_ENET1_CLK_DIR (0x1 << 17) #define IMX6UL_GPR1_ENET2_CLK_DIR (0x1 << 18) From 23b3047588aef3ac58614ad26fddd4410f6fc6e8 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Mon, 26 Aug 2019 09:23:13 +0800 Subject: [PATCH 53/81] ARM: imx: Add anatop initialization for i.MX SoCs i.MX SoCs needs anatop for suspend/resume, add anatop initialization for them. Signed-off-by: Anson Huang --- arch/arm/mach-imx/mach-imx6sl.c | 1 + arch/arm/mach-imx/mach-imx6sx.c | 1 + arch/arm/mach-imx/mach-imx6ul.c | 1 + arch/arm/mach-imx/mach-imx7d.c | 1 + 4 files changed, 4 insertions(+) diff --git a/arch/arm/mach-imx/mach-imx6sl.c b/arch/arm/mach-imx/mach-imx6sl.c index 85fe55cd8e46..d1c6a9fa826a 100644 --- a/arch/arm/mach-imx/mach-imx6sl.c +++ b/arch/arm/mach-imx/mach-imx6sl.c @@ -60,6 +60,7 @@ static void __init imx6sl_init_machine(void) of_platform_default_populate(NULL, NULL, parent); + imx_anatop_init(); if (cpu_is_imx6sl()) imx6sl_fec_init(); imx_anatop_init(); diff --git a/arch/arm/mach-imx/mach-imx6sx.c b/arch/arm/mach-imx/mach-imx6sx.c index a02b403d3ab5..118e9c9bb5a9 100644 --- a/arch/arm/mach-imx/mach-imx6sx.c +++ b/arch/arm/mach-imx/mach-imx6sx.c @@ -80,6 +80,7 @@ static void __init imx6sx_init_machine(void) of_platform_default_populate(NULL, NULL, parent); + imx_anatop_init(); imx6sx_enet_init(); imx_anatop_init(); imx6sx_pm_init(); diff --git a/arch/arm/mach-imx/mach-imx6ul.c b/arch/arm/mach-imx/mach-imx6ul.c index 0f6d18c4b27f..732f9419acd0 100644 --- a/arch/arm/mach-imx/mach-imx6ul.c +++ b/arch/arm/mach-imx/mach-imx6ul.c @@ -64,6 +64,7 @@ static void __init imx6ul_init_machine(void) pr_warn("failed to initialize soc device\n"); of_platform_default_populate(NULL, NULL, parent); + imx_anatop_init(); imx6ul_enet_init(); imx_anatop_init(); imx6ul_pm_init(); diff --git a/arch/arm/mach-imx/mach-imx7d.c b/arch/arm/mach-imx/mach-imx7d.c index 20eed10df965..7ad5e6f240dd 100644 --- a/arch/arm/mach-imx/mach-imx7d.c +++ b/arch/arm/mach-imx/mach-imx7d.c @@ -124,6 +124,7 @@ static void __init imx7d_init_machine(void) imx_anatop_init(); of_platform_default_populate(NULL, NULL, parent); imx7d_pm_init(); + imx_anatop_init(); imx7d_enet_init(); } From 738569521c80f9babe2dbc7a00863ee73b336beb Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Mon, 26 Aug 2019 10:05:39 +0800 Subject: [PATCH 54/81] ARM: imx: Fix i.MX7D debug uart path Correct i.MX7D debug uart path for Mega/Fast mix off suspend to avoid output mess during resume. Signed-off-by: Anson Huang --- arch/arm/mach-imx/pm-imx7.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-imx/pm-imx7.c b/arch/arm/mach-imx/pm-imx7.c index e9f4d86f21aa..1535190c7990 100644 --- a/arch/arm/mach-imx/pm-imx7.c +++ b/arch/arm/mach-imx/pm-imx7.c @@ -1218,7 +1218,7 @@ void __init imx7d_pm_init(void) WARN_ON(!ocram_saved_in_ddr); np = of_find_node_by_path( - "/soc/aips-bus@30800000/serial@30860000"); + "/soc/aips-bus@30800000/spba-bus@30800000/serial@30860000"); if (np) console_base = of_iomap(np, 0); From 2baaa057202479788c436f7fd333f434c7f381ea Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 4 Sep 2019 17:22:46 +0800 Subject: [PATCH 55/81] ARM: imx: Remove PU power operation for i.MX6QP The GPC power domain driver add GENPD_FLAG_RPM_ALWAYS_ON to the i.MX6QP's PU power domain flag, that means it is always ON for runtime PM but can be OFF during suspend, so no need to explicitly power ON/OFF PU power for i.MX6QP during suspend/resume to avoid below dump: Unable to handle kernel NULL pointer dereference at virtual address 00000044 pgd = 20824a30 [00000044] *pgd=4e36d831 Internal error: Oops: 17 [#1] SMP ARM Modules linked in: CPU: 0 PID: 732 Comm: sh Tainted: G W 5.3.0-rc3-next-20190809-01770-g0a0b3ec-dir3 Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree) PC is at regmap_update_bits_base+0x10/0x74 LR is at imx6_pm_domain_power_on+0xbc/0x1b4 pc : [] lr : [] psr: 600001d3 sp : e9339d68 ip : e9338000 fp : c1a24158 r10: c1308b08 r9 : 00000260 r8 : c1308b08 r7 : c1426120 r6 : c1426120 r5 : c1373580 r4 : 00000000 r3 : 00000001 r2 : 00000001 r1 : 00000260 r0 : 00000000 Flags: nZCv IRQs off FIQs off Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 3949404a DAC: 00000051 Process sh (pid: 732, stack limit = 0x8ba716d6) Signed-off-by: Anson Huang --- arch/arm/mach-imx/gpc.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/arch/arm/mach-imx/gpc.c b/arch/arm/mach-imx/gpc.c index 71484c457f14..5007a3b96c6f 100644 --- a/arch/arm/mach-imx/gpc.c +++ b/arch/arm/mach-imx/gpc.c @@ -53,10 +53,6 @@ static u32 gpc_mf_irqs[IMR_NUM]; static u32 gpc_mf_request_on[IMR_NUM]; static DEFINE_SPINLOCK(gpc_lock); -/* implemented in drivers/soc/imx/gpc.c */ -extern void _imx6_pm_pu_power_off(void); -extern void _imx6_pm_pu_power_on(void); - void imx_gpc_add_m4_wake_up_irq(u32 hwirq, bool enable) { unsigned int idx = hwirq / 32; @@ -199,9 +195,6 @@ void imx_gpc_pre_suspend(bool arm_power_off) void __iomem *reg_imr1 = gpc_base + GPC_IMR1; int i; - if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) - _imx6_pm_pu_power_off(); - /* power down the mega-fast power domain */ if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) && arm_power_off) @@ -222,9 +215,6 @@ void imx_gpc_post_resume(void) void __iomem *reg_imr1 = gpc_base + GPC_IMR1; int i; - if (cpu_is_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) - _imx6_pm_pu_power_on(); - /* Keep ARM core powered on for other low-power modes */ imx_gpc_set_arm_power_in_lpm(false); /* Keep M/F mix powered on for other low-power modes */ From 12456dda6cfcaa87e83cbe72f7c8259e357a663c Mon Sep 17 00:00:00 2001 From: Leonard Crestez Date: Thu, 10 Oct 2019 16:48:22 +0300 Subject: [PATCH 56/81] soc: imx8mq: Read SOC revision from TF-A SOC revision on older imx8mq is not available in fuses so on anything other than B1 current code just reports "unknown". TF-A already handles this by parsing the ROM and exposes the value through a SMC call. Call this instead of reimplementing the workaround in the kernel itself. Signed-off-by: Leonard Crestez Tested-by: Clark Wang (cherry picked from commit e814909ddca3067d089a8bd62084aae851387f79) --- drivers/soc/imx/soc-imx8.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/drivers/soc/imx/soc-imx8.c b/drivers/soc/imx/soc-imx8.c index 416e099dd4eb..e66decdbaf3e 100644 --- a/drivers/soc/imx/soc-imx8.c +++ b/drivers/soc/imx/soc-imx8.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,8 @@ #define IMX8MQ_SW_INFO_B1 0x40 #define IMX8MQ_SW_MAGIC_B1 0xff0055aa +#define IMX_SIP_GET_SOC_INFO 0xc2000006 + #define OCOTP_UID_LOW 0x410 #define OCOTP_UID_HIGH 0x420 @@ -40,6 +43,18 @@ static ssize_t soc_uid_show(struct device *dev, static DEVICE_ATTR_RO(soc_uid); +static u32 imx8mq_soc_revision_from_atf(void) +{ + struct arm_smccc_res res; + + arm_smccc_smc(IMX_SIP_GET_SOC_INFO, 0, 0, 0, 0, 0, 0, 0, &res); + + if (res.a0 == SMCCC_RET_NOT_SUPPORTED) + return 0; + else + return res.a0 & 0xff; +} + static u32 __init imx8mq_soc_revision(void) { struct device_node *np; @@ -54,9 +69,16 @@ static u32 __init imx8mq_soc_revision(void) ocotp_base = of_iomap(np, 0); WARN_ON(!ocotp_base); - magic = readl_relaxed(ocotp_base + IMX8MQ_SW_INFO_B1); - if (magic == IMX8MQ_SW_MAGIC_B1) - rev = REV_B1; + /* + * SOC revision on older imx8mq is not available in fuses so query + * the value from ATF instead. + */ + rev = imx8mq_soc_revision_from_atf(); + if (!rev) { + magic = readl_relaxed(ocotp_base + IMX8MQ_SW_INFO_B1); + if (magic == IMX8MQ_SW_MAGIC_B1) + rev = REV_B1; + } soc_uid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH); soc_uid <<= 32; From fbfe972a072a1e8ff14b989cb033313a8a2d0044 Mon Sep 17 00:00:00 2001 From: Robby Cai Date: Fri, 11 Oct 2019 20:44:19 +0800 Subject: [PATCH 57/81] ARM: imx: add ipu csi mux setting for imx6qdl add csi mux setting for imx6qdl Signed-off-by: Robby Cai (cherry picked from commit 2e10c8c02446f4fd20ac98172e86b185892480ba) --- arch/arm/mach-imx/mach-imx6q.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c index e33a22896dc3..dcfbffccb7dc 100644 --- a/arch/arm/mach-imx/mach-imx6q.c +++ b/arch/arm/mach-imx/mach-imx6q.c @@ -235,6 +235,37 @@ put_node: of_node_put(np); } +static void __init imx6q_csi_mux_init(void) +{ + /* + * MX6Q SabreSD board: + * IPU1 CSI0 connects to parallel interface. + * Set GPR1 bit 19 to 0x1. + * + * MX6DL SabreSD board: + * IPU1 CSI0 connects to parallel interface. + * Set GPR13 bit 0-2 to 0x4. + * IPU1 CSI1 connects to MIPI CSI2 virtual channel 1. + * Set GPR13 bit 3-5 to 0x1. + */ + struct regmap *gpr; + + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (!IS_ERR(gpr)) { + if (of_machine_is_compatible("fsl,imx6q-sabresd") || + of_machine_is_compatible("fsl,imx6q-sabreauto") || + of_machine_is_compatible("fsl,imx6qp-sabresd") || + of_machine_is_compatible("fsl,imx6qp-sabreauto")) + regmap_update_bits(gpr, IOMUXC_GPR1, 1 << 19, 1 << 19); + else if (of_machine_is_compatible("fsl,imx6dl-sabresd") || + of_machine_is_compatible("fsl,imx6dl-sabreauto")) + regmap_update_bits(gpr, IOMUXC_GPR13, 0x3F, 0x0C); + } else { + pr_err("%s(): failed to find fsl,imx6q-iomux-gpr regmap\n", + __func__); + } +} + static void __init imx6q_enet_clk_sel(void) { struct regmap *gpr; @@ -307,6 +338,7 @@ static void __init imx6q_init_machine(void) imx_anatop_init(); imx6q_enet_init(); + imx6q_csi_mux_init(); cpu_is_imx6q() ? imx6q_pm_init() : imx6dl_pm_init(); imx6q_axi_init(); } From 593bea4e36d8c8a4fd65ef4f07fb8144dab2de1c Mon Sep 17 00:00:00 2001 From: Jason Liu Date: Thu, 31 Oct 2019 14:48:12 +0800 Subject: [PATCH 58/81] MLK-16005-2 arm64: tlb: add the SW workaround for i.MX8QM TKT340553 on i.MX8QM 1.0/1.1,TLB maintenance through DVM messages over ARADDR channel, some bits (see the following) will be corrupted: ASID[15:12] VA[48:45] VA[44:41] VA[39:36] This issue will result in the TLB aintenance across the clusters not working as expected due to some VA and ASID bits get corrupted The SW workaround is: use the vmalle1is if VA larger than 36bits or ASID[15:12] is not zero, otherwise, we use original TLB maintenance path. Note: To simplify the code, we did not check VA[40] bit specifically Signed-off-by: Jason Liu Reviewed-by: Anson Huang --- arch/arm64/include/asm/tlbflush.h | 37 ++++++++++++++++++++++++------- drivers/soc/imx/soc-imx-scu.c | 7 ++++-- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/arch/arm64/include/asm/tlbflush.h b/arch/arm64/include/asm/tlbflush.h index bc3949064725..2220f4f92ff9 100644 --- a/arch/arm64/include/asm/tlbflush.h +++ b/arch/arm64/include/asm/tlbflush.h @@ -15,6 +15,8 @@ #include #include +extern bool TKT340553_SW_WORKAROUND; + /* * Raw TLBI operations. * @@ -149,8 +151,12 @@ static inline void flush_tlb_mm(struct mm_struct *mm) unsigned long asid = __TLBI_VADDR(0, ASID(mm)); dsb(ishst); - __tlbi(aside1is, asid); - __tlbi_user(aside1is, asid); + if (TKT340553_SW_WORKAROUND && ASID(mm) >> 12) { + __tlbi(vmalle1is); + } else { + __tlbi(aside1is, asid); + __tlbi_user(aside1is, asid); + } dsb(ish); } @@ -160,8 +166,12 @@ static inline void flush_tlb_page_nosync(struct vm_area_struct *vma, unsigned long addr = __TLBI_VADDR(uaddr, ASID(vma->vm_mm)); dsb(ishst); - __tlbi(vale1is, addr); - __tlbi_user(vale1is, addr); + if (TKT340553_SW_WORKAROUND && (uaddr >> 36 || (ASID(vma->vm_mm) >> 12))) { + __tlbi(vmalle1is); + } else { + __tlbi(vale1is, addr); + __tlbi_user(vale1is, addr); + } } static inline void flush_tlb_page(struct vm_area_struct *vma, @@ -183,6 +193,7 @@ static inline void __flush_tlb_range(struct vm_area_struct *vma, { unsigned long asid = ASID(vma->vm_mm); unsigned long addr; + unsigned long mask = (1 << 20) - 1; start = round_down(start, stride); end = round_up(end, stride); @@ -197,10 +208,13 @@ static inline void __flush_tlb_range(struct vm_area_struct *vma, start = __TLBI_VADDR(start, asid); end = __TLBI_VADDR(end, asid); + mask <<= 24; dsb(ishst); for (addr = start; addr < end; addr += stride) { - if (last_level) { + if (TKT340553_SW_WORKAROUND && (addr & mask || (ASID(vma->vm_mm) >> 12))) { + __tlbi(vmalle1is); + } else if (last_level) { __tlbi(vale1is, addr); __tlbi_user(vale1is, addr); } else { @@ -234,8 +248,12 @@ static inline void flush_tlb_kernel_range(unsigned long start, unsigned long end end = __TLBI_VADDR(end, 0); dsb(ishst); - for (addr = start; addr < end; addr += 1 << (PAGE_SHIFT - 12)) - __tlbi(vaale1is, addr); + for (addr = start; addr < end; addr += 1 << (PAGE_SHIFT - 12)) { + if (TKT340553_SW_WORKAROUND && addr >> 24) + __tlbi(vmalle1is); + else + __tlbi(vaale1is, addr); + } dsb(ish); isb(); } @@ -249,7 +267,10 @@ static inline void __flush_tlb_kernel_pgtable(unsigned long kaddr) unsigned long addr = __TLBI_VADDR(kaddr, 0); dsb(ishst); - __tlbi(vaae1is, addr); + if (TKT340553_SW_WORKAROUND && addr >> 24) + __tlbi(vmalle1is); + else + __tlbi(vaae1is, addr); dsb(ish); isb(); } diff --git a/drivers/soc/imx/soc-imx-scu.c b/drivers/soc/imx/soc-imx-scu.c index 0448c08a4421..c1413d9c81e8 100644 --- a/drivers/soc/imx/soc-imx-scu.c +++ b/drivers/soc/imx/soc-imx-scu.c @@ -12,6 +12,8 @@ #define IMX_SCU_SOC_DRIVER_NAME "imx-scu-soc" +bool TKT340553_SW_WORKAROUND; + static struct imx_sc_ipc *soc_ipc_handle; struct imx_sc_msg_misc_get_soc_id { @@ -114,9 +116,10 @@ static int imx_scu_soc_probe(struct platform_device *pdev) /* format soc_id value passed from SCU firmware */ val = id & 0x1f; - if (of_machine_is_compatible("fsl,imx8qm")) + if (of_machine_is_compatible("fsl,imx8qm")) { soc_dev_attr->soc_id = "i.MX8QM"; - else if (of_machine_is_compatible("fsl,imx8qxp")) + TKT340553_SW_WORKAROUND = true; + } else if (of_machine_is_compatible("fsl,imx8qxp")) soc_dev_attr->soc_id = "i.MX8QXP"; /* format revision value passed from SCU firmware */ From b704a998a5a92531032d7199797966073b211483 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Tue, 19 Nov 2019 17:46:41 +0800 Subject: [PATCH 59/81] MLK-23002 ARM: imx: Make sure system counter frequency change successfully During system counter frequency change, the counter will stop, it takes several mS even up to 20mS to finish the frequency change, if system enters STOP mode before the frequency change done, system counter will NOT run during STOP mode, then it will case system time inaccurate and sometimes cause below RCU stall, so system can ONLY enter STOP mode after the system counter frequency change done by checking the ACK of frequency change. rtc_testapp_6 0 TINFO : Waiting 50 seconds for alarm....... fec 30be0000.ethernet eth0: Link is Down PM: suspend devices took 0.670 seconds Disabling non-boot CPUs ... Enabling non-boot CPUs ... rcu: INFO: rcu_sched self-detected stall on CPU rcu: 0-...!: (1 ticks this GP) idle=1f6/1/0x40000002 softirq=5240/5240 fqs=0 (t=4903 jiffies g=3737 q=4) rcu: rcu_sched kthread starved for 4903 jiffies! g3737 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x402 ->0 rcu: RCU grace-period kthread stack dump: rcu_sched I 0 10 2 0x00000000 [] (__schedule) from [] (schedule+0x50/0xc4) [] (schedule) from [] (schedule_timeout+0x1b8/0x37c) [] (schedule_timeout) from [] (rcu_gp_kthread+0x8cc/0x1678) [] (rcu_gp_kthread) from [] (kthread+0x114/0x14c) [] (kthread) from [] (ret_from_fork+0x14/0x20) Exception stack(0xd80fdfb0 to 0xd80fdff8) dfa0: 00000000 00000000 00000000 00000000 dfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 dfe0: 00000000 00000000 00000000 00000000 00000013 00000000 NMI backtrace for cpu 0 CPU: 0 PID: 834 Comm: rtc_testapp_6 Not tainted 5.4.0-rc7-03214-g56a9ca3 #105 Hardware name: Freescale i.MX7 Dual (Device Tree) [] (unwind_backtrace) from [] (show_stack+0x10/0x14) [] (show_stack) from [] (dump_stack+0xe4/0x118) [] (dump_stack) from [] (nmi_cpu_backtrace+0xac/0xbc) [] (nmi_cpu_backtrace) from [] (nmi_trigger_cpumask_backtrace+0xe0/0x130) [] (nmi_trigger_cpumask_backtrace) from [] (rcu_dump_cpu_stacks+0x9c/0xd8) [] (rcu_dump_cpu_stacks) from [] (rcu_sched_clock_irq+0x940/0xbec) [] (rcu_sched_clock_irq) from [] (update_process_times+0x2c/0x54) [] (update_process_times) from [] (tick_sched_timer+0x5c/0xc0) [] (tick_sched_timer) from [] (__hrtimer_run_queues+0x140/0x548) [] (__hrtimer_run_queues) from [] (hrtimer_interrupt+0x134/0x2bc) [] (hrtimer_interrupt) from [] (arch_timer_handler_phys+0x2c/0x34) [] (arch_timer_handler_phys) from [] (handle_percpu_devid_irq+0xd4/0x384) [] (handle_percpu_devid_irq) from [] (generic_handle_irq+0x20/0x34) [] (generic_handle_irq) from [] (__handle_domain_irq+0x64/0xe0) [] (__handle_domain_irq) from [] (gic_handle_irq+0x4c/0xa0) [] (gic_handle_irq) from [] (__irq_svc+0x70/0x98) Exception stack(0xd8cedd00 to 0xd8cedd48) dd00: 00000001 d88a5d20 00000000 200a0013 00000000 00000000 c1b3f6f0 0000002a dd20: d8cec000 00000000 c1b3dbf8 c1b3d6f8 c16c82bc d8cedd50 c018fe50 c019dd68 dd40: 200a0013 ffffffff [] (__irq_svc) from [] (console_unlock+0x4e0/0x634) [] (console_unlock) from [] (vprintk_emit+0xf4/0x2d0) [] (vprintk_emit) from [] (vprintk_default+0x24/0x2c) [] (vprintk_default) from [] (printk+0x2c/0x54) [] (printk) from [] (enable_nonboot_cpus+0x38/0x2cc) [] (enable_nonboot_cpus) from [] (suspend_devices_and_enter+0x374/0xa44) [] (suspend_devices_and_enter) from [] (pm_suspend+0x2ec/0x3d0) [] (pm_suspend) from [] (state_store+0x68/0xc8) [] (state_store) from [] (kernfs_fop_write+0xfc/0x1e0) [] (kernfs_fop_write) from [] (__vfs_write+0x2c/0x1d0) [] (__vfs_write) from [] (vfs_write+0xa0/0x180) [] (vfs_write) from [] (ksys_write+0x5c/0xd8) [] (ksys_write) from [] (ret_fast_syscall+0x0/0x28) Exception stack(0xd8cedfa8 to 0xd8cedff0) dfa0: 00036294 00021590 00000004 bef5cd29 00000007 00000000 dfc0: 00036294 00021590 b6fc0d20 00000004 00000000 00000000 b6fc2fa4 00000000 dfe0: 00000004 bef5c8e8 b6f35d4f b6ec1d16 CPU1 is up imx6q-pcie 33800000.pcie: Phy link never came up imx6q-pcie 33800000.pcie: pcie link is down after resume. mmc1: queuing unknown CIS tuple 0x80 (2 bytes) mmc1: queuing unknown CIS tuple 0x80 (7 bytes) mmc1: queuing unknown CIS tuple 0x80 (6 bytes) PM: resume devices took 0.220 seconds OOM killer enabled. Restarting tasks ... done. PM: suspend exit Signed-off-by: Anson Huang Reviewed-by: Jacky Bai --- arch/arm/mach-imx/pm-imx7.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm/mach-imx/pm-imx7.c b/arch/arm/mach-imx/pm-imx7.c index 1535190c7990..48e167bf18b2 100644 --- a/arch/arm/mach-imx/pm-imx7.c +++ b/arch/arm/mach-imx/pm-imx7.c @@ -90,6 +90,9 @@ #define BM_CCM_ROOT_MUX 0x7000000 #define BM_CCM_ROOT_ENABLE 0x10000000 +#define SYS_COUNTER_CNTSR 0x4 +#define BM_SYS_COUNTER_CNTSR_FCR1 0x200 +#define BM_SYS_COUNTER_CNTSR_FCR0 0x100 #define BM_SYS_COUNTER_CNTCR_FCR1 0x200 #define BM_SYS_COUNTER_CNTCR_FCR0 0x100 @@ -743,6 +746,9 @@ static int imx7_pm_enter(suspend_state_t state) val &= ~BM_SYS_COUNTER_CNTCR_FCR0; val |= BM_SYS_COUNTER_CNTCR_FCR1; writel_relaxed(val, system_counter_ctrl_base); + while (!(readl_relaxed(system_counter_ctrl_base + SYS_COUNTER_CNTSR) + & BM_SYS_COUNTER_CNTSR_FCR1)) + ; switch (state) { case PM_SUSPEND_STANDBY: @@ -848,6 +854,9 @@ static int imx7_pm_enter(suspend_state_t state) val &= ~BM_SYS_COUNTER_CNTCR_FCR1; val |= BM_SYS_COUNTER_CNTCR_FCR0; writel_relaxed(val, system_counter_ctrl_base); + while (!(readl_relaxed(system_counter_ctrl_base + SYS_COUNTER_CNTSR) + & BM_SYS_COUNTER_CNTSR_FCR0)) + ; return 0; } From e7002cc2bd41c8cdc360ccf4638c27c69651fc88 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 20 Nov 2019 14:25:51 +0800 Subject: [PATCH 60/81] MLK-23008 ARM: imx: Remove unused code on i.MX7D suspend driver Remove unused code on i.MX7D suspend driver. Signed-off-by: Anson Huang Reviewed-by: Jacky Bai --- arch/arm/mach-imx/pm-imx7.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/arch/arm/mach-imx/pm-imx7.c b/arch/arm/mach-imx/pm-imx7.c index 48e167bf18b2..e59cbee6dfc2 100644 --- a/arch/arm/mach-imx/pm-imx7.c +++ b/arch/arm/mach-imx/pm-imx7.c @@ -983,11 +983,10 @@ void __init imx7_pm_map_io(void) static int __init imx7_suspend_init(const struct imx7_pm_socdata *socdata) { - struct device_node *node; - int i, ret = 0; const u32 (*ddrc_offset_array)[2]; const u32 (*ddrc_phy_offset_array)[2]; unsigned long iram_paddr; + int i; suspend_set_ops(&imx7_pm_ops); @@ -1101,19 +1100,14 @@ static int __init imx7_suspend_init(const struct imx7_pm_socdata *socdata) } if (psci_ops.cpu_suspend) - goto put_node; + return 0; imx7_suspend_in_ocram_fn = fncpy( suspend_ocram_base + sizeof(*pm_info), &imx7_suspend, MX7_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); - goto put_node; - -put_node: - of_node_put(node); - - return ret; + return 0; } static void __init imx7_pm_common_init(const struct imx7_pm_socdata From 82dbf8881db62b9680bb515f53fddaa22e9c45f2 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Thu, 21 Nov 2019 14:19:56 +0800 Subject: [PATCH 61/81] LF-39 soc: imx: Update busfreq to support different frequncy setpoint On i.MX8M SOC family, we can support LPDDR4, DDR4 or DDR3L, we may need to support different setpoint for audio & low bus mode on different DDR type, So update the code to get all the supported setpoint info from ATF. The maximum setpoints that can be supported by hardware is 4, if the drate for a setpoint is '0', that means this setpoint is not enabled. We can use these info to find out the lowest drate setpoint for audio & low bus mode. BuildInfo: - ATF 59fe78cfe7 Signed-off-by: Jacky Bai Reviewed-by: Anson Huang --- drivers/soc/imx/busfreq-imx8mq.c | 264 ++++++++++--------------------- 1 file changed, 83 insertions(+), 181 deletions(-) diff --git a/drivers/soc/imx/busfreq-imx8mq.c b/drivers/soc/imx/busfreq-imx8mq.c index 624c638b5eb0..acd49fd78d0d 100644 --- a/drivers/soc/imx/busfreq-imx8mq.c +++ b/drivers/soc/imx/busfreq-imx8mq.c @@ -41,6 +41,7 @@ #define LOW_BUS_FREQ_100MTS 0x2 #define LOW_BUS_FREQ_667MTS 0x1 #define WAIT_BUS_FREQ_DONE 0xf +#define DLL_ON_DRATE 667 static struct device *busfreq_dev; static int low_bus_freq_mode; @@ -53,6 +54,12 @@ static int cur_bus_freq_mode; static int busfreq_suspended; static bool cancel_reduce_bus_freq; +static unsigned int fsp_table[4]; +static unsigned long origin_noc_rate; +static int low_bus_mode_fsp_index; +/* no bypass or dll off mode support if lowest fsp > 667mts */ +static bool bypass_support = true; + static struct clk *dram_pll_clk; static struct clk *sys1_pll_800m; static struct clk *sys1_pll_400m; @@ -74,16 +81,6 @@ static struct delayed_work bus_freq_daemon; DEFINE_MUTEX(bus_freq_mutex); -static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id) -{ - struct arm_smccc_res res; - /* call smc trap to ATF */ - arm_smccc_smc(FSL_SIP_DDR_DVFS, WAIT_BUS_FREQ_DONE, 0, - 0, 0, 0, 0, 0, &res); - - return IRQ_HANDLED; -} - static void update_bus_freq(int target_freq) { struct arm_smccc_res res; @@ -102,24 +99,6 @@ static void update_bus_freq(int target_freq) local_irq_enable(); } -/* Match B0 or older */ -static const struct soc_device_attribute imx8mq_b0_older_soc_match[] = { - { - .soc_id = "i.MX8MQ", - .revision = "2.0", - }, - { - .soc_id = "i.MX8MQ", - .revision = "1.*", - }, - { /* sentinel */ } -}; - -static inline bool imx8mq_supports_100mts(void) -{ - return !soc_device_match(imx8mq_b0_older_soc_match); -} - static void reduce_bus_freq(void) { u32 rate; @@ -133,50 +112,14 @@ static void reduce_bus_freq(void) */ if (audio_bus_count) { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { - - if (of_machine_is_compatible("fsl,imx8mq")) { - if (!imx8mq_supports_100mts()) { - update_bus_freq(LOW_BUS_FREQ_667MTS); - - /* - * the dram_apb and dram_core clk rate is changed - * in ATF side, below two lines of code is just used - * to upate the clock tree info in kernel side. - */ - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); - } else { - /* prepare the necessary clk before frequency change */ - clk_prepare_enable(sys1_pll_40m); - clk_prepare_enable(dram_alt_root); - clk_prepare_enable(sys1_pll_100m); - - update_bus_freq(LOW_BUS_FREQ_100MTS); - - clk_set_parent(dram_alt_src, sys1_pll_100m); - clk_set_parent(dram_core_clk, dram_alt_root); - clk_set_parent(dram_apb_src, sys1_pll_40m); - clk_set_rate(dram_apb_pre_div, 20000000); - clk_disable_unprepare(sys1_pll_100m); - clk_disable_unprepare(sys1_pll_40m); - clk_disable_unprepare(dram_alt_root); - } - /* reduce the NOC & bus clock */ - rate = clk_get_rate(noc_div); - if (rate == 0) { - WARN_ON(1); - return; - } - clk_set_rate(noc_div, rate / 8); - } else { + if (bypass_support) { /* prepare the necessary clk before frequency change */ clk_prepare_enable(sys1_pll_40m); clk_prepare_enable(dram_alt_root); clk_prepare_enable(sys1_pll_100m); - update_bus_freq(LOW_BUS_FREQ_100MTS); + update_bus_freq(low_bus_mode_fsp_index); - /* correct the clock tree info */ clk_set_parent(dram_alt_src, sys1_pll_100m); clk_set_parent(dram_core_clk, dram_alt_root); clk_set_parent(dram_apb_src, sys1_pll_40m); @@ -184,15 +127,22 @@ static void reduce_bus_freq(void) clk_disable_unprepare(sys1_pll_100m); clk_disable_unprepare(sys1_pll_40m); clk_disable_unprepare(dram_alt_root); - - /* change the NOC rate */ - rate = clk_get_rate(noc_div); - if (rate == 0) { - WARN_ON(1); - return; - } - clk_set_rate(noc_div, rate / 5); + } else { + update_bus_freq(low_bus_mode_fsp_index); + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to update the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); } + /* change the NOC rate */ + if (of_machine_is_compatible("fsl,imx8mq")) + clk_set_rate(noc_div, origin_noc_rate / 8); + else + clk_set_rate(noc_div, origin_noc_rate / 5); + rate = clk_get_rate(ahb_div); if (rate == 0) { WARN_ON(1); @@ -207,49 +157,14 @@ static void reduce_bus_freq(void) cur_bus_freq_mode = BUS_FREQ_AUDIO; } else { if (cur_bus_freq_mode == BUS_FREQ_HIGH) { - if (of_machine_is_compatible("fsl,imx8mq")) { - if (!imx8mq_supports_100mts()) { - update_bus_freq(LOW_BUS_FREQ_667MTS); - - /* - * the dram_apb and dram_core clk rate is changed - * in ATF side, below two lines of code is just used - * to upate the clock tree info in kernel side. - */ - clk_set_rate(dram_apb_pre_div, 160000000); - clk_get_rate(dram_pll_clk); - } else { - /* prepare the necessary clk before frequency change */ - clk_prepare_enable(sys1_pll_40m); - clk_prepare_enable(dram_alt_root); - clk_prepare_enable(sys1_pll_100m); - - update_bus_freq(LOW_BUS_FREQ_100MTS); - - clk_set_parent(dram_alt_src, sys1_pll_100m); - clk_set_parent(dram_core_clk, dram_alt_root); - clk_set_parent(dram_apb_src, sys1_pll_40m); - clk_set_rate(dram_apb_pre_div, 20000000); - clk_disable_unprepare(sys1_pll_100m); - clk_disable_unprepare(sys1_pll_40m); - clk_disable_unprepare(dram_alt_root); - } - /* reduce the NOC & bus clock */ - rate = clk_get_rate(noc_div); - if (rate == 0) { - WARN_ON(1); - return; - } - clk_set_rate(noc_div, rate / 8); - } else { + if (bypass_support) { /* prepare the necessary clk before frequency change */ clk_prepare_enable(sys1_pll_40m); clk_prepare_enable(dram_alt_root); clk_prepare_enable(sys1_pll_100m); - update_bus_freq(LOW_BUS_FREQ_100MTS); + update_bus_freq(low_bus_mode_fsp_index); - /* correct the clock tree info */ clk_set_parent(dram_alt_src, sys1_pll_100m); clk_set_parent(dram_core_clk, dram_alt_root); clk_set_parent(dram_apb_src, sys1_pll_40m); @@ -257,16 +172,23 @@ static void reduce_bus_freq(void) clk_disable_unprepare(sys1_pll_100m); clk_disable_unprepare(sys1_pll_40m); clk_disable_unprepare(dram_alt_root); - - /* change the NOC clock rate */ - rate = clk_get_rate(noc_div); - if (rate == 0) { - WARN_ON(1); - return; - } - clk_set_rate(noc_div, rate / 5); + } else { + update_bus_freq(low_bus_mode_fsp_index); + /* + * the dram_apb and dram_core clk rate is changed + * in ATF side, below two lines of code is just used + * to update the clock tree info in kernel side. + */ + clk_set_rate(dram_apb_pre_div, 160000000); + clk_get_rate(dram_pll_clk); } + /* change the NOC rate */ + if (of_machine_is_compatible("fsl,imx8mq")) + clk_set_rate(noc_div, origin_noc_rate / 8); + else + clk_set_rate(noc_div, origin_noc_rate / 5); + rate = clk_get_rate(ahb_div); if (rate == 0) { WARN_ON(1); @@ -341,30 +263,7 @@ static int set_high_bus_freq(int high_bus_freq) if (high_bus_freq_mode) return 0; - if (of_machine_is_compatible("fsl,imx8mq")) { - if (!imx8mq_supports_100mts()) { - /* switch the DDR freqeuncy */ - update_bus_freq(HIGH_FREQ_3200MTS); - - clk_set_rate(dram_apb_pre_div, 200000000); - clk_get_rate(dram_pll_clk); - } else { - /* enable the clks needed in frequency */ - clk_prepare_enable(sys1_pll_800m); - clk_prepare_enable(dram_pll_clk); - - /* switch the DDR freqeuncy */ - update_bus_freq(HIGH_FREQ_3200MTS); - - /* correct the clock tree info */ - clk_set_parent(dram_apb_src, sys1_pll_800m); - clk_set_rate(dram_apb_pre_div, 160000000); - clk_set_parent(dram_core_clk, dram_pll_clk); - clk_disable_unprepare(sys1_pll_800m); - clk_disable_unprepare(dram_pll_clk); - } - clk_set_rate(noc_div, 800000000); - } else { + if (bypass_support) { /* enable the clks needed in frequency */ clk_prepare_enable(sys1_pll_800m); clk_prepare_enable(dram_pll_clk); @@ -378,9 +277,15 @@ static int set_high_bus_freq(int high_bus_freq) clk_set_parent(dram_core_clk, dram_pll_clk); clk_disable_unprepare(sys1_pll_800m); clk_disable_unprepare(dram_pll_clk); - clk_set_rate(noc_div, 750000000); + } else { + /* switch the DDR freqeuncy */ + update_bus_freq(HIGH_FREQ_3200MTS); + + clk_set_rate(dram_apb_pre_div, 200000000); + clk_get_rate(dram_pll_clk); } + clk_set_rate(noc_div, origin_noc_rate); clk_set_rate(ahb_div, 133333333); clk_set_parent(main_axi_src, sys2_pll_333m); @@ -571,37 +476,6 @@ static struct notifier_block imx_busfreq_reboot_notifier = { static DEVICE_ATTR(enable, 0644, bus_freq_scaling_enable_show, bus_freq_scaling_enable_store); -static int init_busfreq_irq(struct platform_device *busfreq_pdev) -{ - struct device *dev = &busfreq_pdev->dev; - u32 cpu; - int err; - - for_each_online_cpu(cpu) { - int irq; - /* - * set up a reserved interrupt to get all - * the active cores into a WFE state before - * changing the DDR frequency. - */ - irq = platform_get_irq(busfreq_pdev, cpu); - err = request_irq(irq, wait_in_wfe_irq, - IRQF_PERCPU, "ddrc", NULL); - if (err) { - dev_err(dev, "Busfreq request irq failed %d, err = %d\n", - irq, err); - return err; - } - err = irq_set_affinity(irq, cpumask_of(cpu)); - if (err) { - dev_err(dev, "busfreq can't set irq affinity irq = %d\n", irq); - return err; - } - } - - return 0; -} - static int imx8mq_init_busfreq_clk(struct platform_device *pdev) { dram_pll_clk = devm_clk_get(&pdev->dev, "dram_pll"); @@ -669,9 +543,11 @@ static int imx8mm_init_busfreq_clk(struct platform_device *pdev) * @return The function returns 0 on success * */ + static int busfreq_probe(struct platform_device *pdev) { - int err; + int i, err; + struct arm_smccc_res res; busfreq_dev = &pdev->dev; @@ -686,13 +562,39 @@ static int busfreq_probe(struct platform_device *pdev) return err; } - /* init the irq used for ddr frequency change */ - err = init_busfreq_irq(pdev); - if (err) { - dev_err(busfreq_dev, "init busfreq irq failed!\n"); - return err; + origin_noc_rate = clk_get_rate(noc_div); + if (origin_noc_rate == 0) { + WARN_ON(1); + return -EINVAL; } + /* + * Get the supported frequency, normally the lowest frequency point + * is used for low bus & audio bus mode. + */ + for (i = 0; i < 4; i++) { + arm_smccc_smc(FSL_SIP_DDR_DVFS, 0x11, i, 0, 0, 0, 0, 0, &res); + err = res.a0; + if (err < 0) + return -EINVAL; + + fsp_table[i] = res.a0; + } + + /* get the lowest fsp index */ + for (i = 0; i < 4; i++) + if (fsp_table[i] == 0) + break; + + low_bus_mode_fsp_index = i - 1; + + /* + * if lowest fsp data rate higher than 666mts, then no dll off mode or + * bypass mode support. + */ + if (fsp_table[low_bus_mode_fsp_index] >= DLL_ON_DRATE) + bypass_support = false; + /* create the sysfs file */ err = sysfs_create_file(&busfreq_dev->kobj, &dev_attr_enable.attr); if (err) { From 4ef3de913f5c5c5e76479141c66b172a8fe200a1 Mon Sep 17 00:00:00 2001 From: Alison Wang Date: Fri, 17 Jul 2015 17:11:52 +0800 Subject: [PATCH 62/81] arm: kernel: utilize hrtimer based broadcast Hrtimer based broadcast is used on ARM platform. It can be registered as the tick broadcast device in the absence of a real external clock device. Signed-off-by: Alison Wang Acked-by: Mark Rutland --- arch/arm/kernel/time.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/kernel/time.c b/arch/arm/kernel/time.c index b996b2cf0703..a148421cf482 100644 --- a/arch/arm/kernel/time.c +++ b/arch/arm/kernel/time.c @@ -9,6 +9,7 @@ * reading the RTC at bootup, etc... */ #include +#include #include #include #include @@ -107,5 +108,7 @@ void __init time_init(void) of_clk_init(NULL); #endif timer_probe(); + + tick_setup_hrtimer_broadcast(); } } From 928debc34462850e699b0bf0fd043c849e731e26 Mon Sep 17 00:00:00 2001 From: Haiying Wang Date: Wed, 22 Apr 2015 13:09:47 -0400 Subject: [PATCH 63/81] arm64: add support to remap kernel cacheable memory to userspace Signed-off-by: Haiying Wang Reviewed-by: Roy Pledge Reviewed-by: Stuart Yoder --- arch/arm64/include/asm/pgtable.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h index 565aa45ef134..dcc47e89de95 100644 --- a/arch/arm64/include/asm/pgtable.h +++ b/arch/arm64/include/asm/pgtable.h @@ -418,6 +418,9 @@ static inline pmd_t pmd_mkdevmap(pmd_t pmd) __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRnE) | PTE_PXN | PTE_UXN) #define pgprot_writecombine(prot) \ __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN) +#define pgprot_cached(prot) \ + __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL) | \ + PTE_PXN | PTE_UXN) #define pgprot_device(prot) \ __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_PXN | PTE_UXN) /* From 7c0b2833ff8e7d3b3f2ad93ba90ee86e3732aaaa Mon Sep 17 00:00:00 2001 From: Haiying Wang Date: Sat, 8 Aug 2015 07:25:02 -0400 Subject: [PATCH 64/81] arm64/pgtable: add support to map cacheable and non shareable memory Signed-off-by: Haiying Wang --- arch/arm64/include/asm/pgtable.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h index dcc47e89de95..0b32152d74e7 100644 --- a/arch/arm64/include/asm/pgtable.h +++ b/arch/arm64/include/asm/pgtable.h @@ -421,6 +421,8 @@ static inline pmd_t pmd_mkdevmap(pmd_t pmd) #define pgprot_cached(prot) \ __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL) | \ PTE_PXN | PTE_UXN) +#define pgprot_cached_ns(prot) \ + __pgprot(pgprot_val(pgprot_cached(prot)) ^ PTE_SHARED) #define pgprot_device(prot) \ __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_PXN | PTE_UXN) /* From d761bbdcfe9e31ed05cc19f7b377a3efc39c00fd Mon Sep 17 00:00:00 2001 From: Haiying Wang Date: Wed, 22 Apr 2015 13:07:25 -0400 Subject: [PATCH 65/81] arm64: add ioremap for normal cacheable non-shareable memory Signed-off-by: Haiying Wang Reviewed-by: Roy Pledge Reviewed-by: Stuart Yoder --- arch/arm64/include/asm/io.h | 1 + arch/arm64/include/asm/pgtable-prot.h | 1 + 2 files changed, 2 insertions(+) diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h index 323cb306bd28..0b4bbc602222 100644 --- a/arch/arm64/include/asm/io.h +++ b/arch/arm64/include/asm/io.h @@ -170,6 +170,7 @@ extern void __iomem *ioremap_cache(phys_addr_t phys_addr, size_t size); #define ioremap_nocache(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE)) #define ioremap_wc(addr, size) __ioremap((addr), (size), __pgprot(PROT_NORMAL_NC)) #define ioremap_wt(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE)) +#define ioremap_cache_ns(addr, size) __ioremap((addr), (size), __pgprot(PROT_NORMAL_NS)) /* * PCI configuration space mapping function. diff --git a/arch/arm64/include/asm/pgtable-prot.h b/arch/arm64/include/asm/pgtable-prot.h index 8dc6c5cdabe6..1973209e7c1e 100644 --- a/arch/arm64/include/asm/pgtable-prot.h +++ b/arch/arm64/include/asm/pgtable-prot.h @@ -37,6 +37,7 @@ #define PROT_NORMAL_NC (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_NC)) #define PROT_NORMAL_WT (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_WT)) #define PROT_NORMAL (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL)) +#define PROT_NORMAL_NS (PTE_TYPE_PAGE | PTE_AF | PTE_PXN | PTE_UXN | PTE_DIRTY | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL)) #define PROT_SECT_DEVICE_nGnRE (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_DEVICE_nGnRE)) #define PROT_SECT_NORMAL (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_NORMAL)) From 4e02081fa81ddd9c893e3ce11c4a341b8439e6ed Mon Sep 17 00:00:00 2001 From: Zhao Qiang Date: Fri, 10 Oct 2014 10:38:48 +0800 Subject: [PATCH 66/81] arch: arm: add ARM specific fucntions required for ehci fsl driver Add below functions for ARM platform which are used by ehci fsl driver: 1. spin_event_timeout function 2. set/clear bits functions Signed-off-by: Zhao Qiang Signed-off-by: Rajesh Bhagat --- arch/arm/include/asm/delay.h | 16 ++++++++++++++++ arch/arm/include/asm/io.h | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index 4f80b72372b4..f1aebe735321 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -85,6 +85,22 @@ extern void __bad_udelay(void); __const_udelay((n) * UDELAY_MULT)) : \ __udelay(n)) +#define spin_event_timeout(condition, timeout, delay) \ +({ \ + typeof(condition) __ret; \ + int i = 0; \ + while (!(__ret = (condition)) && (i++ < timeout)) { \ + if (delay) \ + udelay(delay); \ + else \ + cpu_relax(); \ + udelay(1); \ + } \ + if (!__ret) \ + __ret = (condition); \ + __ret; \ +}) + /* Loop-based definitions for assembly code. */ extern void __loop_delay(unsigned long loops); extern void __loop_udelay(unsigned long usecs); diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index 7a0596fcb2e7..9dffeacde7ba 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -224,6 +224,34 @@ void __iomem *pci_remap_cfgspace(resource_size_t res_cookie, size_t size); #endif #endif +/* access ports */ +#define setbits32(_addr, _v) iowrite32be(ioread32be(_addr) | (_v), (_addr)) +#define clrbits32(_addr, _v) iowrite32be(ioread32be(_addr) & ~(_v), (_addr)) + +#define setbits16(_addr, _v) iowrite16be(ioread16be(_addr) | (_v), (_addr)) +#define clrbits16(_addr, _v) iowrite16be(ioread16be(_addr) & ~(_v), (_addr)) + +#define setbits8(_addr, _v) iowrite8(ioread8(_addr) | (_v), (_addr)) +#define clrbits8(_addr, _v) iowrite8(ioread8(_addr) & ~(_v), (_addr)) + +/* Clear and set bits in one shot. These macros can be used to clear and + * set multiple bits in a register using a single read-modify-write. These + * macros can also be used to set a multiple-bit bit pattern using a mask, + * by specifying the mask in the 'clear' parameter and the new bit pattern + * in the 'set' parameter. + */ + +#define clrsetbits_be32(addr, clear, set) \ + iowrite32be((ioread32be(addr) & ~(clear)) | (set), (addr)) +#define clrsetbits_le32(addr, clear, set) \ + iowrite32le((ioread32le(addr) & ~(clear)) | (set), (addr)) +#define clrsetbits_be16(addr, clear, set) \ + iowrite16be((ioread16be(addr) & ~(clear)) | (set), (addr)) +#define clrsetbits_le16(addr, clear, set) \ + iowrite16le((ioread16le(addr) & ~(clear)) | (set), (addr)) +#define clrsetbits_8(addr, clear, set) \ + iowrite8((ioread8(addr) & ~(clear)) | (set), (addr)) + /* * IO port access primitives * ------------------------- From 0babba4195661762131165f35de99f0802c48ade Mon Sep 17 00:00:00 2001 From: Madalin Bucur Date: Wed, 7 Jun 2017 17:54:10 +0300 Subject: [PATCH 67/81] export arch_setup_dma_ops() Signed-off-by: Madalin Bucur --- arch/arm64/mm/dma-mapping.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/mm/dma-mapping.c b/arch/arm64/mm/dma-mapping.c index 9239416e93d4..1cab08bd8f7b 100644 --- a/arch/arm64/mm/dma-mapping.c +++ b/arch/arm64/mm/dma-mapping.c @@ -57,3 +57,4 @@ void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size, dev->dma_ops = &xen_swiotlb_dma_ops; #endif } +EXPORT_SYMBOL(arch_setup_dma_ops); From 690e978155e9524d7ff472c2f1725b6f395f4df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horia=20Geant=C4=83?= Date: Mon, 19 Jun 2017 16:38:59 +0300 Subject: [PATCH 68/81] arm: dma-mapping: export arch_setup_dma_ops() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Horia Geantă --- arch/arm/mm/dma-mapping.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index 7d042d5c43e3..de61796ef295 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c @@ -2320,6 +2320,7 @@ void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size, #endif dev->archdata.dma_ops_setup = true; } +EXPORT_SYMBOL(arch_setup_dma_ops); void arch_teardown_dma_ops(struct device *dev) { From da39c8ee965d9d2e78ef662f1faae93bf08bdd71 Mon Sep 17 00:00:00 2001 From: Pan Jiafei Date: Thu, 17 Mar 2016 02:01:03 +0000 Subject: [PATCH 69/81] arm: add new non-shareable ioremap Signed-off-by: Pan Jiafei Signed-off-by: Roy Pledge --- arch/arm/include/asm/io.h | 3 +++ arch/arm/include/asm/mach/map.h | 4 ++-- arch/arm/mm/ioremap.c | 7 +++++++ arch/arm/mm/mmu.c | 9 +++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index 9dffeacde7ba..b026d499e42d 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -123,6 +123,7 @@ static inline u32 __raw_readl(const volatile void __iomem *addr) #define MT_DEVICE_NONSHARED 1 #define MT_DEVICE_CACHED 2 #define MT_DEVICE_WC 3 +#define MT_MEMORY_RW_NS 4 /* * types 4 onwards can be found in asm/mach/map.h and are undefined * for ioremap @@ -438,6 +439,8 @@ void __iomem *ioremap_wc(resource_size_t res_cookie, size_t size); #define ioremap_wc ioremap_wc #define ioremap_wt ioremap_wc +void __iomem *ioremap_cache_ns(resource_size_t res_cookie, size_t size); + void iounmap(volatile void __iomem *iomem_cookie); #define iounmap iounmap diff --git a/arch/arm/include/asm/mach/map.h b/arch/arm/include/asm/mach/map.h index 92282558caf7..05e1af2179f9 100644 --- a/arch/arm/include/asm/mach/map.h +++ b/arch/arm/include/asm/mach/map.h @@ -18,9 +18,9 @@ struct map_desc { unsigned int type; }; -/* types 0-3 are defined in asm/io.h */ +/* types 0-4 are defined in asm/io.h */ enum { - MT_UNCACHED = 4, + MT_UNCACHED = 5, MT_CACHECLEAN, MT_MINICLEAN, MT_LOW_VECTORS, diff --git a/arch/arm/mm/ioremap.c b/arch/arm/mm/ioremap.c index d42b93316183..5606c0e7fde4 100644 --- a/arch/arm/mm/ioremap.c +++ b/arch/arm/mm/ioremap.c @@ -399,6 +399,13 @@ void __iomem *ioremap_wc(resource_size_t res_cookie, size_t size) } EXPORT_SYMBOL(ioremap_wc); +void __iomem *ioremap_cache_ns(resource_size_t res_cookie, size_t size) +{ + return arch_ioremap_caller(res_cookie, size, MT_MEMORY_RW_NS, + __builtin_return_address(0)); +} +EXPORT_SYMBOL(ioremap_cache_ns); + /* * Remap an arbitrary physical address space into the kernel virtual * address space as memory. Needed when the kernel wants to execute diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c index 48c2888297dd..5cf31b491382 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -312,6 +312,13 @@ static struct mem_type mem_types[] __ro_after_init = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE, .domain = DOMAIN_KERNEL, }, + [MT_MEMORY_RW_NS] = { + .prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | + L_PTE_XN, + .prot_l1 = PMD_TYPE_TABLE, + .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_XN, + .domain = DOMAIN_KERNEL, + }, [MT_ROM] = { .prot_sect = PMD_TYPE_SECT, .domain = DOMAIN_KERNEL, @@ -648,6 +655,7 @@ static void __init build_mem_type_table(void) } kern_pgprot |= PTE_EXT_AF; vecs_pgprot |= PTE_EXT_AF; + mem_types[MT_MEMORY_RW_NS].prot_pte |= PTE_EXT_AF | cp->pte; /* * Set PXN for user mappings @@ -676,6 +684,7 @@ static void __init build_mem_type_table(void) mem_types[MT_MEMORY_RWX].prot_pte |= kern_pgprot; mem_types[MT_MEMORY_RW].prot_sect |= ecc_mask | cp->pmd; mem_types[MT_MEMORY_RW].prot_pte |= kern_pgprot; + mem_types[MT_MEMORY_RW_NS].prot_sect |= ecc_mask | cp->pmd; mem_types[MT_MEMORY_DMA_READY].prot_pte |= kern_pgprot; mem_types[MT_MEMORY_RWX_NONCACHED].prot_sect |= ecc_mask; mem_types[MT_ROM].prot_sect |= cp->pmd; From f262f731132cd267145008707a9d02aad4451459 Mon Sep 17 00:00:00 2001 From: Jianhua Xie Date: Fri, 29 Jan 2016 16:40:46 +0800 Subject: [PATCH 70/81] arm: add pgprot_cached and pgprot_cached_ns support Signed-off-by: Jianhua Xie --- arch/arm/include/asm/pgtable.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/include/asm/pgtable.h b/arch/arm/include/asm/pgtable.h index 3ae120cd1715..ea5c29c11bc8 100644 --- a/arch/arm/include/asm/pgtable.h +++ b/arch/arm/include/asm/pgtable.h @@ -116,6 +116,13 @@ extern pgprot_t pgprot_s2_device; #define pgprot_noncached(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED) +#define pgprot_cached(prot) \ + __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_DEV_CACHED) + +#define pgprot_cached_ns(prot) \ + __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_DEV_CACHED | \ + L_PTE_MT_DEV_NONSHARED) + #define pgprot_writecombine(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE) From bbe3181057f5658405b0bee1aaf38fd0382fd7a2 Mon Sep 17 00:00:00 2001 From: Laurentiu Tudor Date: Tue, 5 Dec 2017 15:24:05 +0200 Subject: [PATCH 71/81] arm64: add stage-2 cache-able non-shareable page type Signed-off-by: Laurentiu Tudor --- arch/arm64/include/asm/pgtable-prot.h | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/include/asm/pgtable-prot.h b/arch/arm64/include/asm/pgtable-prot.h index 1973209e7c1e..5ef7d5eca0d0 100644 --- a/arch/arm64/include/asm/pgtable-prot.h +++ b/arch/arm64/include/asm/pgtable-prot.h @@ -78,6 +78,7 @@ }) #define PAGE_S2 __pgprot(_PROT_DEFAULT | PAGE_S2_MEMATTR(NORMAL) | PTE_S2_RDONLY | PAGE_S2_XN) +#define PAGE_S2_NS __pgprot(PAGE_S2_MEMATTR(NORMAL) | PTE_S2_RDWR | PTE_TYPE_PAGE | PTE_AF) #define PAGE_S2_DEVICE __pgprot(_PROT_DEFAULT | PAGE_S2_MEMATTR(DEVICE_nGnRE) | PTE_S2_RDONLY | PTE_S2_XN) #define PAGE_NONE __pgprot(((_PAGE_DEFAULT) & ~PTE_VALID) | PTE_PROT_NONE | PTE_RDONLY | PTE_NG | PTE_PXN | PTE_UXN) From cbe10bae8d67b04e943b57dd724c25e945cc046a Mon Sep 17 00:00:00 2001 From: Zhao Qiang Date: Wed, 1 Nov 2017 13:59:41 +0800 Subject: [PATCH 72/81] irqchip/qeic: merge qeic init code from platforms to a common function [PowerPC part] The codes of qe_ic init from a variety of platforms are redundant, merge them to a common function and put it to irqchip/irq-qeic.c For non-p1021_mds mpc85xx_mds boards, use "qe_ic_init(np, 0, qe_ic_cascade_low_mpic, qe_ic_cascade_high_mpic);" instead of "qe_ic_init(np, 0, qe_ic_cascade_muxed_mpic, NULL);". qe_ic_cascade_muxed_mpic was used for boards has the same interrupt number for low interrupt and high interrupt, qe_ic_init has checked if "low interrupt == high interrupt" Signed-off-by: Zhao Qiang --- arch/powerpc/platforms/83xx/misc.c | 15 --------------- arch/powerpc/platforms/85xx/corenet_generic.c | 9 --------- arch/powerpc/platforms/85xx/mpc85xx_mds.c | 14 -------------- arch/powerpc/platforms/85xx/mpc85xx_rdb.c | 16 ---------------- arch/powerpc/platforms/85xx/twr_p102x.c | 14 -------------- 5 files changed, 68 deletions(-) diff --git a/arch/powerpc/platforms/83xx/misc.c b/arch/powerpc/platforms/83xx/misc.c index f46d7bf3b140..5f7b79b43f03 100644 --- a/arch/powerpc/platforms/83xx/misc.c +++ b/arch/powerpc/platforms/83xx/misc.c @@ -90,24 +90,9 @@ void __init mpc83xx_ipic_init_IRQ(void) } #ifdef CONFIG_QUICC_ENGINE -void __init mpc83xx_qe_init_IRQ(void) -{ - struct device_node *np; - - np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic"); - if (!np) { - np = of_find_node_by_type(NULL, "qeic"); - if (!np) - return; - } - qe_ic_init(np, 0, qe_ic_cascade_low_ipic, qe_ic_cascade_high_ipic); - of_node_put(np); -} - void __init mpc83xx_ipic_and_qe_init_IRQ(void) { mpc83xx_ipic_init_IRQ(); - mpc83xx_qe_init_IRQ(); } #endif /* CONFIG_QUICC_ENGINE */ diff --git a/arch/powerpc/platforms/85xx/corenet_generic.c b/arch/powerpc/platforms/85xx/corenet_generic.c index 7ee2c6628f64..703b3b194791 100644 --- a/arch/powerpc/platforms/85xx/corenet_generic.c +++ b/arch/powerpc/platforms/85xx/corenet_generic.c @@ -38,8 +38,6 @@ void __init corenet_gen_pic_init(void) unsigned int flags = MPIC_BIG_ENDIAN | MPIC_SINGLE_DEST_CPU | MPIC_NO_RESET; - struct device_node *np; - if (ppc_md.get_irq == mpic_get_coreint_irq) flags |= MPIC_ENABLE_COREINT; @@ -47,13 +45,6 @@ void __init corenet_gen_pic_init(void) BUG_ON(mpic == NULL); mpic_init(mpic); - - np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic"); - if (np) { - qe_ic_init(np, 0, qe_ic_cascade_low_mpic, - qe_ic_cascade_high_mpic); - of_node_put(np); - } } /* diff --git a/arch/powerpc/platforms/85xx/mpc85xx_mds.c b/arch/powerpc/platforms/85xx/mpc85xx_mds.c index 5ca254256c47..265e49c2878b 100644 --- a/arch/powerpc/platforms/85xx/mpc85xx_mds.c +++ b/arch/powerpc/platforms/85xx/mpc85xx_mds.c @@ -279,20 +279,6 @@ static void __init mpc85xx_mds_qeic_init(void) of_node_put(np); return; } - - np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic"); - if (!np) { - np = of_find_node_by_type(NULL, "qeic"); - if (!np) - return; - } - - if (machine_is(p1021_mds)) - qe_ic_init(np, 0, qe_ic_cascade_low_mpic, - qe_ic_cascade_high_mpic); - else - qe_ic_init(np, 0, qe_ic_cascade_muxed_mpic, NULL); - of_node_put(np); } #else static void __init mpc85xx_mds_qe_init(void) { } diff --git a/arch/powerpc/platforms/85xx/mpc85xx_rdb.c b/arch/powerpc/platforms/85xx/mpc85xx_rdb.c index d3c540ee558f..caab2e0b4107 100644 --- a/arch/powerpc/platforms/85xx/mpc85xx_rdb.c +++ b/arch/powerpc/platforms/85xx/mpc85xx_rdb.c @@ -44,10 +44,6 @@ void __init mpc85xx_rdb_pic_init(void) { struct mpic *mpic; -#ifdef CONFIG_QUICC_ENGINE - struct device_node *np; -#endif - if (of_machine_is_compatible("fsl,MPC85XXRDB-CAMP")) { mpic = mpic_alloc(NULL, 0, MPIC_NO_RESET | MPIC_BIG_ENDIAN | @@ -62,18 +58,6 @@ void __init mpc85xx_rdb_pic_init(void) BUG_ON(mpic == NULL); mpic_init(mpic); - -#ifdef CONFIG_QUICC_ENGINE - np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic"); - if (np) { - qe_ic_init(np, 0, qe_ic_cascade_low_mpic, - qe_ic_cascade_high_mpic); - of_node_put(np); - - } else - pr_err("%s: Could not find qe-ic node\n", __func__); -#endif - } /* diff --git a/arch/powerpc/platforms/85xx/twr_p102x.c b/arch/powerpc/platforms/85xx/twr_p102x.c index 720b0c0f03ba..8aae0fb86bc9 100644 --- a/arch/powerpc/platforms/85xx/twr_p102x.c +++ b/arch/powerpc/platforms/85xx/twr_p102x.c @@ -31,26 +31,12 @@ static void __init twr_p1025_pic_init(void) { struct mpic *mpic; -#ifdef CONFIG_QUICC_ENGINE - struct device_node *np; -#endif - mpic = mpic_alloc(NULL, 0, MPIC_BIG_ENDIAN | MPIC_SINGLE_DEST_CPU, 0, 256, " OpenPIC "); BUG_ON(mpic == NULL); mpic_init(mpic); - -#ifdef CONFIG_QUICC_ENGINE - np = of_find_compatible_node(NULL, NULL, "fsl,qe-ic"); - if (np) { - qe_ic_init(np, 0, qe_ic_cascade_low_mpic, - qe_ic_cascade_high_mpic); - of_node_put(np); - } else - pr_err("Could not find qe-ic node\n"); -#endif } /* ************************************************************************ From 37f27cc362ad3950c48d5f1fe8410e601cec39c0 Mon Sep 17 00:00:00 2001 From: Zhao Qiang Date: Wed, 1 Nov 2017 14:02:44 +0800 Subject: [PATCH 73/81] irqchip/qeic: remove PPCisms for QEIC [PowerPC part] QEIC was supported on PowerPC, and dependent on PPC, Now it is supported on other platforms, so remove PPCisms. Signed-off-by: Zhao Qiang --- arch/powerpc/platforms/83xx/km83xx.c | 1 - arch/powerpc/platforms/83xx/misc.c | 1 - arch/powerpc/platforms/83xx/mpc832x_mds.c | 1 - arch/powerpc/platforms/83xx/mpc832x_rdb.c | 1 - arch/powerpc/platforms/83xx/mpc836x_mds.c | 1 - arch/powerpc/platforms/83xx/mpc836x_rdk.c | 1 - arch/powerpc/platforms/85xx/corenet_generic.c | 1 - arch/powerpc/platforms/85xx/mpc85xx_mds.c | 1 - arch/powerpc/platforms/85xx/mpc85xx_rdb.c | 1 - arch/powerpc/platforms/85xx/twr_p102x.c | 1 - 10 files changed, 10 deletions(-) diff --git a/arch/powerpc/platforms/83xx/km83xx.c b/arch/powerpc/platforms/83xx/km83xx.c index 273145aed90a..5c6227f7bc37 100644 --- a/arch/powerpc/platforms/83xx/km83xx.c +++ b/arch/powerpc/platforms/83xx/km83xx.c @@ -34,7 +34,6 @@ #include #include #include -#include #include "mpc83xx.h" diff --git a/arch/powerpc/platforms/83xx/misc.c b/arch/powerpc/platforms/83xx/misc.c index 5f7b79b43f03..6935a5b9fbd1 100644 --- a/arch/powerpc/platforms/83xx/misc.c +++ b/arch/powerpc/platforms/83xx/misc.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include diff --git a/arch/powerpc/platforms/83xx/mpc832x_mds.c b/arch/powerpc/platforms/83xx/mpc832x_mds.c index b428835e5919..1c73af104d19 100644 --- a/arch/powerpc/platforms/83xx/mpc832x_mds.c +++ b/arch/powerpc/platforms/83xx/mpc832x_mds.c @@ -33,7 +33,6 @@ #include #include #include -#include #include "mpc83xx.h" diff --git a/arch/powerpc/platforms/83xx/mpc832x_rdb.c b/arch/powerpc/platforms/83xx/mpc832x_rdb.c index 4588ce632484..87f68ca06255 100644 --- a/arch/powerpc/platforms/83xx/mpc832x_rdb.c +++ b/arch/powerpc/platforms/83xx/mpc832x_rdb.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include diff --git a/arch/powerpc/platforms/83xx/mpc836x_mds.c b/arch/powerpc/platforms/83xx/mpc836x_mds.c index 4a4efa906d35..5b484da9533e 100644 --- a/arch/powerpc/platforms/83xx/mpc836x_mds.c +++ b/arch/powerpc/platforms/83xx/mpc836x_mds.c @@ -41,7 +41,6 @@ #include #include #include -#include #include "mpc83xx.h" diff --git a/arch/powerpc/platforms/83xx/mpc836x_rdk.c b/arch/powerpc/platforms/83xx/mpc836x_rdk.c index 9923059cb111..b7119e443920 100644 --- a/arch/powerpc/platforms/83xx/mpc836x_rdk.c +++ b/arch/powerpc/platforms/83xx/mpc836x_rdk.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include diff --git a/arch/powerpc/platforms/85xx/corenet_generic.c b/arch/powerpc/platforms/85xx/corenet_generic.c index 703b3b194791..8c1bb3941642 100644 --- a/arch/powerpc/platforms/85xx/corenet_generic.c +++ b/arch/powerpc/platforms/85xx/corenet_generic.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include diff --git a/arch/powerpc/platforms/85xx/mpc85xx_mds.c b/arch/powerpc/platforms/85xx/mpc85xx_mds.c index 265e49c2878b..7d3129030395 100644 --- a/arch/powerpc/platforms/85xx/mpc85xx_mds.c +++ b/arch/powerpc/platforms/85xx/mpc85xx_mds.c @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include "smp.h" diff --git a/arch/powerpc/platforms/85xx/mpc85xx_rdb.c b/arch/powerpc/platforms/85xx/mpc85xx_rdb.c index caab2e0b4107..14b5a61d49c1 100644 --- a/arch/powerpc/platforms/85xx/mpc85xx_rdb.c +++ b/arch/powerpc/platforms/85xx/mpc85xx_rdb.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include diff --git a/arch/powerpc/platforms/85xx/twr_p102x.c b/arch/powerpc/platforms/85xx/twr_p102x.c index 8aae0fb86bc9..b099f5607120 100644 --- a/arch/powerpc/platforms/85xx/twr_p102x.c +++ b/arch/powerpc/platforms/85xx/twr_p102x.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include From 3ba26f9e41b70dd32f6b0589a6804ab05b76a0ef Mon Sep 17 00:00:00 2001 From: Ran Wang Date: Wed, 28 Mar 2018 15:38:51 +0800 Subject: [PATCH 74/81] powerpc/pm: Fix suspend=n in menuconfig for e500mc platforms. Also, unselect FSL_PMC which is for older platfroms instead. Signed-off-by: Ran Wang --- arch/powerpc/Kconfig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index 3e56c9c2f16e..2a70433b6f9d 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -322,7 +322,7 @@ config ARCH_HIBERNATION_POSSIBLE config ARCH_SUSPEND_POSSIBLE def_bool y depends on ADB_PMU || PPC_EFIKA || PPC_LITE5200 || PPC_83xx || \ - (PPC_85xx && !PPC_E500MC) || PPC_86xx || PPC_PSERIES \ + FSL_SOC_BOOKE || PPC_86xx || PPC_PSERIES \ || 44x || 40x config ARCH_SUSPEND_NONZERO_CPU @@ -977,8 +977,6 @@ config FSL_PCI config FSL_PMC bool - default y - depends on SUSPEND && (PPC_85xx || PPC_86xx) help Freescale MPC85xx/MPC86xx power management controller support (suspend/resume). For MPC83xx see platforms/83xx/suspend.c From ffb8e12c2efc81874dcd1dee1cda0f1774a22f01 Mon Sep 17 00:00:00 2001 From: Ran Wang Date: Wed, 28 Mar 2018 16:12:01 +0800 Subject: [PATCH 75/81] powerpc/cache: add cache flush operation for various e500 Various e500 core have different cache architecture, so they need different cache flush operations. Therefore, add a callback function cpu_flush_caches to the struct cpu_spec. The cache flush operation for the specific kind of e500 is selected at init time. The callback function will flush all caches in the current cpu. Signed-off-by: Chenhui Zhao Reviewed-by: Yang Li Reviewed-by: Jose Rivera Signed-off-by: Ran Wang --- arch/powerpc/include/asm/cputable.h | 12 ++++ arch/powerpc/kernel/asm-offsets.c | 3 + arch/powerpc/kernel/cpu_setup_fsl_booke.S | 81 +++++++++++++++++++++++ arch/powerpc/kernel/cputable.c | 4 ++ 4 files changed, 100 insertions(+) diff --git a/arch/powerpc/include/asm/cputable.h b/arch/powerpc/include/asm/cputable.h index cf00ff0d121d..3cbd71daf1ec 100644 --- a/arch/powerpc/include/asm/cputable.h +++ b/arch/powerpc/include/asm/cputable.h @@ -43,6 +43,14 @@ extern int machine_check_e500mc(struct pt_regs *regs); extern int machine_check_e500(struct pt_regs *regs); extern int machine_check_e200(struct pt_regs *regs); extern int machine_check_47x(struct pt_regs *regs); + +#if defined(CONFIG_E500) || defined(CONFIG_PPC_E500MC) +extern void __flush_caches_e500v2(void); +extern void __flush_caches_e500mc(void); +extern void __flush_caches_e5500(void); +extern void __flush_caches_e6500(void); +#endif + int machine_check_8xx(struct pt_regs *regs); int machine_check_83xx(struct pt_regs *regs); @@ -70,6 +78,10 @@ struct cpu_spec { /* flush caches inside the current cpu */ void (*cpu_down_flush)(void); +#if defined(CONFIG_E500) || defined(CONFIG_PPC_E500MC) + /* flush caches of the cpu which is running the function */ + void (*cpu_flush_caches)(void); +#endif /* number of performance monitor counters */ unsigned int num_pmcs; enum powerpc_pmc_type pmc_type; diff --git a/arch/powerpc/kernel/asm-offsets.c b/arch/powerpc/kernel/asm-offsets.c index 484f54dab247..74468eeffb6d 100644 --- a/arch/powerpc/kernel/asm-offsets.c +++ b/arch/powerpc/kernel/asm-offsets.c @@ -365,6 +365,9 @@ int main(void) OFFSET(CPU_SPEC_FEATURES, cpu_spec, cpu_features); OFFSET(CPU_SPEC_SETUP, cpu_spec, cpu_setup); OFFSET(CPU_SPEC_RESTORE, cpu_spec, cpu_restore); +#if defined(CONFIG_E500) || defined(CONFIG_PPC_E500MC) + OFFSET(CPU_FLUSH_CACHES, cpu_spec, cpu_flush_caches); +#endif OFFSET(pbe_address, pbe, address); OFFSET(pbe_orig_address, pbe, orig_address); diff --git a/arch/powerpc/kernel/cpu_setup_fsl_booke.S b/arch/powerpc/kernel/cpu_setup_fsl_booke.S index 2b4f3ec0acf7..1b543b5fed59 100644 --- a/arch/powerpc/kernel/cpu_setup_fsl_booke.S +++ b/arch/powerpc/kernel/cpu_setup_fsl_booke.S @@ -340,3 +340,84 @@ _GLOBAL(cpu_down_flush_e5500) /* L1 Data Cache of e6500 contains no modified data, no flush is required */ _GLOBAL(cpu_down_flush_e6500) blr + +_GLOBAL(__flush_caches_e500v2) + mflr r0 + bl flush_dcache_L1 + mtlr r0 + blr + +_GLOBAL(__flush_caches_e500mc) +_GLOBAL(__flush_caches_e5500) + mflr r0 + bl flush_dcache_L1 + bl flush_backside_L2_cache + mtlr r0 + blr + +/* L1 Data Cache of e6500 contains no modified data, no flush is required */ +_GLOBAL(__flush_caches_e6500) + blr + + /* r3 = virtual address of L2 controller, WIMG = 01xx */ +_GLOBAL(flush_disable_L2) + /* It's a write-through cache, so only invalidation is needed. */ + mbar + isync + lwz r4, 0(r3) + li r5, 1 + rlwimi r4, r5, 30, 0xc0000000 + stw r4, 0(r3) + + /* Wait for the invalidate to finish */ +1: lwz r4, 0(r3) + andis. r4, r4, 0x4000 + bne 1b + mbar + + blr + + /* r3 = virtual address of L2 controller, WIMG = 01xx */ +_GLOBAL(invalidate_enable_L2) + mbar + isync + lwz r4, 0(r3) + li r5, 3 + rlwimi r4, r5, 30, 0xc0000000 + stw r4, 0(r3) + + /* Wait for the invalidate to finish */ +1: lwz r4, 0(r3) + andis. r4, r4, 0x4000 + bne 1b + mbar + + blr + +/* Flush L1 d-cache, invalidate and disable d-cache and i-cache */ +_GLOBAL(__flush_disable_L1) + mflr r10 + bl flush_dcache_L1 /* Flush L1 d-cache */ + mtlr r10 + + mfspr r4, SPRN_L1CSR0 /* Invalidate and disable d-cache */ + li r5, 2 + rlwimi r4, r5, 0, 3 + + msync + isync + mtspr SPRN_L1CSR0, r4 + isync + +1: mfspr r4, SPRN_L1CSR0 /* Wait for the invalidate to finish */ + andi. r4, r4, 2 + bne 1b + + mfspr r4, SPRN_L1CSR1 /* Invalidate and disable i-cache */ + li r5, 2 + rlwimi r4, r5, 0, 3 + + mtspr SPRN_L1CSR1, r4 + isync + + blr diff --git a/arch/powerpc/kernel/cputable.c b/arch/powerpc/kernel/cputable.c index e745abc5457a..1635bc906b72 100644 --- a/arch/powerpc/kernel/cputable.c +++ b/arch/powerpc/kernel/cputable.c @@ -2051,6 +2051,7 @@ static struct cpu_spec __initdata cpu_specs[] = { .machine_check = machine_check_e500, .platform = "ppc8548", .cpu_down_flush = cpu_down_flush_e500v2, + .cpu_flush_caches = __flush_caches_e500v2, }, #else { /* e500mc */ @@ -2071,6 +2072,7 @@ static struct cpu_spec __initdata cpu_specs[] = { .machine_check = machine_check_e500mc, .platform = "ppce500mc", .cpu_down_flush = cpu_down_flush_e500mc, + .cpu_flush_caches = __flush_caches_e500mc, }, #endif /* CONFIG_PPC_E500MC */ #endif /* CONFIG_PPC32 */ @@ -2096,6 +2098,7 @@ static struct cpu_spec __initdata cpu_specs[] = { .machine_check = machine_check_e500mc, .platform = "ppce5500", .cpu_down_flush = cpu_down_flush_e5500, + .cpu_flush_caches = __flush_caches_e5500, }, { /* e6500 */ .pvr_mask = 0xffff0000, @@ -2119,6 +2122,7 @@ static struct cpu_spec __initdata cpu_specs[] = { .machine_check = machine_check_e500mc, .platform = "ppce6500", .cpu_down_flush = cpu_down_flush_e6500, + .cpu_flush_caches = __flush_caches_e6500, }, #endif /* CONFIG_PPC_E500MC */ #ifdef CONFIG_PPC32 From dc640f653a5d465ab03598388a1931894fa07ff7 Mon Sep 17 00:00:00 2001 From: Ran Wang Date: Wed, 28 Mar 2018 16:32:08 +0800 Subject: [PATCH 76/81] powerpc/pm: add sleep and deep sleep on QorIQ SoCs In sleep mode, the clocks of CPU core and unused IP blocks are turned off (IP blocks allowed to wake up system will running). Some QorIQ SoCs like MPC8536, P1022 and T104x, have deep sleep PM mode in addtion to the sleep PM mode. While in deep sleep mode, additionally, the power supply is removed from CPU core and most IP blocks. Only the blocks needed to wake up the chip out of deep sleep are ON. This feature supports 32-bit and 36-bit address space. The sleep mode is equal to the Standby state in Linux. The deep sleep mode is equal to the Suspend-to-RAM state of Linux Power Management. Command to enter sleep mode. echo standby > /sys/power/state Command to enter deep sleep mode. echo mem > /sys/power/state Signed-off-by: Dave Liu Signed-off-by: Li Yang Signed-off-by: Jin Qing Signed-off-by: Jerry Huang Signed-off-by: Ramneek Mehresh Signed-off-by: Zhao Chenhui Signed-off-by: Wang Dongsheng Signed-off-by: Tang Yuantian Signed-off-by: Xie Xiaobo Signed-off-by: Zhao Qiang Signed-off-by: Shengzhou Liu Signed-off-by: Ran Wang --- arch/powerpc/include/asm/cacheflush.h | 7 + arch/powerpc/include/asm/fsl_pm.h | 31 + arch/powerpc/kernel/Makefile | 1 + arch/powerpc/kernel/fsl_booke_entry_mapping.S | 10 + arch/powerpc/kernel/fsl_pm.c | 49 + arch/powerpc/kernel/head_64.S | 2 +- arch/powerpc/platforms/85xx/Kconfig | 6 + arch/powerpc/platforms/85xx/Makefile | 2 + arch/powerpc/platforms/85xx/deepsleep.c | 349 +++++ arch/powerpc/platforms/85xx/qoriq_pm.c | 222 +++ arch/powerpc/platforms/85xx/sleep.S | 1192 +++++++++++++++++ arch/powerpc/platforms/86xx/Kconfig | 1 + arch/powerpc/sysdev/fsl_pmc.c | 176 ++- arch/powerpc/sysdev/fsl_soc.c | 31 + arch/powerpc/sysdev/fsl_soc.h | 18 + 15 files changed, 2077 insertions(+), 20 deletions(-) create mode 100644 arch/powerpc/kernel/fsl_pm.c create mode 100644 arch/powerpc/platforms/85xx/deepsleep.c create mode 100644 arch/powerpc/platforms/85xx/qoriq_pm.c create mode 100644 arch/powerpc/platforms/85xx/sleep.S diff --git a/arch/powerpc/include/asm/cacheflush.h b/arch/powerpc/include/asm/cacheflush.h index eef388f2659f..0bf2786c0de0 100644 --- a/arch/powerpc/include/asm/cacheflush.h +++ b/arch/powerpc/include/asm/cacheflush.h @@ -42,6 +42,13 @@ extern void flush_dcache_page(struct page *page); #define flush_dcache_mmap_lock(mapping) do { } while (0) #define flush_dcache_mmap_unlock(mapping) do { } while (0) +extern void __flush_disable_L1(void); +#ifdef CONFIG_FSL_SOC_BOOKE +extern void flush_dcache_L1(void); +#else +#define flush_dcache_L1() do { } while (0) +#endif + extern void flush_icache_range(unsigned long, unsigned long); extern void flush_icache_user_range(struct vm_area_struct *vma, struct page *page, unsigned long addr, diff --git a/arch/powerpc/include/asm/fsl_pm.h b/arch/powerpc/include/asm/fsl_pm.h index 61a4c977320f..07bc10501969 100644 --- a/arch/powerpc/include/asm/fsl_pm.h +++ b/arch/powerpc/include/asm/fsl_pm.h @@ -7,6 +7,9 @@ #ifndef __PPC_FSL_PM_H #define __PPC_FSL_PM_H +#ifndef __ASSEMBLY__ +#include + #define E500_PM_PH10 1 #define E500_PM_PH15 2 #define E500_PM_PH20 3 @@ -42,6 +45,34 @@ struct fsl_pm_ops { extern const struct fsl_pm_ops *qoriq_pm_ops; +struct fsm_reg_vals { + u32 offset; + u32 value; +}; + +void fsl_fsm_setup(void __iomem *base, struct fsm_reg_vals *val); +void fsl_epu_setup_default(void __iomem *epu_base); +void fsl_npc_setup_default(void __iomem *npc_base); +void fsl_fsm_clean(void __iomem *base, struct fsm_reg_vals *val); +void fsl_epu_clean_default(void __iomem *epu_base); + +extern int fsl_dp_iomap(void); +extern void fsl_dp_iounmap(void); + +extern int fsl_enter_epu_deepsleep(void); +extern void fsl_dp_enter_low(void __iomem *ccsr_base, void __iomem *dcsr_base, + void __iomem *pld_base, int pld_flag); +extern void fsl_booke_deep_sleep_resume(void); + int __init fsl_rcpm_init(void); +void set_pm_suspend_state(suspend_state_t state); +suspend_state_t pm_suspend_state(void); + +void fsl_set_power_except(struct device *dev, int on); +#endif /* __ASSEMBLY__ */ + +#define T1040QDS_TETRA_FLAG 1 +#define T104xRDB_CPLD_FLAG 2 + #endif /* __PPC_FSL_PM_H */ diff --git a/arch/powerpc/kernel/Makefile b/arch/powerpc/kernel/Makefile index a7ca8fe62368..c9897459afd6 100644 --- a/arch/powerpc/kernel/Makefile +++ b/arch/powerpc/kernel/Makefile @@ -81,6 +81,7 @@ obj-$(CONFIG_CRASH_DUMP) += crash_dump.o ifneq ($(CONFIG_FA_DUMP)$(CONFIG_PRESERVE_FA_DUMP),) obj-y += fadump.o endif +obj-$(CONFIG_FSL_SOC) += fsl_pm.o ifdef CONFIG_PPC32 obj-$(CONFIG_E500) += idle_e500.o endif diff --git a/arch/powerpc/kernel/fsl_booke_entry_mapping.S b/arch/powerpc/kernel/fsl_booke_entry_mapping.S index ea065282b303..0e3484d3b663 100644 --- a/arch/powerpc/kernel/fsl_booke_entry_mapping.S +++ b/arch/powerpc/kernel/fsl_booke_entry_mapping.S @@ -174,6 +174,10 @@ skpinv: addi r6,r6,1 /* Increment */ lis r6,MAS2_VAL(PAGE_OFFSET, BOOK3E_PAGESZ_64M, M_IF_NEEDED)@h ori r6,r6,MAS2_VAL(PAGE_OFFSET, BOOK3E_PAGESZ_64M, M_IF_NEEDED)@l mtspr SPRN_MAS2,r6 +#ifdef ENTRY_DEEPSLEEP_SETUP + LOAD_REG_IMMEDIATE(r8, MEMORY_START) + ori r8,r8,(MAS3_SX|MAS3_SW|MAS3_SR) +#endif mtspr SPRN_MAS3,r8 tlbwe @@ -216,12 +220,18 @@ next_tlb_setup: #error You need to specify the mapping or not use this at all. #endif +#ifdef ENTRY_DEEPSLEEP_SETUP + LOAD_REG_ADDR(r6, 2f) + mfmsr r7 + rlwinm r7,r7,0,~(MSR_IS|MSR_DS) +#else lis r7,MSR_KERNEL@h ori r7,r7,MSR_KERNEL@l bl 1f /* Find our address */ 1: mflr r9 rlwimi r6,r9,0,20,31 addi r6,r6,(2f - 1b) +#endif mtspr SPRN_SRR0,r6 mtspr SPRN_SRR1,r7 rfi /* start execution out of TLB1[0] entry */ diff --git a/arch/powerpc/kernel/fsl_pm.c b/arch/powerpc/kernel/fsl_pm.c new file mode 100644 index 000000000000..24a179fd8784 --- /dev/null +++ b/arch/powerpc/kernel/fsl_pm.c @@ -0,0 +1,49 @@ +/* + * Freescale General Power Management Implementation + * + * Copyright 2018 NXP + * Author: Wang Dongsheng + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the above-listed copyright holders nor the + * names of any contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +static suspend_state_t pm_state; + +void set_pm_suspend_state(suspend_state_t state) +{ + pm_state = state; +} + +suspend_state_t pm_suspend_state(void) +{ + return pm_state; +} diff --git a/arch/powerpc/kernel/head_64.S b/arch/powerpc/kernel/head_64.S index ad79fddb974d..a7f356d121ae 100644 --- a/arch/powerpc/kernel/head_64.S +++ b/arch/powerpc/kernel/head_64.S @@ -860,7 +860,7 @@ _GLOBAL(start_secondary_resume) /* * This subroutine clobbers r11 and r12 */ -enable_64b_mode: +_GLOBAL(enable_64b_mode) mfmsr r11 /* grab the current MSR */ #ifdef CONFIG_PPC_BOOK3E oris r11,r11,0x8000 /* CM bit set, we'll set ICM later */ diff --git a/arch/powerpc/platforms/85xx/Kconfig b/arch/powerpc/platforms/85xx/Kconfig index fa3d29dcb57e..a933e74ca65f 100644 --- a/arch/powerpc/platforms/85xx/Kconfig +++ b/arch/powerpc/platforms/85xx/Kconfig @@ -10,6 +10,8 @@ menuconfig FSL_SOC_BOOKE select SERIAL_8250_EXTENDED if SERIAL_8250 select SERIAL_8250_SHARE_IRQ if SERIAL_8250 select FSL_CORENET_RCPM if PPC_E500MC + select FSL_QORIQ_PM if SUSPEND && PPC_E500MC + select FSL_PMC if SUSPEND && !PPC_E500MC default y if FSL_SOC_BOOKE @@ -292,3 +294,7 @@ endif # FSL_SOC_BOOKE config TQM85xx bool + +config FSL_QORIQ_PM + bool + select FSL_SLEEP_FSM diff --git a/arch/powerpc/platforms/85xx/Makefile b/arch/powerpc/platforms/85xx/Makefile index d1dd0dca5ebf..8e9f3699a6a7 100644 --- a/arch/powerpc/platforms/85xx/Makefile +++ b/arch/powerpc/platforms/85xx/Makefile @@ -3,7 +3,9 @@ # Makefile for the PowerPC 85xx linux kernel. # obj-$(CONFIG_SMP) += smp.o +obj-$(CONFIG_SUSPEND) += sleep.o obj-$(CONFIG_FSL_PMC) += mpc85xx_pm_ops.o +obj-$(CONFIG_FSL_QORIQ_PM) += qoriq_pm.o deepsleep.o obj-y += common.o diff --git a/arch/powerpc/platforms/85xx/deepsleep.c b/arch/powerpc/platforms/85xx/deepsleep.c new file mode 100644 index 000000000000..73992b4fedcc --- /dev/null +++ b/arch/powerpc/platforms/85xx/deepsleep.c @@ -0,0 +1,349 @@ +/* + * Support deep sleep feature for T104x + * + * Copyright 2018 NXP + * Author: Chenhui Zhao + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the above-listed copyright holders nor the + * names of any contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define SIZE_1MB 0x100000 +#define SIZE_2MB 0x200000 + +#define CPC_CPCHDBCR0 0x10f00 +#define CPC_CPCHDBCR0_SPEC_DIS 0x08000000 + +#define CCSR_SCFG_DPSLPCR 0xfc000 +#define CCSR_SCFG_DPSLPCR_WDRR_EN 0x1 +#define CCSR_SCFG_SPARECR2 0xfc504 +#define CCSR_SCFG_SPARECR3 0xfc508 + +#define CCSR_GPIO1_GPDIR 0x130000 +#define CCSR_GPIO1_GPODR 0x130004 +#define CCSR_GPIO1_GPDAT 0x130008 +#define CCSR_GPIO1_GPDIR_29 0x4 + +#define RCPM_BLOCK_OFFSET 0x00022000 +#define EPU_BLOCK_OFFSET 0x00000000 +#define NPC_BLOCK_OFFSET 0x00001000 + +#define CSTTACR0 0xb00 +#define CG1CR0 0x31c + +#define CCSR_LAW_BASE 0xC00 +#define DCFG_BRR 0xE4 /* boot release register */ +#define LCC_BSTRH 0x20 /* Boot space translation register high */ +#define LCC_BSTRL 0x24 /* Boot space translation register low */ +#define LCC_BSTAR 0x28 /* Boot space translation attribute register */ +#define RCPM_PCTBENR 0x1A0 /* Physical Core Timebase Enable Register */ +#define RCPM_BASE 0xE2000 +#define DCFG_BASE 0xE0000 + +/* 128 bytes buffer for restoring data broke by DDR training initialization */ +#define DDR_BUF_SIZE 128 +static u8 ddr_buff[DDR_BUF_SIZE] __aligned(64); + +static void *dcsr_base, *ccsr_base, *pld_base; +static int pld_flag; + +/* for law */ +struct fsl_law { + u32 lawbarh; /* LAWn base address high */ + u32 lawbarl; /* LAWn base address low */ + u32 lawar; /* LAWn attributes */ + u32 reserved; +}; + +struct fsl_law *saved_law; +static u32 num_laws; + +/* for nonboot cpu */ +struct fsl_bstr { + u32 bstrh; + u32 bstrl; + u32 bstar; + u32 cpu_mask; +}; +static struct fsl_bstr saved_bstr; + +int fsl_dp_iomap(void) +{ + struct device_node *np; + int ret = 0; + phys_addr_t ccsr_phy_addr, dcsr_phy_addr; + + saved_law = NULL; + ccsr_base = NULL; + dcsr_base = NULL; + pld_base = NULL; + + ccsr_phy_addr = get_immrbase(); + if (ccsr_phy_addr == -1) { + pr_err("%s: Can't get the address of CCSR\n", __func__); + ret = -EINVAL; + goto ccsr_err; + } + ccsr_base = ioremap(ccsr_phy_addr, SIZE_2MB); + if (!ccsr_base) { + ret = -ENOMEM; + goto ccsr_err; + } + + dcsr_phy_addr = get_dcsrbase(); + if (dcsr_phy_addr == -1) { + pr_err("%s: Can't get the address of DCSR\n", __func__); + ret = -EINVAL; + goto dcsr_err; + } + dcsr_base = ioremap(dcsr_phy_addr, SIZE_1MB); + if (!dcsr_base) { + ret = -ENOMEM; + goto dcsr_err; + } + + np = of_find_compatible_node(NULL, NULL, "fsl,tetra-fpga"); + if (np) { + pld_flag = T1040QDS_TETRA_FLAG; + } else { + np = of_find_compatible_node(NULL, NULL, "fsl,deepsleep-cpld"); + if (np) { + pld_flag = T104xRDB_CPLD_FLAG; + } else { + pr_err("%s: Can't find the FPGA/CPLD node\n", + __func__); + ret = -EINVAL; + goto pld_err; + } + } + pld_base = of_iomap(np, 0); + of_node_put(np); + + np = of_find_compatible_node(NULL, NULL, "fsl,corenet-law"); + if (!np) { + pr_err("%s: Can't find the node of \"law\"\n", __func__); + ret = -EINVAL; + goto alloc_err; + } + ret = of_property_read_u32(np, "fsl,num-laws", &num_laws); + if (ret) { + ret = -EINVAL; + goto alloc_err; + } + + saved_law = kzalloc(sizeof(*saved_law) * num_laws, GFP_KERNEL); + if (!saved_law) { + ret = -ENOMEM; + goto alloc_err; + } + of_node_put(np); + + return 0; + +alloc_err: + iounmap(pld_base); + pld_base = NULL; +pld_err: + iounmap(dcsr_base); + dcsr_base = NULL; +dcsr_err: + iounmap(ccsr_base); + ccsr_base = NULL; +ccsr_err: + return ret; +} + +void fsl_dp_iounmap(void) +{ + if (dcsr_base) { + iounmap(dcsr_base); + dcsr_base = NULL; + } + + if (ccsr_base) { + iounmap(ccsr_base); + ccsr_base = NULL; + } + + if (pld_base) { + iounmap(pld_base); + pld_base = NULL; + } + + kfree(saved_law); + saved_law = NULL; +} + +static void fsl_dp_ddr_save(void *ccsr_base) +{ + u32 ddr_buff_addr; + + /* + * DDR training initialization will break 128 bytes at the beginning + * of DDR, therefore, save them so that the bootloader will restore + * them. Assume that DDR is mapped to the address space started with + * CONFIG_PAGE_OFFSET. + */ + memcpy(ddr_buff, (void *)CONFIG_PAGE_OFFSET, DDR_BUF_SIZE); + + /* assume ddr_buff is in the physical address space of 4GB */ + ddr_buff_addr = (u32)(__pa(ddr_buff) & 0xffffffff); + + /* + * the bootloader will restore the first 128 bytes of DDR from + * the location indicated by the register SPARECR3 + */ + out_be32(ccsr_base + CCSR_SCFG_SPARECR3, ddr_buff_addr); +} + +static void fsl_dp_mp_save(void *ccsr) +{ + struct fsl_bstr *dst = &saved_bstr; + + dst->bstrh = in_be32(ccsr + LCC_BSTRH); + dst->bstrl = in_be32(ccsr + LCC_BSTRL); + dst->bstar = in_be32(ccsr + LCC_BSTAR); + dst->cpu_mask = in_be32(ccsr + DCFG_BASE + DCFG_BRR); +} + +static void fsl_dp_mp_restore(void *ccsr) +{ + struct fsl_bstr *src = &saved_bstr; + + out_be32(ccsr + LCC_BSTRH, src->bstrh); + out_be32(ccsr + LCC_BSTRL, src->bstrl); + out_be32(ccsr + LCC_BSTAR, src->bstar); + + /* release the nonboot cpus */ + out_be32(ccsr + DCFG_BASE + DCFG_BRR, src->cpu_mask); + + /* enable the time base */ + out_be32(ccsr + RCPM_BASE + RCPM_PCTBENR, src->cpu_mask); + /* read back to sync write */ + in_be32(ccsr + RCPM_BASE + RCPM_PCTBENR); +} + +static void fsl_dp_law_save(void *ccsr) +{ + int i; + struct fsl_law *dst = saved_law; + struct fsl_law *src = (void *)(ccsr + CCSR_LAW_BASE); + + for (i = 0; i < num_laws; i++) { + dst->lawbarh = in_be32(&src->lawbarh); + dst->lawbarl = in_be32(&src->lawbarl); + dst->lawar = in_be32(&src->lawar); + dst++; + src++; + } +} + +static void fsl_dp_law_restore(void *ccsr) +{ + int i; + struct fsl_law *src = saved_law; + struct fsl_law *dst = (void *)(ccsr + CCSR_LAW_BASE); + + for (i = 0; i < num_laws - 1; i++) { + out_be32(&dst->lawar, 0); + out_be32(&dst->lawbarl, src->lawbarl); + out_be32(&dst->lawbarh, src->lawbarh); + out_be32(&dst->lawar, src->lawar); + + /* Read back so that we sync the writes */ + in_be32(&dst->lawar); + src++; + dst++; + } +} + +static void fsl_dp_set_resume_pointer(void *ccsr_base) +{ + u32 resume_addr; + + /* the bootloader will finally jump to this address to return kernel */ +#ifdef CONFIG_PPC32 + resume_addr = (u32)(__pa(fsl_booke_deep_sleep_resume)); +#else + resume_addr = (u32)(__pa(*(u64 *)fsl_booke_deep_sleep_resume) + & 0xffffffff); +#endif + + /* use the register SPARECR2 to save the resume address */ + out_be32(ccsr_base + CCSR_SCFG_SPARECR2, resume_addr); + +} + +int fsl_enter_epu_deepsleep(void) +{ + fsl_dp_ddr_save(ccsr_base); + + fsl_dp_set_resume_pointer(ccsr_base); + + fsl_dp_mp_save(ccsr_base); + fsl_dp_law_save(ccsr_base); + /* enable Warm Device Reset request. */ + setbits32(ccsr_base + CCSR_SCFG_DPSLPCR, CCSR_SCFG_DPSLPCR_WDRR_EN); + + /* set GPIO1_29 as an output pin (not open-drain), and output 0 */ + clrbits32(ccsr_base + CCSR_GPIO1_GPDAT, CCSR_GPIO1_GPDIR_29); + clrbits32(ccsr_base + CCSR_GPIO1_GPODR, CCSR_GPIO1_GPDIR_29); + setbits32(ccsr_base + CCSR_GPIO1_GPDIR, CCSR_GPIO1_GPDIR_29); + + /* + * Disable CPC speculation to avoid deep sleep hang, especially + * in secure boot mode. This bit will be cleared automatically + * when resuming from deep sleep. + */ + setbits32(ccsr_base + CPC_CPCHDBCR0, CPC_CPCHDBCR0_SPEC_DIS); + + fsl_epu_setup_default(dcsr_base + EPU_BLOCK_OFFSET); + fsl_npc_setup_default(dcsr_base + NPC_BLOCK_OFFSET); + out_be32(dcsr_base + RCPM_BLOCK_OFFSET + CSTTACR0, 0x00001001); + out_be32(dcsr_base + RCPM_BLOCK_OFFSET + CG1CR0, 0x00000001); + + fsl_dp_enter_low(ccsr_base, dcsr_base, pld_base, pld_flag); + + fsl_dp_law_restore(ccsr_base); + fsl_dp_mp_restore(ccsr_base); + + /* disable Warm Device Reset request */ + clrbits32(ccsr_base + CCSR_SCFG_DPSLPCR, CCSR_SCFG_DPSLPCR_WDRR_EN); + + fsl_epu_clean_default(dcsr_base + EPU_BLOCK_OFFSET); + + return 0; +} diff --git a/arch/powerpc/platforms/85xx/qoriq_pm.c b/arch/powerpc/platforms/85xx/qoriq_pm.c new file mode 100644 index 000000000000..9390944c53f0 --- /dev/null +++ b/arch/powerpc/platforms/85xx/qoriq_pm.c @@ -0,0 +1,222 @@ +/* + * Support Power Management feature + * + * Copyright 2018 NXP + * Author: Chenhui Zhao + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the above-listed copyright holders nor the + * names of any contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include + +#define FSL_SLEEP 0x1 +#define FSL_DEEP_SLEEP 0x2 + +int (*fsl_enter_deepsleep)(void); + +/* specify the sleep state of the present platform */ +unsigned int sleep_pm_state; +/* supported sleep modes by the present platform */ +static unsigned int sleep_modes; + +/** + * fsl_set_power_except - set which IP block is not powerdown when sleep, + * such as MAC, USB, etc. + * + * @dev: a pointer to the struct device + * @on: if 1, do not power down; if 0, power down. + */ +void fsl_set_power_except(struct device *dev, int on) +{ + u32 value[2]; + u32 pw_mask; + int ret; + struct device_node *mac_node; + const phandle *phandle_prop; + + if (dev && !strncmp(dev->bus->name, "usb", 3)) { + struct usb_device *udev = container_of(dev, + struct usb_device, dev); + struct device *controller = udev->bus->controller; + + ret = of_property_read_u32_array(controller->parent->of_node, + "sleep", value, 2); + } else + ret = of_property_read_u32_array(dev->of_node, "sleep", + value, 2); + + if (ret) { + /* search fman mac node */ + phandle_prop = of_get_property(dev->of_node, "fsl,fman-mac", + NULL); + if (phandle_prop == NULL) + goto err; + + mac_node = of_find_node_by_phandle(*phandle_prop); + ret = of_property_read_u32_array(mac_node, "sleep", value, 2); + of_node_put(mac_node); + if (ret) + goto err; + } + /* get the second value, it is a mask */ + pw_mask = value[1]; + qoriq_pm_ops->set_ip_power(on, pw_mask); + return; + +err: + dev_err(dev, "Can not set wakeup sources\n"); +} +EXPORT_SYMBOL_GPL(fsl_set_power_except); + +void qoriq_set_wakeup_source(struct device *dev, void *enable) +{ + if (!device_may_wakeup(dev)) + return; + + fsl_set_power_except(dev, *((int *)enable)); +} + +static int qoriq_suspend_enter(suspend_state_t state) +{ + int ret = 0; + int cpu; + + switch (state) { + case PM_SUSPEND_STANDBY: + + if (cur_cpu_spec->cpu_flush_caches) + cur_cpu_spec->cpu_flush_caches(); + + ret = qoriq_pm_ops->plat_enter_sleep(); + + break; + + case PM_SUSPEND_MEM: + + cpu = smp_processor_id(); + qoriq_pm_ops->irq_mask(cpu); + + ret = fsl_enter_deepsleep(); + + qoriq_pm_ops->irq_unmask(cpu); + + break; + + default: + ret = -EINVAL; + + } + + return ret; +} + +static int qoriq_suspend_valid(suspend_state_t state) +{ + set_pm_suspend_state(state); + + if (state == PM_SUSPEND_STANDBY && (sleep_modes & FSL_SLEEP)) + return 1; + + if (state == PM_SUSPEND_MEM && (sleep_modes & FSL_DEEP_SLEEP)) + return 1; + + set_pm_suspend_state(PM_SUSPEND_ON); + return 0; +} + +static int qoriq_suspend_begin(suspend_state_t state) +{ + const int enable = 1; + + dpm_for_each_dev((void *)&enable, qoriq_set_wakeup_source); + + if (state == PM_SUSPEND_MEM) + return fsl_dp_iomap(); + + return 0; +} + +static void qoriq_suspend_end(void) +{ + const int enable = 0; + + dpm_for_each_dev((void *)&enable, qoriq_set_wakeup_source); + + set_pm_suspend_state(PM_SUSPEND_ON); + fsl_dp_iounmap(); +} + +static const struct platform_suspend_ops qoriq_suspend_ops = { + .valid = qoriq_suspend_valid, + .enter = qoriq_suspend_enter, + .begin = qoriq_suspend_begin, + .end = qoriq_suspend_end, +}; + +static const struct of_device_id deepsleep_matches[] = { + { + .compatible = "fsl,t1040-rcpm", + }, + { + .compatible = "fsl,t1024-rcpm", + }, + { + .compatible = "fsl,t1023-rcpm", + }, + {}, +}; + +static int __init qoriq_suspend_init(void) +{ + struct device_node *np; + + sleep_modes = FSL_SLEEP; + sleep_pm_state = PLAT_PM_SLEEP; + + np = of_find_compatible_node(NULL, NULL, "fsl,qoriq-rcpm-2.0"); + if (np) + sleep_pm_state = PLAT_PM_LPM20; + + np = of_find_matching_node_and_match(NULL, deepsleep_matches, NULL); + if (np) { + fsl_enter_deepsleep = fsl_enter_epu_deepsleep; + sleep_modes |= FSL_DEEP_SLEEP; + } + + suspend_set_ops(&qoriq_suspend_ops); + set_pm_suspend_state(PM_SUSPEND_ON); + + return 0; +} +arch_initcall(qoriq_suspend_init); diff --git a/arch/powerpc/platforms/85xx/sleep.S b/arch/powerpc/platforms/85xx/sleep.S new file mode 100644 index 000000000000..b7942edce2f0 --- /dev/null +++ b/arch/powerpc/platforms/85xx/sleep.S @@ -0,0 +1,1192 @@ +/* + * Enter and leave deep sleep/sleep state + * + * Copyright 2018 NXP + * Author: Scott Wood + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the above-listed copyright holders nor the + * names of any contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include + +/* + * the number of bytes occupied by one register + * the value of 8 is compatible with both 32-bit and 64-bit registers + */ +#define STRIDE_SIZE 8 + +/* GPR0 - GPR31 */ +#define BOOKE_GPR0_OFF 0x0000 +#define BOOKE_GPR_COUNT 32 +/* IVOR0 - IVOR42 */ +#define BOOKE_IVOR0_OFF (BOOKE_GPR0_OFF + BOOKE_GPR_COUNT * STRIDE_SIZE) +#define BOOKE_IVOR_COUNT 43 +/* SPRG0 - SPRG9 */ +#define BOOKE_SPRG0_OFF (BOOKE_IVOR0_OFF + BOOKE_IVOR_COUNT * STRIDE_SIZE) +#define BOOKE_SPRG_COUNT 10 +/* IVPR */ +#define BOOKE_IVPR_OFF (BOOKE_SPRG0_OFF + BOOKE_SPRG_COUNT * STRIDE_SIZE) + +#define BOOKE_LR_OFF (BOOKE_IVPR_OFF + STRIDE_SIZE) +#define BOOKE_MSR_OFF (BOOKE_LR_OFF + STRIDE_SIZE) +#define BOOKE_TBU_OFF (BOOKE_MSR_OFF + STRIDE_SIZE) +#define BOOKE_TBL_OFF (BOOKE_TBU_OFF + STRIDE_SIZE) +#define BOOKE_EPCR_OFF (BOOKE_TBL_OFF + STRIDE_SIZE) +#define BOOKE_HID0_OFF (BOOKE_EPCR_OFF + STRIDE_SIZE) +#define BOOKE_PIR_OFF (BOOKE_HID0_OFF + STRIDE_SIZE) +#define BOOKE_PID0_OFF (BOOKE_PIR_OFF + STRIDE_SIZE) +#define BOOKE_BUCSR_OFF (BOOKE_PID0_OFF + STRIDE_SIZE) + +#define BUFFER_SIZE (BOOKE_BUCSR_OFF + STRIDE_SIZE) + +#undef SAVE_GPR +#define SAVE_GPR(gpr, offset) \ + PPC_STL gpr, offset(r10) + +#define RESTORE_GPR(gpr, offset) \ + PPC_LL gpr, offset(r10) + +#define SAVE_SPR(spr, offset) \ + mfspr r0, spr ;\ + PPC_STL r0, offset(r10) + +#define RESTORE_SPR(spr, offset) \ + PPC_LL r0, offset(r10) ;\ + mtspr spr, r0 + +#define SAVE_ALL_GPR \ + SAVE_GPR(r1, BOOKE_GPR0_OFF + STRIDE_SIZE * 1) ;\ + SAVE_GPR(r2, BOOKE_GPR0_OFF + STRIDE_SIZE * 2) ;\ + SAVE_GPR(r13, BOOKE_GPR0_OFF + STRIDE_SIZE * 13) ;\ + SAVE_GPR(r14, BOOKE_GPR0_OFF + STRIDE_SIZE * 14) ;\ + SAVE_GPR(r15, BOOKE_GPR0_OFF + STRIDE_SIZE * 15) ;\ + SAVE_GPR(r16, BOOKE_GPR0_OFF + STRIDE_SIZE * 16) ;\ + SAVE_GPR(r17, BOOKE_GPR0_OFF + STRIDE_SIZE * 17) ;\ + SAVE_GPR(r18, BOOKE_GPR0_OFF + STRIDE_SIZE * 18) ;\ + SAVE_GPR(r19, BOOKE_GPR0_OFF + STRIDE_SIZE * 19) ;\ + SAVE_GPR(r20, BOOKE_GPR0_OFF + STRIDE_SIZE * 20) ;\ + SAVE_GPR(r21, BOOKE_GPR0_OFF + STRIDE_SIZE * 21) ;\ + SAVE_GPR(r22, BOOKE_GPR0_OFF + STRIDE_SIZE * 22) ;\ + SAVE_GPR(r23, BOOKE_GPR0_OFF + STRIDE_SIZE * 23) ;\ + SAVE_GPR(r24, BOOKE_GPR0_OFF + STRIDE_SIZE * 24) ;\ + SAVE_GPR(r25, BOOKE_GPR0_OFF + STRIDE_SIZE * 25) ;\ + SAVE_GPR(r26, BOOKE_GPR0_OFF + STRIDE_SIZE * 26) ;\ + SAVE_GPR(r27, BOOKE_GPR0_OFF + STRIDE_SIZE * 27) ;\ + SAVE_GPR(r28, BOOKE_GPR0_OFF + STRIDE_SIZE * 28) ;\ + SAVE_GPR(r29, BOOKE_GPR0_OFF + STRIDE_SIZE * 29) ;\ + SAVE_GPR(r30, BOOKE_GPR0_OFF + STRIDE_SIZE * 30) ;\ + SAVE_GPR(r31, BOOKE_GPR0_OFF + STRIDE_SIZE * 31) + +#define RESTORE_ALL_GPR \ + RESTORE_GPR(r1, BOOKE_GPR0_OFF + STRIDE_SIZE * 1) ;\ + RESTORE_GPR(r2, BOOKE_GPR0_OFF + STRIDE_SIZE * 2) ;\ + RESTORE_GPR(r13, BOOKE_GPR0_OFF + STRIDE_SIZE * 13) ;\ + RESTORE_GPR(r14, BOOKE_GPR0_OFF + STRIDE_SIZE * 14) ;\ + RESTORE_GPR(r15, BOOKE_GPR0_OFF + STRIDE_SIZE * 15) ;\ + RESTORE_GPR(r16, BOOKE_GPR0_OFF + STRIDE_SIZE * 16) ;\ + RESTORE_GPR(r17, BOOKE_GPR0_OFF + STRIDE_SIZE * 17) ;\ + RESTORE_GPR(r18, BOOKE_GPR0_OFF + STRIDE_SIZE * 18) ;\ + RESTORE_GPR(r19, BOOKE_GPR0_OFF + STRIDE_SIZE * 19) ;\ + RESTORE_GPR(r20, BOOKE_GPR0_OFF + STRIDE_SIZE * 20) ;\ + RESTORE_GPR(r21, BOOKE_GPR0_OFF + STRIDE_SIZE * 21) ;\ + RESTORE_GPR(r22, BOOKE_GPR0_OFF + STRIDE_SIZE * 22) ;\ + RESTORE_GPR(r23, BOOKE_GPR0_OFF + STRIDE_SIZE * 23) ;\ + RESTORE_GPR(r24, BOOKE_GPR0_OFF + STRIDE_SIZE * 24) ;\ + RESTORE_GPR(r25, BOOKE_GPR0_OFF + STRIDE_SIZE * 25) ;\ + RESTORE_GPR(r26, BOOKE_GPR0_OFF + STRIDE_SIZE * 26) ;\ + RESTORE_GPR(r27, BOOKE_GPR0_OFF + STRIDE_SIZE * 27) ;\ + RESTORE_GPR(r28, BOOKE_GPR0_OFF + STRIDE_SIZE * 28) ;\ + RESTORE_GPR(r29, BOOKE_GPR0_OFF + STRIDE_SIZE * 29) ;\ + RESTORE_GPR(r30, BOOKE_GPR0_OFF + STRIDE_SIZE * 30) ;\ + RESTORE_GPR(r31, BOOKE_GPR0_OFF + STRIDE_SIZE * 31) + +#define SAVE_ALL_SPRG \ + SAVE_SPR(SPRN_SPRG0, BOOKE_SPRG0_OFF + STRIDE_SIZE * 0) ;\ + SAVE_SPR(SPRN_SPRG1, BOOKE_SPRG0_OFF + STRIDE_SIZE * 1) ;\ + SAVE_SPR(SPRN_SPRG2, BOOKE_SPRG0_OFF + STRIDE_SIZE * 2) ;\ + SAVE_SPR(SPRN_SPRG3, BOOKE_SPRG0_OFF + STRIDE_SIZE * 3) ;\ + SAVE_SPR(SPRN_SPRG4, BOOKE_SPRG0_OFF + STRIDE_SIZE * 4) ;\ + SAVE_SPR(SPRN_SPRG5, BOOKE_SPRG0_OFF + STRIDE_SIZE * 5) ;\ + SAVE_SPR(SPRN_SPRG6, BOOKE_SPRG0_OFF + STRIDE_SIZE * 6) ;\ + SAVE_SPR(SPRN_SPRG7, BOOKE_SPRG0_OFF + STRIDE_SIZE * 7) ;\ + SAVE_SPR(SPRN_SPRG8, BOOKE_SPRG0_OFF + STRIDE_SIZE * 8) ;\ + SAVE_SPR(SPRN_SPRG9, BOOKE_SPRG0_OFF + STRIDE_SIZE * 9) + +#define RESTORE_ALL_SPRG \ + RESTORE_SPR(SPRN_SPRG0, BOOKE_SPRG0_OFF + STRIDE_SIZE * 0) ;\ + RESTORE_SPR(SPRN_SPRG1, BOOKE_SPRG0_OFF + STRIDE_SIZE * 1) ;\ + RESTORE_SPR(SPRN_SPRG2, BOOKE_SPRG0_OFF + STRIDE_SIZE * 2) ;\ + RESTORE_SPR(SPRN_SPRG3, BOOKE_SPRG0_OFF + STRIDE_SIZE * 3) ;\ + RESTORE_SPR(SPRN_SPRG4, BOOKE_SPRG0_OFF + STRIDE_SIZE * 4) ;\ + RESTORE_SPR(SPRN_SPRG5, BOOKE_SPRG0_OFF + STRIDE_SIZE * 5) ;\ + RESTORE_SPR(SPRN_SPRG6, BOOKE_SPRG0_OFF + STRIDE_SIZE * 6) ;\ + RESTORE_SPR(SPRN_SPRG7, BOOKE_SPRG0_OFF + STRIDE_SIZE * 7) ;\ + RESTORE_SPR(SPRN_SPRG8, BOOKE_SPRG0_OFF + STRIDE_SIZE * 8) ;\ + RESTORE_SPR(SPRN_SPRG9, BOOKE_SPRG0_OFF + STRIDE_SIZE * 9) + +#define SAVE_ALL_IVOR \ + SAVE_SPR(SPRN_IVOR0, BOOKE_IVOR0_OFF + STRIDE_SIZE * 0) ;\ + SAVE_SPR(SPRN_IVOR1, BOOKE_IVOR0_OFF + STRIDE_SIZE * 1) ;\ + SAVE_SPR(SPRN_IVOR2, BOOKE_IVOR0_OFF + STRIDE_SIZE * 2) ;\ + SAVE_SPR(SPRN_IVOR3, BOOKE_IVOR0_OFF + STRIDE_SIZE * 3) ;\ + SAVE_SPR(SPRN_IVOR4, BOOKE_IVOR0_OFF + STRIDE_SIZE * 4) ;\ + SAVE_SPR(SPRN_IVOR5, BOOKE_IVOR0_OFF + STRIDE_SIZE * 5) ;\ + SAVE_SPR(SPRN_IVOR6, BOOKE_IVOR0_OFF + STRIDE_SIZE * 6) ;\ + SAVE_SPR(SPRN_IVOR7, BOOKE_IVOR0_OFF + STRIDE_SIZE * 7) ;\ + SAVE_SPR(SPRN_IVOR8, BOOKE_IVOR0_OFF + STRIDE_SIZE * 8) ;\ + SAVE_SPR(SPRN_IVOR9, BOOKE_IVOR0_OFF + STRIDE_SIZE * 9) ;\ + SAVE_SPR(SPRN_IVOR10, BOOKE_IVOR0_OFF + STRIDE_SIZE * 10) ;\ + SAVE_SPR(SPRN_IVOR11, BOOKE_IVOR0_OFF + STRIDE_SIZE * 11) ;\ + SAVE_SPR(SPRN_IVOR12, BOOKE_IVOR0_OFF + STRIDE_SIZE * 12) ;\ + SAVE_SPR(SPRN_IVOR13, BOOKE_IVOR0_OFF + STRIDE_SIZE * 13) ;\ + SAVE_SPR(SPRN_IVOR14, BOOKE_IVOR0_OFF + STRIDE_SIZE * 14) ;\ + SAVE_SPR(SPRN_IVOR15, BOOKE_IVOR0_OFF + STRIDE_SIZE * 15) ;\ + SAVE_SPR(SPRN_IVOR35, BOOKE_IVOR0_OFF + STRIDE_SIZE * 35) ;\ + SAVE_SPR(SPRN_IVOR36, BOOKE_IVOR0_OFF + STRIDE_SIZE * 36) ;\ + SAVE_SPR(SPRN_IVOR37, BOOKE_IVOR0_OFF + STRIDE_SIZE * 37) ;\ + SAVE_SPR(SPRN_IVOR38, BOOKE_IVOR0_OFF + STRIDE_SIZE * 38) ;\ + SAVE_SPR(SPRN_IVOR39, BOOKE_IVOR0_OFF + STRIDE_SIZE * 39) ;\ + SAVE_SPR(SPRN_IVOR40, BOOKE_IVOR0_OFF + STRIDE_SIZE * 40) ;\ + SAVE_SPR(SPRN_IVOR41, BOOKE_IVOR0_OFF + STRIDE_SIZE * 41) + +#define RESTORE_ALL_IVOR \ + RESTORE_SPR(SPRN_IVOR0, BOOKE_IVOR0_OFF + STRIDE_SIZE * 0) ;\ + RESTORE_SPR(SPRN_IVOR1, BOOKE_IVOR0_OFF + STRIDE_SIZE * 1) ;\ + RESTORE_SPR(SPRN_IVOR2, BOOKE_IVOR0_OFF + STRIDE_SIZE * 2) ;\ + RESTORE_SPR(SPRN_IVOR3, BOOKE_IVOR0_OFF + STRIDE_SIZE * 3) ;\ + RESTORE_SPR(SPRN_IVOR4, BOOKE_IVOR0_OFF + STRIDE_SIZE * 4) ;\ + RESTORE_SPR(SPRN_IVOR5, BOOKE_IVOR0_OFF + STRIDE_SIZE * 5) ;\ + RESTORE_SPR(SPRN_IVOR6, BOOKE_IVOR0_OFF + STRIDE_SIZE * 6) ;\ + RESTORE_SPR(SPRN_IVOR7, BOOKE_IVOR0_OFF + STRIDE_SIZE * 7) ;\ + RESTORE_SPR(SPRN_IVOR8, BOOKE_IVOR0_OFF + STRIDE_SIZE * 8) ;\ + RESTORE_SPR(SPRN_IVOR9, BOOKE_IVOR0_OFF + STRIDE_SIZE * 9) ;\ + RESTORE_SPR(SPRN_IVOR10, BOOKE_IVOR0_OFF + STRIDE_SIZE * 10) ;\ + RESTORE_SPR(SPRN_IVOR11, BOOKE_IVOR0_OFF + STRIDE_SIZE * 11) ;\ + RESTORE_SPR(SPRN_IVOR12, BOOKE_IVOR0_OFF + STRIDE_SIZE * 12) ;\ + RESTORE_SPR(SPRN_IVOR13, BOOKE_IVOR0_OFF + STRIDE_SIZE * 13) ;\ + RESTORE_SPR(SPRN_IVOR14, BOOKE_IVOR0_OFF + STRIDE_SIZE * 14) ;\ + RESTORE_SPR(SPRN_IVOR15, BOOKE_IVOR0_OFF + STRIDE_SIZE * 15) ;\ + RESTORE_SPR(SPRN_IVOR35, BOOKE_IVOR0_OFF + STRIDE_SIZE * 35) ;\ + RESTORE_SPR(SPRN_IVOR36, BOOKE_IVOR0_OFF + STRIDE_SIZE * 36) ;\ + RESTORE_SPR(SPRN_IVOR37, BOOKE_IVOR0_OFF + STRIDE_SIZE * 37) ;\ + RESTORE_SPR(SPRN_IVOR38, BOOKE_IVOR0_OFF + STRIDE_SIZE * 38) ;\ + RESTORE_SPR(SPRN_IVOR39, BOOKE_IVOR0_OFF + STRIDE_SIZE * 39) ;\ + RESTORE_SPR(SPRN_IVOR40, BOOKE_IVOR0_OFF + STRIDE_SIZE * 40) ;\ + RESTORE_SPR(SPRN_IVOR41, BOOKE_IVOR0_OFF + STRIDE_SIZE * 41) + +/* reset time base to prevent from overflow */ +#define DELAY(count) \ + li r3, count; \ + li r4, 0; \ + mtspr SPRN_TBWL, r4; \ +101: mfspr r4, SPRN_TBRL; \ + cmpw r4, r3; \ + blt 101b + +#define FSL_DIS_ALL_IRQ \ + mfmsr r8; \ + rlwinm r8, r8, 0, ~MSR_CE; \ + rlwinm r8, r8, 0, ~MSR_ME; \ + rlwinm r8, r8, 0, ~MSR_EE; \ + rlwinm r8, r8, 0, ~MSR_DE; \ + mtmsr r8; \ + isync + +#ifndef CONFIG_PPC_E500MC +#define SS_TB 0x00 +#define SS_HID 0x08 /* 2 HIDs */ +#define SS_IAC 0x10 /* 2 IACs */ +#define SS_DAC 0x18 /* 2 DACs */ +#define SS_DBCR 0x20 /* 3 DBCRs */ +#define SS_PID 0x2c /* 3 PIDs */ +#define SS_SPRG 0x38 /* 8 SPRGs */ +#define SS_IVOR 0x58 /* 20 interrupt vectors */ +#define SS_TCR 0xa8 +#define SS_BUCSR 0xac +#define SS_L1CSR 0xb0 /* 2 L1CSRs */ +#define SS_MSR 0xb8 +#define SS_USPRG 0xbc +#define SS_GPREG 0xc0 /* r12-r31 */ +#define SS_LR 0x110 +#define SS_CR 0x114 +#define SS_SP 0x118 +#define SS_CURRENT 0x11c +#define SS_IVPR 0x120 +#define SS_BPTR 0x124 + + +#define STATE_SAVE_SIZE 0x128 + + .section .data + .align 5 +mpc85xx_sleep_save_area: + .space STATE_SAVE_SIZE +ccsrbase_low: + .long 0 +ccsrbase_high: + .long 0 +powmgtreq: + .long 0 + + .section .text + .align 12 + + /* + * r3 = high word of physical address of CCSR + * r4 = low word of physical address of CCSR + * r5 = JOG or deep sleep request + * JOG-0x00200000, deep sleep-0x00100000 + */ +_GLOBAL(mpc85xx_enter_deep_sleep) + lis r6, ccsrbase_low@ha + stw r4, ccsrbase_low@l(r6) + lis r6, ccsrbase_high@ha + stw r3, ccsrbase_high@l(r6) + + lis r6, powmgtreq@ha + stw r5, powmgtreq@l(r6) + + lis r10, mpc85xx_sleep_save_area@h + ori r10, r10, mpc85xx_sleep_save_area@l + + mfspr r5, SPRN_HID0 + mfspr r6, SPRN_HID1 + + stw r5, SS_HID+0(r10) + stw r6, SS_HID+4(r10) + + mfspr r4, SPRN_IAC1 + mfspr r5, SPRN_IAC2 + mfspr r6, SPRN_DAC1 + mfspr r7, SPRN_DAC2 + + stw r4, SS_IAC+0(r10) + stw r5, SS_IAC+4(r10) + stw r6, SS_DAC+0(r10) + stw r7, SS_DAC+4(r10) + + mfspr r4, SPRN_DBCR0 + mfspr r5, SPRN_DBCR1 + mfspr r6, SPRN_DBCR2 + + stw r4, SS_DBCR+0(r10) + stw r5, SS_DBCR+4(r10) + stw r6, SS_DBCR+8(r10) + + mfspr r4, SPRN_PID0 + mfspr r5, SPRN_PID1 + mfspr r6, SPRN_PID2 + + stw r4, SS_PID+0(r10) + stw r5, SS_PID+4(r10) + stw r6, SS_PID+8(r10) + + mfspr r4, SPRN_SPRG0 + mfspr r5, SPRN_SPRG1 + mfspr r6, SPRN_SPRG2 + mfspr r7, SPRN_SPRG3 + + stw r4, SS_SPRG+0x00(r10) + stw r5, SS_SPRG+0x04(r10) + stw r6, SS_SPRG+0x08(r10) + stw r7, SS_SPRG+0x0c(r10) + + mfspr r4, SPRN_SPRG4 + mfspr r5, SPRN_SPRG5 + mfspr r6, SPRN_SPRG6 + mfspr r7, SPRN_SPRG7 + + stw r4, SS_SPRG+0x10(r10) + stw r5, SS_SPRG+0x14(r10) + stw r6, SS_SPRG+0x18(r10) + stw r7, SS_SPRG+0x1c(r10) + + mfspr r4, SPRN_IVPR + stw r4, SS_IVPR(r10) + + mfspr r4, SPRN_IVOR0 + mfspr r5, SPRN_IVOR1 + mfspr r6, SPRN_IVOR2 + mfspr r7, SPRN_IVOR3 + + stw r4, SS_IVOR+0x00(r10) + stw r5, SS_IVOR+0x04(r10) + stw r6, SS_IVOR+0x08(r10) + stw r7, SS_IVOR+0x0c(r10) + + mfspr r4, SPRN_IVOR4 + mfspr r5, SPRN_IVOR5 + mfspr r6, SPRN_IVOR6 + mfspr r7, SPRN_IVOR7 + + stw r4, SS_IVOR+0x10(r10) + stw r5, SS_IVOR+0x14(r10) + stw r6, SS_IVOR+0x18(r10) + stw r7, SS_IVOR+0x1c(r10) + + mfspr r4, SPRN_IVOR8 + mfspr r5, SPRN_IVOR9 + mfspr r6, SPRN_IVOR10 + mfspr r7, SPRN_IVOR11 + + stw r4, SS_IVOR+0x20(r10) + stw r5, SS_IVOR+0x24(r10) + stw r6, SS_IVOR+0x28(r10) + stw r7, SS_IVOR+0x2c(r10) + + mfspr r4, SPRN_IVOR12 + mfspr r5, SPRN_IVOR13 + mfspr r6, SPRN_IVOR14 + mfspr r7, SPRN_IVOR15 + + stw r4, SS_IVOR+0x30(r10) + stw r5, SS_IVOR+0x34(r10) + stw r6, SS_IVOR+0x38(r10) + stw r7, SS_IVOR+0x3c(r10) + + mfspr r4, SPRN_IVOR32 + mfspr r5, SPRN_IVOR33 + mfspr r6, SPRN_IVOR34 + mfspr r7, SPRN_IVOR35 + + stw r4, SS_IVOR+0x40(r10) + stw r5, SS_IVOR+0x44(r10) + stw r6, SS_IVOR+0x48(r10) + stw r7, SS_IVOR+0x4c(r10) + + mfspr r4, SPRN_TCR + mfspr r5, SPRN_BUCSR + mfspr r6, SPRN_L1CSR0 + mfspr r7, SPRN_L1CSR1 + mfspr r8, SPRN_USPRG0 + + stw r4, SS_TCR(r10) + stw r5, SS_BUCSR(r10) + stw r6, SS_L1CSR+0(r10) + stw r7, SS_L1CSR+4(r10) + stw r8, SS_USPRG+0(r10) + + stmw r12, SS_GPREG(r10) + + mfmsr r4 + mflr r5 + mfcr r6 + + stw r4, SS_MSR(r10) + stw r5, SS_LR(r10) + stw r6, SS_CR(r10) + stw r1, SS_SP(r10) + stw r2, SS_CURRENT(r10) + +1: mftbu r4 + mftb r5 + mftbu r6 + cmpw r4, r6 + bne 1b + + stw r4, SS_TB+0(r10) + stw r5, SS_TB+4(r10) + + lis r5, ccsrbase_low@ha + lwz r4, ccsrbase_low@l(r5) + lis r5, ccsrbase_high@ha + lwz r3, ccsrbase_high@l(r5) + + /* Disable machine checks and critical exceptions */ + mfmsr r5 + rlwinm r5, r5, 0, ~MSR_CE + rlwinm r5, r5, 0, ~MSR_ME + mtmsr r5 + isync + + /* Use TLB1[15] to map the CCSR at 0xf0000000 */ + lis r5, 0x100f + mtspr SPRN_MAS0, r5 + lis r5, 0xc000 + ori r5, r5, 0x0500 + mtspr SPRN_MAS1, r5 + lis r5, 0xf000 + ori r5, r5, 0x000a + mtspr SPRN_MAS2, r5 + rlwinm r5, r4, 0, 0xfffff000 + ori r5, r5, 0x0005 + mtspr SPRN_MAS3, r5 + mtspr SPRN_MAS7, r3 + isync + tlbwe + isync + + lis r3, 0xf000 + lwz r4, 0x20(r3) + stw r4, SS_BPTR(r10) + + lis r3, 0xf002 /* L2 cache controller at CCSR+0x20000 */ + bl flush_disable_L2 + bl __flush_disable_L1 + + /* Enable I-cache, so as not to upset the bus + * with our loop. + */ + + mfspr r4, SPRN_L1CSR1 + ori r4, r4, 1 + mtspr SPRN_L1CSR1, r4 + isync + + /* Set boot page translation */ + lis r3, 0xf000 + lis r4, (mpc85xx_deep_resume - PAGE_OFFSET)@h + ori r4, r4, (mpc85xx_deep_resume - PAGE_OFFSET)@l + rlwinm r4, r4, 20, 0x000fffff + oris r4, r4, 0x8000 + stw r4, 0x20(r3) + lwz r4, 0x20(r3) /* read-back to flush write */ + twi 0, r4, 0 + isync + + /* Disable the decrementer */ + mfspr r4, SPRN_TCR + rlwinm r4, r4, 0, ~TCR_DIE + mtspr SPRN_TCR, r4 + + mfspr r4, SPRN_TSR + oris r4, r4, TSR_DIS@h + mtspr SPRN_TSR, r4 + + /* set PMRCCR[VRCNT] to wait power stable for 40ms */ + lis r3, 0xf00e + lwz r4, 0x84(r3) + clrlwi r4, r4, 16 + oris r4, r4, 0x12a3 + stw r4, 0x84(r3) + lwz r4, 0x84(r3) + + /* set deep sleep bit in POWMGTSCR */ + lis r3, powmgtreq@ha + lwz r8, powmgtreq@l(r3) + + lis r3, 0xf00e + lwz r4, 0x80(r3) + or r4, r4, r8 + stw r4, 0x80(r3) + lwz r4, 0x80(r3) /* read-back to flush write */ + twi 0, r4, 0 + isync + + mftb r5 +1: /* spin until either we enter deep sleep, or the sleep process is + * aborted due to a pending wakeup event. Wait some time between + * accesses, so we don't flood the bus and prevent the pmc from + * detecting an idle system. + */ + + mftb r4 + subf r7, r5, r4 + cmpwi r7, 1000 + blt 1b + mr r5, r4 + + lwz r6, 0x80(r3) + andis. r6, r6, 0x0010 + bne 1b + b 2f + +2: mfspr r4, SPRN_PIR + andi. r4, r4, 1 +99: bne 99b + + /* Establish a temporary 64MB 0->0 mapping in TLB1[1]. */ + lis r4, 0x1001 + mtspr SPRN_MAS0, r4 + lis r4, 0xc000 + ori r4, r4, 0x0800 + mtspr SPRN_MAS1, r4 + li r4, 0 + mtspr SPRN_MAS2, r4 + li r4, 0x0015 + mtspr SPRN_MAS3, r4 + li r4, 0 + mtspr SPRN_MAS7, r4 + isync + tlbwe + isync + + lis r3, (3f - PAGE_OFFSET)@h + ori r3, r3, (3f - PAGE_OFFSET)@l + mtctr r3 + bctr + + /* Locate the resume vector in the last word of the current page. */ + . = mpc85xx_enter_deep_sleep + 0xffc +mpc85xx_deep_resume: + b 2b + +3: + /* Restore the contents of TLB1[0]. It is assumed that it covers + * the currently executing code and the sleep save area, and that + * it does not alias our temporary mapping (which is at virtual zero). + */ + lis r3, (TLBCAM - PAGE_OFFSET)@h + ori r3, r3, (TLBCAM - PAGE_OFFSET)@l + + lwz r4, 0(r3) + lwz r5, 4(r3) + lwz r6, 8(r3) + lwz r7, 12(r3) + lwz r8, 16(r3) + + mtspr SPRN_MAS0, r4 + mtspr SPRN_MAS1, r5 + mtspr SPRN_MAS2, r6 + mtspr SPRN_MAS3, r7 + mtspr SPRN_MAS7, r8 + + isync + tlbwe + isync + + /* Access the ccsrbase address with TLB1[0] */ + lis r5, ccsrbase_low@ha + lwz r4, ccsrbase_low@l(r5) + lis r5, ccsrbase_high@ha + lwz r3, ccsrbase_high@l(r5) + + /* Use TLB1[15] to map the CCSR at 0xf0000000 */ + lis r5, 0x100f + mtspr SPRN_MAS0, r5 + lis r5, 0xc000 + ori r5, r5, 0x0500 + mtspr SPRN_MAS1, r5 + lis r5, 0xf000 + ori r5, r5, 0x000a + mtspr SPRN_MAS2, r5 + rlwinm r5, r4, 0, 0xfffff000 + ori r5, r5, 0x0005 + mtspr SPRN_MAS3, r5 + mtspr SPRN_MAS7, r3 + isync + tlbwe + isync + + lis r3, 0xf002 /* L2 cache controller at CCSR+0x20000 */ + bl invalidate_enable_L2 + + /* Access the MEM(r10) with TLB1[0] */ + lis r10, mpc85xx_sleep_save_area@h + ori r10, r10, mpc85xx_sleep_save_area@l + + lis r3, 0xf000 + lwz r4, SS_BPTR(r10) + stw r4, 0x20(r3) /* restore BPTR */ + + /* Program shift running space to PAGE_OFFSET */ + mfmsr r3 + lis r4, 1f@h + ori r4, r4, 1f@l + + mtsrr1 r3 + mtsrr0 r4 + rfi + +1: /* Restore the rest of TLB1, in ascending order so that + * the TLB1[1] gets invalidated first. + * + * XXX: It's better to invalidate the temporary mapping + * TLB1[15] for CCSR before restore any TLB1 entry include 0. + */ + lis r4, 0x100f + mtspr SPRN_MAS0, r4 + lis r4, 0 + mtspr SPRN_MAS1, r4 + isync + tlbwe + isync + + lis r3, (TLBCAM + 5*4 - 4)@h + ori r3, r3, (TLBCAM + 5*4 - 4)@l + li r4, 15 + mtctr r4 + +2: + lwz r5, 4(r3) + lwz r6, 8(r3) + lwz r7, 12(r3) + lwz r8, 16(r3) + lwzu r9, 20(r3) + + mtspr SPRN_MAS0, r5 + mtspr SPRN_MAS1, r6 + mtspr SPRN_MAS2, r7 + mtspr SPRN_MAS3, r8 + mtspr SPRN_MAS7, r9 + + isync + tlbwe + isync + bdnz 2b + + lis r10, mpc85xx_sleep_save_area@h + ori r10, r10, mpc85xx_sleep_save_area@l + + lwz r5, SS_HID+0(r10) + lwz r6, SS_HID+4(r10) + + isync + mtspr SPRN_HID0, r5 + isync + + msync + mtspr SPRN_HID1, r6 + isync + + lwz r4, SS_IAC+0(r10) + lwz r5, SS_IAC+4(r10) + lwz r6, SS_DAC+0(r10) + lwz r7, SS_DAC+4(r10) + + mtspr SPRN_IAC1, r4 + mtspr SPRN_IAC2, r5 + mtspr SPRN_DAC1, r6 + mtspr SPRN_DAC2, r7 + + lwz r4, SS_DBCR+0(r10) + lwz r5, SS_DBCR+4(r10) + lwz r6, SS_DBCR+8(r10) + + mtspr SPRN_DBCR0, r4 + mtspr SPRN_DBCR1, r5 + mtspr SPRN_DBCR2, r6 + + lwz r4, SS_PID+0(r10) + lwz r5, SS_PID+4(r10) + lwz r6, SS_PID+8(r10) + + mtspr SPRN_PID0, r4 + mtspr SPRN_PID1, r5 + mtspr SPRN_PID2, r6 + + lwz r4, SS_SPRG+0x00(r10) + lwz r5, SS_SPRG+0x04(r10) + lwz r6, SS_SPRG+0x08(r10) + lwz r7, SS_SPRG+0x0c(r10) + + mtspr SPRN_SPRG0, r4 + mtspr SPRN_SPRG1, r5 + mtspr SPRN_SPRG2, r6 + mtspr SPRN_SPRG3, r7 + + lwz r4, SS_SPRG+0x10(r10) + lwz r5, SS_SPRG+0x14(r10) + lwz r6, SS_SPRG+0x18(r10) + lwz r7, SS_SPRG+0x1c(r10) + + mtspr SPRN_SPRG4, r4 + mtspr SPRN_SPRG5, r5 + mtspr SPRN_SPRG6, r6 + mtspr SPRN_SPRG7, r7 + + lwz r4, SS_IVPR(r10) + mtspr SPRN_IVPR, r4 + + lwz r4, SS_IVOR+0x00(r10) + lwz r5, SS_IVOR+0x04(r10) + lwz r6, SS_IVOR+0x08(r10) + lwz r7, SS_IVOR+0x0c(r10) + + mtspr SPRN_IVOR0, r4 + mtspr SPRN_IVOR1, r5 + mtspr SPRN_IVOR2, r6 + mtspr SPRN_IVOR3, r7 + + lwz r4, SS_IVOR+0x10(r10) + lwz r5, SS_IVOR+0x14(r10) + lwz r6, SS_IVOR+0x18(r10) + lwz r7, SS_IVOR+0x1c(r10) + + mtspr SPRN_IVOR4, r4 + mtspr SPRN_IVOR5, r5 + mtspr SPRN_IVOR6, r6 + mtspr SPRN_IVOR7, r7 + + lwz r4, SS_IVOR+0x20(r10) + lwz r5, SS_IVOR+0x24(r10) + lwz r6, SS_IVOR+0x28(r10) + lwz r7, SS_IVOR+0x2c(r10) + + mtspr SPRN_IVOR8, r4 + mtspr SPRN_IVOR9, r5 + mtspr SPRN_IVOR10, r6 + mtspr SPRN_IVOR11, r7 + + lwz r4, SS_IVOR+0x30(r10) + lwz r5, SS_IVOR+0x34(r10) + lwz r6, SS_IVOR+0x38(r10) + lwz r7, SS_IVOR+0x3c(r10) + + mtspr SPRN_IVOR12, r4 + mtspr SPRN_IVOR13, r5 + mtspr SPRN_IVOR14, r6 + mtspr SPRN_IVOR15, r7 + + lwz r4, SS_IVOR+0x40(r10) + lwz r5, SS_IVOR+0x44(r10) + lwz r6, SS_IVOR+0x48(r10) + lwz r7, SS_IVOR+0x4c(r10) + + mtspr SPRN_IVOR32, r4 + mtspr SPRN_IVOR33, r5 + mtspr SPRN_IVOR34, r6 + mtspr SPRN_IVOR35, r7 + + lwz r4, SS_TCR(r10) + lwz r5, SS_BUCSR(r10) + lwz r6, SS_L1CSR+0(r10) + lwz r7, SS_L1CSR+4(r10) + lwz r8, SS_USPRG+0(r10) + + mtspr SPRN_TCR, r4 + mtspr SPRN_BUCSR, r5 + + msync + isync + mtspr SPRN_L1CSR0, r6 + isync + + mtspr SPRN_L1CSR1, r7 + isync + + mtspr SPRN_USPRG0, r8 + + lmw r12, SS_GPREG(r10) + + lwz r1, SS_SP(r10) + lwz r2, SS_CURRENT(r10) + lwz r4, SS_MSR(r10) + lwz r5, SS_LR(r10) + lwz r6, SS_CR(r10) + + msync + mtmsr r4 + isync + + mtlr r5 + mtcr r6 + + li r4, 0 + mtspr SPRN_TBWL, r4 + + lwz r4, SS_TB+0(r10) + lwz r5, SS_TB+4(r10) + + mtspr SPRN_TBWU, r4 + mtspr SPRN_TBWL, r5 + + lis r3, 1 + mtdec r3 + + blr + +#else /* CONFIG_PPC_E500MC */ + + .section .data + .align 6 +regs_buffer: + .space BUFFER_SIZE + + .section .text +/* + * Save CPU registers + * r3 : the base address of the buffer which stores the values of registers + */ +e5500_cpu_state_save: + /* store the base address to r10 */ + mr r10, r3 + + SAVE_ALL_GPR + SAVE_ALL_SPRG + SAVE_ALL_IVOR + + SAVE_SPR(SPRN_IVPR, BOOKE_IVPR_OFF) + SAVE_SPR(SPRN_PID0, BOOKE_PID0_OFF) + SAVE_SPR(SPRN_EPCR, BOOKE_EPCR_OFF) + SAVE_SPR(SPRN_HID0, BOOKE_HID0_OFF) + SAVE_SPR(SPRN_PIR, BOOKE_PIR_OFF) + SAVE_SPR(SPRN_BUCSR, BOOKE_BUCSR_OFF) +1: + mfspr r5, SPRN_TBRU + mfspr r4, SPRN_TBRL + SAVE_GPR(r5, BOOKE_TBU_OFF) + SAVE_GPR(r4, BOOKE_TBL_OFF) + mfspr r3, SPRN_TBRU + cmpw r3, r5 + bne 1b + + blr + +/* + * Restore CPU registers + * r3 : the base address of the buffer which stores the values of registers + */ +e5500_cpu_state_restore: + /* store the base address to r10 */ + mr r10, r3 + + RESTORE_ALL_GPR + RESTORE_ALL_SPRG + RESTORE_ALL_IVOR + + RESTORE_SPR(SPRN_IVPR, BOOKE_IVPR_OFF) + RESTORE_SPR(SPRN_PID0, BOOKE_PID0_OFF) + RESTORE_SPR(SPRN_EPCR, BOOKE_EPCR_OFF) + RESTORE_SPR(SPRN_HID0, BOOKE_HID0_OFF) + RESTORE_SPR(SPRN_PIR, BOOKE_PIR_OFF) + RESTORE_SPR(SPRN_BUCSR, BOOKE_BUCSR_OFF) + + li r0, 0 + mtspr SPRN_TBWL, r0 + RESTORE_SPR(SPRN_TBWU, BOOKE_TBU_OFF) + RESTORE_SPR(SPRN_TBWL, BOOKE_TBL_OFF) + + blr + +#define CPC_CPCCSR0 0x0 +#define CPC_CPCCSR0_CPCFL 0x800 + +/* + * Flush the CPC cache. + * r3 : the base address of CPC + */ +flush_cpc_cache: + lwz r6, CPC_CPCCSR0(r3) + ori r6, r6, CPC_CPCCSR0_CPCFL + stw r6, CPC_CPCCSR0(r3) + sync + + /* Wait until completing the flush */ +1: lwz r6, CPC_CPCCSR0(r3) + andi. r6, r6, CPC_CPCCSR0_CPCFL + bne 1b + + blr + +/* + * the last stage to enter deep sleep + * + */ + .align 6 +_GLOBAL(fsl_dp_enter_low) +deepsleep_start: + LOAD_REG_ADDR(r9, buf_tmp) + /* save the return address and MSR */ + mflr r8 + PPC_STL r8, 0(r9) + mfmsr r8 + PPC_STL r8, 8(r9) + mfspr r8, SPRN_TCR + PPC_STL r8, 16(r9) + mfcr r8 + PPC_STL r8, 24(r9) + li r8, 0 + mtspr SPRN_TCR, r8 + + /* save the parameters */ + PPC_STL r3, 32(r9) + PPC_STL r4, 40(r9) + PPC_STL r5, 48(r9) + PPC_STL r6, 56(r9) + + LOAD_REG_ADDR(r3, regs_buffer) + bl e5500_cpu_state_save + + /* restore the parameters */ + LOAD_REG_ADDR(r9, buf_tmp) + PPC_LL r31, 32(r9) + PPC_LL r30, 40(r9) + PPC_LL r29, 48(r9) + PPC_LL r28, 56(r9) + + /* flush caches inside CPU */ + LOAD_REG_ADDR(r3, cur_cpu_spec) + PPC_LL r3, 0(r3) + PPC_LL r3, CPU_FLUSH_CACHES(r3) + PPC_LCMPI 0, r3, 0 + beq 6f +#ifdef CONFIG_PPC64 + PPC_LL r3, 0(r3) +#endif + mtctr r3 + bctrl +6: + /* Flush the CPC cache */ +#define CPC_OFFSET 0x10000 + mr r3, r31 + addis r3, r3, CPC_OFFSET@h + bl flush_cpc_cache + + /* prefecth TLB */ +#define CCSR_GPIO1_GPDAT 0x130008 +#define CCSR_GPIO1_GPDAT_29 0x4 + LOAD_REG_IMMEDIATE(r11, CCSR_GPIO1_GPDAT) + add r11, r31, r11 + lwz r10, 0(r11) + +#define CCSR_RCPM_PCPH15SETR 0xe20b4 +#define CCSR_RCPM_PCPH15SETR_CORE0 0x1 + LOAD_REG_IMMEDIATE(r12, CCSR_RCPM_PCPH15SETR) + add r12, r31, r12 + lwz r10, 0(r12) + +#define CCSR_DDR_SDRAM_CFG_2 0x8114 +#define CCSR_DDR_SDRAM_CFG_2_FRC_SR 0x80000000 + LOAD_REG_IMMEDIATE(r13, CCSR_DDR_SDRAM_CFG_2) + add r13, r31, r13 + lwz r10, 0(r13) + +#define DCSR_EPU_EPGCR 0x000 +#define DCSR_EPU_EPGCR_GCE 0x80000000 + li r14, DCSR_EPU_EPGCR + add r14, r30, r14 + lwz r10, 0(r14) + +#define DCSR_EPU_EPECR15 0x33C +#define DCSR_EPU_EPECR15_IC0 0x80000000 + li r15, DCSR_EPU_EPECR15 + add r15, r30, r15 + lwz r10, 0(r15) + +#define CCSR_SCFG_QMIFRSTCR 0xfc40c +#define CCSR_SCFG_QMIFRSTCR_QMIFRST 0x80000000 + LOAD_REG_IMMEDIATE(r16, CCSR_SCFG_QMIFRSTCR) + add r16, r31, r16 + lwz r10, 0(r16) + +/* + * There are two kind of register maps, one for T1040QDS and + * the other for T104xRDB. + */ +#define T104XRDB_CPLD_MISCCSR 0x17 +#define T104XRDB_CPLD_MISCCSR_SLEEPEN 0x40 +#define T1040QDS_QIXIS_PWR_CTL2 0x21 +#define T1040QDS_QIXIS_PWR_CTL2_PCTL 0x2 + li r3, T1040QDS_QIXIS_PWR_CTL2 + PPC_LCMPI 0, r28, T1040QDS_TETRA_FLAG + beq 20f + li r3, T104XRDB_CPLD_MISCCSR +20: add r29, r29, r3 + lbz r10, 0(r29) + sync + + LOAD_REG_ADDR(r8, deepsleep_start) + LOAD_REG_ADDR(r9, deepsleep_end) + + /* prefecth code to cache so that executing code after disable DDR */ +1: icbtls 2, 0, r8 + addi r8, r8, 64 + cmpw r8, r9 + blt 1b + sync + + FSL_DIS_ALL_IRQ + + /* + * Place DDR controller in self refresh mode. + * From here on, can't access DDR any more. + */ + lwz r10, 0(r13) + oris r10, r10, CCSR_DDR_SDRAM_CFG_2_FRC_SR@h + stw r10, 0(r13) + lwz r10, 0(r13) + sync + + DELAY(500) + + /* + * Enable deep sleep signals by write external CPLD/FPGA register. + * The bootloader will disable them when wakeup from deep sleep. + */ + lbz r10, 0(r29) + li r3, T1040QDS_QIXIS_PWR_CTL2_PCTL + PPC_LCMPI 0, r28, T1040QDS_TETRA_FLAG + beq 22f + li r3, T104XRDB_CPLD_MISCCSR_SLEEPEN +22: or r10, r10, r3 + stb r10, 0(r29) + lbz r10, 0(r29) + sync + + /* + * Set GPIO1_29 to lock the signal MCKE down during deep sleep. + * The bootloader will clear it when wakeup. + */ + lwz r10, 0(r11) + ori r10, r10, CCSR_GPIO1_GPDAT_29 + stw r10, 0(r11) + lwz r10, 0(r11) + + DELAY(100) + + /* Reset QMan system bus interface */ + lwz r10, 0(r16) + oris r10, r10, CCSR_SCFG_QMIFRSTCR_QMIFRST@h + stw r10, 0(r16) + lwz r10, 0(r16) + + /* Enable all EPU Counters */ + li r10, 0 + oris r10, r10, DCSR_EPU_EPGCR_GCE@h + stw r10, 0(r14) + lwz r10, 0(r14) + + /* Enable SCU15 to trigger on RCPM Concentrator 0 */ + lwz r10, 0(r15) + oris r10, r10, DCSR_EPU_EPECR15_IC0@h + stw r10, 0(r15) + lwz r10, 0(r15) + + /* put Core0 in PH15 mode, trigger EPU FSM */ + lwz r10, 0(r12) + ori r10, r10, CCSR_RCPM_PCPH15SETR_CORE0 + stw r10, 0(r12) +2: + b 2b + + /* + * Leave some space to prevent prefeching instruction + * beyond deepsleep_end. The space also can be used as heap. + */ +buf_tmp: + .space 128 + .align 6 +deepsleep_end: + + .align 12 +#ifdef CONFIG_PPC32 +_GLOBAL(fsl_booke_deep_sleep_resume) + /* disable interrupts */ + FSL_DIS_ALL_IRQ + +#define ENTRY_DEEPSLEEP_SETUP +#define ENTRY_MAPPING_BOOT_SETUP +#include <../../kernel/fsl_booke_entry_mapping.S> +#undef ENTRY_DEEPSLEEP_SETUP +#undef ENTRY_MAPPING_BOOT_SETUP + + li r3, 0 + mfspr r4, SPRN_PIR + bl call_setup_cpu + + /* Load each CAM entry */ + LOAD_REG_ADDR(r3, tlbcam_index) + lwz r3, 0(r3) + mtctr r3 + li r9, 0 +3: mr r3, r9 + bl loadcam_entry + addi r9, r9, 1 + bdnz 3b + + /* restore cpu registers */ + LOAD_REG_ADDR(r3, regs_buffer) + bl e5500_cpu_state_restore + + /* restore return address */ + LOAD_REG_ADDR(r3, buf_tmp) + lwz r4, 16(r3) + mtspr SPRN_TCR, r4 + lwz r4, 0(r3) + mtlr r4 + lwz r4, 8(r3) + mtmsr r4 + lwz r4, 24(r3) + mtcr r4 + + blr + +#else /* CONFIG_PPC32 */ + +_GLOBAL(fsl_booke_deep_sleep_resume) + /* disable interrupts */ + FSL_DIS_ALL_IRQ + + /* switch to 64-bit mode */ + bl .enable_64b_mode + + /* set TOC pointer */ + bl .relative_toc + + /* setup initial TLBs, switch to kernel space ... */ + bl .start_initialization_book3e + + /* address space changed, set TOC pointer again */ + bl .relative_toc + + /* call a cpu state restore handler */ + LOAD_REG_ADDR(r23, cur_cpu_spec) + ld r23,0(r23) + ld r23,CPU_SPEC_RESTORE(r23) + cmpdi 0,r23,0 + beq 1f + ld r23,0(r23) + mtctr r23 + bctrl +1: + LOAD_REG_ADDR(r3, regs_buffer) + bl e5500_cpu_state_restore + + /* Load each CAM entry */ + LOAD_REG_ADDR(r3, tlbcam_index) + lwz r3, 0(r3) + mtctr r3 + li r0, 0 +3: mr r3, r0 + bl loadcam_entry + addi r0, r0, 1 + bdnz 3b + + /* restore return address */ + LOAD_REG_ADDR(r3, buf_tmp) + ld r4, 16(r3) + mtspr SPRN_TCR, r4 + ld r4, 0(r3) + mtlr r4 + ld r4, 8(r3) + mtmsr r4 + ld r4, 24(r3) + mtcr r4 + + blr + +#endif /* CONFIG_PPC32 */ + +#endif diff --git a/arch/powerpc/platforms/86xx/Kconfig b/arch/powerpc/platforms/86xx/Kconfig index 07a9d60c618a..76965a9e6481 100644 --- a/arch/powerpc/platforms/86xx/Kconfig +++ b/arch/powerpc/platforms/86xx/Kconfig @@ -5,6 +5,7 @@ menuconfig PPC_86xx depends on PPC_BOOK3S_32 select FSL_SOC select ALTIVEC + select FSL_PMC if SUSPEND help The Freescale E600 SoCs have 74xx cores. diff --git a/arch/powerpc/sysdev/fsl_pmc.c b/arch/powerpc/sysdev/fsl_pmc.c index 76896de970ca..e72c03d32702 100644 --- a/arch/powerpc/sysdev/fsl_pmc.c +++ b/arch/powerpc/sysdev/fsl_pmc.c @@ -16,54 +16,192 @@ #include #include #include +#include +#include + +#include +#include +#include struct pmc_regs { __be32 devdisr; __be32 devdisr2; - __be32 :32; - __be32 :32; - __be32 pmcsr; -#define PMCSR_SLP (1 << 17) + __be32 res1; + __be32 res2; + __be32 powmgtcsr; +#define POWMGTCSR_SLP 0x00020000 +#define POWMGTCSR_DPSLP 0x00100000 +#define POWMGTCSR_LOSSLESS 0x00400000 + __be32 res3[2]; + __be32 pmcdr; }; -static struct device *pmc_dev; static struct pmc_regs __iomem *pmc_regs; +static unsigned int pmc_flag; + +#define PMC_SLEEP 0x1 +#define PMC_DEEP_SLEEP 0x2 +#define PMC_LOSSLESS 0x4 + +/** + * mpc85xx_pmc_set_wake - enable devices as wakeup event source + * @dev: a device affected + * @enable: True to enable event generation; false to disable + * + * This enables the device as a wakeup event source, or disables it. + * + * RETURN VALUE: + * 0 is returned on success. + * -EINVAL is returned if device is not supposed to wake up the system. + * -ENODEV is returned if PMC is unavailable. + * Error code depending on the platform is returned if both the platform and + * the native mechanism fail to enable the generation of wake-up events + */ +int mpc85xx_pmc_set_wake(struct device *dev, bool enable) +{ + int ret = 0; + struct device_node *clk_np; + const u32 *prop; + u32 pmcdr_mask; + + if (!pmc_regs) { + dev_err(dev, "%s: PMC is unavailable\n", __func__); + return -ENODEV; + } + + if (enable && !device_may_wakeup(dev)) + return -EINVAL; + + clk_np = of_parse_phandle(dev->of_node, "fsl,pmc-handle", 0); + if (!clk_np) + return -EINVAL; + + prop = of_get_property(clk_np, "fsl,pmcdr-mask", NULL); + if (!prop) { + ret = -EINVAL; + goto out; + } + pmcdr_mask = be32_to_cpup(prop); + + if (enable) + /* clear to enable clock in low power mode */ + clrbits32(&pmc_regs->pmcdr, pmcdr_mask); + else + setbits32(&pmc_regs->pmcdr, pmcdr_mask); + +out: + of_node_put(clk_np); + return ret; +} +EXPORT_SYMBOL_GPL(mpc85xx_pmc_set_wake); + +/** + * mpc85xx_pmc_set_lossless_ethernet - enable lossless ethernet + * in (deep) sleep mode + * @enable: True to enable event generation; false to disable + */ +void mpc85xx_pmc_set_lossless_ethernet(int enable) +{ + if (pmc_flag & PMC_LOSSLESS) { + if (enable) + setbits32(&pmc_regs->powmgtcsr, POWMGTCSR_LOSSLESS); + else + clrbits32(&pmc_regs->powmgtcsr, POWMGTCSR_LOSSLESS); + } +} +EXPORT_SYMBOL_GPL(mpc85xx_pmc_set_lossless_ethernet); static int pmc_suspend_enter(suspend_state_t state) { - int ret; + int ret = 0; + int result; - setbits32(&pmc_regs->pmcsr, PMCSR_SLP); - /* At this point, the CPU is asleep. */ + switch (state) { +#ifdef CONFIG_PPC_85xx + case PM_SUSPEND_MEM: +#ifdef CONFIG_SPE + enable_kernel_spe(); +#endif +#ifdef CONFIG_PPC_FPU + enable_kernel_fp(); +#endif - /* Upon resume, wait for SLP bit to be clear. */ - ret = spin_event_timeout((in_be32(&pmc_regs->pmcsr) & PMCSR_SLP) == 0, - 10000, 10) ? 0 : -ETIMEDOUT; - if (ret) - dev_err(pmc_dev, "tired waiting for SLP bit to clear\n"); + pr_debug("%s: Entering deep sleep\n", __func__); + + local_irq_disable(); + mpc85xx_enter_deep_sleep(get_immrbase(), POWMGTCSR_DPSLP); + + pr_debug("%s: Resumed from deep sleep\n", __func__); + break; +#endif + + case PM_SUSPEND_STANDBY: + local_irq_disable(); + flush_dcache_L1(); + + setbits32(&pmc_regs->powmgtcsr, POWMGTCSR_SLP); + /* At this point, the CPU is asleep. */ + + /* Upon resume, wait for SLP bit to be clear. */ + result = spin_event_timeout( + (in_be32(&pmc_regs->powmgtcsr) & POWMGTCSR_SLP) == 0, + 10000, 10); + if (!result) { + pr_err("%s: timeout waiting for SLP bit " + "to be cleared\n", __func__); + ret = -ETIMEDOUT; + } + break; + + default: + ret = -EINVAL; + + } return ret; } static int pmc_suspend_valid(suspend_state_t state) { - if (state != PM_SUSPEND_STANDBY) - return 0; - return 1; + set_pm_suspend_state(state); + + if (((pmc_flag & PMC_SLEEP) && (state == PM_SUSPEND_STANDBY)) || + ((pmc_flag & PMC_DEEP_SLEEP) && (state == PM_SUSPEND_MEM))) + return 1; + + set_pm_suspend_state(PM_SUSPEND_ON); + return 0; +} + +static void pmc_suspend_end(void) +{ + set_pm_suspend_state(PM_SUSPEND_ON); } static const struct platform_suspend_ops pmc_suspend_ops = { .valid = pmc_suspend_valid, .enter = pmc_suspend_enter, + .end = pmc_suspend_end, }; -static int pmc_probe(struct platform_device *ofdev) +static int pmc_probe(struct platform_device *pdev) { - pmc_regs = of_iomap(ofdev->dev.of_node, 0); + struct device_node *np = pdev->dev.of_node; + + pmc_regs = of_iomap(np, 0); if (!pmc_regs) return -ENOMEM; - pmc_dev = &ofdev->dev; + pmc_flag = PMC_SLEEP; + if (of_device_is_compatible(np, "fsl,mpc8536-pmc")) + pmc_flag |= PMC_DEEP_SLEEP; + + if (of_device_is_compatible(np, "fsl,p1022-pmc")) + pmc_flag |= PMC_DEEP_SLEEP | PMC_LOSSLESS; + suspend_set_ops(&pmc_suspend_ops); + set_pm_suspend_state(PM_SUSPEND_ON); + + pr_info("Freescale PMC driver\n"); return 0; } diff --git a/arch/powerpc/sysdev/fsl_soc.c b/arch/powerpc/sysdev/fsl_soc.c index 90ad16161604..21af6d5c075a 100644 --- a/arch/powerpc/sysdev/fsl_soc.c +++ b/arch/powerpc/sysdev/fsl_soc.c @@ -42,6 +42,37 @@ extern void init_fcc_ioports(struct fs_platform_info*); extern void init_fec_ioports(struct fs_platform_info*); extern void init_smc_ioports(struct fs_uart_platform_info*); static phys_addr_t immrbase = -1; +static phys_addr_t dcsrbase = -1; + +phys_addr_t get_dcsrbase(void) +{ + struct device_node *np; + const __be32 *prop; + int size; + u32 naddr; + + if (dcsrbase != -1) + return dcsrbase; + + np = of_find_compatible_node(NULL, NULL, "fsl,dcsr"); + if (!np) + return -1; + + prop = of_get_property(np, "#address-cells", &size); + if (prop && size == 4) + naddr = be32_to_cpup(prop); + else + naddr = 2; + + prop = of_get_property(np, "ranges", NULL); + if (prop) + dcsrbase = of_translate_address(np, prop + naddr); + + of_node_put(np); + + return dcsrbase; +} +EXPORT_SYMBOL(get_dcsrbase); phys_addr_t get_immrbase(void) { diff --git a/arch/powerpc/sysdev/fsl_soc.h b/arch/powerpc/sysdev/fsl_soc.h index db11b06eb38f..674ed672bd62 100644 --- a/arch/powerpc/sysdev/fsl_soc.h +++ b/arch/powerpc/sysdev/fsl_soc.h @@ -7,6 +7,7 @@ struct spi_device; +extern phys_addr_t get_dcsrbase(void); extern phys_addr_t get_immrbase(void); #if defined(CONFIG_CPM) || defined(CONFIG_QUICC_ENGINE) extern u32 get_brgfreq(void); @@ -44,5 +45,22 @@ extern struct platform_diu_data_ops diu_ops; void __noreturn fsl_hv_restart(char *cmd); void __noreturn fsl_hv_halt(void); +/* + * Cast the ccsrbar to 64-bit parameter so that the assembly + * code can be compatible with both 32-bit & 36-bit. + */ +extern void mpc85xx_enter_deep_sleep(u64 ccsrbar, u32 powmgtreq); + +#ifdef CONFIG_FSL_PMC +int mpc85xx_pmc_set_wake(struct device *dev, bool enable); +void mpc85xx_pmc_set_lossless_ethernet(int enable); +#else +static inline int mpc85xx_pmc_set_wake(struct device *dev, bool enable) +{ + return -ENODEV; +} +#define mpc85xx_pmc_set_lossless_ethernet(enable) do { } while (0) +#endif + #endif #endif From b558f49bc0995b68c33887dfe513197de45a38a8 Mon Sep 17 00:00:00 2001 From: Roy Pledge Date: Tue, 12 Jan 2016 13:40:26 -0500 Subject: [PATCH 77/81] Add APIs to setup HugeTLB mappings for USDPAA --- arch/powerpc/mm/mmu_decl.h | 6 ++++++ arch/powerpc/mm/nohash/fsl_booke.c | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/mm/mmu_decl.h b/arch/powerpc/mm/mmu_decl.h index c750ac9ec713..3b520aa5523e 100644 --- a/arch/powerpc/mm/mmu_decl.h +++ b/arch/powerpc/mm/mmu_decl.h @@ -89,6 +89,12 @@ void print_system_hash_info(void); #endif /* CONFIG_PPC_MMU_NOHASH */ +void settlbcam(int index, unsigned long virt, phys_addr_t phys, + unsigned long size, unsigned long flags, unsigned int pid); + +void cleartlbcam(unsigned long virt, unsigned int pid); + + #ifdef CONFIG_PPC32 void hash_preload(struct mm_struct *mm, unsigned long ea); diff --git a/arch/powerpc/mm/nohash/fsl_booke.c b/arch/powerpc/mm/nohash/fsl_booke.c index 556e3cd52a35..e454a69ce6bc 100644 --- a/arch/powerpc/mm/nohash/fsl_booke.c +++ b/arch/powerpc/mm/nohash/fsl_booke.c @@ -102,7 +102,7 @@ unsigned long p_block_mapped(phys_addr_t pa) * an unsigned long (for example, 32-bit implementations cannot support a 4GB * size). */ -static void settlbcam(int index, unsigned long virt, phys_addr_t phys, +void settlbcam(int index, unsigned long virt, phys_addr_t phys, unsigned long size, unsigned long flags, unsigned int pid) { unsigned int tsize; @@ -140,6 +140,18 @@ static void settlbcam(int index, unsigned long virt, phys_addr_t phys, tlbcam_addrs[index].phys = phys; } +void cleartlbcam(unsigned long virt, unsigned int pid) +{ + int i = 0; + for (i = 0; i < NUM_TLBCAMS; i++) { + if (tlbcam_addrs[i].start == virt) { + TLBCAM[i].MAS1 = 0; + loadcam_entry(i); + return; + } + } +} + unsigned long calc_cam_sz(unsigned long ram, unsigned long virt, phys_addr_t phys) { From 52791b37ca90e6564f122ff6ec978fe37c554d67 Mon Sep 17 00:00:00 2001 From: Pankaj Bansal Date: Thu, 28 Feb 2019 17:22:28 +0530 Subject: [PATCH 78/81] drivers: soc: fsl: add qixis driver FPGA on LX2160AQDS/LX2160ARDB connected on I2C bus, so add qixis driver which is basically an i2c client driver to control FPGA. Signed-off-by: Pankaj Bansal --- drivers/soc/fsl/Kconfig | 11 ++++ drivers/soc/fsl/Makefile | 1 + drivers/soc/fsl/qixis_ctrl.c | 105 +++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 drivers/soc/fsl/qixis_ctrl.c diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig index f9ad8ad54a7d..eca29d23dbb8 100644 --- a/drivers/soc/fsl/Kconfig +++ b/drivers/soc/fsl/Kconfig @@ -40,4 +40,15 @@ config DPAA2_CONSOLE /dev/dpaa2_mc_console and /dev/dpaa2_aiop_console, which can be used to dump the Management Complex and AIOP firmware logs. + +config FSL_QIXIS + tristate "QIXIS system controller driver" + depends on OF + select REGMAP_I2C + select REGMAP_MMIO + default n + help + Say y here to enable QIXIS system controller api. The qixis driver + provides FPGA functions to control system. + endmenu diff --git a/drivers/soc/fsl/Makefile b/drivers/soc/fsl/Makefile index 71dee8d0d1f0..6e70a4b56cae 100644 --- a/drivers/soc/fsl/Makefile +++ b/drivers/soc/fsl/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_FSL_DPAA) += qbman/ obj-$(CONFIG_QUICC_ENGINE) += qe/ obj-$(CONFIG_CPM) += qe/ +obj-$(CONFIG_FSL_QIXIS) += qixis_ctrl.o obj-$(CONFIG_FSL_GUTS) += guts.o obj-$(CONFIG_FSL_MC_DPIO) += dpio/ obj-$(CONFIG_DPAA2_CONSOLE) += dpaa2-console.o diff --git a/drivers/soc/fsl/qixis_ctrl.c b/drivers/soc/fsl/qixis_ctrl.c new file mode 100644 index 000000000000..cc4696cf9d50 --- /dev/null +++ b/drivers/soc/fsl/qixis_ctrl.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* Freescale QIXIS system controller driver. + * + * Copyright 2015 Freescale Semiconductor, Inc. + * Copyright 2018-2019 NXP + */ + +#include +#include +#include +#include +#include +#include + +/* QIXIS MAP */ +struct fsl_qixis_regs { + u8 id; /* Identification Registers */ + u8 version; /* Version Register */ + u8 qixis_ver; /* QIXIS Version Register */ + u8 reserved1[0x1f]; +}; + +struct qixis_priv { + struct regmap *regmap; +}; + +static struct regmap_config qixis_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct mfd_cell fsl_qixis_devs[] = { + { + .name = "reg-mux", + .of_compatible = "reg-mux", + }, +}; + +static int fsl_qixis_i2c_probe(struct i2c_client *client) +{ + struct qixis_priv *priv; + int ret = 0; + u32 qver; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EOPNOTSUPP; + + priv = devm_kzalloc(&client->dev, sizeof(struct qixis_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = regmap_init_i2c(client, &qixis_regmap_config); + regmap_read(priv->regmap, offsetof(struct fsl_qixis_regs, qixis_ver), + &qver); + pr_info("Freescale QIXIS Version: 0x%08x\n", qver); + + i2c_set_clientdata(client, priv); + + if (of_device_is_compatible(client->dev.of_node, "simple-mfd")) + ret = devm_mfd_add_devices(&client->dev, -1, fsl_qixis_devs, + ARRAY_SIZE(fsl_qixis_devs), NULL, 0, + NULL); + if (ret) + goto error; + + return ret; +error: + regmap_exit(priv->regmap); + + return ret; +} + +static int fsl_qixis_i2c_remove(struct i2c_client *client) +{ + struct qixis_priv *priv; + + priv = i2c_get_clientdata(client); + regmap_exit(priv->regmap); + + return 0; +} + +static const struct of_device_id fsl_qixis_i2c_of_match[] = { + { .compatible = "fsl,fpga-qixis-i2c" }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_qixis_i2c_of_match); + +static struct i2c_driver fsl_qixis_i2c_driver = { + .driver = { + .name = "qixis_ctrl_i2c", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(fsl_qixis_i2c_of_match), + }, + .probe_new = fsl_qixis_i2c_probe, + .remove = fsl_qixis_i2c_remove, +}; +module_i2c_driver(fsl_qixis_i2c_driver); + +MODULE_AUTHOR("Wang Dongsheng "); +MODULE_DESCRIPTION("Freescale QIXIS system controller driver"); +MODULE_LICENSE("GPL"); + From 9bfb9485e27731e02b49b411e53c1d6b65345b58 Mon Sep 17 00:00:00 2001 From: Fugang Duan Date: Wed, 27 Nov 2019 16:26:07 +0800 Subject: [PATCH 79/81] LF-176 ARM: imx: mach-imx6q: Revert "ARM: imx: correct the enet_clk_ref clock string" enet_clk_ref is the same clock as ptp for i.MX6qdl platform, but dtsi only define ptp clock that source from soc internal anatop clock, and imx6q clock driver already register "enet_ref" clock lookup for the ptp clock, so keep the con_id string as "enet_ref" for clk_get_sys(). This reverts commit a3990871b98587f53548e769c23fda5c2644063c. Signed-off-by: Fugang Duan --- arch/arm/mach-imx/mach-imx6q.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c index dcfbffccb7dc..24b91ec89b46 100644 --- a/arch/arm/mach-imx/mach-imx6q.c +++ b/arch/arm/mach-imx/mach-imx6q.c @@ -206,7 +206,7 @@ static void __init imx6q_1588_init(void) goto put_node; } - enet_ref = clk_get_sys(NULL, "enet_clk_ref"); + 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; From 090b431c46bb79bcbc6314fee90e339e89552c82 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Wed, 27 Nov 2019 14:47:28 +0800 Subject: [PATCH 80/81] LF-171 ARM: imx: Add cpu type check for imx6ulz in msl code Add cpu type check for i.MX6ULZ in MSL code to support low power feature. Signed-off-by: Jacky Bai Reviewed-by: Anson Huang --- arch/arm/mach-imx/anatop.c | 10 +++++----- arch/arm/mach-imx/busfreq-imx.c | 26 ++++++++++++++------------ arch/arm/mach-imx/busfreq_ddr3.c | 4 ++-- arch/arm/mach-imx/busfreq_lpddr2.c | 3 ++- arch/arm/mach-imx/gpc.c | 20 ++++++++++---------- arch/arm/mach-imx/pm-imx6.c | 14 +++++++------- 6 files changed, 40 insertions(+), 37 deletions(-) diff --git a/arch/arm/mach-imx/anatop.c b/arch/arm/mach-imx/anatop.c index c674ab94cc3f..a9ec0159b8f0 100644 --- a/arch/arm/mach-imx/anatop.c +++ b/arch/arm/mach-imx/anatop.c @@ -63,8 +63,8 @@ static void imx_anatop_enable_weak2p5(bool enable) regmap_read(anatop, ANADIG_ANA_MISC0, &val); - if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() - || cpu_is_imx6sll()) + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6ulz() || cpu_is_imx6sll()) mask = BM_ANADIG_ANA_MISC0_V3_STOP_MODE_CONFIG; else if (cpu_is_imx6sl()) mask = BM_ANADIG_ANA_MISC0_V2_STOP_MODE_CONFIG; @@ -92,7 +92,7 @@ static inline void imx_anatop_enable_2p5_pulldown(bool enable) static inline void imx_anatop_disconnect_high_snvs(bool enable) { if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || - cpu_is_imx6sll()) + cpu_is_imx6ulz() || cpu_is_imx6sll()) regmap_write(anatop, ANADIG_ANA_MISC0 + (enable ? REG_SET : REG_CLR), BM_ANADIG_ANA_MISC0_V2_DISCON_HIGH_SNVS); @@ -147,7 +147,7 @@ void imx_anatop_pre_suspend(void) imx_anatop_enable_fet_odrive(true); if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() || - cpu_is_imx6ull() || cpu_is_imx6sll()) + cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) imx_anatop_disconnect_high_snvs(true); } @@ -172,7 +172,7 @@ void imx_anatop_post_resume(void) imx_anatop_enable_fet_odrive(false); if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() || - cpu_is_imx6ull() || cpu_is_imx6sll()) + cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) imx_anatop_disconnect_high_snvs(false); } diff --git a/arch/arm/mach-imx/busfreq-imx.c b/arch/arm/mach-imx/busfreq-imx.c index 57fd6de9cf5a..1efaef308ebe 100644 --- a/arch/arm/mach-imx/busfreq-imx.c +++ b/arch/arm/mach-imx/busfreq-imx.c @@ -258,7 +258,7 @@ static void enter_lpm_imx6_up(void) clk_set_rate(mmdc_clk, HIGH_AUDIO_CLK); } - if ((cpu_is_imx6ull() | cpu_is_imx6sll()) && low_bus_freq_mode) + if ((cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) && low_bus_freq_mode) imx6ull_lower_cpu_rate(false); audio_bus_freq_mode = 1; @@ -273,7 +273,7 @@ static void enter_lpm_imx6_up(void) if (audio_bus_freq_mode) clk_disable_unprepare(pll2_400_clk); - if (cpu_is_imx6ull() | cpu_is_imx6sll()) + if (cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) imx6ull_lower_cpu_rate(true); low_bus_freq_mode = 1; @@ -337,7 +337,7 @@ static void enter_lpm_imx6_smp(void) static void exit_lpm_imx6_up(void) { - if ((cpu_is_imx6ull() | cpu_is_imx6sll()) && low_bus_freq_mode) + if ((cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) && low_bus_freq_mode) imx6ull_lower_cpu_rate(false); clk_prepare_enable(pll2_400_clk); @@ -346,14 +346,15 @@ static void exit_lpm_imx6_up(void) * lower ahb/ocram's freq first to avoid too high * freq during parent switch from OSC to pll3. */ - if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) + if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz() + || cpu_is_imx6sll()) clk_set_rate(ahb_clk, LPAPM_CLK / 4); else clk_set_rate(ahb_clk, LPAPM_CLK / 3); clk_set_rate(ocram_clk, LPAPM_CLK / 2); /* set periph clk to from pll2_bus on i.MX6UL */ - if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) + if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) clk_set_parent(periph_pre_clk, pll2_bus_clk); /* set periph clk to from pll2_400 */ else @@ -675,7 +676,7 @@ static void reduce_bus_freq(void) if (cpu_is_imx7d()) enter_lpm_imx7d(); else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || - cpu_is_imx6sll()) + cpu_is_imx6ulz() || cpu_is_imx6sll()) enter_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) enter_lpm_imx6_smp(); @@ -780,7 +781,7 @@ static int set_high_bus_freq(int high_bus_freq) if (cpu_is_imx7d()) exit_lpm_imx7d(); else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || - cpu_is_imx6sll()) + cpu_is_imx6ulz() || cpu_is_imx6sll()) exit_lpm_imx6_up(); else if (cpu_is_imx6q() || cpu_is_imx6dl()) exit_lpm_imx6_smp(); @@ -1138,8 +1139,8 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx() || cpu_is_imx6sl() || cpu_is_imx6ul() || cpu_is_imx6ull() || - cpu_is_imx6sll()) { + if (cpu_is_imx6sx() || cpu_is_imx6sl() || cpu_is_imx6ul() || + cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) { ahb_clk = devm_clk_get(&pdev->dev, "ahb"); ocram_clk = devm_clk_get(&pdev->dev, "ocram"); periph2_clk = devm_clk_get(&pdev->dev, "periph2"); @@ -1157,7 +1158,8 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) { + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6ulz() || cpu_is_imx6sll()) { mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); if (IS_ERR(mmdc_clk)) { dev_err(busfreq_dev, @@ -1193,7 +1195,7 @@ static int busfreq_probe(struct platform_device *pdev) } } - if (cpu_is_imx6sl() || cpu_is_imx6ull() || cpu_is_imx6sll()) { + if (cpu_is_imx6sl() || cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) { arm_clk = devm_clk_get(&pdev->dev, "arm"); step_clk = devm_clk_get(&pdev->dev, "step"); pll1_clk = devm_clk_get(&pdev->dev, "pll1"); @@ -1298,7 +1300,7 @@ static int busfreq_probe(struct platform_device *pdev) } busfreq_func.init = &init_ddrc_ddr_settings; busfreq_func.update = &update_ddr_freq_imx_smp; - } else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + } else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) { ddr_type = imx_mmdc_get_ddr_type(); if (ddr_type == IMX_DDR_TYPE_DDR3) { diff --git a/arch/arm/mach-imx/busfreq_ddr3.c b/arch/arm/mach-imx/busfreq_ddr3.c index 24ce49d2d540..0088a5d80736 100644 --- a/arch/arm/mach-imx/busfreq_ddr3.c +++ b/arch/arm/mach-imx/busfreq_ddr3.c @@ -540,7 +540,7 @@ int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev) + normal_mmdc_settings[i][0]); } - if (cpu_is_imx6ul() || cpu_is_imx6ull()) + if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz()) iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6ul); else iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6sx); @@ -566,7 +566,7 @@ int init_mmdc_ddr3_settings_imx6_up(struct platform_device *busfreq_pdev) } for (i = 0; i < iomux_settings_size; i++) { - if (cpu_is_imx6ul() || cpu_is_imx6ull()) { + if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz()) { iomux_offsets_mx6ul[i][1] = readl_relaxed(iomux_base + iomux_offsets_mx6ul[i][0]); diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c index dc6d4ce9b685..f5b3caa43645 100644 --- a/arch/arm/mach-imx/busfreq_lpddr2.c +++ b/arch/arm/mach-imx/busfreq_lpddr2.c @@ -167,7 +167,8 @@ int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &mx6_lpddr2_freq_change, ddr_code_size); - if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6ulz()) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &imx6_up_lpddr2_freq_change, ddr_code_size); diff --git a/arch/arm/mach-imx/gpc.c b/arch/arm/mach-imx/gpc.c index 5007a3b96c6f..838d93381c07 100644 --- a/arch/arm/mach-imx/gpc.c +++ b/arch/arm/mach-imx/gpc.c @@ -115,8 +115,8 @@ unsigned int imx_gpc_is_m4_sleeping(void) bool imx_gpc_usb_wakeup_enabled(void) { - if (!(cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() - || cpu_is_imx6sll())) + if (!(cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6ulz() || cpu_is_imx6sll())) return false; /* @@ -196,8 +196,8 @@ void imx_gpc_pre_suspend(bool arm_power_off) int i; /* power down the mega-fast power domain */ - if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() - || cpu_is_imx6sll()) && arm_power_off) + if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6ulz() || cpu_is_imx6sll()) && arm_power_off) imx_gpc_mf_mix_off(); /* Tell GPC to power off ARM core when suspend */ @@ -218,8 +218,8 @@ void imx_gpc_post_resume(void) /* Keep ARM core powered on for other low-power modes */ imx_gpc_set_arm_power_in_lpm(false); /* Keep M/F mix powered on for other low-power modes */ - if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() - || cpu_is_imx6sll()) + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6ulz() || cpu_is_imx6sll()) writel_relaxed(0x0, gpc_base + GPC_PGC_MF_PDN); for (i = 0; i < IMR_NUM; i++) @@ -386,8 +386,8 @@ int imx_gpc_mf_power_on(unsigned int irq, unsigned int on) int imx_gpc_mf_request_on(unsigned int irq, unsigned int on) { - if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() - || cpu_is_imx6sll()) + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6ulz() || cpu_is_imx6sll()) return imx_gpc_mf_power_on(irq, on); else if (cpu_is_imx7d()) return imx_gpcv2_mf_power_on(irq, on); @@ -469,8 +469,8 @@ static int __init imx_gpc_init(struct device_node *node, writel_relaxed(~0, gpc_base + GPC_IMR1 + i * 4); /* Read supported wakeup source in M/F domain */ - if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() - || cpu_is_imx6sll()) { + if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || + cpu_is_imx6ulz() || cpu_is_imx6sll()) { of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 0, &gpc_mf_irqs[0]); of_property_read_u32_index(node, "fsl,mf-mix-wakeup-irq", 1, diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c index 6681973ee9b5..9f44f0fa121f 100644 --- a/arch/arm/mach-imx/pm-imx6.c +++ b/arch/arm/mach-imx/pm-imx6.c @@ -680,7 +680,7 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode) if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6sll()) val |= BM_CLPCR_BYPASS_PMIC_READY; if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() || - cpu_is_imx6ull() || cpu_is_imx6sll()) + cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS; else if (cpu_is_imx6q() && imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2 && @@ -705,7 +705,7 @@ int imx6_set_lpm(enum mxc_cpu_pwr_mode mode) if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6sll()) val |= BM_CLPCR_BYPASS_PMIC_READY; if (cpu_is_imx6sl() || cpu_is_imx6sx() || cpu_is_imx6ul() || - cpu_is_imx6ull() || cpu_is_imx6sll()) + cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS; else if (cpu_is_imx6q() && imx_mmdc_get_ddr_type() == IMX_DDR_TYPE_LPDDR2 && @@ -887,7 +887,7 @@ static int imx6q_pm_enter(suspend_state_t state) imx6_enable_rbc(true); imx_gpc_pre_suspend(true); imx_anatop_pre_suspend(); - if ((cpu_is_imx6ull() || cpu_is_imx6sll()) && + if ((cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) && imx_gpc_is_mf_mix_off()) imx6_console_save(console_saved_reg); if (cpu_is_imx6sx() && imx_gpc_is_mf_mix_off()) { @@ -928,7 +928,7 @@ static int imx6q_pm_enter(suspend_state_t state) sizeof(qspi_regs_imx6sx) / sizeof(struct qspi_regs)); } - if ((cpu_is_imx6ull() || cpu_is_imx6sll()) && + if ((cpu_is_imx6ull() || cpu_is_imx6ulz() || cpu_is_imx6sll()) && imx_gpc_is_mf_mix_off()) imx6_console_restore(console_saved_reg); if (cpu_is_imx6q() || cpu_is_imx6dl()) @@ -1193,7 +1193,7 @@ static int __init imx6q_suspend_init(const struct imx6_pm_socdata *socdata) } /* need to overwrite the value for some mmdc registers */ - if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) && + if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz()) && pm_info->ddr_type != IMX_DDR_TYPE_LPDDR2) { pm_info->mmdc_val[20][1] = (pm_info->mmdc_val[20][1] & 0xffff0000) | 0x0202; @@ -1212,7 +1212,7 @@ static int __init imx6q_suspend_init(const struct imx6_pm_socdata *socdata) pm_info->mmdc_val[32][1] = 0xa1310003; } - if ((cpu_is_imx6ul() || cpu_is_imx6ull()) && + if ((cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6ulz()) && pm_info->ddr_type == IMX_DDR_TYPE_LPDDR2) { pm_info->mmdc_val[0][1] = 0x8000; pm_info->mmdc_val[2][1] = 0xa1390003; @@ -1385,7 +1385,7 @@ void __init imx6ul_pm_init(void) else imx6_pm_common_init(&imx6ul_pm_data); - if (cpu_is_imx6ull()) { + if (cpu_is_imx6ull() || cpu_is_imx6ulz()) { np = of_find_node_by_path( "/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000"); if (np) From 70d28fd884b9c6bd9b807c928455309106a7f934 Mon Sep 17 00:00:00 2001 From: Leonard Crestez Date: Wed, 27 Nov 2019 01:17:53 +0000 Subject: [PATCH 81/81] MLK-21599-1 arm64: Kconfig: Make FORCE_MAX_ZONEORDER configurable Older imx releases increased this based on SOC_IMX8 but that symbol is gone and the expectation is that SOC-selection symbols like "ARCH_MXC" will almost always be defined and shouldn't be used to make incompatible config decisions. Make the value of FORCE_MAX_ZONEORDER configurable so this can be adjusted in a .config file. Signed-off-by: Leonard Crestez Reviewed-by: Li Yang Signed-off-by: Dong Aisheng --- arch/arm64/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 3f047afb982c..178a2ed79dec 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1046,7 +1046,7 @@ config XEN Say Y if you want to run Linux in a Virtual Machine on Xen on ARM64. config FORCE_MAX_ZONEORDER - int + int "Maximum zone order" default "14" if (ARM64_64K_PAGES && TRANSPARENT_HUGEPAGE) default "12" if (ARM64_16K_PAGES && TRANSPARENT_HUGEPAGE) default "11"