ARM: imx: add i.MX6Q bus-freq support

Add i.MX6Q bus-freq support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
This commit is contained in:
Anson Huang 2019-04-17 10:11:31 +08:00 committed by Dong Aisheng
parent dc782dd284
commit ee23850e61
14 changed files with 2652 additions and 22 deletions

View File

@ -78,7 +78,8 @@ AFLAGS_headsmp.o :=-Wa,-march=armv7-a
obj-$(CONFIG_SMP) += headsmp.o platsmp.o
obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
endif
obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o
obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o ddr3_freq_imx6.o smp_wfe_imx6.o busfreq_lpddr2.o \
lpddr2_freq_imx6q.o
obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o
obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o
obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o
@ -90,8 +91,11 @@ obj-$(CONFIG_SOC_IMX7ULP) += mach-imx7ulp.o pm-imx7ulp.o
obj-y += busfreq-imx.o busfreq_ddr3.o
AFLAGS_smp_wfe.o :=-Wa,-march=armv7-a
AFLAGS_smp_wfe_imx6.o :=-Wa,-march=armv7-a
AFLAGS_ddr3_freq_imx7d.o :=-Wa,-march=armv7-a
AFLAGS_lpddr3_freq_imx.o :=-Wa,-march=armv7-a
AFLAGS_ddr3_freq_imx6.o :=-Wa,-march=armv7-a
AFLAGS_lpddr2_freq_imx6q.o :=-Wa,-march=armv7-a
ifeq ($(CONFIG_SUSPEND),y)
AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a

View File

