]> git.ipfire.org Git - people/ms/u-boot.git/blobdiff - arch/arm/cpu/armv7/mx6/clock.c
imx: mx6: Fix procedure to switch the parent of LDB_DI_CLK
[people/ms/u-boot.git] / arch / arm / cpu / armv7 / mx6 / clock.c
index 3b53842e4072d1639171387c840f5522130be418..e6f227548afeae90487a3f8353daadb51adf7130 100644 (file)
@@ -1217,6 +1217,157 @@ void enable_ipu_clock(void)
        }
 }
 #endif
+
+#if defined(CONFIG_MX6Q) || defined(CONFIG_MX6D) || defined(CONFIG_MX6DL) || \
+       defined(CONFIG_MX6S)
+static void disable_ldb_di_clock_sources(void)
+{
+       struct mxc_ccm_reg *mxc_ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
+       int reg;
+
+       /* Make sure PFDs are disabled at boot. */
+       reg = readl(&mxc_ccm->analog_pfd_528);
+       /* Cannot disable pll2_pfd2_396M, as it is the MMDC clock in iMX6DL */
+       if (is_cpu_type(MXC_CPU_MX6DL))
+               reg |= 0x80008080;
+       else
+               reg |= 0x80808080;
+       writel(reg, &mxc_ccm->analog_pfd_528);
+
+       /* Disable PLL3 PFDs */
+       reg = readl(&mxc_ccm->analog_pfd_480);
+       reg |= 0x80808080;
+       writel(reg, &mxc_ccm->analog_pfd_480);
+
+       /* Disable PLL5 */
+       reg = readl(&mxc_ccm->analog_pll_video);
+       reg &= ~(1 << 13);
+       writel(reg, &mxc_ccm->analog_pll_video);
+}
+
+static void enable_ldb_di_clock_sources(void)
+{
+       struct mxc_ccm_reg *mxc_ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
+       int reg;
+
+       reg = readl(&mxc_ccm->analog_pfd_528);
+       if (is_cpu_type(MXC_CPU_MX6DL))
+               reg &= ~(0x80008080);
+       else
+               reg &= ~(0x80808080);
+       writel(reg, &mxc_ccm->analog_pfd_528);
+
+       reg = readl(&mxc_ccm->analog_pfd_480);
+       reg &= ~(0x80808080);
+       writel(reg, &mxc_ccm->analog_pfd_480);
+}
+
+/*
+ * Try call this function as early in the boot process as possible since the
+ * function temporarily disables PLL2 PFD's, PLL3 PFD's and PLL5.
+ */
+void select_ldb_di_clock_source(enum ldb_di_clock clk)
+{
+       struct mxc_ccm_reg *mxc_ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
+       int reg;
+
+       /*
+        * Need to follow a strict procedure when changing the LDB
+        * clock, else we can introduce a glitch. Things to keep in
+        * mind:
+        * 1. The current and new parent clocks must be disabled.
+        * 2. The default clock for ldb_dio_clk is mmdc_ch1 which has
+        * no CG bit.
+        * 3. In the RTL implementation of the LDB_DI_CLK_SEL mux
+        * the top four options are in one mux and the PLL3 option along
+        * with another option is in the second mux. There is third mux
+        * used to decide between the first and second mux.
+        * The code below switches the parent to the bottom mux first
+        * and then manipulates the top mux. This ensures that no glitch
+        * will enter the divider.
+        *
+        * Need to disable MMDC_CH1 clock manually as there is no CG bit
+        * for this clock. The only way to disable this clock is to move
+        * it to pll3_sw_clk and then to disable pll3_sw_clk
+        * Make sure periph2_clk2_sel is set to pll3_sw_clk
+        */
+
+       /* Disable all ldb_di clock parents */
+       disable_ldb_di_clock_sources();
+
+       /* Set MMDC_CH1 mask bit */
+       reg = readl(&mxc_ccm->ccdr);
+       reg |= MXC_CCM_CCDR_MMDC_CH1_HS_MASK;
+       writel(reg, &mxc_ccm->ccdr);
+
+       /* Set periph2_clk2_sel to be sourced from PLL3_sw_clk */
+       reg = readl(&mxc_ccm->cbcmr);
+       reg &= ~MXC_CCM_CBCMR_PERIPH2_CLK2_SEL;
+       writel(reg, &mxc_ccm->cbcmr);
+
+       /*
+        * Set the periph2_clk_sel to the top mux so that
+        * mmdc_ch1 is from pll3_sw_clk.
+        */
+       reg = readl(&mxc_ccm->cbcdr);
+       reg |= MXC_CCM_CBCDR_PERIPH2_CLK_SEL;
+       writel(reg, &mxc_ccm->cbcdr);
+
+       /* Wait for the clock switch */
+       while (readl(&mxc_ccm->cdhipr))
+               ;
+       /* Disable pll3_sw_clk by selecting bypass clock source */
+       reg = readl(&mxc_ccm->ccsr);
+       reg |= MXC_CCM_CCSR_PLL3_SW_CLK_SEL;
+       writel(reg, &mxc_ccm->ccsr);
+
+       /* Set the ldb_di0_clk and ldb_di1_clk to 111b */
+       reg = readl(&mxc_ccm->cs2cdr);
+       reg |= ((7 << MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_OFFSET)
+             | (7 << MXC_CCM_CS2CDR_LDB_DI0_CLK_SEL_OFFSET));
+       writel(reg, &mxc_ccm->cs2cdr);
+
+       /* Set the ldb_di0_clk and ldb_di1_clk to 100b */
+       reg = readl(&mxc_ccm->cs2cdr);
+       reg &= ~(MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_MASK
+             | MXC_CCM_CS2CDR_LDB_DI0_CLK_SEL_MASK);
+       reg |= ((4 << MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_OFFSET)
+             | (4 << MXC_CCM_CS2CDR_LDB_DI0_CLK_SEL_OFFSET));
+       writel(reg, &mxc_ccm->cs2cdr);
+
+       /* Set the ldb_di0_clk and ldb_di1_clk to desired source */
+       reg = readl(&mxc_ccm->cs2cdr);
+       reg &= ~(MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_MASK
+             | MXC_CCM_CS2CDR_LDB_DI0_CLK_SEL_MASK);
+       reg |= ((clk << MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_OFFSET)
+             | (clk << MXC_CCM_CS2CDR_LDB_DI0_CLK_SEL_OFFSET));
+       writel(reg, &mxc_ccm->cs2cdr);
+
+       /* Unbypass pll3_sw_clk */
+       reg = readl(&mxc_ccm->ccsr);
+       reg &= ~MXC_CCM_CCSR_PLL3_SW_CLK_SEL;
+       writel(reg, &mxc_ccm->ccsr);
+
+       /*
+        * Set the periph2_clk_sel back to the bottom mux so that
+        * mmdc_ch1 is from its original parent.
+        */
+       reg = readl(&mxc_ccm->cbcdr);
+       reg &= ~MXC_CCM_CBCDR_PERIPH2_CLK_SEL;
+       writel(reg, &mxc_ccm->cbcdr);
+
+       /* Wait for the clock switch */
+       while (readl(&mxc_ccm->cdhipr))
+               ;
+       /* Clear MMDC_CH1 mask bit */
+       reg = readl(&mxc_ccm->ccdr);
+       reg &= ~MXC_CCM_CCDR_MMDC_CH1_HS_MASK;
+       writel(reg, &mxc_ccm->ccdr);
+
+       enable_ldb_di_clock_sources();
+}
+#endif
+
 /***************************************************/
 
 U_BOOT_CMD(