diff mbox series

[v2,8/8] drm: xlnx: Intoduce TPG CRTC driver

Message ID 20240312-dp-live-fmt-v2-8-a9c35dc5c50d@amd.com (mailing list archive)
State New, archived
Headers show
Series Setting live video input format for ZynqMP DPSUB | expand

Commit Message

Klymenko, Anatoliy March 13, 2024, 12:55 a.m. UTC
DO NOT MERGE. REFERENCE ONLY.

Add CRTC driver based on AMD/Xilinx Video Test Pattern Generator IP. TPG
based FPGA design represents minimalistic harness useful for testing links
between FPGA based CRTC and external DRM encoders, both FPGA and hardened
IP based.

Add driver for AMD/Xilinx Video Timing Controller. The VTC, working in
generator mode, suplements TPG with video timing signals.

Signed-off-by: Anatoliy Klymenko <anatoliy.klymenko@amd.com>
---
 drivers/gpu/drm/xlnx/Kconfig         |  21 +
 drivers/gpu/drm/xlnx/Makefile        |   4 +
 drivers/gpu/drm/xlnx/xlnx_tpg.c      | 854 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/xlnx_vtc.c      | 452 ++++++++++++++++++
 drivers/gpu/drm/xlnx/xlnx_vtc.h      | 101 +++++
 drivers/gpu/drm/xlnx/xlnx_vtc_list.c | 160 +++++++
 6 files changed, 1592 insertions(+)

Comments

Maxime Ripard March 14, 2024, 12:05 p.m. UTC | #1
Hi,

On Tue, Mar 12, 2024 at 05:55:05PM -0700, Anatoliy Klymenko wrote:
> DO NOT MERGE. REFERENCE ONLY.
> 
> Add CRTC driver based on AMD/Xilinx Video Test Pattern Generator IP. TPG
> based FPGA design represents minimalistic harness useful for testing links
> between FPGA based CRTC and external DRM encoders, both FPGA and hardened
> IP based.
> 
> Add driver for AMD/Xilinx Video Timing Controller. The VTC, working in
> generator mode, suplements TPG with video timing signals.
> 
> Signed-off-by: Anatoliy Klymenko <anatoliy.klymenko@amd.com>

As I said previously, we don't want to have unused APIs, so this patch
should be in a good enough state to be merged if we want to merge the
whole API.

> +/* -----------------------------------------------------------------------------
> + * DRM CRTC
> + */
> +
> +static enum drm_mode_status xlnx_tpg_crtc_mode_valid(struct drm_crtc *crtc,
> +						     const struct drm_display_mode *mode)
> +{
> +	return MODE_OK;
> +}
> +
> +static int xlnx_tpg_crtc_check(struct drm_crtc *crtc,
> +			       struct drm_atomic_state *state)
> +{
> +	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> +	int ret;
> +
> +	if (!crtc_state->enable)
> +		goto out;
> +
> +	ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state);
> +	if (ret)
> +		return ret;
> +
> +out:
> +	return drm_atomic_add_affected_planes(state, crtc);
> +}
> +

[...]

> +
> +static u32 xlnx_tpg_crtc_select_output_bus_format(struct drm_crtc *crtc,
> +						  struct drm_crtc_state *crtc_state,
> +						  const u32 *in_bus_fmts,
> +						  unsigned int num_in_bus_fmts)
> +{
> +	struct xlnx_tpg *tpg = crtc_to_tpg(crtc);
> +	unsigned int i;
> +
> +	for (i = 0; i < num_in_bus_fmts; ++i)
> +		if (in_bus_fmts[i] == tpg->output_bus_format)
> +			return tpg->output_bus_format;
> +
> +	return 0;
> +}
> +
> +static const struct drm_crtc_helper_funcs xlnx_tpg_crtc_helper_funcs = {
> +	.mode_valid = xlnx_tpg_crtc_mode_valid,
> +	.atomic_check = xlnx_tpg_crtc_check,
> +	.atomic_enable = xlnx_tpg_crtc_enable,
> +	.atomic_disable = xlnx_tpg_crtc_disable,
> +	.select_output_bus_format = xlnx_tpg_crtc_select_output_bus_format,
> +};

From that code, it's not clear to me how the CRTC is going to be able to
get what the format is.

It looks like you hardcode it here, but what if there's several that
would fit the bill? Is the CRTC expected to store it into its private
structure?

If so, I would expect it to be in the crtc state, and atomic_enable to
just reuse whatever is in the state.

Maxime
Klymenko, Anatoliy March 14, 2024, 7:43 p.m. UTC | #2
Hi Maxime,

Thank you for the review.

> -----Original Message-----
> From: Maxime Ripard <mripard@kernel.org>
> Sent: Thursday, March 14, 2024 5:05 AM
> To: Klymenko, Anatoliy <Anatoliy.Klymenko@amd.com>
> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com>; Maarten Lankhorst
> <maarten.lankhorst@linux.intel.com>; Thomas Zimmermann
> <tzimmermann@suse.de>; David Airlie <airlied@gmail.com>; Daniel Vetter
> <daniel@ffwll.ch>; Simek, Michal <michal.simek@amd.com>; Andrzej Hajda
> <andrzej.hajda@intel.com>; Neil Armstrong <neil.armstrong@linaro.org>; Robert
> Foss <rfoss@kernel.org>; Jonas Karlman <jonas@kwiboo.se>; Jernej Skrabec
> <jernej.skrabec@gmail.com>; Rob Herring <robh+dt@kernel.org>; Krzysztof
> Kozlowski <krzysztof.kozlowski+dt@linaro.org>; Conor Dooley
> <conor+dt@kernel.org>; Mauro Carvalho Chehab <mchehab@kernel.org>; dri-
> devel@lists.freedesktop.org; linux-arm-kernel@lists.infradead.org; linux-
> kernel@vger.kernel.org; devicetree@vger.kernel.org; linux-
> media@vger.kernel.org
> Subject: Re: [PATCH v2 8/8] drm: xlnx: Intoduce TPG CRTC driver
> 
> Hi,
> 
> On Tue, Mar 12, 2024 at 05:55:05PM -0700, Anatoliy Klymenko wrote:
> > DO NOT MERGE. REFERENCE ONLY.
> >
> > Add CRTC driver based on AMD/Xilinx Video Test Pattern Generator IP.
> > TPG based FPGA design represents minimalistic harness useful for
> > testing links between FPGA based CRTC and external DRM encoders, both
> > FPGA and hardened IP based.
> >
> > Add driver for AMD/Xilinx Video Timing Controller. The VTC, working in
> > generator mode, suplements TPG with video timing signals.
> >
> > Signed-off-by: Anatoliy Klymenko <anatoliy.klymenko@amd.com>
> 
> As I said previously, we don't want to have unused APIs, so this patch should be in
> a good enough state to be merged if we want to merge the whole API.
> 

This is understandable, but even having this API just reviewed by the community will open the path forward for aligning AMD/Xilinx downstream DRM drivers with the upstream kernel.

> > +/*
> > +---------------------------------------------------------------------
> > +--------
> > + * DRM CRTC
> > + */
> > +
> > +static enum drm_mode_status xlnx_tpg_crtc_mode_valid(struct drm_crtc
> *crtc,
> > +						     const struct
> drm_display_mode *mode) {
> > +	return MODE_OK;
> > +}
> > +
> > +static int xlnx_tpg_crtc_check(struct drm_crtc *crtc,
> > +			       struct drm_atomic_state *state) {
> > +	struct drm_crtc_state *crtc_state =
> drm_atomic_get_new_crtc_state(state, crtc);
> > +	int ret;
> > +
> > +	if (!crtc_state->enable)
> > +		goto out;
> > +
> > +	ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state);
> > +	if (ret)
> > +		return ret;
> > +
> > +out:
> > +	return drm_atomic_add_affected_planes(state, crtc); }
> > +
> 
> [...]
> 
> > +
> > +static u32 xlnx_tpg_crtc_select_output_bus_format(struct drm_crtc *crtc,
> > +						  struct drm_crtc_state
> *crtc_state,
> > +						  const u32 *in_bus_fmts,
> > +						  unsigned int
> num_in_bus_fmts) {
> > +	struct xlnx_tpg *tpg = crtc_to_tpg(crtc);
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < num_in_bus_fmts; ++i)
> > +		if (in_bus_fmts[i] == tpg->output_bus_format)
> > +			return tpg->output_bus_format;
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct drm_crtc_helper_funcs xlnx_tpg_crtc_helper_funcs = {
> > +	.mode_valid = xlnx_tpg_crtc_mode_valid,
> > +	.atomic_check = xlnx_tpg_crtc_check,
> > +	.atomic_enable = xlnx_tpg_crtc_enable,
> > +	.atomic_disable = xlnx_tpg_crtc_disable,
> > +	.select_output_bus_format = xlnx_tpg_crtc_select_output_bus_format,
> > +};
> 
> From that code, it's not clear to me how the CRTC is going to be able to get what
> the format is.
> 

It's coming from DT "bus-format" property. The idea is that this property will reflect the FPGA design variation synthesized. 

> It looks like you hardcode it here, but what if there's several that would fit the
> bill? Is the CRTC expected to store it into its private structure?
> 

It's impractical from the resources utilization point of view to support multiple runtime options for FPGA-based CRTCs output signal format, so the bus-format will be runtime fixed but can vary between differently synthesized instances.

