* [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568
@ 2026-07-19 1:58 Daniel Golle
2026-07-19 1:58 ` [PATCH 1/9] video: rockchip: Add VOP2 support Daniel Golle
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 1:58 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
U-Boot has no video output support for the RK3568/RK3566 family: the
VOP2 display controller found in these SoCs (and newer Rockchip parts)
is unsupported, so boards cannot show a boot menu, the UEFI console or
any other firmware user interface on a locally attached display, while
the vendor firmware and the Linux kernel have long been able to drive
it.
This series brings up HDMI output on RK3568 in U-Boot proper so that a
plain monitor plus USB keyboard is enough to interact with the
firmware, using the NanoPi R5C as the reference board.
The VOP2 driver and the RK3568 HDMI glue were written by Dang Huynh,
modelled on the existing VOP support and the Linux driver. On top of
that, bring-up on real hardware showed that U-Boot must not rely on the
state left behind by earlier boot stages: the probe path is fixed to
map the right register base, and the HDMI controller clocks, the PHY
reference clock and the PHY analog supplies are now enabled explicitly
by the driver instead of assuming the BootROM, TPL or a previous
firmware left them on. The final patch enables video output on the
NanoPi R5C.
Dang Huynh (4):
video: rockchip: Add VOP2 support
arm: dts: rockchip: rk356x: Prerelocate VOP in U-Boot proper
video: rockchip: Add HDMI support for RK3568
clk: rockchip: rk3568: Use assigned VPLL clock when possible
Daniel Golle (5):
video: rockchip: vop2: fix NULL register base in rk3568 probe
video: rockchip: rk3568: ungate the HDMI controller clocks
video: rockchip: rk3568: bring up the HDMI PHY reference clock
video: rockchip: rk3568: power the HDMI PHY analog supplies
configs: nanopi-r5c: enable HDMI video output
arch/arm/dts/rk356x-u-boot.dtsi | 4 +
.../include/asm/arch-rockchip/vop_rk3568.h | 280 ++++++++++
configs/nanopi-r5c-rk3568_defconfig | 4 +
drivers/clk/rockchip/clk_rk3568.c | 18 +-
drivers/video/rockchip/Makefile | 4 +-
drivers/video/rockchip/rk3568_hdmi.c | 125 +++++
drivers/video/rockchip/rk3568_vop.c | 270 +++++++++
drivers/video/rockchip/rk_vop2.c | 520 ++++++++++++++++++
drivers/video/rockchip/rk_vop2.h | 76 +++
9 files changed, 1299 insertions(+), 2 deletions(-)
create mode 100644 arch/arm/include/asm/arch-rockchip/vop_rk3568.h
create mode 100644 drivers/video/rockchip/rk3568_hdmi.c
create mode 100644 drivers/video/rockchip/rk3568_vop.c
create mode 100644 drivers/video/rockchip/rk_vop2.c
create mode 100644 drivers/video/rockchip/rk_vop2.h
base-commit: ece349ade2973e220f524ce59e59711cc919263f
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/9] video: rockchip: Add VOP2 support
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
@ 2026-07-19 1:58 ` Daniel Golle
2026-07-19 1:58 ` [PATCH 2/9] arm: dts: rockchip: rk356x: Prerelocate VOP in U-Boot proper Daniel Golle
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 1:58 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
From: Dang Huynh <dang.huynh@mainlining.org>
VOP2 (Video Output Processor v2) is a display controller on Rockchip
SoCs. It can be found on RK3566/8 and RK3588.
This commit currently only supports RK3566/8.
Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
.../include/asm/arch-rockchip/vop_rk3568.h | 280 ++++++++++
drivers/video/rockchip/Makefile | 3 +-
drivers/video/rockchip/rk3568_vop.c | 260 +++++++++
drivers/video/rockchip/rk_vop2.c | 520 ++++++++++++++++++
drivers/video/rockchip/rk_vop2.h | 76 +++
5 files changed, 1138 insertions(+), 1 deletion(-)
create mode 100644 arch/arm/include/asm/arch-rockchip/vop_rk3568.h
create mode 100644 drivers/video/rockchip/rk3568_vop.c
create mode 100644 drivers/video/rockchip/rk_vop2.c
create mode 100644 drivers/video/rockchip/rk_vop2.h
diff --git a/arch/arm/include/asm/arch-rockchip/vop_rk3568.h b/arch/arm/include/asm/arch-rockchip/vop_rk3568.h
new file mode 100644
index 00000000000..6388369ce8c
--- /dev/null
+++ b/arch/arm/include/asm/arch-rockchip/vop_rk3568.h
@@ -0,0 +1,280 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2024 - 2025 Dang Huynh <dang.huynh@mainlining.org>
+ *
+ * Based on vop_rk3288.h:
+ * Copyright (c) 2015 Google, Inc
+ * Copyright 2014 Rockchip Inc.
+ */
+
+#ifndef _ASM_ARCH_VOP_RK3568_H
+#define _ASM_ARCH_VOP_RK3568_H
+
+struct rk3568_vop_sysctrl {
+ u32 reg_cfg_done;
+ u32 version_info;
+ u32 autogating_ctrl;
+ u32 win_reg_cfg_done;
+ u32 axi_ctrl0;
+ u32 axi_hurry_ctrl0;
+ u32 axi_hurry_ctrl1;
+ u32 axi_outstanding_ctrl0;
+ u32 axi_outstanding_ctrl1;
+ u32 axi_lut_ctrl;
+ u32 dsp_en;
+ u32 dsp_ctrl;
+ u32 dsp_pol;
+ u32 pwr_ctrl;
+ u32 var_freq_ctrl;
+ u32 mmu_raddr_range;
+ u32 wb_ctrl0;
+ u32 wb_xspd;
+ u32 wb_yrgb_mst;
+ u32 wb_cbr_mst;
+ u32 otp_win;
+ u32 otp_mirr_ctrl;
+ u32 lut_port_sel;
+ u32 pwr_stable_ctrl;
+ u32 status0;
+ u32 status1;
+ u32 status2;
+ u32 status3;
+ u32 line_flag0;
+ u32 line_flag1;
+ u32 line_flag2;
+ u32 line_flag3;
+ u32 sys0_intr_en;
+ u32 sys0_intr_clr;
+ u32 sys0_intr_status;
+ u32 sys0_intr_status_raw;
+ u32 sys1_intr_en;
+ u32 sys1_intr_clr;
+ u32 sys1_intr_status;
+ u32 sys1_intr_status_raw;
+ u32 port0_intr_en;
+ u32 port0_intr_clr;
+ u32 port0_intr_status;
+ u32 port0_intr_status_raw;
+ u32 port1_intr_en;
+ u32 port1_intr_clr;
+ u32 port1_intr_status;
+ u32 port1_intr_status_raw;
+ u32 port2_intr_en;
+ u32 port2_intr_clr;
+ u32 port2_intr_status;
+ u32 port2_intr_status_raw;
+ u32 port3_intr_en;
+ u32 port3_intr_clr;
+ u32 port3_intr_status;
+ u32 port3_intr_status_raw;
+};
+
+check_member(rk3568_vop_sysctrl, port3_intr_status_raw, 0x00DC);
+
+struct rk3568_vop_overlay {
+ u32 overlay_ctrl;
+ u32 layer_sel;
+ u32 port_sel;
+};
+
+check_member(rk3568_vop_overlay, port_sel, 0x0008);
+
+struct rk3568_vop_post {
+ u32 dsp_ctrl;
+ u32 mipi_ctrl;
+ u32 color_ctrl;
+ u32 reserved2;
+ u32 lut_reserved[4];
+ u32 reserved[3];
+ u32 dsp_bg;
+ u32 prescan_htimings;
+ u32 dsp_hact_info;
+ u32 dsp_vact_info;
+ u32 scl_factor_yrgb;
+ u32 scl_ctrl;
+ u32 dsp_vact_info_f1;
+ u32 dsp_htotal_hs_end;
+ u32 dsp_hact_st_end;
+ u32 dsp_vtotal_vs_end;
+ u32 dsp_vact_st_end;
+ u32 dsp_vs_st_end_f1;
+ u32 dsp_vact_st_end_f1;
+};
+
+check_member(rk3568_vop_post, dsp_vact_st_end_f1, 0x005C);
+
+struct rk3568_vop_cluster {
+ u32 win0_ctrl0;
+ u32 win0_ctrl1;
+ u32 win0_ctrl2;
+ u32 reserved0;
+ u32 win0_mst_yrgb;
+ u32 win0_mst_cbcr;
+ u32 win0_vir;
+ u32 reserved1;
+ u32 win0_act_info;
+ u32 win0_dsp_info;
+ u32 win0_dsp_st;
+ u32 win0_dsp_bg;
+ u32 win0_scl_factor_yrgb;
+ u32 reserved2;
+ u32 win0_scl_offset;
+ u32 win0_transformed_offset;
+ u32 reserved3[4];
+ u32 win0_afbcd_output_ctrl;
+ u32 win0_afbcd_mode;
+ u32 win0_afbcd_hdr_ptr;
+ u32 win0_afbcd_vir_width;
+ u32 win0_afbcd_size;
+ u32 win0_afbcd_pic_offset;
+ u32 win0_afbcd_dis_offset;
+ u32 win0_afbcd_ctrl;
+};
+
+check_member(rk3568_vop_cluster, win0_afbcd_ctrl, 0x006C);
+
+struct rk3568_vop_esmart {
+ u32 esmart_ctrl0;
+ u32 esmart_ctrl1;
+ u32 reserved0[2];
+ u32 esmart_region0_mst_ctl;
+ u32 esmart_region0_mst_yrgb;
+ u32 esmart_region0_mst_cbcr;
+ u32 esmart_region0_vir;
+ u32 esmart_region0_act_info;
+ u32 esmart_region0_dsp_info;
+ u32 esmart_region0_dsp_offset;
+ u32 reserved1[1];
+ u32 esmart_region0_scl_ctrl;
+ u32 esmart_region0_scl_factor_yrgb;
+ u32 esmart_region0_scl_factor_cbcr;
+ u32 esmart_region0_scl_offset;
+};
+
+check_member(rk3568_vop_esmart, esmart_region0_scl_offset, 0x003C);
+
+enum rockchip_fb_data_format_t {
+ ARGB8888 = 0,
+ RGB888 = 1,
+ RGB565 = 2,
+};
+
+enum vop_modes {
+ VOP_MODE_EDP = 0,
+ VOP_MODE_MIPI,
+ VOP_MODE_HDMI,
+ VOP_MODE_LVDS,
+ VOP_MODE_DP,
+};
+
+/* OFFSETS */
+#define VOP2_SYSREG_OFFSET 0x0
+#define VOP2_OVERLAY_OFFSET 0x0600
+#define VOP2_POST_OFFSET(n) 0x0c00 + ((n) * 0x100)
+#define VOP2_CLUSTER_OFFSET(n) 0x1000 + ((n) * 0x200)
+#define VOP2_ESMART_OFFSET(n) 0x1800 + ((n) * 0x200)
+
+/* System Registers */
+/* REG_CFG_DONE */
+#define M_GLOBAL_REGDONE (1 << 15)
+#define M_LOAD_GLOBAL(x) (1 << ((x) & 3))
+
+#define V_GLOBAL_REGDONE(x) (((x) & 1) << 15)
+#define V_LOAD_GLOBAL(x, y) (((y) & 1) << ((x) & 3))
+
+/* VERSION_INFO */
+#define M_FPGA_VERSION (0xffff << 16)
+#define M_RTL_VERSION (0xffff)
+
+/* AUTO_GATING_CTRL */
+#define M_AUTO_GATING (1 << 31)
+#define V_AUTO_GATING(x) (((x) & 1) << 31)
+
+/* DSP_INFACE_POL */
+#define M_DSP_INFACE_REGDONE (1 << 28)
+#define V_DSP_INFACE_REGDONE(x) (((x) & 1) << 28)
+
+/* OTP_WIN_EN */
+#define M_OTP_WIN (1 << 0)
+#define V_OTP_WIN(x) (((x) & 1) << 0)
+
+/* Overlay */
+/* OVERLAY_CTRL */
+#define M_LAYER_SEL_REGDONE_SEL (3 << 30)
+#define M_LAYER_SEL_REGDONE_EN (1 << 28)
+#define M_VP_OVERLAY_MODE(vp) (1 << ((vp) & 3))
+
+#define V_LAYER_SEL_REGDONE_SEL(x) (((x) & 3) << 30)
+#define V_LAYER_SEL_REGDONE_EN(x) (((x) & 1) << 28)
+#define V_VP_OVERLAY_MODE(x, vp) (((x) & 1) << ((vp) & 3))
+
+/* LAYER_SEL */
+#define V_LAYER_SEL(x, port) (((port) & 7) << 4 * ((x) & 7))
+
+/* PORT_SEL */
+#define V_ESMART_SEL_PORT(x, vp) (((vp) & 3) << (24 + (2 * ((x) & 3))))
+#define V_CLUSTER_SEL_PORT(x, vp) (((vp) & 3) << (16 + (2 * ((x) & 3))))
+#define V_PORT_MUX(x, vp) (((x) & 0xf) << 4 * ((vp) & 3))
+
+/* Post processing */
+/* DSP_CTRL */
+#define M_POST_STANDBY (1 << 31)
+#define M_POST_FP_STANDBY (1 << 30)
+#define M_POST_BLACK (1 << 27)
+#define M_POST_OUT_ZERO (1 << 26)
+#define M_POST_LB_MODE (1 << 23)
+#define M_PRE_DITHER_DOWN (1 << 16)
+#define M_DSP_OUT_MODE (0xf)
+
+#define V_POST_STANDBY(x) (((x) & 1) << 31)
+#define V_POST_FP_STANDBY(x) (((x) & 1) << 30)
+#define V_POST_BLACK(x) (((x) & 1) << 27)
+#define V_POST_OUT_ZERO(x) (((x) & 1) << 26)
+#define V_POST_LB_MODE(x) (((x) & 1) << 23)
+#define V_PRE_DITHER_DOWN(x) (((x) & 1) << 16)
+#define V_DSP_OUT_MODE(x) ((x) & 0xf)
+
+/* COLOR_CTRL */
+#define M_POST_COLORBAR_MODE (1 << 1)
+#define M_POST_COLORBAR_EN (1 << 0)
+
+#define V_POST_COLORBAR_MODE(x) (((x) & 1) << 1)
+#define V_POST_COLORBAR_EN(x) (((x) & 1) << 0)
+
+/* DSP_BG */
+#define M_DSP_BG_RED (0x3f << 20)
+#define M_DSP_BG_GREEN (0x3f << 10)
+#define M_DSP_BG_BLUE (0x3f << 0)
+
+#define V_DSP_BG_RED(x) (((x) & 0x3f) << 20)
+#define V_DSP_BG_GREEN(x) (((x) & 0x3f) << 10)
+#define V_DSP_BG_BLUE(x) (((x) & 0x3f) << 0)
+
+/* ESMART */
+#define M_ESMART_REGION0_MST_EN (1 << 0)
+#define V_ESMART_REGION0_DATA_FMT(x) (((x) & 0x16) << 1)
+
+/* VOP2_ESMART_REGION0_VIR */
+#define V_ARGB888_VIRWIDTH(x) (((x) & 0xffff) << 0)
+#define V_RGB888_VIRWIDTH(x) ((((((x) * 3) >> 2) + ((x) % 3)) & 0xffff) << 0)
+#define V_RGB565_VIRWIDTH(x) ((((x) / 2) & 0xffff) << 0)
+#define YUV_VIRWIDTH(x) ((((x) / 4) & 0xffff) << 0)
+
+#define V_ACT_HEIGHT(x) (((x) & 0x1fff) << 16)
+#define V_ACT_WIDTH(x) ((x) & 0x1fff)
+#define V_DSP_HEIGHT(x) (((x) & 0x1fff) << 16)
+#define V_DSP_WIDTH(x) ((x) & 0x1fff)
+#define V_DSP_YST(x) (((x) & 0x1fff) << 16)
+#define V_DSP_XST(x) ((x) & 0x1fff)
+
+#define V_HSYNC(x) (((x) & 0x1fff) << 0) /* hsync pulse width */
+#define V_HORPRD(x) (((x) & 0x1fff) << 16) /* horizontal period */
+#define V_VSYNC(x) (((x) & 0x1fff) << 0)
+#define V_VERPRD(x) (((x) & 0x1fff) << 16)
+
+#define V_HEAP(x) (((x) & 0x1fff) << 0)/* horizontal active end */
+#define V_HASP(x) (((x) & 0x1fff) << 16)/* horizontal active start */
+#define V_VAEP(x) (((x) & 0x1fff) << 0)
+#define V_VASP(x) (((x) & 0x1fff) << 16)
+
+#endif
diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile
index f55beceebf1..2f89a979a28 100644
--- a/drivers/video/rockchip/Makefile
+++ b/drivers/video/rockchip/Makefile
@@ -4,10 +4,11 @@
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
ifdef CONFIG_VIDEO_ROCKCHIP
-obj-y += rk_vop.o
+obj-y += rk_vop.o rk_vop2.o
obj-$(CONFIG_ROCKCHIP_RK3288) += rk3288_vop.o
obj-$(CONFIG_ROCKCHIP_RK3328) += rk3328_vop.o
obj-$(CONFIG_ROCKCHIP_RK3399) += rk3399_vop.o
+obj-$(CONFIG_ROCKCHIP_RK3568) += rk3568_vop.o
obj-$(CONFIG_DISPLAY_ROCKCHIP_EDP) += rk_edp.o
obj-$(CONFIG_DISPLAY_ROCKCHIP_LVDS) += rk_lvds.o
obj-hdmi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_hdmi.o
diff --git a/drivers/video/rockchip/rk3568_vop.c b/drivers/video/rockchip/rk3568_vop.c
new file mode 100644
index 00000000000..787060c1d80
--- /dev/null
+++ b/drivers/video/rockchip/rk3568_vop.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024 - 2025 Dang Huynh <dang.huynh@mainlining.org>
+ *
+ * Based on rk3399_vop.c:
+ * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH
+ * Copyright (c) 2015 Google, Inc
+ * Copyright 2014 Rockchip Inc.
+ */
+
+#include <clk.h>
+#include <display.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <regmap.h>
+#include <video.h>
+#include <asm/arch-rockchip/hardware.h>
+#include <asm/global_data.h>
+#include <linux/bitfield.h>
+#include "rk_vop2.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define M_MIPI1_INFACE_MUX (3 << 21)
+#define M_LVDS_INFACE_MUX (3 << 18)
+#define M_MIPI_INFACE_MUX (3 << 16)
+#define M_EDP_INFACE_MUX (3 << 14)
+#define M_HDMI_INFACE_MUX (3 << 10)
+#define M_RGB_INFACE_MUX (3 << 8)
+
+#define V_MIPI1_INFACE_MUX(x) (((x) & 3) << 21)
+#define V_LVDS_INFACE_MUX(x) (((x) & 3) << 18)
+#define V_MIPI_INFACE_MUX(x) (((x) & 3) << 16)
+#define V_EDP_INFACE_MUX(x) (((x) & 3) << 14)
+#define V_HDMI_INFACE_MUX(x) (((x) & 3) << 10)
+#define V_RGB_INFACE_MUX(x) (((x) & 3) << 8)
+
+#define M_MIPI_POL (0xf << 16)
+#define M_EDP_POL (0xf << 12)
+#define M_HDMI_POL (0xf << 4)
+#define M_RGB_LVDS_POL (0xf << 0)
+
+#define V_MIPI_POL(x) (((x) & 0xf) << 16)
+#define V_EDP_POL(x) (((x) & 0xf) << 12)
+#define V_HDMI_POL(x) (((x) & 0xf) << 4)
+#define V_RGB_LVDS_POL(x) (((x) & 0xf) << 0)
+
+#define M_MIPI1_OUT_EN (1 << 20)
+#define M_BT656_OUT_EN (1 << 7)
+#define M_BT1120_OUT_EN (1 << 6)
+#define M_LVDS_OUT_EN (1 << 5)
+#define M_MIPI_OUT_EN (1 << 4)
+#define M_EDP_OUT_EN (1 << 3)
+#define M_HDMI_OUT_EN (1 << 1)
+#define M_RGB_OUT_EN (1 << 0)
+
+#define M_ALL_OUT_EN (M_MIPI1_OUT_EN | M_BT656_OUT_EN | M_BT1120_OUT_EN | M_LVDS_OUT_EN | \
+ M_MIPI_OUT_EN | M_EDP_OUT_EN | M_HDMI_OUT_EN | M_RGB_OUT_EN)
+
+#define V_MIPI1_OUT_EN(x) (((x) & 1) << 20)
+#define V_BT656_OUT_EN(x) (((x) & 1) << 7)
+#define V_BT1120_OUT_EN(x) (((x) & 1) << 6)
+#define V_LVDS_OUT_EN(x) (((x) & 1) << 5)
+#define V_MIPI_OUT_EN(x) (((x) & 1) << 4)
+#define V_EDP_OUT_EN(x) (((x) & 1) << 3)
+#define V_HDMI_OUT_EN(x) (((x) & 1) << 1)
+#define V_RGB_OUT_EN(x) (((x) & 1) << 0)
+
+static void rk3568_enable_output(struct udevice *dev,
+ enum vop_modes mode, u32 port)
+{
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
+ struct rk3568_vop_sysctrl *sysctrl = priv->regs + VOP2_SYSREG_OFFSET;
+ u32 reg;
+
+ switch (mode) {
+ case VOP_MODE_EDP:
+ reg |= M_EDP_OUT_EN | V_EDP_INFACE_MUX(port);
+ break;
+
+ case VOP_MODE_HDMI:
+ reg |= M_HDMI_OUT_EN | V_HDMI_INFACE_MUX(port);
+ break;
+
+ case VOP_MODE_MIPI:
+ reg |= M_MIPI_OUT_EN | V_MIPI_INFACE_MUX(port);
+ break;
+
+ case VOP_MODE_LVDS:
+ reg |= M_LVDS_OUT_EN | V_LVDS_INFACE_MUX(port);
+ break;
+
+ default:
+ debug("%s: unsupported output mode %x\n", __func__, mode);
+ return;
+ }
+
+ debug("%s: vop output 0x%08x\n", __func__, reg);
+ writel(reg, &sysctrl->dsp_en);
+}
+
+static void rk3568_set_pin_polarity(struct udevice *dev,
+ enum vop_modes mode, u32 polarity)
+{
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
+ struct rk3568_vop_sysctrl *sysctrl = priv->regs + VOP2_SYSREG_OFFSET;
+ u32 reg;
+
+ reg = M_DSP_INFACE_REGDONE;
+
+ switch (mode) {
+ case VOP_MODE_EDP:
+ reg |= V_EDP_POL(polarity);
+ break;
+
+ case VOP_MODE_HDMI:
+ reg |= V_HDMI_POL(polarity);
+ break;
+
+ case VOP_MODE_MIPI:
+ reg |= V_MIPI_POL(polarity);
+ break;
+
+ /* RGB and LVDS shares the same polarity */
+ case VOP_MODE_LVDS:
+ reg |= V_RGB_LVDS_POL(polarity);
+ break;
+
+ default:
+ debug("%s: unsupported output mode %x\n", __func__, mode);
+ return;
+ }
+
+ debug("%s: vop polarity 0x%08x\n", __func__, reg);
+ writel(reg, &sysctrl->dsp_pol);
+}
+
+static int rkvop2_initialize(struct udevice *dev)
+{
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
+ struct rk3568_vop_sysctrl *sysctrl = priv->regs + VOP2_SYSREG_OFFSET;
+
+ /* Enable OTP function */
+ clrsetbits_le32(&sysctrl->otp_win, M_OTP_WIN, V_OTP_WIN(1));
+
+ writel(M_GLOBAL_REGDONE, &sysctrl->reg_cfg_done);
+
+ /* Disable auto gating */
+ clrsetbits_le32(&sysctrl->autogating_ctrl, M_AUTO_GATING, V_AUTO_GATING(0));
+
+ return 0;
+}
+
+/*
+ * FIXME: Booting into Linux with a window plane enabled causes VOP IOMMU
+ * to fail.
+ *
+ * This can be removed when there's a better way to handle MMU under Linux.
+ */
+static int rk3568_vop_remove(struct udevice *dev)
+{
+ if (CONFIG_IS_ENABLED(VIDEO_REMOVE)) {
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
+ struct rk3568_vop_sysctrl *sysctrl = priv->regs + VOP2_SYSREG_OFFSET;
+ struct rk3568_vop_esmart *esmart = priv->regs + VOP2_ESMART_OFFSET(priv->layer - 4);
+
+ debug("Removing VOP2 driver (vp=%d, layer=%d)\n", priv->vp, priv->layer);
+
+ /* RK356X appears to only make use of (E)SMARTs */
+ writel(0, &esmart->esmart_region0_mst_ctl);
+
+ /* On RK3568, we don't need to shift the bit by 16. */
+ writel(M_GLOBAL_REGDONE | M_LOAD_GLOBAL(priv->vp),
+ &sysctrl->reg_cfg_done);
+ }
+
+ return 0;
+}
+
+static int rk3568_vop_probe(struct udevice *dev)
+{
+ int ret;
+
+ /* Before relocation we don't need to do anything */
+ if (!(gd->flags & GD_FLG_RELOC))
+ return 0;
+
+ ret = rkvop2_initialize(dev);
+ if (ret)
+ return ret;
+
+ return rk_vop2_probe(dev);
+}
+
+/*
+ * RK3566 datasheet omits the VP2, even though it exist in the hardware
+ * so let's not use it.
+ */
+struct rkvop2_platdata rk3566_platdata = {
+ .delay = 20,
+ .bg_dly = {42, 40, -1},
+ /* SMART0, ESMART0 */
+ .vp_lyr = {3, 2, -1},
+ .layers = {ROCKCHIP_VOP2_CLUSTER0, ROCKCHIP_VOP2_RESERVED,
+ ROCKCHIP_VOP2_ESMART0, ROCKCHIP_VOP2_SMART0,
+ ROCKCHIP_VOP2_RESERVED, ROCKCHIP_VOP2_RESERVED,
+ ROCKCHIP_VOP2_RESERVED, ROCKCHIP_VOP2_RESERVED},
+};
+
+struct rkvop2_platdata rk3568_platdata = {
+ .delay = 20,
+ .bg_dly = {42, 40, 40},
+ /* SMART0, SMART1, ESMART1 */
+ .vp_lyr = {3, 7, 6},
+ .layers = {ROCKCHIP_VOP2_CLUSTER0, ROCKCHIP_VOP2_CLUSTER1,
+ ROCKCHIP_VOP2_ESMART0, ROCKCHIP_VOP2_SMART0,
+ ROCKCHIP_VOP2_RESERVED, ROCKCHIP_VOP2_RESERVED,
+ ROCKCHIP_VOP2_ESMART1, ROCKCHIP_VOP2_SMART1},
+};
+
+struct rkvop2_driverdata rk3566_driverdata = {
+ .features = VOP_FEATURE_OUTPUT_10BIT,
+ .set_pin_polarity = rk3568_set_pin_polarity,
+ .enable_output = rk3568_enable_output,
+ .platdata = &rk3566_platdata,
+};
+
+struct rkvop2_driverdata rk3568_driverdata = {
+ .features = VOP_FEATURE_OUTPUT_10BIT,
+ .set_pin_polarity = rk3568_set_pin_polarity,
+ .enable_output = rk3568_enable_output,
+ .platdata = &rk3568_platdata,
+};
+
+static const struct udevice_id rk3568_vop_ids[] = {
+ { .compatible = "rockchip,rk3566-vop",
+ .data = (ulong)&rk3566_driverdata },
+ { .compatible = "rockchip,rk3568-vop",
+ .data = (ulong)&rk3568_driverdata },
+ { }
+};
+
+static const struct video_ops rk3568_vop_ops = {
+};
+
+U_BOOT_DRIVER(rk3568_vop) = {
+ .name = "rk3568_vop",
+ .id = UCLASS_VIDEO,
+ .of_match = rk3568_vop_ids,
+ .ops = &rk3568_vop_ops,
+ .bind = rk_vop2_bind,
+ .probe = rk3568_vop_probe,
+ .remove = rk3568_vop_remove,
+ .priv_auto = sizeof(struct rk_vop2_priv),
+#if CONFIG_IS_ENABLED(VIDEO_REMOVE)
+ .flags = DM_FLAG_PRE_RELOC | DM_FLAG_OS_PREPARE,
+#else
+ .flags = DM_FLAG_PRE_RELOC,
+#endif
+};
diff --git a/drivers/video/rockchip/rk_vop2.c b/drivers/video/rockchip/rk_vop2.c
new file mode 100644
index 00000000000..992f215d416
--- /dev/null
+++ b/drivers/video/rockchip/rk_vop2.c
@@ -0,0 +1,520 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024 - 2025 Dang Huynh <dang.huynh@mainlining.org>
+ *
+ * Based on rk_vop.c:
+ * Copyright (c) 2015 Google, Inc
+ * Copyright 2014 Rockchip Inc.
+ */
+
+#include <clk.h>
+#include <display.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <edid.h>
+#include <log.h>
+#include <regmap.h>
+#include <reset.h>
+#include <syscon.h>
+#include <video.h>
+#include <asm/global_data.h>
+#include <asm/gpio.h>
+#include <asm/io.h>
+#include <asm/arch-rockchip/clock.h>
+#include <asm/arch-rockchip/vop_rk3568.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#include <efi.h>
+#include <efi_loader.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <power/regulator.h>
+
+#include "rk_vop2.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+enum vop_pol {
+ HSYNC_POSITIVE = 0,
+ VSYNC_POSITIVE = 1,
+ DEN_NEGATIVE = 2,
+ DCLK_INVERT = 3
+};
+
+static void rkvop2_cfg_regdone(struct rk3568_vop_sysctrl *sysctrl, int port)
+{
+ u32 reg;
+
+ reg = M_GLOBAL_REGDONE;
+
+ /*
+ * For RK3588, changes will only take effect when the same bit is
+ * leftshifted by 16.
+ */
+ reg |= M_LOAD_GLOBAL(port) | M_LOAD_GLOBAL(port) << 16;
+
+ writel(reg, &sysctrl->reg_cfg_done);
+}
+
+static int rkvop2_enable(struct udevice *dev, ulong fbbase,
+ int fb_bits_per_pixel, const struct display_timing *edid,
+ int port, int win_id, struct rkvop2_platdata *platdata)
+{
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
+ struct rk3568_vop_overlay *overlay = priv->regs + VOP2_OVERLAY_OFFSET;
+ struct rk3568_vop_esmart *esmart;
+ bool is_cluster = false;
+ u8 layer;
+ u32 reg;
+ u32 rgb_mode;
+ u32 hactive = edid->hactive.typ;
+ u32 vactive = edid->vactive.typ;
+
+ if (platdata->layers[win_id] < 0)
+ return -EINVAL;
+
+ switch (platdata->layers[win_id]) {
+ case ROCKCHIP_VOP2_CLUSTER0:
+ case ROCKCHIP_VOP2_CLUSTER1:
+ case ROCKCHIP_VOP2_CLUSTER2:
+ case ROCKCHIP_VOP2_CLUSTER3:
+ is_cluster = true;
+ break;
+ default:
+ break;
+ }
+
+ layer = platdata->layers[win_id];
+
+ debug("(%s, %s): win_id = %d - layer = %d - cluster: %d\n",
+ dev_read_name(dev), __func__, win_id, layer, is_cluster);
+
+ /* TODO: Support VOP2 CLUSTER */
+ if (is_cluster) {
+ dev_err(dev, "win_id is a cluster, not supported.\n");
+ return -ENOSYS;
+ }
+
+ esmart = priv->regs + VOP2_ESMART_OFFSET(layer - 4);
+
+ debug("(%s, %s): esmart addr: 0x%p\n", dev_read_name(dev), __func__, esmart);
+
+ writel(V_ACT_WIDTH(hactive - 1) | V_ACT_HEIGHT(vactive - 1),
+ &esmart->esmart_region0_act_info);
+
+ /* Set offset to 0,0 */
+ writel(0, &esmart->esmart_region0_dsp_offset);
+
+ writel(V_DSP_WIDTH(hactive - 1) |
+ V_DSP_HEIGHT(vactive - 1),
+ &esmart->esmart_region0_dsp_info);
+
+ switch (fb_bits_per_pixel) {
+ case 16:
+ rgb_mode = RGB565;
+ writel(V_RGB565_VIRWIDTH(hactive), &esmart->esmart_region0_vir);
+ break;
+ case 24:
+ rgb_mode = RGB888;
+ writel(V_RGB888_VIRWIDTH(hactive), &esmart->esmart_region0_vir);
+ break;
+ case 32:
+ default:
+ rgb_mode = ARGB8888;
+ writel(V_ARGB888_VIRWIDTH(hactive), &esmart->esmart_region0_vir);
+ break;
+ }
+
+ writel(fbbase, &esmart->esmart_region0_mst_yrgb);
+
+ writel(V_ESMART_REGION0_DATA_FMT(rgb_mode) | M_ESMART_REGION0_MST_EN,
+ &esmart->esmart_region0_mst_ctl);
+
+ /* Set esmart to the destination video port */
+ reg = V_ESMART_SEL_PORT(layer - 4, port);
+
+ /*
+ * VOP2 requires every port mux to be configured.
+ *
+ * As U-Boot only supports singledisplay, we'll set all
+ * unused ports to set layer to 8 (disabled).
+ */
+ for (int i = 0; i < 4; i++) {
+ if (i != port)
+ reg |= V_PORT_MUX(8, i);
+ }
+
+ writel(reg, &overlay->port_sel);
+
+ /* Set layer 0 to win_id */
+ writel(V_LAYER_SEL(0, win_id), &overlay->layer_sel);
+
+ reg = readl(&overlay->overlay_ctrl) | M_LAYER_SEL_REGDONE_EN;
+ writel(reg, &overlay->overlay_ctrl);
+
+ priv->layer = layer;
+
+ return 0;
+}
+
+static void rkvop2_set_pin_polarity(struct udevice *dev,
+ enum vop_modes mode, u32 polarity)
+{
+ struct rkvop2_driverdata *ops =
+ (struct rkvop2_driverdata *)dev_get_driver_data(dev);
+
+ if (ops->set_pin_polarity)
+ ops->set_pin_polarity(dev, mode, polarity);
+}
+
+static void rkvop2_enable_output(struct udevice *dev, enum vop_modes mode, u32 port)
+{
+ struct rkvop2_driverdata *ops =
+ (struct rkvop2_driverdata *)dev_get_driver_data(dev);
+
+ if (ops->enable_output)
+ ops->enable_output(dev, mode, port);
+}
+
+static void rkvop2_mode_set(struct udevice *dev,
+ const struct display_timing *edid,
+ enum vop_modes mode, int port,
+ struct rkvop2_platdata *platdata)
+{
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
+ struct rk3568_vop_sysctrl *sysctrl = priv->regs + VOP2_SYSREG_OFFSET;
+ struct rk3568_vop_post *post = priv->regs + VOP2_POST_OFFSET(port);
+ struct rkvop2_driverdata *data =
+ (struct rkvop2_driverdata *)dev_get_driver_data(dev);
+
+ debug("(%s, %s): port addr: 0x%p\n", dev_read_name(dev), __func__, post);
+
+ u32 hactive = edid->hactive.typ;
+ u32 vactive = edid->vactive.typ;
+ u32 hsync_len = edid->hsync_len.typ;
+ u32 hback_porch = edid->hback_porch.typ;
+ u32 vsync_len = edid->vsync_len.typ;
+ u32 vback_porch = edid->vback_porch.typ;
+ u32 hfront_porch = edid->hfront_porch.typ;
+ u32 vfront_porch = edid->vfront_porch.typ;
+ int mode_flags;
+ u32 pin_polarity;
+ u32 reg;
+
+ pin_polarity = BIT(DCLK_INVERT);
+ if (edid->flags & DISPLAY_FLAGS_HSYNC_HIGH)
+ pin_polarity |= BIT(HSYNC_POSITIVE);
+ if (edid->flags & DISPLAY_FLAGS_VSYNC_HIGH)
+ pin_polarity |= BIT(VSYNC_POSITIVE);
+
+ rkvop2_enable_output(dev, mode, port);
+ rkvop2_set_pin_polarity(dev, mode, pin_polarity);
+
+ mode_flags = 0; /* RGB888 */
+ if ((data->features & VOP_FEATURE_OUTPUT_10BIT) &&
+ mode == VOP_MODE_HDMI)
+ mode_flags = 15; /* RGBaaa */
+
+ reg = V_DSP_OUT_MODE(mode_flags);
+
+ debug("(%s, %s): bg_dly: %d\n",
+ dev_read_name(dev), __func__, platdata->bg_dly[port]);
+
+ if (platdata->bg_dly[port] < 0) {
+ dev_err(dev, "bg_dly is zero for vp%d\n", port);
+ return;
+ }
+
+ writel(((platdata->bg_dly[port] + (hactive >> 1) - 1) << 16) | hsync_len,
+ &post->prescan_htimings);
+
+ writel(V_HSYNC(hsync_len) |
+ V_HORPRD(hsync_len + hback_porch + hactive + hfront_porch),
+ &post->dsp_htotal_hs_end);
+
+ writel(V_HEAP(hsync_len + hback_porch + hactive) |
+ V_HASP(hsync_len + hback_porch),
+ &post->dsp_hact_st_end);
+
+ writel(V_VAEP(vsync_len + vback_porch + vactive) |
+ V_VASP(vsync_len + vback_porch),
+ &post->dsp_vact_st_end);
+
+ writel(V_VSYNC(vsync_len) |
+ V_VERPRD(vsync_len + vback_porch + vactive + vfront_porch),
+ &post->dsp_vtotal_vs_end);
+
+ writel(V_HEAP(hsync_len + hback_porch + hactive) |
+ V_HASP(hsync_len + hback_porch),
+ &post->dsp_hact_info);
+
+ writel(V_VAEP(vsync_len + vback_porch + vactive) |
+ V_VASP(vsync_len + vback_porch),
+ &post->dsp_vact_info);
+
+ /* No scaling */
+ writel(0x10001000, &post->scl_factor_yrgb);
+
+ writel(reg, &post->dsp_ctrl);
+
+ rkvop2_cfg_regdone(sysctrl, port);
+}
+
+/**
+ * rk_display_init() - Try to enable the given display device
+ *
+ * This function performs many steps:
+ * - Finds the display device being referenced by @ep_node
+ * - Puts the VOP's ID into its uclass platform data
+ * - Probes the device to set it up
+ * - Reads the timing information (from EDID or panel)
+ * - Sets up the VOP clocks, etc. for the selected pixel clock and display mode
+ * - Enables the display (the display device handles this and will do different
+ * things depending on the display type)
+ * - Tells the uclass about the display resolution so that the console will
+ * appear correctly
+ *
+ * @dev: VOP device that we want to connect to the display
+ * @fbbase: Frame buffer address
+ * @vp_node: Device tree node to process
+ * Return: 0 if OK, -ve if something went wrong
+ */
+static int rk_display_init(struct udevice *dev, ulong fbbase, ofnode vp_node)
+{
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
+ struct video_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct rkvop2_driverdata *drvdata =
+ (struct rkvop2_driverdata *)dev_get_driver_data(dev);
+ struct rkvop2_platdata *platdata =
+ (struct rkvop2_platdata *)drvdata->platdata;
+ ofnode ep_node;
+ int vop_id, port_id, win_id;
+ struct display_timing timing;
+ struct udevice *disp;
+ int ret;
+ u32 remote_phandle;
+ struct display_plat *disp_uc_plat;
+ enum video_log2_bpp l2bpp;
+ ofnode remote;
+ const char *compat;
+ char dclk_name[9];
+ struct clk dclk;
+
+ debug("%s(%s, 0x%lx, %s)\n", __func__,
+ dev_read_name(dev), fbbase, ofnode_get_name(vp_node));
+
+ port_id = ofnode_read_u32_default(vp_node, "reg", -1);
+ if (port_id < 0) {
+ debug("%s(%s): no video port id\n", __func__, dev_read_name(dev));
+ return port_id;
+ }
+
+ ep_node = ofnode_first_subnode(vp_node);
+ if (!ofnode_valid(ep_node)) {
+ debug("%s(%s): no valid subnode\n", __func__, dev_read_name(dev));
+ return -EINVAL;
+ }
+
+ ret = ofnode_read_u32(ep_node, "remote-endpoint", &remote_phandle);
+ if (ret) {
+ debug("%s(%s): no remote-endpoint\n", __func__, dev_read_name(dev));
+ return ret;
+ }
+
+ remote = ofnode_get_by_phandle(remote_phandle);
+ if (!ofnode_valid(remote))
+ return -EINVAL;
+
+ remote = ofnode_get_parent(remote);
+ if (!ofnode_valid(remote))
+ return -EINVAL;
+
+ /*
+ * The remote-endpoint references into a subnode of the encoder
+ * (i.e. HDMI, MIPI, etc.) with the DTS looking something like
+ * the following:
+ *
+ * hdmi: hdmi@fe0a0000 {
+ * ports {
+ * hdmi_in: port {
+ * hdmi_in_vp0: endpoint { ... };
+ * }
+ * }
+ * }
+ *
+ * This isn't any different from how VOP1 works, so we'll adapt
+ * the same method of finding the display from the original code
+ * (find the enclosing device of "UCLASS_DISPLAY")
+ *
+ * We also look for UCLASS_VIDEO_BRIDGE so we can use the existing
+ * DW MIPI DSI driver for Rockchip.
+ */
+ while (ofnode_valid(remote)) {
+ remote = ofnode_get_parent(remote);
+ if (!ofnode_valid(remote)) {
+ debug("%s(%s): no UCLASS_DISPLAY for remote-endpoint\n",
+ __func__, dev_read_name(dev));
+ return -EINVAL;
+ }
+
+ uclass_find_device_by_ofnode(UCLASS_DISPLAY, remote, &disp);
+ if (disp)
+ break;
+ };
+ compat = ofnode_get_property(remote, "compatible", NULL);
+ if (!compat) {
+ debug("%s(%s): Failed to find compatible property\n",
+ __func__, dev_read_name(dev));
+ return -EINVAL;
+ }
+ if (strstr(compat, "edp")) {
+ vop_id = VOP_MODE_EDP;
+ } else if (strstr(compat, "mipi")) {
+ vop_id = VOP_MODE_MIPI;
+ } else if (strstr(compat, "hdmi")) {
+ vop_id = VOP_MODE_HDMI;
+ } else if (strstr(compat, "rk3588-dp")) {
+ vop_id = VOP_MODE_DP;
+ } else if (strstr(compat, "lvds")) {
+ vop_id = VOP_MODE_LVDS;
+ } else {
+ debug("%s(%s): Failed to find vop mode for %s\n",
+ __func__, dev_read_name(dev), compat);
+ return -EINVAL;
+ }
+ debug("vop_id=%d - port=%d\n", vop_id, port_id);
+
+ /* Get the video port clock and enable it */
+ snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", port_id);
+ ret = clk_get_by_name(dev, dclk_name, &dclk);
+ if (ret < 0)
+ return ret;
+
+ disp_uc_plat = dev_get_uclass_plat(disp);
+ debug("Found device '%s', disp_uc_priv=%p\n", disp->name, disp_uc_plat);
+ if (display_in_use(disp)) {
+ debug(" - device in use\n");
+ return -EBUSY;
+ }
+
+ disp_uc_plat->source_id = vop_id;
+ disp_uc_plat->src_dev = dev;
+
+ ret = device_probe(disp);
+ if (ret) {
+ debug("%s: device '%s' display won't probe (ret=%d)\n",
+ __func__, dev->name, ret);
+ return ret;
+ }
+
+ ret = display_read_timing(disp, &timing);
+ if (ret) {
+ debug("%s: Failed to read timings\n", __func__);
+ return ret;
+ }
+
+ /* Set clock rate on video port to display timings */
+ ret = clk_set_rate(&dclk, timing.pixelclock.typ);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set clock rate: %d\n", ret);
+ return ret;
+ }
+
+ debug("%s(%s): %s clkrate %lu\n", __func__, dev_read_name(dev),
+ dclk_name, clk_get_rate(&dclk));
+
+ /* Set bitwidth for vop display according to vop mode */
+ switch (vop_id) {
+ case VOP_MODE_EDP:
+ case VOP_MODE_MIPI:
+ case VOP_MODE_HDMI:
+ case VOP_MODE_DP:
+ case VOP_MODE_LVDS:
+ l2bpp = VIDEO_BPP32;
+ break;
+ default:
+ l2bpp = VIDEO_BPP16;
+ }
+
+ /*
+ * We'll use the default platform-specific win_id from Linux
+ * so that Linux can take over U-Boot plane when Linux reconfigures
+ * VOP2.
+ */
+ win_id = platdata->vp_lyr[port_id];
+ if (win_id < 0) {
+ dev_err(dev, "win_id is null, don't setup\n");
+ return -EINVAL;
+ }
+
+ ret = rkvop2_enable(dev, fbbase, 1 << l2bpp, &timing, port_id, win_id, platdata);
+ if (ret < 0)
+ return ret;
+
+ rkvop2_mode_set(dev, &timing, vop_id, port_id, platdata);
+
+ ret = display_enable(disp, 1 << l2bpp, &timing);
+ if (ret)
+ return ret;
+
+ uc_priv->xsize = timing.hactive.typ;
+ uc_priv->ysize = timing.vactive.typ;
+ uc_priv->bpix = l2bpp;
+
+ priv->vp = port_id;
+
+ debug("fb=%lx, size=%d %d\n", fbbase,
+ uc_priv->xsize, uc_priv->ysize);
+
+ return 0;
+}
+
+int rk_vop2_probe(struct udevice *dev)
+{
+ struct video_uc_plat *plat = dev_get_uclass_plat(dev);
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
+ int ret = 0;
+ ofnode port, node;
+
+ /* Before relocation we don't need to do anything */
+ if (!(gd->flags & GD_FLG_RELOC))
+ return 0;
+
+ if (IS_ENABLED(CONFIG_EFI_LOADER)) {
+ debug("Adding to EFI map %d @ %lx\n", plat->size, plat->base);
+ efi_add_memory_map(plat->base, plat->size, EFI_RESERVED_MEMORY_TYPE);
+ }
+
+ priv->regs = dev_read_addr_ptr(dev);
+
+ /* Try all the ports until we find one that works. */
+ port = dev_read_subnode(dev, "ports");
+ if (!ofnode_valid(port)) {
+ debug("%s(%s): 'port' subnode not found\n",
+ __func__, dev_read_name(dev));
+ return -EINVAL;
+ }
+
+ for (node = ofnode_first_subnode(port);
+ ofnode_valid(node);
+ node = dev_read_next_subnode(node)) {
+ ret = rk_display_init(dev, plat->base, node);
+ if (ret)
+ debug("Device failed: ret=%d\n", ret);
+ if (!ret)
+ break;
+ }
+ video_set_flush_dcache(dev, true);
+
+ return ret;
+}
+
+int rk_vop2_bind(struct udevice *dev)
+{
+ struct video_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->size = 4 * (CONFIG_VIDEO_ROCKCHIP_MAX_XRES *
+ CONFIG_VIDEO_ROCKCHIP_MAX_YRES);
+
+ return 0;
+}
diff --git a/drivers/video/rockchip/rk_vop2.h b/drivers/video/rockchip/rk_vop2.h
new file mode 100644
index 00000000000..5d668070014
--- /dev/null
+++ b/drivers/video/rockchip/rk_vop2.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH
+ */
+
+#ifndef __RK_VOP2_H__
+#define __RK_VOP2_H__
+
+#include <asm/arch-rockchip/vop_rk3568.h>
+
+struct rk_vop2_priv {
+ void *grf;
+ void *regs;
+ int vp;
+ int layer;
+};
+
+enum vop2_features {
+ VOP_FEATURE_OUTPUT_10BIT = (1 << 0),
+};
+
+enum vop2_layer {
+ ROCKCHIP_VOP2_CLUSTER0 = 0,
+ ROCKCHIP_VOP2_CLUSTER1,
+ ROCKCHIP_VOP2_CLUSTER2,
+ ROCKCHIP_VOP2_CLUSTER3,
+ ROCKCHIP_VOP2_ESMART0,
+ ROCKCHIP_VOP2_ESMART1,
+ ROCKCHIP_VOP2_ESMART2,
+ ROCKCHIP_VOP2_ESMART3,
+ ROCKCHIP_VOP2_SMART0 = 6,
+ ROCKCHIP_VOP2_SMART1,
+ ROCKCHIP_VOP2_RESERVED = -1,
+};
+
+struct rkvop2_platdata {
+ const u8 delay;
+ const s8 bg_dly[4]; /* VOP2 supports up to 4 video ports (0-3) */
+ const s8 vp_lyr[4];
+ const s8 layers[8];
+};
+
+struct rkvop2_driverdata {
+ /* configuration */
+ u32 features;
+ void (*platdata);
+ /* block-specific setters/getters */
+ void (*enable_output)(struct udevice *dev, enum vop_modes mode, u32 port);
+ void (*set_pin_polarity)(struct udevice *dev, enum vop_modes mode, u32 port);
+};
+
+/**
+ * rk_vop2_probe() - common probe implementation
+ *
+ * Performs the rk_display_init on each port-subnode until finding a
+ * working port (or returning an error if none of the ports could be
+ * successfully initialised).
+ *
+ * @dev: device
+ * Return: 0 if OK, -ve if something went wrong
+ */
+int rk_vop2_probe(struct udevice *dev);
+
+/**
+ * rk_vop2_bind() - common bind implementation
+ *
+ * Sets the plat->size field to the amount of memory to be reserved for
+ * the framebuffer: this is always
+ * (32 BPP) x VIDEO_ROCKCHIP_MAX_XRES x VIDEO_ROCKCHIP_MAX_YRES
+ *
+ * @dev: device
+ * Return: 0 (always OK)
+ */
+int rk_vop2_bind(struct udevice *dev);
+
+#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/9] arm: dts: rockchip: rk356x: Prerelocate VOP in U-Boot proper
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
2026-07-19 1:58 ` [PATCH 1/9] video: rockchip: Add VOP2 support Daniel Golle
@ 2026-07-19 1:58 ` Daniel Golle
2026-07-19 1:59 ` [PATCH 3/9] video: rockchip: Add HDMI support for RK3568 Daniel Golle
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 1:58 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
From: Dang Huynh <dang.huynh@mainlining.org>
We need to prerelocate VOP2 memory so the driver can work. This
will only support U-Boot proper.
Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
arch/arm/dts/rk356x-u-boot.dtsi | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm/dts/rk356x-u-boot.dtsi b/arch/arm/dts/rk356x-u-boot.dtsi
index 738b9673d35..1ed06239f05 100644
--- a/arch/arm/dts/rk356x-u-boot.dtsi
+++ b/arch/arm/dts/rk356x-u-boot.dtsi
@@ -170,6 +170,10 @@
bootph-all;
};
+&vop {
+ bootph-some-ram;
+};
+
&xin24m {
bootph-all;
};
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 3/9] video: rockchip: Add HDMI support for RK3568
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
2026-07-19 1:58 ` [PATCH 1/9] video: rockchip: Add VOP2 support Daniel Golle
2026-07-19 1:58 ` [PATCH 2/9] arm: dts: rockchip: rk356x: Prerelocate VOP in U-Boot proper Daniel Golle
@ 2026-07-19 1:59 ` Daniel Golle
2026-07-19 1:59 ` [PATCH 4/9] clk: rockchip: rk3568: Use assigned VPLL clock when possible Daniel Golle
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 1:59 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
From: Dang Huynh <dang.huynh@mainlining.org>
HDMI on RK3568 is mostly simplified, all this does is enabling
DDC for display timings and HPD.
Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
drivers/video/rockchip/Makefile | 1 +
drivers/video/rockchip/rk3568_hdmi.c | 71 ++++++++++++++++++++++++++++
2 files changed, 72 insertions(+)
create mode 100644 drivers/video/rockchip/rk3568_hdmi.c
diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile
index 2f89a979a28..b751c969b46 100644
--- a/drivers/video/rockchip/Makefile
+++ b/drivers/video/rockchip/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_DISPLAY_ROCKCHIP_LVDS) += rk_lvds.o
obj-hdmi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_hdmi.o
obj-hdmi-$(CONFIG_ROCKCHIP_RK3328) += rk3328_hdmi.o
obj-hdmi-$(CONFIG_ROCKCHIP_RK3399) += rk3399_hdmi.o
+obj-hdmi-$(CONFIG_ROCKCHIP_RK3568) += rk3568_hdmi.o
obj-$(CONFIG_DISPLAY_ROCKCHIP_HDMI) += rk_hdmi.o $(obj-hdmi-y)
obj-mipi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_mipi.o
obj-mipi-$(CONFIG_ROCKCHIP_RK3399) += rk3399_mipi.o
diff --git a/drivers/video/rockchip/rk3568_hdmi.c b/drivers/video/rockchip/rk3568_hdmi.c
new file mode 100644
index 00000000000..c3f4504ca8f
--- /dev/null
+++ b/drivers/video/rockchip/rk3568_hdmi.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2024 - 2025 Dang Huynh <dang.huynh@mainlining.org>
+ */
+
+#include <display.h>
+#include <dm.h>
+#include <dw_hdmi.h>
+#include <edid.h>
+#include <asm/arch-rockchip/hardware.h>
+#include <asm/arch-rockchip/grf_rk3568.h>
+#include "rk_hdmi.h"
+
+#define RK3568_IO_DDC_IN_MSK ((3 << 14) | (3 << (14 + 16)))
+
+static int rk3568_hdmi_enable(struct udevice *dev, int panel_bpp,
+ const struct display_timing *edid)
+{
+ struct rk_hdmi_priv *priv = dev_get_priv(dev);
+
+ return dw_hdmi_enable(&priv->hdmi, edid);
+}
+
+static int rk3568_hdmi_of_to_plat(struct udevice *dev)
+{
+ struct rk_hdmi_priv *priv = dev_get_priv(dev);
+ struct dw_hdmi *hdmi = &priv->hdmi;
+
+ hdmi->i2c_clk_high = 0x6c;
+ hdmi->i2c_clk_low = 0x7d;
+
+ return rk_hdmi_of_to_plat(dev);
+}
+
+static const char * const rk3568_hdmi_reg_names[] = {
+ "avdd-0v9",
+ "avdd-1v8"
+};
+
+static int rk3568_hdmi_probe(struct udevice *dev)
+{
+ struct rk_hdmi_priv *priv = dev_get_priv(dev);
+ struct rk3568_grf *grf = priv->grf;
+
+ rk_hdmi_probe_regulators(dev, rk3568_hdmi_reg_names,
+ ARRAY_SIZE(rk3568_hdmi_reg_names));
+
+ writel(RK3568_IO_DDC_IN_MSK, &grf->vo_con1);
+
+ return rk_hdmi_probe(dev);
+}
+
+static const struct dm_display_ops rk3568_hdmi_ops = {
+ .read_edid = rk_hdmi_read_edid,
+ .enable = rk3568_hdmi_enable,
+};
+
+static const struct udevice_id rk3568_hdmi_ids[] = {
+ { .compatible = "rockchip,rk3568-dw-hdmi" },
+ { }
+};
+
+U_BOOT_DRIVER(rk3568_hdmi_rockchip) = {
+ .name = "rk3568_hdmi_rockchip",
+ .id = UCLASS_DISPLAY,
+ .of_match = rk3568_hdmi_ids,
+ .ops = &rk3568_hdmi_ops,
+ .of_to_plat = rk3568_hdmi_of_to_plat,
+ .probe = rk3568_hdmi_probe,
+ .priv_auto = sizeof(struct rk_hdmi_priv),
+};
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 4/9] clk: rockchip: rk3568: Use assigned VPLL clock when possible
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
` (2 preceding siblings ...)
2026-07-19 1:59 ` [PATCH 3/9] video: rockchip: Add HDMI support for RK3568 Daniel Golle
@ 2026-07-19 1:59 ` Daniel Golle
2026-07-19 1:59 ` [PATCH 5/9] video: rockchip: vop2: fix NULL register base in rk3568 probe Daniel Golle
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 1:59 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
From: Dang Huynh <dang.huynh@mainlining.org>
This matches how VPLL is configured under Linux and avoid weird
behaviors when VPLL is reconfigured under Linux.
Signed-off-by: Dang Huynh <dang.huynh@mainlining.org>
---
drivers/clk/rockchip/clk_rk3568.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/rockchip/clk_rk3568.c b/drivers/clk/rockchip/clk_rk3568.c
index bb49af358e6..c8e8e1d4f7d 100644
--- a/drivers/clk/rockchip/clk_rk3568.c
+++ b/drivers/clk/rockchip/clk_rk3568.c
@@ -1818,7 +1818,11 @@ static ulong rk3568_dclk_vop_set_clk(struct rk3568_clk_priv *priv,
((div - 1) << DCLK0_VOP_DIV_SHIFT));
rk3568_pmu_pll_set_rate(priv, HPLL, div * rate);
} else if (sel == DCLK_VOP_SEL_VPLL) {
- div = DIV_ROUND_UP(RK3568_VOP_PLL_LIMIT_FREQ, rate);
+ if (priv->vpll_hz)
+ div = DIV_ROUND_UP(priv->vpll_hz, rate);
+ else
+ div = DIV_ROUND_UP(RK3568_VOP_PLL_LIMIT_FREQ, rate);
+
rk_clrsetreg(&cru->clksel_con[conid],
DCLK0_VOP_DIV_MASK | DCLK0_VOP_SEL_MASK,
(DCLK_VOP_SEL_VPLL << DCLK0_VOP_SEL_SHIFT) |
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 5/9] video: rockchip: vop2: fix NULL register base in rk3568 probe
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
` (3 preceding siblings ...)
2026-07-19 1:59 ` [PATCH 4/9] clk: rockchip: rk3568: Use assigned VPLL clock when possible Daniel Golle
@ 2026-07-19 1:59 ` Daniel Golle
2026-07-19 1:59 ` [PATCH 6/9] video: rockchip: rk3568: ungate the HDMI controller clocks Daniel Golle
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 1:59 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
rk3568_vop_probe() calls rkvop2_initialize() before rk_vop2_probe(),
but priv->regs (the VOP register base) was only resolved inside
rk_vop2_probe() via dev_read_addr_ptr(). rkvop2_initialize()
dereferences priv->regs (sysctrl = priv->regs + VOP2_SYSREG_OFFSET),
so on probe it faulted on a NULL base:
Synchronous Abort, esr 0x96000010, far 0x50
... rk3568_vop_probe
Resolve the register base at the top of rk3568_vop_probe(), before
rkvop2_initialize() runs; rk_vop2_probe() then sets it again to the
same value.
Fixes: the in-review 'video: rockchip: Add VOP2 support' change.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/video/rockchip/rk3568_vop.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/video/rockchip/rk3568_vop.c b/drivers/video/rockchip/rk3568_vop.c
index 787060c1d80..ce182d1d51c 100644
--- a/drivers/video/rockchip/rk3568_vop.c
+++ b/drivers/video/rockchip/rk3568_vop.c
@@ -179,12 +179,22 @@ static int rk3568_vop_remove(struct udevice *dev)
static int rk3568_vop_probe(struct udevice *dev)
{
+ struct rk_vop2_priv *priv = dev_get_priv(dev);
int ret;
/* Before relocation we don't need to do anything */
if (!(gd->flags & GD_FLG_RELOC))
return 0;
+ /*
+ * rkvop2_initialize() touches the VOP registers, so the register
+ * base must be resolved before it runs; rk_vop2_probe() sets it
+ * again to the same value.
+ */
+ priv->regs = dev_read_addr_ptr(dev);
+ if (!priv->regs)
+ return -EINVAL;
+
ret = rkvop2_initialize(dev);
if (ret)
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 6/9] video: rockchip: rk3568: ungate the HDMI controller clocks
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
` (4 preceding siblings ...)
2026-07-19 1:59 ` [PATCH 5/9] video: rockchip: vop2: fix NULL register base in rk3568 probe Daniel Golle
@ 2026-07-19 1:59 ` Daniel Golle
2026-07-19 2:00 ` [PATCH 7/9] video: rockchip: rk3568: bring up the HDMI PHY reference clock Daniel Golle
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 1:59 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
The RK3568 HDMI probe polled the PHY hot-plug-detect status but always
timed out ('hdmi can not get hpd signal', -ENODEV), so the controller
never probed and HDMI output was dead even with a monitor attached.
The HDMI controller clocks are never enabled: neither the dw_hdmi glue
nor the RK3568 clock driver manages the pclk_hdmi_host / clk_hdmi_sfr /
clk_hdmi_cec gates (CLKGATE_CON21 bits 3-5). clk_hdmi_sfr feeds the
controller/PHY logic that produces the HPD status and is parented off
xin24m, independent of the pclk_vo domain that VOP2 bring-up enables, so
it stayed gated: register reads returned a clean zero and the HPD bit
never asserted. Mainline Linux enables isfr/iahb before reading the same
HDMI_PHY_STAT0 HPD bit.
Ungate the three HDMI gates in rk3568_hdmi_probe() before probing the
controller.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/video/rockchip/rk3568_hdmi.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/drivers/video/rockchip/rk3568_hdmi.c b/drivers/video/rockchip/rk3568_hdmi.c
index c3f4504ca8f..7b2034d6b3f 100644
--- a/drivers/video/rockchip/rk3568_hdmi.c
+++ b/drivers/video/rockchip/rk3568_hdmi.c
@@ -7,12 +7,18 @@
#include <dm.h>
#include <dw_hdmi.h>
#include <edid.h>
+#include <asm/arch-rockchip/clock.h>
+#include <asm/arch-rockchip/cru_rk3568.h>
#include <asm/arch-rockchip/hardware.h>
#include <asm/arch-rockchip/grf_rk3568.h>
#include "rk_hdmi.h"
#define RK3568_IO_DDC_IN_MSK ((3 << 14) | (3 << (14 + 16)))
+/* CLKGATE_CON21: pclk_hdmi_host (b3), clk_hdmi_sfr (b4), clk_hdmi_cec (b5) */
+#define RK3568_HDMI_CLKGATE_CON 21
+#define RK3568_HDMI_CLKGATE_MSK (0x7 << 3)
+
static int rk3568_hdmi_enable(struct udevice *dev, int panel_bpp,
const struct display_timing *edid)
{
@@ -41,10 +47,21 @@ static int rk3568_hdmi_probe(struct udevice *dev)
{
struct rk_hdmi_priv *priv = dev_get_priv(dev);
struct rk3568_grf *grf = priv->grf;
+ struct rk3568_cru *cru = rockchip_get_cru();
rk_hdmi_probe_regulators(dev, rk3568_hdmi_reg_names,
ARRAY_SIZE(rk3568_hdmi_reg_names));
+ /*
+ * Ungate the HDMI controller clocks. The RK3568 clock driver does
+ * not manage these gates, and clk_hdmi_sfr in particular must be
+ * running for the PHY hot-plug-detect status to be valid; without it
+ * the HPD poll times out and the controller is not probed.
+ */
+ if (cru)
+ rk_clrreg(&cru->clkgate_con[RK3568_HDMI_CLKGATE_CON],
+ RK3568_HDMI_CLKGATE_MSK);
+
writel(RK3568_IO_DDC_IN_MSK, &grf->vo_con1);
return rk_hdmi_probe(dev);
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 7/9] video: rockchip: rk3568: bring up the HDMI PHY reference clock
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
` (5 preceding siblings ...)
2026-07-19 1:59 ` [PATCH 6/9] video: rockchip: rk3568: ungate the HDMI controller clocks Daniel Golle
@ 2026-07-19 2:00 ` Daniel Golle
2026-07-19 2:00 ` [PATCH 8/9] video: rockchip: rk3568: power the HDMI PHY analog supplies Daniel Golle
2026-07-19 2:00 ` [PATCH 9/9] configs: nanopi-r5c: enable HDMI video output Daniel Golle
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 2:00 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
Add CLK_HDMI_REF support to the RK3568 PMU clock driver (route the mux to
HPLL and set the HPLL rate) and enable it from the HDMI driver: at probe
to a default rate, and at modeset to the pixel clock, matching mainline
Linux. The RK3568 HDMI PHY needs its reference clock running for output.
Note: this alone does not make hot-plug-detect work in U-Boot; the PHY
still reports no HPD (PHY_STAT0=0) with a monitor attached despite the
controller being live. The remaining HDMI PHY bring-up gap is unresolved.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/clk/rockchip/clk_rk3568.c | 12 ++++++++++++
drivers/video/rockchip/rk3568_hdmi.c | 24 +++++++++++++++++++++---
2 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/rockchip/clk_rk3568.c b/drivers/clk/rockchip/clk_rk3568.c
index c8e8e1d4f7d..61eb813577b 100644
--- a/drivers/clk/rockchip/clk_rk3568.c
+++ b/drivers/clk/rockchip/clk_rk3568.c
@@ -377,6 +377,10 @@ static ulong rk3568_pmuclk_get_rate(struct clk *clk)
case PCLK_PMU:
rate = rk3568_pmu_get_pmuclk(priv);
break;
+ case CLK_HDMI_REF:
+ rate = rockchip_pll_get_rate(&rk3568_pll_clks[HPLL],
+ priv->pmucru, HPLL);
+ break;
default:
return -ENOENT;
}
@@ -408,6 +412,14 @@ static ulong rk3568_pmuclk_set_rate(struct clk *clk, ulong rate)
priv->hpll_hz = rockchip_pll_get_rate(&rk3568_pll_clks[HPLL],
priv->pmucru, HPLL);
break;
+ case CLK_HDMI_REF:
+ /* clk_hdmi_ref is HPLL via PMU_CLKSEL_CON8 bit7 (0 = hpll) */
+ ret = rockchip_pll_set_rate(&rk3568_pll_clks[HPLL],
+ priv->pmucru, HPLL, rate);
+ priv->hpll_hz = rockchip_pll_get_rate(&rk3568_pll_clks[HPLL],
+ priv->pmucru, HPLL);
+ rk_clrsetreg(&priv->pmucru->pmu_clksel_con[8], BIT(7), 0);
+ break;
case CLK_RTC_32K:
case CLK_RTC32K_FRAC:
ret = rk3568_rtc32k_set_pmuclk(priv, rate);
diff --git a/drivers/video/rockchip/rk3568_hdmi.c b/drivers/video/rockchip/rk3568_hdmi.c
index 7b2034d6b3f..9b3e54ce6fd 100644
--- a/drivers/video/rockchip/rk3568_hdmi.c
+++ b/drivers/video/rockchip/rk3568_hdmi.c
@@ -3,6 +3,7 @@
* Copyright (c) 2024 - 2025 Dang Huynh <dang.huynh@mainlining.org>
*/
+#include <clk.h>
#include <display.h>
#include <dm.h>
#include <dw_hdmi.h>
@@ -13,6 +14,9 @@
#include <asm/arch-rockchip/grf_rk3568.h>
#include "rk_hdmi.h"
+/* Default HDMI PHY reference rate to bring HPLL up for hot-plug detect */
+#define RK3568_HDMI_REF_DEFAULT 148500000
+
#define RK3568_IO_DDC_IN_MSK ((3 << 14) | (3 << (14 + 16)))
/* CLKGATE_CON21: pclk_hdmi_host (b3), clk_hdmi_sfr (b4), clk_hdmi_cec (b5) */
@@ -23,6 +27,11 @@ static int rk3568_hdmi_enable(struct udevice *dev, int panel_bpp,
const struct display_timing *edid)
{
struct rk_hdmi_priv *priv = dev_get_priv(dev);
+ struct clk ref_clk;
+
+ /* Drive the PHY reference at the pixel clock for this mode */
+ if (!clk_get_by_name(dev, "ref", &ref_clk))
+ clk_set_rate(&ref_clk, edid->pixelclock.typ);
return dw_hdmi_enable(&priv->hdmi, edid);
}
@@ -48,20 +57,29 @@ static int rk3568_hdmi_probe(struct udevice *dev)
struct rk_hdmi_priv *priv = dev_get_priv(dev);
struct rk3568_grf *grf = priv->grf;
struct rk3568_cru *cru = rockchip_get_cru();
+ struct clk ref_clk;
rk_hdmi_probe_regulators(dev, rk3568_hdmi_reg_names,
ARRAY_SIZE(rk3568_hdmi_reg_names));
/*
* Ungate the HDMI controller clocks. The RK3568 clock driver does
- * not manage these gates, and clk_hdmi_sfr in particular must be
- * running for the PHY hot-plug-detect status to be valid; without it
- * the HPD poll times out and the controller is not probed.
+ * not manage these gates.
*/
if (cru)
rk_clrreg(&cru->clkgate_con[RK3568_HDMI_CLKGATE_CON],
RK3568_HDMI_CLKGATE_MSK);
+ /*
+ * Bring up the PHY reference clock (clk_hdmi_ref off HPLL) before
+ * probing: the internal HDMI PHY needs its reference running for the
+ * hot-plug-detect status to be valid, otherwise the HPD poll times
+ * out and the controller is not probed. Mainline Linux likewise
+ * enables this clock at probe.
+ */
+ if (!clk_get_by_name(dev, "ref", &ref_clk))
+ clk_set_rate(&ref_clk, RK3568_HDMI_REF_DEFAULT);
+
writel(RK3568_IO_DDC_IN_MSK, &grf->vo_con1);
return rk_hdmi_probe(dev);
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 8/9] video: rockchip: rk3568: power the HDMI PHY analog supplies
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
` (6 preceding siblings ...)
2026-07-19 2:00 ` [PATCH 7/9] video: rockchip: rk3568: bring up the HDMI PHY reference clock Daniel Golle
@ 2026-07-19 2:00 ` Daniel Golle
2026-07-19 2:00 ` [PATCH 9/9] configs: nanopi-r5c: enable HDMI video output Daniel Golle
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 2:00 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
On a cold boot the RK3568 HDMI output stayed dead (no hot-plug detect),
while after a warm reboot from Linux it worked, including graphical UEFI.
The difference was the PHY analog supplies avdd-0v9 and avdd-1v8
(vdda0v9_image and vcca1v8_image on the R5C): Linux enables them and a
warm reset leaves the PMIC LDOs on, but U-Boot left them disabled at
their minimum voltage on cold boot, so the PHY had no analog power and
never asserted HPD.
The driver did call rk_hdmi_probe_regulators() with "avdd-0v9"/"avdd-1v8",
but that resolves regulators by their regulator-name via
regulator_autoset_by_name(), which does not match the vdda0v9_image /
vcca1v8_image names, so the lookup failed and the supplies were never
enabled. regulator_autoset() would not have helped either: it returns
early for regulators that are neither always-on nor boot-on.
Resolve the supplies through the avdd-0v9-supply / avdd-1v8-supply
phandles, set the constrained voltage and enable them, matching the
kernel's dw_hdmi-rockchip behaviour.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/video/rockchip/rk3568_hdmi.c | 29 +++++++++++++++++++++++-----
1 file changed, 24 insertions(+), 5 deletions(-)
diff --git a/drivers/video/rockchip/rk3568_hdmi.c b/drivers/video/rockchip/rk3568_hdmi.c
index 9b3e54ce6fd..4fd164cd40b 100644
--- a/drivers/video/rockchip/rk3568_hdmi.c
+++ b/drivers/video/rockchip/rk3568_hdmi.c
@@ -12,6 +12,7 @@
#include <asm/arch-rockchip/cru_rk3568.h>
#include <asm/arch-rockchip/hardware.h>
#include <asm/arch-rockchip/grf_rk3568.h>
+#include <power/regulator.h>
#include "rk_hdmi.h"
/* Default HDMI PHY reference rate to bring HPLL up for hot-plug detect */
@@ -47,11 +48,30 @@ static int rk3568_hdmi_of_to_plat(struct udevice *dev)
return rk_hdmi_of_to_plat(dev);
}
-static const char * const rk3568_hdmi_reg_names[] = {
- "avdd-0v9",
- "avdd-1v8"
+static const char * const rk3568_hdmi_supplies[] = {
+ "avdd-0v9-supply",
+ "avdd-1v8-supply",
};
+static void rk3568_hdmi_enable_supplies(struct udevice *dev)
+{
+ struct dm_regulator_uclass_plat *plat;
+ struct udevice *reg;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rk3568_hdmi_supplies); i++) {
+ if (device_get_supply_regulator(dev, rk3568_hdmi_supplies[i],
+ ®))
+ continue;
+
+ plat = dev_get_uclass_plat(reg);
+ if (plat->min_uV)
+ regulator_set_value(reg, plat->min_uV);
+
+ regulator_set_enable(reg, true);
+ }
+}
+
static int rk3568_hdmi_probe(struct udevice *dev)
{
struct rk_hdmi_priv *priv = dev_get_priv(dev);
@@ -59,8 +79,7 @@ static int rk3568_hdmi_probe(struct udevice *dev)
struct rk3568_cru *cru = rockchip_get_cru();
struct clk ref_clk;
- rk_hdmi_probe_regulators(dev, rk3568_hdmi_reg_names,
- ARRAY_SIZE(rk3568_hdmi_reg_names));
+ rk3568_hdmi_enable_supplies(dev);
/*
* Ungate the HDMI controller clocks. The RK3568 clock driver does
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 9/9] configs: nanopi-r5c: enable HDMI video output
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
` (7 preceding siblings ...)
2026-07-19 2:00 ` [PATCH 8/9] video: rockchip: rk3568: power the HDMI PHY analog supplies Daniel Golle
@ 2026-07-19 2:00 ` Daniel Golle
8 siblings, 0 replies; 10+ messages in thread
From: Daniel Golle @ 2026-07-19 2:00 UTC (permalink / raw)
To: Simon Glass, Philipp Tomsich, Kever Yang, Tom Rini,
Ilias Apalodimas, Tianling Shen, Jonas Karlman, Lukasz Majewski,
Anatolij Gustschin, Dang Huynh, Daniel Golle, Peng Fan,
Patrice Chotard, Yao Zi, u-boot
Enable the VOP2 display controller and RK3568 DesignWare HDMI so U-Boot
drives the HDMI port: vidconsole for the boot menu on a monitor, and
EFI GOP (which registers automatically once a UCLASS_VIDEO device
exists) for graphical UEFI payloads. Uses the in-tree dw_hdmi driver;
no separate PHY driver is needed on RK3568.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
configs/nanopi-r5c-rk3568_defconfig | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/configs/nanopi-r5c-rk3568_defconfig b/configs/nanopi-r5c-rk3568_defconfig
index e75a24dadc8..a89ade9829b 100644
--- a/configs/nanopi-r5c-rk3568_defconfig
+++ b/configs/nanopi-r5c-rk3568_defconfig
@@ -68,4 +68,8 @@ CONFIG_USB_OHCI_HCD=y
CONFIG_USB_OHCI_GENERIC=y
CONFIG_USB_DWC3=y
CONFIG_USB_DWC3_GENERIC=y
+CONFIG_VIDEO=y
+CONFIG_DISPLAY=y
+CONFIG_VIDEO_ROCKCHIP=y
+CONFIG_DISPLAY_ROCKCHIP_HDMI=y
CONFIG_ERRNO_STR=y
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-19 2:03 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-19 1:58 [PATCH 0/9] video: rockchip: VOP2 and HDMI output for RK3568 Daniel Golle
2026-07-19 1:58 ` [PATCH 1/9] video: rockchip: Add VOP2 support Daniel Golle
2026-07-19 1:58 ` [PATCH 2/9] arm: dts: rockchip: rk356x: Prerelocate VOP in U-Boot proper Daniel Golle
2026-07-19 1:59 ` [PATCH 3/9] video: rockchip: Add HDMI support for RK3568 Daniel Golle
2026-07-19 1:59 ` [PATCH 4/9] clk: rockchip: rk3568: Use assigned VPLL clock when possible Daniel Golle
2026-07-19 1:59 ` [PATCH 5/9] video: rockchip: vop2: fix NULL register base in rk3568 probe Daniel Golle
2026-07-19 1:59 ` [PATCH 6/9] video: rockchip: rk3568: ungate the HDMI controller clocks Daniel Golle
2026-07-19 2:00 ` [PATCH 7/9] video: rockchip: rk3568: bring up the HDMI PHY reference clock Daniel Golle
2026-07-19 2:00 ` [PATCH 8/9] video: rockchip: rk3568: power the HDMI PHY analog supplies Daniel Golle
2026-07-19 2:00 ` [PATCH 9/9] configs: nanopi-r5c: enable HDMI video output Daniel Golle
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.