]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
MLK-9669-5 arm: imx: enable busfreq for i.mx6
authorAnson Huang <b20788@freescale.com>
Fri, 10 Oct 2014 02:34:39 +0000 (10:34 +0800)
committerNitin Garg <nitin.garg@freescale.com>
Fri, 16 Jan 2015 03:18:21 +0000 (21:18 -0600)
enable busfreq for i.mx6 SOCs, only support i.MX6Q/DL/SX
DDR3 platform, i.MX6SL and LPDDR2 will be enabled later.

As there are too many conflicts using cherry-pick, so these
files are copied from L3.10.y branch(b01578a8d466d7420cbc7cfabf984998e8e31657),
please check L3.10.y for history.

Signed-off-by: Anson Huang <b20788@freescale.com>
13 files changed:
arch/arm/mach-imx/Makefile
arch/arm/mach-imx/busfreq-imx6.c
arch/arm/mach-imx/busfreq_ddr3.c
arch/arm/mach-imx/busfreq_lpddr2.c [new file with mode: 0644]
arch/arm/mach-imx/common.h
arch/arm/mach-imx/ddr3_freq_imx6.S
arch/arm/mach-imx/ddr3_freq_imx6sx.S [new file with mode: 0644]
arch/arm/mach-imx/lpddr2_freq_imx6.S [new file with mode: 0644]
arch/arm/mach-imx/lpddr2_freq_imx6sx.S [new file with mode: 0644]
arch/arm/mach-imx/mach-imx6q.c
arch/arm/mach-imx/mach-imx6sx.c
arch/arm/mach-imx/mmdc.c
arch/arm/mach-imx/pm-imx6.c

index 9ab0bf7ebd3b5d187292fea19df39edd734d74f3..a5bc944a111e73cb61553a4448393cf8cc266903 100644 (file)
@@ -112,7 +112,13 @@ obj-$(CONFIG_SOC_IMX6) += pm-imx6.o
 
 ifeq ($(CONFIG_ARM_IMX6Q_CPUFREQ),y)
 obj-y += busfreq-imx6.o
+AFLAGS_ddr3_freq_imx6.o :=-Wa,-march=armv7-a
 obj-$(CONFIG_SOC_IMX6Q) += ddr3_freq_imx6.o busfreq_ddr3.o
+AFLAGS_lpddr2_freq_imx6.o :=-Wa,-march=armv7-a
+obj-$(CONFIG_SOC_IMX6SL) += busfreq_lpddr2.o lpddr2_freq_imx6.o
+AFLAGS_lpddr2_freq_imx6sx.o :=-Wa,-march=armv7-a
+AFLAGS_ddr3_freq_imx6sx.o :=-Wa,-march=armv7-a
+obj-$(CONFIG_SOC_IMX6SX) += ddr3_freq_imx6sx.o lpddr2_freq_imx6sx.o
 endif
 
 
index c7a7cce30315b6a3099520303dba274f52a5052c..d9b8aa26bfed28c59863869ec00088442d7bf077 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2011-2014 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
@@ -28,6 +28,7 @@
  */
 
 #include <asm/cacheflush.h>
+#include <asm/fncpy.h>
 #include <asm/io.h>
 #include <asm/mach/map.h>
 #include <asm/mach-types.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 "clk.h"
 #include "hardware.h"
+#include "common.h"
 
 #define LPAPM_CLK              24000000
-#define DDR_AUDIO_CLK          50000000
+#define DDR3_AUDIO_CLK         50000000
+#define LPDDR2_AUDIO_CLK       100000000
 
+#define        MMDC_MDMISC_DDR_TYPE_DDR3       0
+#define        MMDC_MDMISC_DDR_TYPE_LPDDR2     1
+
+static int ddr_type;
 int high_bus_freq_mode;
 int med_bus_freq_mode;
 int audio_bus_freq_mode;
 int low_bus_freq_mode;
+int ultra_low_bus_freq_mode;
+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 bus_freq_scaling_initialized;
 static struct device *busfreq_dev;
 static int busfreq_suspended;
-
+static u32 org_arm_rate;
 static int bus_freq_scaling_is_active;
-static int high_bus_count, med_bus_count, audio_bus_count;
+static int high_bus_count, med_bus_count, audio_bus_count, low_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);
+extern unsigned long iram_tlb_phys_addr;
+extern int unsigned long iram_tlb_base_addr;
+
+extern int init_mmdc_lpddr2_settings(struct platform_device *dev);
+extern int init_mmdc_ddr3_settings_imx6q(struct platform_device *dev);
+extern int init_mmdc_ddr3_settings_imx6sx(struct platform_device *dev);
+extern int update_ddr_freq_imx6q(int ddr_rate);
+extern int update_ddr_freq_imx6sx(int ddr_rate);
+extern int update_lpddr2_freq(int ddr_rate);
 
 DEFINE_MUTEX(bus_freq_mutex);
 
+static struct clk *mmdc_clk;
 static struct clk *pll2_400;
 static struct clk *periph_clk;
 static struct clk *periph_pre_clk;
@@ -79,55 +101,294 @@ static struct clk *cpu_clk;
 static struct clk *pll3;
 static struct clk *pll2;
 static struct clk *pll2_200;
-
+static struct clk *pll1_sys;
+static struct clk *periph2_clk;
+static struct clk *ocram_clk;
+static struct clk *ahb_clk;
+static struct clk *pll1_sw_clk;
+static struct clk *periph2_pre_clk;
+static struct clk *periph2_clk2_sel;
+static struct clk *periph2_clk2;
+static struct clk *step_clk;
+static struct clk *axi_alt_sel_clk;
+static struct clk *axi_sel_clk;
+static struct clk *pll3_pfd1_540m;
+static struct clk *m4_clk;
+
+static u32 pll2_org_rate;
 static struct delayed_work low_bus_freq_handler;
 static struct delayed_work bus_freq_daemon;
 
-int low_bus_freq;
-
-int reduce_bus_freq(void)
+static void enter_lpm_imx6sx(void)
 {
-       int ret = 0;
-       clk_prepare_enable(pll3);
-       if (low_bus_freq) {
+       /* set periph_clk2 to source from OSC for periph */
+       imx_clk_set_parent(periph_clk2_sel, osc_clk);
+       imx_clk_set_parent(periph_clk, periph_clk2);
+       /* set ahb/ocram to 24MHz */
+       imx_clk_set_rate(ahb_clk, LPAPM_CLK);
+       imx_clk_set_rate(ocram_clk, LPAPM_CLK);
+
+       if (audio_bus_count) {
                /* Need to ensure that PLL2_PFD_400M is kept ON. */
                clk_prepare_enable(pll2_400);
-               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__);
+               if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3)
+                       update_ddr_freq_imx6sx(DDR3_AUDIO_CLK);
+               else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2)
+                       update_lpddr2_freq(LPDDR2_AUDIO_CLK);
+               imx_clk_set_parent(periph2_clk2_sel, pll3);
+               imx_clk_set_parent(periph2_pre_clk, pll2_400);
+               imx_clk_set_parent(periph2_clk, periph2_pre_clk);
+               /*
+                * As periph2_clk's parent is not changed from
+                * high mode to audio mode, so clk framework
+                * will not update its children's freq, but we
+                * change the mmdc's podf in asm code, so here
+                * need to update mmdc rate to make sure clk
+                * tree is right, although it will not do any
+                * change to hardware.
+                */
+               if (high_bus_freq_mode) {
+                       if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3)
+                               imx_clk_set_rate(mmdc_clk, DDR3_AUDIO_CLK);
+                       else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2)
+                               imx_clk_set_rate(mmdc_clk, LPDDR2_AUDIO_CLK);
+               }
                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 (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3)
+                       update_ddr_freq_imx6sx(LPAPM_CLK);
+               else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2)
+                       update_lpddr2_freq(LPAPM_CLK);
+               imx_clk_set_parent(periph2_clk2_sel, osc_clk);
+               imx_clk_set_parent(periph2_clk, periph2_clk2);
+
                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())
+}
+
+static void exit_lpm_imx6sx(void)
+{
+       clk_prepare_enable(pll2_400);
+
+       /*
+        * lower ahb/ocram's freq first to avoid too high
+        * freq during parent switch from OSC to pll3.
+        */
+       imx_clk_set_rate(ahb_clk, LPAPM_CLK / 3);
+       imx_clk_set_rate(ocram_clk, LPAPM_CLK / 2);
+       /* set periph_clk2 to pll3 */
+       imx_clk_set_parent(periph_clk2_sel, pll3);
+       /* set periph clk to from pll2_400 */
+       imx_clk_set_parent(periph_pre_clk, pll2_400);
+       imx_clk_set_parent(periph_clk, periph_pre_clk);
+
+       if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3)
+               update_ddr_freq_imx6sx(ddr_normal_rate);
+       else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2)
+               update_lpddr2_freq(ddr_normal_rate);
+       /* correct parent info after ddr freq change in asm code */
+       imx_clk_set_parent(periph2_clk2_sel, pll3);
+       imx_clk_set_parent(periph2_pre_clk, pll2_400);
+       imx_clk_set_parent(periph2_clk, periph2_pre_clk);
+       /*
+        * As periph2_clk's parent is not changed from
+        * audio mode to high mode, so clk framework
+        * will not update its children's freq, but we
+        * change the mmdc's podf in asm code, so here
+        * need to update mmdc rate to make sure clk
+        * tree is right, although it will not do any
+        * change to hardware.
+        */
+       if (audio_bus_freq_mode)
+               imx_clk_set_rate(mmdc_clk, ddr_normal_rate);
+
+       clk_disable_unprepare(pll2_400);
+       if (audio_bus_freq_mode)
                clk_disable_unprepare(pll2_400);
+}
+
+static void enter_lpm_imx6sl(void)
+{
+       if (high_bus_freq_mode) {
+               pll2_org_rate = clk_get_rate(pll2);
+               /* Set periph_clk to be sourced from OSC_CLK */
+               imx_clk_set_parent(periph_clk2_sel, osc_clk);
+               imx_clk_set_parent(periph_clk, periph_clk2);
+               /* Ensure AHB/AXI clks are at 24MHz. */
+               imx_clk_set_rate(ahb_clk, LPAPM_CLK);
+               imx_clk_set_rate(ocram_clk, LPAPM_CLK);
+       }
+       if (audio_bus_count) {
+               /* Set AHB to 8MHz to lower pwer.*/
+               imx_clk_set_rate(ahb_clk, LPAPM_CLK / 3);
+
+               /* Set up DDR to 100MHz. */
+               update_lpddr2_freq(LPDDR2_AUDIO_CLK);
+
+               /* Fix the clock tree in kernel */
+               imx_clk_set_parent(periph2_pre_clk, pll2_200);
+               imx_clk_set_parent(periph2_clk, periph2_pre_clk);
+
+               if (low_bus_freq_mode || ultra_low_bus_freq_mode) {
+                       /*
+                        * Swtich ARM to run off PLL2_PFD2_400MHz
+                        * since DDR is anyway at 100MHz.
+                        */
+                       imx_clk_set_parent(step_clk, pll2_400);
+                       imx_clk_set_parent(pll1_sw_clk, step_clk);
+                       /*
+                        * Ensure that the clock will be
+                        * at original speed.
+                        */
+                       imx_clk_set_rate(cpu_clk, org_arm_rate);
+               }
+               low_bus_freq_mode = 0;
+               ultra_low_bus_freq_mode = 0;
+               audio_bus_freq_mode = 1;
+       } else {
+               u32 arm_div, pll1_rate;
+               org_arm_rate = clk_get_rate(cpu_clk);
+               if (low_bus_freq_mode && low_bus_count == 0) {
+                       /*
+                        * We are already in DDR @ 24MHz state, but
+                        * no one but ARM needs the DDR. In this case,
+                        * we can lower the DDR freq to 1MHz when ARM
+                        * enters WFI in this state. Keep track of this state.
+                        */
+                       ultra_low_bus_freq_mode = 1;
+                       low_bus_freq_mode = 0;
+                       audio_bus_freq_mode = 0;
+               } else {
+                       if (!ultra_low_bus_freq_mode && !low_bus_freq_mode) {
+                               /*
+                                * Set DDR to 24MHz.
+                                * Since we are going to bypass PLL2,
+                                * we need to move ARM clk off PLL2_PFD2
+                                * to PLL1. Make sure the PLL1 is running
+                                * at the lowest possible freq.
+                                * To work well with CPUFREQ we want to ensure that
+                                * the CPU freq does not change, so attempt to
+                                * get a freq as close to 396MHz as possible.
+                                */
+                               imx_clk_set_rate(pll1_sys,
+                                       clk_round_rate(pll1_sys, (org_arm_rate * 2)));
+                               pll1_rate = clk_get_rate(pll1_sys);
+                               arm_div = pll1_rate / org_arm_rate;
+                               if (pll1_rate / arm_div > org_arm_rate)
+                                       arm_div++;
+                               /*
+                                * Ensure ARM CLK is lower before
+                                * changing the parent.
+                                */
+                               imx_clk_set_rate(cpu_clk, org_arm_rate / arm_div);
+                               /* Now set the ARM clk parent to PLL1_SYS. */
+                               imx_clk_set_parent(pll1_sw_clk, pll1_sys);
+
+                               /*
+                                * Set STEP_CLK back to OSC to save power and
+                                * also to maintain the parent.The WFI iram code
+                                * will switch step_clk to osc, but the clock API
+                                * is not aware of the change and when a new request
+                                * to change the step_clk parent to pll2_pfd2_400M
+                                * is requested sometime later, the change is ignored.
+                                */
+                               imx_clk_set_parent(step_clk, osc_clk);
+                               /* Now set DDR to 24MHz. */
+                               update_lpddr2_freq(LPAPM_CLK);
+
+                               /*
+                                * Fix the clock tree in kernel.
+                                * Make sure PLL2 rate is updated as it gets
+                                * bypassed in the DDR freq change code.
+                                */
+                               imx_clk_set_parent(periph2_clk2_sel, pll2);
+                               imx_clk_set_parent(periph2_clk, periph2_clk2);
+
+                       }
+                       if (low_bus_count == 0) {
+                               ultra_low_bus_freq_mode = 1;
+                               low_bus_freq_mode = 0;
+                       } else {
+                               ultra_low_bus_freq_mode = 0;
+                               low_bus_freq_mode = 1;
+                       }
+                       audio_bus_freq_mode = 0;
+               }
+       }
+}
+
+static void exit_lpm_imx6sl(void)
+{
+       /* Change DDR freq in IRAM. */
+       update_lpddr2_freq(ddr_normal_rate);
+
+       /*
+        * Fix the clock tree in kernel.
+        * Make sure PLL2 rate is updated as it gets
+        * un-bypassed in the DDR freq change code.
+        */
+       imx_clk_set_parent(periph2_pre_clk, pll2_400);
+       imx_clk_set_parent(periph2_clk, periph2_pre_clk);
+
+       /* Ensure that periph_clk is sourced from PLL2_400. */
+       imx_clk_set_parent(periph_pre_clk, pll2_400);
+       /*
+        * Before switching the perhiph_clk, ensure that the
+        * AHB/AXI will not be too fast.
+        */
+       imx_clk_set_rate(ahb_clk, LPAPM_CLK / 3);
+       imx_clk_set_rate(ocram_clk, LPAPM_CLK / 2);
+       imx_clk_set_parent(periph_clk, periph_pre_clk);
+
+       if (low_bus_freq_mode || ultra_low_bus_freq_mode) {
+               /* Move ARM from PLL1_SW_CLK to PLL2_400. */
+               imx_clk_set_parent(step_clk, pll2_400);
+               imx_clk_set_parent(pll1_sw_clk, step_clk);
+               imx_clk_set_rate(cpu_clk, org_arm_rate);
+               ultra_low_bus_freq_mode = 0;
+       }
+}
 
