[PATCH RFC 0/9] sunxi: video: Support LCD and HDMI output on H6/D1

This patch series is my first attempt at supporting an LCD panel on the T113. I can confirm this specific use case works, but I've added code for the H6/H616 too in the process as a lot of the logic is the same.
I've also included an untested patch for HDMI support at the end in case anyone wants to work on it. It compiles and looks like it should work, but I haven't ungated/reset the slow HDMI clock, or HDMI sub.
Signed-off-by: John Watts contact@jookia.org --- John Watts (9): sunxi: clock: support H6/D1 video clocks sunxi: ncat2: Specify DE2 and LCD0 base addresses sunxi: sun50i-h6: Specify DE2 and LCD0 base addresses sunxi: video: support LCD on H6/D1 sunxi: video: enable DE2 on H6/D1 sunxi: video: silence unused use_mipi_pll warning sunxi: video: dummy out HDMI on H6/D1 sunxi: Enable display engine on H6/D1 sunxi: video: support HDMI on H6/H616
arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h | 65 ++++++++++++++++++ arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h | 3 + arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h | 3 + arch/arm/mach-sunxi/Kconfig | 2 + arch/arm/mach-sunxi/clock_sun50i_h6.c | 81 +++++++++++++++++++++++ drivers/video/sunxi/lcdc.c | 23 +++++++ drivers/video/sunxi/sunxi_de2.c | 18 +++++ drivers/video/sunxi/sunxi_dw_hdmi.c | 77 +++++++++++++++++++-- drivers/video/sunxi/sunxi_lcd.c | 6 +- 9 files changed, 270 insertions(+), 8 deletions(-) --- base-commit: 1f1bbbdf85b67316770a6964a4a02b73a720a167 change-id: 20240420-d1_de2-9be81d56bd85
Best regards,

