]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00275974-1 [iMX6DQ/iMX6DL] Add busfreq support
authorRanjani Vaidyanathan <ra5478@freescale.com>
Tue, 20 Aug 2013 19:30:16 +0000 (14:30 -0500)
committerNitin Garg <nitin.garg@freescale.com>
Fri, 16 Jan 2015 03:16:20 +0000 (21:16 -0600)
Add support to drop DDR and AHB frequency to 24MHz in
system IDLE state.

Signed-off-by: Ranjani Vaidyanathan <ra5478@freescale.com>
[shawn.guo: cherry-pick commit 7091e3d1c771 from imx_3.10.y]
Signed-off-by: Shawn Guo <shawn.guo@freescale.com>
Documentation/devicetree/bindings/arm/imx/busfreq-imx6.txt [new file with mode: 0644]
arch/arm/mach-imx/Makefile
arch/arm/mach-imx/busfreq-imx6.c [new file with mode: 0644]
arch/arm/mach-imx/busfreq_ddr3.c [new file with mode: 0644]
arch/arm/mach-imx/ddr3_freq_imx6.S [new file with mode: 0644]
include/linux/busfreq-imx6.h [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/arm/imx/busfreq-imx6.txt b/Documentation/devicetree/bindings/arm/imx/busfreq-imx6.txt
new file mode 100644 (file)
index 0000000..93386a8
--- /dev/null
@@ -0,0 +1,64 @@
+Freescale Busfreq driver
+
+It is a generic driver that manages the frequency of the DDR, AHB and AXI buses in the iMX6x architecture.
+It works for both SMP and UP systems and for both DDR3 and LPDDR2 memory types.
+
+Required properties are listed below:
+- compatible: should be "fsl,imx6_busfreq"
+- clocks: Lists the various clocks used by the busfreq driver
+- interrupts - Lists the interrupts used by the busfreq driver. This is needed only for SMP architecutre.
+- fsl,max_ddr_freq - The max ddr freq for this chip
+
+Examples:
+For SOC imx6q.dtsi:
+       busfreq { /* BUSFREQ */
+               compatible = "fsl,imx6_busfreq";
+               clocks = <&clks 171>, <&clks 6>, <&clks 11>, <&clks 104>, <&clks 172>, <&clks 58>,
+                       <&clks 18>, <&clks 60>, <&clks 20>, <&clks 3>;
+               clock-names = "pll2_bus", "pll2_pfd2_396m", "pll2_198m", "arm", "pll3_usb_otg", "periph",
+                       "periph_pre", "periph_clk2", "periph_clk2_sel", "osc";
+               interrupts = <0 107 0x04>, <0 112 0x4>, <0 113 0x4>, <0 114 0x4>;
+               interrupt-names = "irq_busfreq_0", "irq_busfreq_1", "irq_busfreq_2", "irq_busfreq_3";
+               fsl,max_ddr_freq = <528000000>;
+       };
+
+The Freescale Busfreq driver supports the following setpoints for the DDR freq:
+enum bus_freq_mode {
+       BUS_FREQ_HIGH, -> The max freq the SOC supports
+       BUS_FREQ_MED,  -> Medium setpoint (ex 400MHz for DDR3 when the max is 528MHz)
+       BUS_FREQ_AUDIO, -> Audio playback freq (50MHz)
+       BUS_FREQ_LOW,  -> Low power IDLE freq (24MHz)
+};
+
+Currently the Freescale Busfreq driver implementation requires drivers to call the following APIs:
+1. request_bus_freq(enum bus_freq_mode):
+       The driver is requesting the system and ddr freq to be set to the requested value. The driver should call this
+       API before it even enables its clocks.
+
+2. release_bus_freq(enum bus_freq_mode):
+       The driver no longer needs the system and ddr freq at the required value. The driver should call this API after
+       its work is done and it has disabled its clocks.
+
+Examples:
+In the IPU driver, the requesting and releasing of the required bus frequency is tied into the runtime PM implementation:
+
+int ipu_runtime_suspend(struct device *dev)
+{
+       release_bus_freq(BUS_FREQ_HIGH);
+       dev_dbg(dev, "ipu busfreq high release.\n");
+
+       return 0;
+}
+
+int ipu_runtime_resume(struct device *dev)
+{
+       request_bus_freq(BUS_FREQ_HIGH);
+       dev_dbg(dev, "ipu busfreq high requst.\n");
+
+       return 0;
+}
+
+static const struct dev_pm_ops ipu_pm_ops = {
+       SET_RUNTIME_PM_OPS(ipu_runtime_suspend, ipu_runtime_resume, NULL)
+       SET_SYSTEM_SLEEP_PM_OPS(ipu_suspend, ipu_resume)
+};
index f4ed83032dd0fce3f9e4faada7216a0c95d2226b..fdad9de6651d5697a8ef7be0ba47c405df95ed25 100644 (file)
@@ -108,6 +108,12 @@ obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o
 endif
 obj-$(CONFIG_SOC_IMX6) += pm-imx6.o
 
+ifeq ($(CONFIG_ARM_IMX6Q_CPUFREQ),y)
+obj-y += busfreq-imx6.o
+obj-$(CONFIG_SOC_IMX6Q) += ddr3_freq_imx6.o busfreq_ddr3.o
+endif
+
+
 # i.MX5 based machines
 obj-$(CONFIG_MACH_MX51_BABBAGE) += mach-mx51_babbage.o
 obj-$(CONFIG_MACH_EUKREA_CPUIMX51SD) += mach-cpuimx51sd.o
diff --git a/arch/arm/mach-imx/busfreq-imx6.c b/arch/arm/mach-imx/busfreq-imx6.c
new file mode 100644 (file)
index 0000000..c7a7cce
--- /dev/null
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2011-2013 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.
+ */
+
+/*!
+ * @file busfreq-imx6.c
+ *
+ * @brief A common API for the Freescale Semiconductor iMX6 Busfreq API
+ *
+ * The APIs are for setting bus frequency to different values based on the
+ * highest freqeuncy requested.
+ *
+ * @ingroup PM
+ */
+
+#include <asm/cacheflush.h>
+#include <asm/io.h>
+#include <asm/mach/map.h>
+#include <asm/mach-types.h>
+#include <asm/tlb.h>
+#include <linux/busfreq-imx6.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/platform_device.h>
+#include <linux/proc_fs.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sched.h>
+#include <linux/suspend.h>
+#include "hardware.h"
+
+#define LPAPM_CLK              24000000
+#define DDR_AUDIO_CLK          50000000
+
+int high_bus_freq_mode;
+int med_bus_freq_mode;
+int audio_bus_freq_mode;
+int low_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;
+static unsigned int ddr_low_rate;
+unsigned int ddr_med_rate;
+unsigned int ddr_normal_rate;
+
+extern int init_mmdc_settings(struct platform_device *dev);
+extern int update_ddr_freq(int ddr_rate);
+
+DEFINE_MUTEX(bus_freq_mutex);
+
+static struct clk *pll2_400;
+static struct clk *periph_clk;
+static struct clk *periph_pre_clk;
+static struct clk *periph_clk2_sel;
+static struct clk *periph_clk2;
+static struct clk *osc_clk;
+static struct clk *cpu_clk;
+static struct clk *pll3;
+static struct clk *pll2;
+static struct clk *pll2_200;
+
+static struct delayed_work low_bus_freq_handler;
+static struct delayed_work bus_freq_daemon;
+
+int low_bus_freq;
+
+int reduce_bus_freq(void)
+{
+       int ret = 0;
+       clk_prepare_enable(pll3);
+       if (low_bus_freq) {
+               /* Need to ensure that PLL2_PFD_400M is kept ON. */
+               clk_prepare_enable(pll2_400);
+               update_ddr_freq(DDR_AUDIO_CLK);
+               /* Make sure periph clk's parent also got updated */
+               ret = clk_set_parent(periph_clk2_sel, pll3);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               ret = clk_set_parent(periph_pre_clk, pll2_200);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               ret = clk_set_parent(periph_clk, periph_pre_clk);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               audio_bus_freq_mode = 1;
+               low_bus_freq_mode = 0;
+       } else {
+               update_ddr_freq(LPAPM_CLK);
+               /* Make sure periph clk's parent also got updated */
+               ret = clk_set_parent(periph_clk2_sel, osc_clk);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               ret = clk_set_parent(periph_clk, periph_clk2);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               if (audio_bus_freq_mode)
+                       clk_disable_unprepare(pll2_400);
+               low_bus_freq_mode = 1;
+               audio_bus_freq_mode = 0;
+       }
+       if (high_bus_freq_mode && cpu_is_imx6dl())
+               clk_disable_unprepare(pll2_400);
+
+       clk_disable_unprepare(pll3);
+       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);
+
+       return ret;
+}
+
+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(int low_bus_mode)
+{
+       if (busfreq_suspended)
+               return 0;
+
+       if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active)
+               return 0;
+
+       /*
+         * Don't lower the frequency immediately. Instead
+         * scheduled a delayed work and drop the freq if
+         * the conditions still remain the same.
+         */
+       low_bus_freq = low_bus_mode;
+       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.
+  */
+int set_high_bus_freq(int high_bus_freq)
+{
+       int ret = 0;
+
+       if (bus_freq_scaling_initialized && bus_freq_scaling_is_active)
+               cancel_delayed_work_sync(&low_bus_freq_handler);
+
+       if (busfreq_suspended)
+               return 0;
+
+       /* for high setpoint, i.MX6Q is 528MHz, i.MX6DL is 400MHz */
+       if (cpu_is_imx6q())
+               high_bus_freq = 1;
+       else
+               high_bus_freq = 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;
+
+       clk_prepare_enable(pll3);
+       if (high_bus_freq) {
+               update_ddr_freq(ddr_normal_rate);
+               /* Make sure periph clk's parent also got updated */
+               ret = clk_set_parent(periph_clk2_sel, pll3);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               ret = clk_set_parent(periph_pre_clk, pll2);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               ret = clk_set_parent(periph_clk, periph_pre_clk);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               if (med_bus_freq_mode)
+                       clk_disable_unprepare(pll2_400);
+       } else {
+               clk_prepare_enable(pll2_400);
+               update_ddr_freq(ddr_med_rate);
+               /* Make sure periph clk's parent also got updated */
+               ret = clk_set_parent(periph_clk2_sel, pll3);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               ret = clk_set_parent(periph_pre_clk, pll2_400);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+               ret = clk_set_parent(periph_clk, periph_pre_clk);
+               if (ret)
+                       dev_WARN(busfreq_dev, "%s: %d: clk set parent fail!\n",
+                               __func__, __LINE__);
+       }
+       if (audio_bus_freq_mode)
+               clk_disable_unprepare(pll2_400);
+
+       high_bus_freq_mode = 1;
+       med_bus_freq_mode = 0;
+       low_bus_freq_mode = 0;
+       audio_bus_freq_mode = 0;
+
+       clk_disable_unprepare(pll3);
+
+       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_HIGH)
+               high_bus_count++;
+       else if (mode == BUS_FREQ_MED)
+               med_bus_count++;
+       else if (mode == BUS_FREQ_AUDIO)
+               audio_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(1);
+               mutex_unlock(&bus_freq_mutex);
+               return;
+       }
+       mutex_unlock(&bus_freq_mutex);
+       return;
+}
+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_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--;
+       }
+
+       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(1);
+               mutex_unlock(&bus_freq_mutex);
+               return;
+       }
+       if ((!low_bus_freq_mode) && (high_bus_count == 0) &&
+               (med_bus_count == 0) && (audio_bus_count == 0))
+               set_low_bus_freq(0);
+
+       mutex_unlock(&bus_freq_mutex);
+       return;
+}
+EXPORT_SYMBOL(release_bus_freq);
+
+static void bus_freq_daemon_handler(struct work_struct *work)
+{
+       mutex_lock(&bus_freq_mutex);
+       if ((!low_bus_freq_mode) && (high_bus_count == 0) &&
+               (med_bus_count == 0) && (audio_bus_count == 0))
+               set_low_bus_freq(0);
+       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 struct notifier_block imx_bus_freq_pm_notifier = {
+       .notifier_call = bus_freq_pm_notify,
+};
+
+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;
+
+       pll2_400 = devm_clk_get(&pdev->dev, "pll2_pfd2_396m");
+       if (IS_ERR(pll2_400)) {
+               dev_err(busfreq_dev, "%s: failed to get pll2_pfd2_396m\n",
+                      __func__);
+               return PTR_ERR(pll2_400);
+       }
+
+       pll2_200 = devm_clk_get(&pdev->dev, "pll2_198m");
+       if (IS_ERR(pll2_200)) {
+               dev_err(busfreq_dev, "%s: failed to get pll2_198m\n",
+                      __func__);
+               return PTR_ERR(pll2_200);
+       }
+
+       pll2 = devm_clk_get(&pdev->dev, "pll2_bus");
+       if (IS_ERR(pll2)) {
+               dev_err(busfreq_dev, "%s: failed to get pll2_bus\n",
+                      __func__);
+               return PTR_ERR(pll2);
+       }
+
+       cpu_clk = devm_clk_get(&pdev->dev, "arm");
+       if (IS_ERR(cpu_clk)) {
+               dev_err(busfreq_dev, "%s: failed to get cpu_clk\n",
+                      __func__);
+               return PTR_ERR(cpu_clk);
+       }
+
+       pll3 = devm_clk_get(&pdev->dev, "pll3_usb_otg");
+       if (IS_ERR(pll3)) {
+               dev_err(busfreq_dev, "%s: failed to get pll3_usb_otg\n",
+                      __func__);
+               return PTR_ERR(pll3);
+       }
+
+       periph_clk = devm_clk_get(&pdev->dev, "periph");
+       if (IS_ERR(periph_clk)) {
+               dev_err(busfreq_dev, "%s: failed to get periph\n",
+                      __func__);
+               return PTR_ERR(periph_clk);
+       }
+
+       periph_pre_clk = devm_clk_get(&pdev->dev, "periph_pre");
+       if (IS_ERR(periph_pre_clk)) {
+               dev_err(busfreq_dev, "%s: failed to get periph_pre\n",
+                      __func__);
+               return PTR_ERR(periph_pre_clk);
+       }
+
+       periph_clk2 = devm_clk_get(&pdev->dev, "periph_clk2");
+       if (IS_ERR(periph_clk2)) {
+               dev_err(busfreq_dev, "%s: failed to get periph_clk2\n",
+                      __func__);
+               return PTR_ERR(periph_clk2);
+       }
+
+       periph_clk2_sel = devm_clk_get(&pdev->dev, "periph_clk2_sel");
+       if (IS_ERR(periph_clk2_sel)) {
+               dev_err(busfreq_dev, "%s: failed to get periph_clk2_sel\n",
+                      __func__);
+               return PTR_ERR(periph_clk2_sel);
+       }
+
+       osc_clk = devm_clk_get(&pdev->dev, "osc");
+       if (IS_ERR(osc_clk)) {
+               dev_err(busfreq_dev, "%s: failed to get osc_clk\n",
+                      __func__);
+               return PTR_ERR(osc_clk);
+       }
+
+       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;
+
+       bus_freq_scaling_is_active = 1;
+       bus_freq_scaling_initialized = 1;
+
+       ddr_low_rate = LPAPM_CLK;
+       if (cpu_is_imx6q()) {
+               if (of_property_read_u32(pdev->dev.of_node, "fsl,med_ddr_freq",
+                               &ddr_med_rate)) {
+                       dev_err(busfreq_dev,
+                                       "DDR medium rate not supported.\n");
+                       ddr_med_rate = ddr_normal_rate;
+               }
+       }
+
+       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);
+
+       err = init_mmdc_settings(pdev);
+       if (err) {
+               dev_err(busfreq_dev, "Busfreq init of MMDC failed\n");
+               return err;
+       }
+       return 0;
+}
+
+static const struct of_device_id imx6_busfreq_ids[] = {
+       { .compatible = "fsl,imx6_busfreq", },
+       { /* sentinel */ }
+};
+
+static struct platform_driver busfreq_driver = {
+       .driver = {
+               .name = "imx6_busfreq",
+               .owner  = THIS_MODULE,
+               .of_match_table = imx6_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("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 (file)
index 0000000..d410f1f
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2011-2013 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/clk.h>
+#include <linux/cpumask.h>
+#include <linux/delay.h>
+#include <linux/genalloc.h>
+#include <linux/interrupt.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"
+
+/* 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 *ccm_base;
+static void __iomem *l2_base;
+static void __iomem *gic_dist_base;
+static u32 *irqs_used;
+
+void (*mx6_change_ddr_freq)(u32 freq, void *ddr_settings,
+       bool dll_mode, void *iomux_offsets) = NULL;
+
+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);
+
+static void *ddr_freq_change_iram_base;
+static int ddr_settings_size;
+static int iomux_settings_size;
+static volatile unsigned int cpus_in_wfe;
+static volatile bool wait_for_ddr_freq_update;
+static int curr_ddr_rate;
+
+#define MIN_DLL_ON_FREQ                333000000
+#define MAX_DLL_OFF_FREQ               125000000
+#define DDR_FREQ_CHANGE_SIZE   0x2000
+
+unsigned long ddr3_dll_mx6q[][2] = {
+       {0x0c, 0x0},
+       {0x10, 0x0},
+       {0x1C, 0x04088032},
+       {0x1C, 0x0408803a},
+       {0x1C, 0x08408030},
+       {0x1C, 0x08408038},
+       {0x818, 0x0},
+};
+
+unsigned long ddr3_calibration[][2] = {
+       {0x83c, 0x0},
+       {0x840, 0x0},
+       {0x483c, 0x0},
+       {0x4840, 0x0},
+       {0x848, 0x0},
+       {0x4848, 0x0},
+       {0x850, 0x0},
+       {0x4850, 0x0},
+};
+
+unsigned long ddr3_dll_mx6dl[][2] = {
+       {0x0c, 0x0},
+       {0x10, 0x0},
+       {0x1C, 0x04008032},
+       {0x1C, 0x0400803a},
+       {0x1C, 0x07208030},
+       {0x1C, 0x07208038},
+       {0x818, 0x0},
+};
+
+unsigned long iomux_offsets_mx6q[][2] = {
+       {0x5A8, 0x0},
+       {0x5B0, 0x0},
+       {0x524, 0x0},
+       {0x51C, 0x0},
+       {0x518, 0x0},
+       {0x50C, 0x0},
+       {0x5B8, 0x0},
+       {0x5C0, 0x0},
+};
+
+unsigned long iomux_offsets_mx6dl[][2] = {
+       {0x4BC, 0x0},
+       {0x4C0, 0x0},
+       {0x4C4, 0x0},
+       {0x4C8, 0x0},
+       {0x4CC, 0x0},
+       {0x4D0, 0x0},
+       {0x4D4, 0x0},
+       {0x4D8, 0x0},
+};
+
+unsigned long ddr3_400[][2] = {
+       {0x83c, 0x42490249},
+       {0x840, 0x02470247},
+       {0x483c, 0x42570257},
+       {0x4840, 0x02400240},
+       {0x848, 0x4039363C},
+       {0x4848, 0x3A39333F},
+       {0x850, 0x38414441},
+       {0x4850, 0x472D4833}
+};
+
+int can_change_ddr_freq(void)
+{
+       return 1;
+}
+
+/*
+ * 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.
+ */
+irqreturn_t wait_in_wfe_irq(int irq, void *dev_id)
+{
+       u32 me = smp_processor_id();
+
+       *((char *)(&cpus_in_wfe) + (u8)me) = 0xff;
+
+       while (wait_for_ddr_freq_update)
+               wfe();
+
+       *((char *)(&cpus_in_wfe) + (u8)me) = 0;
+
+       return IRQ_HANDLED;
+}
+
+/* change the DDR frequency. */
+int update_ddr_freq(int ddr_rate)
+{
+       int i, j;
+       unsigned int reg;
+       bool dll_off = false;
+       unsigned int online_cpus = 0;
+       int cpu = 0;
+       int me;
+
+       if (!can_change_ddr_freq())
+               return -1;
+
+       if (ddr_rate == curr_ddr_rate)
+               return 0;
+
+       printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate);
+
+       if (low_bus_freq_mode || audio_bus_freq_mode)
+               dll_off = true;
+
+       iram_ddr_settings[0][0] = ddr_settings_size;
+       iram_iomux_settings[0][0] = iomux_settings_size;
+       if (ddr_rate == ddr_med_rate && cpu_is_imx6q()) {
+               for (i = 0; i < ARRAY_SIZE(ddr3_dll_mx6q); i++) {
+                       iram_ddr_settings[i + 1][0] =
+                                       normal_mmdc_settings[i][0];
+                       iram_ddr_settings[i + 1][1] =
+                                       normal_mmdc_settings[i][1];
+               }
+               for (j = 0, i = ARRAY_SIZE(ddr3_dll_mx6q);
+                       i < iram_ddr_settings[0][0]; j++, i++) {
+                       iram_ddr_settings[i + 1][0] =
+                                       ddr3_400[j][0];
+                       iram_ddr_settings[i + 1][1] =
+                                       ddr3_400[j][1];
+               }
+       } else 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();
+
+       me = smp_processor_id();
+
+       *((char *)(&cpus_in_wfe) + (u8)me) = 0xff;
+       wait_for_ddr_freq_update = true;
+       for_each_online_cpu(cpu) {
+               *((char *)(&online_cpus) + (u8)cpu) = 0xff;
+               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);
+               }
+       }
+       while (cpus_in_wfe != online_cpus)
+               udelay(5);
+
+       /* Now we can change the DDR frequency. */
+       mx6_change_ddr_freq(ddr_rate, iram_ddr_settings,
+               dll_off, iram_iomux_settings);
+
+       curr_ddr_rate = ddr_rate;
+
+       /* DDR frequency change is done . */
+       wait_for_ddr_freq_update = false;
+
+       /* wake up all the cores. */
+       sev();
+
+       *((char *)(&cpus_in_wfe) + (u8)me) = 0;
+
+       local_irq_enable();
+
+       printk(KERN_DEBUG "Bus freq set to %d done!\n", ddr_rate);
+
+       return 0;
+}
+
+int init_mmdc_settings(struct platform_device *busfreq_pdev)
+{
+       struct device *dev = &busfreq_pdev->dev;
+       struct platform_device *ocram_dev;
+       unsigned int iram_paddr;
+       int i, err;
+       u32 cpu;
+       struct device_node *node;
+       struct gen_pool *iram_pool;
+
+       node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc-combine");
+       if (!node) {
+               printk(KERN_ERR "failed to find imx6q-mmdc device tree data!\n");
+               return -EINVAL;
+       }
+       mmdc_base = of_iomap(node, 0);
+       WARN(!mmdc_base, "unable to map mmdc registers\n");
+
+       node = NULL;
+       if (cpu_is_imx6q())
+               node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc");
+       if (cpu_is_imx6dl())
+               node = of_find_compatible_node(NULL, NULL,
+                       "fsl,imx6dl-iomuxc");
+       if (!node) {
+               printk(KERN_ERR "failed to find imx6q-iomux device tree data!\n");
+               return -EINVAL;
+       }
+       iomux_base = of_iomap(node, 0);
+       WARN(!iomux_base, "unable to map iomux registers\n");
+
+       node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ccm");
+       if (!node) {
+               printk(KERN_ERR "failed to find imx6q-ccm device tree data!\n");
+               return -EINVAL;
+       }
+       ccm_base = of_iomap(node, 0);
+       WARN(!mmdc_base, "unable to map mmdc registers\n");
+
+       node = of_find_compatible_node(NULL, NULL, "arm,pl310-cache");
+       if (!node) {
+               printk(KERN_ERR "failed to find imx6q-pl310-cache device tree data!\n");
+               return -EINVAL;
+       }
+       l2_base = of_iomap(node, 0);
+       WARN(!mmdc_base, "unable to map mmdc registers\n");
+
+       node = NULL;
+       node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-gic");
+       if (!node) {
+               printk(KERN_ERR "failed to find imx6q-a9-gic device tree data!\n");
+               return -EINVAL;
+       }
+       gic_dist_base = of_iomap(node, 0);
+       WARN(!gic_dist_base, "unable to map gic dist registers\n");
+
+       if (cpu_is_imx6q())
+               ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6q) +
+                       ARRAY_SIZE(ddr3_calibration);
+       if (cpu_is_imx6dl())
+               ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6dl) +
+                       ARRAY_SIZE(ddr3_calibration);
+
+       normal_mmdc_settings = kmalloc((ddr_settings_size * 8), GFP_KERNEL);
+       if (cpu_is_imx6q()) {
+               memcpy(normal_mmdc_settings, ddr3_dll_mx6q,
+                       sizeof(ddr3_dll_mx6q));
+               memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6q)),
+                       ddr3_calibration, sizeof(ddr3_calibration));
+       }
+       if (cpu_is_imx6dl()) {
+               memcpy(normal_mmdc_settings, ddr3_dll_mx6dl,
+                       sizeof(ddr3_dll_mx6dl));
+               memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6dl)),
+                       ddr3_calibration, sizeof(ddr3_calibration));
+       }
+       /* store the original DDR settings at boot. */
+       for (i = 0; i < ddr_settings_size; i++) {
+               /*
+                * writes via command mode register cannot be read back.
+                * hence hardcode them in the initial static array.
+                * this may require modification on a per customer basis.
+                */
+               if (normal_mmdc_settings[i][0] != 0x1C)
+                       normal_mmdc_settings[i][1] =
+                               readl_relaxed(mmdc_base
+                               + normal_mmdc_settings[i][0]);
+       }
+
+       irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(),
+                                       GFP_KERNEL);
+
+       for_each_present_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;
+               }
+               irqs_used[cpu] = irq;
+       }
+
+       node = NULL;
+       node = of_find_compatible_node(NULL, NULL, "mmio-sram");
+       if (!node) {
+               dev_err(dev, "%s: failed to find ocram node\n",
+                       __func__);
+               return -EINVAL;
+       }
+
+       ocram_dev = of_find_device_by_node(node);
+       if (!ocram_dev) {
+               dev_err(dev, "failed to find ocram device!\n");
+               return -EINVAL;
+       }
+
+       iram_pool = dev_get_gen_pool(&ocram_dev->dev);
+       if (!iram_pool) {
+               dev_err(dev, "iram pool unavailable!\n");
+               return -EINVAL;
+       }
+
+       iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6q);
+       iram_iomux_settings = gen_pool_alloc(iram_pool,
+                                               (iomux_settings_size * 8) + 8);
+       if (!iram_iomux_settings) {
+               dev_err(dev, "unable to alloc iram for IOMUX settings!\n");
+               return -ENOMEM;
+       }
+
+       /*
+         * Allocate extra space to store the number of entries in the
+         * ddr_settings plus 4 extra regsiter information that needs
+         * to be passed to the frequency change code.
+         * sizeof(iram_ddr_settings) = sizeof(ddr_settings) +
+         *                                     entries in ddr_settings + 16.
+         * The last 4 enties store the addresses of the registers:
+         * CCM_BASE_ADDR
+         * MMDC_BASE_ADDR
+         * IOMUX_BASE_ADDR
+         * L2X0_BASE_ADDR
+         */
+       iram_ddr_settings = gen_pool_alloc(iram_pool,
+                                       (ddr_settings_size * 8) + 8 + 32);
+       if (!iram_ddr_settings) {
+               dev_err(dev, "unable to alloc iram for ddr settings!\n");
+               return -ENOMEM;
+       }
+       i = ddr_settings_size + 1;
+       iram_ddr_settings[i][0] = (unsigned long)mmdc_base;
+       iram_ddr_settings[i+1][0] = (unsigned long)ccm_base;
+       iram_ddr_settings[i+2][0] = (unsigned long)iomux_base;
+       iram_ddr_settings[i+3][0] = (unsigned long)l2_base;
+
+       if (cpu_is_imx6q()) {
+               /* store the IOMUX settings at boot. */
+               for (i = 0; i < iomux_settings_size; i++) {
+                       iomux_offsets_mx6q[i][1] =
+                               readl_relaxed(iomux_base +
+                                       iomux_offsets_mx6q[i][0]);
+                       iram_iomux_settings[i+1][0] = iomux_offsets_mx6q[i][0];
+                       iram_iomux_settings[i+1][1] = iomux_offsets_mx6q[i][1];
+               }
+       }
+
+       if (cpu_is_imx6dl()) {
+               for (i = 0; i < iomux_settings_size; i++) {
+                       iomux_offsets_mx6dl[i][1] =
+                               readl_relaxed(iomux_base +
+                                       iomux_offsets_mx6dl[i][0]);
+                       iram_iomux_settings[i+1][0] = iomux_offsets_mx6dl[i][0];
+                       iram_iomux_settings[i+1][1] = iomux_offsets_mx6dl[i][1];
+               }
+       }
+
+       ddr_freq_change_iram_base = gen_pool_alloc(iram_pool,
+                                               DDR_FREQ_CHANGE_SIZE);
+       if (!ddr_freq_change_iram_base) {
+               dev_err(dev, "Cannot alloc iram for ddr freq change code!\n");
+               return -ENOMEM;
+       }
+
+       iram_paddr = gen_pool_virt_to_phys(iram_pool,
+                               (unsigned long)ddr_freq_change_iram_base);
+       /*
+        * need to remap the area here since we want
+        * the memory region to be executable.
+        */
+       ddr_freq_change_iram_base = __arm_ioremap(iram_paddr,
+                                               DDR_FREQ_CHANGE_SIZE,
+                                               MT_MEMORY_NONCACHED);
+       mx6_change_ddr_freq = (void *)fncpy(ddr_freq_change_iram_base,
+               &mx6_ddr3_freq_change, DDR_FREQ_CHANGE_SIZE);
+
+       curr_ddr_rate = ddr_normal_rate;
+
+       return 0;
+}
diff --git a/arch/arm/mach-imx/ddr3_freq_imx6.S b/arch/arm/mach-imx/ddr3_freq_imx6.S
new file mode 100644 (file)
index 0000000..e7e67ce
--- /dev/null
@@ -0,0 +1,921 @@
+/*
+ * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/linkage.h>
+
+#define MMDC0_MDPDC            0x4
+#define MMDC0_MDCF0                    0x0c
+#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 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
+
+       .align 3
+
+       .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,
+        * (need to maintain GPT divider).
+        */
+       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,
+        * (need to maintain GPT divider).
+        */
+       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
+
+       /* change the perclk divider so that its at 6MHz. */
+       ldr     r0, [r6, #CCM_CSCMR1]
+       bic     r0, r0, #0x3F
+       orr     r0, r0, #0xA
+       str     r0, [r6, #CCM_CSCMR1]
+       .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,
+        * (need to maintain GPT divider).
+        */
+       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,
+        * (need to maintain GPT divider).
+        */
+       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
+
+       /* change the perclk divider so that its at 6MHz. */
+       ldr     r0, [r6, #CCM_CSCMR1]
+       bic     r0, r0, #0x3F
+       orr     r0, r0, #0xA
+       str     r0, [r6, #CCM_CSCMR1]
+
+       .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,
+        * (need to maintain GPT divider).
+        */
+       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
+
+       /* change the perclk divider so that its at 6MHz. */
+       ldr     r0, [r6, #CCM_CSCMR1]
+       bic     r0, r0, #0x3F
+       orr     r0, r0, #0x1
+       str     r0, [r6, #CCM_CSCMR1]
+
+       .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
+
+       /* change the perclk divider so that its at 6MHz. */
+       ldr     r0, [r6, #CCM_CSCMR1]
+       bic     r0, r0, #0x3F
+       orr     r0, r0, #0x1
+       str     r0, [r6, #CCM_CSCMR1]
+
+       .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)
+
+       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
+
+       /*
+         * Get the addresses of the registers.
+         * They are last few entries in the
+         * ddr_settings parameter.
+         * The first entry contains the count,
+         * and each entry is 2 words.
+         */
+       ldr     r0, [r1]
+       add     r0, r0, #1
+       lsl     r0, r0, #3
+       add     r1, r0, r1
+       /* mmdc_base. */
+       ldr     r5, [r1]
+       add     r1, #8
+       /* ccm_base */
+       ldr     r6, [r1]
+       add     r1, #8
+       /*iomux_base */
+       ldr     r7, [r1]
+       add     r1, #8
+       /*l2_base */
+       ldr     r12, [r1]
+
+ddr_freq_change:
+       /*
+        * make sure no TLB miss will occur when
+        * the DDR is in self refresh. invalidate
+        * TLB single entry to ensure that the
+        * address is not already in the TLB.
+        */
+
+       adr     r10, ddr_freq_change
+
+       ldr     r2, [r6]
+       ldr     r2, [r5]
+       ldr     r2, [r7]
+       ldr     r2, [r8]
+       ldr     r2, [r10]
+       ldr     r2, [r11]
+       ldr     r2, [r12]
+
+#ifdef CONFIG_CACHE_L2X0
+       /*
+         * Make sure the L2 buffers are drained.
+         * Sync operation on L2 drains the buffers.
+         */
+       mov    r1, #0x0
+       str      r1, [r12, #L2_CACHE_SYNC]
+#endif
+
+       /* 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
+
+       ldr     r0, =0x00008010
+       str     r0, [r5, #MMDC0_MDSCR]
+       ldr     r0, =0x00008018
+       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, =0x00091680
+       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]
+
+       /* MMDC0_MAPSR adopt power down enable. */
+       ldr     r0, [r5, #MMDC0_MAPSR]
+       bic     r0, r0, #0x01
+       str     r0, [r5, #MMDC0_MAPSR]
+
+       /* frc_msr + mu bypass */
+       ldr     r0, =0x00000060
+       str     r0, [r5, #MMDC0_MPMUR0]
+       ldr     r2, =MMDC1_MPMUR0
+       str     r0, [r5, r2]
+       ldr     r0, =0x00000460
+       str     r0, [r5, #MMDC0_MPMUR0]
+       ldr     r2, =MMDC1_MPMUR0
+       str     r0, [r5, r2]
+       ldr     r0, =0x00000c60
+       str     r0, [r5, #MMDC0_MPMUR0]
+       ldr     r2, =MMDC1_MPMUR0
+       str     r0, [r5, r2]
+
+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
+
+       ldr     r0, =0xa5390003
+       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]
+
+       /* delay for while. */
+       ldr     r1, =4
+delay5:
+       ldr     r2, =0
+cont5:
+       ldr     r0, [r5, r2]
+       add     r2, r2, #4
+       cmp     r2, #16
+       bne     cont5
+       sub     r1, r1, #1
+       cmp     r1, #0
+       bgt     delay5
+
+       /* 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
+
+       /* update MISC register: WALAT, RALAT */
+       ldr     r0, =0x00081740
+       str     r0, [r5, #MMDC0_MDMISC]
+
+       /* configure ddr devices to dll on, odt. */
+       ldr     r0, =0x00028031
+       str     r0, [r5, #MMDC0_MDSCR]
+
+       ldr     r0, =0x00028039
+       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, =0x09208030
+       str     r0, [r5, #MMDC0_MDSCR]
+
+       ldr     r0, =0x09208038
+       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     r0, [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
+
+       /* MMDC0_MAPSR adopt power down enable. */
+       ldr     r0, [r5, #MMDC0_MAPSR]
+       bic     r0, r0, #0x01
+       str     r0, [r5, #MMDC0_MAPSR]
+
+       /* 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]
+
+       /* clear SBS - unblock DDR accesses. */
+       ldr     r0, [r5, #MMDC0_MADPCR0]
+       bic     r0, r0, #(1 << 8)
+       str     r0, [r5, #MMDC0_MADPCR0]
+
+       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:
+       /* restore registers */
+
+       ldmfd   sp!, {r4-r12}
+       mov     pc, lr
+
+       .type   mx6_do_ddr3_freq_change, #object
+ENTRY(mx6_do_ddr_freq_change)
+       .word   mx6_ddr3_freq_change
+       .size   mx6_ddr3_freq_change, . - mx6_ddr3_freq_change
diff --git a/include/linux/busfreq-imx6.h b/include/linux/busfreq-imx6.h
new file mode 100644 (file)
index 0000000..18eb0f8
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-2013 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__
+
+/*
+ * This enumerates busfreq mode.
+ */
+enum bus_freq_mode {
+       BUS_FREQ_HIGH,
+       BUS_FREQ_MED,
+       BUS_FREQ_AUDIO,
+       BUS_FREQ_LOW,
+};
+void request_bus_freq(enum bus_freq_mode mode);
+void release_bus_freq(enum bus_freq_mode mode);
+#endif