From 1a8e0af78b1886acc96d1e80be5871d287d148c5 Mon Sep 17 00:00:00 2001 From: Hung-Te Lin Date: Tue, 8 Apr 2014 20:03:40 +0800 Subject: [PATCH] tegra124: Setup clock PLLD by approximating display panel pixel clock. PLLD, the clock for display, was previously hard-coded to 306MHz. To support more different panels, we should calcualte PLLD by panel pixel clock configuration. Note existing pixel clock configurations for nyan* boards won't work (they used to rely on hard-coded approximated values) so the device trees are also modified. BRANCH=none BUG=chrome-os-partner:25933 TEST=emerge-nyan_big coreboot chromeos-bootimage See panel correctly initialized and got DEV screen. Original-Change-Id: I8d592f0cc044e7c4e4803c45955642e791210ad3 Original-Signed-off-by: Hung-Te Lin Original-Reviewed-on: https://chromium-review.googlesource.com/193565 (cherry picked from commit 4f9b793633ebb2d104b0544e3b72fa0d105951c4) Signed-off-by: Marc Jones Change-Id: Ib2cabbad60af010e872505e888eab485ba8c2916 Reviewed-on: http://review.coreboot.org/7762 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer --- src/mainboard/google/nyan/devicetree.cb | 10 +- src/mainboard/google/nyan_big/devicetree.cb | 10 +- src/mainboard/google/nyan_blaze/devicetree.cb | 10 +- src/soc/nvidia/tegra124/chip.h | 1 - src/soc/nvidia/tegra124/clock.c | 92 ++++++++++++++++--- src/soc/nvidia/tegra124/display.c | 28 ++++-- src/soc/nvidia/tegra124/include/soc/clock.h | 1 + 7 files changed, 106 insertions(+), 46 deletions(-) diff --git a/src/mainboard/google/nyan/devicetree.cb b/src/mainboard/google/nyan/devicetree.cb index 600bdbef7b..885445022e 100644 --- a/src/mainboard/google/nyan/devicetree.cb +++ b/src/mainboard/google/nyan/devicetree.cb @@ -79,15 +79,7 @@ chip soc/nvidia/tegra124 register "vsync_width" = "12" register "vback_porch" = "12" - # we *know* the pixel clock for this system. - # 1592 x 800 x 60Hz = 76416000 - register "pixel_clock" = "76416000" - register "pll_div" = "2" - - # use plld_out0 (ie, plld/2) as clock source - # plld -> plld_out0 -> pclk - # plld = plld_out0 * 2 = (pclk * pll_div) * 2 - # = 305664000Hz + register "pixel_clock" = "76400000" # link configurations register "lane_count" = "1" diff --git a/src/mainboard/google/nyan_big/devicetree.cb b/src/mainboard/google/nyan_big/devicetree.cb index 600bdbef7b..885445022e 100644 --- a/src/mainboard/google/nyan_big/devicetree.cb +++ b/src/mainboard/google/nyan_big/devicetree.cb @@ -79,15 +79,7 @@ chip soc/nvidia/tegra124 register "vsync_width" = "12" register "vback_porch" = "12" - # we *know* the pixel clock for this system. - # 1592 x 800 x 60Hz = 76416000 - register "pixel_clock" = "76416000" - register "pll_div" = "2" - - # use plld_out0 (ie, plld/2) as clock source - # plld -> plld_out0 -> pclk - # plld = plld_out0 * 2 = (pclk * pll_div) * 2 - # = 305664000Hz + register "pixel_clock" = "76400000" # link configurations register "lane_count" = "1" diff --git a/src/mainboard/google/nyan_blaze/devicetree.cb b/src/mainboard/google/nyan_blaze/devicetree.cb index 600bdbef7b..885445022e 100644 --- a/src/mainboard/google/nyan_blaze/devicetree.cb +++ b/src/mainboard/google/nyan_blaze/devicetree.cb @@ -79,15 +79,7 @@ chip soc/nvidia/tegra124 register "vsync_width" = "12" register "vback_porch" = "12" - # we *know* the pixel clock for this system. - # 1592 x 800 x 60Hz = 76416000 - register "pixel_clock" = "76416000" - register "pll_div" = "2" - - # use plld_out0 (ie, plld/2) as clock source - # plld -> plld_out0 -> pclk - # plld = plld_out0 * 2 = (pclk * pll_div) * 2 - # = 305664000Hz + register "pixel_clock" = "76400000" # link configurations register "lane_count" = "1" diff --git a/src/soc/nvidia/tegra124/chip.h b/src/soc/nvidia/tegra124/chip.h index 87d043a8ca..89a8d977aa 100644 --- a/src/soc/nvidia/tegra124/chip.h +++ b/src/soc/nvidia/tegra124/chip.h @@ -89,7 +89,6 @@ struct soc_nvidia_tegra124_config { int vfront_porch; int pixel_clock; - int pll_div;; /* The minimum link configuraton settings */ u32 lane_count; diff --git a/src/soc/nvidia/tegra124/clock.c b/src/soc/nvidia/tegra124/clock.c index 4675b7b124..d12c4b2e89 100644 --- a/src/soc/nvidia/tegra124/clock.c +++ b/src/soc/nvidia/tegra124/clock.c @@ -88,7 +88,6 @@ struct { int khz; struct pllcx_dividers pllx; /* target: CONFIG_PLLX_KHZ */ struct pllcx_dividers pllc; /* target: 600 MHz */ - struct pllpad_dividers plld; /* target: 306 MHz */ struct pllu_dividers pllu; /* target; 960 MHz */ struct pllcx_dividers plldp; /* target; 270 MHz */ /* Based on T124 TRM (to be updatd), PLLP is set to 408MHz in HW. @@ -99,7 +98,6 @@ struct { .khz = 12000, .pllx = {.n = TEGRA_PLLX_KHZ / 12000, .m = 1, .p = 0}, .pllc = {.n = 50, .m = 1, .p = 0}, - .plld = {.n = 306, .m = 12, .p = 0, .cpcon = 8}, .pllu = {.n = 960, .m = 12, .p = 0, .cpcon = 12, .lfcon = 2}, .plldp = {.n = 90, .m = 1, .p = 3}, }, @@ -107,7 +105,6 @@ struct { .khz = 13000, .pllx = {.n = TEGRA_PLLX_KHZ / 13000, .m = 1, .p = 0}, .pllc = {.n = 231, .m = 5, .p = 0}, /* 600.6 MHz */ - .plld = {.n = 306, .m = 13, .p = 0, .cpcon = 8}, .pllu = {.n = 960, .m = 13, .p = 0, .cpcon = 12, .lfcon = 2}, .plldp = {.n = 83, .m = 1, .p = 3}, /* 269.75 MHz */ }, @@ -115,7 +112,6 @@ struct { .khz = 16800, .pllx = {.n = TEGRA_PLLX_KHZ / 16800, .m = 1, .p = 0}, .pllc = {.n = 250, .m = 7, .p = 0}, - .plld = {.n = 309, .m = 17, .p = 0, .cpcon = 8}, /* 305.4 MHz*/ .pllu = {.n = 400, .m = 7, .p = 0, .cpcon = 5, .lfcon = 2}, .plldp = {.n = 64, .m = 1, .p = 3}, /* 268.8 MHz */ }, @@ -123,7 +119,6 @@ struct { .khz = 19200, .pllx = {.n = TEGRA_PLLX_KHZ / 19200, .m = 1, .p = 0}, .pllc = {.n = 125, .m = 4, .p = 0}, - .plld = {.n = 271, .m = 17, .p = 0, .cpcon = 8}, /* 306.1 MHz */ .pllu = {.n = 200, .m = 4, .p = 0, .cpcon = 3, .lfcon = 2}, .plldp = {.n = 56, .m = 1, .p = 3}, /* 270.75 MHz */ }, @@ -131,7 +126,6 @@ struct { .khz = 26000, .pllx = {.n = TEGRA_PLLX_KHZ / 26000, .m = 1, .p = 0}, .pllc = {.n = 23, .m = 1, .p = 0}, /* 598 MHz */ - .plld = {.n = 306, .m = 26, .p = 0, .cpcon = 8}, .pllu = {.n = 960, .m = 26, .p = 0, .cpcon = 12, .lfcon = 2}, .plldp = {.n = 83, .m = 2, .p = 3}, /* 266.50 MHz */ }, @@ -143,7 +137,6 @@ struct { */ .pllx = {.n = TEGRA_PLLX_KHZ / 19200, .m = 1, .p = 0}, .pllc = {.n = 125, .m = 4, .p = 0}, - .plld = {.n = 271, .m = 17, .p = 0, .cpcon = 8}, /* 306.1 MHz */ .pllu = {.n = 200, .m = 4, .p = 0, .cpcon = 3, .lfcon = 2}, .plldp = {.n = 56, .m = 2, .p = 3}, /* 268 MHz */ }, @@ -155,7 +148,6 @@ struct { */ .pllx = {.n = TEGRA_PLLX_KHZ / 12000, .m = 1, .p = 0}, .pllc = {.n = 50, .m = 1, .p = 0}, - .plld = {.n = 306, .m = 12, .p = 0, .cpcon = 8}, .pllu = {.n = 960, .m = 12, .p = 0, .cpcon = 12, .lfcon = 2}, .plldp = {.n = 90, .m = 4, .p = 3}, /* 264 MHz */ }, @@ -288,10 +280,86 @@ static void graphics_pll(void) scfg = (1<<28) | (1<<24); writel(scfg, cfg); - /* Init clock source for disp1: plld (actually plld_out0) */ - init_pll(&clk_rst->plld_base, &clk_rst->plld_misc, - osc_table[osc].plld, - (PLLUD_MISC_LOCK_ENABLE | PLLD_MISC_CLK_ENABLE)); + /* disp1 will be set when panel information (pixel clock) is + * retrieved (clock_display). + */ +} + +/* Init PLLD clock source. */ +int +clock_display(u32 frequency) +{ + /** + * plld (fo) = vco >> p, where 500MHz < vco < 1000MHz + * = (cf * n) >> p, where 1MHz < cf < 6MHz + * = ((ref / m) * n) >> p + * + * Assume p = 0, find (m, n). since m has only 5 bits, we can iterate + * all possible values. Note Tegra 124 supports 11 bits for n, but our + * pll_fields has only 10 bits for n. + * + * Note values undershoot or overshoot target output frequency may not + * work if the value is not in "safe" range in panel specification, so + * we want exact match. + */ + + struct pllpad_dividers plld = { 0 }; + u32 ref = clock_get_osc_khz() * 1000, m, n; + u32 cf, vco = frequency; + const u32 max_m = 1 << 5, max_n = 1 << 10, mhz = 1000 * 1000, + min_vco = 500 * mhz, max_vco = 1000 * mhz, + min_cf = 1 * mhz, max_cf = 6 * mhz; + + /* TODO(hungte) Replace this by clock_get_pll_input_khz */ + switch (clock_get_osc_bits()) { + case OSC_FREQ_OSC48: + ref /= 4; + break; + case OSC_FREQ_OSC38P4: + ref /= 2; + break; + } + + if (vco < min_vco || vco > max_vco) { + printk(BIOS_ERR, "%s: VCO (%d) out of range. Cannot support.\n", + __func__, vco); + return -1; + } + + for (m = 1; m < max_m; m++) { + cf = ref / m; + if (cf < min_cf) + break; + + n = vco / cf; + if (vco != cf * n || n >= max_n || cf > max_cf) + continue; + + plld.m = m; + plld.n = n; + + if (n < 50) + plld.cpcon = 2; + else if (n < 300) + plld.cpcon = 3; + else if (n < 600) + plld.cpcon = 8; + else + plld.cpcon = 12; + + printk(BIOS_DEBUG, "%s: PLLD=%u ref=%u, m/n/p/cpcon=" + "%u/%u/%u/%u\n", __func__, + (ref / plld.m * plld.n) >> plld.p, ref, + plld.m, plld.n, plld.p, plld.cpcon); + + init_pll(&clk_rst->plld_base, &clk_rst->plld_misc, plld, + (PLLUD_MISC_LOCK_ENABLE | PLLD_MISC_CLK_ENABLE)); + return 0; + } + + printk(BIOS_ERR, "%s: Failed to match output frequency %u.\n", + __func__, frequency); + return -1; } /* Initialize the UART and put it on CLK_M so we can use it during clock_init(). diff --git a/src/soc/nvidia/tegra124/display.c b/src/soc/nvidia/tegra124/display.c index 4a0f4501a2..c3117fdb01 100644 --- a/src/soc/nvidia/tegra124/display.c +++ b/src/soc/nvidia/tegra124/display.c @@ -98,8 +98,6 @@ static void print_mode(const struct soc_nvidia_tegra124_config *config) static int update_display_mode(struct display_controller *disp_ctrl, struct soc_nvidia_tegra124_config *config) { - unsigned long div = config->pll_div; - print_mode(config); WRITEL(0x1, &disp_ctrl->disp.disp_timing_opt); @@ -119,10 +117,25 @@ static int update_display_mode(struct display_controller *disp_ctrl, WRITEL(config->xres | (config->yres << 16), &disp_ctrl->disp.disp_active); + /** + * We want to use PLLD_out0, which is PLLD / 2: + * PixelClock = (PLLD / 2) / ShiftClockDiv / PixelClockDiv. + * + * Currently most panels work inside clock range 50MHz~100MHz, and PLLD + * has some requirements to have VCO in range 500MHz~1000MHz (see + * clock.c for more detail). To simplify calculation, we set + * PixelClockDiv to 1 and ShiftClockDiv to 5. In future these values + * may be calculated by clock_display, to allow wider frequency range. + * + * Note ShiftClockDiv is a 7.1 format value. + */ + const u32 shift_clock_div = 5; WRITEL((PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT) | - SHIFT_CLK_DIVIDER(div), - &disp_ctrl->disp.disp_clk_ctrl); - return 0; + ((shift_clock_div - 1) * 2) << SHIFT_CLK_DIVIDER_SHIFT, + &disp_ctrl->disp.disp_clk_ctrl); + printk(BIOS_DEBUG, "%s: PixelClock=%u, ShiftClockDiv=%u\n", + __func__, config->pixel_clock, shift_clock_div); + return clock_display(config->pixel_clock * shift_clock_div * 2); } static void update_window(struct display_controller *disp_ctrl, @@ -285,7 +298,10 @@ void display_startup(device_t dev) } /* Configure dc mode */ - update_display_mode(disp_ctrl, config); + if (update_display_mode(disp_ctrl, config)) { + printk(BIOS_ERR, "dc: failed to configure display mode.\n"); + return; + } /* Enable dp */ dp_enable(dc->out); diff --git a/src/soc/nvidia/tegra124/include/soc/clock.h b/src/soc/nvidia/tegra124/include/soc/clock.h index 10cf2d8679..aff6abe505 100644 --- a/src/soc/nvidia/tegra124/include/soc/clock.h +++ b/src/soc/nvidia/tegra124/include/soc/clock.h @@ -278,6 +278,7 @@ enum clock_source { /* Careful: Not true for all sources, always check TRM! */ #define TEGRA_PLLU_KHZ (960000) int clock_get_osc_khz(void); +int clock_display(u32 frequency); void clock_early_uart(void); void clock_external_output(int clk_id); void clock_sdram(u32 m, u32 n, u32 p, u32 setup, u32 ph45, u32 ph90,