|
@@ -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(
|