+static void reduce_bus_freq(void)
+{
+       clk_prepare_enable(pll3);
+       if (cpu_is_imx6sl())
+               enter_lpm_imx6sl();
+       else if (cpu_is_imx6sx())
+               enter_lpm_imx6sx();
+       else {
+               if (cpu_is_imx6dl())
+                       /* Set axi to periph_clk */
+                       imx_clk_set_parent(axi_sel_clk, periph_clk);
+
+               if (audio_bus_count) {
+                       /* Need to ensure that PLL2_PFD_400M is kept ON. */
+                       clk_prepare_enable(pll2_400);
+                       update_ddr_freq_imx6q(DDR3_AUDIO_CLK);
+                       /* Make sure periph clk's parent also got updated */
+                       imx_clk_set_parent(periph_clk2_sel, pll3);
+                       imx_clk_set_parent(periph_pre_clk, pll2_200);
+                       imx_clk_set_parent(periph_clk, periph_pre_clk);
+                       audio_bus_freq_mode = 1;
+                       low_bus_freq_mode = 0;
+               } else {
+                       update_ddr_freq_imx6q(LPAPM_CLK);
+                       /* Make sure periph clk's parent also got updated */
+                       imx_clk_set_parent(periph_clk2_sel, osc_clk);
+                       /* Set periph_clk parent to OSC via periph_clk2_sel */
+                       imx_clk_set_parent(periph_clk, periph_clk2);
+                       if (audio_bus_freq_mode)
+                               clk_disable_unprepare(pll2_400);
+                       low_bus_freq_mode = 1;
+                       audio_bus_freq_mode = 0;
+               }
+       }
        clk_disable_unprepare(pll3);
+
        med_bus_freq_mode = 0;
        high_bus_freq_mode = 0;
 
@@ -139,8 +400,6 @@ int reduce_bus_freq(void)
                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)
@@ -153,11 +412,11 @@ static void reduce_bus_freq_handler(struct work_struct *work)
 }
 
 /*
 * 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)
+ * 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;
@@ -166,23 +425,30 @@ int set_low_bus_freq(int low_bus_mode)
                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));
+        * 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.
 */
-int set_high_bus_freq(int high_bus_freq)
+ * Set the DDR to either 528MHz or 400MHz for iMX6qd
+ * or 400MHz for iMX6dl.
+ */
+static int set_high_bus_freq(int high_bus_freq)
 {
-       int ret = 0;
+       struct clk *periph_clk_parent;
 
        if (bus_freq_scaling_initialized && bus_freq_scaling_is_active)
                cancel_delayed_work_sync(&low_bus_freq_handler);
@@ -190,11 +456,10 @@ int set_high_bus_freq(int high_bus_freq)
        if (busfreq_suspended)
                return 0;
 
-       /* for high setpoint, i.MX6Q is 528MHz, i.MX6DL is 400MHz */
        if (cpu_is_imx6q())
-               high_bus_freq = 1;
+               periph_clk_parent = pll2;
        else
-               high_bus_freq = 0;
+               periph_clk_parent = pll2_400;
 
        if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active)
                return 0;
@@ -207,42 +472,34 @@ int set_high_bus_freq(int 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)
+       if (cpu_is_imx6sl())
+               exit_lpm_imx6sl();
+       else if (cpu_is_imx6sx())
+               exit_lpm_imx6sx();
+       else {
+               if (high_bus_freq) {
+                       clk_prepare_enable(pll2_400);
+                       update_ddr_freq_imx6q(ddr_normal_rate);
+                       /* Make sure periph clk's parent also got updated */
+                       imx_clk_set_parent(periph_clk2_sel, pll3);
+                       imx_clk_set_parent(periph_pre_clk, periph_clk_parent);
+                       imx_clk_set_parent(periph_clk, periph_pre_clk);
+                       if (cpu_is_imx6dl()) {
+                               /* Set axi to pll3_pfd1_540m */
+                               imx_clk_set_parent(axi_alt_sel_clk, pll3_pfd1_540m);
+                               imx_clk_set_parent(axi_sel_clk, axi_alt_sel_clk);
+                       }
+                       clk_disable_unprepare(pll2_400);
+               } else {
+                       update_ddr_freq_imx6q(ddr_med_rate);
+                       /* Make sure periph clk's parent also got updated */
+                       imx_clk_set_parent(periph_clk2_sel, pll3);
+                       imx_clk_set_parent(periph_pre_clk, pll2_400);
+                       imx_clk_set_parent(periph_clk, periph_pre_clk);
+               }
+               if (audio_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;
@@ -250,7 +507,6 @@ int set_high_bus_freq(int high_bus_freq)
        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",
@@ -273,14 +529,24 @@ void request_bus_freq(enum bus_freq_mode mode)
                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 (cpu_is_imx6dl() || cpu_is_imx6sx()) {
+               /* No support for medium setpoint on i.MX6DL and i.MX6SX. */
+               if (mode == BUS_FREQ_MED) {
+                       high_bus_count++;
+                       mode = BUS_FREQ_HIGH;
+               }
+       }
+
        if ((mode == BUS_FREQ_HIGH) && (!high_bus_freq_mode)) {
                set_high_bus_freq(1);
                mutex_unlock(&bus_freq_mutex);
@@ -295,7 +561,7 @@ void request_bus_freq(enum bus_freq_mode mode)
        }
        if ((mode == BUS_FREQ_AUDIO) && (!high_bus_freq_mode) &&
                (!med_bus_freq_mode) && (!audio_bus_freq_mode)) {
-               set_low_bus_freq(1);
+               set_low_bus_freq();
                mutex_unlock(&bus_freq_mutex);
                return;
        }
@@ -332,6 +598,14 @@ void release_bus_freq(enum bus_freq_mode mode)
                        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 ||
@@ -340,27 +614,99 @@ void release_bus_freq(enum bus_freq_mode mode)
                return;
        }
 
+       if (cpu_is_imx6dl() || cpu_is_imx6sx()) {
+               /* No support for medium setpoint on i.MX6DL and i.MX6SX. */
+               if (mode == BUS_FREQ_MED) {
+                       high_bus_count--;
+                       mode = BUS_FREQ_HIGH;
+               }
+       }
+
        if ((!audio_bus_freq_mode) && (high_bus_count == 0) &&
                (med_bus_count == 0) && (audio_bus_count != 0)) {
-               set_low_bus_freq(1);
+               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))
-               set_low_bus_freq(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);
        return;
 }
 EXPORT_SYMBOL(release_bus_freq);
 
+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 imx6_dt_find_ddr_sram(unsigned long node,
+               const char *uname, int depth, void *data)
+{
+       unsigned long ddr_iram_addr;
+       __be32 *prop;
+
+       if (of_flat_dt_match(node, ddr_freq_iram_match)) {
+               unsigned long 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 imx6_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(imx6_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) && (high_bus_count == 0) &&
                (med_bus_count == 0) && (audio_bus_count == 0))
-               set_low_bus_freq(0);
+               set_low_bus_freq();
        mutex_unlock(&bus_freq_mutex);
 }
 
@@ -417,10 +763,24 @@ static int bus_freq_pm_notify(struct notifier_block *nb, unsigned long event,
        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);
 
@@ -439,76 +799,189 @@ static int busfreq_probe(struct platform_device *pdev)
 
        busfreq_dev = &pdev->dev;
 
+       /* Return if no IRAM space is allocated for ddr freq change code. */
+       if (!ddr_freq_change_iram_base)
+               return ENOMEM;
+
        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__);
+               __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__);
+                       __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__);
+                       __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__);
+                       __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__);
+                       __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__);
+                       __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__);
+                       __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__);
+                       __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__);
+                       __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__);
+                       __func__);
                return PTR_ERR(osc_clk);
        }
 
+       if (cpu_is_imx6dl()) {
+               axi_alt_sel_clk = devm_clk_get(&pdev->dev, "axi_alt_sel");
+               if (IS_ERR(axi_alt_sel_clk)) {
+                       dev_err(busfreq_dev, "%s: failed to get axi_alt_sel_clk\n",
+                               __func__);
+                       return PTR_ERR(axi_alt_sel_clk);
+               }
+
+               axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel");
+               if (IS_ERR(axi_sel_clk)) {
+                       dev_err(busfreq_dev, "%s: failed to get axi_sel_clk\n",
+                               __func__);
+                       return PTR_ERR(axi_sel_clk);
+               }
+
+               pll3_pfd1_540m = devm_clk_get(&pdev->dev, "pll3_pfd1_540m");
+               if (IS_ERR(pll3_pfd1_540m)) {
+                       dev_err(busfreq_dev,
+                               "%s: failed to get pll3_pfd1_540m\n", __func__);
+                       return PTR_ERR(pll3_pfd1_540m);
+               }
+       }
+
+       if (cpu_is_imx6sl() || cpu_is_imx6sx()) {
+               pll1_sys = devm_clk_get(&pdev->dev, "pll1_sys");
+               if (IS_ERR(pll1_sys)) {
+                       dev_err(busfreq_dev, "%s: failed to get pll1_sys\n",
+                               __func__);
+                       return PTR_ERR(pll1_sys);
+               }
+
+               ahb_clk = devm_clk_get(&pdev->dev, "ahb");
+               if (IS_ERR(ahb_clk)) {
+                       dev_err(busfreq_dev, "%s: failed to get ahb_clk\n",
+                               __func__);
+                       return PTR_ERR(ahb_clk);
+               }
+
+               ocram_clk = devm_clk_get(&pdev->dev, "ocram");
+               if (IS_ERR(ocram_clk)) {
+                       dev_err(busfreq_dev, "%s: failed to get ocram_clk\n",
+                               __func__);
+                       return PTR_ERR(ocram_clk);
+               }
+
+               pll1_sw_clk = devm_clk_get(&pdev->dev, "pll1_sw");
+               if (IS_ERR(pll1_sw_clk)) {
+                       dev_err(busfreq_dev, "%s: failed to get pll1_sw_clk\n",
+                               __func__);
+                       return PTR_ERR(pll1_sw_clk);
+               }
+
+               periph2_clk = devm_clk_get(&pdev->dev, "periph2");
+               if (IS_ERR(periph2_clk)) {
+                       dev_err(busfreq_dev, "%s: failed to get periph2\n",
+                               __func__);
+                       return PTR_ERR(periph2_clk);
+               }
+
+               periph2_pre_clk = devm_clk_get(&pdev->dev, "periph2_pre");
+               if (IS_ERR(periph2_pre_clk)) {
+                       dev_err(busfreq_dev,
+                               "%s: failed to get periph2_pre_clk\n",
+                               __func__);
+                       return PTR_ERR(periph2_pre_clk);
+               }
+
+               periph2_clk2 = devm_clk_get(&pdev->dev, "periph2_clk2");
+               if (IS_ERR(periph2_clk2)) {
+                       dev_err(busfreq_dev,
+                               "%s: failed to get periph2_clk2\n",
+                               __func__);
+                       return PTR_ERR(periph2_clk2);
+               }
+
+               periph2_clk2_sel = devm_clk_get(&pdev->dev, "periph2_clk2_sel");
+               if (IS_ERR(periph2_clk2_sel)) {
+                       dev_err(busfreq_dev,
+                               "%s: failed to get periph2_clk2_sel\n",
+                               __func__);
+                       return PTR_ERR(periph2_clk2_sel);
+               }
+
+               step_clk = devm_clk_get(&pdev->dev, "step");
+               if (IS_ERR(step_clk)) {
+                       dev_err(busfreq_dev,
+                               "%s: failed to get step_clk\n",
+                               __func__);
+                       return PTR_ERR(step_clk);
+               }
+       }
+
+       if (cpu_is_imx6sx()) {
+               mmdc_clk = devm_clk_get(&pdev->dev, "mmdc");
+               if (IS_ERR(mmdc_clk)) {
+                       dev_err(busfreq_dev,
+                               "%s: failed to get mmdc_clk\n",
+                               __func__);
+                       return PTR_ERR(mmdc_clk);
+               }
+               m4_clk = devm_clk_get(&pdev->dev, "m4");
+               if (IS_ERR(m4_clk)) {
+                       dev_err(busfreq_dev,
+                               "%s: failed to get m4_clk\n",
+                               __func__);
+                       return PTR_ERR(m4_clk);
+               }
+       }
+
        err = sysfs_create_file(&busfreq_dev->kobj, &dev_attr_enable.attr);
        if (err) {
                dev_err(busfreq_dev,
@@ -526,6 +999,7 @@ static int busfreq_probe(struct platform_device *pdev)
        med_bus_freq_mode = 0;
        low_bus_freq_mode = 0;
        audio_bus_freq_mode = 0;
+       ultra_low_bus_freq_mode = 0;
 
        bus_freq_scaling_is_active = 1;
        bus_freq_scaling_initialized = 1;
@@ -534,7 +1008,7 @@ static int busfreq_probe(struct platform_device *pdev)
        if (cpu_is_imx6q()) {
                if (of_property_read_u32(pdev->dev.of_node, "fsl,med_ddr_freq",
                                &ddr_med_rate)) {
-                       dev_err(busfreq_dev,
+                       dev_info(busfreq_dev,
                                        "DDR medium rate not supported.\n");
                        ddr_med_rate = ddr_normal_rate;
                }
@@ -543,8 +1017,37 @@ static int busfreq_probe(struct platform_device *pdev)
        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);
+
+       /*
+        * 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_imx6sl()) {
+               err = init_mmdc_lpddr2_settings(pdev);
+       } else if (cpu_is_imx6sx()) {
+               ddr_type = imx_mmdc_get_ddr_type();
+               /* check whether it is a DDR3 or LPDDR2 board */
+               if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3)
+                       err = init_mmdc_ddr3_settings_imx6sx(pdev);
+               else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2)
+                       err = init_mmdc_lpddr2_settings(pdev);
+       } else {
+               err = init_mmdc_ddr3_settings_imx6q(pdev);
+       }
 