This code adds support for clocking VIDEO0 and VIDEO1, as well as registers used for the DE2.
This code deliberately uses a 12MHz step in clocking to align with the DE2 code's expectation of double 6MHz steps.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h | 56 ++++++++++++++++++ arch/arm/mach-sunxi/clock_sun50i_h6.c | 71 +++++++++++++++++++++++ 2 files changed, 127 insertions(+)
diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h index a84a57e5b4..dfe8d9315f 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h @@ -236,6 +236,28 @@ struct sunxi_ccm_reg { #define CCM_PLL1_CTRL_P(p) ((p) << 16) #define CCM_PLL1_CTRL_N(n) (((n) - 1) << 8)
+/* pll3 (video0) bit field */ +#define CCM_PLL3_CTRL_EN BIT(31) +#define CCM_PLL3_LDO_EN BIT(30) +#define CCM_PLL3_LOCK_EN BIT(29) +#define CCM_PLL3_LOCK BIT(28) +#define CCM_PLL3_OUT_EN BIT(27) +#define CCM_PLL3_INPUT_DIV2 BIT(1) +#define CCM_PLL3_CTRL_N(n) (((n) - 1) << 8) +#define CCM_PLL3_CTRL_N_SHIFT 8 +#define CCM_PLL3_CTRL_N_MASK (0xff << CCM_PLL3_CTRL_N_SHIFT) + +/* video1 bit field */ +#define CCM_VIDEO1_CTRL_EN BIT(31) +#define CCM_VIDEO1_LDO_EN BIT(30) +#define CCM_VIDEO1_LOCK_EN BIT(29) +#define CCM_VIDEO1_LOCK BIT(28) +#define CCM_VIDEO1_OUT_EN BIT(27) +#define CCM_VIDEO1_INPUT_DIV2 BIT(1) +#define CCM_VIDEO1_CTRL_N(n) (((n) - 1) << 8) +#define CCM_VIDEO1_CTRL_N_SHIFT 8 +#define CCM_VIDEO1_CTRL_N_MASK (0xff << CCM_VIDEO1_CTRL_N_SHIFT) + /* pll5 bit field */ #define CCM_PLL5_CTRL_EN BIT(31) #define CCM_PLL5_LOCK_EN BIT(29) @@ -258,6 +280,16 @@ struct sunxi_ccm_reg { #define CCM_PLL6_CTRL_DIV2_SHIFT 1 #define CCM_PLL6_CTRL_DIV2_MASK (0x1 << CCM_PLL6_CTRL_DIV2_SHIFT)
+/* pll10 bit field */ +#define CCM_PLL10_CTRL_EN BIT(31) +#define CCM_PLL10_LOCK_EN BIT(29) +#define CCM_PLL10_LOCK BIT(28) +#define CCM_PLL10_OUT_EN BIT(27) +#define CCM_PLL10_INPUT_DIV2 BIT(1) +#define CCM_PLL10_CTRL_N(n) (((n) - 1) << 8) +#define CCM_PLL10_CTRL_N_SHIFT 8 +#define CCM_PLL10_CTRL_N_MASK (0xff << CCM_PLL10_CTRL_N_SHIFT) + /* cpu_axi bit field*/ #define CCM_CPU_AXI_MUX_MASK (0x3 << 24) #define CCM_CPU_AXI_MUX_OSC24M (0x0 << 24) @@ -341,9 +373,33 @@ struct sunxi_ccm_reg { #define CCM_MMC_CTRL_OCLK_DLY(a) ((void) (a), 0) #define CCM_MMC_CTRL_SCLK_DLY(a) ((void) (a), 0)
+/* TCON0 clock bit field */ +#define CCM_TCON0_CTRL_ENABLE (0x1 << 31) +#define CCM_TCON0_CTRL_VIDEO0_4X (0x1 << 24) +#define CCM_TCON0_CTRL_M(m) ((((m) - 1) & 0xf) << 0) + +/* TCON1 clock bit field */ +#define CCM_TCON1_CTRL_ENABLE (0x1 << 31) +#define CCM_TCON1_CTRL_VIDEO0_4X (0x1 << 24) +#define CCM_TCON1_CTRL_M(m) ((((m) - 1) & 0xf) << 0) + +/* CCM bits common to all Display Engine 2.0 clock ctrl regs */ +#define CCM_DE2_CTRL_M(n) ((((n) - 1) & 0xf) << 0) +#define CCM_DE2_CTRL_PLL_MASK (3 << 24) +#define CCM_DE2_CTRL_PLL10_H6 (0 << 24) +#define CCM_DE2_CTRL_VIDEO1_4X_NCAT (2 << 24) +#define CCM_DE2_CTRL_GATE (0x1 << 31) + #ifndef __ASSEMBLY__ void clock_set_pll1(unsigned int hz); unsigned int clock_get_pll6(void); + +#ifdef CONFIG_SUNXI_DE2 +void clock_set_pll3(unsigned int hz); +void clock_set_video1(unsigned int hz); +void clock_set_pll10(unsigned int hz); +unsigned int clock_get_pll3(void); +#endif #endif
#endif /* _SUNXI_CLOCK_SUN50I_H6_H */ diff --git a/arch/arm/mach-sunxi/clock_sun50i_h6.c b/arch/arm/mach-sunxi/clock_sun50i_h6.c index dac3663e1b..11e303f801 100644 --- a/arch/arm/mach-sunxi/clock_sun50i_h6.c +++ b/arch/arm/mach-sunxi/clock_sun50i_h6.c @@ -160,3 +160,74 @@ int clock_twi_onoff(int port, int state)
return 0; } + +#ifdef CONFIG_SUNXI_DE2 + +void clock_set_pll3(unsigned int clk) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + + if (clk == 0) { + clrbits_le32(&ccm->pll3_cfg, CCM_PLL3_CTRL_EN); + return; + } + + /* PLL3 rate = 24000000 * n / 2 */ + writel(CCM_PLL3_CTRL_EN | CCM_PLL3_LOCK_EN | CCM_PLL3_OUT_EN | CCM_PLL3_LDO_EN | + CCM_PLL3_INPUT_DIV2 | CCM_PLL3_CTRL_N(clk / 12000000), + &ccm->pll3_cfg); + + while (!(readl(&ccm->pll3_cfg) & CCM_PLL3_LOCK)) + ; +} + +void clock_set_video1(unsigned int clk) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + + if (clk == 0) { + clrbits_le32(&ccm->pll_video1_cfg, CCM_VIDEO1_CTRL_EN); + return; + } + + /* VIDEO1 rate = 24000000 * n / 2 */ + writel(CCM_VIDEO1_CTRL_EN | CCM_VIDEO1_LOCK_EN | CCM_VIDEO1_OUT_EN | CCM_VIDEO1_LDO_EN | + CCM_VIDEO1_INPUT_DIV2 | CCM_VIDEO1_CTRL_N(clk / 12000000), + &ccm->pll_video1_cfg); + + while (!(readl(&ccm->pll_video1_cfg) & CCM_VIDEO1_LOCK)) + ; +} + +void clock_set_pll10(unsigned int clk) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + + if (clk == 0) { + clrbits_le32(&ccm->pll10_cfg, CCM_PLL10_CTRL_EN); + return; + } + + /* PLL10 rate = 24000000 * n / 2 */ + writel(CCM_PLL10_CTRL_EN | CCM_PLL10_LOCK_EN | CCM_PLL10_OUT_EN | + CCM_PLL10_INPUT_DIV2 | CCM_PLL10_CTRL_N(clk / 12000000), + &ccm->pll_video1_cfg); + + while (!(readl(&ccm->pll_video1_cfg) & CCM_PLL10_LOCK)) + ; +} + +unsigned int clock_get_pll3(void) +{ + struct sunxi_ccm_reg *const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + u32 rval = readl(&ccm->pll3_cfg); + int n = ((rval & CCM_PLL3_CTRL_N_MASK) >> CCM_PLL3_CTRL_N_SHIFT) + 1; + + return 12000 * n * 1000; +} + +#endif

