static void sunxi_dw_hdmi_pll_set(uint clk_khz) { int value, n, m, div = 0, diff; int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF; div = sunxi_dw_hdmi_get_divider(clk_khz * 1000); /* * Find the lowest divider resulting in a matching clock. If there * is no match, pick the closest lower clock, as monitors tend to * not sync to higher frequencies. */ for (m = 1; m <= 16; m++) { n = (m * div * clk_khz) / 24000; if ((n >= 1) && (n <= 128)) { value = (24000 * n) / m / div; diff = clk_khz - value; if (diff < best_diff) { best_diff = diff; best_m = m; best_n = n; } } } clock_set_pll3_factors(best_m, best_n); debug("dotclock: %dkHz = %dkHz: (24MHz * %d) / %d / %d\n", clk_khz, (clock_get_pll3() / 1000) / div, best_n, best_m, div); }
unsigned int clock_get_mipi_pll(void) { struct sunxi_ccm_reg *const ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; uint32_t rval = readl(&ccm->mipi_pll_cfg); unsigned int n = ((rval & CCM_MIPI_PLL_CTRL_N_MASK) >> CCM_MIPI_PLL_CTRL_N_SHIFT) + 1; unsigned int k = ((rval & CCM_MIPI_PLL_CTRL_K_MASK) >> CCM_MIPI_PLL_CTRL_K_SHIFT) + 1; unsigned int m = ((rval & CCM_MIPI_PLL_CTRL_M_MASK) >> CCM_MIPI_PLL_CTRL_M_SHIFT) + 1; unsigned int src = clock_get_pll3(); /* Multiply by 1000 after dividing by m to avoid integer overflows */ return ((src / 1000) * n * k / m) * 1000; }
void clock_set_mipi_pll(unsigned int clk) { struct sunxi_ccm_reg * const ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; unsigned int k, m, n, value, diff; unsigned best_k = 0, best_m = 0, best_n = 0, best_diff = 0xffffffff; unsigned int src = clock_get_pll3(); /* All calculations are in KHz to avoid overflows */ clk /= 1000; src /= 1000; /* Pick the closest lower clock */ for (k = 1; k <= 4; k++) { for (m = 1; m <= 16; m++) { for (n = 1; n <= 16; n++) { value = src * n * k / m; if (value > clk) continue; diff = clk - value; if (diff < best_diff) { best_diff = diff; best_k = k; best_m = m; best_n = n; } if (diff == 0) goto done; } } } done: writel(CCM_MIPI_PLL_CTRL_EN | CCM_MIPI_PLL_CTRL_LDO_EN | CCM_MIPI_PLL_CTRL_N(best_n) | CCM_MIPI_PLL_CTRL_K(best_k) | CCM_MIPI_PLL_CTRL_M(best_m), &ccm->mipi_pll_cfg); }
/* * LCDC, what allwinner calls a CRTC, so timing controller and serializer. */ static void sunxi_lcdc_pll_set(int tcon, int dotclock, int *clk_div, int *clk_double) { struct sunxi_ccm_reg * const ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; int value, n, m, min_m, max_m, diff; int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF; int best_double = 0; bool use_mipi_pll = false; if (tcon == 0) { #ifdef CONFIG_VIDEO_LCD_IF_PARALLEL min_m = 6; max_m = 127; #endif #ifdef CONFIG_VIDEO_LCD_IF_LVDS min_m = max_m = 7; #endif } else { min_m = 1; max_m = 15; } /* * Find the lowest divider resulting in a matching clock, if there * is no match, pick the closest lower clock, as monitors tend to * not sync to higher frequencies. */ for (m = min_m; m <= max_m; m++) { n = (m * dotclock) / 3000; if ((n >= 9) && (n <= 127)) { value = (3000 * n) / m; diff = dotclock - value; if (diff < best_diff) { best_diff = diff; best_m = m; best_n = n; best_double = 0; } } /* These are just duplicates */ if (!(m & 1)) continue; n = (m * dotclock) / 6000; if ((n >= 9) && (n <= 127)) { value = (6000 * n) / m; diff = dotclock - value; if (diff < best_diff) { best_diff = diff; best_m = m; best_n = n; best_double = 1; } } } #ifdef CONFIG_MACH_SUN6I /* * Use the MIPI pll if we've been unable to find any matching setting * for PLL3, this happens with high dotclocks because of min_m = 6. */ if (tcon == 0 && best_n == 0) { use_mipi_pll = true; best_m = 6; /* Minimum m for tcon0 */ } if (use_mipi_pll) { clock_set_pll3(297000000); /* Fix the video pll at 297 MHz */ clock_set_mipi_pll(best_m * dotclock * 1000); debug("dotclock: %dkHz = %dkHz via mipi pll\n", dotclock, clock_get_mipi_pll() / best_m / 1000); } else #endif { clock_set_pll3(best_n * 3000000); debug("dotclock: %dkHz = %dkHz: (%d * 3MHz * %d) / %d\n", dotclock, (best_double + 1) * clock_get_pll3() / best_m / 1000, best_double + 1, best_n, best_m); } if (tcon == 0) { u32 pll; if (use_mipi_pll) pll = CCM_LCD_CH0_CTRL_MIPI_PLL; else if (best_double) pll = CCM_LCD_CH0_CTRL_PLL3_2X; else pll = CCM_LCD_CH0_CTRL_PLL3; writel(CCM_LCD_CH0_CTRL_GATE | CCM_LCD_CH0_CTRL_RST | pll, &ccm->lcd0_ch0_clk_cfg); } else { writel(CCM_LCD_CH1_CTRL_GATE | (best_double ? CCM_LCD_CH1_CTRL_PLL3_2X : CCM_LCD_CH1_CTRL_PLL3) | CCM_LCD_CH1_CTRL_M(best_m), &ccm->lcd0_ch1_clk_cfg); if (sunxi_is_composite()) setbits_le32(&ccm->lcd0_ch1_clk_cfg, CCM_LCD_CH1_CTRL_HALF_SCLK1); } *clk_div = best_m; *clk_double = best_double; }