[U-Boot] [PATCH] board: ge: bx50v3: Update display setup

Stefano Babic
2016-04-06T15:18:52Z

Hi Akshay,

On 15/03/2016 19:10, Akshay Bhat wrote:
> Implements the below changes:
> - Disable LVDS1 on B450v3/B650v3 boards since the final boards no longer
> have connectors for the same. Only LVDS0 hardware connectors are present.
> - Implement imx6 EB821 or ERR009219 errata for LVDS clock switch.
> This patch was ported from Freescale 3.10.17_1.0.0_ga kernel to u-boot.
> - Split the display setup into 2 different functions. One for B850v3 that
> does a setup of LVDS and HDMI with clock source for LVDS/IPU_DI set to
> video PLL. The other for B450v3/B650v3 that does a setup of LVDS only with
> clock source for LVDS/IPU_DI set to USB PLL. This helps us generate
> accurate pixel clock required for display connected to LVDS.

I propose you split your patch exactly as you describe in the commit
message into 3 patches.

Generally, a patch/patchset should address a single issue. This lets to
better understand the workarounf with MMDC handshake, and let use it on
other boards too.

>
> Signed-off-by: Akshay Bhat <akshay.bhat@timesys.com>
> Cc: Stefano Babic <sbabic@denx.de>
> ---
> board/ge/bx50v3/bx50v3.c | 240 ++++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 198 insertions(+), 42 deletions(-)
>
> diff --git a/board/ge/bx50v3/bx50v3.c b/board/ge/bx50v3/bx50v3.c
> index 70c298d..cf2cd1a 100644
> --- a/board/ge/bx50v3/bx50v3.c
> +++ b/board/ge/bx50v3/bx50v3.c
> @@ -390,55 +390,208 @@ struct display_info_t const displays[] = {{
> } } };
> size_t display_count = ARRAY_SIZE(displays);
>
> -static void setup_display(void)
> +static void enable_videopll(void)
> +{
> + struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
> + s32 timeout = 100000;
> +
> + setbits_le32(&ccm->analog_pll_video, BM_ANADIG_PLL_VIDEO_POWERDOWN);
> +
> + /* set video pll to 910MHz (24MHz * (37+11/12))
> + * video pll post div to 910/4 = 227.5MHz
> + */
> + clrsetbits_le32(&ccm->analog_pll_video,
> + BM_ANADIG_PLL_VIDEO_DIV_SELECT |
> + BM_ANADIG_PLL_VIDEO_POST_DIV_SELECT,
> + BF_ANADIG_PLL_VIDEO_DIV_SELECT(37) |
> + BF_ANADIG_PLL_VIDEO_POST_DIV_SELECT(0));
> +
> + writel(BF_ANADIG_PLL_VIDEO_NUM_A(11), &ccm->analog_pll_video_num);
> + writel(BF_ANADIG_PLL_VIDEO_DENOM_B(12), &ccm->analog_pll_video_denom);
> +
> + clrbits_le32(&ccm->analog_pll_video, BM_ANADIG_PLL_VIDEO_POWERDOWN);
> +
> + while (timeout--)
> + if (readl(&ccm->analog_pll_video) & BM_ANADIG_PLL_VIDEO_LOCK)
> + break;
> + if (timeout < 0)
> + printf("Warning: video pll lock timeout!\n");
> +
> + clrsetbits_le32(&ccm->analog_pll_video,
> + BM_ANADIG_PLL_VIDEO_BYPASS,
> + BM_ANADIG_PLL_VIDEO_ENABLE);
> +}
> +
> +static void set_ldb_clock_source(u8 source)
> {
> struct mxc_ccm_reg *mxc_ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
> - struct iomuxc *iomux = (struct iomuxc *)IOMUXC_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
> + */
> + /* 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);
>
> - enable_ipu_clock();
> - imx_setup_hdmi();
> -
> - reg = readl(&mxc_ccm->CCGR3);
> - reg |= MXC_CCM_CCGR3_LDB_DI0_MASK;
> - writel(reg, &mxc_ccm->CCGR3);
> + /* 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_DI0_CLK_SEL_MASK |
> - MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_MASK);
> - reg |= (3 << MXC_CCM_CS2CDR_LDB_DI0_CLK_SEL_OFFSET) |
> - (3 << MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_OFFSET);
> + reg &= ~(MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_MASK
> + | MXC_CCM_CS2CDR_LDB_DI0_CLK_SEL_MASK);
> + reg |= ((source << MXC_CCM_CS2CDR_LDB_DI1_CLK_SEL_OFFSET)
> + | (source << MXC_CCM_CS2CDR_LDB_DI0_CLK_SEL_OFFSET));
> writel(reg, &mxc_ccm->cs2cdr);
>
> - reg = readl(&mxc_ccm->cscmr2);
> - reg |= (MXC_CCM_CSCMR2_LDB_DI0_IPU_DIV);
> - writel(reg, &mxc_ccm->cscmr2);
> -
> - reg = readl(&mxc_ccm->chsccdr);
> - reg |= (CHSCCDR_CLK_SEL_LDB_DI0
> - << MXC_CCM_CHSCCDR_IPU1_DI0_CLK_SEL_OFFSET);
> - writel(reg, &mxc_ccm->chsccdr);
> -
> - reg = IOMUXC_GPR2_BGREF_RRMODE_EXTERNAL_RES
> - | IOMUXC_GPR2_DI1_VS_POLARITY_ACTIVE_HIGH
> - | IOMUXC_GPR2_DI0_VS_POLARITY_ACTIVE_LOW
> - | IOMUXC_GPR2_BIT_MAPPING_CH1_SPWG
> - | IOMUXC_GPR2_DATA_WIDTH_CH1_24BIT
> - | IOMUXC_GPR2_BIT_MAPPING_CH0_SPWG
> - | IOMUXC_GPR2_DATA_WIDTH_CH0_24BIT
> - | IOMUXC_GPR2_LVDS_CH1_MODE_ENABLED_DI0
> - | IOMUXC_GPR2_LVDS_CH0_MODE_ENABLED_DI0;
> - writel(reg, &iomux->gpr[2]);
> -
> - reg = readl(&iomux->gpr[3]);
> - reg = (reg & ~(IOMUXC_GPR3_LVDS0_MUX_CTL_MASK |
> - IOMUXC_GPR3_LVDS1_MUX_CTL_MASK |
> - IOMUXC_GPR3_HDMI_MUX_CTL_MASK))
> - | (IOMUXC_GPR3_MUX_SRC_IPU1_DI0
> - << IOMUXC_GPR3_LVDS0_MUX_CTL_OFFSET)
> - | (IOMUXC_GPR3_MUX_SRC_IPU1_DI0
> - << IOMUXC_GPR3_LVDS1_MUX_CTL_OFFSET);
> - writel(reg, &iomux->gpr[3]);
> + /* 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);
> +}

I frankly ask you if you think that this function can be factorized and
moved from board code to common code. What do you think ? Is there
something that let the implementation bound to the board (I have not
seen it) ?

> +
> +static void setup_display_b850v3(void)
> +{
> + struct mxc_ccm_reg *mxc_ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
> + struct iomuxc *iomux = (struct iomuxc *)IOMUXC_BASE_ADDR;
> +
> + /* Set LDB clock to Video PLL */
> + set_ldb_clock_source(0);
> + enable_videopll();
> +
> + /* IPU1 D0 clock is 227.5 / 3.5 = 65MHz */
> + clrbits_le32(&mxc_ccm->cscmr2, MXC_CCM_CSCMR2_LDB_DI0_IPU_DIV);
> +
> + imx_setup_hdmi();
> +
> + /* Set LDB_DI0 as clock source for IPU_DI0 */
> + clrsetbits_le32(&mxc_ccm->chsccdr,
> + MXC_CCM_CHSCCDR_IPU1_DI0_CLK_SEL_MASK,
> + (CHSCCDR_CLK_SEL_LDB_DI0 <<
> + MXC_CCM_CHSCCDR_IPU1_DI0_CLK_SEL_OFFSET));
> +
> + /* Turn on IPU LDB DI0 clocks */
> + setbits_le32(&mxc_ccm->CCGR3, MXC_CCM_CCGR3_LDB_DI0_MASK);
> +
> + enable_ipu_clock();
> +
> + writel(IOMUXC_GPR2_BGREF_RRMODE_EXTERNAL_RES |
> + IOMUXC_GPR2_DI1_VS_POLARITY_ACTIVE_LOW |
> + IOMUXC_GPR2_DI0_VS_POLARITY_ACTIVE_LOW |
> + IOMUXC_GPR2_BIT_MAPPING_CH1_SPWG |
> + IOMUXC_GPR2_DATA_WIDTH_CH1_24BIT |
> + IOMUXC_GPR2_BIT_MAPPING_CH0_SPWG |
> + IOMUXC_GPR2_DATA_WIDTH_CH0_24BIT |
> + IOMUXC_GPR2_SPLIT_MODE_EN_MASK |
> + IOMUXC_GPR2_LVDS_CH0_MODE_ENABLED_DI0 |
> + IOMUXC_GPR2_LVDS_CH1_MODE_ENABLED_DI0,
> + &iomux->gpr[2]);
> +
> + clrbits_le32(&iomux->gpr[3],
> + IOMUXC_GPR3_LVDS0_MUX_CTL_MASK |
> + IOMUXC_GPR3_LVDS1_MUX_CTL_MASK |
> + IOMUXC_GPR3_HDMI_MUX_CTL_MASK);
> +}
> +
> +static void setup_display_bx50v3(void)
> +{
> + struct mxc_ccm_reg *mxc_ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
> + struct iomuxc *iomux = (struct iomuxc *)IOMUXC_BASE_ADDR;
> +
> + /* Set LDB clock to USB PLL */
> + set_ldb_clock_source(4);
> +
> + /* IPU1 DI0 clock is 480/7 = 68.5 MHz */
> + setbits_le32(&mxc_ccm->cscmr2, MXC_CCM_CSCMR2_LDB_DI0_IPU_DIV);
> +
> + /* Set LDB_DI0 as clock source for IPU_DI0 */
> + clrsetbits_le32(&mxc_ccm->chsccdr,
> + MXC_CCM_CHSCCDR_IPU1_DI0_CLK_SEL_MASK,
> + (CHSCCDR_CLK_SEL_LDB_DI0 <<
> + MXC_CCM_CHSCCDR_IPU1_DI0_CLK_SEL_OFFSET));
> +
> + /* Turn on IPU LDB DI0 clocks */
> + setbits_le32(&mxc_ccm->CCGR3, MXC_CCM_CCGR3_LDB_DI0_MASK);
> +
> + enable_ipu_clock();
> +
> + writel(IOMUXC_GPR2_BGREF_RRMODE_EXTERNAL_RES |
> + IOMUXC_GPR2_DI0_VS_POLARITY_ACTIVE_LOW |
> + IOMUXC_GPR2_BIT_MAPPING_CH0_SPWG |
> + IOMUXC_GPR2_DATA_WIDTH_CH0_24BIT |
> + IOMUXC_GPR2_LVDS_CH0_MODE_ENABLED_DI0,
> + &iomux->gpr[2]);
> +
> + clrsetbits_le32(&iomux->gpr[3],
> + IOMUXC_GPR3_LVDS0_MUX_CTL_MASK,
> + (IOMUXC_GPR3_MUX_SRC_IPU1_DI0 <<
> + IOMUXC_GPR3_LVDS0_MUX_CTL_OFFSET));
>
> /* backlights off until needed */
> imx_iomux_v3_setup_multiple_pads(backlight_pads,
> @@ -487,7 +640,10 @@ int board_init(void)
> gpio_direction_output(SUS_S3_OUT, 1);
> gpio_direction_output(WIFI_EN, 1);
> #if defined(CONFIG_VIDEO_IPUV3)
> - setup_display();
> + if (IS_ENABLED(CONFIG_TARGET_GE_B850V3))
> + setup_display_b850v3();
> + else
> + setup_display_bx50v3();
> #endif
> /* address of boot parameters */
> gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;
>

Best regards,
Stefano Babic

--
=====================================================================
DENX Software Engineering GmbH, Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: +49-8142-66989-53 Fax: +49-8142-66989-80 Email: sbabic at denx.de
=====================================================================