MLK-11390-6 ARM: imx: add busfreq support for imx6sx LPDDR2
authorAnson Huang <b20788@freescale.com>
Thu, 20 Aug 2015 04:17:49 +0000 (12:17 +0800)
committerNitin Garg <nitin.garg@nxp.com>
Mon, 19 Mar 2018 19:48:04 +0000 (14:48 -0500)
This patch adds busfreq support for i.MX6SX LPDDR2, tested
on i.MX6SX 19x19 LPDDR2 ARM2 board.

Signed-off-by: Anson Huang <b20788@freescale.com>
arch/arm/mach-imx/Makefile
arch/arm/mach-imx/busfreq_lpddr2.c [new file with mode: 0644]
arch/arm/mach-imx/lpddr2_freq_imx6sx.S [new file with mode: 0644]

index 4a680bb..7ff16f3 100644 (file)
@@ -82,7 +82,8 @@ endif
 obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o
 obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o
 AFLAGS_ddr3_freq_imx6sx.o :=-Wa,-march=armv7-a
-obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o ddr3_freq_imx6sx.o
+AFLAGS_lpddr2_freq_imx6sx.o :=-Wa,-march=armv7-a
+obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o ddr3_freq_imx6sx.o lpddr2_freq_imx6sx.o busfreq_lpddr2.o
 obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o
 obj-$(CONFIG_SOC_IMX7D) += mach-imx7d.o mu.o
 
diff --git a/arch/arm/mach-imx/busfreq_lpddr2.c b/arch/arm/mach-imx/busfreq_lpddr2.c
new file mode 100644 (file)
index 0000000..f262ccc
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file busfreq_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/busfreq-imx.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 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 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;
+       int mode = get_bus_freq_mode();
+
+       if (ddr_rate == curr_ddr_rate)
+               return 0;
+
+       printk(KERN_DEBUG "\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,
+               (mode == BUS_FREQ_LOW || mode == BUS_FREQ_ULTRA_LOW) ? 1 : 0);
+       restore_ttbr1(ttbr1);
+
+       curr_ddr_rate = ddr_rate;
+       spin_unlock_irqrestore(&freq_lock, flags);
+
+       printk(KERN_DEBUG "\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 = SZ_4K;
+
+       if (cpu_is_imx6sx() || cpu_is_imx6ul())
+               mx6_change_lpddr2_freq = (void *)fncpy(
+                       (void *)ddr_freq_change_iram_base,
+                       &imx6_up_lpddr2_freq_change, ddr_code_size);
+
+       curr_ddr_rate = ddr_normal_rate;
+
+       return 0;
+}
diff --git a/arch/arm/mach-imx/lpddr2_freq_imx6sx.S b/arch/arm/mach-imx/lpddr2_freq_imx6sx.S
new file mode 100644 (file)
index 0000000..2c78aac
--- /dev/null
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/linkage.h>
+#include "hardware.h"
+
+#define 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
+
+       /* Check if the cpu is cortex-a7 */
+       .macro is_ca7
+
+       /* Read the primary cpu number is MPIDR */
+       mrc     p15, 0, r6, c0, c0, 0
+       ldr     r7, =0xfff0
+       and     r6, r6, r7
+       ldr     r7, =0xc070
+       cmp     r6, r7
+
+       .endm
+
+       .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
+/*
+ * Below code can be used by i.MX6SX and i.MX6UL when changing the
+ * frequency of MMDC. the MMDC is the same on these two SOCs.
+ */
+ENTRY(imx6_up_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
+
+       is_ca7
+       beq     skip_disable_l2
+
+#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
+
+skip_disable_l2:
+       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]
+
+       is_ca7
+       beq     skip_enable_l2
+
+#ifdef CONFIG_CACHE_L2X0
+       /* Enable L2. */
+       ldr     r7, =IMX_IO_P2V(MX6Q_L2_BASE_ADDR)
+       ldr     r6, =0x1
+       str     r6, [r7, #0x100]
+#endif
+
+skip_enable_l2:
+       /* 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