@ -108,6 +108,16 @@ static struct clk *pll_dram;
static struct clk *ahb_sel_clk;
static struct clk *axi_clk;
static struct clk *pll3_clk;
static struct clk *pll2_400_clk;
static struct clk *periph_clk2_sel_clk;
static struct clk *periph_pre_clk;
static struct clk *pll2_200_clk;
static struct clk *periph_clk;
static struct clk *mmdc_clk;
static struct clk *periph_clk2_clk;
static struct clk *pll2_bus_clk;
static struct delayed_work low_bus_freq_handler;
static struct delayed_work bus_freq_daemon;
@ -143,6 +153,92 @@ int unregister_busfreq_notifier(struct notifier_block *nb)
}
EXPORT_SYMBOL(unregister_busfreq_notifier);
static void enter_lpm_imx6_smp(void)
{
if (audio_bus_count) {
/* Need to ensure that PLL2_PFD_400M is kept ON. */
clk_prepare_enable(pll2_400_clk);
if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3)
busfreq_func.update(LOW_AUDIO_CLK);
else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2)
busfreq_func.update(HIGH_AUDIO_CLK);
/* Make sure periph clk's parent also got updated */
clk_set_parent(periph_clk2_sel_clk, pll3_clk);
if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3)
clk_set_parent(periph_pre_clk, pll2_200_clk);
else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2)
clk_set_parent(periph_pre_clk, pll2_400_clk);
clk_set_parent(periph_clk, periph_pre_clk);
/*
* As periph_pre_clk's parent is not changed from
* high mode to audio mode on lpddr2, the clk framework
* will not update its children's freq, but we
* change the mmdc_ch0_axi podf in asm code, so here
* need to update mmdc rate to make sure clk
* tree is right, although it will not do any
* change to hardware. Calling get_rate will only call
* the .rate_recalc which is all we need.
*/
if (high_bus_freq_mode && mmdc_clk)
if (ddr_type == IMX_DDR_TYPE_LPDDR2)
clk_get_rate(mmdc_clk);
audio_bus_freq_mode = 1;
low_bus_freq_mode = 0;
cur_bus_freq_mode = BUS_FREQ_AUDIO;
} else {
busfreq_func.update(LPAPM_CLK);
/* Make sure periph clk's parent also got updated */
clk_set_parent(periph_clk2_sel_clk, osc_clk);
/* Set periph_clk parent to OSC via periph_clk2_sel */
clk_set_parent(periph_clk, periph_clk2_clk);
if (audio_bus_freq_mode)
clk_disable_unprepare(pll2_400_clk);
low_bus_freq_mode = 1;
audio_bus_freq_mode = 0;
cur_bus_freq_mode = BUS_FREQ_LOW;
}
}
static void exit_lpm_imx6_smp(void)
{
struct clk *periph_clk_parent;
if (cpu_is_imx6q() && ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3)
periph_clk_parent = pll2_bus_clk;
else
periph_clk_parent = pll2_400_clk;
clk_prepare_enable(pll2_400_clk);
busfreq_func.update(ddr_normal_rate);
/* Make sure periph clk's parent also got updated */
clk_set_parent(periph_clk2_sel_clk, pll3_clk);
clk_set_parent(periph_pre_clk, periph_clk_parent);
clk_set_parent(periph_clk, periph_pre_clk);
/*
* As periph_pre_clk's parent is not changed from
* high mode to audio mode on lpddr2, the clk framework
* will not update its children's freq, but we
* change the mmdc_ch0_axi podf in asm code, so here
* need to update mmdc rate to make sure clk
* tree is right, although it will not do any
* change to hardware. Calling get_rate will only call
* the .rate_recalc which is all we need.
*/
if (audio_bus_freq_mode && mmdc_clk)
if (ddr_type == IMX_DDR_TYPE_LPDDR2)
clk_get_rate(mmdc_clk);
clk_disable_unprepare(pll2_400_clk);
if (audio_bus_freq_mode)
clk_disable_unprepare(pll2_400_clk);
}
static void enter_lpm_imx7d(void)
{
/*
@ -200,6 +296,9 @@ static void exit_lpm_imx7d(void)
static void reduce_bus_freq(void)
{
if (cpu_is_imx6())
clk_prepare_enable(pll3_clk);
if (audio_bus_count && (low_bus_freq_mode || ultra_low_bus_freq_mode))
busfreq_notify(LOW_BUSFREQ_EXIT);
else if (!audio_bus_count)
@ -207,10 +306,15 @@ static void reduce_bus_freq(void)
if (cpu_is_imx7d())
enter_lpm_imx7d();
else if (cpu_is_imx6q())
enter_lpm_imx6_smp();
med_bus_freq_mode = 0;
high_bus_freq_mode = 0;
if (cpu_is_imx6())
clk_disable_unprepare(pll3_clk);
if (audio_bus_freq_mode)
dev_dbg(busfreq_dev,
"Bus freq set to audio mode. Count: high %d, med %d, audio %d\n",
@ -297,8 +401,13 @@ static int set_high_bus_freq(int high_bus_freq)
if (low_bus_freq_mode || ultra_low_bus_freq_mode)
busfreq_notify(LOW_BUSFREQ_EXIT);
if (cpu_is_imx6())
clk_prepare_enable(pll3_clk);
if (cpu_is_imx7d())
exit_lpm_imx7d();
else if (cpu_is_imx6q())
exit_lpm_imx6_smp();
high_bus_freq_mode = 1;
med_bus_freq_mode = 0;
@ -306,6 +415,9 @@ static int set_high_bus_freq(int high_bus_freq)
audio_bus_freq_mode = 0;
cur_bus_freq_mode = BUS_FREQ_HIGH;
if (cpu_is_imx6())
clk_disable_unprepare(pll3_clk);
if (high_bus_freq_mode)
dev_dbg(busfreq_dev,
"Bus freq set to high mode. Count: high %d, med %d, audio %d\n",
@ -612,6 +724,35 @@ static int busfreq_probe(struct platform_device *pdev)
if (!ddr_freq_change_iram_base)
return -ENOMEM;
if (cpu_is_imx6()) {
osc_clk = devm_clk_get(&pdev->dev, "osc");
pll2_400_clk = devm_clk_get(&pdev->dev, "pll2_pfd2_396m");
pll2_200_clk = devm_clk_get(&pdev->dev, "pll2_198m");
pll2_bus_clk = devm_clk_get(&pdev->dev, "pll2_bus");
pll3_clk = devm_clk_get(&pdev->dev, "pll3_usb_otg");
periph_clk = devm_clk_get(&pdev->dev, "periph");
periph_pre_clk = devm_clk_get(&pdev->dev, "periph_pre");
periph_clk2_clk = devm_clk_get(&pdev->dev, "periph_clk2");
periph_clk2_sel_clk = devm_clk_get(&pdev->dev,
"periph_clk2_sel");
if (IS_ERR(osc_clk) || IS_ERR(pll2_400_clk)
|| IS_ERR(pll2_200_clk) || IS_ERR(pll2_bus_clk)
|| IS_ERR(pll3_clk) || IS_ERR(periph_clk)
|| IS_ERR(periph_pre_clk) || IS_ERR(periph_clk2_clk)
|| IS_ERR(periph_clk2_sel_clk)) {
dev_err(busfreq_dev,
"%s: failed to get busfreq clk\n", __func__);
return -EINVAL;
}
}
if (cpu_is_imx6q()) {
mmdc_clk = devm_clk_get(&pdev->dev, "mmdc");
if (IS_ERR(mmdc_clk)) {
mmdc_clk = NULL;
}
}
if (cpu_is_imx7d()) {
osc_clk = devm_clk_get(&pdev->dev, "osc");
axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel");
@ -701,6 +842,15 @@ static int busfreq_probe(struct platform_device *pdev)
}
busfreq_func.init = &init_ddrc_ddr_settings;
busfreq_func.update = &update_ddr_freq_imx_smp;
} else if (cpu_is_imx6q()) {
ddr_type = imx_mmdc_get_ddr_type();
if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) {
busfreq_func.init = &init_mmdc_ddr3_settings_imx6_smp;
busfreq_func.update = &update_ddr_freq_imx_smp;
} else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2) {
busfreq_func.init = &init_mmdc_lpddr2_settings_mx6q;
busfreq_func.update = &update_lpddr2_freq_smp;
}
}
if (busfreq_func.init)

View File

@ -73,19 +73,30 @@ struct imx6_busfreq_info {
} __aligned(8);
/* DDR settings */
static unsigned long (*iram_ddr_settings)[2];
static unsigned long (*normal_mmdc_settings)[2];
static unsigned long (*iram_iomux_settings)[2];
static void __iomem *mmdc_base;
static void __iomem *iomux_base;
static void __iomem *gic_dist_base;
static int ddr_settings_size;
static int iomux_settings_size;
static int curr_ddr_rate;
void (*imx7d_change_ddr_freq)(u32 freq) = NULL;
extern void imx7d_ddr3_freq_change(u32 freq);
extern void imx_lpddr3_freq_change(u32 freq);
void (*mx6_change_ddr_freq)(u32 freq, void *ddr_settings,
bool dll_mode, void *iomux_offsets) = NULL;
extern unsigned int ddr_normal_rate;
extern int low_bus_freq_mode;
extern int audio_bus_freq_mode;
extern void mx6_ddr3_freq_change(u32 freq, void *ddr_settings,
bool dll_mode, void *iomux_offsets);
extern unsigned long save_ttbr1(void);
extern void restore_ttbr1(unsigned long ttbr1);
@ -94,7 +105,10 @@ extern unsigned long ddr_freq_change_iram_base;
extern unsigned long ddr_freq_change_total_size;
extern unsigned long iram_tlb_phys_addr;
extern unsigned long mx6_ddr3_freq_change_start asm("mx6_ddr3_freq_change_start");
extern unsigned long mx6_ddr3_freq_change_end asm("mx6_ddr3_freq_change_end");
#ifdef CONFIG_SMP
static unsigned long wfe_freq_change_iram_base;
volatile u32 *wait_for_ddr_freq_update;
static unsigned int online_cpus;
static u32 *irqs_used;
@ -105,9 +119,41 @@ extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done);
extern void imx7_smp_wfe(u32 cpuid, u32 ocram_base);
extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start");
extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end");
extern void __iomem *imx_scu_base;
extern void __iomem *scu_base;
#endif
unsigned long ddr3_dll_mx6q[][2] = {
{0x0c, 0x0},
{0x10, 0x0},
{0x1C, 0x04088032},
{0x1C, 0x0408803a},
{0x1C, 0x08408030},
{0x1C, 0x08408038},
{0x818, 0x0},
{0x18, 0x0},
};
unsigned long ddr3_calibration[][2] = {
{0x83c, 0x0},
{0x840, 0x0},
{0x483c, 0x0},
{0x4840, 0x0},
{0x848, 0x0},
{0x4848, 0x0},
{0x850, 0x0},
{0x4850, 0x0},
};
unsigned long iomux_offsets_mx6q[][2] = {
{0x5A8, 0x0},
{0x5B0, 0x0},
{0x524, 0x0},
{0x51C, 0x0},
{0x518, 0x0},
{0x50C, 0x0},
{0x5B8, 0x0},
{0x5C0, 0x0},
};
int can_change_ddr_freq(void)
{
return 1;
@ -149,10 +195,13 @@ int update_ddr_freq_imx_smp(int ddr_rate)
{
int me = 0;
unsigned long ttbr1;
bool dll_off = false;
int i;
#ifdef CONFIG_SMP
unsigned int reg = 0;
int cpu = 0;
#endif
int mode = get_bus_freq_mode();
if (!can_change_ddr_freq())
return -1;
@ -162,6 +211,22 @@ int update_ddr_freq_imx_smp(int ddr_rate)
printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate);
if (cpu_is_imx6()) {
if ((mode == BUS_FREQ_LOW) || (mode == BUS_FREQ_AUDIO))
dll_off = true;
iram_ddr_settings[0][0] = ddr_settings_size;
iram_iomux_settings[0][0] = iomux_settings_size;
if (ddr_rate == ddr_normal_rate) {
for (i = 0; i < iram_ddr_settings[0][0]; i++) {
iram_ddr_settings[i + 1][0] =
normal_mmdc_settings[i][0];
iram_ddr_settings[i + 1][1] =
normal_mmdc_settings[i][1];
}
}
}
/* ensure that all Cores are in WFE. */
local_irq_disable();
@ -176,6 +241,8 @@ int update_ddr_freq_imx_smp(int ddr_rate)
for_each_online_cpu(cpu) {
if (cpu_is_imx7d())
reg = *(wait_for_ddr_freq_update + 1);
else if (cpu_is_imx6())
reg = __raw_readl(scu_base + 0x08);
if (reg & (0x02 << (cpu * 8)))
not_exited_busfreq = true;
@ -189,6 +256,8 @@ int update_ddr_freq_imx_smp(int ddr_rate)
dsb();
if (cpu_is_imx7d())
online_cpus = *(wait_for_ddr_freq_update + 1);
else if (cpu_is_imx6())
online_cpus = readl_relaxed(scu_base + 0x08);
for_each_online_cpu(cpu) {
*((char *)(&online_cpus) + (u8)cpu) = 0x02;
if (cpu != me) {
@ -204,6 +273,8 @@ int update_ddr_freq_imx_smp(int ddr_rate)
if (cpu_is_imx7d())
reg = *(wait_for_ddr_freq_update + 1);
else if (cpu_is_imx6())
reg = readl_relaxed(scu_base + 0x08);
reg |= (0x02 << (me * 8));
if (reg == online_cpus)
break;
@ -213,11 +284,17 @@ int update_ddr_freq_imx_smp(int ddr_rate)
/* Ensure iram_tlb_phys_addr is flushed to DDR. */
__cpuc_flush_dcache_area(&iram_tlb_phys_addr,
sizeof(iram_tlb_phys_addr));
if (cpu_is_imx6())
outer_clean_range(__pa(&iram_tlb_phys_addr),
__pa(&iram_tlb_phys_addr + 1));
ttbr1 = save_ttbr1();
/* Now we can change the DDR frequency. */
if (cpu_is_imx7d())
imx7d_change_ddr_freq(ddr_rate);
else if (cpu_is_imx6())
mx6_change_ddr_freq(ddr_rate, iram_ddr_settings,
dll_off, iram_iomux_settings);
restore_ttbr1(ttbr1);
curr_ddr_rate = ddr_rate;
@ -308,3 +385,160 @@ int init_ddrc_ddr_settings(struct platform_device *busfreq_pdev)
return 0;
}
int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *busfreq_pdev)
{
int i;
struct device_node *node;
unsigned long ddr_code_size;
unsigned long wfe_code_size = 0;
#ifdef CONFIG_SMP
u32 cpu;
struct device *dev = &busfreq_pdev->dev;
int err;
struct irq_data *d;
#endif
node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc-combine");
if (!node) {
printk(KERN_ERR "failed to find imx6q-mmdc device tree data!\n");
return -EINVAL;
}
mmdc_base = of_iomap(node, 0);
WARN(!mmdc_base, "unable to map mmdc registers\n");
node = NULL;
if (cpu_is_imx6q())
node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc");
if (!node) {
printk(KERN_ERR "failed to find imx6q-iomux device tree data!\n");
return -EINVAL;
}
iomux_base = of_iomap(node, 0);
WARN(!iomux_base, "unable to map iomux registers\n");
node = NULL;
node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-gic");
if (!node) {
printk(KERN_ERR "failed to find imx6q-a9-gic device tree data!\n");
return -EINVAL;
}
gic_dist_base = of_iomap(node, 0);
WARN(!gic_dist_base, "unable to map gic dist registers\n");
if (cpu_is_imx6q())
ddr_settings_size = ARRAY_SIZE(ddr3_dll_mx6q) +
ARRAY_SIZE(ddr3_calibration);
normal_mmdc_settings = kmalloc((ddr_settings_size * 8), GFP_KERNEL);
if (cpu_is_imx6q()) {
memcpy(normal_mmdc_settings, ddr3_dll_mx6q,
sizeof(ddr3_dll_mx6q));
memcpy(((char *)normal_mmdc_settings + sizeof(ddr3_dll_mx6q)),
ddr3_calibration, sizeof(ddr3_calibration));
}
/* store the original DDR settings at boot. */
for (i = 0; i < ddr_settings_size; i++) {
/*
* writes via command mode register cannot be read back.
* hence hardcode them in the initial static array.
* this may require modification on a per customer basis.
*/
if (normal_mmdc_settings[i][0] != 0x1C)
normal_mmdc_settings[i][1] =
readl_relaxed(mmdc_base
+ normal_mmdc_settings[i][0]);
}
#ifdef CONFIG_SMP
irqs_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(),
GFP_KERNEL);
for_each_online_cpu(cpu) {
int irq;
/*
* set up a reserved interrupt to get all
* the active cores into a WFE state
* before changing the DDR frequency.
*/
irq = platform_get_irq(busfreq_pdev, cpu);
err = request_irq(irq, wait_in_wfe_irq,
IRQF_PERCPU, "mmdc_1", NULL);
if (err) {
dev_err(dev,
"Busfreq:request_irq failed %d, err = %d\n",
irq, err);
return err;
}
err = irq_set_affinity(irq, cpumask_of(cpu));
if (err) {
dev_err(dev,
"Busfreq: Cannot set irq affinity irq=%d,\n",
irq);
return err;
}
d = irq_get_irq_data(irq);
irqs_used[cpu] = d->hwirq + 32;
}
#endif
iomux_settings_size = ARRAY_SIZE(iomux_offsets_mx6q);
ddr_code_size = (&mx6_ddr3_freq_change_end -
&mx6_ddr3_freq_change_start) * 4;
mx6_change_ddr_freq = (void *)fncpy((void *)ddr_freq_change_iram_base,
&mx6_ddr3_freq_change, ddr_code_size);
/*
* Store the size of the array in iRAM also,
* increase the size by 8 bytes.
*/
iram_iomux_settings = (void *)(ddr_freq_change_iram_base +
ddr_code_size);
iram_ddr_settings = iram_iomux_settings + (iomux_settings_size * 8) + 8;
#ifdef CONFIG_SMP
wfe_freq_change_iram_base = (unsigned long)((u32 *)iram_ddr_settings +
(ddr_settings_size * 8) + 8);
if (wfe_freq_change_iram_base & (FNCPY_ALIGN - 1))
wfe_freq_change_iram_base += FNCPY_ALIGN -
((uintptr_t)wfe_freq_change_iram_base % (FNCPY_ALIGN));
wfe_code_size = (&wfe_smp_freq_change_end -
&wfe_smp_freq_change_start) *4;
wfe_change_ddr_freq = (void *)fncpy((void *)wfe_freq_change_iram_base,
&wfe_smp_freq_change, wfe_code_size);
/*
* Store the variable used to communicate
* between cores in a non-cacheable IRAM area
*/
wait_for_ddr_freq_update = (u32 *)&iram_iomux_settings[0][1];
#endif
if ((ddr_code_size + wfe_code_size + (iomux_settings_size +
ddr_settings_size) * 8 + 16)
> ddr_freq_change_total_size) {
printk(KERN_ERR "Not enough memory for DDR Freq scale.\n");
return EINVAL;
}
if (cpu_is_imx6q()) {
/* store the IOMUX settings at boot. */
for (i = 0; i < iomux_settings_size; i++) {
iomux_offsets_mx6q[i][1] =
readl_relaxed(iomux_base +
iomux_offsets_mx6q[i][0]);
iram_iomux_settings[i + 1][0] =
iomux_offsets_mx6q[i][0];
iram_iomux_settings[i + 1][1] =
iomux_offsets_mx6q[i][1];
}
}
curr_ddr_rate = ddr_normal_rate;
return 0;
}

View File

@ -56,8 +56,6 @@ void (*mx6_change_lpddr2_freq)(u32 ddr_freq, int bus_freq_mode) = NULL;
extern unsigned int ddr_normal_rate;
extern void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode);
extern void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode);
extern void imx6sll_lpddr2_freq_change(u32 freq, int bus_freq_mode);
extern unsigned long save_ttbr1(void);
extern void restore_ttbr1(unsigned long ttbr1);
extern void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings);
@ -98,7 +96,7 @@ void (*wfe_change_lpddr2_freq)(u32 cpuid, u32 *ddr_freq_change_done);
extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done);
extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start");
extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end");
extern void __iomem *imx_scu_base;
extern void __iomem *scu_base;
static void __iomem *gic_dist_base;
#endif
@ -163,19 +161,6 @@ int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev)
ddr_code_size = SZ_4K;
if (cpu_is_imx6sl())
mx6_change_lpddr2_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base,
&mx6_lpddr2_freq_change, ddr_code_size);
if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull())
mx6_change_lpddr2_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base,
&imx6_up_lpddr2_freq_change, ddr_code_size);
if (cpu_is_imx6sll())
mx6_change_lpddr2_freq = (void *)fncpy(
(void *)ddr_freq_change_iram_base,
&imx6sll_lpddr2_freq_change, ddr_code_size);
curr_ddr_rate = ddr_normal_rate;
return 0;
@ -214,7 +199,7 @@ int update_lpddr2_freq_smp(int ddr_rate)
while (1) {
bool not_exited_busfreq = false;
for_each_online_cpu(cpu) {
reg = __raw_readl(imx_scu_base + 0x08);
reg = __raw_readl(scu_base + 0x08);
if (reg & (0x02 << (cpu * 8)))
not_exited_busfreq = true;
}
@ -225,7 +210,7 @@ int update_lpddr2_freq_smp(int ddr_rate)
wmb();
*wait_for_lpddr2_freq_update = 1;
dsb();
online_cpus = readl_relaxed(imx_scu_base + 0x08);
online_cpus = readl_relaxed(scu_base + 0x08);
for_each_online_cpu(cpu) {
*((char *)(&online_cpus) + (u8)cpu) = 0x02;
if (cpu != me) {
@ -238,7 +223,7 @@ int update_lpddr2_freq_smp(int ddr_rate)
/* Wait for the other active CPUs to idle */
while (1) {
reg = 0;
reg = readl_relaxed(imx_scu_base + 0x08);
reg = readl_relaxed(scu_base + 0x08);
reg |= (0x02 << (me * 8));
if (reg == online_cpus)
break;

View File

@ -106,6 +106,7 @@ int imx_mmdc_get_ddr_type(void);
int imx7ulp_set_lpm(enum ulp_cpu_pwr_mode mode);
void imx_busfreq_map_io(void);
void imx7_pm_map_io(void);
void imx6_pm_map_io(void);
void imx_cpu_die(unsigned int cpu);
int imx_cpu_kill(unsigned int cpu);

File diff suppressed because it is too large Load Diff

View File

@ -102,6 +102,7 @@
#include "mx2x.h"
#include "mx21.h"
#include "mx27.h"
#include "mx6.h"
#include "mx7.h"
#define imx_map_entry(soc, name, _type) { \

View File

@ -0,0 +1,765 @@
/*
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/linkage.h>
#include <asm/smp_scu.h>
#include "hardware.h"
#define CCM_CBCDR 0x14
#define CCM_CBCMR 0x18
#define CCM_CSCMR1 0x1c
#define CCM_CDHIPR 0x48
.globl mx6q_lpddr2_freq_change_start
.globl mx6q_lpddr2_freq_change_end
.macro wait_for_ccm_handshake
/* wait for div update */
1:
ldr r9, [r2, #CCM_CDHIPR]
cmp r9, #0
bne 1b
.endm
.macro set_mmdc_misc_ralat_2_cycles
/* Set MMDCx_MISC[RALAT] = 2 cycles */
ldr r6, [r8, #0x18]
bic r6, r6, #(0x7 << 6)
orr r6, r6, #(0x2 << 6)
str r6, [r8, #0x18]
/* Check if lpddr2 channel 1 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq 1f
ldr r6, [r4, #0x18]
bic r6, r6, #(0x7 << 6)
orr r6, r6, #(0x2 << 6)
str r6, [r4, #0x18]
1:
.endm
.macro switch_to_400MHz
/* set the MMDC_DIV=1, AXI_DIV=2, AHB_DIV=3 */
ldr r9, [r2, #CCM_CBCDR]
ldr r6, =0x3f1f00
bic r9, r9, r6
orr r9, r9, #(0x9 << 8)
orr r9, r9, #(1 << 16)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/* check periph_clk_sel */
ldr r9, [r2, #CCM_CBCDR]
and r9, r9, #(1 << 25)
cmp r9, #(1 << 25)
bne skip_periph_clk_switch_400m
/* now switch periph_clk back. */
ldr r9, [r2, #CCM_CBCDR]
bic r9, r9, #(1 << 25)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
skip_periph_clk_switch_400m:
.endm
.macro switch_to_100MHz
/* set the MMDC_DIV=4, AXI_DIV=8, AHB_DIV=8 */
ldr r9, [r2, #CCM_CBCDR]
ldr r6, =0x3f1f00
bic r9, r9, r6
orr r9, r9, #(0x1F << 16)
orr r9, r9, #(0x1D << 8)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/* check if periph_clk_sel is already set. */
ldr r9, [r2, #CCM_CBCDR]
and r9, r9, #(1 << 25)
cmp r9, #(1 << 25)
bne skip_periph_clk_switch_100m
/* now switch periph_clk back. */
ldr r9, [r2, #CCM_CBCDR]
bic r9, r9, #(1 << 25)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
skip_periph_clk_switch_100m:
.endm
.macro switch_to_24MHz
/*
* change the freq now try setting DDR to 24MHz.
* source it from the periph_clk2 ensure the
* periph_clk2 is sourced from 24MHz and the
* divider is 1.
*/
ldr r9, [r2, #CCM_CBCMR]
bic r9, r9, #(0x3 << 12)
orr r9, r9, #(1 << 12)
str r9, [r2, #CCM_CBCMR]
ldr r9, [r2, #CCM_CBCDR]
bic r9, r9, #(0x7 << 27)
str r9, [r2, #CCM_CBCDR]
/* now switch periph_clk to 24MHz. */
ldr r9, [r2, #CCM_CBCDR]
orr r9, r9, #(1 << 25)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
/* change all the dividers to 1. */
ldr r9, [r2, #CCM_CBCDR]
ldr r6, =0x3f1f00
bic r9, r9, r6
orr r9, r9, #(1 << 8)
str r9, [r2, #CCM_CBCDR]
/* Wait for the divider to change. */
wait_for_ccm_handshake
.endm
.macro switch_to_24MHZ_from_pll2
/* Change DDR freq settings from pll2_pfd2 (div 2) */
ldr r9, [r2, #CCM_CBCMR]
bic r9, r9, #(0x3 << 18)
orr r9, r9, #(0x3 << 18)
str r9, [r2, #CCM_CBCMR]
ldr r9, [r2, #CCM_CBCDR]
bic r9, r9, #(1 << 25)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
ldr r9, [r2, #CCM_CBCDR]
ldr r6, =0x3f1f00
bic r9, r9, r6
orr r9, r9, #(1 << 8)
orr r9, r9, #(0x7 << 19)
str r9, [r2, #CCM_CBCDR]
wait_for_ccm_handshake
.endm
.macro set_timings_below_100MHz_operation
set_mmdc_misc_ralat_2_cycles
/* Adjust LPDDR2 timings for 24Mhz operation */
ldr r5, =0x03162073
str r5, [r8, #0xC] /* MMDC0_MDCFG0 */
ldr r7, =0x00020482
str r7, [r8, #0x10] /* MMDC0_MDCFG1 */
ldr r9, =0x00000049
str r9, [r8, #0x14] /* MMDC0_MDCFG2 */
ldr r10, =0x00020333
str r10, [r8, #0x38] /* MMDC0_MDCFG3LP */
/* Check if lpddr2 channel 1 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_below_100Mhz_ch1_timings
str r5, [r4, #0xC] /* MMDC1_MDCFG0 */
str r7, [r4, #0x10] /* MMDC1_MDCFG1 */
str r9, [r4, #0x14] /* MMDC1_MDCFG2 */
str r10, [r4, #0x38] /* MMDC1_MDCFG3LP */
skip_below_100Mhz_ch1_timings:
.endm
.macro restore_mmdc_settings_info
/* restore timing from mmdc_settings_info */
ldr r6, [r1, #0x0]
ldr r7, [r1, #0x4]
1:
ldr r9, [r7], #0x4
ldr r10, [r7], #0x4
str r10, [r8, r9]
subs r6, r6, #0x1
bne 1b
/* Check if lpddr2 channel 1 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq 3f
ldr r6, [r1, #0x0]
ldr r7, [r1, #0x4]
2:
ldr r9, [r7], #0x4
ldr r10, [r7], #0x4
str r10, [r4, r9]
subs r6, r6, #0x1
bne 2b
3:
.endm
.macro mmdc_clk_lower_equal_100MHz
ldr r10, =100000000
cmp r0, r10
beq set_timmings_100MHz
set_timings_below_100MHz_operation
b common_to_lower_equal_100MHz
set_timmings_100MHz:
restore_mmdc_settings_info
set_mmdc_misc_ralat_2_cycles
common_to_lower_equal_100MHz:
/* if MMDC is not in 400MHz mode, skip double mu count */
ldr r5, [r1, #0x8]
ldr r6, =400000000
cmp r5, r6
bne skip_lower_force_measure_ch1
/*
* Prior to reducing the DDR frequency (at 528/400 MHz),
* read the Measure unit count bits (MU_UNIT_DEL_NUM)
*/
ldr r5, =0x8B8
ldr r6, [r8, r5]
/* Original MU unit count */
mov r6, r6, LSR #16
ldr r9, =0x3FF
and r6, r6, r9
/* Original MU unit count * 2 */
mov r7, r6, LSL #1
/*
* Bypass the automatic measure unit when below 100 MHz
* by setting the Measure unit bypass enable bit (MU_BYP_EN)
*/
ldr r6, [r8, r5]
orr r6, r6, #0x400
str r6, [r8, r5]
/*
* Double the measure count value read in step 1 and program it in the
* measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
* Register for the reduced frequency operation below 100 MHz
*/
ldr r6, [r8, r5]
ldr r9, =0x3FF
bic r6, r6, r9
orr r6, r6, r7
str r6, [r8, r5]
/* Now perform a Force Measurement. */
ldr r6, [r8, r5]
orr r6, r6, #0x800
str r6, [r8, r5]
/* Wait for FRC_MSR to clear. */
force_measure:
ldr r6, [r8, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_lower_force_measure_ch1
ldr r5, =0x8B8
ldr r6, [r4, r5]
/* Original MU unit count */
mov r6, r6, LSR #16
ldr r9, =0x3FF
and r6, r6, r9
/* Original MU unit count * 2 */
mov r7, r6, LSL #1
/*
* Bypass the automatic measure unit when below 100 MHz
* by setting the Measure unit bypass enable bit (MU_BYP_EN)
*/
ldr r6, [r4, r5]
orr r6, r6, #0x400
str r6, [r4, r5]
/*
* Double the measure count value read in step 1 and program it in the
* measurement bypass bits (MU_BYP_VAL) of the MMDC PHY Measure Unit
* Register for the reduced frequency operation below 100 MHz
*/
ldr r6, [r4, r5]
ldr r9, =0x3FF
bic r6, r6, r9
orr r6, r6, r7
str r6, [r4, r5]
/* Now perform a Force Measurement. */
ldr r6, [r4, r5]
orr r6, r6, #0x800
str r6, [r4, r5]
/* Wait for FRC_MSR to clear. */
force_measure_ch1:
ldr r6, [r4, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure_ch1
skip_lower_force_measure_ch1:
.endm
.macro mmdc_clk_above_100MHz
restore_mmdc_settings_info
/* Make sure that the PHY measurement unit is NOT in bypass mode */
ldr r5, =0x8B8
ldr r6, [r8, r5]
bic r6, r6, #0x400
str r6, [r8, r5]
/* Now perform a Force Measurement. */
ldr r6, [r8, r5]
orr r6, r6, #0x800
str r6, [r8, r5]
/* Wait for FRC_MSR to clear. */
force_measure1:
ldr r6, [r8, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure1
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_above_force_measure_ch1
ldr r5, =0x8B8
ldr r6, [r4, r5]
bic r6, r6, #0x400
str r6, [r4, r5]
/* Now perform a Force Measurement. */
ldr r6, [r4, r5]
orr r6, r6, #0x800
str r6, [r4, r5]
/* Wait for FRC_MSR to clear. */
force_measure1_ch1:
ldr r6, [r4, r5]
and r6, r6, #0x800
cmp r6, #0x0
bne force_measure1_ch1
skip_above_force_measure_ch1:
.endm
.macro disable_l1_dcache
/*
* Flush all data from the L1 data cache before disabling
* SCTLR.C bit.
*/
push {r0 - r11, lr}
ldr r7, =v7_flush_kern_cache_all
mov lr, pc
mov pc, r7
pop {r0 - r11, lr}
/* disable d-cache */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
push {r0 - r11, lr}
ldr r7, =v7_flush_kern_cache_all
mov lr, pc
mov pc, r7
pop {r0 - r11, lr}
.endm
/*
* mx6_lpddr2_freq_change
*
* Make sure DDR is in self-refresh.
* IRQs are already disabled.
* r0 : DDR freq.
* r1 : mmdc_settings_info
*/
.align 3
ENTRY(mx6q_lpddr2_freq_change)
mx6q_lpddr2_freq_change_start:
push {r2-r10}
/*
* Need to flush and disable L1 before
* disabling L2, we need data to
* coherent. Flushing L1 pushes
* everyhting to L2. We sync L2 later, but
* it can still have dirty lines.
* While exiting, we need to enable L2 first
* and then L1.
*/
disable_l1_dcache
/*
* To ensure no page table walks occur in DDR, we
* have a another page table stored in IRAM that only
* contains entries pointing to IRAM, AIPS1 and AIPS2.
* We need to set the TTBR1 to the new IRAM TLB.
* Do the following steps:
* 1. Flush the Branch Target Address Cache (BTAC)
* 2. Set TTBR1 to point to IRAM page table.
* 3. Disable page table walks in TTBR0 (PD0 = 1)
* 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0
* and 2-4G is translated by TTBR1.
*/
ldr r6, =iram_tlb_phys_addr
ldr r7, [r6]
/* Flush the Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
/* Disable Branch Prediction, Z bit in SCTLR. */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x800
mcr p15, 0, r6, c1, c0, 0
dsb
isb
/* Store the IRAM table in TTBR1 */
mcr p15, 0, r7, c2, c0, 1
/* Read TTBCR and set PD0=1, N = 1 */
mrc p15, 0, r6, c2, c0, 2
orr r6, r6, #0x11
mcr p15, 0, r6, c2, c0, 2
dsb
isb
/* flush the TLB */
ldr r6, =0x0
mcr p15, 0, r6, c8, c3, 0
#ifdef CONFIG_CACHE_L2X0
/*
* Need to make sure the buffers in L2 are drained.
* Performing a sync operation does this.
*/
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
/* Wait for background operations to complete. */
wait_for_l2_to_idle:
ldr r6, [r7, #0x730]
cmp r6, #0x0
bne wait_for_l2_to_idle
mov r6, #0x0
str r6, [r7, #0x730]
/*
* The second dsb might be needed to keep cache sync (device write)
* ordering with the memory accesses before it.
*/
dsb
isb
/* Disable L2. */
str r6, [r7, #0x100]
#endif
ldr r3, =IMX_IO_P2V(MX6Q_ANATOP_BASE_ADDR)
ldr r2, =IMX_IO_P2V(MX6Q_CCM_BASE_ADDR)
ldr r8, =IMX_IO_P2V(MX6Q_MMDC_P0_BASE_ADDR)
ldr r4, =IMX_IO_P2V(MX6Q_MMDC_P1_BASE_ADDR)
/* Disable Automatic power savings. */
ldr r6, [r8, #0x404]
orr r6, r6, #0x01
str r6, [r8, #0x404]
/* MMDC0_MDPDC disable power down timer */
ldr r6, [r8, #0x4]
bic r6, r6, #0xff00
str r6, [r8, #0x4]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_psd_ch1
ldr r6, [r4, #0x404]
orr r6, r6, #0x01
str r6, [r4, #0x404]
ldr r6, [r4, #0x4]
bic r6, r6, #0xff00
str r6, [r4, #0x4]
skip_psd_ch1:
/* Delay for a while */
ldr r10, =10
delay1:
ldr r7, =0
cont1:
ldr r6, [r8, r7]
add r7, r7, #4
cmp r7, #16
bne cont1
sub r10, r10, #1
cmp r10, #0
bgt delay1
/* Make the DDR explicitly enter self-refresh. */
ldr r6, [r8, #0x404]
orr r6, r6, #0x200000
str r6, [r8, #0x404]
poll_dvfs_set_1:
ldr r6, [r8, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
bne poll_dvfs_set_1
/* set SBS step-by-step mode */
ldr r6, [r8, #0x410]
orr r6, r6, #0x100
str r6, [r8, #0x410]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_sbs_ch1
ldr r6, [r4, #0x404]
orr r6, r6, #0x200000
str r6, [r4, #0x404]
poll_dvfs_set_2:
ldr r6, [r4, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
bne poll_dvfs_set_2
ldr r6, [r4, #0x410]
orr r6, r6, #0x100
str r6, [r4, #0x410]
skip_sbs_ch1:
ldr r10, =100000000
cmp r0, r10
bgt set_ddr_mu_above_100
mmdc_clk_lower_equal_100MHz
set_ddr_mu_above_100:
ldr r10, =24000000
cmp r0, r10
beq set_to_24MHz
ldr r10, =100000000
cmp r0, r10
beq set_to_100MHz
ldr r10, =400000000
cmp r0, r10
switch_to_400MHz
b done
set_to_24MHz:
/*
switch_to_24MHZ_from_pll2
*/
switch_to_24MHz
b done
set_to_100MHz:
switch_to_100MHz
done:
ldr r10,=100000000
cmp r0, r10
ble skip_mmdc_clk_check
mmdc_clk_above_100MHz
skip_mmdc_clk_check:
/* clear DVFS - exit from self refresh mode */
ldr r6, [r8, #0x404]
bic r6, r6, #0x200000
str r6, [r8, #0x404]
poll_dvfs_clear_1:
ldr r6, [r8, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
beq poll_dvfs_clear_1
/* Enable Automatic power savings. */
ldr r6, [r8, #0x404]
bic r6, r6, #0x01
str r6, [r8, #0x404]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_enable_psd_ch1
ldr r6, [r4, #0x404]
bic r6, r6, #0x200000
str r6, [r4, #0x404]
poll_dvfs_clear_2:
ldr r6, [r4, #0x404]
and r6, r6, #0x2000000
cmp r6, #0x2000000
beq poll_dvfs_clear_2
ldr r6, [r4, #0x404]
bic r6, r6, #0x01
str r6, [r4, #0x404]
skip_enable_psd_ch1:
ldr r10, =24000000
cmp r0, r10
beq skip_power_down
/* Enable MMDC power down timer. */
ldr r6, [r8, #0x4]
orr r6, r6, #0x5500
str r6, [r8, #0x4]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_power_down
ldr r6, [r4, #0x4]
orr r6, r6, #0x5500
str r6, [r4, #0x4]
skip_power_down:
/* clear SBS - unblock DDR accesses */
ldr r6, [r8, #0x410]
bic r6, r6, #0x100
str r6, [r8, #0x410]
/* Check if lpddr2 channel 2 is enabled */
ldr r6, [r8, #0x18]
ands r6, r6, #(1 << 2)
beq skip_disable_sbs_ch1
ldr r6, [r4, #0x410]
bic r6, r6, #0x100
str r6, [r4, #0x410]
skip_disable_sbs_ch1:
#ifdef CONFIG_CACHE_L2X0
/* Enable L2. */
ldr r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
ldr r6, =0x1
str r6, [r7, #0x100]
#endif
/* Enable L1 data cache. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
/* Restore the TTBCR */
dsb
isb
/* Read TTBCR and set PD0=0, N = 0 */
mrc p15, 0, r6, c2, c0, 2
bic r6, r6, #0x11
mcr p15, 0, r6, c2, c0, 2
dsb
isb
/* flush the TLB */
ldr r6, =0x0
mcr p15, 0, r6, c8, c3, 0
dsb
isb
/* Enable Branch Prediction, Z bit in SCTLR. */
mrc p15, 0, r6, c1, c0, 0
orr r6, r6, #0x800
mcr p15, 0, r6, c1, c0, 0
/* Flush the Branch Target Address Cache (BTAC) */
ldr r6, =0x0
mcr p15, 0, r6, c7, c1, 6
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
pop {r2-r10}
/* Restore registers */
mov pc, lr
/*
* Add ltorg here to ensure that all
* literals are stored here and are
* within the text space.
*/
.ltorg
mx6q_lpddr2_freq_change_end:

View File

@ -300,6 +300,8 @@ static void __init imx6q_map_io(void)
{
debug_ll_io_init();
imx_scu_map_io();
imx6_pm_map_io();
imx_busfreq_map_io();
}
static void __init imx6q_init_irq(void)

51
arch/arm/mach-imx/mx6.h Normal file
View File

@ -0,0 +1,51 @@
/*
* Copyright 2004-2015 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
* * This program is free software; you can redistribute it and/or modify
* * it under the terms of the GNU General Public License version 2 as
* * published by the Free Software Foundation.
* */
#ifndef __ASM_ARCH_MXC_IOMAP_H__
#define __ASM_ARCH_MXC_IOMAP_H__
#define MX6Q_IO_P2V(x) IMX_IO_P2V(x)
#define MX6Q_IO_ADDRESS(x) IOMEM(MX6Q_IO_P2V(x))
#define MX6Q_L2_BASE_ADDR 0x00a02000
#define MX6Q_L2_SIZE 0x1000
#define MX6Q_IOMUXC_BASE_ADDR 0x020e0000
#define MX6Q_IOMUXC_SIZE 0x4000
#define MX6Q_SRC_BASE_ADDR 0x020d8000
#define MX6Q_SRC_SIZE 0x4000
#define MX6Q_CCM_BASE_ADDR 0x020c4000
#define MX6Q_CCM_SIZE 0x4000
#define MX6Q_ANATOP_BASE_ADDR 0x020c8000
#define MX6Q_ANATOP_SIZE 0x1000
#define MX6Q_GPC_BASE_ADDR 0x020dc000
#define MX6Q_GPC_SIZE 0x4000
#define MX6Q_SEMA4_BASE_ADDR 0x02290000
#define MX6Q_SEMA4_SIZE 0x4000
#define MX6Q_MMDC_P0_BASE_ADDR 0x021b0000
#define MX6Q_MMDC_P0_SIZE 0x4000
#define MX6Q_MMDC_P1_BASE_ADDR 0x021b4000
#define MX6Q_MMDC_P1_SIZE 0x4000
#define MX6Q_AIPS1_BASE_ADDR 0x02000000
#define MX6Q_AIPS1_SIZE 0x100000
#define MX6Q_AIPS2_BASE_ADDR 0x02100000
#define MX6Q_AIPS2_SIZE 0x100000
#define MX6Q_AIPS3_BASE_ADDR 0x02200000
#define MX6Q_AIPS3_SIZE 0x100000
#define MX6SX_IRAM_TLB_BASE_ADDR 0x008f8000
#define MX6Q_IRAM_TLB_BASE_ADDR 0x00900000
#define MX6Q_IRAM_TLB_SIZE 0x4000
#define TT_ATTRIB_NON_CACHEABLE_1M 0x802
#define MX6_SUSPEND_IRAM_DATA_SIZE 256
#define MX6SL_WFI_IRAM_DATA_SIZE 100
#define MX6_SUSPEND_IRAM_ADDR_OFFSET 0
#define MX6_CPUIDLE_IRAM_ADDR_OFFSET 0x1000
#endif

View File

@ -87,6 +87,18 @@ static inline bool cpu_is_imx6q(void)
return __mxc_cpu_type == MXC_CPU_IMX6Q;
}
static inline bool cpu_is_imx6(void)
{
return __mxc_cpu_type == MXC_CPU_IMX6Q ||
__mxc_cpu_type == MXC_CPU_IMX6DL ||
__mxc_cpu_type == MXC_CPU_IMX6SL ||
__mxc_cpu_type == MXC_CPU_IMX6SX ||
__mxc_cpu_type == MXC_CPU_IMX6UL ||
__mxc_cpu_type == MXC_CPU_IMX6ULL ||
__mxc_cpu_type == MXC_CPU_IMX6SLL ||
__mxc_cpu_type == MXC_CPU_IMX6ULZ;
}
static inline bool cpu_is_imx7d(void)
{
return __mxc_cpu_type == MXC_CPU_IMX7D;

View File

@ -18,7 +18,7 @@
#include "hardware.h"
u32 g_diag_reg;
static void __iomem *scu_base;
void __iomem *scu_base;
static struct map_desc scu_io_desc __initdata = {
/* .virtual and .pfn are run-time assigned */

View File

@ -13,11 +13,13 @@
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_fdt.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <linux/suspend.h>
#include <asm/cacheflush.h>
#include <asm/fncpy.h>
#include <asm/mach/map.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/tlb.h>
@ -58,6 +60,9 @@
#define MX6Q_SUSPEND_OCRAM_SIZE 0x1000
#define MX6_MAX_MMDC_IO_NUM 33
extern unsigned long iram_tlb_base_addr;
extern unsigned long iram_tlb_phys_addr;
static void __iomem *ccm_base;
static void __iomem *suspend_ocram_base;
static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase);
@ -206,6 +211,37 @@ static const struct imx6_pm_socdata imx6ul_pm_data __initconst = {
.mmdc_io_offset = imx6ul_mmdc_io_offset,
};
static struct map_desc iram_tlb_io_desc __initdata = {
/* .virtual and .pfn are run-time assigned */
.length = SZ_1M,
.type = MT_MEMORY_RWX_NONCACHED,
};
/*
* AIPS1 and AIPS2 is not used, because it will trigger a BUG_ON if
* lowlevel debug and earlyprintk are configured.
*
* it is because there is a vm conflict because UART1 is mapped early if
* AIPS1 is mapped using 1M size.
*
* Thus no use AIPS1 and AIPS2 to avoid kernel BUG_ON.
*/
static struct map_desc imx6_pm_io_desc[] __initdata = {
imx_map_entry(MX6Q, MMDC_P0, MT_DEVICE),
imx_map_entry(MX6Q, MMDC_P1, MT_DEVICE),
imx_map_entry(MX6Q, SRC, MT_DEVICE),
imx_map_entry(MX6Q, IOMUXC, MT_DEVICE),
imx_map_entry(MX6Q, CCM, MT_DEVICE),
imx_map_entry(MX6Q, ANATOP, MT_DEVICE),
imx_map_entry(MX6Q, GPC, MT_DEVICE),
imx_map_entry(MX6Q, L2, MT_DEVICE),
};
static const char * const low_power_ocram_match[] __initconst = {
"fsl,lpm-sram",
NULL
};
/*
* This structure is for passing necessary data for low level ocram
* suspend code(arch/arm/mach-imx/suspend-imx6.S), if this struct
@ -433,6 +469,106 @@ static const struct platform_suspend_ops imx6q_pm_ops = {
.valid = imx6q_pm_valid,
};
static int __init imx6_dt_find_lpsram(unsigned long node, const char *uname,
int depth, void *data)
{
unsigned long lpram_addr;
const __be32 *prop = of_get_flat_dt_prop(node, "reg", NULL);
if (of_flat_dt_match(node, low_power_ocram_match)) {
if (!prop)
return -EINVAL;
lpram_addr = be32_to_cpup(prop);
/* We need to create a 1M page table entry. */
iram_tlb_io_desc.virtual = IMX_IO_P2V(lpram_addr & 0xFFF00000);
iram_tlb_io_desc.pfn = __phys_to_pfn(lpram_addr & 0xFFF00000);
iram_tlb_phys_addr = lpram_addr;
iram_tlb_base_addr = IMX_IO_P2V(lpram_addr);
iotable_init(&iram_tlb_io_desc, 1);
}
return 0;
}
void __init imx6_pm_map_io(void)
{
unsigned long i;
iotable_init(imx6_pm_io_desc, ARRAY_SIZE(imx6_pm_io_desc));
/*
* Get the address of IRAM or OCRAM to be used by the low
* power code from the device tree.
*/
WARN_ON(of_scan_flat_dt(imx6_dt_find_lpsram, NULL));
/*
* We moved suspend/resume and lowpower idle to TEE,
* But busfreq now still in Linux, this table is still needed
* If we later decide to move busfreq to TEE, we could drop this.
*/
/* Return if no IRAM space is allocated for suspend/resume code. */
if (!iram_tlb_base_addr) {
pr_warn("No IRAM/OCRAM memory allocated for suspend/resume \
code. Please ensure device tree has an entry for \
fsl,lpm-sram.\n");
return;
}
/* Set all entries to 0. */
memset((void *)iram_tlb_base_addr, 0, MX6Q_IRAM_TLB_SIZE);
/*
* Make sure the IRAM virtual address has a mapping in the IRAM
* page table.
*
* Only use the top 11 bits [31-20] when storing the physical
* address in the page table as only these bits are required
* for 1M mapping.
*/
i = ((iram_tlb_base_addr >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + i) =
(iram_tlb_phys_addr & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the AIPS1 virtual address has a mapping in the
* IRAM page table.
*/
i = ((IMX_IO_P2V(MX6Q_AIPS1_BASE_ADDR) >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + i) =
(MX6Q_AIPS1_BASE_ADDR & 0xFFF00000) |
TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the AIPS2 virtual address has a mapping in the
* IRAM page table.
*/
i = ((IMX_IO_P2V(MX6Q_AIPS2_BASE_ADDR) >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + i) =
(MX6Q_AIPS2_BASE_ADDR & 0xFFF00000) |
TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the AIPS3 virtual address has a mapping
* in the IRAM page table.
*/
i = ((IMX_IO_P2V(MX6Q_AIPS3_BASE_ADDR) >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + i) =
(MX6Q_AIPS3_BASE_ADDR & 0xFFF00000) |
TT_ATTRIB_NON_CACHEABLE_1M;
/*
* Make sure the L2 controller virtual address has a mapping
* in the IRAM page table.
*/
i = ((IMX_IO_P2V(MX6Q_L2_BASE_ADDR) >> 20) << 2) / 4;
*((unsigned long *)iram_tlb_base_addr + i) =
(MX6Q_L2_BASE_ADDR & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M;
}
static int __init imx6_pm_get_base(struct imx6_pm_base *base,
const char *compat)
{

View File

@ -0,0 +1,186 @@
/*
* Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/linkage.h>
#include <asm/smp_scu.h>
#include "hardware.h"
#ifdef CONFIG_SMP
.extern scu_base
#endif
.globl wfe_smp_freq_change_start
.globl wfe_smp_freq_change_end
#ifdef CONFIG_SMP
.align 3
.macro disable_l1_dcache
/*
* Flush all data from the L1 data cache before disabling
* SCTLR.C bit.
*/
push {r0 - r11, lr}
ldr r7, =v7_flush_kern_cache_all
mov lr, pc
mov pc, r7
pop {r0 - r11, lr}
/* disable d-cache */
mrc p15, 0, r6, c1, c0, 0
bic r6, r6, #0x4
mcr p15, 0, r6, c1, c0, 0
dsb
isb
push {r0 - r11, lr}
ldr r7, =v7_flush_kern_cache_all
mov lr, pc
mov pc, r7
pop {r0 - r11, lr}
.endm
ENTRY(wfe_smp_freq_change)
wfe_smp_freq_change_start:
push {r4 - r11, lr}
mov r6, r0
mov r7, r1
dsb
isb
disable_l1_dcache
isb
/* Turn off SMP bit. */
mrc p15, 0, r8, c1, c0, 1
bic r8, r8, #0x40
mcr p15, 0, r8, c1, c0, 1
isb
/* Inform the SCU we are going to enter WFE. */
push {r0 - r11, lr}
ldr r0,=scu_base
ldr r0, [r0]
mov r1, #SCU_PM_DORMANT
ldr r3, =scu_power_mode
mov lr, pc
mov pc, r3
pop {r0 - r11, lr}
go_back_wfe:
wfe
ldr r3, [r7]
cmp r3, #1
beq go_back_wfe
/* Turn ON SMP bit. */
mrc p15, 0, r8, c1, c0, 1
orr r8, r8, #0x40
mcr p15, 0, r8, c1, c0, 1
isb
/* Enable L1 data cache. */
mrc p15, 0, r8, c1, c0, 0
orr r8, r8, #0x4
mcr p15, 0, r8, c1, c0, 0
isb
/* Inform the SCU we have exited WFE. */
push {r0 - r11, lr}
ldr r0,=scu_base
ldr r0, [r0]
mov r1, #SCU_PM_NORMAL
ldr r3, =scu_power_mode
mov lr, pc
mov pc, r3
pop {r0 - r11, lr}
/* Pop all saved registers. */
pop {r4 - r11, lr}
mov pc, lr
.ltorg
wfe_smp_freq_change_end:
ENDPROC(wfe_smp_freq_change)
#ifdef CONFIG_OPTEE
/**
* @brief Switch CPU in WFE mode while bus frequency change
* on-going
*
* @param[in] r0 CPU in WFE Status
* @param[in] r1 Bus frequency change status
*/
.globl imx_smp_wfe_optee_end
ENTRY(imx_smp_wfe_optee)
push {r4-r11, lr}
dsb
isb
disable_l1_dcache
isb
/* Set flag CPU entering WFE. */
mov r4, #1
str r4, [r0]
dsb
isb
1:
wfe
/* Check if busfreq is done, else loop */
ldr r4, [r1]
cmp r4, #1
beq 1b
/* Enable L1 data cache. */
mrc p15, 0, r4, c1, c0, 0
orr r4, r4, #0x4
mcr p15, 0, r4, c1, c0, 0
isb
/* Set flag CPU exiting WFE. */
mov r4, #0
str r4, [r0]
/* Pop all saved registers. */
pop {r4-r11, lr}
mov pc, lr
.ltorg
imx_smp_wfe_optee_end:
ENDPROC(imx_smp_wfe_optee)
#endif
#endif