diff options
Diffstat (limited to 'arch/arm/cpu/armv7/mx6/clock.c')
-rw-r--r-- | arch/arm/cpu/armv7/mx6/clock.c | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/arch/arm/cpu/armv7/mx6/clock.c b/arch/arm/cpu/armv7/mx6/clock.c index 3b53842e40..e6f227548a 100644 --- a/arch/arm/cpu/armv7/mx6/clock.c +++ b/arch/arm/cpu/armv7/mx6/clock.c @@ -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( |