summaryrefslogtreecommitdiff
path: root/arch
diff options
context:
space:
mode:
authorAnson Huang <b20788@freescale.com>2015-08-10 13:42:57 +0800
committerLeonard Crestez <leonard.crestez@nxp.com>2018-08-23 16:56:55 +0300
commit1ee80880db1ced73f48e3174fe8b75229ceb4066 (patch)
tree1fbd5de53be46aac693ee7159de1c33aefe506d0 /arch
parent5114015378d82df2756db3cec989cc5c047d7359 (diff)
MLK-11338-2 ARM: imx: add busfreq support for imx7d sdb board
This patch adds busfreq support for i.MX7D SDB board with DDR3 memory, 3 setpoints supported: HIGH: DRAM CLK = 533MHz, AXI = 332MHz, AHB = 135MHz; AUDIO: DRAM CLK = 100MHz; AXI = 24MHz, AHB = 24MHz; LOW: DRAM CLK = 24MHz; AXI = 24MHz, AHB = 24MHz; Signed-off-by: Anson Huang <b20788@freescale.com>
Diffstat (limited to 'arch')
-rw-r--r--arch/arm/mach-imx/Makefile4
-rw-r--r--arch/arm/mach-imx/busfreq-imx.c689
-rw-r--r--arch/arm/mach-imx/busfreq_ddr3.c278
-rw-r--r--arch/arm/mach-imx/common.h1
-rw-r--r--arch/arm/mach-imx/ddr3_freq_imx7d.S532
-rw-r--r--arch/arm/mach-imx/mach-imx7d.c5
-rw-r--r--arch/arm/mach-imx/pm-imx6.c21
-rw-r--r--arch/arm/mach-imx/smp_wfe.S110
8 files changed, 1637 insertions, 3 deletions
diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
index e44085605692..00dc95708503 100644
--- a/arch/arm/mach-imx/Makefile
+++ b/arch/arm/mach-imx/Makefile
@@ -91,7 +91,9 @@ obj-$(CONFIG_SOC_IMX7D) += suspend-imx7.o
obj-$(CONFIG_SOC_IMX53) += suspend-imx53.o
endif
obj-$(CONFIG_SOC_IMX6) += pm-imx6.o
-obj-$(CONFIG_SOC_IMX7D) += pm-imx7.o
+AFLAGS_smp_wfe.o :=-Wa,-march=armv7-a
+AFLAGS_ddr3_freq_imx7d.o :=-Wa,-march=armv7-a
+obj-$(CONFIG_SOC_IMX7D) += pm-imx7.o busfreq-imx.o busfreq_ddr3.o ddr3_freq_imx7d.o smp_wfe.o
obj-$(CONFIG_SOC_IMX1) += mach-imx1.o
obj-$(CONFIG_SOC_IMX50) += mach-imx50.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..bd8a97554377
--- /dev/null
+++ b/arch/arm/mach-imx/busfreq-imx.c
@@ -0,0 +1,689 @@
+/*
+ * 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 <asm/cacheflush.h>
+#include <asm/fncpy.h>
+#include <asm/io.h>
+#include <asm/mach/map.h>
+#include <asm/mach-types.h>
+#include <asm/tlb.h>
+#include <linux/busfreq-imx.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_fdt.h>
+#include <linux/platform_device.h>
+#include <linux/proc_fs.h>
+#include <linux/reboot.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sched.h>
+#include <linux/suspend.h>
+#include "hardware.h"
+#include "common.h"
+
+#define LPAPM_CLK 24000000
+#define LOW_AUDIO_CLK 50000000
+#define HIGH_AUDIO_CLK 100000000
+
+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 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 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;
+
+extern int init_ddrc_ddr_settings(struct platform_device *dev);
+extern int update_ddr_freq_imx_smp(int ddr_rate);
+extern int update_lpddr2_freq(int ddr_rate);
+
+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 int busfreq_notify(enum busfreq_event event)
+{
+ int ret;
+
+ 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)
+{
+ if (audio_bus_count) {
+ clk_prepare_enable(pfd0_392m);
+ update_ddr_freq_imx_smp(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 {
+ update_ddr_freq_imx_smp(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;
+ }
+}
+
+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);
+
+ update_ddr_freq_imx_smp(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 void reduce_bus_freq_handler(struct work_struct *work)
+{
+ mutex_lock(&bus_freq_mutex);
+
+ reduce_bus_freq();
+
+ 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.
+ */
+int set_low_bus_freq(void)
+{
+ if (busfreq_suspended)
+ return 0;
+
+ if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active)
+ return 0;
+
+ /*
+ * 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_delayed_work_sync(&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_delayed_work_sync(&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(10000));
+
+ /*
+ * 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())
+ err = init_ddrc_ddr_settings(pdev);
+
+ 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;
+
+ printk(KERN_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..5d0ea9ec7980
--- /dev/null
+++ b/arch/arm/mach-imx/busfreq_ddr3.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file busfreq_ddr3.c
+ *
+ * @brief iMX6 DDR3 frequency change specific file.
+ *
+ * @ingroup PM
+ */
+#include <asm/cacheflush.h>
+#include <asm/fncpy.h>
+#include <asm/io.h>
+#include <asm/mach/map.h>
+#include <asm/mach-types.h>
+#include <asm/tlb.h>
+#include <linux/busfreq-imx.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/cpumask.h>
+#include <linux/delay.h>
+#include <linux/genalloc.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip/arm-gic.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/proc_fs.h>
+#include <linux/sched.h>
+#include <linux/smp.h>
+
+#include "hardware.h"
+#include "common.h"
+
+/* DDR settings */
+static void __iomem *gic_dist_base;
+
+static int curr_ddr_rate;
+
+#define SMP_WFE_CODE_SIZE 0x400
+void (*imx7d_change_ddr_freq)(u32 freq) = NULL;
+extern void imx7d_ddr3_freq_change(u32 freq);
+
+extern unsigned int ddr_med_rate;
+extern unsigned int ddr_normal_rate;
+extern int low_bus_freq_mode;
+extern int audio_bus_freq_mode;
+extern void mx6_ddr3_freq_change(u32 freq, void *ddr_settings,
+ bool dll_mode, void *iomux_offsets);
+
+extern unsigned long save_ttbr1(void);
+extern void restore_ttbr1(unsigned long ttbr1);
+extern unsigned long ddr_freq_change_iram_base;
+
+extern unsigned long ddr_freq_change_total_size;
+extern unsigned long iram_tlb_phys_addr;
+
+#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_ddr3_freq_change(u32 cpuid, u32 *ddr_freq_change_done);
+extern void imx7_smp_wfe(u32 cpuid, u32 ocram_base);
+extern unsigned long wfe_ddr3_freq_change_start
+ asm("wfe_ddr3_freq_change_start");
+extern unsigned long wfe_ddr3_freq_change_end asm("wfe_ddr3_freq_change_end");
+#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);
+
+#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);
+
+ curr_ddr_rate = ddr_normal_rate;
+
+ return 0;
+}
diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h
index a790b97925be..c3f67f14bbb8 100644
--- a/arch/arm/mach-imx/common.h
+++ b/arch/arm/mach-imx/common.h
@@ -118,6 +118,7 @@ int imx_mmdc_get_ddr_type(void);
int imx_ddrc_get_ddr_type(void);
void imx_cpu_die(unsigned int cpu);
int imx_cpu_kill(unsigned int cpu);
+void imx_busfreq_map_io(void);
#ifdef CONFIG_SUSPEND
void v7_cpu_resume(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..ab82047298e7
--- /dev/null
+++ b/arch/arm/mach-imx/ddr3_freq_imx7d.S
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/linkage.h>
+#include "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_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_DSKEW_CON0 0x7c
+#define DDRPHY_CA_DSKEW_CON1 0x80
+#define DDRPHY_CA_DSKEW_CON2 0x84
+
+ .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
+ bne 7f
+
+ /* 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, =0x0
+ str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
+ str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
+ str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
+
+ 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]
+
+ .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]
+
+ /* dram root set to from dram main, div by 2 */
+ ldr r7, =0x10000001
+ ldr r8, =0x9880
+ str r7, [r2, r8]
+
+ ldr r7, =0x02020070
+ 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, =0x0
+ str r7, [r5, #DDRPHY_CA_DSKEW_CON0]
+ str r7, [r5, #DDRPHY_CA_DSKEW_CON1]
+ str r7, [r5, #DDRPHY_CA_DSKEW_CON2]
+
+ 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]
+
+ .endm
+
+ENTRY(imx7d_ddr3_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, =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 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 - r8}
+ mov pc, lr
+ .ltorg
+ENDPROC(imx7d_ddr3_freq_change)
diff --git a/arch/arm/mach-imx/mach-imx7d.c b/arch/arm/mach-imx/mach-imx7d.c
index c3db085df6b9..e3a33f15ad3e 100644
--- a/arch/arm/mach-imx/mach-imx7d.c
+++ b/arch/arm/mach-imx/mach-imx7d.c
@@ -115,8 +115,9 @@ static void __init imx7d_init_late(void)
static void __init imx7d_map_io(void)
{
- debug_ll_io_init();
- imx7_pm_map_io();
+ debug_ll_io_init();
+ imx7_pm_map_io();
+ imx_busfreq_map_io();
}
static const char *const imx7d_dt_compat[] __initconst = {
diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c
index e287a9470bd4..e0f59360bd9e 100644
--- a/arch/arm/mach-imx/pm-imx6.c
+++ b/arch/arm/mach-imx/pm-imx6.c
@@ -255,6 +255,27 @@ struct imx6_cpu_pm_info {
u32 mmdc_io_val[MX6_MAX_MMDC_IO_NUM][2]; /* To save offset and value */
} __aligned(8);
+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)
+ );
+}
+
void imx6_set_int_mem_clk_lpm(bool enable)
{
u32 val = readl_relaxed(ccm_base + CGPR);
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 <linux/linkage.h>
+#include <asm/smp_scu.h>
+#include "hardware.h"
+
+ .macro disable_l1_dcache
+
+ /*
+ * Flush all data from the L1 data cache before disabling
+ * SCTLR.C bit.
+ */
+ push {r0 - r10, lr}
+ ldr r7, =v7_flush_dcache_all
+ mov lr, pc
+ mov pc, r7
+ pop {r0 - r10, lr}
+
+ /* disable d-cache */
+ mrc p15, 0, r7, c1, c0, 0
+ bic r7, r7, #(1 << 2)
+ mcr p15, 0, r7, c1, c0, 0
+ dsb
+ isb
+
+ push {r0 - r10, lr}
+ ldr r7, =v7_flush_dcache_all
+ mov lr, pc
+ mov pc, r7
+ pop {r0 - r10, lr}
+
+ .endm
+
+#ifdef CONFIG_SMP
+ .align 3
+
+ENTRY(imx7_smp_wfe)
+ push {r4 - r11, lr}
+
+ dsb
+ isb
+
+ disable_l1_dcache
+
+ isb
+
+ /* Turn off SMP bit. */
+ mrc p15, 0, r8, c1, c0, 1
+ bic r8, r8, #0x40
+ mcr p15, 0, r8, c1, c0, 1
+
+ isb
+ /* Set flag of entering WFE. */
+ mov r7, #0xff
+ lsl r7, r7, r0
+ mov r6, #SCU_PM_DORMANT
+ lsl r6, r6, r0
+ ldr r8, [r1, #0x4]
+ bic r8, r8, r7
+ orr r6, r6, r8
+ str r6, [r1, #0x4]
+
+go_back_wfe:
+ wfe
+
+ /* Offset 0x0 stores busfeq done flag */
+ ldr r6, [r1]
+ cmp r6, #1
+ beq go_back_wfe
+
+ /* Turn ON SMP bit. */
+ mrc p15, 0, r8, c1, c0, 1
+ orr r8, r8, #0x40
+ mcr p15, 0, r8, c1, c0, 1
+
+ isb
+ /* Enable L1 data cache. */
+ mrc p15, 0, r8, c1, c0, 0
+ orr r8, r8, #0x4
+ mcr p15, 0, r8, c1, c0, 0
+ isb
+
+ /* Set flag of exiting WFE. */
+ mov r7, #0xff
+ lsl r7, r7, r0
+ mov r6, #SCU_PM_NORMAL
+ lsl r6, r6, r0
+ ldr r8, [r1, #0x4]
+ bic r8, r8, r7
+ orr r6, r6, r8
+ str r6, [r1, #0x4]
+
+ /* Pop all saved registers. */
+ pop {r4 - r11, lr}
+ mov pc, lr
+ .ltorg
+ENDPROC(imx7_smp_wfe)
+#endif