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
/*
- * 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
*/
#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;
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;
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)
}
/*
- * 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;
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);
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;
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;
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",
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);
}
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;
}
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 ||
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);
}
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);
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,
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;
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;
}
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;
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;
}
/*
- * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
#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];
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;
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},
*/
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;
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);
+ (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) {
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) {
irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(),
GFP_KERNEL);
- for_each_present_cpu(cpu) {
+ for_each_online_cpu(cpu) {
int irq;
/*
}
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. */
}
}
- 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;
--- /dev/null
+/*
+ * 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;
+}
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);
/*
- * 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
*/
#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
* 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
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
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
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
* 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
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
/*
* set the MMDC_DIV=4, AXI_DIV = 4, AHB_DIV=8,
- * (need to maintain GPT divider).
*/
ldr r0, [r6, #CCM_CBCDR]
ldr r2, =0x3f1f00
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
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
*/
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
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. */
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]
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]
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)
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)
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:
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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:
--- /dev/null
+/*
+ * 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
debug_ll_io_init();
imx_scu_map_io();
imx6_pm_map_io();
+ imx6_busfreq_map_io();
}
static void __init imx6q_init_irq(void)
{
debug_ll_io_init();
imx6_pm_map_io();
+ imx6_busfreq_map_io();
}
DT_MACHINE_START(IMX6SX, "Freescale i.MX6 SoloX (Device Tree)")
/*
- * 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;
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 */
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 */ }
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);