From c7f32a5bb444de3de22904cad1fac0f08b88ee8d Mon Sep 17 00:00:00 2001 From: Shunqian Zheng Date: Wed, 4 May 2016 15:54:37 +0800 Subject: rockchip: rk3399: add routines to set vop clocks Let vop aclk sources from CPLL, and vop dclk from NPLL. The dclk freq is decided by the edid mode pixel_clock which may require high accuracy like 252750KHz. The pll_para_config() can calculate the dividers for PLL to output desired clock. BRANCH=none BUG=chrome-os-partner:51537 TEST=check display with the other patches Change-Id: I12cf27d3d1177a8b1c4cfbd7c0be10204e3d3142 Signed-off-by: Martin Roth Original-Commit-Id: 0f019b055fffebe9ea3928aae1e25b0ad4feef81 Original-Change-Id: Icef58f87041905961772b69c6b8170d5a866a531 Original-Signed-off-by: Lin Huang Original-Signed-off-by: Shunqian Zheng Original-Reviewed-on: https://chromium-review.googlesource.com/342335 Original-Tested-by: Vadim Bendebury Original-Reviewed-by: Vadim Bendebury Reviewed-on: https://review.coreboot.org/14846 Tested-by: build bot (Jenkins) Reviewed-by: Vadim Bendebury --- src/soc/rockchip/rk3399/clock.c | 121 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) (limited to 'src/soc/rockchip/rk3399/clock.c') diff --git a/src/soc/rockchip/rk3399/clock.c b/src/soc/rockchip/rk3399/clock.c index 1050552c99..46c7a39006 100644 --- a/src/soc/rockchip/rk3399/clock.c +++ b/src/soc/rockchip/rk3399/clock.c @@ -159,6 +159,23 @@ enum { CLK_SARADC_DIV_CON_MASK = 0xff, CLK_SARADC_DIV_CON_SHIFT = 8, + /* CLKSEL_CON47 & CLKSEL_CON48 */ + ACLK_VOP_PLL_SEL_MASK = 0x3, + ACLK_VOP_PLL_SEL_SHIFT = 6, + ACLK_VOP_PLL_SEL_CPLL = 0x1, + ACLK_VOP_DIV_CON_MASK = 0x1f, + ACLK_VOP_DIV_CON_SHIFT = 0, + + /* CLKSEL_CON49 & CLKSEL_CON50 */ + DCLK_VOP_DCLK_SEL_MASK = 1, + DCLK_VOP_DCLK_SEL_SHIFT = 11, + DCLK_VOP_DCLK_SEL_DIVOUT = 0, + DCLK_VOP_PLL_SEL_MASK = 3, + DCLK_VOP_PLL_SEL_SHIFT = 8, + DCLK_VOP_PLL_SEL_VPLL = 0, + DCLK_VOP_DIV_CON_MASK = 0xff, + DCLK_VOP_DIV_CON_SHIFT = 0, + /* CLKSEL_CON58 */ CLK_SPI_PLL_SEL_MASK = 1, CLK_SPI_PLL_SEL_CPLL = 0, @@ -280,6 +297,70 @@ static void rkclk_set_pll(u32 *pll_con, const struct pll_div *div) PLL_MODE_NORM << PLL_MODE_SHIFT)); } +static int pll_para_config(u32 freq_hz, struct pll_div *div) +{ + u32 ref_khz = OSC_HZ / KHz, refdiv, fbdiv = 0; + u32 postdiv1, postdiv2 = 1; + u32 fref_khz; + u32 diff_khz, best_diff_khz; + const u32 max_refdiv = 63, max_fbdiv = 3200, min_fbdiv = 16; + const u32 max_postdiv1 = 7, max_postdiv2 = 7; + u32 vco_khz; + u32 freq_khz = freq_hz / KHz; + + if (!freq_hz) { + printk(BIOS_ERR, "%s: the frequency can't be 0 Hz\n", __func__); + return -1; + } + + postdiv1 = div_round_up(VCO_MIN_KHZ, freq_khz); + if (postdiv1 > max_postdiv1) { + postdiv2 = div_round_up(postdiv1, max_postdiv1); + postdiv1 = div_round_up(postdiv1, postdiv2); + } + + vco_khz = freq_khz * postdiv1 * postdiv2; + + if (vco_khz < VCO_MIN_KHZ || vco_khz > VCO_MAX_KHZ || + postdiv2 > max_postdiv2) { + printk(BIOS_ERR, "%s: Cannot find out a supported VCO" + " for Frequency (%uHz).\n", __func__, freq_hz); + return -1; + } + + div->postdiv1 = postdiv1; + div->postdiv2 = postdiv2; + + best_diff_khz = vco_khz; + for (refdiv = 1; refdiv < max_refdiv && best_diff_khz; refdiv++) { + fref_khz = ref_khz / refdiv; + + fbdiv = vco_khz / fref_khz; + if ((fbdiv >= max_fbdiv) || (fbdiv <= min_fbdiv)) + continue; + diff_khz = vco_khz - fbdiv * fref_khz; + if (fbdiv + 1 < max_fbdiv && diff_khz > fref_khz / 2) { + fbdiv++; + diff_khz = fref_khz - diff_khz; + } + + if (diff_khz >= best_diff_khz) + continue; + + best_diff_khz = diff_khz; + div->refdiv = refdiv; + div->fbdiv = fbdiv; + } + + if (best_diff_khz > 4 * (MHz/KHz)) { + printk(BIOS_ERR, "%s: Failed to match output frequency %u, " + "difference is %u Hz,exceed 4MHZ\n", __func__, freq_hz, + best_diff_khz * KHz); + return -1; + } + return 0; +} + void rkclk_init(void) { u32 aclk_div; @@ -593,3 +674,43 @@ void rkclk_configure_saradc(unsigned int hz) CLK_SARADC_DIV_CON_SHIFT, (src_clk_div - 1) << CLK_SARADC_DIV_CON_SHIFT)); } + +void rkclk_configure_vop_aclk(u32 vop_id, u32 aclk_hz) +{ + u32 div; + void *reg_addr = vop_id ? &cru_ptr->clksel_con[48] : + &cru_ptr->clksel_con[47]; + + /* vop aclk source clk: cpll */ + div = CPLL_HZ / aclk_hz; + assert((div - 1 < 32) && (div * aclk_hz == CPLL_HZ)); + + write32(reg_addr, RK_CLRSETBITS( + ACLK_VOP_PLL_SEL_MASK << ACLK_VOP_PLL_SEL_SHIFT | + ACLK_VOP_DIV_CON_MASK << ACLK_VOP_DIV_CON_SHIFT, + ACLK_VOP_PLL_SEL_CPLL << ACLK_VOP_PLL_SEL_SHIFT | + (div - 1) << ACLK_VOP_DIV_CON_SHIFT)); +} + +int rkclk_configure_vop_dclk(u32 vop_id, u32 dclk_hz) +{ + struct pll_div vpll_config = {0}; + void *reg_addr = vop_id ? &cru_ptr->clksel_con[50] : + &cru_ptr->clksel_con[49]; + + /* vop dclk source from vpll, and equals to vpll(means div == 1) */ + if (pll_para_config(dclk_hz, &vpll_config)) + return -1; + + rkclk_set_pll(&cru_ptr->vpll_con[0], &vpll_config); + + write32(reg_addr, RK_CLRSETBITS( + DCLK_VOP_DCLK_SEL_MASK << DCLK_VOP_DCLK_SEL_SHIFT | + DCLK_VOP_PLL_SEL_MASK << DCLK_VOP_PLL_SEL_SHIFT | + DCLK_VOP_DIV_CON_MASK << DCLK_VOP_DIV_CON_SHIFT, + DCLK_VOP_DCLK_SEL_DIVOUT << DCLK_VOP_DCLK_SEL_SHIFT | + DCLK_VOP_PLL_SEL_VPLL << DCLK_VOP_PLL_SEL_SHIFT | + (1 - 1) << DCLK_VOP_DIV_CON_SHIFT)); + + return 0; +} -- cgit v1.2.3