-       err = init_mmdc_settings(pdev);
        if (err) {
                dev_err(busfreq_dev, "Busfreq init of MMDC failed\n");
                return err;
@@ -574,11 +1077,12 @@ static struct platform_driver busfreq_driver = {
 
 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;
 }
 
index ef4db2c8756fd5a42d414bd9870bce25c927feb8..6a78d31eed793e14274d9a38ab19e23b7f39a714 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved.
  */
 
 /*
@@ -25,6 +25,7 @@
 #include <asm/mach-types.h>
 #include <asm/tlb.h>
 #include <linux/clk.h>
+#include <linux/clockchips.h>
 #include <linux/cpumask.h>
 #include <linux/delay.h>
 #include <linux/genalloc.h>
 
 #include "hardware.h"
 
+static unsigned int online_cpus;
+u32 *wait_for_ddr_freq_update;
+
+/*
+ * This structure is for passing necessary data for low level ocram
+ * busfreq code(arch/arm/mach-imx/ddr3_freq_imx6.S), if this struct
+ * definition is changed, the offset definition in
+ * arch/arm/mach-imx/ddr3_freq_imx6.S must be also changed accordingly,
+ * otherwise, the busfreq change function will be broken!
+ *
+ * This structure will be placed in front of the asm code on ocram.
+ */
+struct imx6_busfreq_info {
+       u32 freq;
+       void *ddr_settings;
+       u32 dll_off;
+       void *iomux_offsets;
+       u32 mu_delay_val;
+} __aligned(8);
+
+static struct imx6_busfreq_info *imx6sx_busfreq_info;
+
 /* DDR settings */
 static unsigned long (*iram_ddr_settings)[2];
 static unsigned long (*normal_mmdc_settings)[2];
@@ -49,11 +72,17 @@ 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;
 
+static int ddr_settings_size;
+static int iomux_settings_size;
+static int curr_ddr_rate;
+static unsigned long wfe_freq_change_iram_base;
+
+void (*imx6sx_change_ddr_freq)(struct imx6_busfreq_info *busfreq_info);
+extern void imx6sx_ddr3_freq_change(struct imx6_busfreq_info *busfreq_info);
+
 void (*mx6_change_ddr_freq)(u32 freq, void *ddr_settings,
        bool dll_mode, void *iomux_offsets) = NULL;
 
@@ -64,16 +93,52 @@ 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;
+void (*wfe_change_ddr_freq)(u32 cpuid, u32 *ddr_freq_change_done);
+extern void wfe_ddr3_freq_change(u32 cpuid, u32 *ddr_freq_change_done);
+
+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 mx6_ddr3_freq_change_start asm("mx6_ddr3_freq_change_start");
+extern unsigned long mx6_ddr3_freq_change_end asm("mx6_ddr3_freq_change_end");
+extern unsigned long 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");
+extern unsigned long imx6sx_ddr3_freq_change_start asm("imx6sx_ddr3_freq_change_start");
+extern unsigned long imx6sx_ddr3_freq_change_end asm("imx6sx_ddr3_freq_change_end");
+extern void __iomem *imx_scu_base;
+extern unsigned long iram_tlb_phys_addr;
 
 #define MIN_DLL_ON_FREQ                333000000
 #define MAX_DLL_OFF_FREQ               125000000
-#define DDR_FREQ_CHANGE_SIZE   0x2000
+#define MMDC0_MPMUR0                   0x8b8
+#define MMDC0_MPMUR0_OFFSET    16
+#define MMDC0_MPMUR0_MASK              0x3ff
+
+unsigned long ddr3_dll_mx6sx[][2] = {
+       {0x0c, 0x0},
+       {0x10, 0x0},
+       {0x1C, 0x04008032},
+       {0x1C, 0x00048031},
+       {0x1C, 0x05208030},
+       {0x1C, 0x04008040},
+       {0x818, 0x0},
+};
+
+unsigned long ddr3_calibration_mx6sx[][2] = {
+       {0x83c, 0x0},
+       {0x840, 0x0},
+       {0x848, 0x0},
+       {0x850, 0x0},
+};
+
+unsigned long iomux_offsets_mx6sx[][2] = {
+       {0x330, 0x0},
+       {0x334, 0x0},
+       {0x338, 0x0},
+       {0x33c, 0x0},
+};
 
 unsigned long ddr3_dll_mx6q[][2] = {
        {0x0c, 0x0},
@@ -152,27 +217,76 @@ int can_change_ddr_freq(void)
  */
 irqreturn_t wait_in_wfe_irq(int irq, void *dev_id)
 {
-       u32 me = smp_processor_id();
-
-       *((char *)(&cpus_in_wfe) + (u8)me) = 0xff;
+       u32 me;
 
-       while (wait_for_ddr_freq_update)
-               wfe();
+       me = smp_processor_id();
+#ifdef CONFIG_LOCAL_TIMERS
+               clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER,
+                                  &me);
+#endif
+       wfe_change_ddr_freq(0xff << (me * 8), (u32 *)&iram_iomux_settings[0][1]);
 
-       *((char *)(&cpus_in_wfe) + (u8)me) = 0;
+#ifdef CONFIG_LOCAL_TIMERS
+               clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT,
+                                  &me);
+#endif
 
        return IRQ_HANDLED;
 }
 
+int update_ddr_freq_imx6sx(int ddr_rate)
+{
+       int i;
+       bool dll_off = false;
+       unsigned long ttbr1;
+
+       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;
+
+       imx6sx_busfreq_info->dll_off = dll_off;
+       iram_ddr_settings[0][0] = ddr_settings_size;
+       iram_iomux_settings[0][0] = iomux_settings_size;
+       for (i = 0; i < iram_ddr_settings[0][0]; i++) {
+               iram_ddr_settings[i + 1][0] =
+                               normal_mmdc_settings[i][0];
+               iram_ddr_settings[i + 1][1] =
+                               normal_mmdc_settings[i][1];
+       }
+
+       local_irq_disable();
+
+       ttbr1 = save_ttbr1();
+       imx6sx_busfreq_info->freq = ddr_rate;
+       imx6sx_busfreq_info->ddr_settings = iram_ddr_settings;
+       imx6sx_busfreq_info->iomux_offsets = iram_iomux_settings;
+       imx6sx_busfreq_info->mu_delay_val  = ((readl_relaxed(mmdc_base + MMDC0_MPMUR0)
+               >> MMDC0_MPMUR0_OFFSET) & MMDC0_MPMUR0_MASK);
+
+       imx6sx_change_ddr_freq(imx6sx_busfreq_info);
+       restore_ttbr1(ttbr1);
+       curr_ddr_rate = ddr_rate;
+
+       local_irq_enable();
+
+       printk(KERN_DEBUG "Bus freq set to %d done!\n", ddr_rate);
+
+       return 0;
+}
+
 /* change the DDR frequency. */
-int update_ddr_freq(int ddr_rate)
+int update_ddr_freq_imx6q(int ddr_rate)
 {
        int i, j;
        unsigned int reg;
        bool dll_off = false;
-       unsigned int online_cpus = 0;
        int cpu = 0;
        int me;
+       unsigned long ttbr1;
 
        if (!can_change_ddr_freq())
                return -1;
@@ -215,10 +329,25 @@ int update_ddr_freq(int ddr_rate)
 
        me = smp_processor_id();
 
-       *((char *)(&cpus_in_wfe) + (u8)me) = 0xff;
-       wait_for_ddr_freq_update = true;
+       /* Make sure all the online cores are active */
+       while (1) {
+               bool not_exited_busfreq = false;
+               for_each_online_cpu(cpu) {
+                       u32 reg = __raw_readl(imx_scu_base + 0x08);
+                       if (reg & (0x02 << (cpu * 8)))
+                               not_exited_busfreq = true;
+               }
+               if (!not_exited_busfreq)
+                       break;
+       }
+
+       wmb();
+       *wait_for_ddr_freq_update = 1;
+       dsb();
+
+       online_cpus = readl_relaxed(imx_scu_base + 0x08);
        for_each_online_cpu(cpu) {
-               *((char *)(&online_cpus) + (u8)cpu) = 0xff;
+               *((char *)(&online_cpus) + (u8)cpu) = 0x02;
                if (cpu != me) {
                        /* set the interrupt to be pending in the GIC. */
                        reg = 1 << (irqs_used[cpu] % 32);
@@ -226,40 +355,130 @@ int update_ddr_freq(int ddr_rate)
                                + (irqs_used[cpu] / 32) * 4);
                }
        }
-       while (cpus_in_wfe != online_cpus)
-               udelay(5);
+       /* Wait for the other active CPUs to idle */
+       while (1) {
+               u32 reg = readl_relaxed(imx_scu_base + 0x08);
+               reg |= (0x02 << (me * 8));
+               if (reg == online_cpus)
+                       break;
+       }
+
+       /* Ensure iram_tlb_phys_addr is flushed to DDR. */
+       __cpuc_flush_dcache_area(&iram_tlb_phys_addr, sizeof(iram_tlb_phys_addr));
+       outer_clean_range(virt_to_phys(&iram_tlb_phys_addr), virt_to_phys(&iram_tlb_phys_addr + 1));
 
+       ttbr1 = save_ttbr1();
        /* Now we can change the DDR frequency. */
        mx6_change_ddr_freq(ddr_rate, iram_ddr_settings,
                dll_off, iram_iomux_settings);
-
+       restore_ttbr1(ttbr1);
        curr_ddr_rate = ddr_rate;
 
+       wmb();
        /* DDR frequency change is done . */
-       wait_for_ddr_freq_update = false;
+       *wait_for_ddr_freq_update = 0;
+       dsb();
 
        /* 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);
+       printk(KERN_DEBUG "Bus freq set to %d done! cpu=%d\n", ddr_rate, me);
+
+       return 0;
+}
+
+int init_mmdc_ddr3_settings_imx6sx(struct platform_device *busfreq_pdev)
+{
+       int i;
+       struct device_node *node;
+       unsigned long ddr_code_size;
+
+       node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc");
+       if (!node) {
+               printk(KERN_ERR "failed to find mmdc device tree data!\n");
+               return -EINVAL;
+       }
+       mmdc_base = of_iomap(node, 0);
+       WARN(!mmdc_base, "unable to map mmdc registers\n");
+
+       node = of_find_compatible_node(NULL, NULL, "fsl,imx6sx-iomuxc");
+       if (!node) {
+               printk(KERN_ERR "failed to find iomuxc device tree data!\n");
+               return -EINVAL;
+       }
+       iomux_base = of_iomap(node, 0);
+       WARN(!iomux_base, "unable to map iomux registers\n");
+
+       ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6sx) +
+               ARRAY_SIZE(ddr3_calibration_mx6sx);
+
+       normal_mmdc_settings = kmalloc((ddr_settings_size * 8), GFP_KERNEL);
+       memcpy(normal_mmdc_settings, ddr3_dll_mx6sx,
+               sizeof(ddr3_dll_mx6sx));
+       memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6sx)),
+               ddr3_calibration_mx6sx, sizeof(ddr3_calibration_mx6sx));
+
+       /* store the original DDR settings at boot. */
+       for (i = 0; i < ddr_settings_size; i++) {
+               /*
+                * writes via command mode register cannot be read back.
+                * hence hardcode them in the initial static array.
+                * this may require modification on a per customer basis.
+                */
+               if (normal_mmdc_settings[i][0] != 0x1C)
+                       normal_mmdc_settings[i][1] =
+                               readl_relaxed(mmdc_base
+                               + normal_mmdc_settings[i][0]);
+       }
+
+       iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6sx);
+
+       ddr_code_size = (&imx6sx_ddr3_freq_change_end -&imx6sx_ddr3_freq_change_start) *4 +
+                       sizeof(*imx6sx_busfreq_info);
+
+       imx6sx_busfreq_info = (struct imx6_busfreq_info *)ddr_freq_change_iram_base;
+
+       imx6sx_change_ddr_freq = (void *)fncpy((void *)ddr_freq_change_iram_base + sizeof(*imx6sx_busfreq_info),
+               &imx6sx_ddr3_freq_change, ddr_code_size - sizeof(*imx6sx_busfreq_info));
+
+       /*
+        * Store the size of the array in iRAM also,
+        * increase the size by 8 bytes.
+        */
+       iram_iomux_settings = (void *)(ddr_freq_change_iram_base + ddr_code_size);
+       iram_ddr_settings = iram_iomux_settings + (iomux_settings_size * 8) + 8;
+
+       if ((ddr_code_size + (iomux_settings_size + ddr_settings_size) * 8 + 16)
+               > ddr_freq_change_total_size) {
+               printk(KERN_ERR "Not enough memory allocated for DDR Frequency change code.\n");
+               return EINVAL;
+       }
+
+       for (i = 0; i < iomux_settings_size; i++) {
+               iomux_offsets_mx6sx[i][1] =
+                       readl_relaxed(iomux_base +
+                               iomux_offsets_mx6sx[i][0]);
+               iram_iomux_settings[i + 1][0] =
+                               iomux_offsets_mx6sx[i][0];
+                       iram_iomux_settings[i + 1][1] =
+                               iomux_offsets_mx6sx[i][1];
+       }
+
+       curr_ddr_rate = ddr_normal_rate;
 
        return 0;
 }
 
-int init_mmdc_settings(struct platform_device *busfreq_pdev)
+int init_mmdc_ddr3_settings_imx6q(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;
-       void *iram_addr;
+       unsigned long ddr_code_size;
+unsigned long wfe_code_size;
 
        node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc-combine");
        if (!node) {
@@ -282,22 +501,6 @@ int init_mmdc_settings(struct platform_device *busfreq_pdev)
        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) {
@@ -343,7 +546,7 @@ int init_mmdc_settings(struct platform_device *busfreq_pdev)
        irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(),
                                        GFP_KERNEL);
 
-       for_each_present_cpu(cpu) {
+       for_each_online_cpu(cpu) {
                int irq;
 
                /*
@@ -369,61 +572,25 @@ int init_mmdc_settings(struct platform_device *busfreq_pdev)
                }
                irqs_used[cpu] = irq;
        }
+       iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6q);
 
-       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;
-       }
+       ddr_code_size = (&mx6_ddr3_freq_change_end -&mx6_ddr3_freq_change_start) *4;
 
-       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_addr = (void *)gen_pool_alloc(iram_pool,
-                                               (iomux_settings_size * 8) + 8);
-       iram_iomux_settings = iram_addr;
-       if (!iram_iomux_settings) {
-               dev_err(dev, "unable to alloc iram for IOMUX settings!\n");
-               return -ENOMEM;
-       }
+       mx6_change_ddr_freq = (void *)fncpy((void *)ddr_freq_change_iram_base,
+               &mx6_ddr3_freq_change, ddr_code_size);
 
        /*
-         * 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_addr = (void *)gen_pool_alloc(iram_pool,
-                                       (ddr_settings_size * 8) + 8 + 32);
-       iram_ddr_settings = iram_addr;
-       if (!iram_ddr_settings) {
-               dev_err(dev, "unable to alloc iram for ddr settings!\n");
-               return -ENOMEM;
-       }
+        * Store the size of the array in iRAM also,
+        * increase the size by 8 bytes.
+        */
+       iram_iomux_settings = (void *)(ddr_freq_change_iram_base + ddr_code_size);
+       iram_ddr_settings = iram_iomux_settings + (iomux_settings_size * 8) + 8;
 
-       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 ((ddr_code_size + (iomux_settings_size + ddr_settings_size) * 8 + 16)
+               > ddr_freq_change_total_size) {
+               printk(KERN_ERR "Not enough memory allocated for DDR Frequency change code.\n");
+               return EINVAL;
+       }
 
        if (cpu_is_imx6q()) {
                /* store the IOMUX settings at boot. */
@@ -446,24 +613,18 @@ int init_mmdc_settings(struct platform_device *busfreq_pdev)
                }
        }
 
-       ddr_freq_change_iram_base = (void *)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;
-       }
+       wfe_freq_change_iram_base = (unsigned long)((u32 *)iram_ddr_settings + (ddr_settings_size * 8) + 8);
 
-       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_RWX_NONCACHED);
-       mx6_change_ddr_freq = (void *)fncpy(ddr_freq_change_iram_base,
-               &mx6_ddr3_freq_change, DDR_FREQ_CHANGE_SIZE);
+       if (wfe_freq_change_iram_base & (FNCPY_ALIGN - 1))
+                       wfe_freq_change_iram_base += FNCPY_ALIGN - ((uintptr_t)wfe_freq_change_iram_base % (FNCPY_ALIGN));
+
+       wfe_code_size = (&wfe_ddr3_freq_change_end -&wfe_ddr3_freq_change_start) *4;
+
+       wfe_change_ddr_freq = (void *)fncpy((void *)wfe_freq_change_iram_base,
+               &wfe_ddr3_freq_change, wfe_code_size);
+
+       /* Store the variable used to communicate between cores in a non-cacheable IRAM area */
+       wait_for_ddr_freq_update = (u32 *)&iram_iomux_settings[0][1];
 
        curr_ddr_rate = ddr_normal_rate;
 
diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c
new file mode 100644 (file)
index 0000000..e71b22f
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2011-2014 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_lpddr2.c
+ *
+ * @brief iMX6 LPDDR2 frequency change specific file.
+ *
+ * @ingroup PM
+ */
+#include <asm/cacheflush.h>
+#include <asm/fncpy.h>
+#include <asm/io.h>
+#include <asm/mach/map.h>
+#include <asm/mach-types.h>
+#include <asm/tlb.h>
+#include <linux/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"
+
+
+static struct device *busfreq_dev;
+static int curr_ddr_rate;
+static DEFINE_SPINLOCK(freq_lock);
+
+void (*mx6_change_lpddr2_freq)(u32 ddr_freq, int bus_freq_mode) = NULL;
+
+extern unsigned int ddr_normal_rate;
+extern int low_bus_freq_mode;
+extern int ultra_low_bus_freq_mode;
+extern void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode);
+extern void imx6sx_lpddr2_freq_change(u32 freq, int bus_freq_mode);
+extern unsigned long save_ttbr1(void);
+extern void restore_ttbr1(unsigned long ttbr1);
+extern unsigned long ddr_freq_change_iram_base;
+extern unsigned long imx6_lpddr2_freq_change_start asm("imx6_lpddr2_freq_change_start");
+extern unsigned long imx6_lpddr2_freq_change_end asm("imx6_lpddr2_freq_change_end");
+
+/* change the DDR frequency. */
+int update_lpddr2_freq(int ddr_rate)
+{
+       unsigned long ttbr1, flags;
+
+       if (ddr_rate == curr_ddr_rate)
+               return 0;
+
+       dev_dbg(busfreq_dev, "\nBus freq set to %d start...\n", ddr_rate);
+
+       spin_lock_irqsave(&freq_lock, flags);
+       /*
+        * Flush the TLB, to ensure no TLB maintenance occurs
+        * when DDR is in self-refresh.
+        */
+       ttbr1 = save_ttbr1();
+
+       /* Now change DDR frequency. */
+       mx6_change_lpddr2_freq(ddr_rate,
+               (low_bus_freq_mode | ultra_low_bus_freq_mode));
+       restore_ttbr1(ttbr1);
+
+       curr_ddr_rate = ddr_rate;
+       spin_unlock_irqrestore(&freq_lock, flags);
+
+       dev_dbg(busfreq_dev, "\nBus freq set to %d done...\n", ddr_rate);
+
+       return 0;
+}
+
+int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev)
+{
+       unsigned long ddr_code_size;
+       busfreq_dev = &busfreq_pdev->dev;
+
+       ddr_code_size = (&imx6_lpddr2_freq_change_end -&imx6_lpddr2_freq_change_start) *4;
+
+       if (cpu_is_imx6sl())
+               mx6_change_lpddr2_freq = (void *)fncpy(
+                       (void *)ddr_freq_change_iram_base,
+                       &mx6_lpddr2_freq_change, ddr_code_size);
+       else if (cpu_is_imx6sx())
+               mx6_change_lpddr2_freq = (void *)fncpy(
+                       (void *)ddr_freq_change_iram_base,
+                       &imx6sx_lpddr2_freq_change, ddr_code_size);
+
+       curr_ddr_rate = ddr_normal_rate;
+
+       return 0;
+}
index 8cf1ce5b0e0e4a6891533e035d9df19598edfb4a..caa965cfa32fdaabfac5b3d9773bc93c586fa4fc 100644 (file)
@@ -142,6 +142,8 @@ void imx_anatop_post_resume(void);
 int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode);
 void imx6q_set_int_mem_clk_lpm(void);
 void imx6sl_set_wait_clk(bool enter);
+int imx_mmdc_get_ddr_type(void);
+void imx6_busfreq_map_io(void);
 
 void imx_cpu_die(unsigned int cpu);
 int imx_cpu_kill(unsigned int cpu);
index e7e67ce34382a35cb6cc0ebc22f863ffffa8be74..e0e9a720b26f3ba8ab00db76790e1f71dc3dffda 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2011-2014 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
@@ -17,6 +17,8 @@
  */
 
 #include <linux/linkage.h>
+#include <asm/smp_scu.h>
+#include "hardware.h"
 
 #define MMDC0_MDPDC            0x4
 #define MMDC0_MDCF0                    0x0c
 
 #define L2_CACHE_SYNC          0x730
 
+.extern iram_tlb_phys_addr
+.extern imx_scu_base
+
+.globl mx6_ddr3_freq_change_start
+.globl mx6_ddr3_freq_change_end
+.globl wfe_ddr3_freq_change_start
+.globl wfe_ddr3_freq_change_end
+
        .align 3
 
        .macro  switch_to_528MHz
@@ -64,7 +74,6 @@
         * 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
@@ -93,7 +102,6 @@ periph_clk_switch3:
 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
@@ -124,11 +132,6 @@ periph_clk_switch4:
        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
@@ -163,7 +166,6 @@ periph_clk_switch5:
 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
@@ -199,7 +201,6 @@ 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
@@ -213,12 +214,6 @@ wait_div_update400_2:
        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