> If so, I would expect it to be in the crtc state, and atomic_enable to just reuse
> whatever is in the state.
> 

This could be totally valid for different kinds of CRTCs, although for this particular case, the bus-fomat choice is runtime immutable.

> Maxime

Thank you,
Anatoliy
Maxime Ripard March 15, 2024, 3:24 p.m. UTC | #3
On Thu, Mar 14, 2024 at 07:43:30PM +0000, Klymenko, Anatoliy wrote:
> > > +/*
> > > +---------------------------------------------------------------------
> > > +--------
> > > + * DRM CRTC
> > > + */
> > > +
> > > +static enum drm_mode_status xlnx_tpg_crtc_mode_valid(struct drm_crtc
> > *crtc,
> > > +						     const struct
> > drm_display_mode *mode) {
> > > +	return MODE_OK;
> > > +}
> > > +
> > > +static int xlnx_tpg_crtc_check(struct drm_crtc *crtc,
> > > +			       struct drm_atomic_state *state) {
> > > +	struct drm_crtc_state *crtc_state =
> > drm_atomic_get_new_crtc_state(state, crtc);
> > > +	int ret;
> > > +
> > > +	if (!crtc_state->enable)
> > > +		goto out;
> > > +
> > > +	ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +out:
> > > +	return drm_atomic_add_affected_planes(state, crtc); }
> > > +
> > 
> > [...]
> > 
> > > +
> > > +static u32 xlnx_tpg_crtc_select_output_bus_format(struct drm_crtc *crtc,
> > > +						  struct drm_crtc_state
> > *crtc_state,
> > > +						  const u32 *in_bus_fmts,
> > > +						  unsigned int
> > num_in_bus_fmts) {
> > > +	struct xlnx_tpg *tpg = crtc_to_tpg(crtc);
> > > +	unsigned int i;
> > > +
> > > +	for (i = 0; i < num_in_bus_fmts; ++i)
> > > +		if (in_bus_fmts[i] == tpg->output_bus_format)
> > > +			return tpg->output_bus_format;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static const struct drm_crtc_helper_funcs xlnx_tpg_crtc_helper_funcs = {
> > > +	.mode_valid = xlnx_tpg_crtc_mode_valid,
> > > +	.atomic_check = xlnx_tpg_crtc_check,
> > > +	.atomic_enable = xlnx_tpg_crtc_enable,
> > > +	.atomic_disable = xlnx_tpg_crtc_disable,
> > > +	.select_output_bus_format = xlnx_tpg_crtc_select_output_bus_format,
> > > +};
> > 
> > From that code, it's not clear to me how the CRTC is going to be able to get what
> > the format is.
> > 
> 
> It's coming from DT "bus-format" property. The idea is that this
> property will reflect the FPGA design variation synthesized.
> 
> > It looks like you hardcode it here, but what if there's several that
> > would fit the bill? Is the CRTC expected to store it into its
> > private structure?
> > 
> 
> It's impractical from the resources utilization point of view to
> support multiple runtime options for FPGA-based CRTCs output signal
> format, so the bus-format will be runtime fixed but can vary between
> differently synthesized instances.
>
> > If so, I would expect it to be in the crtc state, and atomic_enable to just reuse
> > whatever is in the state.
> > 
> 
> This could be totally valid for different kinds of CRTCs, although for
> this particular case, the bus-fomat choice is runtime immutable.

Sure, but we're still discussing an API to accomodate your use-case
here. Your usecase is one thing, but the API has to be cover all cases,
and there's definitely some CRTCs out there that support multiple output
formats that would benefit from that API.

And it would mimic the drm_bridge API, which is a nice consistency
bonus.

Maxime
diff mbox series

Patch

diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
index 68ee897de9d7..c40e98c1a5e6 100644
--- a/drivers/gpu/drm/xlnx/Kconfig
+++ b/drivers/gpu/drm/xlnx/Kconfig
@@ -15,3 +15,24 @@  config DRM_ZYNQMP_DPSUB
 	  This is a DRM/KMS driver for ZynqMP DisplayPort controller. Choose
 	  this option if you have a Xilinx ZynqMP SoC with DisplayPort
 	  subsystem.
+
+config DRM_XLNX_BRIDGE_VTC
+	bool "Xilinx DRM VTC Driver"
+	depends on OF
+	help
+	  DRM brige driver for Xilinx Video Timing Controller. Choose
+	  this option to make VTC a part of the CRTC in display pipeline.
+	  Currently the support is added to the Xilinx Video Mixer and
+	  Xilinx PL display CRTC drivers. This driver provides ability
+	  to generate timings through the bridge layer.
+
+config DRM_XLNX_TPG
+	bool "Xilinx DRM TPG Driver"
+	depends on DRM && OF
+	select DRM_XLNX_BRIDGE_VTC
+	select VIDEOMODE_HELPERS
+	help
+	  CRTC driver based on AMD/Xilinx Test Pattern Generator IP. Choose
+	  this driver to enable Test Pattern Generator CRTC. This driver
+	  implements simplistic CRTC with the single plane and is perfect for
+	  testing PL to PS and PL to PL display output pipelines.
diff --git a/drivers/gpu/drm/xlnx/Makefile b/drivers/gpu/drm/xlnx/Makefile
index ea1422a39502..26fb3ad21fa9 100644
--- a/drivers/gpu/drm/xlnx/Makefile
+++ b/drivers/gpu/drm/xlnx/Makefile
@@ -1,2 +1,6 @@ 
 zynqmp-dpsub-y := zynqmp_disp.o zynqmp_dpsub.o zynqmp_dp.o zynqmp_kms.o
 obj-$(CONFIG_DRM_ZYNQMP_DPSUB) += zynqmp-dpsub.o
+xlnx-tpg-objs := xlnx_tpg.o
+xlnx-tpg-$(CONFIG_DRM_XLNX_BRIDGE_VTC) += xlnx_vtc_list.o
+obj-$(CONFIG_DRM_XLNX_TPG) += xlnx-tpg.o
+obj-$(CONFIG_DRM_XLNX_BRIDGE_VTC) += xlnx_vtc.o
diff --git a/drivers/gpu/drm/xlnx/xlnx_tpg.c b/drivers/gpu/drm/xlnx/xlnx_tpg.c
new file mode 100644
index 000000000000..297a65fdb289
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_tpg.c
@@ -0,0 +1,854 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx logicore test pattern generator driver
+ *
+ * Copyright (C) 2024 Advanced Micro Devices, Inc.
+ *
+ * Author: Anatoliy Klymenko <anatoliy.klymenko@amd.com>
+ *
+ * This driver introduces support for the test CRTC based on AMD/Xilinx
+ * Test Pattern Generator IP. The main goal of the driver is to enable
+ * simplistic FPGA design that could be used to test FPGA CRTC to external
+ * encoder IP connectivity.
+ * Reference: https://docs.xilinx.com/r/en-US/pg103-v-tpg
+ */
+
+#include "xlnx_vtc.h"
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <linux/media-bus-format.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_vblank.h>
+#include <linux/component.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <video/videomode.h>
+
+#define DRIVER_NAME	"xlnx-tpg"
+#define DRIVER_DESC	"Xilinx TPG DRM KMS Driver"
+#define DRIVER_DATE	"20240307"
+#define DRIVER_MAJOR	1
+#define DRIVER_MINOR	0
+
+#define XLNX_TPG_CONTROL		0x0000
+#define XLNX_TPG_GLOBAL_IRQ_EN		0x0004
+#define XLNX_TPG_IP_IRQ_EN		0x0008
+#define XLNX_TPG_IP_IRQ_STATUS		0x000C
+#define XLNX_TPG_ACTIVE_HEIGHT		0x0010
+#define XLNX_TPG_ACTIVE_WIDTH		0x0018
+#define XLNX_TPG_PATTERN_ID		0x0020
+#define XLNX_TPG_COLOR_FORMAT		0x0040
+
+#define XLNX_TPG_IP_IRQ_AP_DONE		BIT(0)
+
+#define XLNX_TPG_START			BIT(0)
+#define XLNX_TPG_AUTO_RESTART		BIT(7)
+
+enum xlnx_tpg_pattern {
+	XTPG_PAT_HORIZONTAL_RAMP = 0x1,
+	XTPG_PAT_VERTICAL_RAMP,
+	XTPG_PAT_TEMPORAL_RAMP,
+	XTPG_PAT_SOLID_RED,
+	XTPG_PAT_SOLID_GREEN,
+	XTPG_PAT_SOLID_BLUE,
+	XTPG_PAT_SOLID_BLACK,
+	XTPG_PAT_SOLID_WHITE,
+	XTPG_PAT_COLOR_BARS,
+	XTPG_PAT_ZONE_PLATE,
+	XTPG_PAT_TARTAN_COLOR_BARS,
+	XTPG_PAT_CROSS_HATCH,
+	XTPG_PAT_COLOR_SWEEP,
+	XTPG_PAT_COMBO_RAMP,
+	XTPG_PAT_CHECKER_BOARD,
+	XTPG_PAT_DP_COLOR_RAMP,
+	XTPG_PAT_DP_VERTICAL_LINES,
+	XTPG_PAT_DP_COLOR_SQUARE,
+};
+
+static const struct drm_prop_enum_list xtpg_pattern_list[] = {
+	{ XTPG_PAT_HORIZONTAL_RAMP, "horizontal-ramp" },
+	{ XTPG_PAT_VERTICAL_RAMP, "vertical-ramp" },
+	{ XTPG_PAT_TEMPORAL_RAMP, "temporal-ramp" },
+	{ XTPG_PAT_SOLID_RED, "red" },
+	{ XTPG_PAT_SOLID_GREEN, "green" },
+	{ XTPG_PAT_SOLID_BLUE, "blue" },
+	{ XTPG_PAT_SOLID_BLACK, "black" },
+	{ XTPG_PAT_SOLID_WHITE, "white" },
+	{ XTPG_PAT_COLOR_BARS, "color-bars" },
+	{ XTPG_PAT_ZONE_PLATE, "zone-plate" },
+	{ XTPG_PAT_TARTAN_COLOR_BARS, "tartan-color-bars" },
+	{ XTPG_PAT_CROSS_HATCH, "cross-hatch" },
+	{ XTPG_PAT_COLOR_SWEEP, "color-sweep" },
+	{ XTPG_PAT_COMBO_RAMP, "combo-ramp" },
+	{ XTPG_PAT_CHECKER_BOARD, "checker-board" },
+	{ XTPG_PAT_DP_COLOR_RAMP, "dp-color-ramp" },
+	{ XTPG_PAT_DP_VERTICAL_LINES, "dp-vertical-lines" },
+	{ XTPG_PAT_DP_COLOR_SQUARE, "dp-color-square" },
+};
+
+enum xlnx_tpg_format {
+	XTPG_FMT_RGB = 0x0,
+	XTPG_FMT_YUV_444,
+	XTPG_FMT_YUV_422,
+	XTPG_FMT_YUV_420,
+	XTPG_FMT_INVALID,
+};
+
+struct xlnx_tpg;
+
+/**
+ * struct xlnx_tpg_drm - TPG CRTC DRM/KMS data
+ * @tpg: Back pointer to parent TPG
+ * @dev: DRM device
+ * @crtc: DRM CRTC
+ * @plane: DRM primary plane
+ * @encoder: DRM encoder
+ * @connector: DRM connector
+ * @pattern_prop: DRM property representing TPG video pattern
+ * @event: Pending DRM VBLANK event
+ */
+struct xlnx_tpg_drm {
+	struct xlnx_tpg *tpg;
+	struct drm_device dev;
+	struct drm_crtc crtc;
+	struct drm_plane plane;
+	struct drm_encoder encoder;
+	struct drm_connector *connector;
+	struct drm_property *pattern_prop;
+	struct drm_pending_vblank_event *event;
+};
+
+/**
+ * struct xlnx_tpg_drm - Test Pattern Generator data
+ * @pdev: Platform device
+ * @drm: TPG DRM data
+ * @vtc: Video timing controller interface
+ * @disp_bridge: DRM display bridge
+ * @regs: Mapped TPG IP register space
+ * @irq: TPG IRQ
+ * @output_bus_format: Chosen TPG output bus format
+ * @color_format: TPG color format
+ */
+struct xlnx_tpg {
+	struct platform_device *pdev;
+	struct xlnx_tpg_drm *drm;
+	struct xlnx_vtc_iface *vtc;
+	struct drm_bridge *disp_bridge;
+	void __iomem *regs;
+	int irq;
+	u32 output_bus_format;
+	enum xlnx_tpg_format color_format;
+};
+
+static inline struct xlnx_tpg *crtc_to_tpg(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct xlnx_tpg_drm, crtc)->tpg;
+}
+
+static inline struct xlnx_tpg *plane_to_tpg(struct drm_plane *plane)
+{
+	return container_of(plane, struct xlnx_tpg_drm, plane)->tpg;
+}
+
+static inline struct xlnx_tpg *encoder_to_tpg(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct xlnx_tpg_drm, encoder)->tpg;
+}
+
+struct xlnx_tpg_format_map {
+	u32 bus_format;
+	enum xlnx_tpg_format color_format;
+};
+
+/**
+ * xlnx_tpg_bus_to_color_format - Map media bus format to TPG color format
+ * @bus_format: Media bus format
+ *
+ * Return: TPG color format that matches @bus_format or XTPG_FMT_INVALID if
+ * input media bus format is not supported
+ */
+static enum xlnx_tpg_format xlnx_tpg_bus_to_color_format(u32 bus_format)
+{
+	static const struct xlnx_tpg_format_map format_map[] = {
+		{
+			.bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+			.color_format = XTPG_FMT_RGB,
+		}, {
+			.bus_format = MEDIA_BUS_FMT_RBG888_1X24,
+			.color_format = XTPG_FMT_RGB,
+		}, {
+			.bus_format = MEDIA_BUS_FMT_UYVY8_1X16,
+			.color_format = XTPG_FMT_YUV_422,
+		}, {
+			.bus_format = MEDIA_BUS_FMT_VUY8_1X24,
+			.color_format = XTPG_FMT_YUV_444,
+		}, {
+			.bus_format = MEDIA_BUS_FMT_UYVY10_1X20,
+			.color_format = XTPG_FMT_YUV_422,
+		},
+	};
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(format_map); ++i)
+		if (format_map[i].bus_format == bus_format)
+			return format_map[i].color_format;
+
+	return XTPG_FMT_INVALID;
+}
+
+/* -----------------------------------------------------------------------------
+ * TPG IP ops
+ */
+
+static void xlnx_tpg_write(struct xlnx_tpg *tpg, int offset, u32 val)
+{
+	writel(val, tpg->regs + offset);
+}
+
+static u32 xlnx_tpg_read(struct xlnx_tpg *tpg, int offset)
+{
+	return readl(tpg->regs + offset);
+}
+
+/**
+ * xlnx_tpg_set_dimensions - Set TPG output signal dimensions
+ * @tpg: The TPG
+ * @w: Output video frame width
+ * @h: Output video frame height
+ */
+static void xlnx_tpg_set_dimensions(struct xlnx_tpg *tpg, u16 w, u16 h)
+{
+	xlnx_tpg_write(tpg, XLNX_TPG_ACTIVE_WIDTH, w);
+	xlnx_tpg_write(tpg, XLNX_TPG_ACTIVE_HEIGHT, h);
+}
+
+/**
+ * xlnx_tpg_set_pattern - Set TPG output video pattern
+ * @tpg: The TPG
+ * @pattern: The pattern
+ */
+static void xlnx_tpg_set_pattern(struct xlnx_tpg *tpg,  enum xlnx_tpg_pattern pattern)
+{
+	xlnx_tpg_write(tpg, XLNX_TPG_PATTERN_ID, pattern);
+}
+
+/**
+ * xlnx_tpg_get_pattern - Get programmed TPG output video pattern
+ * @tpg: The TPG
+ *
+ * Return: Video signal pattern programmed
+ */
+static enum xlnx_tpg_pattern xlnx_tpg_get_pattern(struct xlnx_tpg *tpg)
+{
+	return xlnx_tpg_read(tpg, XLNX_TPG_PATTERN_ID);
+}
+
+/**
+ * xlnx_tpg_set_format - Set TPG output video color format
+ * @tpg: The TPG
+ * @format: Color format to program
+ */
+static void xlnx_tpg_set_format(struct xlnx_tpg *tpg,  enum xlnx_tpg_format format)
+{
+	xlnx_tpg_write(tpg, XLNX_TPG_COLOR_FORMAT, format);
+}
+
+/**
+ * xlnx_tpg_start - Start generation of the video signal
+ * @tpg: The TPG
+ */
+static void xlnx_tpg_start(struct xlnx_tpg *tpg)
+{
+	xlnx_tpg_write(tpg, XLNX_TPG_CONTROL, XLNX_TPG_START | XLNX_TPG_AUTO_RESTART);
+}
+
+/**
+ * xlnx_tpg_enable_irq - Enable generation of the frame done interrupts
+ * @tpg: The TPG
+ */
+static void xlnx_tpg_enable_irq(struct xlnx_tpg *tpg)
+{
+	xlnx_tpg_write(tpg, XLNX_TPG_GLOBAL_IRQ_EN, 1);
+	xlnx_tpg_write(tpg, XLNX_TPG_IP_IRQ_EN, 1);
+}
+
+/**
+ * xlnx_tpg_disable_irq - Disable generation of the frame done interrupts
+ * @tpg: The TPG
+ */
+static void xlnx_tpg_disable_irq(struct xlnx_tpg *tpg)
+{
+	xlnx_tpg_write(tpg, XLNX_TPG_GLOBAL_IRQ_EN, 0);
+	xlnx_tpg_write(tpg, XLNX_TPG_IP_IRQ_EN, 0);
+}
+
+static irqreturn_t xlnx_tpg_irq_handler(int irq, void *data)
+{
+	struct xlnx_tpg *tpg = data;
+	struct drm_crtc *crtc = &tpg->drm->crtc;
+	struct drm_pending_vblank_event *event;
+	unsigned long flags;
+	u32 status = xlnx_tpg_read(tpg, XLNX_TPG_IP_IRQ_STATUS);
+
+	xlnx_tpg_write(tpg, XLNX_TPG_IP_IRQ_STATUS, status);
+
+	status &= XLNX_TPG_IP_IRQ_AP_DONE;
+	if (!status)
+		return IRQ_NONE;
+
+	drm_crtc_handle_vblank(crtc);
+
+	/* Finish page flip */
+	spin_lock_irqsave(&crtc->dev->event_lock, flags);
+	event = tpg->drm->event;
+	tpg->drm->event = NULL;
+	if (event) {
+		drm_crtc_send_vblank_event(crtc, event);
+		drm_crtc_vblank_put(crtc);
+	}
+	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * xlnx_tpg_setup_irq - Setup TPG interrupt
+ * @tpg: The TPG
+ *
+ * Return: 0 on success or error code
+ */
+static int xlnx_tpg_setup_irq(struct xlnx_tpg *tpg)
+{
+	struct device_node *node = tpg->pdev->dev.of_node;
+	int ret;
+
+	tpg->irq = irq_of_parse_and_map(node, 0);
+	if (!tpg->irq) {
+		dev_err(&tpg->pdev->dev, "failed to parse irq\n");
+		return -EINVAL;
+	}
+
+	ret = devm_request_irq(&tpg->pdev->dev, tpg->irq, xlnx_tpg_irq_handler,
+			       IRQF_SHARED, "xlnx-tpg", tpg);
+	if (ret < 0) {
+		dev_err(&tpg->pdev->dev, "failed to request irq\n");
+		return ret;
+	}
+
+	xlnx_tpg_enable_irq(tpg);
+
+	return 0;
+}
+
+/**
+ * xlnx_tpg_map_resources - Map TPG register space
+ * @tpg: The TPG
+ *
+ * Return: 0 on success or error code
+ */
+static int xlnx_tpg_map_resources(struct xlnx_tpg *tpg)
+{
+	struct device_node *node = tpg->pdev->dev.of_node;
+	struct resource	res;
+	int ret;
+
+	ret = of_address_to_resource(node, 0, &res);
+	if (ret < 0) {
+		dev_err(&tpg->pdev->dev, "failed to parse resource\n");
+		return ret;
+	}
+
+	tpg->regs = devm_ioremap_resource(&tpg->pdev->dev, &res);
+	if (IS_ERR(tpg->regs)) {
+		ret = PTR_ERR(tpg->regs);
+		dev_err(&tpg->pdev->dev, "failed to map register space\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * DRM plane
+ */
+
+static void xlnx_tpg_plane_atomic_update(struct drm_plane *plane,
+					 struct drm_atomic_state *state)
+{
+	struct xlnx_tpg *tpg = plane_to_tpg(plane);
+	struct drm_crtc *crtc = &tpg->drm->crtc;
+
+	drm_crtc_vblank_on(crtc);
+	if (crtc->state->event) {
+		/* Consume the flip_done event from atomic helper */
+		crtc->state->event->pipe = drm_crtc_index(crtc);
+		drm_crtc_vblank_get(crtc);
+		tpg->drm->event = crtc->state->event;
+		crtc->state->event = NULL;
+	}
+}
+
+static int xlnx_tpg_plane_atomic_check(struct drm_plane *plane,
+				       struct drm_atomic_state *state)
+{
+	struct drm_plane_state *plane_state =
+		drm_atomic_get_new_plane_state(state, plane);
+	struct xlnx_tpg *tpg = plane_to_tpg(plane);
+	struct drm_crtc_state *crtc_state;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, &tpg->drm->crtc);
+
+	return drm_atomic_helper_check_plane_state(plane_state, crtc_state,
+						   DRM_PLANE_NO_SCALING,
+						   DRM_PLANE_NO_SCALING,
+						   false, false);
+}
+
+static const struct drm_plane_helper_funcs xlnx_tpg_plane_helper_funcs = {
+	.prepare_fb = drm_gem_plane_helper_prepare_fb,
+	.atomic_check = xlnx_tpg_plane_atomic_check,
+	.atomic_update = xlnx_tpg_plane_atomic_update,
+};
+
+static bool xlnx_tpg_format_mod_supported(struct drm_plane *plane,
+					  uint32_t format,
+					  uint64_t modifier)
+{
+	return modifier == DRM_FORMAT_MOD_LINEAR;
+}
+
+static int xlnx_tpg_plane_set_property(struct drm_plane *plane,
+				       struct drm_plane_state *state,
+				       struct drm_property *property,
+				       u64 val)
+{
+	struct xlnx_tpg *tpg = plane_to_tpg(plane);
+
+	if (property == tpg->drm->pattern_prop)
+		xlnx_tpg_set_pattern(tpg, val);
+	else
+		return -EINVAL;
+	return 0;
+}
+
+static int xlnx_tpg_plane_get_property(struct drm_plane *plane,
+				       const struct drm_plane_state *state,
+				       struct drm_property *property,
+				       uint64_t *val)
+{
+	struct xlnx_tpg *tpg = plane_to_tpg(plane);
+
+	if (property == tpg->drm->pattern_prop)
+		*val = xlnx_tpg_get_pattern(tpg);
+	else
+		return -EINVAL;
+	return 0;
+}
+
+static const struct drm_plane_funcs xlnx_tpg_plane_funcs = {
+	.update_plane		= drm_atomic_helper_update_plane,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.destroy		= drm_plane_cleanup,
+	.reset			= drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+	.format_mod_supported   = xlnx_tpg_format_mod_supported,
+	.atomic_set_property	= xlnx_tpg_plane_set_property,
+	.atomic_get_property	= xlnx_tpg_plane_get_property,
+};
+
+/**
+ * xlnx_tpg_create_properties - Create TPG DRM properties
+ * @tpg: The TPG
+ */
+static void xlnx_tpg_create_properties(struct xlnx_tpg *tpg)
+{
+	struct drm_device *drm = &tpg->drm->dev;
+	struct drm_mode_object *obj = &tpg->drm->plane.base;
+
+	tpg->drm->pattern_prop = drm_property_create_enum(drm, 0, "pattern", xtpg_pattern_list,
+							  ARRAY_SIZE(xtpg_pattern_list));
+	drm_object_attach_property(obj, tpg->drm->pattern_prop, XTPG_PAT_COLOR_BARS);
+	xlnx_tpg_set_pattern(tpg, XTPG_PAT_COLOR_BARS);
+}
+
+/* -----------------------------------------------------------------------------
+ * DRM encoder
+ */
+
+static const struct drm_encoder_funcs xlnx_tpg_encoder_funcs_cleanup = {
+	.destroy = drm_encoder_cleanup,
+};
+
+/* -----------------------------------------------------------------------------
+ * DRM CRTC
+ */
+
+static enum drm_mode_status xlnx_tpg_crtc_mode_valid(struct drm_crtc *crtc,
+						     const struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static int xlnx_tpg_crtc_check(struct drm_crtc *crtc,
+			       struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	int ret;
+
+	if (!crtc_state->enable)
+		goto out;
+
+	ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state);
+	if (ret)
+		return ret;
+
+out:
+	return drm_atomic_add_affected_planes(state, crtc);
+}
+
+static void xlnx_tpg_crtc_enable(struct drm_crtc *crtc,
+				 struct drm_atomic_state *state)
+{
+	struct videomode vm;
+	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	struct xlnx_tpg *tpg = crtc_to_tpg(crtc);
+
+	if (tpg->vtc) {
+		drm_display_mode_to_videomode(mode, &vm);
+		xlnx_vtc_iface_set_timing(tpg->vtc, &vm);
+		xlnx_vtc_iface_enable(tpg->vtc);
+	}
+
+	xlnx_tpg_set_dimensions(tpg, mode->hdisplay, mode->vdisplay);
+
+	xlnx_tpg_set_format(tpg, tpg->color_format);
+	xlnx_tpg_start(tpg);
+}
+
+static void xlnx_tpg_crtc_disable(struct drm_crtc *crtc,
+				  struct drm_atomic_state *state)
+{
+	struct xlnx_tpg *tpg = crtc_to_tpg(crtc);
+
+	if (tpg->vtc)
+		xlnx_vtc_iface_disable(tpg->vtc);
+	if (crtc->state->event) {
+		complete_all(crtc->state->event->base.completion);
+		crtc->state->event = NULL;
+	}
+	drm_crtc_vblank_off(crtc);
+}
+
+static u32 xlnx_tpg_crtc_select_output_bus_format(struct drm_crtc *crtc,
+						  struct drm_crtc_state *crtc_state,
+						  const u32 *in_bus_fmts,
+						  unsigned int num_in_bus_fmts)
+{
+	struct xlnx_tpg *tpg = crtc_to_tpg(crtc);
+	unsigned int i;
+
+	for (i = 0; i < num_in_bus_fmts; ++i)
+		if (in_bus_fmts[i] == tpg->output_bus_format)
+			return tpg->output_bus_format;
+
+	return 0;
+}
+
+static const struct drm_crtc_helper_funcs xlnx_tpg_crtc_helper_funcs = {
+	.mode_valid = xlnx_tpg_crtc_mode_valid,
+	.atomic_check = xlnx_tpg_crtc_check,
+	.atomic_enable = xlnx_tpg_crtc_enable,
+	.atomic_disable = xlnx_tpg_crtc_disable,
+	.select_output_bus_format = xlnx_tpg_crtc_select_output_bus_format,
+};
+
+static int xlnx_tpg_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct xlnx_tpg *tpg = crtc_to_tpg(crtc);
+
+	xlnx_tpg_enable_irq(tpg);
+
+	return 0;
+}
+
+static void xlnx_tpg_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct xlnx_tpg *tpg = crtc_to_tpg(crtc);
+
+	xlnx_tpg_disable_irq(tpg);
+}
+
+static const struct drm_crtc_funcs xlnx_tpg_crtc_funcs = {
+	.reset = drm_atomic_helper_crtc_reset,
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = xlnx_tpg_crtc_enable_vblank,
+	.disable_vblank = xlnx_tpg_crtc_disable_vblank,
+};
+
+/* -----------------------------------------------------------------------------
+ * Setup & Init
+ */
+
+/**
+ * xlnx_tpg_pipeline_init - Initialize DRM pipeline
+ * @drm: DRM device
+ *
+ * Create and link CRTC, plane, and encoder. Attach external DRM bridge.
+ *
+ * Return: 0 on success, or a negative error code otherwise
+ */
+static int xlnx_tpg_pipeline_init(struct drm_device *drm)
+{
+	static const uint32_t xlnx_tpg_formats[] = {
+		DRM_FORMAT_XRGB8888,
+	};
+	static const uint64_t xlnx_tpg_modifiers[] = {
+		DRM_FORMAT_MOD_LINEAR,
+		DRM_FORMAT_MOD_INVALID,
+	};
+
+	struct xlnx_tpg *tpg = dev_get_drvdata(drm->dev);
+	struct drm_connector *connector;
+	struct drm_encoder *encoder = &tpg->drm->encoder;
+	struct drm_plane *plane = &tpg->drm->plane;
+	struct drm_crtc *crtc = &tpg->drm->crtc;
+	int ret;
+
+	ret = xlnx_tpg_map_resources(tpg);
+	if (ret < 0)
+		return ret;
+
+	ret = xlnx_tpg_setup_irq(tpg);
+	if (ret < 0)
+		return ret;
+
+	drm_plane_helper_add(plane, &xlnx_tpg_plane_helper_funcs);
+	ret = drm_universal_plane_init(drm, plane, 0,
+				       &xlnx_tpg_plane_funcs,
+				       xlnx_tpg_formats,
+				       ARRAY_SIZE(xlnx_tpg_formats),
+				       xlnx_tpg_modifiers,
+				       DRM_PLANE_TYPE_PRIMARY, NULL);
+	if (ret) {
+		dev_err(drm->dev, "failed to init plane: %d\n", ret);
+		return ret;
+	}
+
+	drm_crtc_helper_add(crtc, &xlnx_tpg_crtc_helper_funcs);
+	ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL,
+					&xlnx_tpg_crtc_funcs, NULL);
+	if (ret) {
+		dev_err(drm->dev, "failed to init crtc: %d\n", ret);
+		return ret;
+	}
+
+	encoder->possible_crtcs = drm_crtc_mask(crtc);
+	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
+	if (ret) {
+		dev_err(drm->dev, "failed to init encoder: %d\n", ret);
+		return ret;
+	}
+
+	ret = drm_bridge_attach(encoder, tpg->disp_bridge, NULL,
+				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+	if (ret < 0) {
+		dev_err(drm->dev, "failed to attach bridge to encoder: %d\n", ret);
+		return ret;
+	}
+
+	connector = drm_bridge_connector_init(drm, encoder);
+	if (IS_ERR(connector)) {
+		ret = PTR_ERR(connector);
+		dev_err(drm->dev, "failed to init connector: %d\n", ret);
+		return ret;
+	}
+
+	ret = drm_connector_attach_encoder(connector, encoder);
+	if (ret < 0) {
+		dev_err(drm->dev, "failed to attach encoder: %d\n", ret);
+		return ret;
+	}
+
+	xlnx_tpg_create_properties(tpg);
+
+	return 0;
+}
+
+static const struct drm_mode_config_funcs xlnx_tpg_mode_config_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+DEFINE_DRM_GEM_DMA_FOPS(xlnx_tpg_gem_dma_fops);
+static struct drm_driver xlnx_tpg_drm_driver = {
+	.driver_features		= DRIVER_MODESET | DRIVER_GEM |
+					  DRIVER_ATOMIC,
+	.fops				= &xlnx_tpg_gem_dma_fops,
+	.name				= DRIVER_NAME,
+	.desc				= DRIVER_DESC,
+	.date				= DRIVER_DATE,
+	.major				= DRIVER_MAJOR,
+	.minor				= DRIVER_MINOR,
+	DRM_GEM_DMA_DRIVER_OPS,
+};
+
+/**
+ * xlnx_tpg_drm_init - Initialize DRM device
+ * @dev: The device
+ *
+ * Allocate and initialize DRM device. Configure mode config and initialize
+ * TPG DRM pipeline.
+ *
+ * Return: 0 on success, or a negative error code otherwise
+ */
+static int xlnx_tpg_drm_init(struct device *dev)
+{
+	struct xlnx_tpg *tpg = dev_get_drvdata(dev);
+	struct drm_device *drm;
+	int ret;
+
+	tpg->drm = devm_drm_dev_alloc(dev, &xlnx_tpg_drm_driver,
+				      struct xlnx_tpg_drm, dev);
+	if (IS_ERR(tpg->drm))
+		return PTR_ERR(tpg->drm);
+	tpg->drm->tpg = tpg;
+	drm = &tpg->drm->dev;
+
+	ret = drm_mode_config_init(drm);
+	if (ret < 0)
+		return ret;
+
+	tpg->drm->dev.mode_config.funcs = &xlnx_tpg_mode_config_funcs;
+	tpg->drm->dev.mode_config.min_width = 0;
+	tpg->drm->dev.mode_config.min_height = 0;
+	tpg->drm->dev.mode_config.max_width = 4096;
+	tpg->drm->dev.mode_config.max_height = 4096;
+
+	ret = drm_vblank_init(drm, 1);
+	if (ret < 0)
+		return ret;
+
+	drm_kms_helper_poll_init(drm);
+
+	ret = xlnx_tpg_pipeline_init(drm);
+	if (ret < 0)
+		goto err_poll_fini;
+
+	drm_mode_config_reset(drm);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret < 0)
+		goto err_poll_fini;
+
+	return ret;
+
+err_poll_fini:
+	drm_kms_helper_poll_fini(drm);
+
+	return ret;
+}
+
+/**
+ * xlnx_tpg_drm_fini - Finilize DRM device
+ * @dev: The device
+ */
+static void xlnx_tpg_drm_fini(struct device *dev)
+{
+	struct xlnx_tpg *tpg = dev_get_drvdata(dev);
+
+	drm_kms_helper_poll_fini(&tpg->drm->dev);
+}
+
+static int xlnx_tpg_probe(struct platform_device *pdev)
+{
+	struct xlnx_tpg *tpg;
+	struct device_node *node, *vtc_node;
+	int ret;
+
+	tpg = devm_kzalloc(&pdev->dev, sizeof(*tpg), GFP_KERNEL);
+	if (!tpg)
+		return -ENOMEM;
+
+	tpg->pdev = pdev;
+	platform_set_drvdata(pdev, tpg);
+	node = pdev->dev.of_node;
+
+	tpg->disp_bridge = devm_drm_of_get_bridge(&pdev->dev, node, 0, 0);
+	if (IS_ERR(tpg->disp_bridge)) {
+		ret = PTR_ERR(tpg->disp_bridge);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "failed to discover display bridge\n");
+		return ret;
+	}
+
+	if (of_property_read_u32(node, "bus-format", &tpg->output_bus_format)) {
+		dev_err(&pdev->dev, "required bus-format property undefined\n");
+		return -EINVAL;
+	}
+	tpg->color_format = xlnx_tpg_bus_to_color_format(tpg->output_bus_format);
+
+	vtc_node = of_parse_phandle(node, "xlnx,bridge", 0);
+	if (!vtc_node) {
+		dev_err(&pdev->dev, "required vtc node is missing\n");
+		return -EINVAL;
+	}
+	ret = xlnx_of_find_vtc(vtc_node, &tpg->vtc);
+	if (ret < 0)
+		return ret;
+
+	ret = xlnx_tpg_drm_init(&pdev->dev);
+	if (ret < 0)
+		return ret;
+
+	dev_info(&pdev->dev, "xlnx-tpg driver probed\n");
+
+	return 0;
+}
+
+static int xlnx_tpg_remove(struct platform_device *pdev)
+{
+	xlnx_tpg_drm_fini(&pdev->dev);
+
+	return 0;
+}
+
+static const struct of_device_id xlnx_tpg_of_match[] = {
+	{ .compatible = "xlnx,v-tpg-8.2", },
+	{ .compatible = "xlnx,v-tpg-8.0", },
+	{ /* end of table */ },
+};
+MODULE_DEVICE_TABLE(of, xlnx_tpg_of_match);
+
+static struct platform_driver xlnx_tpg_driver = {
+	.probe			= xlnx_tpg_probe,
+	.remove			= xlnx_tpg_remove,
+	.driver			= {
+		.name		= "xlnx-tpg",
+		.of_match_table	= xlnx_tpg_of_match,
+	},
+};
+
+module_platform_driver(xlnx_tpg_driver);
+
+MODULE_AUTHOR("Anatoliy Klymenko");
+MODULE_DESCRIPTION("Xilinx TPG CRTC Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/xlnx/xlnx_vtc.c b/drivers/gpu/drm/xlnx/xlnx_vtc.c
new file mode 100644
index 000000000000..351b736706d1
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_vtc.c
@@ -0,0 +1,452 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Video Timing Controller support for Xilinx DRM KMS
+ *
+ * Copyright (C) 2013 - 2024 Advanced Micro Devices, Inc.
+ *
+ * Author: Hyun Woo Kwon <hyunk@xilinx.com>
+ *	   Saurabh Sengar <saurabhs@xilinx.com>
+ *	   Vishal Sagar <vishal.sagar@xilinx.com>
+ *	   Anatoliy Klymenko <anatoliy.klymenko@amd.com>
+ *
+ * This driver adds support to control the Xilinx Video Timing
+ * Controller connected to the CRTC.
+ */
+
+#include "xlnx_vtc.h"
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <video/videomode.h>
+
+/* register offsets */
+#define XVTC_CTL		0x000
+#define XVTC_VER		0x010
+#define XVTC_GASIZE		0x060
+#define XVTC_GENC		0x068
+#define XVTC_GPOL		0x06c
+#define XVTC_GHSIZE		0x070
+#define XVTC_GVSIZE		0x074
+#define XVTC_GHSYNC		0x078
+#define XVTC_GVBHOFF_F0		0x07c
+#define XVTC_GVSYNC_F0		0x080
+#define XVTC_GVSHOFF_F0		0x084
+#define XVTC_GVBHOFF_F1		0x088
+#define XVTC_GVSYNC_F1		0x08C
+#define XVTC_GVSHOFF_F1		0x090
+#define XVTC_GASIZE_F1		0x094
+
+/* vtc control register bits */
+#define XVTC_CTL_SWRESET	BIT(31)
+#define XVTC_CTL_FIPSS		BIT(26)
+#define XVTC_CTL_ACPSS		BIT(25)
+#define XVTC_CTL_AVPSS		BIT(24)
+#define XVTC_CTL_HSPSS		BIT(23)
+#define XVTC_CTL_VSPSS		BIT(22)
+#define XVTC_CTL_HBPSS		BIT(21)
+#define XVTC_CTL_VBPSS		BIT(20)
+#define XVTC_CTL_VCSS		BIT(18)
+#define XVTC_CTL_VASS		BIT(17)
+#define XVTC_CTL_VBSS		BIT(16)
+#define XVTC_CTL_VSSS		BIT(15)
+#define XVTC_CTL_VFSS		BIT(14)
+#define XVTC_CTL_VTSS		BIT(13)
+#define XVTC_CTL_HBSS		BIT(11)
+#define XVTC_CTL_HSSS		BIT(10)
+#define XVTC_CTL_HFSS		BIT(9)
+#define XVTC_CTL_HTSS		BIT(8)
+#define XVTC_CTL_GE		BIT(2)
+#define XVTC_CTL_RU		BIT(1)
+
+/* vtc generator polarity register bits */
+#define XVTC_GPOL_FIP		BIT(6)
+#define XVTC_GPOL_ACP		BIT(5)
+#define XVTC_GPOL_AVP		BIT(4)
+#define XVTC_GPOL_HSP		BIT(3)
+#define XVTC_GPOL_VSP		BIT(2)
+#define XVTC_GPOL_HBP		BIT(1)
+#define XVTC_GPOL_VBP		BIT(0)
+
+/* vtc generator horizontal 1 */
+#define XVTC_GH1_BPSTART_MASK	GENMASK(28, 16)
+#define XVTC_GH1_BPSTART_SHIFT	16
+#define XVTC_GH1_SYNCSTART_MASK GENMASK(12, 0)
+/* vtc generator vertical 1 (field 0) */
+#define XVTC_GV1_BPSTART_MASK	GENMASK(28, 16)
+#define XVTC_GV1_BPSTART_SHIFT	16
+#define XVTC_GV1_SYNCSTART_MASK	GENMASK(12, 0)
+/* vtc generator/detector vblank/vsync horizontal offset registers */
+#define XVTC_XVXHOX_HEND_MASK	GENMASK(28, 16)
+#define XVTC_XVXHOX_HEND_SHIFT	16
+#define XVTC_XVXHOX_HSTART_MASK	GENMASK(12, 0)
+
+#define XVTC_GHFRAME_HSIZE	GENMASK(12, 0)
+#define XVTC_GVFRAME_HSIZE_F1	GENMASK(12, 0)
+#define XVTC_GA_ACTSIZE_MASK	GENMASK(12, 0)
+
+/* vtc generator encoding register bits */
+#define XVTC_GENC_INTERL	BIT(6)
+
+/**
+ * struct xlnx_vtc - Xilinx VTC object
+ *
+ * @list: vtc list entry
+ * @dev: device structure
+ * @base: base addr
+ * @ppc: pixels per clock
+ * @axi_clk: AXI Lite clock
+ * @vid_clk: Video clock
+ */
+struct xlnx_vtc {
+	struct xlnx_vtc_iface iface;
+	struct device *dev;
+	void __iomem *base;
+	u32 ppc;
+	struct clk *axi_clk;
+	struct clk *vid_clk;
+};
+
+#define iface_to_vtc(ptr) container_of(ptr, struct xlnx_vtc, iface)
+
+static inline void xlnx_vtc_writel(void __iomem *base, int offset, u32 val)
+{
+	writel(val, base + offset);
+}
+
+static inline u32 xlnx_vtc_readl(void __iomem *base, int offset)
+{
+	return readl(base + offset);
+}
+
+static void xlnx_vtc_reset(struct xlnx_vtc *vtc)
+{
+	u32 reg;
+
+	xlnx_vtc_writel(vtc->base, XVTC_CTL, XVTC_CTL_SWRESET);
+
+	/* enable register update */
+	reg = xlnx_vtc_readl(vtc->base, XVTC_CTL);
+	xlnx_vtc_writel(vtc->base, XVTC_CTL, reg | XVTC_CTL_RU);
+}
+
+/**
+ * xlnx_vtc_enable - Enable the VTC
+ * @vtc: xilinx vtc structure pointer
+ *
+ * Return:
+ * Zero on success.
+ *
+ * This function enables the VTC
+ */
+static int xlnx_vtc_enable(struct xlnx_vtc_iface *iface)
+{
+	u32 reg;
+	struct xlnx_vtc *vtc = iface_to_vtc(iface);
+
+	/* enable generator */
+	reg = xlnx_vtc_readl(vtc->base, XVTC_CTL);
+	xlnx_vtc_writel(vtc->base, XVTC_CTL, reg | XVTC_CTL_GE);
+	dev_dbg(vtc->dev, "enabled\n");
+	return 0;
+}
+
+/**
+ * xlnx_vtc_disable - Disable the VTC
+ * @bridge: xilinx vtc structure pointer
+ *
+ * This function disables and resets the VTC.
+ */
+static void xlnx_vtc_disable(struct xlnx_vtc_iface *iface)
+{
+	u32 reg;
+	struct xlnx_vtc *vtc = iface_to_vtc(iface);
+
+	/* disable generator and reset */
+	reg = xlnx_vtc_readl(vtc->base, XVTC_CTL);
+	xlnx_vtc_writel(vtc->base, XVTC_CTL, reg & ~XVTC_CTL_GE);
+	xlnx_vtc_reset(vtc);
+	dev_dbg(vtc->dev, "disabled\n");
+}
+
+/**
+ * xlnx_vtc_set_timing - Configures the VTC
+ * @vtc: xilinx vtc structure pointer
+ * @vm: video mode requested
+ *
+ * Return:
+ * Zero on success.
+ *
+ * This function calculates the timing values from the video mode
+ * structure passed from the CRTC and configures the VTC.
+ */
+static int xlnx_vtc_set_timing(struct xlnx_vtc_iface *iface,
+			       struct videomode *vm)
+{
+	int ret;
+	u32 reg;
+	u32 htotal, hactive, hsync_start, hbackporch_start;
+	u32 vtotal, vactive, vsync_start, vbackporch_start;
+	struct xlnx_vtc *vtc = iface_to_vtc(iface);
+
+	/* Make sure video clock is in sync with video timing */
+	ret = clk_set_rate(vtc->vid_clk, vm->pixelclock / vtc->ppc);
+	if (ret) {
+		dev_err(vtc->dev, "failed to set video clock rate: %d\n", ret);
+		return ret;
+	}
+
+	reg = xlnx_vtc_readl(vtc->base, XVTC_CTL);
+	xlnx_vtc_writel(vtc->base, XVTC_CTL, reg & ~XVTC_CTL_RU);
+
+	vm->hactive /= vtc->ppc;
+	vm->hfront_porch /= vtc->ppc;
+	vm->hback_porch /= vtc->ppc;
+	vm->hsync_len /= vtc->ppc;
+
+	htotal = vm->hactive + vm->hfront_porch + vm->hsync_len +
+		 vm->hback_porch;
+	vtotal = vm->vactive + vm->vfront_porch + vm->vsync_len +
+		 vm->vback_porch;
+
+	hactive = vm->hactive;
+	vactive = vm->vactive;
+
+	hsync_start = vm->hactive + vm->hfront_porch;
+	vsync_start = vm->vactive + vm->vfront_porch;
+
+	hbackporch_start = hsync_start + vm->hsync_len;
+	vbackporch_start = vsync_start + vm->vsync_len;
+
+	dev_dbg(vtc->dev, "ha: %d, va: %d\n", hactive, vactive);
+	dev_dbg(vtc->dev, "ht: %d, vt: %d\n", htotal, vtotal);
+	dev_dbg(vtc->dev, "hs: %d, hb: %d\n", hsync_start, hbackporch_start);
+	dev_dbg(vtc->dev, "vs: %d, vb: %d\n", vsync_start, vbackporch_start);
+
+	reg = htotal & XVTC_GHFRAME_HSIZE;
+	xlnx_vtc_writel(vtc->base, XVTC_GHSIZE, reg);
+
+	reg = vtotal & XVTC_GVFRAME_HSIZE_F1;
+	reg |= reg << XVTC_GV1_BPSTART_SHIFT;
+	xlnx_vtc_writel(vtc->base, XVTC_GVSIZE, reg);
+
+	reg = hactive & XVTC_GA_ACTSIZE_MASK;
+	reg |= (vactive & XVTC_GA_ACTSIZE_MASK) << 16;
+	xlnx_vtc_writel(vtc->base, XVTC_GASIZE, reg);
+
+	if (vm->flags & DISPLAY_FLAGS_INTERLACED)
+		xlnx_vtc_writel(vtc->base, XVTC_GASIZE_F1, reg);
+
+	reg = hsync_start & XVTC_GH1_SYNCSTART_MASK;
+	reg |= (hbackporch_start << XVTC_GH1_BPSTART_SHIFT) &
+	       XVTC_GH1_BPSTART_MASK;
+	xlnx_vtc_writel(vtc->base, XVTC_GHSYNC, reg);
+
+	reg = vsync_start & XVTC_GV1_SYNCSTART_MASK;
+	reg |= (vbackporch_start << XVTC_GV1_BPSTART_SHIFT) &
+	       XVTC_GV1_BPSTART_MASK;
+	xlnx_vtc_writel(vtc->base, XVTC_GVSYNC_F0, reg);
+
+	if (vm->flags & DISPLAY_FLAGS_INTERLACED) {
+		xlnx_vtc_writel(vtc->base, XVTC_GVSYNC_F1, reg);
+		reg = xlnx_vtc_readl(vtc->base, XVTC_GENC) | XVTC_GENC_INTERL;
+		xlnx_vtc_writel(vtc->base, XVTC_GENC, reg);
+	} else {
+		reg = xlnx_vtc_readl(vtc->base, XVTC_GENC) & ~XVTC_GENC_INTERL;
+		xlnx_vtc_writel(vtc->base, XVTC_GENC, reg);
+	}
+
+	/* configure horizontal offset */
+	/* Calculate and update Generator VBlank Hori field 0 */
+	reg = hactive & XVTC_XVXHOX_HSTART_MASK;
+	reg |= (hactive << XVTC_XVXHOX_HEND_SHIFT) &
+		XVTC_XVXHOX_HEND_MASK;
+	xlnx_vtc_writel(vtc->base, XVTC_GVBHOFF_F0, reg);
+
+	/* Calculate and update Generator VSync Hori field 0 */
+	reg = hsync_start & XVTC_XVXHOX_HSTART_MASK;
+	reg |= (hsync_start << XVTC_XVXHOX_HEND_SHIFT) &
+		XVTC_XVXHOX_HEND_MASK;
+	xlnx_vtc_writel(vtc->base, XVTC_GVSHOFF_F0, reg);
+
+	/* Calculate and update Generator VBlank Hori field 1 */
+	if (vm->flags & DISPLAY_FLAGS_INTERLACED) {
+		reg = hactive & XVTC_XVXHOX_HSTART_MASK;
+		reg |= (hactive << XVTC_XVXHOX_HEND_SHIFT) &
+			XVTC_XVXHOX_HEND_MASK;
+		xlnx_vtc_writel(vtc->base, XVTC_GVBHOFF_F1, reg);
+	}
+
+	/* Calculate and update Generator VBlank Hori field 1 */
+	if (vm->flags & DISPLAY_FLAGS_INTERLACED) {
+		reg =  (hsync_start - (htotal / 2)) & XVTC_XVXHOX_HSTART_MASK;
+		reg |= ((hsync_start - (htotal / 2)) <<
+			XVTC_XVXHOX_HEND_SHIFT) & XVTC_XVXHOX_HEND_MASK;
+	} else {
+		reg =  hsync_start & XVTC_XVXHOX_HSTART_MASK;
+		reg |= (hsync_start << XVTC_XVXHOX_HEND_SHIFT) &
+			XVTC_XVXHOX_HEND_MASK;
+	}
+
+	if (vm->flags & DISPLAY_FLAGS_INTERLACED)
+		xlnx_vtc_writel(vtc->base, XVTC_GVSHOFF_F1, reg);
+
+	/* configure polarity of signals */
+	reg = 0;
+	reg |= XVTC_GPOL_ACP;
+	reg |= XVTC_GPOL_AVP;
+	if (vm->flags & DISPLAY_FLAGS_INTERLACED)
+		reg |= XVTC_GPOL_FIP;
+	if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH) {
+		reg |= XVTC_GPOL_VBP;
+		reg |= XVTC_GPOL_VSP;
+	}
+	if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH) {
+		reg |= XVTC_GPOL_HBP;
+		reg |= XVTC_GPOL_HSP;
+	}
+	xlnx_vtc_writel(vtc->base, XVTC_GPOL, reg);
+
+	/* configure timing source */
+	reg = xlnx_vtc_readl(vtc->base, XVTC_CTL);
+	reg |= XVTC_CTL_VCSS;
+	reg |= XVTC_CTL_VASS;
+	reg |= XVTC_CTL_VBSS;
+	reg |= XVTC_CTL_VSSS;
+	reg |= XVTC_CTL_VFSS;
+	reg |= XVTC_CTL_VTSS;
+	reg |= XVTC_CTL_HBSS;
+	reg |= XVTC_CTL_HSSS;
+	reg |= XVTC_CTL_HFSS;
+	reg |= XVTC_CTL_HTSS;
+	xlnx_vtc_writel(vtc->base, XVTC_CTL, reg);
+
+	reg = xlnx_vtc_readl(vtc->base, XVTC_CTL);
+	xlnx_vtc_writel(vtc->base, XVTC_CTL, reg | XVTC_CTL_RU);
+	dev_dbg(vtc->dev, "set timing done\n");
+
+	return 0;
+}
+
+static int xlnx_vtc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct xlnx_vtc *vtc;
+	struct resource *res;
+	int ret;
+
+	vtc = devm_kzalloc(dev, sizeof(*vtc), GFP_KERNEL);
+	if (!vtc)
+		return -ENOMEM;
+
+	vtc->dev = dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "failed to get resource for device\n");
+		return -EFAULT;
+	}
+
+	vtc->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vtc->base)) {
+		dev_err(dev, "failed to remap io region\n");
+		return PTR_ERR(vtc->base);
+	}
+
+	platform_set_drvdata(pdev, vtc);
+
+	ret = of_property_read_u32(dev->of_node, "xlnx,pixels-per-clock",
+				   &vtc->ppc);
+	if (ret || (vtc->ppc != 1 && vtc->ppc != 2 && vtc->ppc != 4)) {
+		dev_err(dev, "failed to get ppc\n");
+		return ret;
+	}
+	dev_info(dev, "vtc ppc = %d\n", vtc->ppc);
+
+	vtc->axi_clk = devm_clk_get(vtc->dev, "s_axi_aclk");
+	if (IS_ERR(vtc->axi_clk)) {
+		ret = PTR_ERR(vtc->axi_clk);
+		dev_err(dev, "failed to get axi lite clk %d\n", ret);
+		return ret;
+	}
+
+	vtc->vid_clk = devm_clk_get(vtc->dev, "clk");
+	if (IS_ERR(vtc->vid_clk)) {
+		ret = PTR_ERR(vtc->vid_clk);
+		dev_err(dev, "failed to get video clk %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(vtc->axi_clk);
+	if (ret) {
+		dev_err(vtc->dev, "unable to enable axilite clk %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(vtc->vid_clk);
+	if (ret) {
+		dev_err(vtc->dev, "unable to enable video clk %d\n", ret);
+		goto err_axi_clk;
+	}
+
+	xlnx_vtc_reset(vtc);
+
+	vtc->iface.of_node = dev->of_node;
+	vtc->iface.enable = xlnx_vtc_enable;
+	vtc->iface.disable = xlnx_vtc_disable;
+	vtc->iface.set_timing = xlnx_vtc_set_timing;
+
+	xlnx_vtc_register(&vtc->iface);
+
+	dev_info(dev, "Xilinx VTC IP version : 0x%08x\n",
+		 xlnx_vtc_readl(vtc->base, XVTC_VER));
+	dev_info(dev, "Xilinx VTC DRM Bridge driver probed\n");
+	return 0;
+
+err_axi_clk:
+	clk_disable_unprepare(vtc->axi_clk);
+	return ret;
+}
+
+static int xlnx_vtc_remove(struct platform_device *pdev)
+{
+	struct xlnx_vtc *vtc = platform_get_drvdata(pdev);
+
+	xlnx_vtc_unregister(&vtc->iface);
+
+	clk_disable_unprepare(vtc->vid_clk);
+	clk_disable_unprepare(vtc->axi_clk);
+
+	return 0;
+}
+
+static const struct of_device_id xlnx_vtc_of_match[] = {
+	{ .compatible = "xlnx,bridge-v-tc-6.1" },
+	{ /* end of table */ },
+};
+
+MODULE_DEVICE_TABLE(of, xlnx_vtc_of_match);
+
+static struct platform_driver xlnx_vtc_bridge_driver = {
+	.probe = xlnx_vtc_probe,
+	.remove = xlnx_vtc_remove,
+	.driver = {
+		.name = "xlnx,bridge-vtc",
+		.of_match_table = xlnx_vtc_of_match,
+	},
+};
+
+module_init(xlnx_vtc_list_init);
+module_platform_driver(xlnx_vtc_bridge_driver);
+module_exit(xlnx_vtc_list_fini);
+
+MODULE_AUTHOR("Vishal Sagar");
+MODULE_DESCRIPTION("Xilinx VTC Bridge Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/xlnx/xlnx_vtc.h b/drivers/gpu/drm/xlnx/xlnx_vtc.h
new file mode 100644
index 000000000000..8abe01a5d943
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_vtc.h
@@ -0,0 +1,101 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Xilinx DRM VTC header
+ *
+ *  Copyright (C) 2024 Advanced Micro Devices, Inc.
+ *
+ *  Author: Anatoliy Klymenko <anatoliy.klymenko@amd.com>
+ */
+
+#ifndef _XLNX_VTC_H_
+#define _XLNX_VTC_H_
+
+#include <linux/errno.h>
+#include <linux/kconfig.h>
+#include <linux/of.h>
+#include <linux/types.h>
+#include <video/videomode.h>
+
+/**
+ * struct xlnx_vtc_iface - Video Timing Controller interface
+ * @list: VTC list entry
+ * @of_node: Device tree node
+ * @enable: Enable VTC callback
+ * @disable: Disable VTC callback
+ * @set_timing: Program VTC timing callback
+ */
+struct xlnx_vtc_iface {
+	struct list_head list;
+	struct device_node *of_node;
+	int (*enable)(struct xlnx_vtc_iface *vtc);
+	void (*disable)(struct xlnx_vtc_iface *vtc);
+	int (*set_timing)(struct xlnx_vtc_iface *vtc, struct videomode *vm);
+};
+
+#if IS_ENABLED(CONFIG_DRM_XLNX_BRIDGE_VTC)
+
+int xlnx_vtc_iface_enable(struct xlnx_vtc_iface *vtc);
+void xlnx_vtc_iface_disable(struct xlnx_vtc_iface *vtc);
+int xlnx_vtc_iface_set_timing(struct xlnx_vtc_iface *vtc,
+			      struct videomode *vm);
+
+int xlnx_vtc_list_init(void) __init;
+void xlnx_vtc_list_fini(void) __exit;
+
+int xlnx_vtc_register(struct xlnx_vtc_iface *vtc);
+void xlnx_vtc_unregister(struct xlnx_vtc_iface *vtc);
+int xlnx_of_find_vtc(const struct device_node *np,
+		     struct xlnx_vtc_iface **vtc);
+
+#else /* CONFIG_DRM_XLNX_BRIDGE_VTC */
+
+static inline int xlnx_vtc_iface_enable(struct xlnx_vtc_iface *vtc)
+{
+	return vtc ? -ENODEV : 0;
+}
+
+static inline xlnx_vtc_iface_disable(struct xlnx_vtc_iface *vtc)
+{
+}
+
+static inline int xlnx_vtc_iface_set_timing(struct xlnx_vtc_iface *vtc,
+					    struct videomode *vm)
+{
+	return vtc ? -ENODEV : 0;
+}
+
+static inline int xlnx_of_find_vtc(const struct device_node *np,
+				   struct xlnx_vtc_iface **vtc)
+{
+	*vtc = NULL;
+	return -ENODEV;
+}
+
+static inline int xlnx_vtc_list_init(void)
+{
+	return 0;
+}
+
+static inline void xlnx_vtc_list_fini(void)
+{
+}
+
+static inline int xlnx_vtc_register(struct xlnx_vtc_iface *vtc)
+{
+	return 0;
+}
+
+static inline void xlnx_vtc_unregister(struct xlnx_vtc_iface *vtc)
+{
+}
+
+static inline int xlnx_of_find_vtc(const struct device_node *np,
+				   struct xlnx_vtc_iface **vtc)
+{
+	*vtc = NULL;
+	return -ENODEV;
+}
+
+#endif /* CONFIG_DRM_XLNX_BRIDGE_VTC */
+
+#endif /* _XLNX_VTC_H_ */
diff --git a/drivers/gpu/drm/xlnx/xlnx_vtc_list.c b/drivers/gpu/drm/xlnx/xlnx_vtc_list.c
new file mode 100644
index 000000000000..fe8d8447a18c
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_vtc_list.c
@@ -0,0 +1,160 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Video Timing Controller List
+ *
+ * Copyright (C) 2024 Advanced Micro Devices, Inc.
+ *
+ * Author: Anatoliy Klymenko <anatoliy.klymenko@amd.com>
+ */
+
+#include "xlnx_vtc.h"
+
+#include <linux/mutex.h>
+
+/**
+ * struct xlnx_vtc_list - List of registered Video Timing Controllers
+ * @head: Head of the list of registered VTC instances
+ * @lock: Mutex protecting the list
+ * @initialized: Initialization flag
+ */
+struct xlnx_vtc_list {
+	struct list_head head;
+	struct mutex lock;
+	bool initialized;
+};
+
+static struct xlnx_vtc_list vtc_list;
+
+/**
+ * xlnx_vtc_list_init - Initialize VTC list
+ *
+ * Return 0 on success, or error code otherwise
+ */
+int xlnx_vtc_list_init(void)
+{
+	if (!vtc_list.initialized) {
+		INIT_LIST_HEAD(&vtc_list.head);
+		mutex_init(&vtc_list.lock);
+		vtc_list.initialized = true;
+	}
+
+	return 0;
+}
+
+/**
+ * xlnx_vtc_list_fini - Deinitialize VTC list, free resources
+ */
+void xlnx_vtc_list_fini(void)
+{
+	if (vtc_list.initialized) {
+		mutex_destroy(&vtc_list.lock);
+		vtc_list.initialized = false;
+	}
+}
+
+/**
+ * xlnx_vtc_register - Register new VTC instance
+ * @vtc: Pointer to VTC interface instance to register
+ *
+ * Return 0 on success, or error code otherwise
+ */
+int xlnx_vtc_register(struct xlnx_vtc_iface *vtc)
+{
+	if (!vtc || !vtc->of_node)
+		return -EINVAL;
+
+	if (!vtc_list.initialized)
+		return -EFAULT;
+
+	mutex_lock(&vtc_list.lock);
+	list_add_tail(&vtc->list, &vtc_list.head);
+	mutex_unlock(&vtc_list.lock);
+
+	return 0;
+}
+
+/**
+ * xlnx_vtc_unregister - Register new VTC instance
+ * @vtc: The VTC interface instance
+ */
+void xlnx_vtc_unregister(struct xlnx_vtc_iface *vtc)
+{
+	if (!vtc || !vtc_list.initialized)
+		return;
+
+	mutex_lock(&vtc_list.lock);
+	list_del(&vtc->list);
+	mutex_unlock(&vtc_list.lock);
+}
+
+/**
+ * xlnx_of_find_vtc - Lookup VTC instance by OF node pointer
+ * @np: Pointer to VTC device node
+ * @vtc: Output vtc instance pointer
+ *
+ * Return 0 on success, or error code otherwise
+ */
+int xlnx_of_find_vtc(const struct device_node *np, struct xlnx_vtc_iface **vtc)
+{
+	struct xlnx_vtc_iface *vtc_pos;
+	int ret = -EPROBE_DEFER;
+
+	*vtc = NULL;
+
+	if (!vtc_list.initialized)
+		return ret;
+
+	mutex_lock(&vtc_list.lock);
+	list_for_each_entry(vtc_pos, &vtc_list.head, list) {
+		if (vtc_pos->of_node == np) {
+			*vtc = vtc_pos;
+			ret = 0;
+			break;
+		}
+	}
+	mutex_unlock(&vtc_list.lock);
+
+	return ret;
+}
+
+/**
+ * xlnx_vtc_iface_enable - Enable VTC
+ * @vtc: The VTC
+ *
+ * Return 0 on success, or error code otherwise
+ */
+int xlnx_vtc_iface_enable(struct xlnx_vtc_iface *vtc)
+{
+	if (!vtc || !vtc->enable)
+		return -EINVAL;
+
+	return vtc->enable(vtc);
+}
+
+/**
+ * xlnx_vtc_iface_disable - Disable VTC
+ * @vtc: The VTC
+ */
+void xlnx_vtc_iface_disable(struct xlnx_vtc_iface *vtc)
+{
+	if (!vtc || !vtc->disable)
+		return;
+
+	vtc->disable(vtc);
+}
+
+/**
+ * xlnx_vtc_iface_set_timing - Program VTC video timing
+ * @vtc: The VTC
+ * @vm: Video mode to program timing for
+ *
+ * Return 0 on success, or error code otherwise
+ */
+int xlnx_vtc_iface_set_timing(struct xlnx_vtc_iface *vtc,
+			      struct videomode *vm)
+{
+	if (!vtc || !vtc->set_timing)
+		return -EINVAL;
+
+	return vtc->set_timing(vtc, vm);
+}