These are used for operating the LCD on the D1/T113.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h b/arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h index 908a582ae0..4ae93b5095 100644 --- a/arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h +++ b/arch/arm/include/asm/arch-sunxi/cpu_sunxi_ncat2.h @@ -25,6 +25,9 @@ #define SUNXI_MMC1_BASE 0x04021000 #define SUNXI_MMC2_BASE 0x04022000
+#define SUNXI_DE2_BASE 0x05000000 +#define SUNXI_LCD0_BASE 0x05461000 + #define SUNXI_R_CPUCFG_BASE 0x07000400 #define SUNXI_PRCM_BASE 0x07010000

These are used for operating the LCD on the H6/H616.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h index 8a3f465545..2c9fe18289 100644 --- a/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h +++ b/arch/arm/include/asm/arch-sunxi/cpu_sun50i_h6.h @@ -31,6 +31,9 @@ #define SUNXI_DRAM_PHY0_BASE 0x04800000 #endif
+#define SUNXI_DE2_BASE 0x01000000 +#define SUNXI_LCD0_BASE 0x06515000 + #define SUNXI_TWI0_BASE 0x05002000 #define SUNXI_TWI1_BASE 0x05002400 #define SUNXI_TWI2_BASE 0x05002800

The H6/D1/R528 don't require any big changes, just some small tweaks to support new clock and reset logic.
Signed-off-by: John Watts contact@jookia.org --- drivers/video/sunxi/lcdc.c | 20 ++++++++++++++++++++ drivers/video/sunxi/sunxi_lcd.c | 6 ++++-- 2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/drivers/video/sunxi/lcdc.c b/drivers/video/sunxi/lcdc.c index 73033c3b85..ea21d602be 100644 --- a/drivers/video/sunxi/lcdc.c +++ b/drivers/video/sunxi/lcdc.c @@ -277,6 +277,12 @@ void lcdc_pll_set(struct sunxi_ccm_reg *ccm, int tcon, int dotclock, } }
+#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) + /* No need to clock doubling, just ask for a higher PLL clock */ + best_double = 0; + step *= 2; +#endif + #ifdef CONFIG_MACH_SUN6I /* * Use the MIPI pll if we've been unable to find any matching setting @@ -302,6 +308,19 @@ void lcdc_pll_set(struct sunxi_ccm_reg *ccm, int tcon, int dotclock, best_double + 1, step, best_n, best_m); }
+#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) + if (tcon == 0) { + writel(CCM_TCON0_CTRL_VIDEO0_4X | CCM_TCON0_CTRL_ENABLE, + &ccm->tcon_lcd0_clk_cfg); + setbits_le32(&ccm->tcon_lcd_gate_reset, BIT(RESET_SHIFT)); + setbits_le32(&ccm->tcon_lcd_gate_reset, BIT(GATE_SHIFT)); + } else { + writel(CCM_TCON1_CTRL_VIDEO0_4X | CCM_TCON1_CTRL_ENABLE, + &ccm->tcon_tv0_clk_cfg); + setbits_le32(&ccm->tcon_tv_gate_reset, BIT(RESET_SHIFT)); + setbits_le32(&ccm->tcon_tv_gate_reset, BIT(GATE_SHIFT)); + } +#else if (tcon == 0) { u32 pll;
@@ -329,6 +348,7 @@ void lcdc_pll_set(struct sunxi_ccm_reg *ccm, int tcon, int dotclock, setbits_le32(&ccm->lcd0_ch1_clk_cfg, CCM_LCD_CH1_CTRL_HALF_SCLK1); } +#endif #endif
*clk_div = best_m; diff --git a/drivers/video/sunxi/sunxi_lcd.c b/drivers/video/sunxi/sunxi_lcd.c index 7a01cc343c..3b0e63233f 100644 --- a/drivers/video/sunxi/sunxi_lcd.c +++ b/drivers/video/sunxi/sunxi_lcd.c @@ -26,7 +26,7 @@ struct sunxi_lcd_priv {
static void sunxi_lcdc_config_pinmux(void) { -#ifdef CONFIG_MACH_SUN50I +#if IS_ENABLED(CONFIG_MACH_SUN50I) || IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_MACH_SUN8I_R528) int pin;
for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(21); pin++) { @@ -47,10 +47,12 @@ static int sunxi_lcd_enable(struct udevice *dev, int bpp, struct udevice *backlight; int clk_div, clk_double, ret;
+#if !IS_ENABLED(CONFIG_SUN50I_GEN_H6) && !IS_ENABLED(CONFIG_MACH_SUN8I_R528) /* Reset off */ setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0); /* Clock on */ setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0); +#endif
lcdc_init(lcdc); sunxi_lcdc_config_pinmux(); @@ -146,7 +148,7 @@ U_BOOT_DRIVER(sunxi_lcd) = { .priv_auto = sizeof(struct sunxi_lcd_priv), };
-#ifdef CONFIG_MACH_SUN50I +#if IS_ENABLED(CONFIG_MACH_SUN50I) || IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_MACH_SUN8I_R528) U_BOOT_DRVINFO(sunxi_lcd) = { .name = "sunxi_lcd" };

This requires just a little change to clocking and reset logic.
Signed-off-by: John Watts contact@jookia.org --- drivers/video/sunxi/sunxi_de2.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/drivers/video/sunxi/sunxi_de2.c b/drivers/video/sunxi/sunxi_de2.c index e02d359cd2..9b6c41b212 100644 --- a/drivers/video/sunxi/sunxi_de2.c +++ b/drivers/video/sunxi/sunxi_de2.c @@ -45,6 +45,23 @@ static void sunxi_de2_composer_init(void) writel(reg_value, SUNXI_SRAMC_BASE + 0x04); #endif
+#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) + if (IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) { + /* Set DE parent to video1 */ + clock_set_video1(432000000); + clrsetbits_le32(&ccm->de_clk_cfg, CCM_DE2_CTRL_PLL_MASK, + CCM_DE2_CTRL_VIDEO1_4X_NCAT); + } else { + /* Set DE parent to pll10 */ + clock_set_pll10(432000000); + clrsetbits_le32(&ccm->de_clk_cfg, CCM_DE2_CTRL_PLL_MASK, + CCM_DE2_CTRL_PLL10_H6); + } + + /* Ungate the DE */ + setbits_le32(&ccm->de_gate_reset, BIT(RESET_SHIFT)); + setbits_le32(&ccm->de_gate_reset, BIT(GATE_SHIFT)); +#else clock_set_pll10(432000000);
/* Set DE parent to pll10 */ @@ -54,6 +71,7 @@ static void sunxi_de2_composer_init(void) /* Set ahb gating to pass */ setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DE); setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE); +#endif
/* Clock on */ setbits_le32(&ccm->de_clk_cfg, CCM_DE2_CTRL_GATE);

This variable is only used sometimes, so gate it behind an #ifdef.
Signed-off-by: John Watts contact@jookia.org --- drivers/video/sunxi/lcdc.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/video/sunxi/lcdc.c b/drivers/video/sunxi/lcdc.c index ea21d602be..1474f6cb2e 100644 --- a/drivers/video/sunxi/lcdc.c +++ b/drivers/video/sunxi/lcdc.c @@ -216,7 +216,10 @@ void lcdc_pll_set(struct sunxi_ccm_reg *ccm, int tcon, int dotclock, int value, n, m, min_m, max_m, diff, step; int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF; int best_double = 0; + +#ifdef CONFIG_MACH_SUN6I bool use_mipi_pll = false; +#endif
#ifdef CONFIG_SUNXI_DE2 step = 6000;

I don't have any hardware to implement HDMI support on, but it's still worth making the code compile for future work.
Adding HDMI support shouldn't be too difficult, though beware: The H6 and H616 both have a different set of HDMI clock parents. Future code will most likely need to pick VIDEO0 or VIDEO1 to run the TCON and HDMI encoder based on the chip.
Signed-off-by: John Watts contact@jookia.org --- drivers/video/sunxi/sunxi_dw_hdmi.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/drivers/video/sunxi/sunxi_dw_hdmi.c b/drivers/video/sunxi/sunxi_dw_hdmi.c index 0324a050d0..34a6b8bab7 100644 --- a/drivers/video/sunxi/sunxi_dw_hdmi.c +++ b/drivers/video/sunxi/sunxi_dw_hdmi.c @@ -230,7 +230,12 @@ static void sunxi_dw_hdmi_pll_set(uint clk_khz, int *phy_div)
*phy_div = best_div;
+#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) + panic("setting HDMI pll not implemented"); +#else clock_set_pll3_factors(best_m, best_n); +#endif + debug("dotclock: %dkHz = %dkHz: (24MHz * %d) / %d / %d\n", clk_khz, (clock_get_pll3() / 1000) / best_div, best_n, best_m, best_div); @@ -244,6 +249,9 @@ static void sunxi_dw_hdmi_lcdc_init(int mux, const struct display_timing *edid, int div = DIV_ROUND_UP(clock_get_pll3(), edid->pixelclock.typ); struct sunxi_lcdc_reg *lcdc;
+#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) + panic("initializing HDMI lcdc not implemented"); +#else if (mux == 0) { lcdc = (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
@@ -265,6 +273,7 @@ static void sunxi_dw_hdmi_lcdc_init(int mux, const struct display_timing *edid, writel(CCM_LCD1_CTRL_GATE | CCM_LCD1_CTRL_M(div), &ccm->lcd1_clk_cfg); } +#endif
lcdc_init(lcdc); lcdc_tcon1_mode_set(lcdc, edid, false, false); @@ -338,6 +347,9 @@ static int sunxi_dw_hdmi_probe(struct udevice *dev) if (priv->hvcc) regulator_set_enable(priv->hvcc, true);
+#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) + panic("initializing HDMI not implemented"); +#else /* Set pll3 to 297 MHz */ clock_set_pll3(297000000);
@@ -347,6 +359,7 @@ static int sunxi_dw_hdmi_probe(struct udevice *dev)
/* This reset is referenced from the PHY devicetree node. */ setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI2); +#endif
ret = reset_deassert_bulk(&priv->resets); if (ret)

Now that all the code is present and can compile, enable the DE2 so people can use it.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/mach-sunxi/Kconfig | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig index ddf9414b08..9d76e429ef 100644 --- a/arch/arm/mach-sunxi/Kconfig +++ b/arch/arm/mach-sunxi/Kconfig @@ -202,6 +202,7 @@ config SUN50I_GEN_H6 select SPL_LOAD_FIT if SPL select MMC_SUNXI_HAS_NEW_MODE select SUPPORT_SPL + select SUNXI_DE2 ---help--- Select this for sunxi SoCs which have H6 like peripherals, clocks and memory map. @@ -210,6 +211,7 @@ config SUNXI_GEN_NCAT2 bool select MMC_SUNXI_HAS_NEW_MODE select SUPPORT_SPL + select SUNXI_DE2 ---help--- Select this for sunxi SoCs which have D1 like peripherals, clocks and memory map.

The H6 and H616 support outputting HDMI through the Display Engine. Set up the clocks and resets appropriately for the HDMI controller.
This turns out to be a little tricky as the HDMI clock requires a different parent on the H6 compared to the H616. So we have to end up choosing VIDEO1 on the H616 and VIDEO0 elsewhere.
Signed-off-by: John Watts contact@jookia.org --- arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h | 9 +++ arch/arm/mach-sunxi/clock_sun50i_h6.c | 10 ++++ drivers/video/sunxi/sunxi_dw_hdmi.c | 70 ++++++++++++++++++++--- 3 files changed, 80 insertions(+), 9 deletions(-)
diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h index dfe8d9315f..35bd3dd2d8 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun50i_h6.h @@ -376,13 +376,21 @@ struct sunxi_ccm_reg { /* TCON0 clock bit field */ #define CCM_TCON0_CTRL_ENABLE (0x1 << 31) #define CCM_TCON0_CTRL_VIDEO0_4X (0x1 << 24) +#define CCM_TCON0_CTRL_VIDEO1_4X (0x3 << 24) #define CCM_TCON0_CTRL_M(m) ((((m) - 1) & 0xf) << 0)
/* TCON1 clock bit field */ #define CCM_TCON1_CTRL_ENABLE (0x1 << 31) #define CCM_TCON1_CTRL_VIDEO0_4X (0x1 << 24) +#define CCM_TCON1_CTRL_VIDEO1_4X (0x3 << 24) #define CCM_TCON1_CTRL_M(m) ((((m) - 1) & 0xf) << 0)
+/* HDMI clock bit field */ +#define CCM_HDMI_CTRL_ENABLE (0x1 << 31) +#define CCM_HDMI_CTRL_VIDEO1_4X_H6 (0x2 << 24) +#define CCM_HDMI_CTRL_VIDEO0_4X_H616 (0x1 << 24) +#define CCM_HDMI_CTRL_M(m) ((((m) - 1) & 0xf) << 0) + /* CCM bits common to all Display Engine 2.0 clock ctrl regs */ #define CCM_DE2_CTRL_M(n) ((((n) - 1) & 0xf) << 0) #define CCM_DE2_CTRL_PLL_MASK (3 << 24) @@ -399,6 +407,7 @@ void clock_set_pll3(unsigned int hz); void clock_set_video1(unsigned int hz); void clock_set_pll10(unsigned int hz); unsigned int clock_get_pll3(void); +unsigned int clock_get_video1(void); #endif #endif
diff --git a/arch/arm/mach-sunxi/clock_sun50i_h6.c b/arch/arm/mach-sunxi/clock_sun50i_h6.c index 11e303f801..23b7c13e28 100644 --- a/arch/arm/mach-sunxi/clock_sun50i_h6.c +++ b/arch/arm/mach-sunxi/clock_sun50i_h6.c @@ -230,4 +230,14 @@ unsigned int clock_get_pll3(void) return 12000 * n * 1000; }
+unsigned int clock_get_video1(void) +{ + struct sunxi_ccm_reg *const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + u32 rval = readl(&ccm->pll_video1_cfg); + int n = ((rval & CCM_VIDEO1_CTRL_N_MASK) >> CCM_VIDEO1_CTRL_N_SHIFT) + 1; + + return 12000 * n * 1000; +} + #endif diff --git a/drivers/video/sunxi/sunxi_dw_hdmi.c b/drivers/video/sunxi/sunxi_dw_hdmi.c index 34a6b8bab7..475d61a888 100644 --- a/drivers/video/sunxi/sunxi_dw_hdmi.c +++ b/drivers/video/sunxi/sunxi_dw_hdmi.c @@ -198,6 +198,12 @@ static void sunxi_dw_hdmi_pll_set(uint clk_khz, int *phy_div) { int value, n, m, div, diff; int best_n = 0, best_m = 0, best_div = 0, best_diff = 0x0FFFFFFF; + int step = 24000, max_m = 16, pll_value = 0; + + if (IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) { + step = 12000; + max_m = 1; + }
/* * Find the lowest divider resulting in a matching clock. If there @@ -212,11 +218,11 @@ static void sunxi_dw_hdmi_pll_set(uint clk_khz, int *phy_div) if (target > 912000) continue;
- for (m = 1; m <= 16; m++) { - n = (m * target) / 24000; + for (m = 1; m <= max_m; m++) { + n = (m * target) / step;
if (n >= 1 && n <= 128) { - value = (24000 * n) / m / div; + value = (step * n) / m / div; diff = clk_khz - value; if (diff < best_diff) { best_diff = diff; @@ -231,13 +237,20 @@ static void sunxi_dw_hdmi_pll_set(uint clk_khz, int *phy_div) *phy_div = best_div;
#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) - panic("setting HDMI pll not implemented"); + if (IS_ENABLED(CONFIG_MACH_SUN50I_H6)) { + clock_set_video1(step * best_n); + pll_value = clock_get_video1(); + } else { + clock_set_pll3(step * best_n); + pll_value = clock_get_pll3(); + } #else clock_set_pll3_factors(best_m, best_n); + pll_value = clock_get_pll3(); #endif
debug("dotclock: %dkHz = %dkHz: (24MHz * %d) / %d / %d\n", - clk_khz, (clock_get_pll3() / 1000) / best_div, + clk_khz, (pll_value / 1000) / best_div, best_n, best_m, best_div); }
@@ -246,12 +259,34 @@ static void sunxi_dw_hdmi_lcdc_init(int mux, const struct display_timing *edid, { struct sunxi_ccm_reg * const ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; - int div = DIV_ROUND_UP(clock_get_pll3(), edid->pixelclock.typ); + int div, pll_value; struct sunxi_lcdc_reg *lcdc;
#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) - panic("initializing HDMI lcdc not implemented"); + int tcon1_src; + + if (IS_ENABLED(CONFIG_MACH_SUN50I_H6)) { + tcon1_src = CCM_TCON1_CTRL_VIDEO1_4X; + pll_value = clock_get_video1(); + } else { + tcon1_src = CCM_TCON1_CTRL_VIDEO0_4X; + pll_value = clock_get_pll3(); + } + + div = DIV_ROUND_UP(pll_value, edid->pixelclock.typ); + + if (mux == 0) { + writel(tcon1_src | CCM_TCON1_CTRL_ENABLE | CCM_TCON1_CTRL_M(div), + &ccm->tcon_tv0_clk_cfg); + setbits_le32(&ccm->tcon_tv_gate_reset, BIT(RESET_SHIFT)); + setbits_le32(&ccm->tcon_tv_gate_reset, BIT(GATE_SHIFT)); + } else { + /* TODO: H616 supports a second TV encoder */ + panic("using HDMI lcdc mux 1 is not implemented"); + } #else + div = DIV_ROUND_UP(pll_value, edid->pixelclock.typ); + if (mux == 0) { lcdc = (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
@@ -300,7 +335,12 @@ static int sunxi_dw_hdmi_read_edid(struct udevice *dev, u8 *buf, int buf_size) static bool sunxi_dw_hdmi_mode_valid(struct udevice *dev, const struct display_timing *timing) { - return timing->pixelclock.typ <= 297000000; + int max_clock = 297000000; + + if(IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) + max_clock = 594000; + + return timing->pixelclock.typ <= max_clock; }
static int sunxi_dw_hdmi_enable(struct udevice *dev, int panel_bpp, @@ -348,7 +388,19 @@ static int sunxi_dw_hdmi_probe(struct udevice *dev) regulator_set_enable(priv->hvcc, true);
#if IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2) - panic("initializing HDMI not implemented"); + int hdmi_src = CCM_HDMI_CTRL_VIDEO0_4X_H616; + + /* Set HDMI PLL to 297 MHz */ + if (IS_ENABLED(CONFIG_MACH_SUN50I_H6)) { + hdmi_src = CCM_HDMI_CTRL_VIDEO1_4X_H6; + clock_set_video1(297000000); + } else { + clock_set_pll3(297000000); + } + + writel(hdmi_src | CCM_HDMI_CTRL_ENABLE, &ccm->hdmi_clk_cfg); + setbits_le32(&ccm->hdmi_gate_reset, BIT(RESET_SHIFT)); + setbits_le32(&ccm->hdmi_gate_reset, BIT(GATE_SHIFT)); #else /* Set pll3 to 297 MHz */ clock_set_pll3(297000000);
participants (1)
-
John Watts