Merge tag 'video-20240421' of https://source.denx.de/u-boot/custodians/u-boot-video

CI: https://source.denx.de/u-boot/custodians/u-boot-video/-/pipelines/20466

- simple_panel: support timing parsing from EDID
- dw_hdmi: fix gcc-14 compiler warnings
- dw_hdmi: support vendor PHY for HDMI
- rockchip: add Rockchip INNO HDMI PHY driver
- rockchip: RK3328 HDMI and VOP support
- evb-rk3328: enable vidconsole support
- Tegra DC and DSI improvements and Tegra 114 support
- add LG LG070WX3 MIPI DSI panel driver
- add Samsung LTL106HL02 MIPI DSI panel driver
- add Toshiba TC358768 RGB to DSI bridge support
- add basic support for the Parade DP501 transmitter
- Tegra 3 panel and bridge driver improvements
- simplefb: modernise DT parsing
- fdt_simplefb: Enumerate framebuffer info from video handoff
- preserve framebuffer if SPL is passing video hand-off
- fdt_support: allow reserving FB region without simplefb
diff --git a/arch/arm/dts/rk3328-u-boot.dtsi b/arch/arm/dts/rk3328-u-boot.dtsi
index e0c6aee..7c5067c 100644
--- a/arch/arm/dts/rk3328-u-boot.dtsi
+++ b/arch/arm/dts/rk3328-u-boot.dtsi
@@ -130,6 +130,10 @@
 	bootph-all;
 };
 
+&vop {
+	bootph-all;
+};
+
 #ifdef CONFIG_ROCKCHIP_SPI_IMAGE
 &binman {
 	simple-bin-spi {
diff --git a/arch/arm/dts/tegra114-u-boot.dtsi b/arch/arm/dts/tegra114-u-boot.dtsi
index 7c11972..6a02714 100644
--- a/arch/arm/dts/tegra114-u-boot.dtsi
+++ b/arch/arm/dts/tegra114-u-boot.dtsi
@@ -1,3 +1,16 @@
 #include <config.h>
 
 #include "tegra-u-boot.dtsi"
+
+/ {
+	host1x@50000000 {
+		bootph-all;
+		dc@54200000 {
+			bootph-all;
+		};
+
+		dc@54240000 {
+			bootph-all;
+		};
+	};
+};
diff --git a/arch/arm/dts/tegra114.dtsi b/arch/arm/dts/tegra114.dtsi
index 68ee7f3..250d692 100644
--- a/arch/arm/dts/tegra114.dtsi
+++ b/arch/arm/dts/tegra114.dtsi
@@ -42,7 +42,7 @@
 		};
 
 		dc@54200000 {
-			compatible = "nvidia,tegra114-dc", "nvidia,tegra20-dc";
+			compatible = "nvidia,tegra114-dc";
 			reg = <0x54200000 0x00040000>;
 			interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
 			clocks = <&tegra_car TEGRA114_CLK_DISP1>,
@@ -61,7 +61,7 @@
 		};
 
 		dc@54240000 {
-			compatible = "nvidia,tegra114-dc", "nvidia,tegra20-dc";
+			compatible = "nvidia,tegra114-dc";
 			reg = <0x54240000 0x00040000>;
 			interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
 			clocks = <&tegra_car TEGRA114_CLK_DISP2>,
diff --git a/arch/arm/dts/tegra30-u-boot.dtsi b/arch/arm/dts/tegra30-u-boot.dtsi
index 3038227..6a02714 100644
--- a/arch/arm/dts/tegra30-u-boot.dtsi
+++ b/arch/arm/dts/tegra30-u-boot.dtsi
@@ -8,5 +8,9 @@
 		dc@54200000 {
 			bootph-all;
 		};
+
+		dc@54240000 {
+			bootph-all;
+		};
 	};
 };
diff --git a/arch/arm/dts/tegra30.dtsi b/arch/arm/dts/tegra30.dtsi
index f198bc0..1177e2a 100644
--- a/arch/arm/dts/tegra30.dtsi
+++ b/arch/arm/dts/tegra30.dtsi
@@ -158,7 +158,7 @@
 		};
 
 		dc@54200000 {
-			compatible = "nvidia,tegra30-dc", "nvidia,tegra20-dc";
+			compatible = "nvidia,tegra30-dc";
 			reg = <0x54200000 0x00040000>;
 			interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
 			clocks = <&tegra_car TEGRA30_CLK_DISP1>,
diff --git a/arch/arm/include/asm/arch-rockchip/cru_rk3328.h b/arch/arm/include/asm/arch-rockchip/cru_rk3328.h
index 226744d..4ad1d33 100644
--- a/arch/arm/include/asm/arch-rockchip/cru_rk3328.h
+++ b/arch/arm/include/asm/arch-rockchip/cru_rk3328.h
@@ -62,6 +62,40 @@
 enum apll_frequencies {
 	APLL_816_MHZ,
 	APLL_600_MHZ,
+
+	/* CRU_CLK_SEL37_CON */
+	ACLK_VIO_PLL_SEL_CPLL		= 0,
+	ACLK_VIO_PLL_SEL_GPLL		= 1,
+	ACLK_VIO_PLL_SEL_HDMIPHY	= 2,
+	ACLK_VIO_PLL_SEL_USB480M	= 3,
+	ACLK_VIO_PLL_SEL_SHIFT		= 6,
+	ACLK_VIO_PLL_SEL_MASK		= 3 << ACLK_VIO_PLL_SEL_SHIFT,
+	ACLK_VIO_DIV_CON_SHIFT		= 0,
+	ACLK_VIO_DIV_CON_MASK		= 0x1f << ACLK_VIO_DIV_CON_SHIFT,
+	HCLK_VIO_DIV_CON_SHIFT		= 8,
+	HCLK_VIO_DIV_CON_MASK		= 0x1f << HCLK_VIO_DIV_CON_SHIFT,
+
+	/* CRU_CLK_SEL39_CON */
+	ACLK_VOP_PLL_SEL_CPLL		= 0,
+	ACLK_VOP_PLL_SEL_GPLL		= 1,
+	ACLK_VOP_PLL_SEL_HDMIPHY	= 2,
+	ACLK_VOP_PLL_SEL_USB480M	= 3,
+	ACLK_VOP_PLL_SEL_SHIFT		= 6,
+	ACLK_VOP_PLL_SEL_MASK		= 3 << ACLK_VOP_PLL_SEL_SHIFT,
+	ACLK_VOP_DIV_CON_SHIFT		= 0,
+	ACLK_VOP_DIV_CON_MASK		= 0x1f << ACLK_VOP_DIV_CON_SHIFT,
+
+	/* CRU_CLK_SEL40_CON */
+	DCLK_LCDC_PLL_SEL_GPLL		= 0,
+	DCLK_LCDC_PLL_SEL_CPLL		= 1,
+	DCLK_LCDC_PLL_SEL_SHIFT		= 0,
+	DCLK_LCDC_PLL_SEL_MASK		= 1 << DCLK_LCDC_PLL_SEL_SHIFT,
+	DCLK_LCDC_SEL_HDMIPHY		= 0,
+	DCLK_LCDC_SEL_PLL		= 1,
+	DCLK_LCDC_SEL_SHIFT		= 1,
+	DCLK_LCDC_SEL_MASK		= 1 << DCLK_LCDC_SEL_SHIFT,
+	DCLK_LCDC_DIV_CON_SHIFT		= 8,
+	DCLK_LCDC_DIV_CON_MASK		= 0xFf << DCLK_LCDC_DIV_CON_SHIFT,
 };
 
 void rk3328_configure_cpu(struct rk3328_cru *cru,
diff --git a/arch/arm/include/asm/arch-tegra/dc.h b/arch/arm/include/asm/arch-tegra/dc.h
index 7613d84..ca37184 100644
--- a/arch/arm/include/asm/arch-tegra/dc.h
+++ b/arch/arm/include/asm/arch-tegra/dc.h
@@ -443,6 +443,11 @@
 #define	WINDOW_D_SELECT		BIT(7)
 #define	WINDOW_H_SELECT		BIT(8)
 
+/* DC_COM_PIN_OUTPUT_POLARITY1 0x307 */
+#define LHS_OUTPUT_POLARITY_LOW		BIT(30)
+#define LVS_OUTPUT_POLARITY_LOW		BIT(28)
+#define LSC0_OUTPUT_POLARITY_LOW	BIT(24)
+
 /* DC_DISP_DISP_WIN_OPTIONS 0x402 */
 #define	CURSOR_ENABLE		BIT(16)
 #define	SOR_ENABLE		BIT(25)
@@ -569,12 +574,4 @@
 #define DC_N_WINDOWS			5
 #define DC_REG_SAVE_SPACE		(DC_N_WINDOWS + 5)
 
-#define TEGRA_DSI_A		"dsi@54300000"
-#define TEGRA_DSI_B		"dsi@54400000"
-
-struct tegra_dc_plat {
-	struct udevice *dev;		/* Display controller device */
-	struct dc_ctlr *dc;		/* Display controller regmap */
-};
-
 #endif /* __ASM_ARCH_TEGRA_DC_H */
diff --git a/arch/arm/include/asm/arch-tegra114/pwm.h b/arch/arm/include/asm/arch-tegra114/pwm.h
new file mode 100644
index 0000000..af39151
--- /dev/null
+++ b/arch/arm/include/asm/arch-tegra114/pwm.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Tegra pulse width frequency modulator definitions
+ *
+ * Copyright (c) 2011 The Chromium OS Authors.
+ */
+
+#ifndef __ASM_ARCH_TEGRA114_PWM_H
+#define __ASM_ARCH_TEGRA114_PWM_H
+
+#include <asm/arch-tegra/pwm.h>
+
+#endif	/* __ASM_ARCH_TEGRA114_PWM_H */
diff --git a/arch/arm/include/asm/arch-tegra20/display.h b/arch/arm/include/asm/arch-tegra20/display.h
deleted file mode 100644
index e7b3cff..0000000
--- a/arch/arm/include/asm/arch-tegra20/display.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- *  (C) Copyright 2010
- *  NVIDIA Corporation <www.nvidia.com>
- */
-
-#ifndef __ASM_ARCH_TEGRA_DISPLAY_H
-#define __ASM_ARCH_TEGRA_DISPLAY_H
-
-#include <asm/arch-tegra/dc.h>
-
-/* This holds information about a window which can be displayed */
-struct disp_ctl_win {
-	enum win_color_depth_id fmt;	/* Color depth/format */
-	unsigned	bpp;		/* Bits per pixel */
-	phys_addr_t	phys_addr;	/* Physical address in memory */
-	unsigned	x;		/* Horizontal address offset (bytes) */
-	unsigned	y;		/* Veritical address offset (bytes) */
-	unsigned	w;		/* Width of source window */
-	unsigned	h;		/* Height of source window */
-	unsigned	stride;		/* Number of bytes per line */
-	unsigned	out_x;		/* Left edge of output window (col) */
-	unsigned	out_y;		/* Top edge of output window (row) */
-	unsigned	out_w;		/* Width of output window in pixels */
-	unsigned	out_h;		/* Height of output window in pixels */
-};
-
-#endif /*__ASM_ARCH_TEGRA_DISPLAY_H*/
diff --git a/arch/arm/include/asm/arch-tegra30/display.h b/arch/arm/include/asm/arch-tegra30/display.h
deleted file mode 100644
index 9411525..0000000
--- a/arch/arm/include/asm/arch-tegra30/display.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- *  (C) Copyright 2010
- *  NVIDIA Corporation <www.nvidia.com>
- */
-
-#ifndef __ASM_ARCH_TEGRA_DISPLAY_H
-#define __ASM_ARCH_TEGRA_DISPLAY_H
-
-#include <asm/arch-tegra/dc.h>
-
-/* This holds information about a window which can be displayed */
-struct disp_ctl_win {
-	enum win_color_depth_id fmt;	/* Color depth/format */
-	unsigned int	bpp;		/* Bits per pixel */
-	phys_addr_t	phys_addr;	/* Physical address in memory */
-	unsigned int	x;		/* Horizontal address offset (bytes) */
-	unsigned int	y;		/* Veritical address offset (bytes) */
-	unsigned int	w;		/* Width of source window */
-	unsigned int	h;		/* Height of source window */
-	unsigned int	stride;		/* Number of bytes per line */
-	unsigned int	out_x;		/* Left edge of output window (col) */
-	unsigned int	out_y;		/* Top edge of output window (row) */
-	unsigned int	out_w;		/* Width of output window in pixels */
-	unsigned int	out_h;		/* Height of output window in pixels */
-};
-
-#endif /*__ASM_ARCH_TEGRA_DISPLAY_H*/
diff --git a/arch/arm/mach-rockchip/Kconfig b/arch/arm/mach-rockchip/Kconfig
index f68a0a4..4f22d9b 100644
--- a/arch/arm/mach-rockchip/Kconfig
+++ b/arch/arm/mach-rockchip/Kconfig
@@ -180,6 +180,7 @@
 	select SUPPORT_TPL
 	select TPL
 	select TPL_NEEDS_SEPARATE_STACK if TPL
+	imply PRE_CONSOLE_BUFFER
 	imply ROCKCHIP_COMMON_BOARD
 	imply ROCKCHIP_SDRAM_COMMON
 	imply SPL_ROCKCHIP_COMMON_BOARD
diff --git a/boot/fdt_simplefb.c b/boot/fdt_simplefb.c
index 069ced7..837920b 100644
--- a/boot/fdt_simplefb.c
+++ b/boot/fdt_simplefb.c
@@ -12,6 +12,8 @@
 #include <asm/global_data.h>
 #include <linux/libfdt.h>
 #include <video.h>
+#include <spl.h>
+#include <bloblist.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
@@ -26,15 +28,29 @@
 	struct udevice *dev;
 	int ret;
 
-	ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
-	if (ret)
-		return ret;
-	uc_priv = dev_get_uclass_priv(dev);
-	plat = dev_get_uclass_plat(dev);
-	xsize = uc_priv->xsize;
-	ysize = uc_priv->ysize;
-	bpix = uc_priv->bpix;
-	fb_base = plat->base;
+	if (IS_ENABLED(CONFIG_SPL_VIDEO_HANDOFF) && spl_phase() > PHASE_SPL) {
+		struct video_handoff *ho;
+
+		ho = bloblist_find(BLOBLISTT_U_BOOT_VIDEO, sizeof(*ho));
+		if (!ho)
+			return log_msg_ret("Missing video bloblist", -ENOENT);
+
+		xsize = ho->xsize;
+		ysize = ho->ysize;
+		bpix = ho->bpix;
+		fb_base = ho->fb;
+	} else {
+		ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
+		if (ret)
+			return ret;
+		uc_priv = dev_get_uclass_priv(dev);
+		plat = dev_get_uclass_plat(dev);
+		xsize = uc_priv->xsize;
+		ysize = uc_priv->ysize;
+		bpix = uc_priv->bpix;
+		fb_base = plat->base;
+	}
+
 	switch (bpix) {
 	case 4: /* VIDEO_BPP16 */
 		name = "r5g6b5";
@@ -91,7 +107,6 @@
 #if IS_ENABLED(CONFIG_VIDEO)
 int fdt_simplefb_enable_and_mem_rsv(void *blob)
 {
-	struct fdt_memory mem;
 	int ret;
 
 	/* nothing to do when video is not active */
@@ -102,15 +117,6 @@
 	if (ret)
 		return ret;
 
-	/* nothing to do when the frame buffer is not defined */
-	if (gd->video_bottom == gd->video_top)
-		return 0;
-
-	/* reserved with no-map tag the video buffer */
-	mem.start = gd->video_bottom;
-	mem.end = gd->video_top - 1;
-
-	return fdtdec_add_reserved_memory(blob, "framebuffer", &mem, NULL, 0, NULL,
-					  FDTDEC_RESERVED_MEMORY_NO_MAP);
+	return fdt_add_fb_mem_rsv(blob);
 }
 #endif
diff --git a/boot/fdt_support.c b/boot/fdt_support.c
index 9844c70..2bd80a9 100644
--- a/boot/fdt_support.c
+++ b/boot/fdt_support.c
@@ -23,6 +23,9 @@
 #include <exports.h>
 #include <fdtdec.h>
 #include <version.h>
+#include <video.h>
+
+DECLARE_GLOBAL_DATA_PTR;
 
 /**
  * fdt_getprop_u32_default_node - Return a node's property or a default
@@ -2043,6 +2046,24 @@
 	return 0;
 }
 
+#if CONFIG_IS_ENABLED(VIDEO)
+int fdt_add_fb_mem_rsv(void *blob)
+{
+	struct fdt_memory mem;
+
+	/* nothing to do when the frame buffer is not defined */
+	if (gd->video_bottom == gd->video_top)
+		return 0;
+
+	/* reserved with no-map tag the video buffer */
+	mem.start = gd->video_bottom;
+	mem.end = gd->video_top - 1;
+
+	return fdtdec_add_reserved_memory(blob, "framebuffer", &mem, NULL, 0, NULL,
+					  FDTDEC_RESERVED_MEMORY_NO_MAP);
+}
+#endif
+
 /*
  * Update native-mode in display-timings from display environment variable.
  * The node to update are specified by path.
diff --git a/common/Kconfig b/common/Kconfig
index 0283701..5e3070e 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -231,7 +231,7 @@
 	default 0x2f000000 if ARCH_SUNXI && MACH_SUN9I
 	default 0x4f000000 if ARCH_SUNXI && !MACH_SUN9I
 	default 0x0f000000 if ROCKCHIP_RK3288
-	default 0x0f200000 if ROCKCHIP_RK3399
+	default 0x0f200000 if ROCKCHIP_RK3399 || ROCKCHIP_RK3328
 	help
 	  This sets the start address of the pre-console buffer. This must
 	  be in available memory and is accessed before relocation and
diff --git a/configs/roc-cc-rk3328_defconfig b/configs/roc-cc-rk3328_defconfig
index 6526d26..1dbd39e 100644
--- a/configs/roc-cc-rk3328_defconfig
+++ b/configs/roc-cc-rk3328_defconfig
@@ -62,6 +62,7 @@
 CONFIG_PHY_GIGE=y
 CONFIG_ETH_DESIGNWARE=y
 CONFIG_GMAC_ROCKCHIP=y
+CONFIG_PHY_ROCKCHIP_INNO_HDMI=y
 CONFIG_PHY_ROCKCHIP_INNO_USB2=y
 CONFIG_PINCTRL=y
 CONFIG_SPL_PINCTRL=y
@@ -94,6 +95,10 @@
 CONFIG_USB_DWC2=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_SPL_TINY_MEMSET=y
 CONFIG_TPL_TINY_MEMSET=y
 CONFIG_ERRNO_STR=y
diff --git a/drivers/clk/rockchip/clk_rk3328.c b/drivers/clk/rockchip/clk_rk3328.c
index cfec1d9..87075ec 100644
--- a/drivers/clk/rockchip/clk_rk3328.c
+++ b/drivers/clk/rockchip/clk_rk3328.c
@@ -178,6 +178,10 @@
 	CLK_I2C3_DIV_CON_SHIFT		= 8,
 	CLK_I2C2_PLL_SEL_SHIFT		= 7,
 	CLK_I2C2_DIV_CON_SHIFT		= 0,
+
+	/* CLKSEL_CON40 */
+	CLK_HDMIPHY_DIV_CON_SHIFT	= 3,
+	CLK_HDMIPHY_DIV_CON_MASK	= 0x7 << CLK_HDMIPHY_DIV_CON_SHIFT,
 };
 
 #define VCO_MAX_KHZ	(3200 * (MHz / KHz))
@@ -580,6 +584,96 @@
 	return rk3328_spi_get_clk(cru);
 }
 
+#ifndef CONFIG_SPL_BUILD
+static ulong rk3328_vop_get_clk(struct rk3328_clk_priv *priv, ulong clk_id)
+{
+	struct rk3328_cru *cru = priv->cru;
+	u32 div, con, parent;
+
+	switch (clk_id) {
+	case ACLK_VOP_PRE:
+		con = readl(&cru->clksel_con[39]);
+		div = (con & ACLK_VOP_DIV_CON_MASK) >> ACLK_VOP_DIV_CON_SHIFT;
+		parent = GPLL_HZ;
+		break;
+	case ACLK_VIO_PRE:
+		con = readl(&cru->clksel_con[37]);
+		div = (con & ACLK_VIO_DIV_CON_MASK) >> ACLK_VIO_DIV_CON_SHIFT;
+		parent = GPLL_HZ;
+		break;
+	case DCLK_LCDC:
+		con = readl(&cru->clksel_con[40]);
+		div = (con & DCLK_LCDC_DIV_CON_MASK) >> DCLK_LCDC_DIV_CON_SHIFT;
+		parent = GPLL_HZ;
+		break;
+	default:
+		printf("%s: Unsupported vop get clk#%ld\n", __func__, clk_id);
+		return -ENOENT;
+	}
+
+	return DIV_TO_RATE(parent, div);
+}
+
+static ulong rk3328_vop_set_clk(struct rk3328_clk_priv *priv,
+				ulong clk_id, uint hz)
+{
+	struct rk3328_cru *cru = priv->cru;
+	int src_clk_div;
+	u32 con, parent;
+
+	src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz);
+	assert(src_clk_div - 1 < 31);
+
+	switch (clk_id) {
+	case ACLK_VOP_PRE:
+		rk_clrsetreg(&cru->clksel_con[39],
+			     ACLK_VOP_PLL_SEL_MASK | ACLK_VOP_DIV_CON_MASK,
+			     ACLK_VOP_PLL_SEL_CPLL << ACLK_VOP_PLL_SEL_SHIFT |
+			     (src_clk_div - 1) << ACLK_VOP_DIV_CON_SHIFT);
+		break;
+	case ACLK_VIO_PRE:
+		rk_clrsetreg(&cru->clksel_con[37],
+			     ACLK_VIO_PLL_SEL_MASK | ACLK_VIO_DIV_CON_MASK,
+			     ACLK_VIO_PLL_SEL_CPLL << ACLK_VIO_PLL_SEL_SHIFT |
+			     (src_clk_div - 1) << ACLK_VIO_DIV_CON_SHIFT);
+		break;
+	case DCLK_LCDC:
+		con = readl(&cru->clksel_con[40]);
+		con = (con & DCLK_LCDC_SEL_MASK) >> DCLK_LCDC_SEL_SHIFT;
+		if (con) {
+			parent = readl(&cru->clksel_con[40]);
+			parent = (parent & DCLK_LCDC_PLL_SEL_MASK) >>
+				 DCLK_LCDC_PLL_SEL_SHIFT;
+			if (parent)
+				src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz);
+			else
+				src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz);
+
+			rk_clrsetreg(&cru->clksel_con[40],
+				     DCLK_LCDC_DIV_CON_MASK,
+				     (src_clk_div - 1) <<
+				     DCLK_LCDC_DIV_CON_SHIFT);
+		}
+		break;
+	default:
+		printf("%s: Unable to set vop clk#%ld\n", __func__, clk_id);
+		return -EINVAL;
+	}
+
+	return rk3328_vop_get_clk(priv, clk_id);
+}
+#endif
+
+static ulong rk3328_hdmiphy_get_clk(struct rk3328_cru *cru)
+{
+	u32 div, con;
+
+	con = readl(&cru->clksel_con[40]);
+	div = (con & CLK_HDMIPHY_DIV_CON_MASK) >> CLK_HDMIPHY_DIV_CON_SHIFT;
+
+	return DIV_TO_RATE(GPLL_HZ, div);
+}
+
 static ulong rk3328_clk_get_rate(struct clk *clk)
 {
 	struct rk3328_clk_priv *priv = dev_get_priv(clk->dev);
@@ -609,6 +703,9 @@
 	case SCLK_SPI:
 		rate = rk3328_spi_get_clk(priv->cru);
 		break;
+	case PCLK_HDMIPHY:
+		rate = rk3328_hdmiphy_get_clk(priv->cru);
+		break;
 	default:
 		return -ENOENT;
 	}
@@ -648,7 +745,13 @@
 	case SCLK_SPI:
 		ret = rk3328_spi_set_clk(priv->cru, rate);
 		break;
+#ifndef CONFIG_SPL_BUILD
 	case DCLK_LCDC:
+	case ACLK_VOP_PRE:
+	case ACLK_VIO_PRE:
+		rate = rk3328_vop_set_clk(priv, clk->id, rate);
+		break;
+#endif
 	case SCLK_PDM:
 	case SCLK_RTC32K:
 	case SCLK_UART0:
@@ -663,11 +766,9 @@
 	case ACLK_PERI_PRE:
 	case HCLK_PERI:
 	case PCLK_PERI:
-	case ACLK_VIO_PRE:
 	case HCLK_VIO_PRE:
 	case ACLK_RGA_PRE:
 	case SCLK_RGA:
-	case ACLK_VOP_PRE:
 	case ACLK_RKVDEC_PRE:
 	case ACLK_RKVENC:
 	case ACLK_VPU_PRE:
diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
index 0247d93..8012833 100644
--- a/drivers/phy/rockchip/Kconfig
+++ b/drivers/phy/rockchip/Kconfig
@@ -12,6 +12,13 @@
 	help
 	  Support for Rockchip MIPI DPHY with Innosilicon IP block.
 
+config PHY_ROCKCHIP_INNO_HDMI
+	bool "Rockchip INNO HDMI PHY Driver"
+	depends on ARCH_ROCKCHIP
+	select PHY
+	help
+	  Enable this to support the Rockchip Innosilicon HDMI PHY.
+
 config PHY_ROCKCHIP_INNO_USB2
 	bool "Rockchip INNO USB2PHY Driver"
 	depends on ARCH_ROCKCHIP
diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile
index 7fdbd10..0420017 100644
--- a/drivers/phy/rockchip/Makefile
+++ b/drivers/phy/rockchip/Makefile
@@ -3,6 +3,7 @@
 # Copyright (C) 2020 Amarula Solutions(India)
 #
 
+obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI)	+= phy-rockchip-inno-hdmi.o
 obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2)	+= phy-rockchip-inno-usb2.o
 obj-$(CONFIG_PHY_ROCKCHIP_NANENG_COMBOPHY) += phy-rockchip-naneng-combphy.o
 obj-$(CONFIG_PHY_ROCKCHIP_PCIE)		+= phy-rockchip-pcie.o
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
new file mode 100644
index 0000000..3bb1a25
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
@@ -0,0 +1,885 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Rockchip Innosilicon HDMI PHY
+ *
+ * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd.
+ * Copyright (c) 2017 Rockchip Electronics Co. Ltd.
+ */
+
+#include <clk-uclass.h>
+#include <dm.h>
+#include <div64.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <generic-phy.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+
+#define UPDATE(x, h, l)		(((x) << (l)) & GENMASK((h), (l)))
+
+/* REG: 0x01 */
+#define RK3328_BYPASS_RXSENSE_EN			BIT(2)
+#define RK3328_BYPASS_POWERON_EN			BIT(1)
+#define RK3328_BYPASS_PLLPD_EN				BIT(0)
+/* REG: 0x02 */
+#define RK3328_INT_POL_HIGH				BIT(7)
+#define RK3328_BYPASS_PDATA_EN				BIT(4)
+#define RK3328_PDATA_EN					BIT(0)
+/* REG:0x05 */
+#define RK3328_INT_TMDS_CLK(x)				UPDATE(x, 7, 4)
+#define RK3328_INT_TMDS_D2(x)				UPDATE(x, 3, 0)
+/* REG:0x07 */
+#define RK3328_INT_TMDS_D1(x)				UPDATE(x, 7, 4)
+#define RK3328_INT_TMDS_D0(x)				UPDATE(x, 3, 0)
+/* for all RK3328_INT_TMDS_*, ESD_DET as defined in 0xc8-0xcb */
+#define RK3328_INT_AGND_LOW_PULSE_LOCKED		BIT(3)
+#define RK3328_INT_RXSENSE_LOW_PULSE_LOCKED		BIT(2)
+#define RK3328_INT_VSS_AGND_ESD_DET			BIT(1)
+#define RK3328_INT_AGND_VSS_ESD_DET			BIT(0)
+/* REG: 0xa0 */
+#define RK3328_PCLK_VCO_DIV_5_MASK			BIT(1)
+#define RK3328_PCLK_VCO_DIV_5(x)			UPDATE(x, 1, 1)
+#define RK3328_PRE_PLL_POWER_DOWN			BIT(0)
+/* REG: 0xa1 */
+#define RK3328_PRE_PLL_PRE_DIV_MASK			GENMASK(5, 0)
+#define RK3328_PRE_PLL_PRE_DIV(x)			UPDATE(x, 5, 0)
+/* REG: 0xa2 */
+/* unset means center spread */
+#define RK3328_SPREAD_SPECTRUM_MOD_DOWN			BIT(7)
+#define RK3328_SPREAD_SPECTRUM_MOD_DISABLE		BIT(6)
+#define RK3328_PRE_PLL_FRAC_DIV_DISABLE			UPDATE(3, 5, 4)
+#define RK3328_PRE_PLL_FB_DIV_11_8_MASK			GENMASK(3, 0)
+#define RK3328_PRE_PLL_FB_DIV_11_8(x)			UPDATE((x) >> 8, 3, 0)
+/* REG: 0xa3 */
+#define RK3328_PRE_PLL_FB_DIV_7_0(x)			UPDATE(x, 7, 0)
+/* REG: 0xa4*/
+#define RK3328_PRE_PLL_TMDSCLK_DIV_C_MASK		GENMASK(1, 0)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_C(x)			UPDATE(x, 1, 0)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_B_MASK		GENMASK(3, 2)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_B(x)			UPDATE(x, 3, 2)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_A_MASK		GENMASK(5, 4)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_A(x)			UPDATE(x, 5, 4)
+/* REG: 0xa5 */
+#define RK3328_PRE_PLL_PCLK_DIV_B_SHIFT			5
+#define RK3328_PRE_PLL_PCLK_DIV_B_MASK			GENMASK(6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_B(x)			UPDATE(x, 6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_A_MASK			GENMASK(4, 0)
+#define RK3328_PRE_PLL_PCLK_DIV_A(x)			UPDATE(x, 4, 0)
+/* REG: 0xa6 */
+#define RK3328_PRE_PLL_PCLK_DIV_C_SHIFT			5
+#define RK3328_PRE_PLL_PCLK_DIV_C_MASK			GENMASK(6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_C(x)			UPDATE(x, 6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_D_MASK			GENMASK(4, 0)
+#define RK3328_PRE_PLL_PCLK_DIV_D(x)			UPDATE(x, 4, 0)
+/* REG: 0xa9 */
+#define RK3328_PRE_PLL_LOCK_STATUS			BIT(0)
+/* REG: 0xaa */
+#define RK3328_POST_PLL_POST_DIV_ENABLE			GENMASK(3, 2)
+#define RK3328_POST_PLL_REFCLK_SEL_TMDS			BIT(1)
+#define RK3328_POST_PLL_POWER_DOWN			BIT(0)
+/* REG:0xab */
+#define RK3328_POST_PLL_FB_DIV_8(x)			UPDATE((x) >> 8, 7, 7)
+#define RK3328_POST_PLL_PRE_DIV(x)			UPDATE(x, 4, 0)
+/* REG: 0xac */
+#define RK3328_POST_PLL_FB_DIV_7_0(x)			UPDATE(x, 7, 0)
+/* REG: 0xad */
+#define RK3328_POST_PLL_POST_DIV_MASK			GENMASK(1, 0)
+#define RK3328_POST_PLL_POST_DIV_2			0x0
+#define RK3328_POST_PLL_POST_DIV_4			0x1
+#define RK3328_POST_PLL_POST_DIV_8			0x3
+/* REG: 0xaf */
+#define RK3328_POST_PLL_LOCK_STATUS			BIT(0)
+/* REG: 0xb0 */
+#define RK3328_BANDGAP_ENABLE				BIT(2)
+/* REG: 0xb2 */
+#define RK3328_TMDS_CLK_DRIVER_EN			BIT(3)
+#define RK3328_TMDS_D2_DRIVER_EN			BIT(2)
+#define RK3328_TMDS_D1_DRIVER_EN			BIT(1)
+#define RK3328_TMDS_D0_DRIVER_EN			BIT(0)
+#define RK3328_TMDS_DRIVER_ENABLE		(RK3328_TMDS_CLK_DRIVER_EN | \
+						RK3328_TMDS_D2_DRIVER_EN | \
+						RK3328_TMDS_D1_DRIVER_EN | \
+						RK3328_TMDS_D0_DRIVER_EN)
+/* REG:0xc5 */
+#define RK3328_BYPASS_TERM_RESISTOR_CALIB		BIT(7)
+#define RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(x)	UPDATE((x) >> 8, 6, 0)
+/* REG:0xc6 */
+#define RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(x)		UPDATE(x, 7, 0)
+/* REG:0xc7 */
+#define RK3328_TERM_RESISTOR_50				UPDATE(0, 2, 1)
+#define RK3328_TERM_RESISTOR_62_5			UPDATE(1, 2, 1)
+#define RK3328_TERM_RESISTOR_75				UPDATE(2, 2, 1)
+#define RK3328_TERM_RESISTOR_100			UPDATE(3, 2, 1)
+/* REG 0xc8 - 0xcb */
+#define RK3328_ESD_DETECT_MASK				GENMASK(7, 6)
+#define RK3328_ESD_DETECT_340MV				(0x0 << 6)
+#define RK3328_ESD_DETECT_280MV				(0x1 << 6)
+#define RK3328_ESD_DETECT_260MV				(0x2 << 6)
+#define RK3328_ESD_DETECT_240MV				(0x3 << 6)
+/* resistors can be used in parallel */
+#define RK3328_TMDS_TERM_RESIST_MASK			GENMASK(5, 0)
+#define RK3328_TMDS_TERM_RESIST_75			BIT(5)
+#define RK3328_TMDS_TERM_RESIST_150			BIT(4)
+#define RK3328_TMDS_TERM_RESIST_300			BIT(3)
+#define RK3328_TMDS_TERM_RESIST_600			BIT(2)
+#define RK3328_TMDS_TERM_RESIST_1000			BIT(1)
+#define RK3328_TMDS_TERM_RESIST_2000			BIT(0)
+/* REG: 0xd1 */
+#define RK3328_PRE_PLL_FRAC_DIV_23_16(x)		UPDATE((x) >> 16, 7, 0)
+/* REG: 0xd2 */
+#define RK3328_PRE_PLL_FRAC_DIV_15_8(x)			UPDATE((x) >> 8, 7, 0)
+/* REG: 0xd3 */
+#define RK3328_PRE_PLL_FRAC_DIV_7_0(x)			UPDATE(x, 7, 0)
+
+struct phy_config {
+	unsigned long	tmdsclock;
+	u8		regs[14];
+};
+
+struct pre_pll_config {
+	unsigned long pixclock;
+	unsigned long tmdsclock;
+	u8 prediv;
+	u16 fbdiv;
+	u8 tmds_div_a;
+	u8 tmds_div_b;
+	u8 tmds_div_c;
+	u8 pclk_div_a;
+	u8 pclk_div_b;
+	u8 pclk_div_c;
+	u8 pclk_div_d;
+	u8 vco_div_5_en;
+	u32 fracdiv;
+};
+
+struct post_pll_config {
+	unsigned long tmdsclock;
+	u8 prediv;
+	u16 fbdiv;
+	u8 postdiv;
+	u8 version;
+};
+
+struct inno_hdmi_phy_plat_ops {
+	void (*init)(struct phy *phy);
+	int (*power_on)(struct phy *phy, const struct post_pll_config *cfg,
+			const struct phy_config *phy_cfg);
+	void (*power_off)(struct phy *phy);
+	void (*clk_enable)(struct phy *phy);
+	void (*clk_disable)(struct phy *phy);
+	unsigned long (*clk_recalc_rate)(struct phy *phy,
+					 unsigned long parent_rate);
+	long (*clk_round_rate)(struct phy *phy, unsigned long rate);
+	int (*clk_set_rate)(struct phy *phy, unsigned long rate,
+			    unsigned long parent_rate);
+};
+
+enum inno_hdmi_phy_type {
+	INNO_HDMI_PHY_RK3328,
+};
+
+struct inno_hdmi_phy_data {
+	enum inno_hdmi_phy_type	phy_type;
+	const struct inno_hdmi_phy_plat_ops *plat_ops;
+	const struct phy_config	*phy_cfg_table;
+};
+
+struct inno_hdmi_phy {
+	struct udevice *dev;
+	ofnode node;
+	void *regs;
+
+	struct clk refoclk;
+	struct clk sysclk;
+	unsigned long tmdsclock;
+	unsigned long pixclock;
+	u32 bus_width;
+	struct phy_config *phy_cfg;
+	const struct inno_hdmi_phy_data *data;
+};
+
+static const struct pre_pll_config pre_pll_cfg_table[] = {
+	{ 25175000,  25175000,  3,  125, 3, 1, 1,  1, 3, 3,  4, 0, 0xe00000},
+	{ 25175000,  31468750,  1,   41, 0, 3, 3,  1, 3, 3,  4, 0, 0xf5554f},
+	{ 27000000,  27000000,  1,   36, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 27000000,  33750000,  1,   45, 0, 3, 3,  1, 3, 3,  4, 0,      0x0},
+	{ 31500000,  31500000,  1,   42, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 31500000,  39375000,  1,  105, 1, 3, 3, 10, 0, 3,  4, 0,      0x0},
+	{ 33750000,  33750000,  1,   45, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 33750000,  42187500,  1,  169, 2, 3, 3, 15, 0, 3,  4, 0,      0x0},
+	{ 35500000,  35500000,  1,   71, 2, 2, 2,  6, 0, 3,  4, 0,      0x0},
+	{ 35500000,  44375000,  1,   74, 3, 1, 1, 25, 0, 1,  1, 0,      0x0},
+	{ 36000000,  36000000,  1,   36, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{ 36000000,  45000000,  1,   45, 2, 1, 1, 15, 0, 1,  1, 0,      0x0},
+	{ 40000000,  40000000,  1,   40, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{ 40000000,  50000000,  1,   50, 2, 1, 1, 15, 0, 1,  1, 0,      0x0},
+	{ 49500000,  49500000,  1,   66, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 49500000,  61875000,  1,  165, 1, 3, 3, 10, 0, 3,  4, 0,      0x0},
+	{ 50000000,  50000000,  1,   50, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{ 50000000,  62500000,  1,  125, 2, 2, 2, 15, 0, 2,  2, 0,      0x0},
+	{ 54000000,  54000000,  1,   36, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{ 54000000,  67500000,  1,   45, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{ 56250000,  56250000,  1,   75, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 56250000,  70312500,  1,  117, 3, 1, 1, 25, 0, 1,  1, 0,      0x0},
+	{ 59341000,  59341000,  1,  118, 2, 2, 2,  6, 0, 3,  4, 0, 0xae978d},
+	{ 59341000,  74176250,  2,  148, 2, 1, 1, 15, 0, 1,  1, 0, 0x5a3d70},
+	{ 59400000,  59400000,  1,   99, 3, 1, 1,  1, 3, 3,  4, 0,      0x0},
+	{ 59400000,  74250000,  1,   99, 0, 3, 3,  1, 3, 3,  4, 0,      0x0},
+	{ 65000000,  65000000,  1,   65, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{ 65000000,  81250000,  3,  325, 0, 3, 3,  1, 3, 3,  4, 0,      0x0},
+	{ 68250000,  68250000,  1,   91, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 68250000,  85312500,  1,  142, 3, 1, 1, 25, 0, 1,  1, 0,      0x0},
+	{ 71000000,  71000000,  1,   71, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{ 71000000,  88750000,  3,  355, 0, 3, 3,  1, 3, 3,  4, 0,      0x0},
+	{ 72000000,  72000000,  1,   36, 2, 0, 0,  1, 1, 2,  2, 0,      0x0},
+	{ 72000000,  90000000,  1,   60, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{ 73250000,  73250000,  3,  293, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 73250000,  91562500,  1,   61, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{ 74176000,  74176000,  1,   37, 2, 0, 0,  1, 1, 2,  2, 0, 0x16872b},
+	{ 74176000,  92720000,  2,  185, 2, 1, 1, 15, 0, 1,  1, 0, 0x70a3d7},
+	{ 74250000,  74250000,  1,   99, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 74250000,  92812500,  4,  495, 0, 3, 3,  1, 3, 3,  4, 0,      0x0},
+	{ 75000000,  75000000,  1,   50, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{ 75000000,  93750000,  1,  125, 0, 3, 3,  1, 3, 3,  4, 0,      0x0},
+	{ 78750000,  78750000,  1,  105, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 78750000,  98437500,  1,  164, 3, 1, 1, 25, 0, 1,  1, 0,      0x0},
+	{ 79500000,  79500000,  1,   53, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{ 79500000,  99375000,  1,  199, 2, 2, 2, 15, 0, 2,  2, 0,      0x0},
+	{ 83500000,  83500000,  2,  167, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{ 83500000, 104375000,  1,  104, 2, 1, 1, 15, 0, 1,  1, 0, 0x600000},
+	{ 85500000,  85500000,  1,   57, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{ 85500000, 106875000,  1,  178, 3, 1, 1, 25, 0, 1,  1, 0,      0x0},
+	{ 85750000,  85750000,  3,  343, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 85750000, 107187500,  1,  143, 0, 3, 3,  1, 3, 3,  4, 0,      0x0},
+	{ 88750000,  88750000,  3,  355, 0, 3, 3,  1, 2, 3,  4, 0,      0x0},
+	{ 88750000, 110937500,  1,  110, 2, 1, 1, 15, 0, 1,  1, 0, 0xf00000},
+	{ 94500000,  94500000,  1,   63, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{ 94500000, 118125000,  1,  197, 3, 1, 1, 25, 0, 1,  1, 0,      0x0},
+	{101000000, 101000000,  1,  101, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{101000000, 126250000,  1,   42, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{102250000, 102250000,  4,  409, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{102250000, 127812500,  1,  128, 2, 1, 1, 15, 0, 1,  1, 0,      0x0},
+	{106500000, 106500000,  1,   71, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{106500000, 133125000,  1,  133, 2, 1, 1, 15, 0, 1,  1, 0,      0x0},
+	{108000000, 108000000,  1,   36, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{108000000, 135000000,  1,   45, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{115500000, 115500000,  1,   77, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{115500000, 144375000,  1,   48, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{117500000, 117500000,  2,  235, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{117500000, 146875000,  1,   49, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{119000000, 119000000,  1,  119, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{119000000, 148750000,  3,  148, 0, 1, 1,  1, 3, 1,  1, 0, 0xc00000},
+	{121750000, 121750000,  4,  487, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{121750000, 152187500,  1,  203, 0, 3, 3,  1, 3, 3,  4, 0,      0x0},
+	{122500000, 122500000,  2,  245, 2, 1, 1,  1, 1, 3,  4, 0,      0x0},
+	{122500000, 153125000,  1,   51, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{135000000, 135000000,  1,   45, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{135000000, 168750000,  1,  169, 2, 1, 1, 15, 0, 1,  1, 0,      0x0},
+	{136750000, 136750000,  1,   68, 2, 0, 0,  1, 1, 2,  2, 0, 0x600000},
+	{136750000, 170937500,  1,  113, 0, 2, 2,  1, 3, 2,  2, 0, 0xf5554f},
+	{140250000, 140250000,  2,  187, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{140250000, 175312500,  1,  117, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{146250000, 146250000,  2,  195, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{146250000, 182812500,  1,   61, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{148250000, 148250000,  3,  222, 2, 0, 0,  1, 1, 2,  2, 0, 0x600000},
+	{148250000, 185312500,  1,  123, 0, 2, 2,  1, 3, 2,  2, 0, 0x8aaab0},
+	{148352000, 148352000,  2,  148, 2, 0, 0,  1, 1, 2,  2, 0, 0x5a1cac},
+	{148352000, 185440000,  3,  185, 0, 1, 1,  1, 3, 1,  1, 0, 0x70a3d7},
+	{148500000, 148500000,  1,   99, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{148500000, 185625000,  4,  495, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{154000000, 154000000,  1,   77, 2, 0, 0,  1, 1, 2,  2, 0,      0x0},
+	{154000000, 192500000,  1,   64, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{156000000, 156000000,  1,   52, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{156000000, 195000000,  1,   65, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{156750000, 156750000,  2,  209, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{156750000, 195937500,  1,  196, 2, 1, 1, 15, 0, 1,  1, 0,      0x0},
+	{157000000, 157000000,  2,  157, 2, 0, 0,  1, 1, 2,  2, 0,      0x0},
+	{157000000, 196250000,  1,  131, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{157500000, 157500000,  1,  105, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{157500000, 196875000,  1,  197, 2, 1, 1, 15, 0, 1,  1, 0,      0x0},
+	{162000000, 162000000,  1,   54, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{162000000, 202500000,  2,  135, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{175500000, 175500000,  1,  117, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{175500000, 219375000,  1,   73, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{179500000, 179500000,  3,  359, 0, 2, 2,  1, 0, 3,  4, 0,      0x0},
+	{179500000, 224375000,  1,   75, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{182750000, 182750000,  1,   91, 2, 0, 0,  1, 1, 2,  2, 0, 0x600000},
+	{182750000, 228437500,  1,  152, 0, 2, 2,  1, 3, 2,  2, 0, 0x4aaab0},
+	{182750000, 228437500,  1,  152, 0, 2, 2,  1, 3, 2,  2, 0, 0x4aaab0},
+	{187000000, 187000000,  2,  187, 2, 0, 0,  1, 1, 2,  2, 0,      0x0},
+	{187000000, 233750000,  1,   39, 0, 0, 0,  1, 3, 0,  0, 1,      0x0},
+	{187250000, 187250000,  3,  280, 2, 0, 0,  1, 1, 2,  2, 0, 0xe00000},
+	{187250000, 234062500,  1,  156, 0, 2, 2,  1, 3, 2,  2, 0,  0xaaab0},
+	{189000000, 189000000,  1,   63, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{189000000, 236250000,  1,   79, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{193250000, 193250000,  3,  289, 2, 0, 0,  1, 1, 2,  2, 0, 0xe00000},
+	{193250000, 241562500,  1,  161, 0, 2, 2,  1, 3, 2,  2, 0,  0xaaab0},
+	{202500000, 202500000,  2,  135, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{202500000, 253125000,  1,  169, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{204750000, 204750000,  4,  273, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{204750000, 255937500,  1,  171, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{208000000, 208000000,  1,  104, 2, 0, 0,  1, 1, 2,  2, 0,      0x0},
+	{208000000, 260000000,  1,  173, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{214750000, 214750000,  1,  107, 2, 0, 0,  1, 1, 2,  2, 0, 0x600000},
+	{214750000, 268437500,  1,  178, 0, 2, 2,  1, 3, 2,  2, 0, 0xf5554f},
+	{218250000, 218250000,  4,  291, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{218250000, 272812500,  1,   91, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{229500000, 229500000,  2,  153, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{229500000, 286875000,  1,  191, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{234000000, 234000000,  1,   39, 0, 0, 0,  1, 0, 1,  1, 0,      0x0},
+	{234000000, 292500000,  1,  195, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{241500000, 241500000,  2,  161, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{241500000, 301875000,  1,  201, 0, 2, 2,  1, 3, 2,  2, 0,      0x0},
+	{245250000, 245250000,  4,  327, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{245250000, 306562500,  1,   51, 0, 0, 0,  1, 3, 0,  0, 1,      0x0},
+	{245500000, 245500000,  4,  491, 2, 0, 0,  1, 1, 2,  2, 0,      0x0},
+	{245500000, 306875000,  1,   51, 0, 0, 0,  1, 3, 0,  0, 1,      0x0},
+	{261000000, 261000000,  1,   87, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{261000000, 326250000,  1,  109, 0, 1, 1,  1, 3, 1,  1, 0,      0x0},
+	{268250000, 268250000,  9,  402, 0, 0, 0,  1, 0, 1,  1, 0, 0x600000},
+	{268250000, 335312500,  1,  111, 0, 1, 1,  1, 3, 1,  1, 0, 0xc5554f},
+	{268500000, 268500000,  2,  179, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{268500000, 335625000,  1,   56, 0, 0, 0,  1, 3, 0,  0, 1,      0x0},
+	{281250000, 281250000,  4,  375, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{281250000, 351562500,  1,  117, 0, 3, 1,  1, 3, 1,  1, 0,      0x0},
+	{288000000, 288000000,  1,   48, 0, 0, 0,  1, 0, 1,  1, 0,      0x0},
+	{288000000, 360000000,  1,   60, 0, 2, 0,  1, 3, 0,  0, 1,      0x0},
+	{296703000, 296703000,  1,   49, 0, 0, 0,  1, 0, 1,  1, 0, 0x7353f7},
+	{296703000, 370878750,  1,  123, 0, 3, 1,  1, 3, 1,  1, 0, 0xa051eb},
+	{297000000, 297000000,  1,   99, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{297000000, 371250000,  4,  495, 0, 3, 1,  1, 3, 1,  1, 0,      0x0},
+	{312250000, 312250000,  9,  468, 0, 0, 0,  1, 0, 1,  1, 0, 0x600000},
+	{312250000, 390312500,  1,  130, 0, 3, 1,  1, 3, 1,  1, 0, 0x1aaab0},
+	{317000000, 317000000,  3,  317, 0, 1, 1,  1, 0, 2,  2, 0,      0x0},
+	{317000000, 396250000,  1,   66, 0, 2, 0,  1, 3, 0,  0, 1,      0x0},
+	{319750000, 319750000,  3,  159, 0, 0, 0,  1, 0, 1,  1, 0, 0xe00000},
+	{319750000, 399687500,  3,  199, 0, 2, 0,  1, 3, 0,  0, 1, 0xd80000},
+	{333250000, 333250000,  9,  499, 0, 0, 0,  1, 0, 1,  1, 0, 0xe00000},
+	{333250000, 416562500,  1,  138, 0, 3, 1,  1, 3, 1,  1, 0, 0xdaaab0},
+	{348500000, 348500000,  9,  522, 0, 2, 0,  1, 0, 1,  1, 0, 0xc00000},
+	{348500000, 435625000,  1,  145, 0, 3, 1,  1, 3, 1,  1, 0, 0x35554f},
+	{356500000, 356500000,  9,  534, 0, 2, 0,  1, 0, 1,  1, 0, 0xc00000},
+	{356500000, 445625000,  1,  148, 0, 3, 1,  1, 3, 1,  1, 0, 0x8aaab0},
+	{380500000, 380500000,  9,  570, 0, 2, 0,  1, 0, 1,  1, 0, 0xc00000},
+	{380500000, 475625000,  1,  158, 0, 3, 1,  1, 3, 1,  1, 0, 0x8aaab0},
+	{443250000, 443250000,  1,   73, 0, 2, 0,  1, 0, 1,  1, 0, 0xe00000},
+	{443250000, 554062500,  1,   92, 0, 2, 0,  1, 3, 0,  0, 1, 0x580000},
+	{505250000, 505250000,  9,  757, 0, 2, 0,  1, 0, 1,  1, 0, 0xe00000},
+	{552750000, 552750000,  3,  276, 0, 2, 0,  1, 0, 1,  1, 0, 0x600000},
+	{593407000, 296703500,  3,  296, 0, 1, 1,  1, 0, 1,  1, 0, 0xb41893},
+	{593407000, 370879375,  4,  494, 0, 3, 1,  1, 3, 0,  0, 1, 0x817e4a},
+	{593407000, 593407000,  3,  296, 0, 2, 0,  1, 0, 1,  1, 0, 0xb41893},
+	{594000000, 297000000,  1,   99, 0, 1, 1,  1, 0, 1,  1, 0,      0x0},
+	{594000000, 371250000,  4,  495, 0, 3, 1,  1, 3, 0,  0, 1,      0x0},
+	{594000000, 594000000,  1,   99, 0, 2, 0,  1, 0, 1,  1, 0,      0x0},
+	{ /* sentinel */ }
+};
+
+static const struct post_pll_config post_pll_cfg_table[] = {
+	{33750000,  1, 40, 8, 1},
+	{33750000,  1, 80, 8, 2},
+	{74250000,  1, 40, 8, 1},
+	{74250000, 18, 80, 8, 2},
+	{148500000, 2, 40, 4, 3},
+	{297000000, 4, 40, 2, 3},
+	{594000000, 8, 40, 1, 3},
+	{ /* sentinel */ }
+};
+
+/* phy tuning values for an undocumented set of registers */
+static const struct phy_config rk3328_phy_cfg[] = {
+	{	165000000, {
+			0x07, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x08, 0x08, 0x08,
+			0x00, 0xac, 0xcc, 0xcc, 0xcc,
+		},
+	}, {
+		340000000, {
+			0x0b, 0x0d, 0x0d, 0x0d, 0x07, 0x15, 0x08, 0x08, 0x08,
+			0x3f, 0xac, 0xcc, 0xcd, 0xdd,
+		},
+	}, {
+		594000000, {
+			0x10, 0x1a, 0x1a, 0x1a, 0x07, 0x15, 0x08, 0x08, 0x08,
+			0x00, 0xac, 0xcc, 0xcc, 0xcc,
+		},
+	}, { /* sentinel */ },
+};
+
+static inline void inno_write(struct inno_hdmi_phy *inno, u32 reg, u8 val)
+{
+	writel(val, inno->regs + (reg * 4));
+}
+
+static inline u8 inno_read(struct inno_hdmi_phy *inno, u32 reg)
+{
+	u32 val;
+
+	val = readl(inno->regs + (reg * 4));
+
+	return val;
+}
+
+static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg,
+				    u8 mask, u8 val)
+{
+	u32 tmp, orig;
+
+	orig = inno_read(inno, reg);
+	tmp = orig & ~mask;
+	tmp |= val & mask;
+	inno_write(inno, reg, tmp);
+}
+
+#define inno_poll(reg, val, cond, sleep_us, timeout_us) \
+	readl_poll_sleep_timeout((reg) * 4, val, cond, sleep_us, timeout_us)
+
+static unsigned long inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno,
+					       unsigned long rate)
+{
+	int bus_width = inno->bus_width;
+
+	switch (bus_width) {
+	case 4:
+	case 5:
+	case 6:
+	case 10:
+	case 12:
+	case 16:
+		return (u64)rate * bus_width / 8;
+	default:
+		return rate;
+	}
+}
+
+static
+unsigned long inno_hdmi_phy_rk3328_clk_recalc_rate(struct phy *phy,
+						   unsigned long parent_rate)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+	unsigned long frac;
+	u8 nd, no_a, no_b, no_d;
+	u64 vco;
+	u16 nf;
+
+	nd = inno_read(inno, 0xa1) & RK3328_PRE_PLL_PRE_DIV_MASK;
+	nf = ((inno_read(inno, 0xa2) & RK3328_PRE_PLL_FB_DIV_11_8_MASK) << 8);
+	nf |= inno_read(inno, 0xa3);
+	vco = parent_rate * nf;
+
+	if (!(inno_read(inno, 0xa2) & RK3328_PRE_PLL_FRAC_DIV_DISABLE)) {
+		frac = inno_read(inno, 0xd3) |
+		       (inno_read(inno, 0xd2) << 8) |
+		       (inno_read(inno, 0xd1) << 16);
+		vco += DIV_ROUND_CLOSEST(parent_rate * frac, (1 << 24));
+	}
+
+	if (inno_read(inno, 0xa0) & RK3328_PCLK_VCO_DIV_5_MASK) {
+		do_div(vco, nd * 5);
+	} else {
+		no_a = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_A_MASK;
+		no_b = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_B_MASK;
+		no_b >>= RK3328_PRE_PLL_PCLK_DIV_B_SHIFT;
+		no_b += 2;
+		no_d = inno_read(inno, 0xa6) & RK3328_PRE_PLL_PCLK_DIV_D_MASK;
+
+		do_div(vco, (nd * (no_a == 1 ? no_b : no_a) * no_d * 2));
+	}
+
+	inno->pixclock = DIV_ROUND_CLOSEST((unsigned long)vco, 1000) * 1000;
+
+	dev_info(phy->dev, "rate %lu vco %llu\n", inno->pixclock, vco);
+
+	return inno->pixclock;
+}
+
+static long inno_hdmi_phy_rk3328_clk_round_rate(struct phy *phy,
+						unsigned long rate)
+{
+	const struct pre_pll_config *cfg = pre_pll_cfg_table;
+
+	rate = (rate / 1000) * 1000;
+
+	for (; cfg->pixclock != 0; cfg++)
+		if (cfg->pixclock == rate)
+			break;
+
+	if (cfg->pixclock == 0)
+		return -EINVAL;
+
+	return cfg->pixclock;
+}
+
+static const
+struct pre_pll_config *inno_hdmi_phy_get_pre_pll_cfg(struct inno_hdmi_phy *inno,
+						     unsigned long rate)
+{
+	const struct pre_pll_config *cfg = pre_pll_cfg_table;
+	unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate);
+
+	for (; cfg->pixclock != 0; cfg++)
+		if (cfg->pixclock == rate && cfg->tmdsclock == tmdsclock)
+			break;
+
+	if (cfg->pixclock == 0)
+		return ERR_PTR(-EINVAL);
+
+	return cfg;
+}
+
+static int
+inno_hdmi_phy_rk3328_clk_set_rate(struct phy *phy,
+				  unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+	unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate);
+	const struct pre_pll_config *cfg;
+	u32 val;
+	int ret;
+
+	dev_info(phy->dev, "rate %lu tmdsclk %lu\n", rate, tmdsclock);
+
+	if (inno->pixclock == rate && inno->tmdsclock == tmdsclock)
+		return 0;
+
+	cfg = inno_hdmi_phy_get_pre_pll_cfg(inno, rate);
+	if (IS_ERR(cfg))
+		return PTR_ERR(cfg);
+
+	inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN,
+			 RK3328_PRE_PLL_POWER_DOWN);
+
+	/* Configure pre-pll */
+	inno_update_bits(inno, 0xa0, RK3328_PCLK_VCO_DIV_5_MASK,
+			 RK3328_PCLK_VCO_DIV_5(cfg->vco_div_5_en));
+	inno_write(inno, 0xa1, RK3328_PRE_PLL_PRE_DIV(cfg->prediv));
+
+	val = RK3328_SPREAD_SPECTRUM_MOD_DISABLE;
+	if (!cfg->fracdiv)
+		val |= RK3328_PRE_PLL_FRAC_DIV_DISABLE;
+	inno_write(inno, 0xa2, RK3328_PRE_PLL_FB_DIV_11_8(cfg->fbdiv) | val);
+	inno_write(inno, 0xa3, RK3328_PRE_PLL_FB_DIV_7_0(cfg->fbdiv));
+	inno_write(inno, 0xa5, RK3328_PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a) |
+		   RK3328_PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b));
+	inno_write(inno, 0xa6, RK3328_PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) |
+		   RK3328_PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d));
+	inno_write(inno, 0xa4, RK3328_PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) |
+		   RK3328_PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) |
+		   RK3328_PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b));
+	inno_write(inno, 0xd3, RK3328_PRE_PLL_FRAC_DIV_7_0(cfg->fracdiv));
+	inno_write(inno, 0xd2, RK3328_PRE_PLL_FRAC_DIV_15_8(cfg->fracdiv));
+	inno_write(inno, 0xd1, RK3328_PRE_PLL_FRAC_DIV_23_16(cfg->fracdiv));
+
+	inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0);
+
+	/* Wait for Pre-PLL lock */
+	ret = inno_poll(0xa9, val, val & RK3328_PRE_PLL_LOCK_STATUS,
+			1000, 10000);
+	if (ret) {
+		dev_err(phy->dev, "Pre-PLL locking failed\n");
+		return ret;
+	}
+
+	inno->pixclock = rate;
+	inno->tmdsclock = tmdsclock;
+
+	return 0;
+}
+
+static void inno_hdmi_phy_rk3328_clk_enable(struct phy *phy)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+
+	inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0);
+}
+
+static void inno_hdmi_phy_rk3328_clk_disable(struct phy *phy)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+
+	inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN,
+			 RK3328_PRE_PLL_POWER_DOWN);
+}
+
+static int
+inno_hdmi_phy_rk3328_power_on(struct phy *phy,
+			      const struct post_pll_config *cfg,
+			      const struct phy_config *phy_cfg)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+	int ret;
+	u32 v;
+
+	inno_update_bits(inno, 0x02, RK3328_PDATA_EN, 0);
+	inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN,
+			 RK3328_POST_PLL_POWER_DOWN);
+
+	inno_write(inno, 0xac, RK3328_POST_PLL_FB_DIV_7_0(cfg->fbdiv));
+	if (cfg->postdiv == 1) {
+		inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) |
+			   RK3328_POST_PLL_PRE_DIV(cfg->prediv));
+		inno_write(inno, 0xaa, RK3328_POST_PLL_REFCLK_SEL_TMDS |
+			   RK3328_POST_PLL_POWER_DOWN);
+	} else {
+		v = (cfg->postdiv / 2) - 1;
+		v &= RK3328_POST_PLL_POST_DIV_MASK;
+		inno_write(inno, 0xad, v);
+		inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) |
+			   RK3328_POST_PLL_PRE_DIV(cfg->prediv));
+		inno_write(inno, 0xaa, RK3328_POST_PLL_POST_DIV_ENABLE |
+			   RK3328_POST_PLL_REFCLK_SEL_TMDS |
+			   RK3328_POST_PLL_POWER_DOWN);
+	}
+
+	for (v = 0; v < 14; v++)
+		inno_write(inno, 0xb5 + v, phy_cfg->regs[v]);
+
+	/* set ESD detection threshold for TMDS CLK, D2, D1 and D0 */
+	for (v = 0; v < 4; v++)
+		inno_update_bits(inno, 0xc8 + v, RK3328_ESD_DETECT_MASK,
+				 RK3328_ESD_DETECT_340MV);
+
+	if (phy_cfg->tmdsclock > 340000000) {
+		/* Set termination resistor to 100ohm */
+		v = clk_get_rate(&inno->sysclk) / 100000;
+		inno_write(inno, 0xc5, RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(v)
+			   | RK3328_BYPASS_TERM_RESISTOR_CALIB);
+		inno_write(inno, 0xc6, RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(v));
+		inno_write(inno, 0xc7, RK3328_TERM_RESISTOR_100);
+		inno_update_bits(inno, 0xc5,
+				 RK3328_BYPASS_TERM_RESISTOR_CALIB, 0);
+	} else {
+		inno_write(inno, 0xc5, RK3328_BYPASS_TERM_RESISTOR_CALIB);
+
+		/* clk termination resistor is 50ohm (parallel resistors) */
+		if (phy_cfg->tmdsclock > 165000000)
+			inno_update_bits(inno, 0xc8,
+					 RK3328_TMDS_TERM_RESIST_MASK,
+					 RK3328_TMDS_TERM_RESIST_75 |
+					 RK3328_TMDS_TERM_RESIST_150);
+
+		/* data termination resistor for D2, D1 and D0 is 150ohm */
+		for (v = 0; v < 3; v++)
+			inno_update_bits(inno, 0xc9 + v,
+					 RK3328_TMDS_TERM_RESIST_MASK,
+					 RK3328_TMDS_TERM_RESIST_150);
+	}
+
+	inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, 0);
+	inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE,
+			 RK3328_BANDGAP_ENABLE);
+	inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE,
+			 RK3328_TMDS_DRIVER_ENABLE);
+
+	/* Wait for post PLL lock */
+	ret = inno_poll(0xaf, v, v & RK3328_POST_PLL_LOCK_STATUS,
+			1000, 10000);
+	if (ret) {
+		dev_err(phy->dev, "Post-PLL locking failed\n");
+		return ret;
+	}
+
+	if (phy_cfg->tmdsclock > 340000000)
+		mdelay(100);
+
+	inno_update_bits(inno, 0x02, RK3328_PDATA_EN, RK3328_PDATA_EN);
+
+	/* Enable PHY IRQ */
+	inno_write(inno, 0x05, RK3328_INT_TMDS_CLK(RK3328_INT_VSS_AGND_ESD_DET)
+		   | RK3328_INT_TMDS_D2(RK3328_INT_VSS_AGND_ESD_DET));
+	inno_write(inno, 0x07, RK3328_INT_TMDS_D1(RK3328_INT_VSS_AGND_ESD_DET)
+		   | RK3328_INT_TMDS_D0(RK3328_INT_VSS_AGND_ESD_DET));
+
+	return 0;
+}
+
+static void inno_hdmi_phy_rk3328_power_off(struct phy *phy)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+
+	inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE, 0);
+	inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE, 0);
+	inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN,
+			 RK3328_POST_PLL_POWER_DOWN);
+
+	/* Disable PHY IRQ */
+	inno_write(inno, 0x05, 0);
+	inno_write(inno, 0x07, 0);
+}
+
+static void inno_hdmi_phy_rk3328_init(struct phy *phy)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+	const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops;
+
+	/*
+	 * Use phy internal register control
+	 * rxsense/poweron/pllpd/pdataen signal.
+	 */
+	inno_write(inno, 0x01, RK3328_BYPASS_RXSENSE_EN |
+		   RK3328_BYPASS_POWERON_EN |
+		   RK3328_BYPASS_PLLPD_EN);
+	inno_write(inno, 0x02, RK3328_INT_POL_HIGH | RK3328_BYPASS_PDATA_EN |
+		   RK3328_PDATA_EN);
+
+	/* Disable phy irq */
+	inno_write(inno, 0x05, 0);
+	inno_write(inno, 0x07, 0);
+
+	if (plat_ops->clk_recalc_rate)
+		plat_ops->clk_recalc_rate(phy, clk_get_rate(&inno->refoclk));
+
+	if (plat_ops->clk_round_rate)
+		plat_ops->clk_round_rate(phy, inno->pixclock);
+}
+
+static const struct inno_hdmi_phy_plat_ops rk3328_hdmi_phy_plat_ops = {
+	.init = inno_hdmi_phy_rk3328_init,
+	.power_on = inno_hdmi_phy_rk3328_power_on,
+	.power_off = inno_hdmi_phy_rk3328_power_off,
+	.clk_enable = inno_hdmi_phy_rk3328_clk_enable,
+	.clk_disable = inno_hdmi_phy_rk3328_clk_disable,
+	.clk_recalc_rate = inno_hdmi_phy_rk3328_clk_recalc_rate,
+	.clk_round_rate = inno_hdmi_phy_rk3328_clk_round_rate,
+	.clk_set_rate = inno_hdmi_phy_rk3328_clk_set_rate,
+};
+
+static int inno_hdmi_phy_power_on(struct phy *phy)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+	const struct post_pll_config *cfg = post_pll_cfg_table;
+	const struct phy_config *phy_cfg = inno->data->phy_cfg_table;
+	u32 tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, inno->pixclock);
+	const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops;
+	int ret;
+
+	if (!tmdsclock) {
+		dev_err(phy->dev, "TMDS clock is zero!\n");
+		return -EINVAL;
+	}
+
+	if (!plat_ops->power_on)
+		return -EINVAL;
+
+	dev_info(phy->dev, "TMDS clock = %d\n", tmdsclock);
+
+	for (; cfg->tmdsclock != ~0UL; cfg++)
+		if (tmdsclock <= cfg->tmdsclock)
+			break;
+
+	for (; phy_cfg->tmdsclock != ~0UL; phy_cfg++)
+		if (tmdsclock <= phy_cfg->tmdsclock)
+			break;
+
+	if (cfg->tmdsclock == 0 || phy_cfg->tmdsclock == 0)
+		return -EINVAL;
+
+	if (plat_ops->clk_set_rate) {
+		ret = plat_ops->clk_set_rate(phy, inno->pixclock, 24000000);
+		if (ret)
+			return ret;
+	}
+
+	if (plat_ops->clk_enable)
+		plat_ops->clk_enable(phy);
+
+	if (plat_ops->power_on) {
+		ret = plat_ops->power_on(phy, cfg, phy_cfg);
+		if (ret) {
+			if (plat_ops->clk_disable)
+				plat_ops->clk_disable(phy);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int inno_hdmi_phy_power_off(struct phy *phy)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+	const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops;
+
+	if (!plat_ops->power_off)
+		return -EINVAL;
+
+	plat_ops->power_off(phy);
+
+	if (plat_ops->clk_disable)
+		plat_ops->clk_disable(phy);
+
+	inno->tmdsclock = 0;
+
+	return 0;
+}
+
+static int inno_hdmi_phy_init(struct phy *phy)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+
+	if (inno->data->plat_ops->init)
+		inno->data->plat_ops->init(phy);
+
+	return 0;
+}
+
+static struct phy_ops inno_hdmi_phy_ops = {
+	.init = inno_hdmi_phy_init,
+	.power_on = inno_hdmi_phy_power_on,
+	.power_off = inno_hdmi_phy_power_off,
+};
+
+static int inno_hdmi_phy_probe(struct udevice *dev)
+{
+	struct inno_hdmi_phy *inno = dev_get_priv(dev);
+	int ret;
+
+	inno->regs = dev_read_addr_ptr(dev);
+	if (!inno->regs)
+		return -ENOMEM;
+
+	inno->data = (const struct inno_hdmi_phy_data *)dev_get_driver_data(dev);
+	if (!inno->data)
+		return -EINVAL;
+
+	inno->bus_width = 8;
+
+	ret = clk_get_by_name(dev, "refoclk", &inno->refoclk);
+	if (ret) {
+		dev_err(dev, "failed to get the refoclk (ret=%d)\n", ret);
+		return ret;
+	}
+
+	ret = clk_get_by_name(dev, "sysclk", &inno->sysclk);
+	if (ret) {
+		dev_err(dev, "failed to get the sysclk (ret=%d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct inno_hdmi_phy_data rk3328_inno_hdmi_phy_drv_data = {
+	.phy_type = INNO_HDMI_PHY_RK3328,
+	.plat_ops = &rk3328_hdmi_phy_plat_ops,
+	.phy_cfg_table = rk3328_phy_cfg,
+};
+
+static const struct udevice_id inno_hdmi_phy_ids[] = {
+	{
+		.compatible = "rockchip,rk3328-hdmi-phy",
+		.data = (ulong)&rk3328_inno_hdmi_phy_drv_data,
+	},
+	{ /* sentile */ }
+};
+
+U_BOOT_DRIVER(inno_hdmi_phy) = {
+	.name = "inno_hdmi_phy",
+	.id = UCLASS_PHY,
+	.of_match = inno_hdmi_phy_ids,
+	.ops = &inno_hdmi_phy_ops,
+	.probe = inno_hdmi_phy_probe,
+	.priv_auto = sizeof(struct inno_hdmi_phy),
+};
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 39c8252..7808ae7 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -522,6 +522,14 @@
 	Say Y here if you want to enable support for Orise Technology
 	otm8009a 480x800 dsi 2dl panel.
 
+config VIDEO_LCD_LG_LD070WX3
+	bool "LD070WX3 DSI LCD panel support"
+	depends on PANEL && BACKLIGHT
+	select VIDEO_MIPI_DSI
+	help
+	  Say Y here if you want to enable support for LG LD070WX3
+	  800x1280 DSI video mode panel.
+
 config VIDEO_LCD_RAYDIUM_RM68200
 	bool "RM68200 DSI LCD panel support"
 	select VIDEO_MIPI_DSI
@@ -547,6 +555,15 @@
 	  IPS-LCD module with Renesas R69328 IC. The panel has a 720x1280
 	  resolution and uses 24 bit RGB per pixel.
 
+config VIDEO_LCD_SAMSUNG_LTL106HL02
+	tristate "Samsung LTL106HL02 1920x1080 DSI video mode panel"
+	depends on PANEL && BACKLIGHT
+	select VIDEO_MIPI_DSI
+	help
+	  Say Y here if you want to enable support for Samsung LTL106HL02
+	  LCD module found in Microsoft Surface 2. The panel has a FullHD
+	  resolution (1920x1080).
+
 config VIDEO_LCD_SSD2828
 	bool "SSD2828 bridge chip"
 	---help---
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index fdc2937..f3f70cd 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -58,10 +58,12 @@
 obj-$(CONFIG_VIDEO_LCD_ENDEAVORU) += endeavoru-panel.o
 obj-$(CONFIG_VIDEO_LCD_HIMAX_HX8394) += himax-hx8394.o
 obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o
+obj-$(CONFIG_VIDEO_LCD_LG_LD070WX3) += lg-ld070wx3.o
 obj-$(CONFIG_VIDEO_LCD_ORISETECH_OTM8009A) += orisetech_otm8009a.o
 obj-$(CONFIG_VIDEO_LCD_RAYDIUM_RM68200) += raydium-rm68200.o
 obj-$(CONFIG_VIDEO_LCD_RENESAS_R61307) += renesas-r61307.o
 obj-$(CONFIG_VIDEO_LCD_RENESAS_R69328) += renesas-r69328.o
+obj-$(CONFIG_VIDEO_LCD_SAMSUNG_LTL106HL02) += samsung-ltl106hl02.o
 obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o
 obj-$(CONFIG_VIDEO_LCD_TDO_TL070WSH30) += tdo-tl070wsh30.o
 obj-$(CONFIG_VIDEO_MCDE_SIMPLE) += mcde_simple.o
diff --git a/drivers/video/bridge/Kconfig b/drivers/video/bridge/Kconfig
index 2311ca2..ab91727 100644
--- a/drivers/video/bridge/Kconfig
+++ b/drivers/video/bridge/Kconfig
@@ -7,6 +7,16 @@
 	  requires LVDS, an eDP->LVDS bridge chip can be used to provide the
 	  necessary conversion. This option enables support for these devices.
 
+config VIDEO_BRIDGE_PARADE_DP501
+	bool "Support Parade DP501 DP & DVI/HDMI dual mode transmitter"
+	depends on PANEL && DM_GPIO
+	select DM_I2C
+	help
+	  The Parade DP501 is a DP & DVI/HDMI dual-mode transmitter. It
+	  enables an RGB/Parallel SOC output to be converted, packed and
+	  serialized into either DP or TMDS output device. Only DisplayPort
+	  functionality of this transmitter has been implemented and tested.
+
 config VIDEO_BRIDGE_PARADE_PS862X
 	bool "Support Parade PS862X DP->LVDS bridge"
 	depends on VIDEO_BRIDGE
@@ -40,3 +50,12 @@
 	select VIDEO_MIPI_DSI
 	help
 	  Solomon SSD2824 SPI RGB-DSI bridge driver wrapped into panel uClass.
+
+config VIDEO_BRIDGE_TOSHIBA_TC358768
+	bool "Support Toshiba TC358768 MIPI DSI bridge"
+	depends on PANEL && DM_GPIO
+	select VIDEO_MIPI_DSI
+	select DM_I2C
+	help
+	  Toshiba TC358768AXBG/TC358778XBG DSI bridge chip driver.
+	  Found in Asus Transformer Infinity TF700T.
diff --git a/drivers/video/bridge/Makefile b/drivers/video/bridge/Makefile
index 22625c8..58697e3 100644
--- a/drivers/video/bridge/Makefile
+++ b/drivers/video/bridge/Makefile
@@ -4,7 +4,9 @@
 #  Written by Simon Glass <sjg@chromium.org>
 
 obj-$(CONFIG_VIDEO_BRIDGE) += video-bridge-uclass.o
+obj-$(CONFIG_VIDEO_BRIDGE_PARADE_DP501) += dp501.o
 obj-$(CONFIG_VIDEO_BRIDGE_PARADE_PS862X) += ps862x.o
 obj-$(CONFIG_VIDEO_BRIDGE_NXP_PTN3460) += ptn3460.o
 obj-$(CONFIG_VIDEO_BRIDGE_ANALOGIX_ANX6345) += anx6345.o
 obj-$(CONFIG_VIDEO_BRIDGE_SOLOMON_SSD2825) += ssd2825.o
+obj-$(CONFIG_VIDEO_BRIDGE_TOSHIBA_TC358768) += tc358768.o
diff --git a/drivers/video/bridge/dp501.c b/drivers/video/bridge/dp501.c
new file mode 100644
index 0000000..095e3e7
--- /dev/null
+++ b/drivers/video/bridge/dp501.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Jonas Schwöbel <jonasschwoebel@yahoo.de>
+ * Copyright (C) 2024 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <dm.h>
+#include <i2c.h>
+#include <log.h>
+#include <backlight.h>
+#include <panel.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <power/regulator.h>
+
+#include <asm/gpio.h>
+
+/* TOP */
+#define TOPCFG0			0x00
+#define ROMI2C_PRESCALE		0x01
+#define HDCPI2C_PRESCALE	0x02
+#define GPIO			0x03
+#define GPIO_OUT_ENB		0x04
+#define TESTI2C_CTL		0x05
+#define I2CMTIMEOUT		0x06
+#define TOPCFG1			0x07
+#define TOPCFG2			0x08
+#define TOPCFG3			0x09
+#define TOPCFG4			0x0A
+#define CLKSWRST		0x0B
+#define CADETB_CTL		0x0C
+
+/* Video Attribute */
+#define HTOTAL_L		0x10
+#define HTOTAL_H		0x11
+#define HSTART_L		0x12
+#define HSTART_H		0x13
+#define HWIDTH_L		0x14
+#define HWIDTH_H		0x15
+#define VTOTAL_L		0x16
+#define VTOTAL_H		0x17
+#define VSTART_L		0x18
+#define VSTART_H		0x19
+#define VHEIGHT_L		0x1A
+#define VHEIGHT_H		0x1B
+#define HSPHSW_L		0x1C
+#define HSPHSW_H		0x1D
+#define VSPVSW_L		0x1E
+#define VSPVSW_H		0x1F
+#define MISC0			0x20
+#define MISC1			0x21
+
+/* Video Capture */
+#define VCAPCTRL0		0x24
+#define VCAPCTRL1		0x25
+#define VCAPCTRL2		0x26
+#define VCAPCTRL3		0x27
+#define VCAPCTRL4		0x28
+#define VCAP_MEASURE		0x29
+
+/* Main Link Control */
+#define NVID_L			0x2C
+#define NVID_M			0x2D
+#define NVID_H			0x2E
+#define LINK_CTRL0		0x2F
+#define LINK_CTRL1		0x30
+#define LINK_DEBUG		0x31
+#define ERR_POS			0x32
+#define ERR_PAT			0x33
+#define LINK_DEB_SEL		0x34
+#define IDLE_PATTERN		0x35
+#define TU_SIZE			0x36
+#define CRC_CTRL		0x37
+#define CRC_OUT			0x38
+
+/* AVI-2 InfoFrame */
+#define SD_CTRL0		0x3A
+#define SD_CTRL1		0x3B
+#define SD_HB0			0x3C
+#define SD_HB1			0x3D
+#define SD_HB2			0x3E
+#define SD_HB3			0x3F
+#define SD_DB0			0x40
+#define SD_DB1			0x41
+#define SD_DB2			0x42
+#define SD_DB3			0x43
+#define SD_DB4			0x44
+#define SD_DB5			0x45
+#define SD_DB6			0x46
+#define SD_DB7			0x47
+#define SD_DB8			0x48
+#define SD_DB9			0x49
+#define SD_DB10			0x4A
+#define SD_DB11			0x4B
+#define SD_DB12			0x4C
+#define SD_DB13			0x4D
+#define SD_DB14			0x4E
+#define SD_DB15			0x4F
+
+/* Aux Channel and PCS */
+#define DPCD_REV		0X50
+#define MAX_LINK_RATE		0x51
+#define MAX_LANE_COUNT		0x52
+#define MAX_DOWNSPREAD		0x53
+#define NORP			0x54
+#define DOWNSTRMPORT_PRE	0x55
+#define MLINK_CH_CODING		0x56
+#define RCV_P0_CAP0		0x58
+#define RCV_P0_CAP1		0x59
+#define RCV_P1_CAP0		0x5A
+#define RCV_P1_CAP1		0x5B
+#define DOWNSPREAD_CTL		0x5C
+#define LINK_BW			0x5D
+#define LANE_CNT		0x5E
+#define TRAINING_CTL		0x5F
+#define QUALTEST_CTL		0x60
+#define SINK_COUNT		0x61
+#define DEV_SERVICE_IRQ		0x62
+#define LANE01_STATUS		0x63
+#define LANE23_STATUS		0x64
+#define LANE_STATUS_UPDATE	0x65
+#define SINK_STATUS		0x66
+#define AUX_NOISE		0x67
+#define TEST_MODE		0x69
+#define TEST_PATTERN0		0x6A
+#define TEST_PATTERN1		0x6B
+#define TEST_PATTERN2		0x6C
+#define SIGNATURE		0x6D
+#define PCSCFG			0x6E
+#define AUXCTRL0		0x6f
+#define AUXCTRL2		0x70
+#define AUXCTRL1		0x71
+#define HPDCTL0			0x72
+#define HPDCTL1			0x73
+#define LINK_STATE_CTRL		0x74
+#define SWRST			0x75
+#define LINK_IRQ		0x76
+#define AUXIRQ_CTRL		0x77
+#define HPD2_IRQ_CTRL		0x78
+#define SW_TRAIN_CTRL		0x79
+#define SW_DRV_SET		0x7A
+#define SW_PRE_SET		0x7B
+#define DPCD_ADDR_L		0x7D
+#define DPCD_ADDR_M		0x7E
+#define DPCD_ADDR_H		0x7F
+#define DPCD_LENGTH		0x80
+#define DPCD_WDATA		0x81
+#define DPCD_RDATA		0x82
+#define DPCD_CTL		0x83
+#define DPCD_STATUS		0x84
+#define AUX_STATUS		0x85
+#define I2CTOAUX_RELENGTH	0x86
+#define AUX_RETRY_CTRL		0x87
+#define TIMEOUT_CTRL		0x88
+#define I2CCMD_OPT1		0x89
+#define AUXCMD_ERR_IRQ		0x8A
+#define AUXCMD_OPT2		0x8B
+#define HDCP_Reserved		0x8C
+
+/* Audio InfoFrame */
+#define TX_MVID0		0x90
+#define TX_MVID1		0x91
+#define TX_MVID2		0x92
+#define TX_MVID_OFF		0x93
+#define TX_MAUD0		0x94
+#define TX_MAUD1		0x95
+#define TX_MAUD2		0x96
+#define TX_MAUD_OFF		0x97
+#define MN_CTRL			0x98
+#define MOUT0			0x99
+#define MOUT1			0x9A
+#define MOUT2			0x9B
+
+/* Audio Control */
+#define NAUD_L			0x9F
+#define NAUD_M			0xA0
+#define NAUD_H			0xA1
+#define AUD_CTRL0		0xA2
+#define AUD_CTRL1		0xA3
+#define LANE_POL		0xAA
+#define LANE_EN			0xAB
+#define LANE_MAP		0xAC
+#define SCR_POLY0		0xAD
+#define SCR_POLY1		0xAE
+#define PRBS7_POLY		0xAF
+
+/* Video Pre-process */
+#define MISC_SHDOW		0xB0
+#define VCAPCPCTL0		0xB1
+#define VCAPCPCTL1		0xB2
+#define VCAPCPCTL2		0xB3
+#define CSCPAR			0xB4
+#define I2CTODPCDSTATUS2	0xBA
+#define AUXCTL_REG		0xBB
+
+/*   Page 2   */
+#define SEL_PIO1		0x24
+#define SEL_PIO2		0x25
+#define SEL_PIO3		0x26
+#define CHIP_VER_L		0x82
+
+struct dp501_priv {
+	struct udevice *panel;
+	struct display_timing timing;
+
+	struct udevice *chip2;
+
+	struct udevice *vdd;
+	struct gpio_desc reset_gpio;
+	struct gpio_desc enable_gpio;
+};
+
+static int dp501_sw_init(struct udevice *dev)
+{
+	struct dp501_priv *priv = dev_get_priv(dev);
+	int i;
+	u8 val;
+
+	dm_i2c_reg_write(dev, TOPCFG4, 0x30);
+	udelay(200);
+	dm_i2c_reg_write(dev, TOPCFG4, 0x0c);
+	dm_i2c_reg_write(dev, 0x8f, 0x02);
+
+	/* check for connected panel during 1 msec */
+	for (i = 0; i < 5; i++)	{
+		val = dm_i2c_reg_read(dev, 0x8d);
+		val &= BIT(2);
+		if (val)
+			break;
+
+		udelay(200);
+	}
+
+	if (!val) {
+		log_debug("%s: panel is not connected!\n", __func__);
+		return -ENODEV;
+	}
+
+	dm_i2c_reg_write(priv->chip2, SEL_PIO1, 0x02);
+	dm_i2c_reg_write(priv->chip2, SEL_PIO2, 0x04);
+	dm_i2c_reg_write(priv->chip2, SEL_PIO3, 0x10);
+
+	dm_i2c_reg_write(dev, LINK_STATE_CTRL, 0xa0);
+	dm_i2c_reg_write(dev, 0x8f, 0x02);
+	dm_i2c_reg_write(dev, TOPCFG1, 0x16);
+	dm_i2c_reg_write(dev, TOPCFG0, 0x24);
+	dm_i2c_reg_write(dev, HPD2_IRQ_CTRL, 0x30);
+	dm_i2c_reg_write(dev, AUXIRQ_CTRL, 0xff);
+	dm_i2c_reg_write(dev, LINK_IRQ, 0xff);
+
+	/* auto detect DVO timing */
+	dm_i2c_reg_write(dev, VCAPCTRL3, 0x30);
+
+	/* reset tpfifo at v blank */
+	dm_i2c_reg_write(dev, LINK_CTRL0, 0x82);
+
+	dm_i2c_reg_write(dev, VCAPCTRL4, 0x07);
+	dm_i2c_reg_write(dev, AUX_RETRY_CTRL, 0x7f);
+	dm_i2c_reg_write(dev, TIMEOUT_CTRL, 0x1e);
+	dm_i2c_reg_write(dev, AUXCTL_REG, 0x06);
+
+	/* DPCD readable */
+	dm_i2c_reg_write(dev, HPDCTL0, 0xa9);
+
+	/* Scramble on */
+	dm_i2c_reg_write(dev, QUALTEST_CTL, 0x00);
+
+	dm_i2c_reg_write(dev, 0x8f, 0x02);
+
+	dm_i2c_reg_write(dev, VCAPCTRL0, 0xc4);
+
+	/* set color depth 8bit (0x00: 6bit; 0x20: 8bit; 0x40: 10bit) */
+	dm_i2c_reg_write(dev, MISC0, 0x20);
+
+	dm_i2c_reg_write(dev, VCAPCPCTL2, 0x01);
+
+	/* check if bridge returns ready status */
+	for (i = 0; i < 5; i++)	{
+		val = dm_i2c_reg_read(dev, LINK_IRQ);
+		val &= BIT(0);
+		if (val)
+			break;
+
+		udelay(200);
+	}
+
+	if (!val) {
+		log_debug("%s: bridge is not ready\n", __func__);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void dpcd_configure(struct udevice *dev, u32 config, bool write)
+{
+	dm_i2c_reg_write(dev, DPCD_ADDR_L, (u8)(config >> 8));
+	dm_i2c_reg_write(dev, DPCD_ADDR_M, (u8)(config >> 16));
+	dm_i2c_reg_write(dev, DPCD_ADDR_H, (u8)((config >> 24) | BIT(7)));
+	dm_i2c_reg_write(dev, DPCD_LENGTH, 0x00);
+	dm_i2c_reg_write(dev, LINK_IRQ, 0x20);
+
+	if (write)
+		dm_i2c_reg_write(dev, DPCD_WDATA, (u8)(config & 0xff));
+
+	dm_i2c_reg_write(dev, DPCD_CTL, 0x01);
+
+	udelay(10);
+}
+
+static int dump_dpcd_data(struct udevice *dev, u32 config, u8 *data)
+{
+	int i;
+	u8 value;
+
+	dpcd_configure(dev, config, false);
+
+	value = dm_i2c_reg_read(dev, DPCD_CTL);
+	if (value)
+		return -ENODATA;
+
+	for (i = 0; i < 5; i++) {
+		value = dm_i2c_reg_read(dev, LINK_IRQ);
+		value &= BIT(5);
+		if (value)
+			break;
+
+		udelay(100);
+	}
+
+	if (!value)
+		return -ENODATA;
+
+	value = dm_i2c_reg_read(dev, DPCD_STATUS);
+	if (!(value & 0xe0))
+		*data = dm_i2c_reg_read(dev, DPCD_RDATA);
+	else
+		return -ENODATA;
+
+	return 0;
+}
+
+static int dp501_dpcd_dump(struct udevice *dev, u32 config, u8 *data)
+{
+	int i, ret;
+
+	for (i = 0; i < 5; i++) {
+		ret = dump_dpcd_data(dev, config, data);
+		if (!ret)
+			break;
+
+		udelay(100);
+	}
+
+	return ret;
+}
+
+static int dp501_reset_link(struct udevice *dev)
+{
+	dm_i2c_reg_write(dev, TRAINING_CTL, 0x00);
+	dm_i2c_reg_write(dev, SWRST, 0xf8);
+	dm_i2c_reg_write(dev, SWRST, 0x00);
+
+	return -ENODEV;
+}
+
+static int dp501_link_training(struct udevice *dev)
+{
+	int i, ret;
+	u8 lane, link, link_out;
+	u8 lane_cnt, lane01, lane23;
+
+	dpcd_configure(dev, 0x030000, true);
+	dpcd_configure(dev, 0x03011c, true);
+	dpcd_configure(dev, 0x0301f8, true);
+
+	ret = dp501_dpcd_dump(dev, 0x90000100, &link);
+	if (ret) {
+		log_debug("%s: link dump failed %d\n", __func__, ret);
+		return dp501_reset_link(dev);
+	}
+
+	ret = dp501_dpcd_dump(dev, 0x90000200, &lane);
+	if (ret) {
+		log_debug("%s: lane dump failed %d\n", __func__, ret);
+		return dp501_reset_link(dev);
+	}
+
+	/* Software trainig */
+	for (i = 10; i > 0; i--) {
+		dm_i2c_reg_write(dev, LINK_BW, link);
+		dm_i2c_reg_write(dev, LANE_CNT, lane | BIT(7));
+
+		link_out = dm_i2c_reg_read(dev, LINK_BW);
+		lane_cnt = dm_i2c_reg_read(dev, LANE_CNT);
+
+		if (link_out == link &&
+		    (lane_cnt == (lane | BIT(7))))
+			break;
+
+		udelay(500);
+	}
+
+	if (!i)
+		return dp501_reset_link(dev);
+
+	dm_i2c_reg_write(dev, LINK_STATE_CTRL, 0x00);
+	dm_i2c_reg_write(dev, TRAINING_CTL, 0x0d);
+
+	/* check if bridge returns link ready status */
+	for (i = 0; i < 100; i++) {
+		link_out = dm_i2c_reg_read(dev, LINK_IRQ);
+		link_out &= BIT(1);
+		if (link_out) {
+			dm_i2c_reg_write(dev, LINK_IRQ, 0xff);
+			break;
+		}
+
+		udelay(100);
+	}
+
+	if (!link_out) {
+		log_debug("%s: link prepare failed %d\n",
+			  __func__, link_out);
+		return dp501_reset_link(dev);
+	}
+
+	lane01 = dm_i2c_reg_read(dev, LANE01_STATUS);
+	lane23 = dm_i2c_reg_read(dev, LANE23_STATUS);
+
+	switch (lane_cnt & 0xf) {
+	case 4:
+		if (lane01 == 0x77 &&
+		    lane23 == 0x77)
+			return 0;
+		break;
+
+	case 2:
+		if (lane01 == 0x77)
+			return 0;
+		break;
+
+	default:
+		if ((lane01 & 7) == 7)
+			return 0;
+		break;
+	}
+
+	return dp501_reset_link(dev);
+}
+
+static int dp501_attach(struct udevice *dev)
+{
+	struct dp501_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = dp501_sw_init(dev);
+	if (ret)
+		return ret;
+
+	mdelay(90);
+
+	ret = dp501_link_training(dev);
+	if (ret)
+		return ret;
+
+	/* Perform panel HW setup */
+	return panel_enable_backlight(priv->panel);
+}
+
+static int dp501_set_backlight(struct udevice *dev, int percent)
+{
+	struct dp501_priv *priv = dev_get_priv(dev);
+
+	return panel_set_backlight(priv->panel, percent);
+}
+
+static int dp501_panel_timings(struct udevice *dev,
+			       struct display_timing *timing)
+{
+	struct dp501_priv *priv = dev_get_priv(dev);
+
+	memcpy(timing, &priv->timing, sizeof(*timing));
+	return 0;
+}
+
+static void dp501_hw_init(struct dp501_priv *priv)
+{
+	dm_gpio_set_value(&priv->reset_gpio, 1);
+
+	regulator_set_enable_if_allowed(priv->vdd, 1);
+	dm_gpio_set_value(&priv->enable_gpio, 1);
+
+	udelay(100);
+
+	dm_gpio_set_value(&priv->reset_gpio, 0);
+	mdelay(80);
+}
+
+static int dp501_setup(struct udevice *dev)
+{
+	struct dm_i2c_chip *chip = dev_get_parent_plat(dev);
+	struct dp501_priv *priv = dev_get_priv(dev);
+	struct udevice *bus = dev_get_parent(dev);
+	int ret;
+
+	/* get panel */
+	ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev,
+					   "panel", &priv->panel);
+	if (ret) {
+		log_debug("%s: Cannot get panel: ret=%d\n", __func__, ret);
+		return log_ret(ret);
+	}
+
+	/* get regulators */
+	ret = device_get_supply_regulator(dev, "power-supply", &priv->vdd);
+	if (ret) {
+		log_debug("%s: vddc regulator error: %d\n", __func__, ret);
+		if (ret != -ENOENT)
+			return log_ret(ret);
+	}
+
+	/* get gpios */
+	ret = gpio_request_by_name(dev, "reset-gpios", 0,
+				   &priv->reset_gpio, GPIOD_IS_OUT);
+	if (ret) {
+		log_debug("%s: Could not decode reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = gpio_request_by_name(dev, "enable-gpios", 0,
+				   &priv->enable_gpio, GPIOD_IS_OUT);
+	if (ret) {
+		log_debug("%s: Could not decode enable-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = i2c_get_chip(bus, chip->chip_addr + 2, 1, &priv->chip2);
+	if (ret) {
+		log_debug("%s: cannot get second PMIC I2C chip (err %d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	dp501_hw_init(priv);
+
+	/* get EDID */
+	return panel_get_display_timing(priv->panel, &priv->timing);
+}
+
+static int dp501_probe(struct udevice *dev)
+{
+	if (device_get_uclass_id(dev->parent) != UCLASS_I2C)
+		return -EPROTONOSUPPORT;
+
+	return dp501_setup(dev);
+}
+
+struct panel_ops dp501_ops = {
+	.enable_backlight	= dp501_attach,
+	.set_backlight		= dp501_set_backlight,
+	.get_display_timing	= dp501_panel_timings,
+};
+
+static const struct udevice_id dp501_ids[] = {
+	{ .compatible = "parade,dp501" },
+	{ }
+};
+
+U_BOOT_DRIVER(dp501) = {
+	.name		= "dp501",
+	.id		= UCLASS_PANEL,
+	.of_match	= dp501_ids,
+	.ops		= &dp501_ops,
+	.probe		= dp501_probe,
+	.priv_auto	= sizeof(struct dp501_priv),
+};
diff --git a/drivers/video/bridge/ssd2825.c b/drivers/video/bridge/ssd2825.c
index cea20dc..f0ef3da 100644
--- a/drivers/video/bridge/ssd2825.c
+++ b/drivers/video/bridge/ssd2825.c
@@ -349,39 +349,6 @@
 	struct ssd2825_bridge_priv *priv = dev_get_priv(dev);
 	struct mipi_dsi_device *device = &priv->device;
 	struct display_timing *dt = &priv->timing;
-	int ret;
-
-	ret = clk_prepare_enable(priv->tx_clk);
-	if (ret) {
-		log_err("error enabling tx_clk (%d)\n", ret);
-		return ret;
-	}
-
-	ret = dm_gpio_set_value(&priv->power_gpio, 1);
-	if (ret) {
-		log_err("error changing power-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(10);
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
-	if (ret) {
-		log_err("error changing reset-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(10);
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
-	if (ret) {
-		log_err("error changing reset-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(10);
-
-	/* Perform panel HW setup */
-	ret = panel_enable_backlight(priv->panel);
-	if (ret)
-		return ret;
 
 	/* Perform SW reset */
 	ssd2825_write_register(dev, SSD2825_OPERATION_CTRL_REG, 0x0100);
@@ -417,17 +384,15 @@
 			       SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT);
 	ssd2825_write_register(dev, SSD2825_VC_CTRL_REG, 0x0000);
 
-	/* Set up SW panel configuration */
-	ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
-	if (ret)
-		return ret;
-
-	return 0;
+	/* Perform panel setup */
+	return panel_enable_backlight(priv->panel);
 }
 
 static int ssd2825_bridge_set_panel(struct udevice *dev, int percent)
 {
-	return 0;
+	struct ssd2825_bridge_priv *priv = dev_get_priv(dev);
+
+	return panel_set_backlight(priv->panel, percent);
 }
 
 static int ssd2825_bridge_panel_timings(struct udevice *dev,
@@ -440,6 +405,45 @@
 	return 0;
 }
 
+static int ssd2825_bridge_hw_init(struct udevice *dev)
+{
+	struct ssd2825_bridge_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = clk_prepare_enable(priv->tx_clk);
+	if (ret) {
+		log_debug("%s: error enabling tx_clk (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = dm_gpio_set_value(&priv->power_gpio, 1);
+	if (ret) {
+		log_debug("%s: error changing power-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(10);
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+	if (ret) {
+		log_debug("%s: error changing reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(10);
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+	if (ret) {
+		log_debug("%s: error changing reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(10);
+
+	return 0;
+}
+
 static int ssd2825_bridge_probe(struct udevice *dev)
 {
 	struct ssd2825_bridge_priv *priv = dev_get_priv(dev);
@@ -496,7 +500,7 @@
 		return PTR_ERR(priv->tx_clk);
 	}
 
-	return 0;
+	return ssd2825_bridge_hw_init(dev);
 }
 
 static const struct panel_ops ssd2825_bridge_ops = {
diff --git a/drivers/video/bridge/tc358768.c b/drivers/video/bridge/tc358768.c
new file mode 100644
index 0000000..19b6ca2
--- /dev/null
+++ b/drivers/video/bridge/tc358768.c
@@ -0,0 +1,985 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Texas Instruments Incorporated
+ * Copyright (C) 2022 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <i2c.h>
+#include <log.h>
+#include <mipi_display.h>
+#include <mipi_dsi.h>
+#include <backlight.h>
+#include <panel.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <power/regulator.h>
+
+#include <asm/gpio.h>
+
+/* Global (16-bit addressable) */
+#define TC358768_CHIPID			0x0000
+#define TC358768_SYSCTL			0x0002
+#define TC358768_CONFCTL		0x0004
+#define TC358768_VSDLY			0x0006
+#define TC358768_DATAFMT		0x0008
+#define TC358768_GPIOEN			0x000E
+#define TC358768_GPIODIR		0x0010
+#define TC358768_GPIOIN			0x0012
+#define TC358768_GPIOOUT		0x0014
+#define TC358768_PLLCTL0		0x0016
+#define TC358768_PLLCTL1		0x0018
+#define TC358768_CMDBYTE		0x0022
+#define TC358768_PP_MISC		0x0032
+#define TC358768_DSITX_DT		0x0050
+#define TC358768_FIFOSTATUS		0x00F8
+
+/* Debug (16-bit addressable) */
+#define TC358768_VBUFCTRL		0x00E0
+#define TC358768_DBG_WIDTH		0x00E2
+#define TC358768_DBG_VBLANK		0x00E4
+#define TC358768_DBG_DATA		0x00E8
+
+/* TX PHY (32-bit addressable) */
+#define TC358768_CLW_DPHYCONTTX		0x0100
+#define TC358768_D0W_DPHYCONTTX		0x0104
+#define TC358768_D1W_DPHYCONTTX		0x0108
+#define TC358768_D2W_DPHYCONTTX		0x010C
+#define TC358768_D3W_DPHYCONTTX		0x0110
+#define TC358768_CLW_CNTRL		0x0140
+#define TC358768_D0W_CNTRL		0x0144
+#define TC358768_D1W_CNTRL		0x0148
+#define TC358768_D2W_CNTRL		0x014C
+#define TC358768_D3W_CNTRL		0x0150
+
+/* TX PPI (32-bit addressable) */
+#define TC358768_STARTCNTRL		0x0204
+#define TC358768_DSITXSTATUS		0x0208
+#define TC358768_LINEINITCNT		0x0210
+#define TC358768_LPTXTIMECNT		0x0214
+#define TC358768_TCLK_HEADERCNT		0x0218
+#define TC358768_TCLK_TRAILCNT		0x021C
+#define TC358768_THS_HEADERCNT		0x0220
+#define TC358768_TWAKEUP		0x0224
+#define TC358768_TCLK_POSTCNT		0x0228
+#define TC358768_THS_TRAILCNT		0x022C
+#define TC358768_HSTXVREGCNT		0x0230
+#define TC358768_HSTXVREGEN		0x0234
+#define TC358768_TXOPTIONCNTRL		0x0238
+#define TC358768_BTACNTRL1		0x023C
+
+/* TX CTRL (32-bit addressable) */
+#define TC358768_DSI_CONTROL		0x040C
+#define TC358768_DSI_STATUS		0x0410
+#define TC358768_DSI_INT		0x0414
+#define TC358768_DSI_INT_ENA		0x0418
+#define TC358768_DSICMD_RDFIFO		0x0430
+#define TC358768_DSI_ACKERR		0x0434
+#define TC358768_DSI_ACKERR_INTENA	0x0438
+#define TC358768_DSI_ACKERR_HALT	0x043c
+#define TC358768_DSI_RXERR		0x0440
+#define TC358768_DSI_RXERR_INTENA	0x0444
+#define TC358768_DSI_RXERR_HALT		0x0448
+#define TC358768_DSI_ERR		0x044C
+#define TC358768_DSI_ERR_INTENA		0x0450
+#define TC358768_DSI_ERR_HALT		0x0454
+#define TC358768_DSI_CONFW		0x0500
+#define TC358768_DSI_LPCMD		0x0500
+#define TC358768_DSI_RESET		0x0504
+#define TC358768_DSI_INT_CLR		0x050C
+#define TC358768_DSI_START		0x0518
+
+/* DSITX CTRL (16-bit addressable) */
+#define TC358768_DSICMD_TX		0x0600
+#define TC358768_DSICMD_TYPE		0x0602
+#define TC358768_DSICMD_WC		0x0604
+#define TC358768_DSICMD_WD0		0x0610
+#define TC358768_DSICMD_WD1		0x0612
+#define TC358768_DSICMD_WD2		0x0614
+#define TC358768_DSICMD_WD3		0x0616
+#define TC358768_DSI_EVENT		0x0620
+#define TC358768_DSI_VSW		0x0622
+#define TC358768_DSI_VBPR		0x0624
+#define TC358768_DSI_VACT		0x0626
+#define TC358768_DSI_HSW		0x0628
+#define TC358768_DSI_HBPR		0x062A
+#define TC358768_DSI_HACT		0x062C
+
+/* TC358768_DSI_CONTROL (0x040C) register */
+#define TC358768_DSI_CONTROL_DIS_MODE		BIT(15)
+#define TC358768_DSI_CONTROL_TXMD		BIT(7)
+#define TC358768_DSI_CONTROL_HSCKMD		BIT(5)
+#define TC358768_DSI_CONTROL_EOTDIS		BIT(0)
+
+/* TC358768_DSI_CONFW (0x0500) register */
+#define TC358768_DSI_CONFW_MODE_SET		(5 << 29)
+#define TC358768_DSI_CONFW_MODE_CLR		(6 << 29)
+#define TC358768_DSI_CONFW_ADDR_DSI_CONTROL	(3 << 24)
+
+#define NANO	1000000000UL
+#define PICO	1000000000000ULL
+
+struct tc358768_priv {
+	struct mipi_dsi_host host;
+	struct mipi_dsi_device device;
+
+	struct udevice *panel;
+	struct display_timing timing;
+
+	struct udevice *vddc;
+	struct udevice *vddmipi;
+	struct udevice *vddio;
+
+	struct clk *refclk;
+
+	struct gpio_desc reset_gpio;
+
+	u32 pd_lines;	/* number of Parallel Port Input Data Lines */
+	u32 dsi_lanes;	/* number of DSI Lanes */
+
+	/* Parameters for PLL programming */
+	u32 fbd;	/* PLL feedback divider */
+	u32 prd;	/* PLL input divider */
+	u32 frs;	/* PLL Freqency range for HSCK (post divider) */
+
+	u32 dsiclk;	/* pll_clk / 2 */
+};
+
+static void tc358768_read(struct udevice *dev, u32 reg, u32 *val)
+{
+	int count;
+	u8 buf[4] = { 0, 0, 0, 0 };
+
+	/* 16-bit register? */
+	if (reg < 0x100 || reg >= 0x600)
+		count = 2;
+	else
+		count = 4;
+
+	dm_i2c_read(dev, reg, buf, count);
+	*val = (buf[0] <<  8) | (buf[1] & 0xff) |
+	       (buf[2] << 24) | (buf[3] << 16);
+
+	log_debug("%s 0x%04x >> 0x%08x\n",
+		  __func__, reg, *val);
+}
+
+static void tc358768_write(struct udevice *dev, u32 reg, u32 val)
+{
+	int count;
+	u8 buf[4];
+
+	/* 16-bit register? */
+	if (reg < 0x100 || reg >= 0x600)
+		count = 2;
+	else
+		count = 4;
+
+	buf[0] = val >> 8;
+	buf[1] = val & 0xff;
+	buf[2] = val >> 24;
+	buf[3] = val >> 16;
+
+	log_debug("%s 0x%04x << 0x%08x\n",
+		  __func__, reg, val);
+
+	dm_i2c_write(dev, reg, buf, count);
+}
+
+static void tc358768_update_bits(struct udevice *dev, u32 reg, u32 mask,
+				 u32 val)
+{
+	u32 tmp, orig;
+
+	tc358768_read(dev, reg, &orig);
+
+	tmp = orig & ~mask;
+	tmp |= val & mask;
+	if (tmp != orig)
+		tc358768_write(dev, reg, tmp);
+}
+
+static ssize_t tc358768_dsi_host_transfer(struct mipi_dsi_host *host,
+					  const struct mipi_dsi_msg *msg)
+{
+	struct udevice *dev = (struct udevice *)host->dev;
+	struct mipi_dsi_packet packet;
+	int ret;
+
+	if (msg->rx_len) {
+		log_debug("%s: MIPI rx is not supported\n", __func__);
+		return -EOPNOTSUPP;
+	}
+
+	if (msg->tx_len > 8) {
+		log_debug("%s: Maximum 8 byte MIPI tx is supported\n", __func__);
+		return -EOPNOTSUPP;
+	}
+
+	ret = mipi_dsi_create_packet(&packet, msg);
+	if (ret)
+		return ret;
+
+	if (mipi_dsi_packet_format_is_short(msg->type)) {
+		tc358768_write(dev, TC358768_DSICMD_TYPE,
+			       (0x10 << 8) | (packet.header[0] & 0x3f));
+		tc358768_write(dev, TC358768_DSICMD_WC, 0);
+		tc358768_write(dev, TC358768_DSICMD_WD0,
+			       (packet.header[2] << 8) | packet.header[1]);
+	} else {
+		int i;
+
+		tc358768_write(dev, TC358768_DSICMD_TYPE,
+			       (0x40 << 8) | (packet.header[0] & 0x3f));
+		tc358768_write(dev, TC358768_DSICMD_WC, packet.payload_length);
+		for (i = 0; i < packet.payload_length; i += 2) {
+			u16 val = packet.payload[i];
+
+			if (i + 1 < packet.payload_length)
+				val |= packet.payload[i + 1] << 8;
+
+			tc358768_write(dev, TC358768_DSICMD_WD0 + i, val);
+		}
+	}
+
+	/* start transfer */
+	tc358768_write(dev, TC358768_DSICMD_TX, 1);
+
+	return packet.size;
+}
+
+static const struct mipi_dsi_host_ops tc358768_dsi_host_ops = {
+	.transfer = tc358768_dsi_host_transfer,
+};
+
+static void tc358768_sw_reset(struct udevice *dev)
+{
+	/* Assert Reset */
+	tc358768_write(dev, TC358768_SYSCTL, 1);
+	mdelay(5);
+
+	/* Release Reset, Exit Sleep */
+	tc358768_write(dev, TC358768_SYSCTL, 0);
+}
+
+static void tc358768_hw_enable(struct tc358768_priv *priv)
+{
+	int ret;
+
+	ret = clk_prepare_enable(priv->refclk);
+	if (ret)
+		log_debug("%s: error enabling refclk (%d)\n", __func__, ret);
+
+	ret = regulator_set_enable_if_allowed(priv->vddc, true);
+	if (ret)
+		log_debug("%s: error enabling vddc (%d)\n", __func__, ret);
+
+	ret = regulator_set_enable_if_allowed(priv->vddmipi, true);
+	if (ret)
+		log_debug("%s: error enabling vddmipi (%d)\n", __func__, ret);
+
+	mdelay(10);
+
+	ret = regulator_set_enable_if_allowed(priv->vddio, true);
+	if (ret)
+		log_debug("%s: error enabling vddio (%d)\n", __func__, ret);
+
+	mdelay(2);
+
+	/*
+	 * The RESX is active low (GPIO_ACTIVE_LOW).
+	 * DEASSERT (value = 0) the reset_gpio to enable the chip
+	 */
+	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+	if (ret)
+		log_debug("%s: error changing reset-gpio (%d)\n", __func__, ret);
+
+	/* wait for encoder clocks to stabilize */
+	mdelay(2);
+}
+
+static u32 tc358768_pclk_to_pll(struct tc358768_priv *priv, u32 pclk)
+{
+	return (u32)div_u64((u64)pclk * priv->pd_lines, priv->dsi_lanes);
+}
+
+static int tc358768_calc_pll(struct tc358768_priv *priv,
+			     struct display_timing *dt)
+{
+	static const u32 frs_limits[] = {
+		1000000000,
+		500000000,
+		250000000,
+		125000000,
+		62500000
+	};
+	unsigned long refclk;
+	u32 prd, target_pll, i, max_pll, min_pll;
+	u32 frs, best_diff, best_pll, best_prd, best_fbd;
+
+	target_pll = tc358768_pclk_to_pll(priv, dt->pixelclock.typ);
+
+	/* pll_clk = RefClk * FBD / PRD * (1 / (2^FRS)) */
+
+	for (i = 0; i < ARRAY_SIZE(frs_limits); i++)
+		if (target_pll >= frs_limits[i])
+			break;
+
+	if (i == ARRAY_SIZE(frs_limits) || i == 0)
+		return -EINVAL;
+
+	frs = i - 1;
+	max_pll = frs_limits[i - 1];
+	min_pll = frs_limits[i];
+
+	refclk = clk_get_rate(priv->refclk);
+
+	best_diff = UINT_MAX;
+	best_pll = 0;
+	best_prd = 0;
+	best_fbd = 0;
+
+	for (prd = 1; prd <= 16; ++prd) {
+		u32 divisor = prd * (1 << frs);
+		u32 fbd;
+
+		for (fbd = 1; fbd <= 512; ++fbd) {
+			u32 pll, diff, pll_in;
+
+			pll = (u32)div_u64((u64)refclk * fbd, divisor);
+
+			if (pll >= max_pll || pll < min_pll)
+				continue;
+
+			pll_in = (u32)div_u64((u64)refclk, prd);
+			if (pll_in < 4000000)
+				continue;
+
+			diff = max(pll, target_pll) - min(pll, target_pll);
+
+			if (diff < best_diff) {
+				best_diff = diff;
+				best_pll = pll;
+				best_prd = prd;
+				best_fbd = fbd;
+
+				if (best_diff == 0)
+					goto found;
+			}
+		}
+	}
+
+	if (best_diff == UINT_MAX) {
+		log_debug("%s: could not find suitable PLL setup\n", __func__);
+		return -EINVAL;
+	}
+
+found:
+	priv->fbd = best_fbd;
+	priv->prd = best_prd;
+	priv->frs = frs;
+	priv->dsiclk = best_pll / 2;
+
+	return 0;
+}
+
+static void tc358768_setup_pll(struct udevice *dev)
+{
+	struct tc358768_priv *priv = dev_get_priv(dev);
+	u32 fbd, prd, frs;
+	int ret;
+
+	ret = tc358768_calc_pll(priv, &priv->timing);
+	if (ret)
+		log_debug("%s: PLL calculation failed: %d\n", __func__, ret);
+
+	fbd = priv->fbd;
+	prd = priv->prd;
+	frs = priv->frs;
+
+	log_debug("%s: PLL: refclk %lu, fbd %u, prd %u, frs %u\n", __func__,
+		  clk_get_rate(priv->refclk), fbd, prd, frs);
+	log_debug("%s: PLL: pll_clk: %u, DSIClk %u, HSByteClk %u\n", __func__,
+		  priv->dsiclk * 2, priv->dsiclk, priv->dsiclk / 4);
+
+	/* PRD[15:12] FBD[8:0] */
+	tc358768_write(dev, TC358768_PLLCTL0, ((prd - 1) << 12) | (fbd - 1));
+
+	/* FRS[11:10] LBWS[9:8] CKEN[4] RESETB[1] EN[0] */
+	tc358768_write(dev, TC358768_PLLCTL1,
+		       (frs << 10) | (0x2 << 8) | BIT(1) | BIT(0));
+
+	/* wait for lock */
+	mdelay(5);
+
+	/* FRS[11:10] LBWS[9:8] CKEN[4] PLL_CKEN[4] RESETB[1] EN[0] */
+	tc358768_write(dev, TC358768_PLLCTL1,
+		       (frs << 10) | (0x2 << 8) | BIT(4) | BIT(1) | BIT(0));
+}
+
+static u32 tc358768_ns_to_cnt(u32 ns, u32 period_ps)
+{
+	return DIV_ROUND_UP(ns * 1000, period_ps);
+}
+
+static u32 tc358768_ps_to_ns(u32 ps)
+{
+	return ps / 1000;
+}
+
+static u32 tc358768_dpi_to_ns(u32 val, u32 pclk)
+{
+	return (u32)div_u64((u64)val * NANO, pclk);
+}
+
+/* Convert value in DPI pixel clock units to DSI byte count */
+static u32 tc358768_dpi_to_dsi_bytes(struct tc358768_priv *priv, u32 val)
+{
+	u64 m = (u64)val * priv->dsiclk / 4 * priv->dsi_lanes;
+	u64 n = priv->timing.pixelclock.typ;
+
+	return (u32)div_u64(m + n - 1, n);
+}
+
+static u32 tc358768_dsi_bytes_to_ns(struct tc358768_priv *priv, u32 val)
+{
+	u64 m = (u64)val * NANO;
+	u64 n = priv->dsiclk / 4 * priv->dsi_lanes;
+
+	return (u32)div_u64(m, n);
+}
+
+static int tc358768_attach(struct udevice *dev)
+{
+	struct tc358768_priv *priv = dev_get_priv(dev);
+	struct mipi_dsi_device *device = &priv->device;
+	struct display_timing *dt = &priv->timing;
+	u32 val, val2, lptxcnt, hact, data_type;
+	s32 raw_val;
+	u32 hsbyteclk_ps, dsiclk_ps, ui_ps;
+	u32 dsiclk, hsbyteclk;
+	int i;
+	/* In pixelclock units */
+	u32 dpi_htot, dpi_data_start;
+	/* In byte units */
+	u32 dsi_dpi_htot, dsi_dpi_data_start;
+	u32 dsi_hsw, dsi_hbp, dsi_hact, dsi_hfp;
+	const u32 dsi_hss = 4; /* HSS is a short packet (4 bytes) */
+	/* In hsbyteclk units */
+	u32 dsi_vsdly;
+	const u32 internal_dly = 40;
+
+	if (device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
+		debug("%s: Non-continuous mode unimplemented, falling back to continuous\n", __func__);
+		device->mode_flags &= ~MIPI_DSI_CLOCK_NON_CONTINUOUS;
+	}
+
+	tc358768_hw_enable(priv);
+	tc358768_sw_reset(dev);
+
+	tc358768_setup_pll(dev);
+
+	dsiclk = priv->dsiclk;
+	hsbyteclk = dsiclk / 4;
+
+	/* Data Format Control Register */
+	val = BIT(2) | BIT(1) | BIT(0); /* rdswap_en | dsitx_en | txdt_en */
+	switch (device->format) {
+	case MIPI_DSI_FMT_RGB888:
+		val |= (0x3 << 4);
+		hact = dt->hactive.typ * 3;
+		data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24;
+		break;
+	case MIPI_DSI_FMT_RGB666:
+		val |= (0x4 << 4);
+		hact = dt->hactive.typ * 3;
+		data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18;
+		break;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		val |= (0x4 << 4) | BIT(3);
+		hact = dt->hactive.typ * 18 / 8;
+		data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18;
+		break;
+	case MIPI_DSI_FMT_RGB565:
+		val |= (0x5 << 4);
+		hact = dt->hactive.typ * 2;
+		data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16;
+		break;
+	default:
+		log_debug("%s: Invalid data format (%u)\n",
+			  __func__, device->format);
+		return -EINVAL;
+	}
+
+	/*
+	 * There are three important things to make TC358768 work correctly,
+	 * which are not trivial to manage:
+	 *
+	 * 1. Keep the DPI line-time and the DSI line-time as close to each
+	 *    other as possible.
+	 * 2. TC358768 goes to LP mode after each line's active area. The DSI
+	 *    HFP period has to be long enough for entering and exiting LP mode.
+	 *    But it is not clear how to calculate this.
+	 * 3. VSDly (video start delay) has to be long enough to ensure that the
+	 *    DSI TX does not start transmitting until we have started receiving
+	 *    pixel data from the DPI input. It is not clear how to calculate
+	 *    this either.
+	 */
+
+	dpi_htot = dt->hactive.typ + dt->hfront_porch.typ +
+		   dt->hsync_len.typ + dt->hback_porch.typ;
+	dpi_data_start = dt->hsync_len.typ + dt->hback_porch.typ;
+
+	log_debug("%s: dpi horiz timing (pclk): %u + %u + %u + %u = %u\n", __func__,
+		  dt->hsync_len.typ, dt->hback_porch.typ, dt->hactive.typ,
+		  dt->hfront_porch.typ, dpi_htot);
+
+	log_debug("%s: dpi horiz timing (ns): %u + %u + %u + %u = %u\n", __func__,
+		  tc358768_dpi_to_ns(dt->hsync_len.typ, dt->pixelclock.typ),
+		  tc358768_dpi_to_ns(dt->hback_porch.typ, dt->pixelclock.typ),
+		  tc358768_dpi_to_ns(dt->hactive.typ, dt->pixelclock.typ),
+		  tc358768_dpi_to_ns(dt->hfront_porch.typ, dt->pixelclock.typ),
+		  tc358768_dpi_to_ns(dpi_htot, dt->pixelclock.typ));
+
+	log_debug("%s: dpi data start (ns): %u + %u = %u\n", __func__,
+		  tc358768_dpi_to_ns(dt->hsync_len.typ, dt->pixelclock.typ),
+		  tc358768_dpi_to_ns(dt->hback_porch.typ, dt->pixelclock.typ),
+		  tc358768_dpi_to_ns(dpi_data_start, dt->pixelclock.typ));
+
+	dsi_dpi_htot = tc358768_dpi_to_dsi_bytes(priv, dpi_htot);
+	dsi_dpi_data_start = tc358768_dpi_to_dsi_bytes(priv, dpi_data_start);
+
+	if (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+		dsi_hsw = tc358768_dpi_to_dsi_bytes(priv, dt->hsync_len.typ);
+		dsi_hbp = tc358768_dpi_to_dsi_bytes(priv, dt->hback_porch.typ);
+	} else {
+		/* HBP is included in HSW in event mode */
+		dsi_hbp = 0;
+		dsi_hsw = tc358768_dpi_to_dsi_bytes(priv,
+						    dt->hsync_len.typ +
+						    dt->hback_porch.typ);
+
+		/*
+		 * The pixel packet includes the actual pixel data, and:
+		 * DSI packet header = 4 bytes
+		 * DCS code = 1 byte
+		 * DSI packet footer = 2 bytes
+		 */
+		dsi_hact = hact + 4 + 1 + 2;
+
+		dsi_hfp = dsi_dpi_htot - dsi_hact - dsi_hsw - dsi_hss;
+
+		/*
+		 * Here we should check if HFP is long enough for entering LP
+		 * and exiting LP, but it's not clear how to calculate that.
+		 * Instead, this is a naive algorithm that just adjusts the HFP
+		 * and HSW so that HFP is (at least) roughly 2/3 of the total
+		 * blanking time.
+		 */
+		if (dsi_hfp < (dsi_hfp + dsi_hsw + dsi_hss) * 2 / 3) {
+			u32 old_hfp = dsi_hfp;
+			u32 old_hsw = dsi_hsw;
+			u32 tot = dsi_hfp + dsi_hsw + dsi_hss;
+
+			dsi_hsw = tot / 3;
+
+			/*
+			 * Seems like sometimes HSW has to be divisible by num-lanes, but
+			 * not always...
+			 */
+			dsi_hsw = roundup(dsi_hsw, priv->dsi_lanes);
+
+			dsi_hfp = dsi_dpi_htot - dsi_hact - dsi_hsw - dsi_hss;
+
+			log_debug("%s: hfp too short, adjusting dsi hfp and dsi hsw from %u, %u to %u, %u\n",
+				  __func__, old_hfp, old_hsw, dsi_hfp, dsi_hsw);
+		}
+
+		log_debug("%s: dsi horiz timing (bytes): %u, %u + %u + %u + %u = %u\n", __func__,
+			  dsi_hss, dsi_hsw, dsi_hbp, dsi_hact, dsi_hfp,
+			  dsi_hss + dsi_hsw + dsi_hbp + dsi_hact + dsi_hfp);
+
+		log_debug("%s: dsi horiz timing (ns): %u + %u + %u + %u + %u = %u\n", __func__,
+			  tc358768_dsi_bytes_to_ns(priv, dsi_hss),
+			  tc358768_dsi_bytes_to_ns(priv, dsi_hsw),
+			  tc358768_dsi_bytes_to_ns(priv, dsi_hbp),
+			  tc358768_dsi_bytes_to_ns(priv, dsi_hact),
+			  tc358768_dsi_bytes_to_ns(priv, dsi_hfp),
+			  tc358768_dsi_bytes_to_ns(priv, dsi_hss + dsi_hsw +
+					       dsi_hbp + dsi_hact + dsi_hfp));
+	}
+
+	/* VSDly calculation */
+
+	/* Start with the HW internal delay */
+	dsi_vsdly = internal_dly;
+
+	/* Convert to byte units as the other variables are in byte units */
+	dsi_vsdly *= priv->dsi_lanes;
+
+	/* Do we need more delay, in addition to the internal? */
+	if (dsi_dpi_data_start > dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp) {
+		dsi_vsdly = dsi_dpi_data_start - dsi_hss - dsi_hsw - dsi_hbp;
+		dsi_vsdly = roundup(dsi_vsdly, priv->dsi_lanes);
+	}
+
+	log_debug("%s: dsi data start (bytes) %u + %u + %u + %u = %u\n", __func__,
+		  dsi_vsdly, dsi_hss, dsi_hsw, dsi_hbp,
+		  dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp);
+
+	log_debug("%s: dsi data start (ns) %u + %u + %u + %u = %u\n", __func__,
+		  tc358768_dsi_bytes_to_ns(priv, dsi_vsdly),
+		  tc358768_dsi_bytes_to_ns(priv, dsi_hss),
+		  tc358768_dsi_bytes_to_ns(priv, dsi_hsw),
+		  tc358768_dsi_bytes_to_ns(priv, dsi_hbp),
+		  tc358768_dsi_bytes_to_ns(priv, dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp));
+
+	/* Convert back to hsbyteclk */
+	dsi_vsdly /= priv->dsi_lanes;
+
+	/*
+	 * The docs say that there is an internal delay of 40 cycles.
+	 * However, we get underflows if we follow that rule. If we
+	 * instead ignore the internal delay, things work. So either
+	 * the docs are wrong or the calculations are wrong.
+	 *
+	 * As a temporary fix, add the internal delay here, to counter
+	 * the subtraction when writing the register.
+	 */
+	dsi_vsdly += internal_dly;
+
+	/* Clamp to the register max */
+	if (dsi_vsdly - internal_dly > 0x3ff) {
+		log_warning("%s: VSDly too high, underflows likely\n", __func__);
+		dsi_vsdly = 0x3ff + internal_dly;
+	}
+
+	/* VSDly[9:0] */
+	tc358768_write(dev, TC358768_VSDLY, dsi_vsdly - internal_dly);
+
+	tc358768_write(dev, TC358768_DATAFMT, val);
+	tc358768_write(dev, TC358768_DSITX_DT, data_type);
+
+	/* Enable D-PHY (HiZ->LP11) */
+	tc358768_write(dev, TC358768_CLW_CNTRL, 0x0000);
+	/* Enable lanes */
+	for (i = 0; i < device->lanes; i++)
+		tc358768_write(dev, TC358768_D0W_CNTRL + i * 4, 0x0000);
+
+	/* Set up D-PHY CONTTX */
+	tc358768_write(dev, TC358768_CLW_DPHYCONTTX, 0x0203);
+	/* Adjust lanes */
+	for (i = 0; i < device->lanes; i++)
+		tc358768_write(dev, TC358768_D0W_DPHYCONTTX + i * 4, 0x0203);
+
+	/* DSI Timings */
+	hsbyteclk_ps = (u32)div_u64(PICO, hsbyteclk);
+	dsiclk_ps = (u32)div_u64(PICO, dsiclk);
+	ui_ps = dsiclk_ps / 2;
+	log_debug("%s: dsiclk: %u ps, ui %u ps, hsbyteclk %u ps\n",
+		  __func__, dsiclk_ps, ui_ps, hsbyteclk_ps);
+
+	/* LP11 > 100us for D-PHY Rx Init */
+	val = tc358768_ns_to_cnt(100 * 1000, hsbyteclk_ps) - 1;
+	log_debug("%s: LINEINITCNT: 0x%x\n", __func__, val);
+	tc358768_write(dev, TC358768_LINEINITCNT, val);
+
+	/* LPTimeCnt > 50ns */
+	val = tc358768_ns_to_cnt(50, hsbyteclk_ps) - 1;
+	lptxcnt = val;
+	log_debug("%s: LPTXTIMECNT: 0x%x\n", __func__, val);
+	tc358768_write(dev, TC358768_LPTXTIMECNT, val);
+
+	/* 38ns < TCLK_PREPARE < 95ns */
+	val = tc358768_ns_to_cnt(65, hsbyteclk_ps) - 1;
+	log_debug("%s: TCLK_PREPARECNT: 0x%x\n", __func__, val);
+	/* TCLK_PREPARE + TCLK_ZERO > 300ns */
+	val2 = tc358768_ns_to_cnt(300 - tc358768_ps_to_ns(2 * ui_ps),
+				  hsbyteclk_ps) - 2;
+	log_debug("%s: TCLK_ZEROCNT: 0x%x\n", __func__, val2);
+	val |= val2 << 8;
+	tc358768_write(dev, TC358768_TCLK_HEADERCNT, val);
+
+	/* TCLK_TRAIL > 60ns AND TEOT <= 105 ns + 12*UI */
+	raw_val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(2 * ui_ps),
+				     hsbyteclk_ps) - 5;
+	val = clamp(raw_val, 0, 127);
+	log_debug("%s: TCLK_TRAILCNT: 0x%x\n", __func__, val);
+	tc358768_write(dev, TC358768_TCLK_TRAILCNT, val);
+
+	/* 40ns + 4*UI < THS_PREPARE < 85ns + 6*UI */
+	val = 50 + tc358768_ps_to_ns(4 * ui_ps);
+	val = tc358768_ns_to_cnt(val, hsbyteclk_ps) - 1;
+	log_debug("%s: THS_PREPARECNT: 0x%x\n", __func__, val);
+	/* THS_PREPARE + THS_ZERO > 145ns + 10*UI */
+	raw_val = tc358768_ns_to_cnt(145 - tc358768_ps_to_ns(3 * ui_ps),
+				     hsbyteclk_ps) - 10;
+	val2 = clamp(raw_val, 0, 127);
+	log_debug("%s: THS_ZEROCNT: 0x%x\n", __func__, val2);
+	val |= val2 << 8;
+	tc358768_write(dev, TC358768_THS_HEADERCNT, val);
+
+	/* TWAKEUP > 1ms in lptxcnt steps */
+	val = tc358768_ns_to_cnt(1020000, hsbyteclk_ps);
+	val = val / (lptxcnt + 1) - 1;
+	log_debug("%s: TWAKEUP: 0x%x\n", __func__, val);
+	tc358768_write(dev, TC358768_TWAKEUP, val);
+
+	/* TCLK_POSTCNT > 60ns + 52*UI */
+	val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(52 * ui_ps),
+				 hsbyteclk_ps) - 3;
+	log_debug("%s: TCLK_POSTCNT: 0x%x\n", __func__, val);
+	tc358768_write(dev, TC358768_TCLK_POSTCNT, val);
+
+	/* max(60ns + 4*UI, 8*UI) < THS_TRAILCNT < 105ns + 12*UI */
+	raw_val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(18 * ui_ps),
+				     hsbyteclk_ps) - 4;
+	val = clamp(raw_val, 0, 15);
+	log_debug("%s: THS_TRAILCNT: 0x%x\n", __func__, val);
+	tc358768_write(dev, TC358768_THS_TRAILCNT, val);
+
+	val = BIT(0);
+	for (i = 0; i < device->lanes; i++)
+		val |= BIT(i + 1);
+	tc358768_write(dev, TC358768_HSTXVREGEN, val);
+
+	tc358768_write(dev, TC358768_TXOPTIONCNTRL,
+		       (device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) ? 0 : BIT(0));
+
+	/* TXTAGOCNT[26:16] RXTASURECNT[10:0] */
+	val = tc358768_ps_to_ns((lptxcnt + 1) * hsbyteclk_ps * 4);
+	val = tc358768_ns_to_cnt(val, hsbyteclk_ps) / 4 - 1;
+	log_debug("%s: TXTAGOCNT: 0x%x\n", __func__, val);
+	val2 = tc358768_ns_to_cnt(tc358768_ps_to_ns((lptxcnt + 1) * hsbyteclk_ps),
+				  hsbyteclk_ps) - 2;
+	log_debug("%s: RXTASURECNT: 0x%x\n", __func__, val2);
+	val = val << 16 | val2;
+	tc358768_write(dev, TC358768_BTACNTRL1, val);
+
+	/* START[0] */
+	tc358768_write(dev, TC358768_STARTCNTRL, 1);
+
+	if (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+		/* Set pulse mode */
+		tc358768_write(dev, TC358768_DSI_EVENT, 0);
+
+		/* vact */
+		tc358768_write(dev, TC358768_DSI_VACT, dt->vactive.typ);
+		/* vsw */
+		tc358768_write(dev, TC358768_DSI_VSW, dt->vsync_len.typ);
+		/* vbp */
+		tc358768_write(dev, TC358768_DSI_VBPR, dt->vback_porch.typ);
+	} else {
+		/* Set event mode */
+		tc358768_write(dev, TC358768_DSI_EVENT, 1);
+
+		/* vact */
+		tc358768_write(dev, TC358768_DSI_VACT, dt->vactive.typ);
+
+		/* vsw (+ vbp) */
+		tc358768_write(dev, TC358768_DSI_VSW,
+			       dt->vsync_len.typ + dt->vback_porch.typ);
+		/* vbp (not used in event mode) */
+		tc358768_write(dev, TC358768_DSI_VBPR, 0);
+	}
+
+	/* hsw (bytes) */
+	tc358768_write(dev, TC358768_DSI_HSW, dsi_hsw);
+
+	/* hbp (bytes) */
+	tc358768_write(dev, TC358768_DSI_HBPR, dsi_hbp);
+
+	/* hact (bytes) */
+	tc358768_write(dev, TC358768_DSI_HACT, hact);
+
+	/* VSYNC polarity */
+	tc358768_update_bits(dev, TC358768_CONFCTL, BIT(5),
+			     (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH) ? BIT(5) : 0);
+
+	/* HSYNC polarity */
+	tc358768_update_bits(dev, TC358768_PP_MISC, BIT(0),
+			     (dt->flags & DISPLAY_FLAGS_HSYNC_LOW) ? BIT(0) : 0);
+
+	/* Start DSI Tx */
+	tc358768_write(dev, TC358768_DSI_START, 0x1);
+
+	/* Configure DSI_Control register */
+	val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL;
+	val |= TC358768_DSI_CONTROL_TXMD | TC358768_DSI_CONTROL_HSCKMD |
+	       0x3 << 1 | TC358768_DSI_CONTROL_EOTDIS;
+	tc358768_write(dev, TC358768_DSI_CONFW, val);
+
+	val = TC358768_DSI_CONFW_MODE_SET | TC358768_DSI_CONFW_ADDR_DSI_CONTROL;
+	val |= (device->lanes - 1) << 1;
+
+	val |= TC358768_DSI_CONTROL_TXMD;
+
+	if (!(device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
+		val |= TC358768_DSI_CONTROL_HSCKMD;
+
+	/*
+	 * TODO: Actually MIPI_DSI_MODE_NO_EOT_PACKET
+	 *
+	 * Many of the DSI flags have names opposite to their
+	 * actual effects, e.g. MIPI_DSI_MODE_EOT_PACKET means
+	 * that EoT packets will actually be disabled.
+	 */
+	if (device->mode_flags & MIPI_DSI_MODE_EOT_PACKET)
+		val |= TC358768_DSI_CONTROL_EOTDIS;
+
+	tc358768_write(dev, TC358768_DSI_CONFW, val);
+
+	val = TC358768_DSI_CONFW_MODE_CLR |
+	      TC358768_DSI_CONFW_ADDR_DSI_CONTROL |
+	      TC358768_DSI_CONTROL_DIS_MODE; /* DSI mode */
+	tc358768_write(dev, TC358768_DSI_CONFW, val);
+
+	/* clear FrmStop and RstPtr */
+	tc358768_update_bits(dev, TC358768_PP_MISC, 0x3 << 14, 0);
+
+	/* set PP_en */
+	tc358768_update_bits(dev, TC358768_CONFCTL, BIT(6), BIT(6));
+
+	/* Set up panel configuration */
+	return panel_enable_backlight(priv->panel);
+}
+
+static int tc358768_set_backlight(struct udevice *dev, int percent)
+{
+	struct tc358768_priv *priv = dev_get_priv(dev);
+
+	return panel_set_backlight(priv->panel, percent);
+}
+
+static int tc358768_panel_timings(struct udevice *dev,
+				  struct display_timing *timing)
+{
+	struct tc358768_priv *priv = dev_get_priv(dev);
+
+	/* Default to positive sync */
+
+	if (!(priv->timing.flags &
+	      (DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_HSYNC_HIGH)))
+		priv->timing.flags |= DISPLAY_FLAGS_HSYNC_HIGH;
+
+	if (!(priv->timing.flags &
+	      (DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_VSYNC_HIGH)))
+		priv->timing.flags |= DISPLAY_FLAGS_VSYNC_HIGH;
+
+	memcpy(timing, &priv->timing, sizeof(*timing));
+
+	return 0;
+}
+
+static int tc358768_setup(struct udevice *dev)
+{
+	struct tc358768_priv *priv = dev_get_priv(dev);
+	struct mipi_dsi_device *device = &priv->device;
+	struct mipi_dsi_panel_plat *mipi_plat;
+	int ret;
+
+	/* The bridge uses 16 bit registers */
+	ret = i2c_set_chip_offset_len(dev, 2);
+	if (ret) {
+		log_debug("%s: set_chip_offset_len failed: %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev,
+					   "panel", &priv->panel);
+	if (ret) {
+		log_debug("%s: Cannot get panel: ret=%d\n", __func__, ret);
+		return log_ret(ret);
+	}
+
+	panel_get_display_timing(priv->panel, &priv->timing);
+
+	mipi_plat = dev_get_plat(priv->panel);
+	mipi_plat->device = device;
+
+	priv->host.dev = (struct device *)dev;
+	priv->host.ops = &tc358768_dsi_host_ops;
+
+	device->host = &priv->host;
+	device->lanes = mipi_plat->lanes;
+	device->format = mipi_plat->format;
+	device->mode_flags = mipi_plat->mode_flags;
+
+	priv->pd_lines = mipi_dsi_pixel_format_to_bpp(device->format);
+	priv->dsi_lanes = device->lanes;
+
+	/* get regulators */
+	ret = device_get_supply_regulator(dev, "vddc-supply", &priv->vddc);
+	if (ret) {
+		log_debug("%s: vddc regulator error: %d\n", __func__, ret);
+		if (ret != -ENOENT)
+			return log_ret(ret);
+	}
+
+	ret = device_get_supply_regulator(dev, "vddmipi-supply", &priv->vddmipi);
+	if (ret) {
+		log_debug("%s: vddmipi regulator error: %d\n", __func__, ret);
+		if (ret != -ENOENT)
+			return log_ret(ret);
+	}
+
+	ret = device_get_supply_regulator(dev, "vddio-supply", &priv->vddio);
+	if (ret) {
+		log_debug("%s: vddio regulator error: %d\n", __func__, ret);
+		if (ret != -ENOENT)
+			return log_ret(ret);
+	}
+
+	/* get clk */
+	priv->refclk = devm_clk_get(dev, "refclk");
+	if (IS_ERR(priv->refclk)) {
+		log_debug("%s: Could not get refclk: %ld\n",
+			  __func__, PTR_ERR(priv->refclk));
+		return PTR_ERR(priv->refclk);
+	}
+
+	/* get gpios */
+	ret = gpio_request_by_name(dev, "reset-gpios", 0,
+				   &priv->reset_gpio, GPIOD_IS_OUT);
+	if (ret) {
+		log_debug("%s: Could not decode reset-gpios (%d)\n", __func__, ret);
+		return ret;
+	}
+
+	dm_gpio_set_value(&priv->reset_gpio, 1);
+
+	return 0;
+}
+
+static int tc358768_probe(struct udevice *dev)
+{
+	if (device_get_uclass_id(dev->parent) != UCLASS_I2C)
+		return -EPROTONOSUPPORT;
+
+	return tc358768_setup(dev);
+}
+
+struct panel_ops tc358768_ops = {
+	.enable_backlight	= tc358768_attach,
+	.set_backlight		= tc358768_set_backlight,
+	.get_display_timing	= tc358768_panel_timings,
+};
+
+static const struct udevice_id tc358768_ids[] = {
+	{ .compatible = "toshiba,tc358768" },
+	{ .compatible = "toshiba,tc358778" },
+	{ }
+};
+
+U_BOOT_DRIVER(tc358768) = {
+	.name		= "tc358768",
+	.id		= UCLASS_PANEL,
+	.of_match	= tc358768_ids,
+	.ops		= &tc358768_ops,
+	.probe		= tc358768_probe,
+	.priv_auto	= sizeof(struct tc358768_priv),
+};
diff --git a/drivers/video/dw_hdmi.c b/drivers/video/dw_hdmi.c
index c4fbb18..c217af9 100644
--- a/drivers/video/dw_hdmi.c
+++ b/drivers/video/dw_hdmi.c
@@ -78,10 +78,10 @@
 {
 	switch (hdmi->reg_io_width) {
 	case 1:
-		writeb(val, hdmi->ioaddr + offset);
+		writeb(val, (void *)(hdmi->ioaddr + offset));
 		break;
 	case 4:
-		writel(val, hdmi->ioaddr + (offset << 2));
+		writel(val, (void *)(hdmi->ioaddr + (offset << 2)));
 		break;
 	default:
 		debug("reg_io_width has unsupported width!\n");
@@ -93,9 +93,9 @@
 {
 	switch (hdmi->reg_io_width) {
 	case 1:
-		return readb(hdmi->ioaddr + offset);
+		return readb((void *)(hdmi->ioaddr + offset));
 	case 4:
-		return readl(hdmi->ioaddr + (offset << 2));
+		return readl((void *)(hdmi->ioaddr + (offset << 2)));
 	default:
 		debug("reg_io_width has unsupported width!\n");
 		break;
@@ -936,6 +936,22 @@
 	return -1;
 }
 
+int dw_hdmi_detect_hpd(struct dw_hdmi *hdmi)
+{
+	int ret;
+
+	ret = dw_hdmi_phy_wait_for_hpd(hdmi);
+	if (ret < 0) {
+		debug("hdmi can not get hpd signal\n");
+		return -ENODEV;
+	}
+
+	if (hdmi->ops && hdmi->ops->read_hpd)
+		hdmi->ops->read_hpd(hdmi, true);
+
+	return 0;
+}
+
 void dw_hdmi_phy_init(struct dw_hdmi *hdmi)
 {
 	/* enable phy i2cm done irq */
@@ -988,7 +1004,7 @@
 
 	hdmi_av_composer(hdmi, edid);
 
-	ret = hdmi->phy_set(hdmi, edid->pixelclock.typ);
+	ret = hdmi->ops->phy_set(hdmi, edid->pixelclock.typ);
 	if (ret)
 		return ret;
 
@@ -1009,10 +1025,18 @@
 	return 0;
 }
 
+static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = {
+	.phy_set = dw_hdmi_phy_cfg,
+};
+
 void dw_hdmi_init(struct dw_hdmi *hdmi)
 {
 	uint ih_mute;
 
+	/* hook Synopsys PHYs ops */
+	if (!hdmi->ops)
+		hdmi->ops = &dw_hdmi_synopsys_phy_ops;
+
 	/*
 	 * boot up defaults are:
 	 * hdmi_ih_mute   = 0x03 (disabled)
@@ -1037,4 +1061,7 @@
 
 	/* enable i2c client nack % arbitration error irq */
 	hdmi_write(hdmi, ~0x44, HDMI_I2CM_CTLINT);
+
+	if (hdmi->ops && hdmi->ops->setup_hpd)
+		hdmi->ops->setup_hpd(hdmi);
 }
diff --git a/drivers/video/endeavoru-panel.c b/drivers/video/endeavoru-panel.c
index 79a2721..1bff641 100644
--- a/drivers/video/endeavoru-panel.c
+++ b/drivers/video/endeavoru-panel.c
@@ -57,61 +57,8 @@
 
 static int endeavoru_panel_enable_backlight(struct udevice *dev)
 {
-	struct endeavoru_panel_priv *priv = dev_get_priv(dev);
-	int ret;
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
-	if (ret) {
-		log_err("error changing reset-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(5);
-
-	ret = regulator_set_enable_if_allowed(priv->vddio, 1);
-	if (ret) {
-		log_err("error enabling iovcc-supply (%d)\n", ret);
-		return ret;
-	}
-	mdelay(1);
-
-	ret = regulator_set_enable_if_allowed(priv->vdd, 1);
-	if (ret) {
-		log_err("error enabling vcc-supply (%d)\n", ret);
-		return ret;
-	}
-	mdelay(20);
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
-	if (ret) {
-		log_err("error changing reset-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(2);
-
-	/* Reset panel */
-	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
-	if (ret) {
-		log_err("error changing reset-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(1);
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
-	if (ret) {
-		log_err("error changing reset-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(25);
-
-	return 0;
-}
-
-static int endeavoru_panel_set_backlight(struct udevice *dev, int percent)
-{
-	struct endeavoru_panel_priv *priv = dev_get_priv(dev);
 	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
 	struct mipi_dsi_device *dsi = plat->device;
-	int ret;
 
 	dcs_write_one(dsi, 0xc2, 0x08);
 
@@ -160,18 +107,22 @@
 	dcs_write_one(dsi, 0x55, 0x80);
 	dcs_write_one(dsi, 0x5e, 0x06);
 
+	/* Set backlight */
+	dcs_write_one(dsi, 0x51, 0x96);
+
+	return 0;
+}
+
+static int endeavoru_panel_set_backlight(struct udevice *dev, int percent)
+{
+	struct endeavoru_panel_priv *priv = dev_get_priv(dev);
+	int ret;
+
 	ret = backlight_enable(priv->backlight);
 	if (ret)
 		return ret;
 
-	/* Set backlight */
-	dcs_write_one(dsi, 0x51, 0x96);
-
-	ret = backlight_set_brightness(priv->backlight, percent);
-	if (ret)
-		return ret;
-
-	return 0;
+	return backlight_set_brightness(priv->backlight, percent);
 }
 
 static int endeavoru_panel_timings(struct udevice *dev,
@@ -217,6 +168,63 @@
 	return 0;
 }
 
+static int endeavoru_panel_hw_init(struct udevice *dev)
+{
+	struct endeavoru_panel_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+	if (ret) {
+		log_debug("%s: error changing reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(5);
+
+	ret = regulator_set_enable_if_allowed(priv->vddio, 1);
+	if (ret) {
+		log_debug("%s: error enabling iovcc-supply (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(1);
+
+	ret = regulator_set_enable_if_allowed(priv->vdd, 1);
+	if (ret) {
+		log_debug("%s: error enabling vcc-supply (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(20);
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+	if (ret) {
+		log_debug("%s: error changing reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(2);
+
+	/* Reset panel */
+	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+	if (ret) {
+		log_debug("%s: error changing reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(1);
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+	if (ret) {
+		log_debug("%s: error changing reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(25);
+
+	return 0;
+}
+
 static int endeavoru_panel_probe(struct udevice *dev)
 {
 	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
@@ -226,7 +234,7 @@
 	plat->format = MIPI_DSI_FMT_RGB888;
 	plat->mode_flags = MIPI_DSI_MODE_VIDEO;
 
-	return 0;
+	return endeavoru_panel_hw_init(dev);
 }
 
 static const struct panel_ops endeavoru_panel_ops = {
diff --git a/drivers/video/lg-ld070wx3.c b/drivers/video/lg-ld070wx3.c
new file mode 100644
index 0000000..610a06f
--- /dev/null
+++ b/drivers/video/lg-ld070wx3.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * LG LD070WX3-SL01 DSI panel driver
+ *
+ * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <backlight.h>
+#include <dm.h>
+#include <panel.h>
+#include <log.h>
+#include <mipi_dsi.h>
+#include <linux/delay.h>
+#include <power/regulator.h>
+
+struct lg_ld070wx3_priv {
+	struct udevice *vdd;
+	struct udevice *vcc;
+
+	struct udevice *backlight;
+};
+
+static struct display_timing default_timing = {
+	.pixelclock.typ		= 70000000,
+	.hactive.typ		= 800,
+	.hfront_porch.typ	= 32,
+	.hback_porch.typ	= 48,
+	.hsync_len.typ		= 8,
+	.vactive.typ		= 1280,
+	.vfront_porch.typ	= 5,
+	.vback_porch.typ	= 3,
+	.vsync_len.typ		= 1,
+};
+
+static void dcs_write_one(struct mipi_dsi_device *dsi, u8 cmd, u8 data)
+{
+	mipi_dsi_dcs_write(dsi, cmd, &data, 1);
+}
+
+static int lg_ld070wx3_enable_backlight(struct udevice *dev)
+{
+	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
+	struct mipi_dsi_device *dsi = plat->device;
+	int ret;
+
+	ret = mipi_dsi_dcs_soft_reset(dsi);
+	if (ret < 0) {
+		log_debug("%s: failed to soft reset panel: %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	/* Delay before sending new command after soft reset */
+	mdelay(20);
+
+	/* Differential input impedance selection */
+	dcs_write_one(dsi, 0xAE, 0x0B);
+
+	/* Enter test mode 1 and 2*/
+	dcs_write_one(dsi, 0xEE, 0xEA);
+	dcs_write_one(dsi, 0xEF, 0x5F);
+
+	/* Increased MIPI CLK driving ability */
+	dcs_write_one(dsi, 0xF2, 0x68);
+
+	/* Exit test mode 1 and 2 */
+	dcs_write_one(dsi, 0xEE, 0x00);
+	dcs_write_one(dsi, 0xEF, 0x00);
+
+	return 0;
+}
+
+static int lg_ld070wx3_set_backlight(struct udevice *dev, int percent)
+{
+	struct lg_ld070wx3_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = backlight_enable(priv->backlight);
+	if (ret)
+		return ret;
+
+	return backlight_set_brightness(priv->backlight, percent);
+}
+
+static int lg_ld070wx3_timings(struct udevice *dev,
+			       struct display_timing *timing)
+{
+	memcpy(timing, &default_timing, sizeof(*timing));
+	return 0;
+}
+
+static int lg_ld070wx3_of_to_plat(struct udevice *dev)
+{
+	struct lg_ld070wx3_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
+					   "backlight", &priv->backlight);
+	if (ret) {
+		log_debug("%s: cannot get backlight: ret = %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
+					   "vdd-supply", &priv->vdd);
+	if (ret) {
+		log_debug("%s: cannot get vdd-supply: ret = %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
+					   "vcc-supply", &priv->vcc);
+	if (ret) {
+		log_debug("%s: cannot get vcc-supply: ret = %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lg_ld070wx3_hw_init(struct udevice *dev)
+{
+	struct lg_ld070wx3_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = regulator_set_enable_if_allowed(priv->vcc, 1);
+	if (ret) {
+		log_debug("%s: enabling vcc-supply failed (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = regulator_set_enable_if_allowed(priv->vdd, 1);
+	if (ret) {
+		log_debug("%s: enabling vdd-supply failed (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	/*
+	 * According to spec delay between enabling supply is 0,
+	 * for regulators to reach required voltage ~5ms needed.
+	 * MIPI interface signal for setup requires additional
+	 * 110ms which in total results in 115ms.
+	 */
+	mdelay(115);
+
+	return 0;
+}
+
+static int lg_ld070wx3_probe(struct udevice *dev)
+{
+	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
+
+	/* fill characteristics of DSI data link */
+	plat->lanes = 4;
+	plat->format = MIPI_DSI_FMT_RGB888;
+	plat->mode_flags = MIPI_DSI_MODE_VIDEO;
+
+	return lg_ld070wx3_hw_init(dev);
+}
+
+static const struct panel_ops lg_ld070wx3_ops = {
+	.enable_backlight	= lg_ld070wx3_enable_backlight,
+	.set_backlight		= lg_ld070wx3_set_backlight,
+	.get_display_timing	= lg_ld070wx3_timings,
+};
+
+static const struct udevice_id lg_ld070wx3_ids[] = {
+	{ .compatible = "lg,ld070wx3-sl01" },
+	{ }
+};
+
+U_BOOT_DRIVER(lg_ld070wx3) = {
+	.name		= "lg_ld070wx3",
+	.id		= UCLASS_PANEL,
+	.of_match	= lg_ld070wx3_ids,
+	.ops		= &lg_ld070wx3_ops,
+	.of_to_plat	= lg_ld070wx3_of_to_plat,
+	.probe		= lg_ld070wx3_probe,
+	.plat_auto	= sizeof(struct mipi_dsi_panel_plat),
+	.priv_auto	= sizeof(struct lg_ld070wx3_priv),
+};
diff --git a/drivers/video/meson/meson_dw_hdmi.c b/drivers/video/meson/meson_dw_hdmi.c
index 5db0190..259af1b 100644
--- a/drivers/video/meson/meson_dw_hdmi.c
+++ b/drivers/video/meson/meson_dw_hdmi.c
@@ -375,6 +375,10 @@
 	return -ETIMEDOUT;
 }
 
+static const struct dw_hdmi_phy_ops dw_hdmi_meson_phy_ops = {
+	.phy_set = meson_dw_hdmi_phy_init,
+};
+
 static int meson_dw_hdmi_probe(struct udevice *dev)
 {
 	struct meson_dw_hdmi *priv = dev_get_priv(dev);
@@ -397,7 +401,7 @@
 
 	priv->hdmi.hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
 	priv->hdmi.hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_YUV8_1X24;
-	priv->hdmi.phy_set = meson_dw_hdmi_phy_init;
+	priv->hdmi.ops = &dw_hdmi_meson_phy_ops;
 	if (meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_G12A))
 		priv->hdmi.reg_io_width = 1;
 	else {
diff --git a/drivers/video/renesas-r61307.c b/drivers/video/renesas-r61307.c
index 426fdc6..1eccaf6 100644
--- a/drivers/video/renesas-r61307.c
+++ b/drivers/video/renesas-r61307.c
@@ -120,42 +120,6 @@
 static int renesas_r61307_enable_backlight(struct udevice *dev)
 {
 	struct renesas_r61307_priv *priv = dev_get_priv(dev);
-	int ret;
-
-	ret = regulator_set_enable_if_allowed(priv->vcc, 1);
-	if (ret) {
-		log_err("enabling vcc-supply failed (%d)\n", ret);
-		return ret;
-	}
-	mdelay(5);
-
-	ret = regulator_set_enable_if_allowed(priv->iovcc, 1);
-	if (ret) {
-		log_err("enabling iovcc-supply failed (%d)\n", ret);
-		return ret;
-	}
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
-	if (ret) {
-		log_err("changing reset-gpio failed (%d)\n", ret);
-		return ret;
-	}
-	mdelay(5);
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
-	if (ret) {
-		log_err("changing reset-gpio failed (%d)\n", ret);
-		return ret;
-	}
-
-	mdelay(5);
-
-	return 0;
-}
-
-static int renesas_r61307_set_backlight(struct udevice *dev, int percent)
-{
-	struct renesas_r61307_priv *priv = dev_get_priv(dev);
 	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
 	struct mipi_dsi_device *dsi = plat->device;
 	int ret;
@@ -205,18 +169,23 @@
 		log_err("failed to set display on: %d\n", ret);
 		return ret;
 	}
-
 	mdelay(50);
 
+	return 0;
+}
+
+static int renesas_r61307_set_backlight(struct udevice *dev, int percent)
+{
+	struct renesas_r61307_priv *priv = dev_get_priv(dev);
+	int ret;
+
 	ret = backlight_enable(priv->backlight);
 	if (ret)
 		return ret;
 
-	ret = backlight_set_brightness(priv->backlight, percent);
-	if (ret)
-		return ret;
+	mdelay(5);
 
-	return 0;
+	return backlight_set_brightness(priv->backlight, percent);
 }
 
 static int renesas_r61307_timings(struct udevice *dev,
@@ -266,6 +235,46 @@
 	return 0;
 }
 
+static int renesas_r61307_hw_init(struct udevice *dev)
+{
+	struct renesas_r61307_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = regulator_set_enable_if_allowed(priv->vcc, 1);
+	if (ret) {
+		log_debug("%s: enabling vcc-supply failed (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(5);
+
+	ret = regulator_set_enable_if_allowed(priv->iovcc, 1);
+	if (ret) {
+		log_debug("%s: enabling iovcc-supply failed (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+	if (ret) {
+		log_debug("%s: changing reset-gpio failed (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(5);
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+	if (ret) {
+		log_debug("%s: changing reset-gpio failed (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	mdelay(5);
+
+	return 0;
+}
+
 static int renesas_r61307_probe(struct udevice *dev)
 {
 	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
@@ -275,7 +284,7 @@
 	plat->format = MIPI_DSI_FMT_RGB888;
 	plat->mode_flags = MIPI_DSI_MODE_VIDEO;
 
-	return 0;
+	return renesas_r61307_hw_init(dev);
 }
 
 static const struct panel_ops renesas_r61307_ops = {
diff --git a/drivers/video/renesas-r69328.c b/drivers/video/renesas-r69328.c
index d2f7169..ecf89ec 100644
--- a/drivers/video/renesas-r69328.c
+++ b/drivers/video/renesas-r69328.c
@@ -65,37 +65,6 @@
 
 static int renesas_r69328_enable_backlight(struct udevice *dev)
 {
-	struct renesas_r69328_priv *priv = dev_get_priv(dev);
-	int ret;
-
-	ret = dm_gpio_set_value(&priv->enable_gpio, 1);
-	if (ret) {
-		log_err("error changing enable-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(5);
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
-	if (ret) {
-		log_err("error changing reset-gpios (%d)\n", ret);
-		return ret;
-	}
-	mdelay(5);
-
-	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
-	if (ret) {
-		log_err("error changing reset-gpios (%d)\n", ret);
-		return ret;
-	}
-
-	mdelay(5);
-
-	return 0;
-}
-
-static int renesas_r69328_set_backlight(struct udevice *dev, int percent)
-{
-	struct renesas_r69328_priv *priv = dev_get_priv(dev);
 	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
 	struct mipi_dsi_device *dsi = plat->device;
 	int ret;
@@ -153,18 +122,23 @@
 		log_err("failed to set display on: %d\n", ret);
 		return ret;
 	}
-
 	mdelay(50);
 
+	return 0;
+}
+
+static int renesas_r69328_set_backlight(struct udevice *dev, int percent)
+{
+	struct renesas_r69328_priv *priv = dev_get_priv(dev);
+	int ret;
+
 	ret = backlight_enable(priv->backlight);
 	if (ret)
 		return ret;
 
-	ret = backlight_set_brightness(priv->backlight, percent);
-	if (ret)
-		return ret;
+	mdelay(5);
 
-	return 0;
+	return backlight_set_brightness(priv->backlight, percent);
 }
 
 static int renesas_r69328_timings(struct udevice *dev,
@@ -203,6 +177,39 @@
 	return 0;
 }
 
+static int renesas_r69328_hw_init(struct udevice *dev)
+{
+	struct renesas_r69328_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = dm_gpio_set_value(&priv->enable_gpio, 1);
+	if (ret) {
+		log_debug("%s: error changing enable-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(5);
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+	if (ret) {
+		log_debug("%s: error changing reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(5);
+
+	ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+	if (ret) {
+		log_debug("%s: error changing reset-gpios (%d)\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	mdelay(5);
+
+	return 0;
+}
+
 static int renesas_r69328_probe(struct udevice *dev)
 {
 	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
@@ -212,7 +219,7 @@
 	plat->format = MIPI_DSI_FMT_RGB888;
 	plat->mode_flags = MIPI_DSI_MODE_VIDEO;
 
-	return 0;
+	return renesas_r69328_hw_init(dev);
 }
 
 static const struct panel_ops renesas_r69328_ops = {
diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile
index 8128289..f55bece 100644
--- a/drivers/video/rockchip/Makefile
+++ b/drivers/video/rockchip/Makefile
@@ -6,10 +6,12 @@
 ifdef CONFIG_VIDEO_ROCKCHIP
 obj-y += rk_vop.o
 obj-$(CONFIG_ROCKCHIP_RK3288) += rk3288_vop.o
+obj-$(CONFIG_ROCKCHIP_RK3328) += rk3328_vop.o
 obj-$(CONFIG_ROCKCHIP_RK3399) += rk3399_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
+obj-hdmi-$(CONFIG_ROCKCHIP_RK3328) += rk3328_hdmi.o
 obj-hdmi-$(CONFIG_ROCKCHIP_RK3399) += rk3399_hdmi.o
 obj-$(CONFIG_DISPLAY_ROCKCHIP_HDMI) += rk_hdmi.o $(obj-hdmi-y)
 obj-mipi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_mipi.o
diff --git a/drivers/video/rockchip/rk3328_hdmi.c b/drivers/video/rockchip/rk3328_hdmi.c
new file mode 100644
index 0000000..763669c
--- /dev/null
+++ b/drivers/video/rockchip/rk3328_hdmi.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd.
+ */
+
+#include <clk.h>
+#include <display.h>
+#include <dm.h>
+#include <dw_hdmi.h>
+#include <asm/io.h>
+#include <asm/arch-rockchip/grf_rk3328.h>
+#include "rk_hdmi.h"
+
+#define RK3328_IO_3V_DOMAIN              (7 << (9 + 16))
+#define RK3328_IO_5V_DOMAIN              ((7 << 9) | (3 << (9 + 16)))
+#define RK3328_IO_DDC_IN_MSK             ((3 << 10) | (3 << (10 + 16)))
+#define RK3328_IO_CTRL_BY_HDMI           ((1 << 13) | (1 << (13 + 16)))
+
+static int rk3328_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 rk3328_dw_hdmi_phy_cfg(struct dw_hdmi *hdmi, uint pixclock)
+{
+	struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi);
+	int ret;
+
+	ret = generic_phy_init(&priv->phy);
+	if (ret) {
+		printf("failed to init phy (ret=%d)\n", ret);
+		return ret;
+	}
+
+	ret = generic_phy_power_on(&priv->phy);
+	if (ret) {
+		printf("failed to power on phy (ret=%d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void rk3328_dw_hdmi_setup_hpd(struct dw_hdmi *hdmi)
+{
+	struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi);
+	struct rk3328_grf_regs *grf = priv->grf;
+
+	writel(RK3328_IO_DDC_IN_MSK, &grf->soc_con[2]);
+	writel(RK3328_IO_CTRL_BY_HDMI, &grf->soc_con[3]);
+}
+
+static void rk3328_dw_hdmi_read_hpd(struct dw_hdmi *hdmi, bool hpd_status)
+{
+	struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi);
+	struct rk3328_grf_regs *grf = priv->grf;
+
+	if (hpd_status)
+		writel(RK3328_IO_5V_DOMAIN, &grf->soc_con[4]);
+	else
+		writel(RK3328_IO_3V_DOMAIN, &grf->soc_con[4]);
+}
+
+static const struct dw_hdmi_phy_ops dw_hdmi_rk3328_phy_ops = {
+	.phy_set = rk3328_dw_hdmi_phy_cfg,
+	.setup_hpd = rk3328_dw_hdmi_setup_hpd,
+	.read_hpd = rk3328_dw_hdmi_read_hpd,
+};
+
+static int rk3328_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 = 0x71;
+	hdmi->i2c_clk_low = 0x76;
+
+	rk_hdmi_of_to_plat(dev);
+
+	hdmi->ops = &dw_hdmi_rk3328_phy_ops;
+
+	return 0;
+}
+
+static int rk3328_hdmi_probe(struct udevice *dev)
+{
+	struct rk_hdmi_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = generic_phy_get_by_name(dev, "hdmi", &priv->phy);
+	if (ret) {
+		printf("failed to get hdmi phy\n");
+		return ret;
+	};
+
+	ret = rk_hdmi_probe(dev);
+	if (ret) {
+		printf("failed to probe rk hdmi\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct dm_display_ops rk3328_hdmi_ops = {
+	.read_edid = rk_hdmi_read_edid,
+	.enable = rk3328_hdmi_enable,
+};
+
+static const struct udevice_id rk3328_hdmi_ids[] = {
+	{ .compatible = "rockchip,rk3328-dw-hdmi" },
+	{ }
+};
+
+U_BOOT_DRIVER(rk3328_hdmi_rockchip) = {
+	.name = "rk3328_hdmi_rockchip",
+	.id = UCLASS_DISPLAY,
+	.of_match = rk3328_hdmi_ids,
+	.ops = &rk3328_hdmi_ops,
+	.of_to_plat = rk3328_hdmi_of_to_plat,
+	.probe = rk3328_hdmi_probe,
+	.priv_auto	= sizeof(struct rk_hdmi_priv),
+};
diff --git a/drivers/video/rockchip/rk3328_vop.c b/drivers/video/rockchip/rk3328_vop.c
new file mode 100644
index 0000000..55233f1
--- /dev/null
+++ b/drivers/video/rockchip/rk3328_vop.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd.
+ */
+
+#include <dm.h>
+#include <video.h>
+#include <asm/io.h>
+#include "rk_vop.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static void rk3328_set_pin_polarity(struct udevice *dev,
+				    enum vop_modes mode, u32 polarity)
+{
+	struct rk_vop_priv *priv = dev_get_priv(dev);
+	struct rk3288_vop *regs = priv->regs;
+
+	switch (mode) {
+	case VOP_MODE_HDMI:
+		clrsetbits_le32(&regs->dsp_ctrl1,
+				M_RK3399_DSP_HDMI_POL,
+				V_RK3399_DSP_HDMI_POL(polarity));
+		break;
+	default:
+		debug("%s: unsupported output mode %x\n", __func__, mode);
+	}
+}
+
+static int rk3328_vop_probe(struct udevice *dev)
+{
+	/* Before relocation we don't need to do anything */
+	if (!(gd->flags & GD_FLG_RELOC))
+		return 0;
+
+	return rk_vop_probe(dev);
+}
+
+static int rk3328_vop_remove(struct udevice *dev)
+{
+	struct rk_vop_priv *priv = dev_get_priv(dev);
+	struct rk3288_vop *regs = priv->regs;
+	struct rk3288_vop *win_regs = priv->regs + priv->win_offset;
+
+	/* FIXME: Explicit disabling of WIN0 is needed to avoid iommu
+	 * page-fault in Linux, better handling of iommu-address in
+	 * Linux might drop this.
+	 */
+	clrbits_le32(&win_regs->win0_ctrl0, M_WIN0_EN);
+	writel(0x01, &regs->reg_cfg_done);
+
+	return 0;
+}
+
+struct rkvop_driverdata rk3328_driverdata = {
+	.dsp_offset = 0x490,
+	.win_offset = 0xd0,
+	.features = VOP_FEATURE_OUTPUT_10BIT,
+	.set_pin_polarity = rk3328_set_pin_polarity,
+};
+
+static const struct udevice_id rk3328_vop_ids[] = {
+	{
+		.compatible = "rockchip,rk3328-vop",
+		.data = (ulong)&rk3328_driverdata
+	},
+	{ /* sentile */ }
+};
+
+static const struct video_ops rk3328_vop_ops = {
+};
+
+U_BOOT_DRIVER(rk3328_vop) = {
+	.name	= "rk3328_vop",
+	.id	= UCLASS_VIDEO,
+	.of_match = rk3328_vop_ids,
+	.ops	= &rk3328_vop_ops,
+	.bind	= rk_vop_bind,
+	.probe	= rk3328_vop_probe,
+	.remove = rk3328_vop_remove,
+	.priv_auto	= sizeof(struct rk_vop_priv),
+	.flags	= DM_FLAG_PRE_RELOC | DM_FLAG_OS_PREPARE,
+};
diff --git a/drivers/video/rockchip/rk_hdmi.c b/drivers/video/rockchip/rk_hdmi.c
index 044a29e..d31f6a4 100644
--- a/drivers/video/rockchip/rk_hdmi.c
+++ b/drivers/video/rockchip/rk_hdmi.c
@@ -89,7 +89,6 @@
 	/* hdmi->i2c_clk_{high,low} are set up by the SoC driver */
 
 	hdmi->reg_io_width = 4;
-	hdmi->phy_set = dw_hdmi_phy_cfg;
 
 	priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
 
@@ -111,14 +110,12 @@
 	struct dw_hdmi *hdmi = &priv->hdmi;
 	int ret;
 
-	ret = dw_hdmi_phy_wait_for_hpd(hdmi);
-	if (ret < 0) {
-		debug("hdmi can not get hpd signal\n");
-		return -1;
-	}
-
 	dw_hdmi_init(hdmi);
 	dw_hdmi_phy_init(hdmi);
 
+	ret = dw_hdmi_detect_hpd(hdmi);
+	if (ret < 0)
+		return ret;
+
 	return 0;
 }
diff --git a/drivers/video/rockchip/rk_hdmi.h b/drivers/video/rockchip/rk_hdmi.h
index 200dbae..dcfba3d 100644
--- a/drivers/video/rockchip/rk_hdmi.h
+++ b/drivers/video/rockchip/rk_hdmi.h
@@ -6,6 +6,8 @@
 #ifndef __RK_HDMI_H__
 #define __RK_HDMI_H__
 
+#include <generic-phy.h>
+
 struct rkhdmi_driverdata {
 	/* configuration */
 	u8 i2c_clk_high;
@@ -19,6 +21,7 @@
 
 struct rk_hdmi_priv {
 	struct dw_hdmi hdmi;
+	struct phy phy;
 	void *grf;
 };
 
diff --git a/drivers/video/rockchip/rk_vop.c b/drivers/video/rockchip/rk_vop.c
index c514e2a..acc02e5 100644
--- a/drivers/video/rockchip/rk_vop.c
+++ b/drivers/video/rockchip/rk_vop.c
@@ -39,11 +39,14 @@
 	DCLK_INVERT    = 3
 };
 
-static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbbase,
+static void rkvop_enable(struct udevice *dev, ulong fbbase,
 			 int fb_bits_per_pixel,
 			 const struct display_timing *edid,
 			 struct reset_ctl *dclk_rst)
 {
+	struct rk_vop_priv *priv = dev_get_priv(dev);
+	struct rk3288_vop *regs = priv->regs;
+	struct rk3288_vop *win_regs = priv->regs + priv->win_offset;
 	u32 lb_mode;
 	u32 rgb_mode;
 	u32 hactive = edid->hactive.typ;
@@ -51,32 +54,32 @@
 	int ret;
 
 	writel(V_ACT_WIDTH(hactive - 1) | V_ACT_HEIGHT(vactive - 1),
-	       &regs->win0_act_info);
+	       &win_regs->win0_act_info);
 
 	writel(V_DSP_XST(edid->hsync_len.typ + edid->hback_porch.typ) |
 	       V_DSP_YST(edid->vsync_len.typ + edid->vback_porch.typ),
-	       &regs->win0_dsp_st);
+	       &win_regs->win0_dsp_st);
 
 	writel(V_DSP_WIDTH(hactive - 1) |
 		V_DSP_HEIGHT(vactive - 1),
-		&regs->win0_dsp_info);
+		&win_regs->win0_dsp_info);
 
-	clrsetbits_le32(&regs->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR,
+	clrsetbits_le32(&win_regs->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR,
 			V_WIN0_KEY_EN(0) | V_WIN0_KEY_COLOR(0));
 
 	switch (fb_bits_per_pixel) {
 	case 16:
 		rgb_mode = RGB565;
-		writel(V_RGB565_VIRWIDTH(hactive), &regs->win0_vir);
+		writel(V_RGB565_VIRWIDTH(hactive), &win_regs->win0_vir);
 		break;
 	case 24:
 		rgb_mode = RGB888;
-		writel(V_RGB888_VIRWIDTH(hactive), &regs->win0_vir);
+		writel(V_RGB888_VIRWIDTH(hactive), &win_regs->win0_vir);
 		break;
 	case 32:
 	default:
 		rgb_mode = ARGB8888;
-		writel(V_ARGB888_VIRWIDTH(hactive), &regs->win0_vir);
+		writel(V_ARGB888_VIRWIDTH(hactive), &win_regs->win0_vir);
 		break;
 	}
 
@@ -89,12 +92,12 @@
 	else
 		lb_mode = LB_RGB_1280X8;
 
-	clrsetbits_le32(&regs->win0_ctrl0,
+	clrsetbits_le32(&win_regs->win0_ctrl0,
 			M_WIN0_LB_MODE | M_WIN0_DATA_FMT | M_WIN0_EN,
 			V_WIN0_LB_MODE(lb_mode) | V_WIN0_DATA_FMT(rgb_mode) |
 			V_WIN0_EN(1));
 
-	writel(fbbase, &regs->win0_yrgb_mst);
+	writel(fbbase, &win_regs->win0_yrgb_mst);
 	writel(0x01, &regs->reg_cfg_done); /* enable reg config */
 
 	ret = reset_assert(dclk_rst);
@@ -162,6 +165,7 @@
 {
 	struct rk_vop_priv *priv = dev_get_priv(dev);
 	struct rk3288_vop *regs = priv->regs;
+	struct rk3288_vop *dsp_regs = priv->regs + priv->dsp_offset;
 	struct rkvop_driverdata *data =
 		(struct rkvop_driverdata *)dev_get_driver_data(dev);
 
@@ -195,27 +199,27 @@
 
 	writel(V_HSYNC(hsync_len) |
 	       V_HORPRD(hsync_len + hback_porch + hactive + hfront_porch),
-			&regs->dsp_htotal_hs_end);
+			&dsp_regs->dsp_htotal_hs_end);
 
 	writel(V_HEAP(hsync_len + hback_porch + hactive) |
 	       V_HASP(hsync_len + hback_porch),
-	       &regs->dsp_hact_st_end);
+	       &dsp_regs->dsp_hact_st_end);
 
 	writel(V_VSYNC(vsync_len) |
 	       V_VERPRD(vsync_len + vback_porch + vactive + vfront_porch),
-	       &regs->dsp_vtotal_vs_end);
+	       &dsp_regs->dsp_vtotal_vs_end);
 
 	writel(V_VAEP(vsync_len + vback_porch + vactive)|
 	       V_VASP(vsync_len + vback_porch),
-	       &regs->dsp_vact_st_end);
+	       &dsp_regs->dsp_vact_st_end);
 
 	writel(V_HEAP(hsync_len + hback_porch + hactive) |
 	       V_HASP(hsync_len + hback_porch),
-	       &regs->post_dsp_hact_info);
+	       &dsp_regs->post_dsp_hact_info);
 
 	writel(V_VAEP(vsync_len + vback_porch + vactive)|
 	       V_VASP(vsync_len + vback_porch),
-	       &regs->post_dsp_vact_info);
+	       &dsp_regs->post_dsp_vact_info);
 
 	writel(0x01, &regs->reg_cfg_done); /* enable reg config */
 }
@@ -243,9 +247,7 @@
 static int rk_display_init(struct udevice *dev, ulong fbbase, ofnode ep_node)
 {
 	struct video_priv *uc_priv = dev_get_uclass_priv(dev);
-	struct rk_vop_priv *priv = dev_get_priv(dev);
 	int vop_id, remote_vop_id;
-	struct rk3288_vop *regs = priv->regs;
 	struct display_timing timing;
 	struct udevice *disp;
 	int ret;
@@ -380,7 +382,7 @@
 		return ret;
 	}
 
-	rkvop_enable(dev, regs, fbbase, 1 << l2bpp, &timing, &dclk_rst);
+	rkvop_enable(dev, fbbase, 1 << l2bpp, &timing, &dclk_rst);
 
 	ret = display_enable(disp, 1 << l2bpp, &timing);
 	if (ret)
@@ -415,6 +417,8 @@
 {
 	struct video_uc_plat *plat = dev_get_uclass_plat(dev);
 	struct rk_vop_priv *priv = dev_get_priv(dev);
+	struct rkvop_driverdata *ops =
+		(struct rkvop_driverdata *)dev_get_driver_data(dev);
 	int ret = 0;
 	ofnode port, node;
 	struct reset_ctl ahb_rst;
@@ -448,6 +452,8 @@
 #endif
 
 	priv->regs = dev_read_addr_ptr(dev);
+	priv->win_offset = ops->win_offset;
+	priv->dsp_offset = ops->dsp_offset;
 
 	/*
 	 * Try all the ports until we find one that works. In practice this
diff --git a/drivers/video/rockchip/rk_vop.h b/drivers/video/rockchip/rk_vop.h
index 0528fb2..eba68d8 100644
--- a/drivers/video/rockchip/rk_vop.h
+++ b/drivers/video/rockchip/rk_vop.h
@@ -11,6 +11,8 @@
 struct rk_vop_priv {
 	void *grf;
 	void *regs;
+	int win_offset;
+	int dsp_offset;
 };
 
 enum vop_features {
@@ -18,6 +20,8 @@
 };
 
 struct rkvop_driverdata {
+	int win_offset;
+	int dsp_offset;
 	/* configuration */
 	u32 features;
 	/* block-specific setters/getters */
diff --git a/drivers/video/samsung-ltl106hl02.c b/drivers/video/samsung-ltl106hl02.c
new file mode 100644
index 0000000..5e6c11c
--- /dev/null
+++ b/drivers/video/samsung-ltl106hl02.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Samsung LTL106HL02-001 DSI panel driver
+ *
+ * Copyright (c) 2020 Anton Bambura <jenneron@protonmail.com>
+ * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ * Copyright (c) 2024 Jonas Schwöbel <jonasschwoebel@yahoo.de>
+ */
+
+#include <backlight.h>
+#include <dm.h>
+#include <panel.h>
+#include <log.h>
+#include <mipi_dsi.h>
+#include <asm/gpio.h>
+#include <linux/delay.h>
+#include <power/regulator.h>
+
+struct samsung_ltl106hl02_priv {
+	struct udevice *vdd;
+	struct udevice *backlight;
+
+	struct gpio_desc reset_gpio;
+};
+
+static struct display_timing default_timing = {
+	.pixelclock.typ		= 137000000,
+	.hactive.typ		= 1920,
+	.hfront_porch.typ	= 32,
+	.hback_porch.typ	= 64,
+	.hsync_len.typ		= 32,
+	.vactive.typ		= 1080,
+	.vfront_porch.typ	= 2,
+	.vback_porch.typ	= 26,
+	.vsync_len.typ		= 3,
+};
+
+static int samsung_ltl106hl02_enable_backlight(struct udevice *dev)
+{
+	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
+	struct mipi_dsi_device *dsi = plat->device;
+	int ret;
+
+	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+	if (ret < 0) {
+		log_debug("%s: failed to exit sleep mode: %d\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(70);
+
+	ret = mipi_dsi_dcs_set_display_on(dsi);
+	if (ret < 0) {
+		log_debug("%s: failed to enable display: %d\n",
+			  __func__, ret);
+		return ret;
+	}
+	mdelay(5);
+
+	return 0;
+}
+
+static int samsung_ltl106hl02_set_backlight(struct udevice *dev, int percent)
+{
+	struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = backlight_enable(priv->backlight);
+	if (ret)
+		return ret;
+
+	return backlight_set_brightness(priv->backlight, percent);
+}
+
+static int samsung_ltl106hl02_timings(struct udevice *dev,
+				      struct display_timing *timing)
+{
+	memcpy(timing, &default_timing, sizeof(*timing));
+	return 0;
+}
+
+static int samsung_ltl106hl02_of_to_plat(struct udevice *dev)
+{
+	struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
+					   "backlight", &priv->backlight);
+	if (ret) {
+		log_debug("%s: cannot get backlight: ret = %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
+					   "vdd-supply", &priv->vdd);
+	if (ret)
+		log_debug("%s: cannot get vdd-supply: error %d\n",
+			  __func__, ret);
+
+	ret = gpio_request_by_name(dev, "reset-gpios", 0,
+				   &priv->reset_gpio, GPIOD_IS_OUT);
+	if (ret)
+		log_debug("%s: cannot get reset-gpios: error %d\n",
+			  __func__, ret);
+
+	return 0;
+}
+
+static int samsung_ltl106hl02_hw_init(struct udevice *dev)
+{
+	struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev);
+
+	dm_gpio_set_value(&priv->reset_gpio, 1);
+	regulator_set_enable_if_allowed(priv->vdd, 1);
+
+	/* Dataheets states at least 8.5 msec for vdd stabilization */
+	mdelay(10);
+
+	dm_gpio_set_value(&priv->reset_gpio, 0);
+
+	return 0;
+}
+
+static int samsung_ltl106hl02_probe(struct udevice *dev)
+{
+	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
+
+	/* fill characteristics of DSI data link */
+	plat->lanes = 4;
+	plat->format = MIPI_DSI_FMT_RGB888;
+	plat->mode_flags = MIPI_DSI_MODE_VIDEO;
+
+	return samsung_ltl106hl02_hw_init(dev);
+}
+
+static const struct panel_ops samsung_ltl106hl02_ops = {
+	.enable_backlight	= samsung_ltl106hl02_enable_backlight,
+	.set_backlight		= samsung_ltl106hl02_set_backlight,
+	.get_display_timing	= samsung_ltl106hl02_timings,
+};
+
+static const struct udevice_id samsung_ltl106hl02_ids[] = {
+	{ .compatible = "samsung,ltl106hl02-001" },
+	{ }
+};
+
+U_BOOT_DRIVER(samsung_ltl106hl02) = {
+	.name		= "samsung_ltl106hl02",
+	.id		= UCLASS_PANEL,
+	.of_match	= samsung_ltl106hl02_ids,
+	.ops		= &samsung_ltl106hl02_ops,
+	.of_to_plat	= samsung_ltl106hl02_of_to_plat,
+	.probe		= samsung_ltl106hl02_probe,
+	.plat_auto	= sizeof(struct mipi_dsi_panel_plat),
+	.priv_auto	= sizeof(struct samsung_ltl106hl02_priv),
+};
diff --git a/drivers/video/simple_panel.c b/drivers/video/simple_panel.c
index efb122b..76a3042 100644
--- a/drivers/video/simple_panel.c
+++ b/drivers/video/simple_panel.c
@@ -7,31 +7,22 @@
 #include <common.h>
 #include <backlight.h>
 #include <dm.h>
+#include <edid.h>
+#include <i2c.h>
 #include <log.h>
 #include <mipi_dsi.h>
 #include <panel.h>
 #include <asm/gpio.h>
 #include <power/regulator.h>
 
+#define EDID_I2C_ADDR	0x50
+
 struct simple_panel_priv {
 	struct udevice *reg;
 	struct udevice *backlight;
 	struct gpio_desc enable;
 };
 
-/* List of supported DSI panels */
-enum {
-	PANEL_NON_DSI,
-	PANASONIC_VVX10F004B00,
-};
-
-static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = {
-	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
-		      MIPI_DSI_CLOCK_NON_CONTINUOUS,
-	.format = MIPI_DSI_FMT_RGB888,
-	.lanes = 4,
-};
-
 static int simple_panel_enable_backlight(struct udevice *dev)
 {
 	struct simple_panel_priv *priv = dev_get_priv(dev);
@@ -62,13 +53,71 @@
 	return 0;
 }
 
+#if CONFIG_IS_ENABLED(I2C_EDID) && CONFIG_IS_ENABLED(DM_I2C)
+static int simple_panel_get_edid_timing(struct udevice *dev,
+					struct display_timing *timings)
+{
+	struct udevice *panel_ddc, *panel_edid;
+	struct display_timing edid_timing;
+	u8 edid_buf[EDID_SIZE] = { 0 };
+	int ret, bpc;
+	/* Check for DDC i2c if no timings are provided */
+	ret = uclass_get_device_by_phandle(UCLASS_I2C, dev,
+					   "ddc-i2c-bus",
+					   &panel_ddc);
+	if (ret) {
+		log_debug("%s: cannot get DDC i2c bus: error %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = dm_i2c_probe(panel_ddc, EDID_I2C_ADDR, 0, &panel_edid);
+	if (ret) {
+		log_debug("%s: cannot probe EDID: error %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = dm_i2c_read(panel_edid, 0, edid_buf, sizeof(edid_buf));
+	if (ret) {
+		log_debug("%s: cannot dump EDID buffer: error %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	ret = edid_get_timing(edid_buf, sizeof(edid_buf),
+			      &edid_timing, &bpc);
+	if (ret) {
+		log_debug("%s: cannot decode EDID info: error %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
+	memcpy(timings, &edid_timing, sizeof(*timings));
+
+	return 0;
+}
+#else
+static int simple_panel_get_edid_timing(struct udevice *dev,
+					struct display_timing *timings)
+{
+	return -ENOTSUPP;
+}
+#endif
+
 static int simple_panel_get_display_timing(struct udevice *dev,
 					   struct display_timing *timings)
 {
 	const void *blob = gd->fdt_blob;
+	int ret;
 
-	return fdtdec_decode_display_timing(blob, dev_of_offset(dev),
-					    0, timings);
+	/* Check for timing subnode if panel node first */
+	ret = fdtdec_decode_display_timing(blob, dev_of_offset(dev),
+					   0, timings);
+	if (!ret)
+		return ret;
+
+	return simple_panel_get_edid_timing(dev, timings);
 }
 
 static int simple_panel_of_to_plat(struct udevice *dev)
@@ -111,7 +160,8 @@
 {
 	struct simple_panel_priv *priv = dev_get_priv(dev);
 	struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
-	const u32 dsi_data = dev_get_driver_data(dev);
+	struct mipi_dsi_panel_plat *dsi_data =
+		(struct mipi_dsi_panel_plat *)dev_get_driver_data(dev);
 	int ret;
 
 	ret = regulator_set_enable_if_allowed(priv->reg, true);
@@ -121,15 +171,8 @@
 		return ret;
 	}
 
-	switch (dsi_data) {
-	case PANASONIC_VVX10F004B00:
-		memcpy(plat, &panasonic_vvx10f004b00,
-		       sizeof(panasonic_vvx10f004b00));
-		break;
-	case PANEL_NON_DSI:
-	default:
-		break;
-	}
+	if (dsi_data)
+		memcpy(plat, dsi_data, sizeof(struct mipi_dsi_panel_plat));
 
 	return 0;
 }
@@ -140,6 +183,13 @@
 	.get_display_timing	= simple_panel_get_display_timing,
 };
 
+static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = {
+	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+		      MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.format = MIPI_DSI_FMT_RGB888,
+	.lanes = 4,
+};
+
 static const struct udevice_id simple_panel_ids[] = {
 	{ .compatible = "simple-panel" },
 	{ .compatible = "auo,b133xtn01" },
@@ -150,7 +200,7 @@
 	{ .compatible = "sharp,lq123p1jx31" },
 	{ .compatible = "boe,nv101wxmn51" },
 	{ .compatible = "panasonic,vvx10f004b00",
-	  .data = PANASONIC_VVX10F004B00 },
+	  .data = (ulong)&panasonic_vvx10f004b00 },
 	{ }
 };
 
diff --git a/drivers/video/simplefb.c b/drivers/video/simplefb.c
index 235ec76..33bb78b 100644
--- a/drivers/video/simplefb.c
+++ b/drivers/video/simplefb.c
@@ -15,14 +15,14 @@
 {
 	struct video_uc_plat *plat = dev_get_uclass_plat(dev);
 	struct video_priv *uc_priv = dev_get_uclass_priv(dev);
-	const void *blob = gd->fdt_blob;
-	const int node = dev_of_offset(dev);
+	ofnode node = dev_ofnode(dev);
 	const char *format;
+	int ret;
 	fdt_addr_t base;
 	fdt_size_t size;
+	u32 width, height, rot;
 
-	base = fdtdec_get_addr_size_auto_parent(blob, dev_of_offset(dev->parent),
-			node, "reg", 0, &size, false);
+	base = dev_read_addr_size(dev, &size);
 	if (base == FDT_ADDR_T_NONE) {
 		debug("%s: Failed to decode memory region\n", __func__);
 		return -EINVAL;
@@ -41,17 +41,25 @@
 
 	debug("%s: Query resolution...\n", __func__);
 
-	uc_priv->xsize = fdtdec_get_uint(blob, node, "width", 0);
-	uc_priv->ysize = fdtdec_get_uint(blob, node, "height", 0);
-	uc_priv->rot = fdtdec_get_uint(blob, node, "rot", 0);
-	if (uc_priv->rot > 3) {
-		log_debug("%s: invalid rot\n", __func__);
-		return log_msg_ret("rot", -EINVAL);
+	ret = ofnode_read_u32(node, "width", &width);
+	ret = ret ?: ofnode_read_u32(node, "height", &height);
+	if (ret || !width || !height) {
+		log_err("%s: invalid width or height: %d\n", __func__, ret);
+		return ret ?: -EINVAL;
 	}
+	ofnode_read_u32(node, "rot", &rot);
+	uc_priv->rot = rot;
+	uc_priv->xsize = width;
+	uc_priv->ysize = height;
 
-	format = fdt_getprop(blob, node, "format", NULL);
+	format = ofnode_read_string(node, "format");
 	debug("%s: %dx%d@%s\n", __func__, uc_priv->xsize, uc_priv->ysize, format);
 
+	if (!format) {
+		log_err("%s: please add required property \"format\"\n", __func__);
+		return -EINVAL;
+	}
+
 	if (strcmp(format, "r5g6b5") == 0) {
 		uc_priv->bpix = VIDEO_BPP16;
 	} else if (strcmp(format, "a8b8g8r8") == 0 ||
@@ -67,7 +75,7 @@
 		uc_priv->bpix = VIDEO_BPP32;
 		uc_priv->format = VIDEO_X2R10G10B10;
 	} else {
-		printf("%s: invalid format: %s\n", __func__, format);
+		log_err("%s: invalid format: %s\n", __func__, format);
 		return -EINVAL;
 	}
 
diff --git a/drivers/video/sunxi/sunxi_dw_hdmi.c b/drivers/video/sunxi/sunxi_dw_hdmi.c
index 0324a05..a5e8d39 100644
--- a/drivers/video/sunxi/sunxi_dw_hdmi.c
+++ b/drivers/video/sunxi/sunxi_dw_hdmi.c
@@ -358,17 +358,19 @@
 
 	sunxi_dw_hdmi_phy_init(&priv->hdmi);
 
-	ret = dw_hdmi_phy_wait_for_hpd(&priv->hdmi);
-	if (ret < 0) {
-		debug("hdmi can not get hpd signal\n");
-		return -1;
-	}
+	ret = dw_hdmi_detect_hpd(&priv->hdmi);
+	if (ret < 0)
+		return ret;
 
 	dw_hdmi_init(&priv->hdmi);
 
 	return 0;
 }
 
+static const struct dw_hdmi_phy_ops dw_hdmi_sunxi_phy_ops = {
+	.phy_set = sunxi_dw_hdmi_phy_cfg,
+};
+
 static int sunxi_dw_hdmi_of_to_plat(struct udevice *dev)
 {
 	struct sunxi_dw_hdmi_priv *priv = dev_get_priv(dev);
@@ -379,7 +381,7 @@
 	hdmi->i2c_clk_high = 0xd8;
 	hdmi->i2c_clk_low = 0xfe;
 	hdmi->reg_io_width = 1;
-	hdmi->phy_set = sunxi_dw_hdmi_phy_cfg;
+	hdmi->ops = &dw_hdmi_sunxi_phy_ops;
 
 	ret = reset_get_bulk(dev, &priv->resets);
 	if (ret)
diff --git a/drivers/video/tegra20/Makefile b/drivers/video/tegra20/Makefile
index f0b534c..a75aea2 100644
--- a/drivers/video/tegra20/Makefile
+++ b/drivers/video/tegra20/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0+
 
 obj-$(CONFIG_VIDEO_TEGRA20) += tegra-dc.o
-obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o mipi-phy.o
+obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o tegra-mipi.o mipi-phy.o
 obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += tegra-pwm-backlight.o
diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c
index f53ad46..d073da7 100644
--- a/drivers/video/tegra20/tegra-dc.c
+++ b/drivers/video/tegra20/tegra-dc.c
@@ -3,8 +3,8 @@
  * Copyright (c) 2011 The Chromium OS Authors.
  */
 
-#include <common.h>
 #include <backlight.h>
+#include <cpu_func.h>
 #include <dm.h>
 #include <fdtdec.h>
 #include <log.h>
@@ -21,12 +21,20 @@
 #include <asm/arch/clock.h>
 #include <asm/arch/funcmux.h>
 #include <asm/arch/pinmux.h>
+#include <asm/arch/powergate.h>
 #include <asm/arch/pwm.h>
-#include <asm/arch/display.h>
-#include <asm/arch-tegra/timer.h>
+
+#include "tegra-dc.h"
 
 DECLARE_GLOBAL_DATA_PTR;
 
+/* Holder of Tegra per-SOC DC differences */
+struct tegra_dc_soc_info {
+	bool has_timer;
+	bool has_rgb;
+	bool has_pgate;
+};
+
 /* Information about the display controller */
 struct tegra_lcd_priv {
 	int width;			/* width in pixels */
@@ -35,16 +43,19 @@
 	struct display_timing timing;
 	struct udevice *panel;
 	struct dc_ctlr *dc;		/* Display controller regmap */
+	const struct tegra_dc_soc_info *soc;
 	fdt_addr_t frame_buffer;	/* Address of frame buffer */
 	unsigned pixel_clock;		/* Pixel clock in Hz */
 	int dc_clk[2];			/* Contains clk and its parent */
+	ulong scdiv;			/* Clock divider used by disp_clk_ctrl */
 	bool rotation;			/* 180 degree panel turn */
+	bool pipe;			/* DC controller: 0 for A, 1 for B */
 };
 
 enum {
 	/* Maximum LCD size we support */
-	LCD_MAX_WIDTH		= 1920,
-	LCD_MAX_HEIGHT		= 1200,
+	LCD_MAX_WIDTH		= 2560,
+	LCD_MAX_HEIGHT		= 1600,
 	LCD_MAX_LOG2_BPP	= VIDEO_BPP16,
 };
 
@@ -110,13 +121,11 @@
 	writel(val, &dc->cmd.state_ctrl);
 }
 
-static int update_display_mode(struct dc_disp_reg *disp,
-			       struct tegra_lcd_priv *priv)
+static int update_display_mode(struct tegra_lcd_priv *priv)
 {
+	struct dc_disp_reg *disp = &priv->dc->disp;
 	struct display_timing *dt = &priv->timing;
 	unsigned long val;
-	unsigned long rate;
-	unsigned long div;
 
 	writel(0x0, &disp->disp_timing_opt);
 
@@ -128,29 +137,22 @@
 	       &disp->front_porch);
 	writel(dt->hactive.typ | (dt->vactive.typ << 16), &disp->disp_active);
 
-	val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT;
-	val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT;
-	writel(val, &disp->data_enable_opt);
+	if (priv->soc->has_rgb) {
+		val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT;
+		val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT;
+		writel(val, &disp->data_enable_opt);
 
-	val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT;
-	val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT;
-	val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT;
-	writel(val, &disp->disp_interface_ctrl);
+		val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT;
+		val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT;
+		val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT;
+		writel(val, &disp->disp_interface_ctrl);
+	}
 
-	/*
-	 * The pixel clock divider is in 7.1 format (where the bottom bit
-	 * represents 0.5). Here we calculate the divider needed to get from
-	 * the display clock (typically 600MHz) to the pixel clock. We round
-	 * up or down as requried.
-	 */
-	rate = clock_get_periph_rate(priv->dc_clk[0], priv->dc_clk[1]);
-	div = ((rate * 2 + priv->pixel_clock / 2) / priv->pixel_clock) - 2;
-	debug("Display clock %lu, divider %lu\n", rate, div);
-
-	writel(0x00010001, &disp->shift_clk_opt);
+	if (priv->soc->has_rgb)
+		writel(0x00010001, &disp->shift_clk_opt);
 
 	val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT;
-	val |= div << SHIFT_CLK_DIVIDER_SHIFT;
+	val |= priv->scdiv << SHIFT_CLK_DIVIDER_SHIFT;
 	writel(val, &disp->disp_clk_ctrl);
 
 	return 0;
@@ -174,6 +176,7 @@
 	writel(val, &cmd->disp_pow_ctrl);
 
 	val = readl(&cmd->disp_cmd);
+	val &= ~CTRL_MODE_MASK;
 	val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT;
 	writel(val, &cmd->disp_cmd);
 }
@@ -215,8 +218,11 @@
 	0x00020000,
 };
 
-static void rgb_enable(struct dc_com_reg *com)
+static void rgb_enable(struct tegra_lcd_priv *priv)
 {
+	struct dc_com_reg *com = &priv->dc->com;
+	struct display_timing *dt = &priv->timing;
+	u32 value;
 	int i;
 
 	for (i = 0; i < PIN_REG_COUNT; i++) {
@@ -225,16 +231,31 @@
 		writel(rgb_data_tab[i], &com->pin_output_data[i]);
 	}
 
+	/* configure H- and V-sync signal polarities */
+	value = readl(&com->pin_output_polarity[1]);
+
+	if (dt->flags & DISPLAY_FLAGS_HSYNC_LOW)
+		value |= LHS_OUTPUT_POLARITY_LOW;
+	else
+		value &= ~LHS_OUTPUT_POLARITY_LOW;
+
+	if (dt->flags & DISPLAY_FLAGS_VSYNC_LOW)
+		value |= LVS_OUTPUT_POLARITY_LOW;
+	else
+		value &= ~LVS_OUTPUT_POLARITY_LOW;
+
+	writel(value, &com->pin_output_polarity[1]);
+
 	for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++)
 		writel(rgb_sel_tab[i], &com->pin_output_sel[i]);
 }
 
-static int setup_window(struct disp_ctl_win *win,
-			struct tegra_lcd_priv *priv)
+static int setup_window(struct tegra_lcd_priv *priv,
+			struct disp_ctl_win *win)
 {
 	if (priv->rotation) {
-		win->x = priv->width * 2;
-		win->y = priv->height;
+		win->x = priv->width * 2 - 1;
+		win->y = priv->height - 1;
 	} else {
 		win->x = 0;
 		win->y = 0;
@@ -274,12 +295,11 @@
  * You should pass in the U-Boot address here, and check the contents of
  * struct tegra_lcd_priv to see what was actually chosen.
  *
- * @param blob			Device tree blob
  * @param priv			Driver's private data
  * @param default_lcd_base	Default address of LCD frame buffer
  * Return: 0 if ok, -1 on error (unsupported bits per pixel)
  */
-static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv,
+static int tegra_display_probe(struct tegra_lcd_priv *priv,
 			       void *default_lcd_base)
 {
 	struct disp_ctl_win window;
@@ -288,12 +308,29 @@
 	priv->frame_buffer = (u32)default_lcd_base;
 
 	/*
-	 * We halve the rate if DISP1 paret is PLLD, since actual parent
+	 * We halve the rate if DISP1 parent is PLLD, since actual parent
 	 * is plld_out0 which is PLLD divided by 2.
 	 */
 	if (priv->dc_clk[1] == CLOCK_ID_DISPLAY)
 		rate /= 2;
 
+#ifndef CONFIG_TEGRA20
+	/* PLLD2 obeys same rules as PLLD but it is present only on T30+ */
+	if (priv->dc_clk[1] == CLOCK_ID_DISPLAY2)
+		rate /= 2;
+#endif
+
+	/*
+	 * The pixel clock divider is in 7.1 format (where the bottom bit
+	 * represents 0.5). Here we calculate the divider needed to get from
+	 * the display clock (typically 600MHz) to the pixel clock. We round
+	 * up or down as required.
+	 */
+	if (!priv->scdiv)
+		priv->scdiv = ((rate * 2 + priv->pixel_clock / 2)
+						/ priv->pixel_clock) - 2;
+	debug("Display clock %lu, divider %lu\n", rate, priv->scdiv);
+
 	/*
 	 * HOST1X is init by default at 150MHz with PLLC as parent
 	 */
@@ -303,13 +340,17 @@
 			       rate);
 
 	basic_init(&priv->dc->cmd);
-	basic_init_timer(&priv->dc->disp);
-	rgb_enable(&priv->dc->com);
+
+	if (priv->soc->has_timer)
+		basic_init_timer(&priv->dc->disp);
+
+	if (priv->soc->has_rgb)
+		rgb_enable(priv);
 
 	if (priv->pixel_clock)
-		update_display_mode(&priv->dc->disp, priv);
+		update_display_mode(priv);
 
-	if (setup_window(&window, priv))
+	if (setup_window(priv, &window))
 		return -1;
 
 	update_window(priv, &window);
@@ -322,7 +363,6 @@
 	struct video_uc_plat *plat = dev_get_uclass_plat(dev);
 	struct video_priv *uc_priv = dev_get_uclass_priv(dev);
 	struct tegra_lcd_priv *priv = dev_get_priv(dev);
-	const void *blob = gd->fdt_blob;
 	int ret;
 
 	/* Initialize the Tegra display controller */
@@ -330,8 +370,42 @@
 	funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT);
 #endif
 
-	if (tegra_display_probe(blob, priv, (void *)plat->base)) {
-		printf("%s: Failed to probe display driver\n", __func__);
+	if (priv->soc->has_pgate) {
+		uint powergate;
+
+		if (priv->pipe)
+			powergate = TEGRA_POWERGATE_DISB;
+		else
+			powergate = TEGRA_POWERGATE_DIS;
+
+		ret = tegra_powergate_power_off(powergate);
+		if (ret < 0) {
+			log_err("failed to power off DISP gate: %d", ret);
+			return ret;
+		}
+
+		ret = tegra_powergate_sequence_power_up(powergate,
+							priv->dc_clk[0]);
+		if (ret < 0) {
+			log_err("failed to power up DISP gate: %d", ret);
+			return ret;
+		}
+	}
+
+	/* Get shift clock divider from Tegra DSI if used */
+	if (!strcmp(priv->panel->name, TEGRA_DSI_A) ||
+	    !strcmp(priv->panel->name, TEGRA_DSI_B)) {
+		struct tegra_dc_plat *dc_plat = dev_get_plat(priv->panel);
+
+		priv->scdiv = dc_plat->scdiv;
+	}
+
+	/* Clean the framebuffer area */
+	memset((u8 *)plat->base, 0, plat->size);
+	flush_dcache_all();
+
+	if (tegra_display_probe(priv, (void *)plat->base)) {
+		debug("%s: Failed to probe display driver\n", __func__);
 		return -1;
 	}
 
@@ -346,12 +420,6 @@
 		return ret;
 	}
 
-	ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
-	if (ret) {
-		debug("%s: Cannot set backlight to default, ret=%d\n", __func__, ret);
-		return ret;
-	}
-
 	mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size,
 					DCACHE_WRITETHROUGH);
 
@@ -361,10 +429,10 @@
 	uc_priv->xsize = priv->width;
 	uc_priv->ysize = priv->height;
 	uc_priv->bpix = priv->log2_bpp;
-	debug("LCD frame buffer at %pa, size %x\n", &priv->frame_buffer,
+	debug("LCD frame buffer at %08x, size %x\n", priv->frame_buffer,
 	      plat->size);
 
-	return 0;
+	return panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
 }
 
 static int tegra_lcd_of_to_plat(struct udevice *dev)
@@ -383,6 +451,8 @@
 		return -EINVAL;
 	}
 
+	priv->soc = (struct tegra_dc_soc_info *)dev_get_driver_data(dev);
+
 	ret = clock_decode_pair(dev, priv->dc_clk);
 	if (ret < 0) {
 		debug("%s: Cannot decode clocks for '%s' (ret = %d)\n",
@@ -392,6 +462,9 @@
 
 	priv->rotation = dev_read_bool(dev, "nvidia,180-rotation");
 
+	if (!strcmp(dev->name, TEGRA_DC_B))
+		priv->pipe = 1;
+
 	rgb = fdt_subnode_offset(blob, node, "rgb");
 	if (rgb < 0) {
 		debug("%s: Cannot find rgb subnode for '%s' (ret=%d)\n",
@@ -417,12 +490,14 @@
 		return ret;
 	}
 
+	/* Fill the platform data for internal devices */
 	if (!strcmp(priv->panel->name, TEGRA_DSI_A) ||
 	    !strcmp(priv->panel->name, TEGRA_DSI_B)) {
 		struct tegra_dc_plat *dc_plat = dev_get_plat(priv->panel);
 
 		dc_plat->dev = dev;
 		dc_plat->dc = priv->dc;
+		dc_plat->pipe = priv->pipe;
 	}
 
 	ret = panel_get_display_timing(priv->panel, &priv->timing);
@@ -464,19 +539,46 @@
 static const struct video_ops tegra_lcd_ops = {
 };
 
+static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
+	.has_timer = true,
+	.has_rgb = true,
+	.has_pgate = false,
+};
+
+static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
+	.has_timer = false,
+	.has_rgb = true,
+	.has_pgate = false,
+};
+
+static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
+	.has_timer = false,
+	.has_rgb = false,
+	.has_pgate = true,
+};
+
 static const struct udevice_id tegra_lcd_ids[] = {
-	{ .compatible = "nvidia,tegra20-dc" },
-	{ .compatible = "nvidia,tegra30-dc" },
-	{ }
+	{
+		.compatible = "nvidia,tegra20-dc",
+		.data = (ulong)&tegra20_dc_soc_info
+	}, {
+		.compatible = "nvidia,tegra30-dc",
+		.data = (ulong)&tegra30_dc_soc_info
+	}, {
+		.compatible = "nvidia,tegra114-dc",
+		.data = (ulong)&tegra114_dc_soc_info
+	}, {
+		/* sentinel */
+	}
 };
 
 U_BOOT_DRIVER(tegra_lcd) = {
-	.name	= "tegra_lcd",
-	.id	= UCLASS_VIDEO,
-	.of_match = tegra_lcd_ids,
-	.ops	= &tegra_lcd_ops,
-	.bind	= tegra_lcd_bind,
-	.probe	= tegra_lcd_probe,
+	.name		= "tegra_lcd",
+	.id		= UCLASS_VIDEO,
+	.of_match	= tegra_lcd_ids,
+	.ops		= &tegra_lcd_ops,
+	.bind		= tegra_lcd_bind,
+	.probe		= tegra_lcd_probe,
 	.of_to_plat	= tegra_lcd_of_to_plat,
 	.priv_auto	= sizeof(struct tegra_lcd_priv),
 };
diff --git a/drivers/video/tegra20/tegra-dc.h b/drivers/video/tegra20/tegra-dc.h
new file mode 100644
index 0000000..05042da
--- /dev/null
+++ b/drivers/video/tegra20/tegra-dc.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ *  (C) Copyright 2010
+ *  NVIDIA Corporation <www.nvidia.com>
+ */
+
+#ifndef _TEGRA_DC_H
+#define _TEGRA_DC_H
+
+#ifndef __ASSEMBLY__
+#include <linux/bitops.h>
+#endif
+
+/* arch-tegra/dc exists only because T124 uses it */
+#include <asm/arch-tegra/dc.h>
+
+#define TEGRA_DC_A		"dc@54200000"
+#define TEGRA_DC_B		"dc@54240000"
+#define TEGRA_DSI_A		"dsi@54300000"
+#define TEGRA_DSI_B		"dsi@54400000"
+
+struct tegra_dc_plat {
+	struct udevice *dev;		/* Display controller device */
+	struct dc_ctlr *dc;		/* Display controller regmap */
+	bool pipe;			/* DC number: 0 for A, 1 for B */
+	ulong scdiv;			/* Shift clock divider */
+};
+
+/* This holds information about a window which can be displayed */
+struct disp_ctl_win {
+	enum win_color_depth_id fmt;	/* Color depth/format */
+	unsigned int bpp;		/* Bits per pixel */
+	phys_addr_t phys_addr;		/* Physical address in memory */
+	unsigned int x;			/* Horizontal address offset (bytes) */
+	unsigned int y;			/* Veritical address offset (bytes) */
+	unsigned int w;			/* Width of source window */
+	unsigned int h;			/* Height of source window */
+	unsigned int stride;		/* Number of bytes per line */
+	unsigned int out_x;		/* Left edge of output window (col) */
+	unsigned int out_y;		/* Top edge of output window (row) */
+	unsigned int out_w;		/* Width of output window in pixels */
+	unsigned int out_h;		/* Height of output window in pixels */
+};
+
+#endif /* _TEGRA_DC_H */
diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c
index a48f9c8..13dae37 100644
--- a/drivers/video/tegra20/tegra-dsi.c
+++ b/drivers/video/tegra20/tegra-dsi.c
@@ -12,6 +12,7 @@
 #include <mipi_dsi.h>
 #include <backlight.h>
 #include <panel.h>
+#include <reset.h>
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/time.h>
@@ -20,17 +21,24 @@
 #include <asm/gpio.h>
 #include <asm/io.h>
 #include <asm/arch/clock.h>
-#include <asm/arch/display.h>
-#include <asm/arch-tegra30/dsi.h>
 
+#include "tegra-dc.h"
+#include "tegra-dsi.h"
 #include "mipi-phy.h"
 
+/* List of supported DSI bridges */
+enum {
+	DSI_V0,
+	DSI_V1,
+};
+
 struct tegra_dsi_priv {
 	struct mipi_dsi_host host;
 	struct mipi_dsi_device device;
 	struct mipi_dphy_timing dphy_timing;
 
 	struct udevice *panel;
+	struct udevice *mipi;
 	struct display_timing timing;
 
 	struct dsi_ctlr *dsi;
@@ -41,6 +49,8 @@
 	int dsi_clk;
 	int video_fifo_depth;
 	int host_fifo_depth;
+
+	u32 version;
 };
 
 static void tegra_dc_enable_controller(struct udevice *dev)
@@ -501,6 +511,41 @@
 	writel(value, TEGRA_VI_BASE + (CSI_CIL_PAD_CONFIG << 2));
 }
 
+static void tegra_dsi_mipi_calibrate(struct tegra_dsi_priv *priv)
+{
+	struct dsi_pad_ctrl_reg *pad = &priv->dsi->pad;
+	u32 value;
+	int ret;
+
+	ret = misc_set_enabled(priv->mipi, true);
+	if (ret)
+		log_debug("%s: failed to enable MIPI calibration: %d\n",
+			  __func__, ret);
+
+	writel(0, &pad->pad_ctrl);
+	writel(0, &pad->pad_ctrl_1);
+	writel(0, &pad->pad_ctrl_2);
+	writel(0, &pad->pad_ctrl_3);
+	writel(0, &pad->pad_ctrl_4);
+
+	/* DSI pad enable */
+	value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
+	writel(value, &pad->pad_ctrl);
+
+	value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
+		DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
+		DSI_PAD_OUT_CLK(0x0);
+	writel(value, &pad->pad_ctrl_2);
+
+	value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
+		DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
+	writel(value, &pad->pad_ctrl_3);
+
+	ret = misc_write(priv->mipi, 0, NULL, 0);
+	if (ret)
+		log_debug("%s: MIPI calibration failed %d\n", __func__, ret);
+}
+
 static void tegra_dsi_set_timeout(struct dsi_timeout_reg *rtimeout,
 				  unsigned long bclk,
 				  unsigned int vrefresh)
@@ -664,10 +709,25 @@
 	u32 value;
 	int ret;
 
+	/* If for some reasone DSI is enabled then it needs to
+	 * be disabled in order for the panel initialization
+	 * commands to be properly sent.
+	 */
+	value = readl(&misc->dsi_pwr_ctrl);
+
+	if (value & DSI_POWER_CONTROL_ENABLE) {
+		value = readl(&misc->dsi_pwr_ctrl);
+		value &= ~DSI_POWER_CONTROL_ENABLE;
+		writel(value, &misc->dsi_pwr_ctrl);
+	}
+
 	/* Disable interrupt */
 	writel(0, &misc->int_enable);
 
-	tegra_dsi_pad_calibrate(&priv->dsi->pad);
+	if (priv->version)
+		tegra_dsi_mipi_calibrate(priv);
+	else
+		tegra_dsi_pad_calibrate(&priv->dsi->pad);
 
 	tegra_dsi_get_muldiv(device->format, &mul, &div);
 
@@ -706,12 +766,6 @@
 	if (ret)
 		return ret;
 
-	tegra_dsi_configure(dev, 0);
-
-	ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
-	if (ret)
-		return ret;
-
 	tegra_dsi_configure(dev, device->mode_flags);
 
 	tegra_dc_enable_controller(dev);
@@ -726,8 +780,10 @@
 
 static int tegra_dsi_bridge_set_panel(struct udevice *dev, int percent)
 {
-	/* Is not used in tegra dc */
-	return 0;
+	struct tegra_dsi_priv *priv = dev_get_priv(dev);
+
+	/* Turn on/off backlight */
+	return panel_set_backlight(priv->panel, percent);
 }
 
 static int tegra_dsi_panel_timings(struct udevice *dev,
@@ -743,6 +799,7 @@
 static void tegra_dsi_init_clocks(struct udevice *dev)
 {
 	struct tegra_dsi_priv *priv = dev_get_priv(dev);
+	struct tegra_dc_plat *dc_plat = dev_get_plat(dev);
 	struct mipi_dsi_device *device = &priv->device;
 	unsigned int mul, div;
 	unsigned long bclk, plld;
@@ -754,6 +811,19 @@
 
 	plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC);
 
+	dc_plat->scdiv = ((plld * USEC_PER_SEC +
+		priv->timing.pixelclock.typ / 2) /
+		priv->timing.pixelclock.typ) - 2;
+
+	/*
+	 * BUG: If DISP1 is a PLLD/D2 child, it cannot go over 370MHz. The
+	 * cause of this is not quite clear. This can be overcomed by
+	 * halving the PLLD/D2 if the target rate is > 800MHz. This way
+	 * DISP1 and DSI clocks will be equal.
+	 */
+	if (plld > 800)
+		plld /= 2;
+
 	switch (clock_get_osc_freq()) {
 	case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */
 	case CLOCK_OSC_FREQ_48_0: /* OSC is 48Mhz */
@@ -790,17 +860,27 @@
 	struct tegra_dsi_priv *priv = dev_get_priv(dev);
 	struct mipi_dsi_device *device = &priv->device;
 	struct mipi_dsi_panel_plat *mipi_plat;
+	struct reset_ctl reset_ctl;
 	int ret;
 
+	priv->version = dev_get_driver_data(dev);
+
 	priv->dsi = (struct dsi_ctlr *)dev_read_addr_ptr(dev);
 	if (!priv->dsi) {
 		printf("%s: No display controller address\n", __func__);
 		return -EINVAL;
 	}
 
-	priv->video_fifo_depth = 480;
+	priv->video_fifo_depth = 1920;
 	priv->host_fifo_depth = 64;
 
+	ret = reset_get_by_name(dev, "dsi", &reset_ctl);
+	if (ret) {
+		log_debug("%s: reset_get_by_name() failed: %d\n",
+			  __func__, ret);
+		return ret;
+	}
+
 	ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
 					   "avdd-dsi-csi-supply", &priv->avdd);
 	if (ret)
@@ -814,6 +894,16 @@
 		return log_ret(ret);
 	}
 
+	if (priv->version) {
+		ret = uclass_get_device_by_phandle(UCLASS_MISC, dev,
+						   "nvidia,mipi-calibrate",
+						   &priv->mipi);
+		if (ret) {
+			log_debug("%s: cannot get MIPI: error %d\n", __func__, ret);
+			return ret;
+		}
+	}
+
 	panel_get_display_timing(priv->panel, &priv->timing);
 
 	mipi_plat = dev_get_plat(priv->panel);
@@ -829,12 +919,17 @@
 
 	tegra_dsi_get_format(device->format, &priv->format);
 
+	reset_assert(&reset_ctl);
+
 	ret = regulator_set_enable_if_allowed(priv->avdd, true);
 	if (ret && ret != -ENOSYS)
 		return ret;
 
 	tegra_dsi_init_clocks(dev);
 
+	mdelay(2);
+	reset_deassert(&reset_ctl);
+
 	return 0;
 }
 
@@ -845,7 +940,8 @@
 };
 
 static const struct udevice_id tegra_dsi_bridge_ids[] = {
-	{ .compatible = "nvidia,tegra30-dsi" },
+	{ .compatible = "nvidia,tegra30-dsi", .data = DSI_V0 },
+	{ .compatible = "nvidia,tegra114-dsi", .data = DSI_V1 },
 	{ }
 };
 
diff --git a/arch/arm/include/asm/arch-tegra30/dsi.h b/drivers/video/tegra20/tegra-dsi.h
similarity index 89%
rename from arch/arm/include/asm/arch-tegra30/dsi.h
rename to drivers/video/tegra20/tegra-dsi.h
index 7ade132..69dac4b 100644
--- a/arch/arm/include/asm/arch-tegra30/dsi.h
+++ b/drivers/video/tegra20/tegra-dsi.h
@@ -4,8 +4,8 @@
  *  NVIDIA Corporation <www.nvidia.com>
  */
 
-#ifndef __ASM_ARCH_TEGRA_DSI_H
-#define __ASM_ARCH_TEGRA_DSI_H
+#ifndef _TEGRA_DSI_H
+#define _TEGRA_DSI_H
 
 #ifndef __ASSEMBLY__
 #include <linux/bitops.h>
@@ -105,6 +105,10 @@
 	uint pad_ctrl_cd;		/* _PAD_CONTROL_CD_0 */
 	uint pad_cd_status;		/* _PAD_CD_STATUS_0 */
 	uint dsi_vid_mode_control;	/* _DSI_VID_MODE_CONTROL_0 */
+	uint pad_ctrl_1;		/* _PAD_CONTROL_1 */
+	uint pad_ctrl_2;		/* _PAD_CONTROL_2 */
+	uint pad_ctrl_3;		/* _PAD_CONTROL_3 */
+	uint pad_ctrl_4;		/* _PAD_CONTROL_4 */
 };
 
 /* Display Serial Interface (DSI_) regs */
@@ -184,6 +188,20 @@
 #define DSI_PAD_CONTROL_PAD_LPUPADJ(x)		(((x) & 0x3) << 14)
 #define DSI_PAD_CONTROL_PAD_LPDNADJ(x)		(((x) & 0x3) << 12)
 
+#define DSI_PAD_CONTROL_VS1_PDIO(x)	(((x) & 0xf) <<  0)
+#define DSI_PAD_CONTROL_VS1_PULLDN(x)	(((x) & 0xf) << 16)
+
+#define DSI_PAD_OUT_CLK(x)		(((x) & 0x7) <<  0)
+#define DSI_PAD_LP_DN(x)		(((x) & 0x7) <<  4)
+#define DSI_PAD_LP_UP(x)		(((x) & 0x7) <<  8)
+#define DSI_PAD_SLEW_DN(x)		(((x) & 0x7) << 12)
+#define DSI_PAD_SLEW_UP(x)		(((x) & 0x7) << 16)
+
+#define DSI_PAD_PREEMP_PD_CLK(x)	(((x) & 0x3) << 12)
+#define DSI_PAD_PREEMP_PU_CLK(x)	(((x) & 0x3) << 8)
+#define DSI_PAD_PREEMP_PD(x)		(((x) & 0x3) << 4)
+#define DSI_PAD_PREEMP_PU(x)		(((x) & 0x3) << 0)
+
 /*
  * pixel format as used in the DSI_CONTROL_FORMAT field
  */
@@ -214,4 +232,4 @@
 #define  PAD_DRIV_DN_REF(x)		(((x) & 0x7) << 16)
 #define  PAD_DRIV_UP_REF(x)		(((x) & 0x7) << 8)
 
-#endif /* __ASM_ARCH_TEGRA_DSI_H */
+#endif /* _TEGRA_DSI_H */
diff --git a/drivers/video/tegra20/tegra-mipi.c b/drivers/video/tegra20/tegra-mipi.c
new file mode 100644
index 0000000..2df3c1a
--- /dev/null
+++ b/drivers/video/tegra20/tegra-mipi.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <dm.h>
+#include <clk.h>
+#include <misc.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+
+#include <asm/io.h>
+
+/* MIPI control registers 0x00 ~ 0x60 */
+struct mipi_ctlr {
+	uint mipi_cal_ctrl;
+	uint mipi_cal_autocal_ctrl;
+	uint mipi_cal_status;
+
+	uint unused1[2];
+
+	uint mipi_cal_config_csia;
+	uint mipi_cal_config_csib;
+	uint mipi_cal_config_csic;
+	uint mipi_cal_config_csid;
+	uint mipi_cal_config_csie;
+
+	uint unused2[4];
+
+	uint mipi_cal_config_dsia;
+	uint mipi_cal_config_dsib;
+	uint mipi_cal_config_dsic;
+	uint mipi_cal_config_dsid;
+
+	uint unused3[4];
+
+	uint mipi_cal_bias_pad_cfg0;
+	uint mipi_cal_bias_pad_cfg1;
+	uint mipi_cal_bias_pad_cfg2;
+};
+
+#define MIPI_CAL_CTRL_NOISE_FILTER(x)	(((x) & 0xf) << 26)
+#define MIPI_CAL_CTRL_PRESCALE(x)	(((x) & 0x3) << 24)
+#define MIPI_CAL_CTRL_CLKEN_OVR		BIT(4)
+#define MIPI_CAL_CTRL_START		BIT(0)
+
+#define MIPI_CAL_STATUS_DONE		BIT(16)
+#define MIPI_CAL_STATUS_ACTIVE		BIT(0)
+
+#define MIPI_CAL_OVERIDE(x)		(((x) & 0x1) << 30)
+#define MIPI_CAL_SEL(x)			(((x) & 0x1) << 21)
+#define MIPI_CAL_HSPDOS(x)		(((x) & 0x1f) << 16)
+#define MIPI_CAL_HSPUOS(x)		(((x) & 0x1f) << 8)
+#define MIPI_CAL_TERMOS(x)		(((x) & 0x1f) << 0)
+
+#define MIPI_CAL_BIAS_PAD_PDVCLAMP	BIT(1)
+#define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF	BIT(0)
+
+#define MIPI_CAL_BIAS_PAD_DRV_DN_REF(x) (((x) & 0x7) << 16)
+#define MIPI_CAL_BIAS_PAD_DRV_UP_REF(x) (((x) & 0x7) << 8)
+
+#define MIPI_CAL_BIAS_PAD_VCLAMP(x)	(((x) & 0x7) << 16)
+#define MIPI_CAL_BIAS_PAD_VAUXP(x)	(((x) & 0x7) << 4)
+#define MIPI_CAL_BIAS_PAD_PDVREG	BIT(1)
+
+struct tegra_mipi_priv {
+	struct mipi_ctlr	*mipi;
+	struct clk		*mipi_cal;
+};
+
+static int tegra_mipi_calibrate(struct udevice *dev, int offset, const void *buf,
+				int size)
+{
+	struct tegra_mipi_priv *priv = dev_get_priv(dev);
+	u32 value;
+
+	value = MIPI_CAL_BIAS_PAD_DRV_DN_REF(0x2) |
+		MIPI_CAL_BIAS_PAD_DRV_UP_REF(0x0);
+	writel(value, &priv->mipi->mipi_cal_bias_pad_cfg1);
+
+	value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2);
+	value &= ~MIPI_CAL_BIAS_PAD_VCLAMP(0x7);
+	value &= ~MIPI_CAL_BIAS_PAD_VAUXP(0x7);
+	writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2);
+
+	value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) |
+		MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x4) |
+		MIPI_CAL_TERMOS(0x5);
+	writel(value, &priv->mipi->mipi_cal_config_dsia);
+	writel(value, &priv->mipi->mipi_cal_config_dsib);
+
+	/* Deselect PAD C */
+	value = readl(&priv->mipi->mipi_cal_config_dsic);
+	value &= ~(MIPI_CAL_SEL(0x1));
+	writel(value, &priv->mipi->mipi_cal_config_dsic);
+
+	/* Deselect PAD D */
+	value = readl(&priv->mipi->mipi_cal_config_dsid);
+	value &= ~(MIPI_CAL_SEL(0x1));
+	writel(value, &priv->mipi->mipi_cal_config_dsid);
+
+	value = readl(&priv->mipi->mipi_cal_ctrl);
+	value &= ~MIPI_CAL_CTRL_NOISE_FILTER(0xf);
+	value &= ~MIPI_CAL_CTRL_PRESCALE(0x3);
+	value |= MIPI_CAL_CTRL_NOISE_FILTER(0xa) |
+		 MIPI_CAL_CTRL_PRESCALE(0x2) |
+		 MIPI_CAL_CTRL_CLKEN_OVR;
+	writel(value, &priv->mipi->mipi_cal_ctrl);
+
+	/* clear any pending status bits */
+	value = readl(&priv->mipi->mipi_cal_status);
+	writel(value, &priv->mipi->mipi_cal_status);
+
+	value = readl(&priv->mipi->mipi_cal_ctrl);
+	value |= MIPI_CAL_CTRL_START;
+	writel(value, &priv->mipi->mipi_cal_ctrl);
+
+	/*
+	 * Wait for min 72uS to let calibration logic finish calibration
+	 * sequence codes before waiting for pads idle state to apply the
+	 * results.
+	 */
+	udelay(80);
+
+	return readl_poll_sleep_timeout(&priv->mipi->mipi_cal_status, value,
+					!(value & MIPI_CAL_STATUS_ACTIVE) &&
+					(value & MIPI_CAL_STATUS_DONE), 100,
+					250000);
+}
+
+static int tegra_mipi_enable(struct udevice *dev, bool val)
+{
+	struct tegra_mipi_priv *priv = dev_get_priv(dev);
+	u32 value;
+
+	clk_enable(priv->mipi_cal);
+
+	value = readl(&priv->mipi->mipi_cal_bias_pad_cfg0);
+	value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP;
+	value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF;
+	writel(value, &priv->mipi->mipi_cal_bias_pad_cfg0);
+
+	value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2);
+	value &= ~MIPI_CAL_BIAS_PAD_PDVREG;
+	writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2);
+
+	return 0;
+}
+
+static const struct misc_ops tegra_mipi_ops = {
+	.write = tegra_mipi_calibrate,
+	.set_enabled = tegra_mipi_enable,
+};
+
+static int tegra_mipi_probe(struct udevice *dev)
+{
+	struct tegra_mipi_priv *priv = dev_get_priv(dev);
+
+	priv->mipi = (struct mipi_ctlr *)dev_read_addr_ptr(dev);
+	if (!priv->mipi) {
+		log_debug("%s: no MIPI controller address\n", __func__);
+		return -EINVAL;
+	}
+
+	priv->mipi_cal = devm_clk_get(dev, NULL);
+	if (IS_ERR(priv->mipi_cal)) {
+		log_debug("%s: Could not get MIPI clock: %ld\n",
+			  __func__, PTR_ERR(priv->mipi_cal));
+		return PTR_ERR(priv->mipi_cal);
+	}
+
+	return 0;
+}
+
+static const struct udevice_id tegra_mipi_ids[] = {
+	{ .compatible = "nvidia,tegra114-mipi" },
+	{ }
+};
+
+U_BOOT_DRIVER(tegra_mipi) = {
+	.name           = "tegra_mipi",
+	.id             = UCLASS_MISC,
+	.ops		= &tegra_mipi_ops,
+	.of_match       = tegra_mipi_ids,
+	.probe          = tegra_mipi_probe,
+	.priv_auto	= sizeof(struct tegra_mipi_priv),
+};
diff --git a/drivers/video/tegra20/tegra-pwm-backlight.c b/drivers/video/tegra20/tegra-pwm-backlight.c
index bb677da..5f93f57 100644
--- a/drivers/video/tegra20/tegra-pwm-backlight.c
+++ b/drivers/video/tegra20/tegra-pwm-backlight.c
@@ -15,7 +15,8 @@
 
 #include <asm/io.h>
 #include <asm/gpio.h>
-#include <asm/arch/display.h>
+
+#include "tegra-dc.h"
 
 #define TEGRA_DISPLAY_A_BASE		0x54200000
 #define TEGRA_DISPLAY_B_BASE		0x54240000
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 3571e62..7b5d1df 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -404,6 +404,10 @@
 {
 	struct udevice *dev;
 
+	/* Assume video to be active if SPL passed video hand-off to U-boot */
+	if (IS_ENABLED(CONFIG_SPL_VIDEO_HANDOFF) && spl_phase() > PHASE_SPL)
+		return true;
+
 	for (uclass_find_first_device(UCLASS_VIDEO, &dev);
 	     dev;
 	     uclass_find_next_device(&dev)) {
diff --git a/include/configs/evb_rk3328.h b/include/configs/evb_rk3328.h
index d10e5b1..c985080 100644
--- a/include/configs/evb_rk3328.h
+++ b/include/configs/evb_rk3328.h
@@ -6,6 +6,11 @@
 #ifndef __EVB_RK3328_H
 #define __EVB_RK3328_H
 
+#define ROCKCHIP_DEVICE_SETTINGS \
+		"stdin=serial,usbkbd\0" \
+		"stdout=serial,vidconsole\0" \
+		"stderr=serial,vidconsole\0"
+
 #include <configs/rk3328_common.h>
 
 #endif
diff --git a/include/configs/rk3328_common.h b/include/configs/rk3328_common.h
index e920ec7..2c40674 100644
--- a/include/configs/rk3328_common.h
+++ b/include/configs/rk3328_common.h
@@ -26,6 +26,7 @@
 	ENV_MEM_LAYOUT_SETTINGS \
 	"fdtfile=" CONFIG_DEFAULT_FDT_FILE "\0" \
 	"partitions=" PARTS_DEFAULT \
+	ROCKCHIP_DEVICE_SETTINGS \
 	"boot_targets=" BOOT_TARGETS "\0"
 
 #endif
diff --git a/include/dw_hdmi.h b/include/dw_hdmi.h
index 8acae38..f4d66ed 100644
--- a/include/dw_hdmi.h
+++ b/include/dw_hdmi.h
@@ -534,6 +534,14 @@
 	struct hdmi_vmode video_mode;
 };
 
+struct dw_hdmi;
+
+struct dw_hdmi_phy_ops {
+	int (*phy_set)(struct dw_hdmi *hdmi, uint mpixelclock);
+	void (*read_hpd)(struct dw_hdmi *hdmi, bool hdp_status);
+	void (*setup_hpd)(struct dw_hdmi *hdmi);
+};
+
 struct dw_hdmi {
 	ulong ioaddr;
 	const struct hdmi_mpll_config *mpll_cfg;
@@ -543,8 +551,8 @@
 	u8 reg_io_width;
 	struct hdmi_data_info hdmi_data;
 	struct udevice *ddc_bus;
+	const struct dw_hdmi_phy_ops *ops;
 
-	int (*phy_set)(struct dw_hdmi *hdmi, uint mpixelclock);
 	void (*write_reg)(struct dw_hdmi *hdmi, u8 val, int offset);
 	u8 (*read_reg)(struct dw_hdmi *hdmi, int offset);
 };
@@ -556,5 +564,6 @@
 int dw_hdmi_enable(struct dw_hdmi *hdmi, const struct display_timing *edid);
 int dw_hdmi_read_edid(struct dw_hdmi *hdmi, u8 *buf, int buf_size);
 void dw_hdmi_init(struct dw_hdmi *hdmi);
+int dw_hdmi_detect_hpd(struct dw_hdmi *hdmi);
 
 #endif
diff --git a/include/fdt_support.h b/include/fdt_support.h
index 25600d6..4b71b89 100644
--- a/include/fdt_support.h
+++ b/include/fdt_support.h
@@ -423,6 +423,8 @@
 int fdt_setup_simplefb_node(void *fdt, int node, u64 base_address, u32 width,
 			    u32 height, u32 stride, const char *format);
 
+int fdt_add_fb_mem_rsv(void *blob);
+
 int fdt_overlay_apply_verbose(void *fdt, void *fdto);
 
 int fdt_valid(struct fdt_header **blobp);