@@ -261,7 +256,6 @@ switch_pre_periph_clk_50:
 
        /*
         * set the MMDC_DIV=4, AXI_DIV = 4, AHB_DIV=8,
-        * (need to maintain GPT divider).
         */
        ldr     r0, [r6, #CCM_CBCDR]
        ldr     r2, =0x3f1f00
@@ -291,12 +285,6 @@ periph_clk_switch2:
        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
@@ -339,11 +327,34 @@ wait_div_update:
        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
+
+       .macro  disable_l1_dcache
+
+       /*
+        * Flush all data from the L1 data cache before disabling
+        * SCTLR.C bit.
+        */
+       push    {r0 - r11, lr}
+
+       ldr     r7, =v7_flush_kern_cache_all
+       mov     lr, pc
+       mov     pc, r7
+       pop     {r0 - r11, lr}
+
+       /* disable d-cache */
+       mrc     p15, 0, r6, c1, c0, 0
+       bic     r6, r6, #0x4
+       mcr     p15, 0, r6, c1, c0, 0
+       dsb
+       isb
+
+       push    {r0 - r11, lr}
+
+       ldr     r7, =v7_flush_kern_cache_all
+       mov     lr, pc
+       mov     pc, r7
+       pop     {r0 - r11, lr}
 
        .endm
 
@@ -356,69 +367,114 @@ wait_div_update:
  */
 ENTRY(mx6_ddr3_freq_change)
 
+mx6_ddr3_freq_change_start:
        stmfd   sp!, {r4-r12}
 
        /*
-         * r5 -> mmdc_base
-         * r6 -> ccm_base
-         * r7 -> iomux_base
-         * r12 -> l2_base
-         */
+        * r5 -> mmdc_base
+        * r6 -> ccm_base
+        * r7 -> iomux_base
+        * r12 -> l2_base
+        */
        mov     r4, r0
        mov     r8, r1
        mov     r9, r2
        mov     r11, r3
 
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       ldr     r6, =iram_tlb_phys_addr
+       ldr     r7, [r6]
+
        /*
-         * 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:
+         * Need to flush and disable L1 before disabling L2, we need data to
+         * coherent. Flushing L1 pushes everyhting to L2. We sync L2 later, but
+         * it can still have dirty lines. While exiting, we need to enable L2 first
+         * and then L1.
+        . */
+       disable_l1_dcache
+
+#ifdef CONFIG_CACHE_L2X0
        /*
-        * make sure 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.
+        * Make sure the L2 buffers are drained.
+        * Sync operation on L2 drains the buffers.
         */
+       ldr     r12, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
 
-       adr     r10, ddr_freq_change
+       /* Wait for background operations to complete. */
+wait_for_l2_to_idle:
+       ldr     r1, [r12, #L2_CACHE_SYNC]
+       cmp     r1, #0x0
+       bne     wait_for_l2_to_idle
 
-       ldr     r2, [r6]
-       ldr     r2, [r5]
-       ldr     r2, [r7]
-       ldr     r2, [r8]
-       ldr     r2, [r10]
-       ldr     r2, [r11]
-       ldr     r2, [r12]
+       mov     r1, #0x0
+       str     r1, [r12, #L2_CACHE_SYNC]
 
-#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]
+       /* Disable L2. */
+       str     r1, [r12, #0x100]
+
+       dsb
+       isb
 #endif
 
+       /*
+        * To ensure no page table walks occur in DDR, we
+        * have a another page table stored in IRAM that only
+        * contains entries pointing to IRAM, AIPS1 and AIPS2.
+        * We need to set the TTBR1 to the new IRAM TLB.
+        * Do the following steps:
+        * 1. Flush the Branch Target Address Cache (BTAC)
+        * 2. Set TTBR1 to point to IRAM page table.
+        * 3. Disable page table walks in TTBR0 (PD0 = 1)
+        * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
+        *     and 2-4G is translated by TTBR1.
+        */
+
+
+       /* Now switch the TTBR. */
+       /* Disable Branch Prediction, Z bit in SCTLR. */
+       mrc     p15, 0, r6, c1, c0, 0
+       bic     r6, r6, #0x800
+       mcr     p15, 0, r6, c1, c0, 0
+
+       /* Flush the Branch Target Address Cache (BTAC) */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c7, c1, 6
+
+       dsb
+       isb
+
+       /* Store the IRAM table in TTBR1 */
+       mcr     p15, 0, r7, c2, c0, 1
+
+       /* Read TTBCR and set PD0=1, N = 1 */
+       mrc     p15, 0, r6, c2, c0, 2
+       orr     r6, r6, #0x11
+       mcr     p15, 0, r6, c2, c0, 2
+
+       dsb
+       isb
+
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       dsb
+       isb
+
+
+       ldr     r5, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
+       ldr     r6, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
+       ldr     r7, =IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR)
+
+       /* Read the Original MU delay value */
+       ldr     r1, [r5, #MMDC0_MPMUR0]
+       mov     r10, r1, lsr #16
+       ldr     r1, =0x3ff
+       and     r10, r10, r1
+
        /* disable automatic power saving. */
        ldr     r0, [r5, #MMDC0_MAPSR]
        orr     r0, r0, #0x01
@@ -614,24 +670,33 @@ update_iomux:
        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
+       /* Add workaround for ERR005778.*/
+       /* double the original MU_UNIT_DEL_NUM. */
+       lsl     r10, r10, #1
+
+       /* Bypass the automatic MU by setting the mu_byp_en */
+       ldr     r2, [r5, #MMDC0_MPMUR0]
+       orr     r2, r2, #0x400
+       orr     r2, r2, r10
+       str     r2, [r5, #MMDC0_MPMUR0]
+       ldr     r0, =MMDC1_MPMUR0
+       str     r2, [r5, r0]
+
+       /* Now perform a force measure */
+       ldr     r0, [r5, #MMDC0_MPMUR0]
+       orr     r0, r0, #0x800
        str     r0, [r5, #MMDC0_MPMUR0]
        ldr     r2, =MMDC1_MPMUR0
        str     r0, [r5, r2]
+       /* Wait for FRC_MSR to clear. */
+1:
+       ldr     r0, [r5, #MMDC0_MPMUR0]
+       and     r0, r0, #0x800
+       ldr     r1, [r5, r2]
+       and     r1, r1, #0x800
+       orr     r0, r0, r1
+       cmp     r0, #0x0
+       bne     1b
 
 continue_dll_off_3:
        /* clear SBS - unblock accesses to DDR. */
@@ -699,7 +764,7 @@ poll_dvfs_clear_2:
        cmp     r9, #0
        beq     update_calibration_only
 
-       ldr     r0, =0xa5390003
+       ldr     r0, =0xa1390003
        str     r0, [r5, #MMDC0_MPZQHWCTRL]
        ldr     r2, =MMDC1_MPZQHWCTRL
        str     r0, [r5, r2]
@@ -721,18 +786,15 @@ poll_dvfs_clear_2:
        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
+       /* Wait for FRC_MSR to clear. */
+1:
+       ldr     r0, [r5, #MMDC0_MPMUR0]
+       and     r0, r0, #0x800
+       ldr     r1, [r5, r2]
+       and     r1, r1, #0x800
+       orr     r0, r0, r1
+       cmp     r0, #0x0
+       bne     1b
 
        /* disable dqs pull down in the IOMUX. */
        ldr     r1, [r11]
@@ -858,11 +920,6 @@ cont15:
        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)
@@ -896,6 +953,16 @@ update_calib:
        ldr     r2, =MMDC1_MPMUR0
        str     r0, [r5, r2]
 
+       /* Wait for FRC_MSR to clear. */
+1:
+       ldr     r0, [r5, #MMDC0_MPMUR0]
+       and     r0, r0, #0x800
+       ldr     r1, [r5, r2]
+       and     r1, r1, #0x800
+       orr     r0, r0, r1
+       cmp     r0, #0x0
+       bne     1b
+
        /* clear SBS - unblock DDR accesses. */
        ldr     r0, [r5, #MMDC0_MADPCR0]
        bic     r0, r0, #(1 << 8)
@@ -910,12 +977,136 @@ poll_conreq_clear_2:
        beq     poll_conreq_clear_2
 
 done:
-       /* restore registers */
+       /* MMDC0_MAPSR adopt power down enable. */
+       ldr     r0, [r5, #MMDC0_MAPSR]
+       bic     r0, r0, #0x01
+       str     r0, [r5, #MMDC0_MAPSR]
+
+#ifdef CONFIG_CACHE_L2X0
+       /* Enable L2. */
+       ldr     r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
+       ldr     r6, =0x1
+       str     r6, [r7, #0x100]
+       isb
+       dsb
+#endif
+
+       /* Enable L1 data cache. */
+       mrc     p15, 0, r6, c1, c0, 0
+       orr     r6, r6, #0x4
+       mcr     p15, 0, r6, c1, c0, 0
+
+       /* Restore the TTBCR */
+       dsb
+       isb
+
+       /* Read TTBCR and set PD0=0, N = 0 */
+       mrc     p15, 0, r6, c2, c0, 2
+       bic     r6, r6, #0x11
+       mcr     p15, 0, r6, c2, c0, 2
+       dsb
+       isb
+
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       dsb
+       isb
+
+       /* Enable Branch Prediction, Z bit in SCTLR. */
+       mrc     p15, 0, r6, c1, c0, 0
+       orr     r6, r6, #0x800
+       mcr     p15, 0, r6, c1, c0, 0
+
+       isb
 
+       /* Flush the Branch Target Address Cache (BTAC) */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c7, c1, 6
+       isb
+       dsb
+
+       /* restore registers */
        ldmfd   sp!, {r4-r12}
        mov     pc, lr
 
-       .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
+       /*
+        * Add ltorg here to ensure that all
+        * literals are stored here and are
+        * within the text space.
+        */
+       .ltorg
+mx6_ddr3_freq_change_end:
+
+       .align 3
+
+ENTRY(wfe_ddr3_freq_change)
+wfe_ddr3_freq_change_start:
+       push    {r4 - r11, lr}
+
+       mov     r6, r0
+       mov     r7, r1
+
+       dsb
+       isb
+
+       disable_l1_dcache
+
+       isb
+
+       /* Turn off SMP bit. */
+       mrc     p15, 0, r8, c1, c0, 1
+       bic     r8, r8, #0x40
+       mcr     p15, 0, r8, c1, c0, 1
+
+       isb
+
+       /* Inform the SCU we are going to enter WFE. */
+       push    {r0 - r11, lr}
+
+       ldr     r0,=imx_scu_base
+       ldr     r0, [r0]
+       mov     r1, #SCU_PM_DORMANT
+       ldr     r3, =scu_power_mode
+       mov     lr, pc
+       mov     pc, r3
+
+       pop     {r0 - r11, lr}
+
+go_back_wfe:
+       wfe
+
+       ldr     r3, [r7]
+       cmp     r3, #1
+       beq     go_back_wfe
+
+       /* Turn ON SMP bit. */
+       mrc     p15, 0, r8, c1, c0, 1
+       orr     r8, r8, #0x40
+       mcr     p15, 0, r8, c1, c0, 1
+
+       isb
+       /* Enable L1 data cache. */
+       mrc     p15, 0, r8, c1, c0, 0
+       orr     r8, r8, #0x4
+       mcr     p15, 0, r8, c1, c0, 0
+       isb
+
+       /* Inform the SCU we have exited WFE. */
+       push    {r0 - r11, lr}
+
+       ldr     r0,=imx_scu_base
+       ldr     r0, [r0]
+       mov     r1, #SCU_PM_NORMAL
+       ldr     r3, =scu_power_mode
+       mov     lr, pc
+       mov     pc, r3
+
+       pop     {r0 - r11, lr}
+
+       /* Pop all saved registers. */
+       pop     {r4 - r11, lr}
+       mov     pc, lr
+       .ltorg
+wfe_ddr3_freq_change_end:
diff --git a/arch/arm/mach-imx/ddr3_freq_imx6sx.S b/arch/arm/mach-imx/ddr3_freq_imx6sx.S
new file mode 100644 (file)
index 0000000..2db7fe1
--- /dev/null
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/linkage.h>
+#include "hardware.h"
+
+.globl imx6sx_ddr3_freq_change_start
+.globl imx6sx_ddr3_freq_change_end
+
+#define MMDC0_MDPDC            0x4
+#define MMDC0_MDCF0            0xc
+#define MMDC0_MDCF1            0x10
+#define MMDC0_MDMISC           0x18
+#define MMDC0_MDSCR            0x1c
+#define MMDC0_MAPSR            0x404
+#define MMDC0_MADPCR0          0x410
+#define MMDC0_MPZQHWCTRL       0x800
+#define MMDC0_MPODTCTRL                0x818
+#define MMDC0_MPDGCTRL0                0x83c
+#define MMDC0_MPMUR0           0x8b8
+
+#define CCM_CBCDR              0x14
+#define CCM_CBCMR              0x18
+#define CCM_CSCMR1             0x1c
+#define CCM_CDHIPR             0x48
+
+#define L2_CACHE_SYNC          0x730
+
+#define BUSFREQ_INFO_FREQ_OFFSET               0x0
+#define BUSFREQ_INFO_DDR_SETTINGS_OFFSET       0x4
+#define BUSFREQ_INFO_DLL_OFF_OFFSET            0x8
+#define BUSFREQ_INFO_IOMUX_OFFSETS_OFFSET      0xc
+#define BUSFREQ_INFO_MU_DELAY_OFFSET           0x10
+
+.extern iram_tlb_phys_addr
+
+       .align 3
+
+       .macro do_delay
+
+1:
+       ldr     r9, =0
+2:
+       ldr     r10, [r4, r9]
+       add     r9, r9, #4
+       cmp     r9, #16
+       bne     2b
+       sub     r8, r8, #1
+       cmp     r8, #0
+       bgt     1b
+
+       .endm
+
+       .macro wait_for_ccm_handshake
+
+3:
+       ldr     r8, [r5, #CCM_CDHIPR]
+       cmp     r8, #0
+       bne     3b
+
+       .endm
+
+       .macro  switch_to_400MHz
+
+       /* check whether periph2_clk is already from top path */
+       ldr     r8, [r5, #CCM_CBCDR]
+       ands    r8, #(1 << 26)
+       beq     skip_periph2_clk2_switch_400m
+
+       /* now switch periph2_clk back. */
+       ldr     r8, [r5, #CCM_CBCDR]
+       bic     r8, r8, #(1 << 26)
+       str     r8, [r5, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       /*
+        * on i.MX6SX, pre_periph2_clk will be always from
+        * pll2_pfd2, so no need to set pre_periph2_clk
+        * parent, just set the mmdc divider directly.
+        */
+skip_periph2_clk2_switch_400m:
+
+       /* fabric_mmdc_podf to 0 */
+       ldr     r8, [r5, #CCM_CBCDR]
+       bic     r8, r8, #(0x7 << 3)
+       str     r8, [r5, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       .endm
+
+       .macro  switch_to_50MHz
+
+       /* check whether periph2_clk is already from top path */
+       ldr     r8, [r5, #CCM_CBCDR]
+       ands    r8, #(1 << 26)
+       beq     skip_periph2_clk2_switch_50m
+
+       /* now switch periph2_clk back. */
+       ldr     r8, [r5, #CCM_CBCDR]
+       bic     r8, r8, #(1 << 26)
+       str     r8, [r5, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       /*
+        * on i.MX6SX, pre_periph2_clk will be always from
+        * pll2_pfd2, so no need to set pre_periph2_clk
+        * parent, just set the mmdc divider directly.
+        */
+skip_periph2_clk2_switch_50m:
+
+       /* fabric_mmdc_podf to 7 so that mmdc is 400 / 8 = 50MHz */
+       ldr     r8, [r5, #CCM_CBCDR]
+       orr     r8, r8, #(0x7 << 3)
+       str     r8, [r5, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       .endm
+
+       .macro  switch_to_24MHz
+
+       /* periph2_clk2 sel to OSC_CLK */
+       ldr     r8, [r5, #CCM_CBCMR]
+       orr     r8, r8, #(1 << 20)
+       str     r8, [r5, #CCM_CBCMR]
+
+       /* periph2_clk2_podf to 0 */
+       ldr     r8, [r5, #CCM_CBCDR]
+       bic     r8, r8, #0x7
+       str     r8, [r5, #CCM_CBCDR]
+
+       /* periph2_clk sel to periph2_clk2 */
+       ldr     r8, [r5, #CCM_CBCDR]
+       orr     r8, r8, #(0x1 << 26)
+       str     r8, [r5, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       /* fabric_mmdc_podf to 0 */
+       ldr     r8, [r5, #CCM_CBCDR]
+       bic     r8, r8, #(0x7 << 3)
+       str     r8, [r5, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       .endm
+
+/*
+ *  imx6sx_ddr3_freq_change
+ *
+ *  idle the processor (eg, wait for interrupt).
+ *  make sure DDR is in self-refresh.
+ *  IRQs are already disabled.
+ */
+ENTRY(imx6sx_ddr3_freq_change)
+
+imx6sx_ddr3_freq_change_start:
+       stmfd   sp!, {r4 - r11}
+
+       ldr     r1, [r0, #BUSFREQ_INFO_DDR_SETTINGS_OFFSET]
+       ldr     r2, [r0, #BUSFREQ_INFO_DLL_OFF_OFFSET]
+       ldr     r3, [r0, #BUSFREQ_INFO_IOMUX_OFFSETS_OFFSET]
+
+       /*
+        * To ensure no page table walks occur in DDR, we
+        * have a another page table stored in IRAM that only
+        * contains entries pointing to IRAM, AIPS1 and AIPS2.
+        * We need to set the TTBR1 to the new IRAM TLB.
+        * Do the following steps:
+        * 1. Flush the Branch Target Address Cache (BTAC)
+        * 2. Set TTBR1 to point to IRAM page table.
+        * 3. Disable page table walks in TTBR0 (PD0 = 1)
+        * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
+        *    and 2-4G is translated by TTBR1.
+        */
+
+       ldr     r6, =iram_tlb_phys_addr
+       ldr     r7, [r6]
+
+       /* Disable Branch Prediction, Z bit in SCTLR. */
+       mrc     p15, 0, r6, c1, c0, 0
+       bic     r6, r6, #0x800
+       mcr     p15, 0, r6, c1, c0, 0
+
+       /* Flush the Branch Target Address Cache (BTAC) */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c7, c1, 6
+
+       dsb
+       isb
+
+       /* Store the IRAM table in TTBR1 */
+       mcr     p15, 0, r7, c2, c0, 1
+
+       /* Read TTBCR and set PD0=1, N = 1 */
+       mrc     p15, 0, r6, c2, c0, 2
+       orr     r6, r6, #0x11
+       mcr     p15, 0, r6, c2, c0, 2
+
+       dsb
+       isb
+
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       dsb
+       isb
+
+       /* Disable L1 data cache. */
+       mrc     p15, 0, r6, c1, c0, 0
+       bic     r6, r6, #0x4
+       mcr     p15, 0, r6, c1, c0, 0
+
+       ldr     r4, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
+       ldr     r5, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
+       ldr     r6, =IMX_IO_P2V(MX6Q_IOMUXC_BASE_ADDR)
+
+#ifdef CONFIG_CACHE_L2X0
+       /*
+        * make sure the L2 buffers are drained,
+        * sync operation on L2 drains the buffers.
+        */
+       ldr     r8, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
+
+       /* Wait for background operations to complete. */
+wait_for_l2_to_idle:
+       ldr     r7, [r8, #0x730]
+       cmp     r7, #0x0
+       bne     wait_for_l2_to_idle
+
+       mov     r7, #0x0
+       str     r7, [r8, #L2_CACHE_SYNC]
+
+       /* Disable L2. */
+       mov     r7, #0x0
+       str     r7, [r8, #0x100]
+
+       /*
+        * The second dsb might be needed to keep cache sync (device write)
+        * ordering with the memory accesses before it.
+        */
+       dsb
+       isb
+#endif
+
+       /* disable automatic power saving. */
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       orr     r8, r8, #0x1
+       str     r8, [r4, #MMDC0_MAPSR]
+
+       /* disable MMDC power down timer. */
+       ldr     r8, [r4, #MMDC0_MDPDC]
+       bic     r8, r8, #(0xff << 8)
+       str     r8, [r4, #MMDC0_MDPDC]
+
+       /* delay for a while */
+       ldr     r8, =4
+       do_delay
+
+       /* set CON_REG */
+       ldr     r8, =0x8000
+       str     r8, [r4, #MMDC0_MDSCR]
+poll_conreq_set_1:
+       ldr     r8, [r4, #MMDC0_MDSCR]
+       and     r8, r8, #(0x4 << 12)
+       cmp     r8, #(0x4 << 12)
+       bne     poll_conreq_set_1
+
+       /*
+        * if requested frequency is greater than
+        * 300MHz go to DLL on mode.
+        */
+       ldr     r8, [r0, #BUSFREQ_INFO_FREQ_OFFSET]
+       ldr     r9, =300000000
+       cmp     r8, r9
+       bge     dll_on_mode
+
+dll_off_mode:
+       /* if DLL is currently on, turn it off. */
+       cmp     r2, #1
+       beq     continue_dll_off_1
+
+       ldr     r8, =0x00018031
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x00018039
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =10
+       do_delay
+
+continue_dll_off_1:
+       /* set DVFS - enter self refresh mode */
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       orr     r8, r8, #(1 << 21)
+       str     r8, [r4, #MMDC0_MAPSR]
+
+       /* de-assert con_req */
+       mov     r8, #0x0
+       str     r8, [r4, #MMDC0_MDSCR]
+
+poll_dvfs_set_1:
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       and     r8, r8, #(1 << 25)
+       cmp     r8, #(1 << 25)
+       bne     poll_dvfs_set_1
+
+       ldr     r8, [r0, #BUSFREQ_INFO_FREQ_OFFSET]
+       ldr     r9, =24000000
+       cmp     r8, r9
+       beq     switch_freq_24
+
+       switch_to_50MHz
+       b       continue_dll_off_2
+
+switch_freq_24:
+       switch_to_24MHz
+
+continue_dll_off_2:
+       /* set SBS - block ddr accesses */
+       ldr     r8, [r4, #MMDC0_MADPCR0]
+       orr     r8, r8, #(1 << 8)
+       str     r8, [r4, #MMDC0_MADPCR0]
+
+       /* clear DVFS - exit from self refresh mode */
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       bic     r8, r8, #(1 << 21)
+       str     r8, [r4, #MMDC0_MAPSR]
+
+poll_dvfs_clear_1:
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       and     r8, r8, #(1 << 25)
+       cmp     r8, #(1 << 25)
+       beq     poll_dvfs_clear_1
+
+       /* if DLL was previously on, continue DLL off routine. */
+       cmp     r2, #1
+       beq     continue_dll_off_3
+
+       ldr     r8, =0x00018031
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x00018039
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x04208030
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x04208038
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x00088032
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x0008803A
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       /* delay for a while. */
+       ldr     r8, =4
+       do_delay
+
+       ldr     r8, [r4, #MMDC0_MDCF0]
+       bic     r8, r8, #0xf
+       orr     r8, r8, #0x3
+       str     r8, [r4, #MMDC0_MDCF0]
+
+       ldr     r8, [r4, #MMDC0_MDCF1]
+       bic     r8, r8, #0x7
+       orr     r8, r8, #0x4
+       str     r8, [r4, #MMDC0_MDCF1]
+
+       ldr     r8, =0x00091680
+       str     r8, [r4, #MMDC0_MDMISC]
+
+       /* enable dqs pull down in the IOMUX. */
+       ldr     r8, [r3]
+       add     r3, r3, #8
+       ldr     r9, =0x3028
+update_iomux:
+       ldr     r10, [r3]
+       ldr     r11, [r6, r10]
+       bic     r11, r11, r9
+       orr     r11, r11, #(0x3 << 12)
+       orr     r11, r11, #0x28
+       str     r11, [r6, r10]
+       add     r3, r3, #8
+       sub     r8, r8, #1
+       cmp     r8, #0
+       bgt     update_iomux
+
+       /*  ODT disabled. */
+       ldr     r8, =0x0
+       str     r8, [r4, #MMDC0_MPODTCTRL]
+
+       /* DQS gating disabled. */
+       ldr     r8, [r4, #MMDC0_MPDGCTRL0]
+       orr     r8, r8, #(1 << 29)
+       str     r8, [r4, #MMDC0_MPDGCTRL0]
+
+       /* Add workaround for ERR005778.*/
+       /* double the original MU_UNIT_DEL_NUM. */
+       ldr     r8, [r0, #BUSFREQ_INFO_MU_DELAY_OFFSET]
+       lsl     r8, r8, #1
+
+       /* Bypass the automatic MU by setting the mu_byp_en */
+       ldr     r10, [r4, #MMDC0_MPMUR0]
+       orr     r10, r10, #0x400
+       /* Set the MU_BYP_VAL */
+       orr     r10, r10, r8
+       str     r10, [r4, #MMDC0_MPMUR0]
+
+       /* Now perform a force measure */
+       ldr     r8, [r4, #MMDC0_MPMUR0]
+       orr     r8, r8, #0x800
+       str     r8, [r4, #MMDC0_MPMUR0]
+       /* Wait for FRC_MSR to clear. */
+1:
+       ldr     r8, [r4, #MMDC0_MPMUR0]
+       and     r8, r8, #0x800
+       cmp     r8, #0x0
+       bne     1b
+
+continue_dll_off_3:
+       /* clear SBS - unblock accesses to DDR. */
+       ldr     r8, [r4, #MMDC0_MADPCR0]
+       bic     r8, r8, #(0x1 << 8)
+       str     r8, [r4, #MMDC0_MADPCR0]
+
+       mov     r8, #0x0
+       str     r8, [r4, #MMDC0_MDSCR]
+poll_conreq_clear_1:
+       ldr     r8, [r4, #MMDC0_MDSCR]
+       and     r8, r8, #(0x4 << 12)
+       cmp     r8, #(0x4 << 12)
+       beq     poll_conreq_clear_1
+
+       b       done
+
+dll_on_mode:
+       /* assert DVFS - enter self refresh mode. */
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       orr     r8, r8, #(1 << 21)
+       str     r8, [r4, #MMDC0_MAPSR]
+
+       /* de-assert CON_REQ. */
+       mov     r8, #0x0
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       /* poll DVFS ack. */
+poll_dvfs_set_2:
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       and     r8, r8, #(1 << 25)
+       cmp     r8, #(1 << 25)
+       bne     poll_dvfs_set_2
+
+       switch_to_400MHz
+
+       /* set SBS step-by-step mode. */
+       ldr     r8, [r4, #MMDC0_MADPCR0]
+       orr     r8, r8, #(1 << 8)
+       str     r8, [r4, #MMDC0_MADPCR0]
+
+       /* clear DVFS - exit self refresh mode. */
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       bic     r8, r8, #(1 << 21)
+       str     r8, [r4, #MMDC0_MAPSR]
+
+poll_dvfs_clear_2:
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       ands    r8, r8, #(1 << 25)
+       bne     poll_dvfs_clear_2
+
+       /* if DLL is currently off, turn it back on. */
+       cmp     r2, #0
+       beq     update_calibration_only
+
+       ldr     r8, =0xa5390003
+       str     r8, [r4, #MMDC0_MPZQHWCTRL]
+
+       /* enable DQS gating. */
+       ldr     r10, =MMDC0_MPDGCTRL0
+       ldr     r8, [r4, r10]
+       bic     r8, r8, #(1 << 29)
+       str     r8, [r4, r10]
+
+       /* Now perform a force measure */
+       ldr     r8, =0x00000800
+       str     r8, [r4, #MMDC0_MPMUR0]
+       /* Wait for FRC_MSR to clear. */
+1:
+       ldr     r8, [r4, #MMDC0_MPMUR0]
+       and     r8, r8, #0x800
+       cmp     r8, #0x0
+       bne     1b
+
+       /* disable dqs pull down in the IOMUX. */
+       ldr     r8, [r3]
+       add     r3, r3, #8
+update_iomux1:
+       ldr     r10, [r3, #0x0]
+       ldr     r11, [r3, #0x4]
+       str     r11, [r6, r10]
+       add     r3, r3, #8
+       sub     r8, r8, #1
+       cmp     r8, #0
+       bgt     update_iomux1
+
+       /* config MMDC timings to 400MHz. */
+       ldr     r1, [r0, #BUSFREQ_INFO_DDR_SETTINGS_OFFSET]
+       ldr     r7, [r1]
+       add     r1, r1, #8
+       ldr     r10, [r1, #0x0]
+       ldr     r11, [r1, #0x4]
+       str     r11, [r4, r10]
+       add     r1, r1, #8
+
+       ldr     r10, [r1, #0x0]
+       ldr     r11, [r1, #0x4]
+       str     r11, [r4, r10]
+       add     r1, r1, #8
+
+       /* update MISC register: WALAT, RALAT */
+       ldr     r8, =0x00081740
+       str     r8, [r4, #MMDC0_MDMISC]
+
+       /* configure ddr devices to dll on, odt. */
+       ldr     r8, =0x00028031
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x00028039
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       /* delay for while. */
+       ldr     r8, =4
+       do_delay
+
+       /* reset dll. */
+       ldr     r8, =0x09208030
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x09208038
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       /* delay for while. */
+       ldr     r8, =100
+       do_delay
+
+       ldr     r10, [r1, #0x0]
+       ldr     r11, [r1, #0x4]
+       str     r11, [r4, r10]
+       add     r1, r1, #8
+
+       ldr     r10, [r1, #0x0]
+       ldr     r11, [r1, #0x4]
+       str     r11, [r4, r10]
+       add     r1, r1, #8
+
+       ldr     r8, =0x00428031
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x00428039
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r10, [r1, #0x0]
+       ldr     r11, [r1, #0x4]
+       str     r11, [r4, r10]
+       add     r1, r1, #8
+
+       ldr     r10, [r1, #0x0]
+       ldr     r11, [r1, #0x4]
+       str     r11, [r4, r10]
+       add     r1, r1, #8
+
+       /* issue a zq command. */
+       ldr     r8, =0x04008040
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       ldr     r8, =0x04008048
+       str     r8, [r4, #MMDC0_MDSCR]
+
+       /* MMDC ODT enable. */
+       ldr     r10, [r1, #0x0]
+       ldr     r11, [r1, #0x4]
+       str     r11, [r4, r10]
+       add     r1, r1, #8
+
+       /* delay for while. */
+       ldr     r8, =40
+       do_delay
+
+       /* enable MMDC power down timer. */
+       ldr     r8, [r4, #MMDC0_MDPDC]
+       orr     r8, r8, #(0x55 << 8)
+       str     r8, [r4, #MMDC0_MDPDC]
+
+       b       update_calibration
+
+update_calibration_only:
+       ldr     r8, [r1]
+       sub     r8, r8, #7
+       add     r1, r1, #64
+       b       update_calib
+
+update_calibration:
+       /* write the new calibration values. */
+       mov     r8, r7
+       sub     r8, r8, #7
+
+update_calib:
+       ldr     r10, [r1, #0x0]
+       ldr     r11, [r1, #0x4]
+       str     r11, [r4, r10]
+       add     r1, r1, #8
+       sub     r8, r8, #1
+       cmp     r8, #0
+       bgt     update_calib
+
+       /* perform a force measurement. */
+       ldr     r8, =0x800
+       str     r8, [r4, #MMDC0_MPMUR0]
+       /* Wait for FRC_MSR to clear. */
+1:
+       ldr     r8, [r4, #MMDC0_MPMUR0]
+       and     r8, r8, #0x800
+       cmp     r8, #0x0
+       bne     1b
+
+       /* clear SBS - unblock DDR accesses. */
+       ldr     r8, [r4, #MMDC0_MADPCR0]
+       bic     r8, r8, #(1 << 8)
+       str     r8, [r4, #MMDC0_MADPCR0]
+
+       mov     r8, #0x0
+       str     r8, [r4, #MMDC0_MDSCR]
+poll_conreq_clear_2:
+       ldr     r8, [r4, #MMDC0_MDSCR]
+       and     r8, r8, #(0x4 << 12)
+       cmp     r8, #(0x4 << 12)
+       beq     poll_conreq_clear_2
+
+done:
+
+       /* MMDC0_MAPSR adopt power down enable. */
+       ldr     r8, [r4, #MMDC0_MAPSR]
+       bic     r8, r8, #0x01
+       str     r8, [r4, #MMDC0_MAPSR]
+
+#ifdef CONFIG_CACHE_L2X0
+       /* Enable L2. */
+       ldr     r8, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
+       ldr     r7, =0x1
+       str     r7, [r8, #0x100]
+#endif
+
+       /* Enable L1 data cache. */
+       mrc     p15, 0, r7, c1, c0, 0
+       orr     r7, r7, #0x4
+       mcr     p15, 0, r7, c1, c0, 0
+
+       /* Restore the TTBCR */
+       dsb
+       isb
+
+       /* Read TTBCR and set PD0=0, N = 0 */
+       mrc     p15, 0, r6, c2, c0, 2
+       bic     r6, r6, #0x11
+       mcr     p15, 0, r6, c2, c0, 2
+
+       dsb
+       isb
+
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       dsb
+       isb
+
+       /* Enable Branch Prediction, Z bit in SCTLR. */
+       mrc     p15, 0, r7, c1, c0, 0
+       orr     r7, r7, #0x800
+       mcr     p15, 0, r7, c1, c0, 0
+
+       /* Flush the Branch Target Address Cache (BTAC) */
+       ldr     r7, =0x0
+       mcr     p15, 0, r7, c7, c1, 6
+
+       /* restore registers */
+       ldmfd   sp!, {r4 - r11}
+       mov     pc, lr
+
+       /*
+        * Add ltorg here to ensure that all
+        * literals are stored here and are
+        * within the text space.
+        */
+       .ltorg
+imx6sx_ddr3_freq_change_end:
+ENDPROC(imx6sx_ddr3_freq_change)
diff --git a/arch/arm/mach-imx/lpddr2_freq_imx6.S b/arch/arm/mach-imx/lpddr2_freq_imx6.S
new file mode 100644 (file)
index 0000000..7114789
--- /dev/null
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2012-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/linkage.h>
+#include "hardware.h"
+
+.globl imx6_lpddr2_freq_change_start
+.globl imx6_lpddr2_freq_change_end
+
+       .macro    mx6sl_switch_to_24MHz
+
+       /*
+        * Set MMDC clock to be sourced from PLL3.
+        * Ensure first periph2_clk2 is sourced from PLL3.
+        * Set the PERIPH2_CLK2_PODF to divide by 2.
+        */
+       ldr     r6, [r2, #0x14]
+       bic     r6, r6, #0x7
+       orr     r6, r6, #0x1
+       str     r6, [r2, #0x14]
+
+       /* Select PLL3 to source MMDC. */
+       ldr     r6, [r2, #0x18]
+       bic     r6, r6, #0x100000
+       str     r6, [r2, #0x18]
+
+       /* Swtich periph2_clk_sel to run from PLL3. */
+       ldr     r6, [r2, #0x14]
+       orr     r6, r6, #0x4000000
+       str     r6, [r2, #0x14]
+
+periph2_clk_switch1:
+       ldr     r6, [r2, #0x48]
+       cmp     r6, #0
+       bne     periph2_clk_switch1
+
+       /*
+        * Need to clock gate the 528 PFDs before
+        * powering down PLL2.
+        * Only the PLL2_PFD2_400M should be ON
+        * at this time, so only clock gate that one.
+        */
+       ldr     r6, [r3, #0x100]
+       orr     r6, r6, #0x800000
+       str     r6, [r3, #0x100]
+
+       /*
+        * Set PLL2 to bypass state. We should be here
+        * only if MMDC is not sourced from PLL2.
+        */
+       ldr     r6, [r3, #0x30]
+       orr     r6, r6, #0x10000
+       str     r6, [r3, #0x30]
+
+       ldr     r6, [r3, #0x30]
+       orr     r6, r6, #0x1000
+       str     r6, [r3, #0x30]
+
+       /* Ensure pre_periph2_clk_mux is set to pll2 */
+       ldr     r6, [r2, #0x18]
+       bic     r6, r6, #0x600000
+       str     r6, [r2, #0x18]
+
+       /* Set MMDC clock to be sourced from the bypassed PLL2. */
+       ldr     r6, [r2, #0x14]
+       bic     r6, r6, #0x4000000
+       str     r6, [r2, #0x14]
+
+periph2_clk_switch2:
+       ldr     r6, [r2, #0x48]
+       cmp     r6, #0
+       bne     periph2_clk_switch2
+
+       /*
+        * Now move MMDC back to periph2_clk2 source.
+        * after selecting PLL2 as the option.
+        * Select PLL2 as the source.
+        */
+       ldr     r6, [r2, #0x18]
+       orr     r6, r6, #0x100000
+       str     r6, [r2, #0x18]
+
+       /* set periph2_clk2_podf to divide by 1. */
+       ldr     r6, [r2, #0x14]
+       bic     r6, r6, #0x7
+       str     r6, [r2, #0x14]
+
+       /* Now move periph2_clk to periph2_clk2 source */
+       ldr     r6, [r2, #0x14]
+       orr     r6, r6, #0x4000000
+       str     r6, [r2, #0x14]
+
+periph2_clk_switch3:
+       ldr     r6, [r2, #0x48]
+       cmp     r6, #0
+       bne     periph2_clk_switch3
+
+       /* Now set the MMDC PODF back to 1.*/
+       ldr     r6, [r2, #0x14]
+       bic     r6, r6, #0x38
+       str     r6, [r2, #0x14]
+
+mmdc_podf0:
+       ldr     r6, [r2, #0x48]
+       cmp     r6, #0
+       bne     mmdc_podf0
+
+       .endm
+
+         .macro        ddr_switch_400MHz
+
+       /* Set MMDC divider first, in case PLL3 is at 480MHz. */
+       ldr     r6, [r3, #0x10]
+       and     r6, r6, #0x10000
+       cmp     r6, #0x10000
+       beq     pll3_in_bypass
+
+       /* Set MMDC divder to divide by 2. */
+       ldr     r6, [r2, #0x14]
+       bic     r6, r6, #0x38
+       orr     r6, r6, #0x8
+       str     r6, [r2, #0x14]
+
+mmdc_podf:
+       ldr     r6, [r2, #0x48]
+       cmp     r6, #0
+       bne     mmdc_podf
+
+pll3_in_bypass:
+       /*
+        * Check if we are switching between
+        * 400Mhz <-> 100MHz.If so, we should
+        * try to source MMDC from PLL2_200M.
+        */
+       cmp     r1, #0
+       beq     not_low_bus_freq
+
+       /* Ensure that MMDC is sourced from PLL2 mux first. */
+       ldr     r6, [r2, #0x14]
+       bic     r6, r6, #0x4000000
+       str     r6, [r2, #0x14]
+
+periph2_clk_switch4:
+       ldr     r6, [r2, #0x48]
+       cmp     r6, #0
+       bne     periph2_clk_switch4
+
+not_low_bus_freq:
+       /* Now ensure periph2_clk2_sel mux is set to PLL3 */
+       ldr     r6, [r2, #0x18]
+       bic     r6, r6, #0x100000
+       str     r6, [r2, #0x18]
+
+       /* Now switch MMDC to PLL3. */
+       ldr     r6, [r2, #0x14]
+       orr     r6, r6, #0x4000000
+       str     r6, [r2, #0x14]
+
+periph2_clk_switch5:
+       ldr     r6, [r2, #0x48]
+       cmp     r6, #0
+       bne     periph2_clk_switch5
+
+       /*
+        * Check if PLL2 is already unlocked.
+        * If so do nothing with PLL2.
+        */
+       cmp     r1, #0
+       beq     pll2_already_on
+
+       /* Now power up PLL2 and unbypass it. */
+       ldr     r6, [r3, #0x30]
+       bic     r6, r6, #0x1000
+       str     r6, [r3, #0x30]
+
+       /* Make sure PLL2 has locked.*/
+wait_for_pll_lock:
+       ldr     r6, [r3, #0x30]
+       and     r6, r6, #0x80000000
+       cmp     r6, #0x80000000
+       bne     wait_for_pll_lock
+
+       ldr     r6, [r3, #0x30]
+       bic     r6, r6, #0x10000
+       str     r6, [r3, #0x30]
+
+       /*
+        * Need to enable the 528 PFDs after
+        * powering up PLL2.
+        * Only the PLL2_PFD2_400M should be ON
+        * as it feeds the MMDC. Rest should have
+        * been managed by clock code.
+        */
+       ldr     r6, [r3, #0x100]
+       bic     r6, r6, #0x800000
+       str     r6, [r3, #0x100]
+
+pll2_already_on:
+       /*
+        * Now switch MMDC clk back to pll2_mux option.
+        * Ensure pre_periph2_clk2 is set to pll2_pfd_400M.
+        * If switching to audio DDR freq, set the
+        * pre_periph2_clk2 to PLL2_PFD_200M
+        */
+       ldr     r6, =400000000
+       cmp     r6, r0
+       bne     use_pll2_pfd_200M
+
+       ldr     r6, [r2, #0x18]
+       bic     r6, r6, #0x600000
+       orr     r6, r6, #0x200000
+       str     r6, [r2, #0x18]
+       ldr     r6, =400000000
+       b       cont2
+
+use_pll2_pfd_200M:
+       ldr     r6, [r2, #0x18]
+       orr     r6, r6, #0x600000
+       str     r6, [r2, #0x18]
+       ldr     r6, =200000000
+
+cont2:
+       ldr     r4, [r2, #0x14]
+       bic     r4, r4, #0x4000000
+       str     r4, [r2, #0x14]
+
+periph2_clk_switch6:
+       ldr     r4, [r2, #0x48]
+       cmp     r4, #0
+       bne     periph2_clk_switch6
+
+change_divider_only:
+       /*
+        * Calculate the MMDC divider
+        * based on the requested freq.
+        */
+       ldr     r4, =0
+Loop2:
+       sub     r6, r6, r0
+       cmp     r6, r0
+       blt     Div_Found
+       add     r4, r4, #1
+       bgt     Loop2
+
+       /* Shift divider into correct offset. */
+       lsl     r4, r4, #3
+Div_Found:
+       /* Set the MMDC PODF. */
+       ldr     r6, [r2, #0x14]
+       bic     r6, r6, #0x38
+       orr     r6, r6, r4
+       str     r6, [r2, #0x14]
+
+mmdc_podf1:
+       ldr     r6, [r2, #0x48]
+       cmp     r6, #0
+       bne     mmdc_podf1
+
+       .endm
+
+       .macro  mmdc_clk_lower_100MHz
+
+       /*
+        * Prior to reducing the DDR frequency (at 528/400 MHz),
+        * read the Measure unit count bits (MU_UNIT_DEL_NUM)
+        */
+       ldr     r5, =0x8B8
+       ldr     r6, [r8, r5]
+       /* Original MU unit count */
+       mov     r6, r6, LSR #16
+       ldr     r4, =0x3FF
+       and     r6, r6, r4
+       /* Original MU unit count * 2 */
+       mov     r7, r6, LSL #1
+       /*
+        * Bypass the automatic measure unit when below 100 MHz
+        * by setting the Measure unit bypass enable bit (MU_BYP_EN)
+        */
+       ldr     r6, [r8, r5]
+       orr     r6, r6, #0x400
+       str     r6, [r8, r5]
+       /*
+        * Double the measure count value read in step 1 and program it in the
+        * measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
+        * Register for the reduced frequency operation below 100 MHz
+        */
+       ldr     r6, [r8, r5]
+       ldr     r4, =0x3FF
+       bic     r6, r6, r4
+       orr     r6, r6, r7
+       str     r6, [r8, r5]
+       /* Now perform a Force Measurement. */
+       ldr     r6, [r8, r5]
+       orr     r6, r6, #0x800
+       str     r6, [r8, r5]
+       /* Wait for FRC_MSR to clear. */
+force_measure:
+       ldr     r6, [r8, r5]
+       and     r6, r6, #0x800
+       cmp     r6, #0x0
+       bne     force_measure
+
+       .endm
+
+       .macro  mmdc_clk_above_100MHz
+
+       /* Make sure that the PHY measurement unit is NOT in bypass mode */
+       ldr     r5, =0x8B8
+       ldr     r6, [r8, r5]
+       bic     r6, r6, #0x400
+       str     r6, [r8, r5]
+       /* Now perform a Force Measurement. */
+       ldr     r6, [r8, r5]
+       orr     r6, r6, #0x800
+       str     r6, [r8, r5]
+       /* Wait for FRC_MSR to clear. */
+force_measure1:
+       ldr     r6, [r8, r5]
+       and     r6, r6, #0x800
+       cmp     r6, #0x0
+       bne     force_measure1
+       .endm
+
+/*
+ *  mx6_lpddr2_freq_change
+ *
+ *  Make sure DDR is in self-refresh.
+ *  IRQs are already disabled.
+ * r0 : DDR freq.
+ * r1: low_bus_freq_mode flag
+ */
+       .align 3
+ENTRY(mx6_lpddr2_freq_change)
+imx6_lpddr2_freq_change_start:
+       push {r4-r10}
+
+       /*
+        * To ensure no page table walks occur in DDR, we
+        * have a another page table stored in IRAM that only
+        * contains entries pointing to IRAM, AIPS1 and AIPS2.
+        * We need to set the TTBR1 to the new IRAM TLB.
+        * Do the following steps:
+        * 1. Flush the Branch Target Address Cache (BTAC)
+        * 2. Set TTBR1 to point to IRAM page table.
+        * 3. Disable page table walks in TTBR0 (PD0 = 1)
+        * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
+        *     and 2-4G is translated by TTBR1.
+        */
+
+       ldr     r6, =iram_tlb_phys_addr
+       ldr     r7, [r6]
+
+       /* Disable Branch Prediction, Z bit in SCTLR. */
+       mrc     p15, 0, r6, c1, c0, 0
+       bic     r6, r6, #0x800
+       mcr     p15, 0, r6, c1, c0, 0
+
+       /* Flush the Branch Target Address Cache (BTAC) */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c7, c1, 6
+
+       dsb
+       isb
+       /* Store the IRAM table in TTBR1 */
+       mcr     p15, 0, r7, c2, c0, 1
+
+       /* Read TTBCR and set PD0=1, N = 1 */
+       mrc     p15, 0, r6, c2, c0, 2
+       orr     r6, r6, #0x11
+       mcr     p15, 0, r6, c2, c0, 2
+
+       dsb
+       isb
+
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       /* Disable L1 data cache. */
+       mrc     p15, 0, r6, c1, c0, 0
+       bic     r6, r6, #0x4
+       mcr     p15, 0, r6, c1, c0, 0
+
+       dsb
+       isb
+
+
+#ifdef CONFIG_CACHE_L2X0
+       /*
+        * Need to make sure the buffers in L2 are drained.
+        * Performing a sync operation does this.
+        */
+       ldr     r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
+
+       /* Wait for background operations to complete. */
+wait_for_l2_to_idle:
+       ldr     r6, [r7, #0x730]
+       cmp     r6, #0x0
+       bne     wait_for_l2_to_idle
+
+       mov     r6, #0x0
+       str     r6, [r7, #0x730]
+
+       /*
+        * The second dsb might be needed to keep cache sync (device write)
+        * ordering with the memory accesses before it.
+        */
+       dsb
+       isb
+
+       /* Disable L2. */
+       str     r6, [r7, #0x100]
+#endif
+
+       ldr     r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR)
+       ldr     r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
+       ldr     r8, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
+
+       /* Disable Automatic power savings. */
+       ldr     r6, [r8, #0x404]
+       orr     r6, r6, #0x01
+       str     r6, [r8, #0x404]
+
+       /* MMDC0_MDPDC disable power down timer */
+       ldr     r6, [r8, #0x4]
+       bic     r6, r6, #0xff00
+       str     r6, [r8, #0x4]
+
+       /* Delay for a while */
+       ldr     r10, =10
+delay1:
+       ldr     r7, =0
+cont1:
+       ldr     r6, [r8, r7]
+       add     r7, r7, #4
+       cmp     r7, #16
+       bne     cont1
+       sub     r10, r10, #1
+       cmp     r10, #0
+       bgt     delay1
+
+       /* Make the DDR explicitly enter self-refresh. */
+       ldr     r6, [r8, #0x404]
+       orr     r6, r6, #0x200000
+       str     r6, [r8, #0x404]
+
+poll_dvfs_set_1:
+       ldr     r6, [r8, #0x404]
+       and     r6, r6, #0x2000000
+       cmp     r6, #0x2000000
+       bne     poll_dvfs_set_1
+
+       /* set SBS step-by-step mode */
+       ldr     r6, [r8, #0x410]
+       orr     r6, r6, #0x100
+       str     r6, [r8, #0x410]
+
+       ldr     r10, =100000000
+       cmp     r0, r10
+       bgt     set_ddr_mu_above_100
+       mmdc_clk_lower_100MHz
+
+set_ddr_mu_above_100:
+       ldr     r10, =24000000
+       cmp     r0, r10
+       beq     set_to_24MHz
+
+       ddr_switch_400MHz
+
+       ldr     r10,=100000000
+       cmp     r0, r10
+       blt     done
+       mmdc_clk_above_100MHz
+
+       b       done
+
+set_to_24MHz:
+       mx6sl_switch_to_24MHz
+
+done:
+       /* clear DVFS - exit from self refresh mode */
+       ldr     r6, [r8, #0x404]
+       bic     r6, r6, #0x200000
+       str     r6, [r8, #0x404]
+
+poll_dvfs_clear_1:
+       ldr     r6, [r8, #0x404]
+       and     r6, r6, #0x2000000
+       cmp     r6, #0x2000000
+       beq     poll_dvfs_clear_1
+
+       /* Enable Automatic power savings. */
+       ldr     r6, [r8, #0x404]
+       bic     r6, r6, #0x01
+       str     r6, [r8, #0x404]
+
+       ldr     r10, =24000000
+       cmp     r0, r10
+       beq     skip_power_down
+
+       /* Enable MMDC power down timer. */
+       ldr     r6, [r8, #0x4]
+       orr     r6, r6, #0x5500
+       str     r6, [r8, #0x4]
+
+skip_power_down:
+       /* clear SBS - unblock DDR accesses */
+       ldr     r6, [r8, #0x410]
+       bic     r6, r6, #0x100
+       str     r6, [r8, #0x410]
+
+#ifdef CONFIG_CACHE_L2X0
+       /* Enable L2. */
+       ldr     r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
+       ldr     r6, =0x1
+       str     r6, [r7, #0x100]
+#endif
+
+       /* Enable L1 data cache. */
+       mrc     p15, 0, r6, c1, c0, 0
+       orr     r6, r6, #0x4
+       mcr     p15, 0, r6, c1, c0, 0
+
+       /* Restore the TTBCR */
+       dsb
+       isb
+
+       /* Read TTBCR and set PD0=0, N = 0 */
+       mrc     p15, 0, r6, c2, c0, 2
+       bic     r6, r6, #0x11
+       mcr     p15, 0, r6, c2, c0, 2
+       dsb
+       isb
+
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       dsb
+       isb
+
+       /* Enable Branch Prediction, Z bit in SCTLR. */
+       mrc     p15, 0, r6, c1, c0, 0
+       orr     r6, r6, #0x800
+       mcr     p15, 0, r6, c1, c0, 0
+
+       /* Flush the Branch Target Address Cache (BTAC) */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c7, c1, 6
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       pop {r4-r10}
+
+       /* Restore registers */
+       mov     pc, lr
+
+       /*
+        * Add ltorg here to ensure that all
+        * literals are stored here and are
+        * within the text space.
+        */
+       .ltorg
+imx6_lpddr2_freq_change_end:
diff --git a/arch/arm/mach-imx/lpddr2_freq_imx6sx.S b/arch/arm/mach-imx/lpddr2_freq_imx6sx.S
new file mode 100644 (file)
index 0000000..cf3e8fc
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/linkage.h>
+#include "hardware.h"
+
+#define CCM_CBCDR      0x14
+#define CCM_CBCMR      0x18
+#define CCM_CSCMR1     0x1c
+#define CCM_CDHIPR     0x48
+
+#define L2_CACHE_SYNC  0x730
+
+#define MMDC0_MDPDC    0x4
+#define MMDC0_MAPSR    0x404
+#define MMDC0_MADPCR0  0x410
+
+       .macro  wait_for_ccm_handshake
+
+1:
+       ldr     r8, [r2, #CCM_CDHIPR]
+       cmp     r8, #0
+       bne     1b
+
+       .endm
+
+       .macro  switch_to_24MHz
+
+       /* periph2_clk2 sel to OSC_CLK */
+       ldr     r8, [r2, #CCM_CBCMR]
+       orr     r8, r8, #(1 << 20)
+       str     r8, [r2, #CCM_CBCMR]
+
+       /* periph2_clk2_podf to 0 */
+       ldr     r8, [r2, #CCM_CBCDR]
+       bic     r8, r8, #0x7
+       str     r8, [r2, #CCM_CBCDR]
+
+       /* periph2_clk sel to periph2_clk2 */
+       ldr     r8, [r2, #CCM_CBCDR]
+       orr     r8, r8, #(0x1 << 26)
+       str     r8, [r2, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       /* fabric_mmdc_podf to 0 */
+       ldr     r8, [r2, #CCM_CBCDR]
+       bic     r8, r8, #(0x7 << 3)
+       str     r8, [r2, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       .endm
+
+       .macro  switch_to_100MHz
+
+       /* check whether periph2_clk is from top path */
+       ldr     r8, [r2, #CCM_CBCDR]
+       ands    r8, #(1 << 26)
+       beq     skip_periph2_clk2_switch_100m
+
+       /* now switch periph2_clk back. */
+       ldr     r8, [r2, #CCM_CBCDR]
+       bic     r8, r8, #(1 << 26)
+       str     r8, [r2, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       /*
+        * on i.MX6SX, pre_periph2_clk will be always from
+        * pll2_pfd2, so no need to set pre_periph2_clk
+        * parent, just set the mmdc divider directly.
+        */
+skip_periph2_clk2_switch_100m:
+
+       /* fabric_mmdc_podf to 3 so that mmdc is 400 / 4 = 100MHz */
+       ldr     r8, [r2, #CCM_CBCDR]
+       bic     r8, r8, #(0x7 << 3)
+       orr     r8, r8, #(0x3 << 3)
+       str     r8, [r2, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       .endm
+
+       .macro  switch_to_400MHz
+
+       /* check whether periph2_clk is from top path */
+       ldr     r8, [r2, #CCM_CBCDR]
+       ands    r8, #(1 << 26)
+       beq     skip_periph2_clk2_switch_400m
+
+       /* now switch periph2_clk back. */
+       ldr     r8, [r2, #CCM_CBCDR]
+       bic     r8, r8, #(1 << 26)
+       str     r8, [r2, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       /*
+        * on i.MX6SX, pre_periph2_clk will be always from
+        * pll2_pfd2, so no need to set pre_periph2_clk
+        * parent, just set the mmdc divider directly.
+        */
+skip_periph2_clk2_switch_400m:
+
+       /* fabric_mmdc_podf to 0 */
+       ldr     r8, [r2, #CCM_CBCDR]
+       bic     r8, r8, #(0x7 << 3)
+       str     r8, [r2, #CCM_CBCDR]
+
+       wait_for_ccm_handshake
+
+       .endm
+
+       .macro  mmdc_clk_lower_100MHz
+
+       /*
+        * Prior to reducing the DDR frequency (at 528/400 MHz),
+        * read the Measure unit count bits (MU_UNIT_DEL_NUM)
+        */
+       ldr     r8, =0x8B8
+       ldr     r6, [r5, r8]
+       /* Original MU unit count */
+       mov     r6, r6, LSR #16
+       ldr     r4, =0x3FF
+       and     r6, r6, r4
+       /* Original MU unit count * 2 */
+       mov     r7, r6, LSL #1
+       /*
+        * Bypass the automatic measure unit when below 100 MHz
+        * by setting the Measure unit bypass enable bit (MU_BYP_EN)
+        */
+       ldr     r6, [r5, r8]
+       orr     r6, r6, #0x400
+       str     r6, [r5, r8]
+       /*
+        * Double the measure count value read in step 1 and program it in the
+        * measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
+        * Register for the reduced frequency operation below 100 MHz
+        */
+       ldr     r6, [r5, r8]
+       ldr     r4, =0x3FF
+       bic     r6, r6, r4
+       orr     r6, r6, r7
+       str     r6, [r5, r8]
+       /* Now perform a Force Measurement. */
+       ldr     r6, [r5, r8]
+       orr     r6, r6, #0x800
+       str     r6, [r5, r8]
+       /* Wait for FRC_MSR to clear. */
+force_measure:
+       ldr     r6, [r5, r8]
+       and     r6, r6, #0x800
+       cmp     r6, #0x0
+       bne     force_measure
+
+       /* For freq lower than 100MHz, need to set RALAT to 2 */
+       ldr     r6, [r5, #0x18]
+       bic     r6, r6, #(0x7 << 6)
+       orr     r6, r6, #(0x2 << 6)
+       str     r6, [r5, #0x18]
+
+       .endm
+
+       .macro  mmdc_clk_above_100MHz
+
+       /* Make sure that the PHY measurement unit is NOT in bypass mode */
+       ldr     r8, =0x8B8
+       ldr     r6, [r5, r8]
+       bic     r6, r6, #0x400
+       str     r6, [r5, r8]
+       /* Now perform a Force Measurement. */
+       ldr     r6, [r5, r8]
+       orr     r6, r6, #0x800
+       str     r6, [r5, r8]
+       /* Wait for FRC_MSR to clear. */
+force_measure1:
+       ldr     r6, [r5, r8]
+       and     r6, r6, #0x800
+       cmp     r6, #0x0
+       bne     force_measure1
+
+       /* For freq higher than 100MHz, need to set RALAT to 5 */
+       ldr     r6, [r5, #0x18]
+       bic     r6, r6, #(0x7 << 6)
+       orr     r6, r6, #(0x5 << 6)
+       str     r6, [r5, #0x18]
+
+       .endm
+
+       .align 3
+ENTRY(imx6sx_lpddr2_freq_change)
+
+       push    {r2 - r8}
+
+       /*
+        * To ensure no page table walks occur in DDR, we
+        * have a another page table stored in IRAM that only
+        * contains entries pointing to IRAM, AIPS1 and AIPS2.
+        * We need to set the TTBR1 to the new IRAM TLB.
+        * Do the following steps:
+        * 1. Flush the Branch Target Address Cache (BTAC)
+        * 2. Set TTBR1 to point to IRAM page table.
+        * 3. Disable page table walks in TTBR0 (PD0 = 1)
+        * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
+        *     and 2-4G is translated by TTBR1.
+        */
+
+       ldr     r6, =iram_tlb_phys_addr
+       ldr     r7, [r6]
+
+       /* Flush the Branch Target Address Cache (BTAC) */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c7, c1, 6
+
+       /* Disable Branch Prediction, Z bit in SCTLR. */
+       mrc     p15, 0, r6, c1, c0, 0
+       bic     r6, r6, #0x800
+       mcr     p15, 0, r6, c1, c0, 0
+
+       dsb
+       isb
+       /* Store the IRAM table in TTBR1 */
+       mcr     p15, 0, r7, c2, c0, 1
+
+       /* Read TTBCR and set PD0=1, N = 1 */
+       mrc     p15, 0, r6, c2, c0, 2
+       orr     r6, r6, #0x11
+       mcr     p15, 0, r6, c2, c0, 2
+
+       dsb
+       isb
+
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       /* Disable L1 data cache. */
+       mrc     p15, 0, r6, c1, c0, 0
+       bic     r6, r6, #0x4
+       mcr     p15, 0, r6, c1, c0, 0
+
+       dsb
+       isb
+
+#ifdef CONFIG_CACHE_L2X0
+       /*
+        * Need to make sure the buffers in L2 are drained.
+        * Performing a sync operation does this.
+        */
+       ldr     r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
+       mov     r6, #0x0
+       str     r6, [r7, #L2_CACHE_SYNC]
+
+       /*
+        * The second dsb might be needed to keep cache sync (device write)
+        * ordering with the memory accesses before it.
+        */
+       dsb
+       isb
+
+       /* Disable L2. */
+       str     r6, [r7, #0x100]
+#endif
+
+       ldr     r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
+       ldr     r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR)
+       ldr     r5, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
+
+       /* Disable Automatic power savings. */
+       ldr     r6, [r5, #MMDC0_MAPSR]
+       orr     r6, r6, #0x1
+       str     r6, [r5, #MMDC0_MAPSR]
+
+       /* MMDC0_MDPDC disable power down timer */
+       ldr     r6, [r5, #MMDC0_MDPDC]
+       bic     r6, r6, #0xff00
+       str     r6, [r5, #MMDC0_MDPDC]
+
+       /* Delay for a while */
+       ldr     r8, =10
+delay:
+       ldr     r7, =0
+cont:
+       ldr     r6, [r5, r7]
+       add     r7, r7, #4
+       cmp     r7, #16
+       bne     cont
+       sub     r8, r8, #1
+       cmp     r8, #0
+       bgt     delay
+
+       /* Make the DDR explicitly enter self-refresh. */
+       ldr     r6, [r5, #MMDC0_MAPSR]
+       orr     r6, r6, #0x200000
+       str     r6, [r5, #MMDC0_MAPSR]
+
+poll_dvfs_set_1:
+       ldr     r6, [r5, #MMDC0_MAPSR]
+       and     r6, r6, #0x2000000
+       cmp     r6, #0x2000000
+       bne     poll_dvfs_set_1
+
+       /* set SBS step-by-step mode */
+       ldr     r6, [r5, #MMDC0_MADPCR0]
+       orr     r6, r6, #0x100
+       str     r6, [r5, #MMDC0_MADPCR0]
+
+       ldr     r6, =100000000
+       cmp     r0, r6
+       bgt     set_ddr_mu_above_100
+       mmdc_clk_lower_100MHz
+
+set_ddr_mu_above_100:
+       ldr     r6, =24000000
+       cmp     r0, r6
+       beq     set_to_24MHz
+
+       ldr     r6, =100000000
+       cmp     r0, r6
+       beq     set_to_100MHz
+
+       switch_to_400MHz
+
+       mmdc_clk_above_100MHz
+
+       b       done
+
+set_to_24MHz:
+       switch_to_24MHz
+       b       done
+set_to_100MHz:
+       switch_to_100MHz
+done:
+       /* clear DVFS - exit from self refresh mode */
+       ldr     r6, [r5, #MMDC0_MAPSR]
+       bic     r6, r6, #0x200000
+       str     r6, [r5, #MMDC0_MAPSR]
+
+poll_dvfs_clear_1:
+       ldr     r6, [r5, #MMDC0_MAPSR]
+       and     r6, r6, #0x2000000
+       cmp     r6, #0x2000000
+       beq     poll_dvfs_clear_1
+
+       /* Enable Automatic power savings. */
+       ldr     r6, [r5, #MMDC0_MAPSR]
+       bic     r6, r6, #0x1
+       str     r6, [r5, #MMDC0_MAPSR]
+
+       ldr     r6, =24000000
+       cmp     r0, r6
+       beq     skip_power_down
+
+       /* Enable MMDC power down timer. */
+       ldr     r6, [r5, #MMDC0_MDPDC]
+       orr     r6, r6, #0x5500
+       str     r6, [r5, #MMDC0_MDPDC]
+
+skip_power_down:
+       /* clear SBS - unblock DDR accesses */
+       ldr     r6, [r5, #MMDC0_MADPCR0]
+       bic     r6, r6, #0x100
+       str     r6, [r5, #MMDC0_MADPCR0]
+
+
+#ifdef CONFIG_CACHE_L2X0
+       /* Enable L2. */
+       ldr     r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
+       ldr     r6, =0x1
+       str     r6, [r7, #0x100]
+#endif
+
+       /* Enable L1 data cache. */
+       mrc     p15, 0, r6, c1, c0, 0
+       orr     r6, r6, #0x4
+       mcr     p15, 0, r6, c1, c0, 0
+
+       /* Restore the TTBCR */
+       dsb
+       isb
+
+       /* Read TTBCR and set PD0=0, N = 0 */
+       mrc     p15, 0, r6, c2, c0, 2
+       bic     r6, r6, #0x11
+       mcr     p15, 0, r6, c2, c0, 2
+       dsb
+       isb
+
+       /* flush the TLB */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c8, c3, 0
+
+       dsb
+       isb
+
+       /* Enable Branch Prediction, Z bit in SCTLR. */
+       mrc     p15, 0, r6, c1, c0, 0
+       orr     r6, r6, #0x800
+       mcr     p15, 0, r6, c1, c0, 0
+
+       /* Flush the Branch Target Address Cache (BTAC) */
+       ldr     r6, =0x0
+       mcr     p15, 0, r6, c7, c1, 6
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       nop
+       nop
+       nop
+       nop
+       nop
+
+       /* Restore registers */
+       pop     {r2 - r8}
+       mov     pc, lr
index 66b3f4606aded0332d698334b6f10aa523d53449..bd049abf96746b3afc2f705c17261a03009e618d 100644 (file)
@@ -429,6 +429,7 @@ static void __init imx6q_map_io(void)
        debug_ll_io_init();
        imx_scu_map_io();
        imx6_pm_map_io();
+       imx6_busfreq_map_io();
 }
 
 static void __init imx6q_init_irq(void)
index 078c2273cf2eceb3b2101d37195303320d24e633..925c8759cfc6072e35fec26770aab201889ae142 100644 (file)
@@ -163,6 +163,7 @@ static void __init imx6sx_map_io(void)
 {
        debug_ll_io_init();
        imx6_pm_map_io();
+       imx6_busfreq_map_io();
 }
 
 DT_MACHINE_START(IMX6SX, "Freescale i.MX6 SoloX (Device Tree)")
index 7a9686ad994ce99fed6431692040d162f3c0f5ce..b63516fcf0eaf388c1bb1e4a04e37c5aeb3338b1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 Freescale Semiconductor, Inc.
+ * Copyright 2011-2014 Freescale Semiconductor, Inc.
  * Copyright 2011 Linaro Ltd.
  *
  * The code contained herein is licensed under the GNU General Public
 #define BP_MMDC_MAPSR_PSD      0
 #define BP_MMDC_MAPSR_PSS      4
 
+#define        MMDC_MDMISC             0x18
+#define        BM_MMDC_MDMISC_DDR_TYPE 0x18
+#define        BP_MMDC_MDMISC_DDR_TYPE 0x3
+
+static int ddr_type;
+
 static int imx_mmdc_probe(struct platform_device *pdev)
 {
        struct device_node *np = pdev->dev.of_node;
@@ -31,6 +37,13 @@ static int imx_mmdc_probe(struct platform_device *pdev)
        mmdc_base = of_iomap(np, 0);
        WARN_ON(!mmdc_base);
 
+       reg = mmdc_base + MMDC_MDMISC;
+       /* Get ddr type */
+       val = readl_relaxed(reg);
+       val &= BM_MMDC_MDMISC_DDR_TYPE;
+       val >>= BP_MMDC_MDMISC_DDR_TYPE;
+       ddr_type = val;
+
        reg = mmdc_base + MMDC_MAPSR;
 
        /* Enable automatic power saving */
@@ -51,6 +64,11 @@ static int imx_mmdc_probe(struct platform_device *pdev)
        return 0;
 }
 
+int imx_mmdc_get_ddr_type(void)
+{
+       return ddr_type;
+}
+
 static struct of_device_id imx_mmdc_dt_ids[] = {
        { .compatible = "fsl,imx6q-mmdc", },
        { /* sentinel */ }
index ec68ba051fd3221bfbe15cdc68efaee536ee063c..fdfd9cd5732e068519eb697c0bcc7933122a7cb8 100644 (file)
@@ -214,6 +214,26 @@ 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 imx6q_set_int_mem_clk_lpm(void)
 {
        u32 val = readl_relaxed(ccm_base + CGPR);