From ad153782e07ee3a95ccef6b6eeddaaaec918421f Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 29 Oct 2015 15:54:47 +0800 Subject: imx: mx6: add clock api for lcdif Implement mxs_set_lcdclk, enable_lcdif_clock and enable_pll_video. The three API can be used to configure lcdif related clock when CONFIG_VIDEO_MXS enabled. Signed-off-by: Peng Fan Cc: Stefano Babic --- arch/arm/cpu/armv7/mx6/clock.c | 245 ++++++++++++++++++++++++++++++++++ arch/arm/include/asm/arch-mx6/clock.h | 2 + 2 files changed, 247 insertions(+) (limited to 'arch') diff --git a/arch/arm/cpu/armv7/mx6/clock.c b/arch/arm/cpu/armv7/mx6/clock.c index 11efd12c9a..67e0f3252f 100644 --- a/arch/arm/cpu/armv7/mx6/clock.c +++ b/arch/arm/cpu/armv7/mx6/clock.c @@ -473,6 +473,251 @@ static u32 get_mmdc_ch0_clk(void) } } +#if defined(CONFIG_VIDEO_MXS) +static int enable_pll_video(u32 pll_div, u32 pll_num, u32 pll_denom, + u32 post_div) +{ + u32 reg = 0; + ulong start; + + debug("pll5 div = %d, num = %d, denom = %d\n", + pll_div, pll_num, pll_denom); + + /* Power up PLL5 video */ + writel(BM_ANADIG_PLL_VIDEO_POWERDOWN | + BM_ANADIG_PLL_VIDEO_BYPASS | + BM_ANADIG_PLL_VIDEO_DIV_SELECT | + BM_ANADIG_PLL_VIDEO_POST_DIV_SELECT, + &imx_ccm->analog_pll_video_clr); + + /* Set div, num and denom */ + switch (post_div) { + case 1: + writel(BF_ANADIG_PLL_VIDEO_DIV_SELECT(pll_div) | + BF_ANADIG_PLL_VIDEO_POST_DIV_SELECT(0x2), + &imx_ccm->analog_pll_video_set); + break; + case 2: + writel(BF_ANADIG_PLL_VIDEO_DIV_SELECT(pll_div) | + BF_ANADIG_PLL_VIDEO_POST_DIV_SELECT(0x1), + &imx_ccm->analog_pll_video_set); + break; + case 4: + writel(BF_ANADIG_PLL_VIDEO_DIV_SELECT(pll_div) | + BF_ANADIG_PLL_VIDEO_POST_DIV_SELECT(0x0), + &imx_ccm->analog_pll_video_set); + break; + default: + puts("Wrong test_div!\n"); + return -EINVAL; + } + + writel(BF_ANADIG_PLL_VIDEO_NUM_A(pll_num), + &imx_ccm->analog_pll_video_num); + writel(BF_ANADIG_PLL_VIDEO_DENOM_B(pll_denom), + &imx_ccm->analog_pll_video_denom); + + /* Wait PLL5 lock */ + start = get_timer(0); /* Get current timestamp */ + + do { + reg = readl(&imx_ccm->analog_pll_video); + if (reg & BM_ANADIG_PLL_VIDEO_LOCK) { + /* Enable PLL out */ + writel(BM_ANADIG_PLL_VIDEO_ENABLE, + &imx_ccm->analog_pll_video_set); + return 0; + } + } while (get_timer(0) < (start + 10)); /* Wait 10ms */ + + puts("Lock PLL5 timeout\n"); + + return -ETIME; +} + +/* + * 24M--> PLL_VIDEO -> LCDIFx_PRED -> LCDIFx_PODF -> LCD + * + * 'freq' using KHz as unit, see driver/video/mxsfb.c. + */ +void mxs_set_lcdclk(u32 base_addr, u32 freq) +{ + u32 reg = 0; + u32 hck = MXC_HCLK / 1000; + /* DIV_SELECT ranges from 27 to 54 */ + u32 min = hck * 27; + u32 max = hck * 54; + u32 temp, best = 0; + u32 i, j, max_pred = 8, max_postd = 8, pred = 1, postd = 1; + u32 pll_div, pll_num, pll_denom, post_div = 1; + + debug("mxs_set_lcdclk, freq = %dKHz\n", freq); + + if ((!is_cpu_type(MXC_CPU_MX6SX)) && !is_cpu_type(MXC_CPU_MX6UL)) { + debug("This chip not support lcd!\n"); + return; + } + + if (base_addr == LCDIF1_BASE_ADDR) { + reg = readl(&imx_ccm->cscdr2); + /* Can't change clocks when clock not from pre-mux */ + if ((reg & MXC_CCM_CSCDR2_LCDIF1_CLK_SEL_MASK) != 0) + return; + } + + if (is_cpu_type(MXC_CPU_MX6SX)) { + reg = readl(&imx_ccm->cscdr2); + /* Can't change clocks when clock not from pre-mux */ + if ((reg & MXC_CCM_CSCDR2_LCDIF2_CLK_SEL_MASK) != 0) + return; + } + + temp = freq * max_pred * max_postd; + if (temp > max) { + puts("Please decrease freq, too large!\n"); + return; + } + if (temp < min) { + /* + * Register: PLL_VIDEO + * Bit Field: POST_DIV_SELECT + * 00 — Divide by 4. + * 01 — Divide by 2. + * 10 — Divide by 1. + * 11 — Reserved + * No need to check post_div(1) + */ + for (post_div = 2; post_div <= 4; post_div <<= 1) { + if ((temp * post_div) > min) { + freq *= post_div; + break; + } + } + + if (post_div > 4) { + printf("Fail to set rate to %dkhz", freq); + return; + } + } + + /* Choose the best pred and postd to match freq for lcd */ + for (i = 1; i <= max_pred; i++) { + for (j = 1; j <= max_postd; j++) { + temp = freq * i * j; + if (temp > max || temp < min) + continue; + if (best == 0 || temp < best) { + best = temp; + pred = i; + postd = j; + } + } + } + + if (best == 0) { + printf("Fail to set rate to %dKHz", freq); + return; + } + + debug("best %d, pred = %d, postd = %d\n", best, pred, postd); + + pll_div = best / hck; + pll_denom = 1000000; + pll_num = (best - hck * pll_div) * pll_denom / hck; + + /* + * pll_num + * (24MHz * (pll_div + --------- )) + * pll_denom + *freq KHz = -------------------------------- + * post_div * pred * postd * 1000 + */ + + if (base_addr == LCDIF1_BASE_ADDR) { + if (enable_pll_video(pll_div, pll_num, pll_denom, post_div)) + return; + + /* Select pre-lcd clock to PLL5 and set pre divider */ + clrsetbits_le32(&imx_ccm->cscdr2, + MXC_CCM_CSCDR2_LCDIF1_PRED_SEL_MASK | + MXC_CCM_CSCDR2_LCDIF1_PRE_DIV_MASK, + (0x2 << MXC_CCM_CSCDR2_LCDIF1_PRED_SEL_OFFSET) | + ((pred - 1) << + MXC_CCM_CSCDR2_LCDIF1_PRE_DIV_OFFSET)); + + /* Set the post divider */ + clrsetbits_le32(&imx_ccm->cbcmr, + MXC_CCM_CBCMR_LCDIF1_PODF_MASK, + ((postd - 1) << + MXC_CCM_CBCMR_LCDIF1_PODF_OFFSET)); + } else if (is_cpu_type(MXC_CPU_MX6SX)) { + /* Setting LCDIF2 for i.MX6SX */ + if (enable_pll_video(pll_div, pll_num, pll_denom, post_div)) + return; + + /* Select pre-lcd clock to PLL5 and set pre divider */ + clrsetbits_le32(&imx_ccm->cscdr2, + MXC_CCM_CSCDR2_LCDIF2_PRED_SEL_MASK | + MXC_CCM_CSCDR2_LCDIF2_PRE_DIV_MASK, + (0x2 << MXC_CCM_CSCDR2_LCDIF2_PRED_SEL_OFFSET) | + ((pred - 1) << + MXC_CCM_CSCDR2_LCDIF2_PRE_DIV_OFFSET)); + + /* Set the post divider */ + clrsetbits_le32(&imx_ccm->cscmr1, + MXC_CCM_CSCMR1_LCDIF2_PODF_MASK, + ((postd - 1) << + MXC_CCM_CSCMR1_LCDIF2_PODF_OFFSET)); + } +} + +int enable_lcdif_clock(u32 base_addr) +{ + u32 reg = 0; + u32 lcdif_clk_sel_mask, lcdif_ccgr3_mask; + + if (is_cpu_type(MXC_CPU_MX6SX)) { + if ((base_addr == LCDIF1_BASE_ADDR) || + (base_addr == LCDIF2_BASE_ADDR)) { + puts("Wrong LCD interface!\n"); + return -EINVAL; + } + /* Set to pre-mux clock at default */ + lcdif_clk_sel_mask = (base_addr == LCDIF2_BASE_ADDR) ? + MXC_CCM_CSCDR2_LCDIF2_CLK_SEL_MASK : + MXC_CCM_CSCDR2_LCDIF1_CLK_SEL_MASK; + lcdif_ccgr3_mask = (base_addr == LCDIF2_BASE_ADDR) ? + (MXC_CCM_CCGR3_LCDIF2_PIX_MASK | + MXC_CCM_CCGR3_DISP_AXI_MASK) : + (MXC_CCM_CCGR3_LCDIF1_PIX_MASK | + MXC_CCM_CCGR3_DISP_AXI_MASK); + } else if (is_cpu_type(MXC_CPU_MX6UL)) { + if (base_addr != LCDIF1_BASE_ADDR) { + puts("Wrong LCD interface!\n"); + return -EINVAL; + } + /* Set to pre-mux clock at default */ + lcdif_clk_sel_mask = MXC_CCM_CSCDR2_LCDIF1_CLK_SEL_MASK; + lcdif_ccgr3_mask = MXC_CCM_CCGR3_LCDIF1_PIX_MASK; + } else { + return 0; + } + + reg = readl(&imx_ccm->cscdr2); + reg &= ~lcdif_clk_sel_mask; + writel(reg, &imx_ccm->cscdr2); + + /* Enable the LCDIF pix clock */ + reg = readl(&imx_ccm->CCGR3); + reg |= lcdif_ccgr3_mask; + writel(reg, &imx_ccm->CCGR3); + + reg = readl(&imx_ccm->CCGR2); + reg |= MXC_CCM_CCGR2_LCD_MASK; + writel(reg, &imx_ccm->CCGR2); +} +#endif + #ifdef CONFIG_FSL_QSPI /* qspi_num can be from 0 - 1 */ void enable_qspi_clk(int qspi_num) diff --git a/arch/arm/include/asm/arch-mx6/clock.h b/arch/arm/include/asm/arch-mx6/clock.h index 2b220d6f8f..14505239e8 100644 --- a/arch/arm/include/asm/arch-mx6/clock.h +++ b/arch/arm/include/asm/arch-mx6/clock.h @@ -66,6 +66,8 @@ int enable_spi_clk(unsigned char enable, unsigned spi_num); void enable_ipu_clock(void); int enable_fec_anatop_clock(int fec_id, enum enet_freq freq); void enable_enet_clk(unsigned char enable); +int enable_lcdif_clock(u32 base_addr); void enable_qspi_clk(int qspi_num); void enable_thermal_clk(void); +void mxs_set_lcdclk(u32 base_addr, u32 freq); #endif /* __ASM_ARCH_CLOCK_H */ -- cgit v1.2.1