devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver
@ 2018-01-05  2:05 Hyun Kwon
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

Hi,

This patchset adds the DRM KMS driver for Xilinx ZynqMP DisplayPort subsystem. The Xilinx ZynqMP SoC has a hardened full display pipeline which supports blending of up to 2 planes, and the encoder is DisplayPort v1.2 compatible.

This series mainly includes 2 sets: Xilinx DRM KMS (patch 1/10 - 5/10) and ZynqMP DP subsystem drivers (patch 6/10 - 10/10).

The Xilinx DRM KMS is intended as a common layer shared across other (upcoming) Xilinx sub-drivers. It helps sub-drivers for both hardened as well as soft IPs interoperate together.

ZynqMP DP subsystem driver is a sub-driver that implements corresponding drm objects (crtc, plane, encoder, connector,,,) for ZynqMP SoC display pipeline. The entire pipeline is mainly partitioned into 2 blocks: generic display logic (zynqmp_disp.c) such as blending, csc,,, and the DP transmitter logic (zynqmp_dp.c).

Thanks,
-hyun

Hyun Kwon (10):
  dt-bindings: display: xlnx: Add Xilinx kms bindings
  drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
  drm: xlnx: Add xlnx fb of Xilinx DRM KMS
  drm: xlnx: Add xlnx gem of Xilinx DRM KMS
  drm: xlnx: Xilinx DRM KMS driver
  dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings
  drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display
  drm: xlnx: DRM KMS driver for Xilinx ZynqMP DisplayPort
  drm: xlnx: ZynqMP DP subsystem DRM KMS driver
  drm: xlnx: zynqmp: Add debugfs

 .../devicetree/bindings/display/xlnx/xlnx,kms.txt  |   20 +
 .../bindings/display/xlnx/xlnx,zynqmp-dpsub.txt    |   94 +
 MAINTAINERS                                        |    8 +
 drivers/gpu/drm/Kconfig                            |    2 +
 drivers/gpu/drm/Makefile                           |    1 +
 drivers/gpu/drm/xlnx/Kconfig                       |   44 +
 drivers/gpu/drm/xlnx/Makefile                      |    5 +
 drivers/gpu/drm/xlnx/xlnx_crtc.c                   |  195 ++
 drivers/gpu/drm/xlnx/xlnx_crtc.h                   |   70 +
 drivers/gpu/drm/xlnx/xlnx_drv.c                    |  436 +++
 drivers/gpu/drm/xlnx/xlnx_drv.h                    |   22 +
 drivers/gpu/drm/xlnx/xlnx_fb.c                     |  468 +++
 drivers/gpu/drm/xlnx/xlnx_fb.h                     |   30 +
 drivers/gpu/drm/xlnx/xlnx_gem.c                    |   39 +
 drivers/gpu/drm/xlnx/xlnx_gem.h                    |   18 +
 drivers/gpu/drm/xlnx/zynqmp_disp.c                 | 3261 ++++++++++++++++++++
 drivers/gpu/drm/xlnx/zynqmp_disp.h                 |   28 +
 drivers/gpu/drm/xlnx/zynqmp_dp.c                   | 2168 +++++++++++++
 drivers/gpu/drm/xlnx/zynqmp_dp.h                   |   29 +
 drivers/gpu/drm/xlnx/zynqmp_dpsub.c                |  141 +
 drivers/gpu/drm/xlnx/zynqmp_dpsub.h                |   19 +
 21 files changed, 7098 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
 create mode 100644 Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
 create mode 100644 drivers/gpu/drm/xlnx/Kconfig
 create mode 100644 drivers/gpu/drm/xlnx/Makefile
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.c
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.h
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.c
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.h
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.c
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.h
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.c
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.h
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.c
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.h
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.c
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.h
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.c
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.h

-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms bindings
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
@ 2018-01-05  2:05   ` Hyun Kwon
       [not found]     ` <1515117959-18068-2-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
  2018-01-05  2:05   ` [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS Hyun Kwon
                     ` (9 subsequent siblings)
  10 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

The dt binding for Xilinx DRM KMS driver.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 .../devicetree/bindings/display/xlnx/xlnx,kms.txt    | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt

diff --git a/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
new file mode 100644
index 0000000..8dcd552
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
@@ -0,0 +1,20 @@
+Xilinx KMS Pipeline
+-------------------
+
+Xilinx display pipelines can be designed with hardened video IPs and soft video
+IPs in programmable logic. This KMS module provides the common functionality
+of individual subdevice drivers, and glue logics between them.
+
+Required properties:
+
+- compatible: Must be "xlnx,kms".
+
+- ports: phandles for CRTC ports, using the DT bindings defined in
+  Documentation/devicetree/bindings/graph.txt.
+
+Example:
+
+	xlnx_drm: xlnx_drm {
+		compatible = "xlnx,kms";
+		ports = <&crtc_port>;
+	};
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
  2018-01-05  2:05   ` [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms bindings Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
  2018-01-09  9:37     ` Daniel Vetter
  2018-01-05  2:05   ` [PATCH 03/10] drm: xlnx: Add xlnx fb " Hyun Kwon
                     ` (8 subsequent siblings)
  10 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

xlnx_crtc is a part of Xilinx DRM KMS and a layer that
provides some interface between the Xilinx DRM KMS and
crtc drivers.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 drivers/gpu/drm/xlnx/xlnx_crtc.c | 194 +++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/xlnx_crtc.h |  70 ++++++++++++++
 2 files changed, 264 insertions(+)
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.c
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.h

diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.c b/drivers/gpu/drm/xlnx/xlnx_crtc.c
new file mode 100644
index 0000000..57ee939
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_crtc.c
@@ -0,0 +1,194 @@
+/*
+ * Xilinx DRM crtc driver
+ *
+ *  Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <drm/drmP.h>
+
+#include <linux/list.h>
+
+#include "xlnx_crtc.h"
+
+/*
+ * Overview
+ * --------
+ *
+ * The Xilinx CRTC layer is to enable the custom interface to CRTC drivers.
+ * The interface is used by Xilinx DRM driver where it needs CRTC
+ * functionailty. CRTC drivers should attach the desired callbacks
+ * to struct xlnx_crtc and register the xlnx_crtc with correcsponding
+ * drm_device. It's highly recommended CRTC drivers register all callbacks
+ * even though many of them are optional.
+ * The CRTC helper simply walks through the registered CRTC device,
+ * and call the callbacks.
+ */
+
+/**
+ * struct xlnx_crtc_helper - Xilinx CRTC helper
+ * @xlnx_crtcs: list of Xilinx CRTC devices
+ * @lock: lock to protect @xlnx_crtcs
+ * @drm: back pointer to DRM core
+ */
+struct xlnx_crtc_helper {
+	struct list_head xlnx_crtcs;
+	struct mutex lock; /* lock for @xlnx_crtcs */
+	struct drm_device *drm;
+};
+
+#define XLNX_CRTC_MAX_HEIGHT_WIDTH	UINT_MAX
+
+int xlnx_crtc_helper_enable_vblank(struct xlnx_crtc_helper *helper,
+				   unsigned int crtc_id)
+{
+	struct xlnx_crtc *crtc;
+
+	list_for_each_entry(crtc, &helper->xlnx_crtcs, list)
+		if (drm_crtc_index(&crtc->crtc) == crtc_id)
+			if (crtc->enable_vblank)
+				return crtc->enable_vblank(crtc);
+	return -ENODEV;
+}
+
+void xlnx_crtc_helper_disable_vblank(struct xlnx_crtc_helper *helper,
+				     unsigned int crtc_id)
+{
+	struct xlnx_crtc *crtc;
+
+	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
+		if (drm_crtc_index(&crtc->crtc) == crtc_id) {
+			if (crtc->disable_vblank)
+				crtc->disable_vblank(crtc);
+			return;
+		}
+	}
+}
+
+unsigned int xlnx_crtc_helper_get_align(struct xlnx_crtc_helper *helper)
+{
+	struct xlnx_crtc *crtc;
+	unsigned int align = 1, tmp;
+
+	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
+		if (crtc->get_align) {
+			tmp = crtc->get_align(crtc);
+			align = ALIGN(align, tmp);
+		}
+	}
+
+	return align;
+}
+
+u64 xlnx_crtc_helper_get_dma_mask(struct xlnx_crtc_helper *helper)
+{
+	struct xlnx_crtc *crtc;
+	u64 mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8), tmp;
+
+	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
+		if (crtc->get_dma_mask) {
+			tmp = crtc->get_dma_mask(crtc);
+			mask = min(mask, tmp);
+		}
+	}
+
+	return mask;
+}
+
+int xlnx_crtc_helper_get_max_width(struct xlnx_crtc_helper *helper)
+{
+	struct xlnx_crtc *crtc;
+	unsigned int width = XLNX_CRTC_MAX_HEIGHT_WIDTH, tmp;
+
+	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
+		if (crtc->get_max_width) {
+			tmp = crtc->get_max_width(crtc);
+			width = min(width, tmp);
+		}
+	}
+
+	return width;
+}
+
+int xlnx_crtc_helper_get_max_height(struct xlnx_crtc_helper *helper)
+{
+	struct xlnx_crtc *crtc;
+	unsigned int height = XLNX_CRTC_MAX_HEIGHT_WIDTH, tmp;
+
+	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
+		if (crtc->get_max_height) {
+			tmp = crtc->get_max_height(crtc);
+			height = min(height, tmp);
+		}
+	}
+
+	return height;
+}
+
+uint32_t xlnx_crtc_helper_get_format(struct xlnx_crtc_helper *helper)
+{
+	struct xlnx_crtc *crtc;
+	u32 format = 0, tmp;
+
+	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
+		if (crtc->get_format) {
+			tmp = crtc->get_format(crtc);
+			if (format && format != tmp)
+				return 0;
+			format = tmp;
+		}
+	}
+
+	return format;
+}
+
+struct xlnx_crtc_helper *xlnx_crtc_helper_init(struct drm_device *drm)
+{
+	struct xlnx_crtc_helper *helper;
+
+	helper = devm_kzalloc(drm->dev, sizeof(*helper), GFP_KERNEL);
+	if (!helper)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&helper->xlnx_crtcs);
+	mutex_init(&helper->lock);
+	helper->drm = drm;
+
+	return helper;
+}
+
+void xlnx_crtc_helper_fini(struct drm_device *drm,
+			   struct xlnx_crtc_helper *helper)
+{
+	if (WARN_ON(helper->drm != drm))
+		return;
+
+	if (WARN_ON(!list_empty(&helper->xlnx_crtcs)))
+		return;
+
+	mutex_destroy(&helper->lock);
+	devm_kfree(drm->dev, helper);
+}
+
+void xlnx_crtc_register(struct drm_device *drm, struct xlnx_crtc *crtc)
+{
+	struct xlnx_crtc_helper *helper = xlnx_get_crtc_helper(drm);
+
+	mutex_lock(&helper->lock);
+	list_add_tail(&crtc->list, &helper->xlnx_crtcs);
+	mutex_unlock(&helper->lock);
+}
+EXPORT_SYMBOL_GPL(xlnx_crtc_register);
+
+void xlnx_crtc_unregister(struct drm_device *drm, struct xlnx_crtc *crtc)
+{
+	struct xlnx_crtc_helper *helper = xlnx_get_crtc_helper(drm);
+
+	mutex_lock(&helper->lock);
+	list_del(&crtc->list);
+	mutex_unlock(&helper->lock);
+}
+EXPORT_SYMBOL_GPL(xlnx_crtc_unregister);
diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.h b/drivers/gpu/drm/xlnx/xlnx_crtc.h
new file mode 100644
index 0000000..db7404e
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_crtc.h
@@ -0,0 +1,70 @@
+/*
+ * Xilinx DRM crtc header
+ *
+ *  Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _XLNX_CRTC_H_
+#define _XLNX_CRTC_H_
+
+/**
+ * struct xlnx_crtc - Xilinx CRTC device
+ * @crtc: DRM CRTC device
+ * @list: list node for Xilinx CRTC device list
+ * @enable_vblank: Enable vblank
+ * @disable_vblank: Disable vblank
+ * @get_align: Get the alignment requirement of CRTC device
+ * @get_dma_mask: Get the dma mask of CRTC device
+ * @get_max_width: Get the maximum supported width
+ * @get_max_height: Get the maximum supported height
+ * @get_format: Get the current format of CRTC device
+ */
+struct xlnx_crtc {
+	struct drm_crtc crtc;
+	struct list_head list;
+	int (*enable_vblank)(struct xlnx_crtc *crtc);
+	void (*disable_vblank)(struct xlnx_crtc *crtc);
+	unsigned int (*get_align)(struct xlnx_crtc *crtc);
+	u64 (*get_dma_mask)(struct xlnx_crtc *crtc);
+	int (*get_max_width)(struct xlnx_crtc *crtc);
+	int (*get_max_height)(struct xlnx_crtc *crtc);
+	uint32_t (*get_format)(struct xlnx_crtc *crtc);
+};
+
+/*
+ * Helper functions: used within Xlnx DRM
+ */
+
+struct xlnx_crtc_helper;
+
+int xlnx_crtc_helper_enable_vblank(struct xlnx_crtc_helper *helper,
+				   unsigned int crtc_id);
+void xlnx_crtc_helper_disable_vblank(struct xlnx_crtc_helper *helper,
+				     unsigned int crtc_id);
+unsigned int xlnx_crtc_helper_get_align(struct xlnx_crtc_helper *helper);
+u64 xlnx_crtc_helper_get_dma_mask(struct xlnx_crtc_helper *helper);
+int xlnx_crtc_helper_get_max_width(struct xlnx_crtc_helper *helper);
+int xlnx_crtc_helper_get_max_height(struct xlnx_crtc_helper *helper);
+uint32_t xlnx_crtc_helper_get_format(struct xlnx_crtc_helper *helper);
+
+struct xlnx_crtc_helper *xlnx_crtc_helper_init(struct drm_device *drm);
+void xlnx_crtc_helper_fini(struct drm_device *drm,
+			   struct xlnx_crtc_helper *helper);
+
+/*
+ * CRTC registration: used by other sub-driver modules
+ */
+
+static inline struct xlnx_crtc *to_xlnx_crtc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct xlnx_crtc, crtc);
+}
+
+void xlnx_crtc_register(struct drm_device *drm, struct xlnx_crtc *crtc);
+void xlnx_crtc_unregister(struct drm_device *drm, struct xlnx_crtc *crtc);
+
+#endif /* _XLNX_CRTC_H_ */
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 03/10] drm: xlnx: Add xlnx fb of Xilinx DRM KMS
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
  2018-01-05  2:05   ` [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms bindings Hyun Kwon
  2018-01-05  2:05   ` [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
  2018-01-09  9:35     ` Daniel Vetter
  2018-01-05  2:05   ` [PATCH 04/10] drm: xlnx: Add xlnx gem " Hyun Kwon
                     ` (7 subsequent siblings)
  10 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

Helpers for framebuffers backed by cma allocator.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 drivers/gpu/drm/xlnx/xlnx_fb.c | 467 +++++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/xlnx_fb.h |  30 +++
 2 files changed, 497 insertions(+)
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.c
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.h

diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.c b/drivers/gpu/drm/xlnx/xlnx_fb.c
new file mode 100644
index 0000000..dbe9fbf
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_fb.c
@@ -0,0 +1,467 @@
+/*
+ * Xilinx DRM KMS Framebuffer helper
+ *
+ *  Copyright (C) 2015 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * Based on drm_fb_cma_helper.c
+ *
+ *  Copyright (C) 2012 Analog Device Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "xlnx_fb.h"
+
+#define XLNX_MAX_PLANES	4
+
+struct xlnx_fb {
+	struct drm_framebuffer		base;
+	struct drm_gem_cma_object	*obj[XLNX_MAX_PLANES];
+};
+
+struct xlnx_fbdev {
+	struct drm_fb_helper fb_helper;
+	struct xlnx_fb	*fb;
+	unsigned int align;
+	unsigned int vres_mult;
+};
+
+static inline struct xlnx_fbdev *to_fbdev(struct drm_fb_helper *fb_helper)
+{
+	return container_of(fb_helper, struct xlnx_fbdev, fb_helper);
+}
+
+static inline struct xlnx_fb *to_fb(struct drm_framebuffer *base_fb)
+{
+	return container_of(base_fb, struct xlnx_fb, base);
+}
+
+static void xlnx_fb_destroy(struct drm_framebuffer *base_fb)
+{
+	struct xlnx_fb *fb = to_fb(base_fb);
+	int i;
+
+	for (i = 0; i < XLNX_MAX_PLANES; i++)
+		if (fb->obj[i])
+			drm_gem_object_unreference_unlocked(&fb->obj[i]->base);
+
+	drm_framebuffer_cleanup(base_fb);
+	kfree(fb);
+}
+
+static int xlnx_fb_create_handle(struct drm_framebuffer *base_fb,
+				 struct drm_file *file_priv,
+				 unsigned int *handle)
+{
+	struct xlnx_fb *fb = to_fb(base_fb);
+
+	return drm_gem_handle_create(file_priv, &fb->obj[0]->base, handle);
+}
+
+static struct drm_framebuffer_funcs xlnx_fb_funcs = {
+	.destroy	= xlnx_fb_destroy,
+	.create_handle	= xlnx_fb_create_handle,
+};
+
+/**
+ * xlnx_fb_alloc - Allocate a xlnx_fb
+ * @drm: DRM object
+ * @mode_cmd: drm_mode_fb_cmd2 struct
+ * @obj: pointers for returned drm_gem_cma_objects
+ * @num_planes: number of planes to be allocated
+ *
+ * This function is based on drm_fb_cma_alloc().
+ *
+ * Return: a xlnx_fb object, or ERR_PTR.
+ */
+static struct xlnx_fb *
+xlnx_fb_alloc(struct drm_device *drm,
+	      const struct drm_mode_fb_cmd2 *mode_cmd,
+	      struct drm_gem_cma_object **obj, unsigned int num_planes)
+{
+	struct xlnx_fb *fb;
+	int ret;
+	int i;
+
+	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+	if (!fb)
+		return ERR_PTR(-ENOMEM);
+
+	drm_helper_mode_fill_fb_struct(drm, &fb->base, mode_cmd);
+
+	for (i = 0; i < num_planes; i++)
+		fb->obj[i] = obj[i];
+
+	ret = drm_framebuffer_init(drm, &fb->base, &xlnx_fb_funcs);
+	if (ret) {
+		dev_err(drm->dev, "Failed to initialize fb: %d\n", ret);
+		kfree(fb);
+		return ERR_PTR(ret);
+	}
+
+	return fb;
+}
+
+/**
+ * xlnx_fb_get_paddr - Get physycal address of framebuffer
+ * @base_fb: the framebuffer
+ * @plane: which plane
+ *
+ * This function is based on drm_fb_cma_get_gem_obj().
+ *
+ * Return: a physical address of the plane, or 0
+ */
+dma_addr_t
+xlnx_fb_get_paddr(struct drm_framebuffer *base_fb, unsigned int plane)
+{
+	struct xlnx_fb *fb = to_fb(base_fb);
+
+	if (plane >= XLNX_MAX_PLANES)
+		return 0;
+
+	return fb->obj[plane]->paddr;
+}
+EXPORT_SYMBOL_GPL(xlnx_fb_get_paddr);
+
+static int
+xlnx_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
+{
+	struct drm_fb_helper *fb_helper = info->par;
+	unsigned int i;
+	int ret = 0;
+
+	switch (cmd) {
+	case FBIO_WAITFORVSYNC:
+		for (i = 0; i < fb_helper->crtc_count; i++) {
+			struct drm_mode_set *mode_set;
+			struct drm_crtc *crtc;
+
+			mode_set = &fb_helper->crtc_info[i].mode_set;
+			crtc = mode_set->crtc;
+			ret = drm_crtc_vblank_get(crtc);
+			if (!ret) {
+				drm_crtc_wait_one_vblank(crtc);
+				drm_crtc_vblank_put(crtc);
+			}
+		}
+		return ret;
+	default:
+		return -ENOTTY;
+	}
+
+	return 0;
+}
+
+static struct fb_ops xlnx_fbdev_ops = {
+	.owner		= THIS_MODULE,
+	.fb_fillrect	= sys_fillrect,
+	.fb_copyarea	= sys_copyarea,
+	.fb_imageblit	= sys_imageblit,
+	.fb_check_var	= drm_fb_helper_check_var,
+	.fb_set_par	= drm_fb_helper_set_par,
+	.fb_blank	= drm_fb_helper_blank,
+	.fb_pan_display	= drm_fb_helper_pan_display,
+	.fb_setcmap	= drm_fb_helper_setcmap,
+	.fb_ioctl	= xlnx_fb_ioctl,
+};
+
+/**
+ * xlnx_fbdev_create - Create the fbdev with a framebuffer
+ * @fb_helper: fb helper structure
+ * @size: framebuffer size info
+ *
+ * This function is based on drm_fbdev_cma_create().
+ *
+ * Return: 0 if successful, or the error code.
+ */
+static int xlnx_fbdev_create(struct drm_fb_helper *fb_helper,
+			     struct drm_fb_helper_surface_size *size)
+{
+	struct xlnx_fbdev *fbdev = to_fbdev(fb_helper);
+	struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+	struct drm_device *drm = fb_helper->dev;
+	struct drm_gem_cma_object *obj;
+	struct drm_framebuffer *base_fb;
+	unsigned int bytes_per_pixel;
+	unsigned long offset;
+	struct fb_info *fbi;
+	size_t bytes;
+	int ret;
+
+	dev_dbg(drm->dev, "surface width(%d), height(%d) and bpp(%d)\n",
+		size->surface_width, size->surface_height, size->surface_bpp);
+
+	bytes_per_pixel = DIV_ROUND_UP(size->surface_bpp, 8);
+
+	mode_cmd.width = size->surface_width;
+	mode_cmd.height = size->surface_height;
+	mode_cmd.pitches[0] = ALIGN(size->surface_width * bytes_per_pixel,
+				    fbdev->align);
+	mode_cmd.pixel_format = xlnx_get_format(drm);
+
+	mode_cmd.height *= fbdev->vres_mult;
+	bytes = mode_cmd.pitches[0] * mode_cmd.height;
+	obj = drm_gem_cma_create(drm, bytes);
+	if (IS_ERR(obj))
+		return PTR_ERR(obj);
+
+	fbi = framebuffer_alloc(0, drm->dev);
+	if (!fbi) {
+		dev_err(drm->dev, "Failed to allocate framebuffer info.\n");
+		ret = -ENOMEM;
+		goto err_drm_gem_cma_free_object;
+	}
+
+	fbdev->fb = xlnx_fb_alloc(drm, &mode_cmd, &obj, 1);
+	if (IS_ERR(fbdev->fb)) {
+		dev_err(drm->dev, "Failed to allocate DRM framebuffer.\n");
+		ret = PTR_ERR(fbdev->fb);
+		goto err_framebuffer_release;
+	}
+
+	base_fb = &fbdev->fb->base;
+	fb_helper->fb = base_fb;
+	fb_helper->fbdev = fbi;
+	fbi->par = fb_helper;
+	fbi->flags = FBINFO_FLAG_DEFAULT;
+	fbi->fbops = &xlnx_fbdev_ops;
+
+	ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
+	if (ret) {
+		dev_err(drm->dev, "Failed to allocate color map.\n");
+		goto err_xlnx_fb_destroy;
+	}
+
+	drm_fb_helper_fill_fix(fbi, base_fb->pitches[0],
+			       base_fb->format->depth);
+	drm_fb_helper_fill_var(fbi, fb_helper, base_fb->width, base_fb->height);
+	fbi->var.yres = base_fb->height / fbdev->vres_mult;
+
+	offset = fbi->var.xoffset * bytes_per_pixel;
+	offset += fbi->var.yoffset * base_fb->pitches[0];
+
+	drm->mode_config.fb_base = (resource_size_t)obj->paddr;
+	fbi->screen_base = (char __iomem *)(obj->vaddr + offset);
+	fbi->fix.smem_start = (unsigned long)(obj->paddr + offset);
+	fbi->screen_size = bytes;
+	fbi->fix.smem_len = bytes;
+
+	return 0;
+
+err_xlnx_fb_destroy:
+	drm_framebuffer_unregister_private(base_fb);
+	xlnx_fb_destroy(base_fb);
+err_framebuffer_release:
+	framebuffer_release(fbi);
+err_drm_gem_cma_free_object:
+	drm_gem_cma_free_object(&obj->base);
+	return ret;
+}
+
+static struct drm_fb_helper_funcs xlnx_fb_helper_funcs = {
+	.fb_probe = xlnx_fbdev_create,
+};
+
+/**
+ * xlnx_fb_init - Allocate and initializes the Xilinx framebuffer
+ * @drm: DRM device
+ * @preferred_bpp: preferred bits per pixel for the device
+ * @max_conn_count: maximum number of connectors
+ * @align: alignment value for pitch
+ * @vres_mult: multiplier for virtual resolution
+ *
+ * This function is based on drm_fbdev_cma_init().
+ *
+ * Return: a newly allocated drm_fb_helper struct or a ERR_PTR.
+ */
+struct drm_fb_helper *
+xlnx_fb_init(struct drm_device *drm, int preferred_bpp,
+	     unsigned int max_conn_count, unsigned int align,
+	     unsigned int vres_mult)
+{
+	struct xlnx_fbdev *fbdev;
+	struct drm_fb_helper *fb_helper;
+	int ret;
+
+	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
+	if (!fbdev)
+		return ERR_PTR(-ENOMEM);
+
+	fbdev->vres_mult = vres_mult;
+	fbdev->align = align;
+	fb_helper = &fbdev->fb_helper;
+	drm_fb_helper_prepare(drm, fb_helper, &xlnx_fb_helper_funcs);
+
+	ret = drm_fb_helper_init(drm, fb_helper, max_conn_count);
+	if (ret < 0) {
+		dev_err(drm->dev, "Failed to initialize drm fb helper.\n");
+		goto err_free;
+	}
+
+	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
+	if (ret < 0) {
+		dev_err(drm->dev, "Failed to add connectors.\n");
+		goto err_drm_fb_helper_fini;
+	}
+
+	ret = drm_fb_helper_initial_config(fb_helper, preferred_bpp);
+	if (ret < 0) {
+		dev_err(drm->dev, "Failed to set initial hw configuration.\n");
+		goto err_drm_fb_helper_fini;
+	}
+
+	return fb_helper;
+
+err_drm_fb_helper_fini:
+	drm_fb_helper_fini(fb_helper);
+err_free:
+	kfree(fbdev);
+	return ERR_PTR(ret);
+}
+
+/**
+ * xlnx_fbdev_defio_fini - Free the defio fb
+ * @fbi: fb_info struct
+ *
+ * This function is based on drm_fbdev_cma_defio_fini().
+ */
+static void xlnx_fbdev_defio_fini(struct fb_info *fbi)
+{
+	if (!fbi->fbdefio)
+		return;
+
+	fb_deferred_io_cleanup(fbi);
+	kfree(fbi->fbdefio);
+	kfree(fbi->fbops);
+}
+
+/**
+ * xlnx_fbdev_fini - Free the Xilinx framebuffer
+ * @fb_helper: drm_fb_helper struct
+ *
+ * This function is based on drm_fbdev_cma_fini().
+ */
+void xlnx_fb_fini(struct drm_fb_helper *fb_helper)
+{
+	struct xlnx_fbdev *fbdev = to_fbdev(fb_helper);
+
+	drm_fb_helper_unregister_fbi(&fbdev->fb_helper);
+	if (fbdev->fb_helper.fbdev)
+		xlnx_fbdev_defio_fini(fbdev->fb_helper.fbdev);
+
+	if (fbdev->fb_helper.fb)
+		drm_framebuffer_remove(fbdev->fb_helper.fb);
+
+	drm_fb_helper_fini(&fbdev->fb_helper);
+	kfree(fbdev);
+}
+
+/**
+ * xlnx_fb_restore_mode - Restores initial framebuffer mode
+ * @fb_helper: drm_fb_helper struct, may be NULL
+ *
+ * This function is based on drm_fbdev_cma_restore_mode() and usually called
+ * from the Xilinx DRM drivers lastclose callback.
+ */
+void xlnx_fb_restore_mode(struct drm_fb_helper *fb_helper)
+{
+	if (!fb_helper)
+		return;
+	drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
+}
+
+/**
+ * xlnx_fb_create - (struct drm_mode_config_funcs *)->fb_create callback
+ * @drm: DRM device
+ * @file_priv: drm file private data
+ * @mode_cmd: mode command for fb creation
+ *
+ * This functions creates a drm_framebuffer for given mode @mode_cmd. This
+ * functions is intended to be used for the fb_create callback function of
+ * drm_mode_config_funcs.
+ *
+ * Return: a drm_framebuffer object if successful, or ERR_PTR.
+ */
+struct drm_framebuffer *
+xlnx_fb_create(struct drm_device *drm, struct drm_file *file_priv,
+	       const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct xlnx_fb *fb;
+	struct drm_gem_cma_object *objs[XLNX_MAX_PLANES];
+	struct drm_gem_object *obj;
+	const struct drm_format_info *info;
+	struct drm_format_name_buf format_name;
+	int ret;
+	int i;
+
+	info = drm_format_info(mode_cmd->pixel_format);
+	if (!info) {
+		dev_err(drm->dev, "unsupported framebuffer format %s\n",
+			drm_get_format_name(mode_cmd->pixel_format,
+					    &format_name));
+		ret = -EINVAL;
+		goto err_out;
+	}
+
+	for (i = 0; i < info->num_planes; i++) {
+		unsigned int width = mode_cmd->width / (i ? info->hsub : 1);
+		unsigned int height = mode_cmd->height / (i ? info->vsub : 1);
+		unsigned int min_size;
+
+		obj = drm_gem_object_lookup(file_priv,
+					    mode_cmd->handles[i]);
+		if (!obj) {
+			dev_err(drm->dev, "Failed to lookup GEM object\n");
+			ret = -ENXIO;
+			goto err_gem_object_unreference;
+		}
+
+		min_size = (height - 1) * mode_cmd->pitches[i] + width *
+			   info->cpp[i] + mode_cmd->offsets[i];
+
+		if (obj->size < min_size) {
+			drm_gem_object_unreference_unlocked(obj);
+			ret = -EINVAL;
+			goto err_gem_object_unreference;
+		}
+		objs[i] = to_drm_gem_cma_obj(obj);
+	}
+
+	fb = xlnx_fb_alloc(drm, mode_cmd, objs, i);
+	if (IS_ERR(fb)) {
+		ret = PTR_ERR(fb);
+		goto err_gem_object_unreference;
+	}
+
+	fb->base.format = info;
+
+	return &fb->base;
+
+err_gem_object_unreference:
+	for (i--; i >= 0; i--)
+		drm_gem_object_unreference_unlocked(&objs[i]->base);
+err_out:
+	return ERR_PTR(ret);
+}
+
+/**
+ * xlnx_fb_hotplug_event - Poll for hotpulug events
+ * @fb_helper: drm_fb_helper struct, may be NULL
+ *
+ * This function is based on drm_fbdev_cma_hotplug_event() and usually called
+ * from the Xilinx DRM drivers output_poll_changed callback.
+ */
+void xlnx_fb_hotplug_event(struct drm_fb_helper *fb_helper)
+{
+	if (!fb_helper)
+		return;
+	drm_fb_helper_hotplug_event(fb_helper);
+}
diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.h b/drivers/gpu/drm/xlnx/xlnx_fb.h
new file mode 100644
index 0000000..3f7e962
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_fb.h
@@ -0,0 +1,30 @@
+/*
+ * Xilinx DRM KMS Framebuffer helper header
+ *
+ *  Copyright (C) 2015 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _XLNX_FB_H_
+#define _XLNX_FB_H_
+
+struct drm_fb_helper;
+
+dma_addr_t
+xlnx_fb_get_paddr(struct drm_framebuffer *base_fb, unsigned int plane);
+
+void xlnx_fb_restore_mode(struct drm_fb_helper *fb_helper);
+struct drm_framebuffer *
+xlnx_fb_create(struct drm_device *drm, struct drm_file *file_priv,
+	       const struct drm_mode_fb_cmd2 *mode_cmd);
+void xlnx_fb_hotplug_event(struct drm_fb_helper *fb_helper);
+struct drm_fb_helper *
+xlnx_fb_init(struct drm_device *drm, int preferred_bpp,
+	     unsigned int max_conn_count, unsigned int align,
+	     unsigned int vres_mult);
+void xlnx_fb_fini(struct drm_fb_helper *fb_helper);
+
+#endif /* _XLNX_FB_H_ */
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 04/10] drm: xlnx: Add xlnx gem of Xilinx DRM KMS
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
                     ` (2 preceding siblings ...)
  2018-01-05  2:05   ` [PATCH 03/10] drm: xlnx: Add xlnx fb " Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
  2018-01-05  2:05   ` [PATCH 05/10] drm: xlnx: Xilinx DRM KMS driver Hyun Kwon
                     ` (6 subsequent siblings)
  10 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

Implement a simple wrapper around drm_gem_cma_dumb_create_internal()
for alignment.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 drivers/gpu/drm/xlnx/xlnx_gem.c | 38 ++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/xlnx_gem.h | 18 ++++++++++++++++++
 2 files changed, 56 insertions(+)
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.c
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.h

diff --git a/drivers/gpu/drm/xlnx/xlnx_gem.c b/drivers/gpu/drm/xlnx/xlnx_gem.c
new file mode 100644
index 0000000..af1abdc
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_gem.c
@@ -0,0 +1,38 @@
+/*
+ * Xilinx DRM KMS GEM helper
+ *
+ *  Copyright (C) 2015 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "xlnx_gem.h"
+
+/*
+ * xlnx_gem_cma_dumb_create - (struct drm_driver)->dumb_create callback
+ * @file_priv: drm_file object
+ * @drm: DRM object
+ * @args: info for dumb scanout buffer creation
+ *
+ * This function is for dumb_create callback of drm_driver struct. Simply
+ * it wraps around drm_gem_cma_dumb_create() and sets the pitch value
+ * by retrieving the value from the device.
+ *
+ * Return: The return value from drm_gem_cma_dumb_create()
+ */
+int xlnx_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *drm,
+			     struct drm_mode_create_dumb *args)
+{
+	int pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+	unsigned int align = xlnx_get_align(drm);
+
+	if (!args->pitch || !IS_ALIGNED(args->pitch, align))
+		args->pitch = ALIGN(pitch, align);
+
+	return drm_gem_cma_dumb_create_internal(file_priv, drm, args);
+}
diff --git a/drivers/gpu/drm/xlnx/xlnx_gem.h b/drivers/gpu/drm/xlnx/xlnx_gem.h
new file mode 100644
index 0000000..bed8ff9
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_gem.h
@@ -0,0 +1,18 @@
+/*
+ * Xilinx DRM KMS GEM helper header
+ *
+ *  Copyright (C) 2015 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _XLNX_GEM_H_
+#define _XLNX_GEM_H_
+
+int xlnx_gem_cma_dumb_create(struct drm_file *file_priv,
+			     struct drm_device *drm,
+			     struct drm_mode_create_dumb *args);
+
+#endif /* _XLNX_GEM_H_ */
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 05/10] drm: xlnx: Xilinx DRM KMS driver
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
                     ` (3 preceding siblings ...)
  2018-01-05  2:05   ` [PATCH 04/10] drm: xlnx: Add xlnx gem " Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
       [not found]     ` <1515117959-18068-6-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
  2018-01-05  2:05   ` [PATCH 06/10] dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings Hyun Kwon
                     ` (5 subsequent siblings)
  10 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

Xilinx has various platforms for display, where users can create
using multiple IPs in the programmable FPGA fabric, or where
some hardened piepline is available on the chip. Furthermore,
hardened pipeline can also interact with soft logics in FPGA.

The Xilinx DRM KMS is a softwrae layer to glue subdevice drivers
with DRM core and integrate multiple subdevice drivers together.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 MAINTAINERS                      |   8 +
 drivers/gpu/drm/Kconfig          |   2 +
 drivers/gpu/drm/Makefile         |   1 +
 drivers/gpu/drm/xlnx/Kconfig     |  12 ++
 drivers/gpu/drm/xlnx/Makefile    |   2 +
 drivers/gpu/drm/xlnx/xlnx_crtc.c |   1 +
 drivers/gpu/drm/xlnx/xlnx_drv.c  | 436 +++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/xlnx_drv.h  |  22 ++
 drivers/gpu/drm/xlnx/xlnx_fb.c   |   1 +
 drivers/gpu/drm/xlnx/xlnx_gem.c  |   1 +
 10 files changed, 486 insertions(+)
 create mode 100644 drivers/gpu/drm/xlnx/Kconfig
 create mode 100644 drivers/gpu/drm/xlnx/Makefile
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.c
 create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.h

diff --git a/MAINTAINERS b/MAINTAINERS
index d4b1635..101a3e6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4785,6 +4785,14 @@ F:	drivers/gpu/drm/etnaviv/
 F:	include/uapi/drm/etnaviv_drm.h
 F:	Documentation/devicetree/bindings/display/etnaviv/
 
+DRM DRIVERS FOR XILINX
+M:	Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+L:	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
+S:	Maintained
+F:	drivers/gpu/drm/xlnx/
+F:	Documentation/devicetree/bindings/display/xlnx/
+T:	git git://anongit.freedesktop.org/drm/drm-misc
+
 DRM DRIVERS FOR ZTE ZX
 M:	Shawn Guo <shawnguo-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
 L:	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 0bc3744..82b7fc3 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -293,6 +293,8 @@ source "drivers/gpu/drm/pl111/Kconfig"
 
 source "drivers/gpu/drm/tve200/Kconfig"
 
+source "drivers/gpu/drm/xlnx/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index dd5ae67..72ee1a1 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
 obj-$(CONFIG_DRM_PL111) += pl111/
 obj-$(CONFIG_DRM_TVE200) += tve200/
 obj-$(CONFIG_DRM_SCHED)	+= scheduler/
+obj-$(CONFIG_DRM_XLNX)	+= xlnx/
diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
new file mode 100644
index 0000000..19fd7cd
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/Kconfig
@@ -0,0 +1,12 @@
+config DRM_XLNX
+	tristate "Xilinx DRM KMS Driver"
+	depends on DRM && OF
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	help
+	  Xilinx DRM KMS driver. Choose this option if you have
+	  a Xilinx SoCs with hardened display pipeline or soft
+	  display pipeline using Xilinx IPs in FPGA. This module
+	  provides the kernel mode setting functionalities
+	  for Xilinx display drivers.
diff --git a/drivers/gpu/drm/xlnx/Makefile b/drivers/gpu/drm/xlnx/Makefile
new file mode 100644
index 0000000..c60a281
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/Makefile
@@ -0,0 +1,2 @@
+xlnx_drm-objs += xlnx_crtc.o xlnx_drv.o xlnx_fb.o xlnx_gem.o
+obj-$(CONFIG_DRM_XLNX) += xlnx_drm.o
diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.c b/drivers/gpu/drm/xlnx/xlnx_crtc.c
index 57ee939..8387e1e 100644
--- a/drivers/gpu/drm/xlnx/xlnx_crtc.c
+++ b/drivers/gpu/drm/xlnx/xlnx_crtc.c
@@ -13,6 +13,7 @@
 #include <linux/list.h>
 
 #include "xlnx_crtc.h"
+#include "xlnx_drv.h"
 
 /*
  * Overview
diff --git a/drivers/gpu/drm/xlnx/xlnx_drv.c b/drivers/gpu/drm/xlnx/xlnx_drv.c
new file mode 100644
index 0000000..273420b
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_drv.c
@@ -0,0 +1,436 @@
+/*
+ * Xilinx DRM KMS Driver
+ *
+ *  Copyright (C) 2013 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_of.h>
+
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/reservation.h>
+
+#include "xlnx_crtc.h"
+#include "xlnx_fb.h"
+#include "xlnx_gem.h"
+
+#define DRIVER_NAME	"xlnx"
+#define DRIVER_DESC	"Xilinx DRM KMS Driver"
+#define DRIVER_DATE	"20130509"
+#define DRIVER_MAJOR	1
+#define DRIVER_MINOR	0
+
+static uint xlnx_fbdev_vres = 2;
+module_param_named(fbdev_vres, xlnx_fbdev_vres, uint, 0444);
+MODULE_PARM_DESC(fbdev_vres,
+		 "fbdev virtual resolution multiplier for fb (default: 2)");
+
+/**
+ * struct xlnx_drm - Xilinx DRM private data
+ * @drm: DRM core
+ * @crtc: Xilinx DRM CRTC helper
+ * @fb: DRM fb helper
+ * @pdev: platform device
+ * @suspend_state: atomic state for suspend / resume
+ */
+struct xlnx_drm {
+	struct drm_device *drm;
+	struct xlnx_crtc_helper *crtc;
+	struct drm_fb_helper *fb;
+	struct platform_device *pdev;
+	struct drm_atomic_state *suspend_state;
+};
+
+/**
+ * xlnx_get_crtc_helper - Return the crtc helper instance
+ * @drm: DRM device
+ *
+ * Return: the crtc helper instance
+ */
+struct xlnx_crtc_helper *xlnx_get_crtc_helper(struct drm_device *drm)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+
+	return xlnx_drm->crtc;
+}
+
+/**
+ * xlnx_get_align - Return the align requirement through CRTC helper
+ * @drm: DRM device
+ *
+ * Return: the alignment requirement
+ */
+unsigned int xlnx_get_align(struct drm_device *drm)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+
+	return xlnx_crtc_helper_get_align(xlnx_drm->crtc);
+}
+
+/**
+ * xlnx_get_format - Return the current format of CRTC
+ * @drm: DRM device
+ *
+ * Return: the current CRTC format
+ */
+uint32_t xlnx_get_format(struct drm_device *drm)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+
+	return xlnx_crtc_helper_get_format(xlnx_drm->crtc);
+}
+
+static void xlnx_output_poll_changed(struct drm_device *drm)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+
+	xlnx_fb_hotplug_event(xlnx_drm->fb);
+}
+
+static const struct drm_mode_config_funcs xlnx_mode_config_funcs = {
+	.fb_create		= xlnx_fb_create,
+	.output_poll_changed	= xlnx_output_poll_changed,
+	.atomic_check		= drm_atomic_helper_check,
+	.atomic_commit		= drm_atomic_helper_commit,
+};
+
+static struct drm_mode_config_helper_funcs xlnx_mode_config_helper_funcs = {
+	.atomic_commit_tail = drm_atomic_helper_commit_tail,
+};
+
+static int xlnx_enable_vblank(struct drm_device *drm, unsigned int crtc)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+
+	return xlnx_crtc_helper_enable_vblank(xlnx_drm->crtc, crtc);
+}
+
+static void xlnx_disable_vblank(struct drm_device *drm, unsigned int crtc)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+
+	xlnx_crtc_helper_disable_vblank(xlnx_drm->crtc, crtc);
+}
+
+static void xlnx_mode_config_init(struct drm_device *drm)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+	struct xlnx_crtc_helper *crtc = xlnx_drm->crtc;
+
+	drm->mode_config.min_width = 0;
+	drm->mode_config.min_height = 0;
+	drm->mode_config.max_width = xlnx_crtc_helper_get_max_width(crtc);
+	drm->mode_config.max_height = xlnx_crtc_helper_get_max_height(crtc);
+}
+
+static void xlnx_lastclose(struct drm_device *drm)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+
+	xlnx_fb_restore_mode(xlnx_drm->fb);
+}
+
+static const struct file_operations xlnx_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.release	= drm_release,
+	.unlocked_ioctl	= drm_ioctl,
+	.mmap		= drm_gem_cma_mmap,
+	.poll		= drm_poll,
+	.read		= drm_read,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= drm_compat_ioctl,
+#endif
+	.llseek		= noop_llseek,
+};
+
+static struct drm_driver xlnx_drm_driver = {
+	.driver_features		= DRIVER_MODESET | DRIVER_GEM |
+					  DRIVER_ATOMIC | DRIVER_PRIME,
+	.lastclose			= xlnx_lastclose,
+
+	.enable_vblank			= xlnx_enable_vblank,
+	.disable_vblank			= xlnx_disable_vblank,
+
+	.prime_handle_to_fd		= drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle		= drm_gem_prime_fd_to_handle,
+	.gem_prime_export		= drm_gem_prime_export,
+	.gem_prime_import		= drm_gem_prime_import,
+	.gem_prime_get_sg_table		= drm_gem_cma_prime_get_sg_table,
+	.gem_prime_import_sg_table	= drm_gem_cma_prime_import_sg_table,
+	.gem_prime_vmap			= drm_gem_cma_prime_vmap,
+	.gem_prime_vunmap		= drm_gem_cma_prime_vunmap,
+	.gem_prime_mmap			= drm_gem_cma_prime_mmap,
+	.gem_free_object		= drm_gem_cma_free_object,
+	.gem_vm_ops			= &drm_gem_cma_vm_ops,
+	.dumb_create			= xlnx_gem_cma_dumb_create,
+	.dumb_destroy			= drm_gem_dumb_destroy,
+
+	.fops				= &xlnx_fops,
+
+	.name				= DRIVER_NAME,
+	.desc				= DRIVER_DESC,
+	.date				= DRIVER_DATE,
+	.major				= DRIVER_MAJOR,
+	.minor				= DRIVER_MINOR,
+};
+
+static int xlnx_bind(struct device *dev)
+{
+	struct xlnx_drm *xlnx_drm;
+	struct drm_device *drm;
+	const struct drm_format_info *info;
+	struct platform_device *pdev = to_platform_device(dev);
+	int ret;
+	u32 format;
+
+	drm = drm_dev_alloc(&xlnx_drm_driver, &pdev->dev);
+	if (IS_ERR(drm))
+		return PTR_ERR(drm);
+
+	xlnx_drm = devm_kzalloc(drm->dev, sizeof(*xlnx_drm), GFP_KERNEL);
+	if (!xlnx_drm) {
+		ret = -ENOMEM;
+		goto err_drm;
+	}
+
+	drm_mode_config_init(drm);
+	drm->mode_config.funcs = &xlnx_mode_config_funcs;
+	drm->mode_config.helper_private = &xlnx_mode_config_helper_funcs;
+
+	ret = drm_vblank_init(drm, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to initialize vblank\n");
+		goto err_xlnx_drm;
+	}
+
+	drm->irq_enabled = 1;
+	drm->dev_private = xlnx_drm;
+	xlnx_drm->drm = drm;
+	drm_kms_helper_poll_init(drm);
+	platform_set_drvdata(pdev, xlnx_drm);
+
+	xlnx_drm->crtc = xlnx_crtc_helper_init(drm);
+	if (IS_ERR(xlnx_drm->crtc)) {
+		ret = PTR_ERR(xlnx_drm->crtc);
+		goto err_xlnx_drm;
+	}
+
+	ret = component_bind_all(drm->dev, drm);
+	if (ret)
+		goto err_crtc;
+
+	xlnx_mode_config_init(drm);
+	drm_mode_config_reset(drm);
+	dma_set_mask(drm->dev, xlnx_crtc_helper_get_dma_mask(xlnx_drm->crtc));
+
+	format = xlnx_crtc_helper_get_format(xlnx_drm->crtc);
+	info = drm_format_info(format);
+	if (info && info->depth && info->cpp[0]) {
+		unsigned int align;
+
+		align = xlnx_crtc_helper_get_align(xlnx_drm->crtc);
+		xlnx_drm->fb = xlnx_fb_init(drm, info->cpp[0] * 8, 1, align,
+					    xlnx_fbdev_vres);
+		if (IS_ERR(xlnx_drm->fb))
+			dev_err(&pdev->dev,
+				"failed to initialize drm fb\n");
+	} else {
+		/* fbdev emulation is optional */
+		dev_info(&pdev->dev, "fbdev is not initialized\n");
+	}
+
+	ret = drm_dev_register(drm, 0);
+	if (ret < 0)
+		goto err_fb;
+
+	return 0;
+
+err_fb:
+	if (xlnx_drm->fb)
+		xlnx_fb_fini(xlnx_drm->fb);
+	component_unbind_all(drm->dev, drm);
+err_crtc:
+	xlnx_crtc_helper_fini(drm, xlnx_drm->crtc);
+err_xlnx_drm:
+	drm_mode_config_cleanup(drm);
+err_drm:
+	drm_dev_unref(drm);
+	return ret;
+}
+
+static void xlnx_unbind(struct device *dev)
+{
+	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
+	struct drm_device *drm = xlnx_drm->drm;
+
+	drm_dev_unregister(drm);
+	if (xlnx_drm->fb)
+		xlnx_fb_fini(xlnx_drm->fb);
+	component_unbind_all(drm->dev, drm);
+	xlnx_crtc_helper_fini(drm, xlnx_drm->crtc);
+	drm_kms_helper_poll_fini(drm);
+	drm_mode_config_cleanup(drm);
+	drm_dev_unref(drm);
+}
+
+static const struct component_master_ops xlnx_master_ops = {
+	.bind	= xlnx_bind,
+	.unbind	= xlnx_unbind,
+};
+
+static int xlnx_of_component_probe(struct device *dev,
+				   int (*compare_of)(struct device *, void *),
+				   const struct component_master_ops *m_ops)
+{
+	struct device_node *ep, *port, *remote;
+	struct component_match *match = NULL;
+	int i;
+
+	if (!dev->of_node)
+		return -EINVAL;
+
+	for (i = 0; ; i++) {
+		port = of_parse_phandle(dev->of_node, "ports", i);
+		if (!port)
+			break;
+
+		if (!of_device_is_available(port->parent)) {
+			of_node_put(port);
+			continue;
+		}
+
+		component_match_add(dev, &match, compare_of, port->parent);
+		of_node_put(port);
+	}
+
+	if (i == 0) {
+		dev_err(dev, "missing 'ports' property\n");
+		return -ENODEV;
+	}
+
+	if (!match) {
+		dev_err(dev, "no available port\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; ; i++) {
+		port = of_parse_phandle(dev->of_node, "ports", i);
+		if (!port)
+			break;
+
+		if (!of_device_is_available(port->parent)) {
+			of_node_put(port);
+			continue;
+		}
+
+		for_each_child_of_node(port, ep) {
+			remote = of_graph_get_remote_port_parent(ep);
+			if (!remote || !of_device_is_available(remote)) {
+				of_node_put(remote);
+				continue;
+			} else if (!of_device_is_available(remote->parent)) {
+				dev_warn(dev, "parent dev of %s unavailable\n",
+					 remote->full_name);
+				of_node_put(remote);
+				continue;
+			}
+			component_match_add(dev, &match, compare_of, remote);
+			of_node_put(remote);
+		}
+		of_node_put(port);
+	}
+
+	return component_master_add_with_match(dev, m_ops, match);
+}
+
+static int xlnx_compare_of(struct device *dev, void *data)
+{
+	return dev->of_node == data;
+}
+
+static int xlnx_platform_probe(struct platform_device *pdev)
+{
+	return xlnx_of_component_probe(&pdev->dev, xlnx_compare_of,
+				       &xlnx_master_ops);
+}
+
+static int xlnx_platform_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &xlnx_master_ops);
+	return 0;
+}
+
+static void xlnx_platform_shutdown(struct platform_device *pdev)
+{
+	struct xlnx_drm *xlnx_drm = platform_get_drvdata(pdev);
+
+	drm_put_dev(xlnx_drm->drm);
+}
+
+static int __maybe_unused xlnx_pm_suspend(struct device *dev)
+{
+	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
+	struct drm_device *drm = xlnx_drm->drm;
+
+	drm_kms_helper_poll_disable(drm);
+
+	xlnx_drm->suspend_state = drm_atomic_helper_suspend(drm);
+	if (IS_ERR(xlnx_drm->suspend_state)) {
+		drm_kms_helper_poll_enable(drm);
+		return PTR_ERR(xlnx_drm->suspend_state);
+	}
+
+	return 0;
+}
+
+static int __maybe_unused xlnx_pm_resume(struct device *dev)
+{
+	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
+	struct drm_device *drm = xlnx_drm->drm;
+
+	drm_atomic_helper_resume(drm, xlnx_drm->suspend_state);
+	drm_kms_helper_poll_enable(drm);
+
+	return 0;
+}
+
+static const struct dev_pm_ops xlnx_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(xlnx_pm_suspend, xlnx_pm_resume)
+};
+
+static const struct of_device_id xlnx_of_match[] = {
+	{ .compatible = "xlnx,kms", },
+	{ /* end of table */ },
+};
+MODULE_DEVICE_TABLE(of, xlnx_of_match);
+
+static struct platform_driver xlnx_driver = {
+	.probe			= xlnx_platform_probe,
+	.remove			= xlnx_platform_remove,
+	.shutdown		= xlnx_platform_shutdown,
+	.driver			= {
+		.name		= "xlnx-drm",
+		.pm		= &xlnx_pm_ops,
+		.of_match_table	= xlnx_of_match,
+	},
+};
+
+module_platform_driver(xlnx_driver);
+
+MODULE_AUTHOR("Xilinx, Inc.");
+MODULE_DESCRIPTION("Xilinx DRM KMS Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/xlnx/xlnx_drv.h b/drivers/gpu/drm/xlnx/xlnx_drv.h
new file mode 100644
index 0000000..6ec285c
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_drv.h
@@ -0,0 +1,22 @@
+/*
+ * Xilinx DRM KMS Header for Xilinx
+ *
+ *  Copyright (C) 2013 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _XLNX_DRV_H_
+#define _XLNX_DRV_H_
+
+struct drm_device;
+struct xlnx_crtc_helper;
+
+uint32_t xlnx_get_format(struct drm_device *drm);
+unsigned int xlnx_get_align(struct drm_device *drm);
+struct xlnx_crtc_helper *xlnx_get_crtc_helper(struct drm_device *drm);
+struct xlnx_bridge_helper *xlnx_get_bridge_helper(struct drm_device *drm);
+
+#endif /* _XLNX_DRV_H_ */
diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.c b/drivers/gpu/drm/xlnx/xlnx_fb.c
index dbe9fbf..029a4d3 100644
--- a/drivers/gpu/drm/xlnx/xlnx_fb.c
+++ b/drivers/gpu/drm/xlnx/xlnx_fb.c
@@ -18,6 +18,7 @@
 #include <drm/drm_fb_helper.h>
 #include <drm/drm_gem_cma_helper.h>
 
+#include "xlnx_drv.h"
 #include "xlnx_fb.h"
 
 #define XLNX_MAX_PLANES	4
diff --git a/drivers/gpu/drm/xlnx/xlnx_gem.c b/drivers/gpu/drm/xlnx/xlnx_gem.c
index af1abdc..6395f65 100644
--- a/drivers/gpu/drm/xlnx/xlnx_gem.c
+++ b/drivers/gpu/drm/xlnx/xlnx_gem.c
@@ -11,6 +11,7 @@
 #include <drm/drmP.h>
 #include <drm/drm_gem_cma_helper.h>
 
+#include "xlnx_drv.h"
 #include "xlnx_gem.h"
 
 /*
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 06/10] dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
                     ` (4 preceding siblings ...)
  2018-01-05  2:05   ` [PATCH 05/10] drm: xlnx: Xilinx DRM KMS driver Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
  2018-01-09  4:07     ` Rob Herring
  2018-01-05  2:05   ` [PATCH 07/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display Hyun Kwon
                     ` (4 subsequent siblings)
  10 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

This add a dt binding for ZynqMP DP subsystem.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 .../bindings/display/xlnx/xlnx,zynqmp-dpsub.txt    | 94 ++++++++++++++++++++++
 1 file changed, 94 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt

diff --git a/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt b/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
new file mode 100644
index 0000000..4e478ca
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
@@ -0,0 +1,94 @@
+Xilinx ZynqMP DisplayPort subsystem
+-----------------------------------
+
+Required properties:
+
+- compatible: Must be "xlnx,zynqmp-dpsub-1.7".
+
+- reg: Physical base address and length of the registers set for the device.
+- reg-names: Must be "dp", "blend", "av_buf", and "aud" to map logical register
+  partitions.
+
+- interrupts: Interrupt number.
+- interrupts-parent: phandle for interrupt controller.
+
+- clocks: phandles for axi, audio, non-live video, and live video clocks.
+  axi clock is required. Audio clock is optional. If not present, audio will
+  be disabled. One of non-live or live video clock should be present.
+- clock-names: The identification strings are required. "aclk" for axi clock.
+  "dp_aud_clk" for audio clock. "dp_vtc_pixel_clk_in" for non-live video clock.
+  "dp_live_video_in_clk" for live video clock (clock from programmable logic).
+
+- phys: phandles for phy specifier.
+- phy-names: The identifier strings. "dp-phy" followed by index.
+
+- power-domains: phandle for the corresponding power domain
+
+- ports: crtc and encoder ports are required using DT bindings defined in
+  Documentation/devicetree/bindings/graph.txt.
+
+- vid-layer, gfx-layer: Required to represent available layers
+
+Required layer properties
+
+- dmas: phandles for DMA channels as defined in
+  Documentation/devicetree/bindings/dma/dma.txt.
+- dma-names: The identifier strings are required. "graphics0" for graphics
+  layer. "video" followed by index for video layer
+
+Optional child node
+
+- The driver populates any child device node in this node. This can be used,
+  for example, to populate the sound device from the DisplayPort subsystem
+  driver.
+
+Example:
+	zynqmp_dpsub: zynqmp_dpsub@fd4a0000 {
+		compatible = "xlnx,zynqmp-dpsub-1.7";
+		reg = <0x0 0xfd4a0000 0x0 0x1000>,
+		      <0x0 0xfd4aa000 0x0 0x1000>,
+		      <0x0 0xfd4ab000 0x0 0x1000>,
+		      <0x0 0xfd4ac000 0x0 0x1000>;
+		reg-names = "dp", "blend", "av_buf", "aud";
+		interrupts = <0 119 4>;
+		interrupt-parent = <&gic>;
+
+		clock-names = "dp_apb_clk", "dp_aud_clk", "dp_live_video_in_clk";
+		clocks = <&dp_aclk>, <&clkc 17>, <&si570_1>;
+
+		phys = <&lane1 PHY_TYPE_DP 0 1 27000000>,
+		       <&lane0 PHY_TYPE_DP 1 1 27000000>;
+		phy-names = "dp-phy0", "dp-phy1";
+
+		power-domains = <&pd_dp>;
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		vid-layer {
+			dma-names = "vid0", "vid1", "vid2";
+			dmas = <&xlnx_dpdma 0>,
+			       <&xlnx_dpdma 1>,
+			       <&xlnx_dpdma 2>;
+		};
+
+		gfx-layer {
+			dma-names = "gfx0";
+			dmas = <&xlnx_dpdma 3>;
+		};
+
+		crtc_port: port@0 {
+			reg = <0>;
+			crtc: endpoint {
+				remote-endpoint = <&encoder>;
+			};
+		};
+		port@1 {
+			reg = <1>;
+			encoder: endpoint {
+				remote-endpoint = <&crtc>;
+			};
+		};
+	};
+};
+
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 07/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
                     ` (5 preceding siblings ...)
  2018-01-05  2:05   ` [PATCH 06/10] dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
       [not found]     ` <1515117959-18068-8-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
  2018-01-05  2:05   ` [PATCH 08/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DisplayPort Hyun Kwon
                     ` (3 subsequent siblings)
  10 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

Xilinx ZynqMP has a hardened display pipeline. The pipeline can
be logically partitioned into 2 parts: display and DisplayPort.
This driver handles the display part of the pipeline that handles
buffer management and blending.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 drivers/gpu/drm/xlnx/zynqmp_disp.c | 2935 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/zynqmp_disp.h |   28 +
 2 files changed, 2963 insertions(+)
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.c
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.h

diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c
new file mode 100644
index 0000000..68f829c
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
@@ -0,0 +1,2935 @@
+/*
+ * ZynqMP Display Controller Driver
+ *
+ *  Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_plane_helper.h>
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+
+#include "xlnx_crtc.h"
+#include "xlnx_fb.h"
+#include "zynqmp_disp.h"
+#include "zynqmp_dp.h"
+#include "zynqmp_dpsub.h"
+
+/*
+ * Overview
+ * --------
+ *
+ * The display part of ZynqMP DP subsystem. Internally, the device
+ * is partitioned into 3 blocks: AV buffer manager, Blender, Audio.
+ * The driver creates the DRM crtc and plane objectes and maps the DRM
+ * interface into those 3 blocks. In high level, the driver is layered
+ * in the following way:
+ *
+ * zynqmp_disp_crtc & zynqmp_disp_plane
+ * |->zynqmp_disp
+ *	|->zynqmp_disp_aud
+ *	|->zynqmp_disp_blend
+ *	|->zynqmp_disp_av_buf
+ *
+ * The driver APIs are used externally by
+ * - zynqmp_dpsub: Top level ZynqMP DP subsystem driver
+ * - zynqmp_dp: ZynqMP DP driver
+ * - xlnx_crtc: Xilinx DRM specific crtc functions
+ */
+
+/* Blender registers */
+#define ZYNQMP_DISP_V_BLEND_BG_CLR_0			0x0
+#define ZYNQMP_DISP_V_BLEND_BG_CLR_1			0x4
+#define ZYNQMP_DISP_V_BLEND_BG_CLR_2			0x8
+#define ZYNQMP_DISP_V_BLEND_BG_MAX			0xfff
+#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA		0xc
+#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MASK	0x1fe
+#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX	0xff
+#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT		0x14
+#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB		0x0
+#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444	0x1
+#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422	0x2
+#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY	0x3
+#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_XVYCC	0x4
+#define ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE	BIT(4)
+#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL		0x18
+#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_EN_US		BIT(0)
+#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_RGB		BIT(1)
+#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_BYPASS	BIT(8)
+#define ZYNQMP_DISP_V_BLEND_NUM_COEFF			9
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF0		0x20
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF1		0x24
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF2		0x28
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF3		0x2c
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF4		0x30
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF5		0x34
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF6		0x38
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF7		0x3c
+#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF8		0x40
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0		0x44
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF1		0x48
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF2		0x4c
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF3		0x50
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF4		0x54
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF5		0x58
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF6		0x5c
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF7		0x60
+#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF8		0x64
+#define ZYNQMP_DISP_V_BLEND_NUM_OFFSET			3
+#define ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET		0x68
+#define ZYNQMP_DISP_V_BLEND_CR_IN1CSC_OFFSET		0x6c
+#define ZYNQMP_DISP_V_BLEND_CB_IN1CSC_OFFSET		0x70
+#define ZYNQMP_DISP_V_BLEND_LUMA_OUTCSC_OFFSET		0x74
+#define ZYNQMP_DISP_V_BLEND_CR_OUTCSC_OFFSET		0x78
+#define ZYNQMP_DISP_V_BLEND_CB_OUTCSC_OFFSET		0x7c
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0		0x80
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF1		0x84
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF2		0x88
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF3		0x8c
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF4		0x90
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF5		0x94
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF6		0x98
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF7		0x9c
+#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF8		0xa0
+#define ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET		0xa4
+#define ZYNQMP_DISP_V_BLEND_CR_IN2CSC_OFFSET		0xa8
+#define ZYNQMP_DISP_V_BLEND_CB_IN2CSC_OFFSET		0xac
+#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_ENABLE		0x1d0
+#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP1		0x1d4
+#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP2		0x1d8
+#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP3		0x1dc
+
+/* AV buffer manager registers */
+#define ZYNQMP_DISP_AV_BUF_FMT				0x0
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_SHIFT		0
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MASK		(0x1f << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_UYVY		(0 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY		(1 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YVYU		(2 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV		(3 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16		(4 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24		(5 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI		(6 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MONO		(7 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2		(8 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUV444		(9 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888		(10 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880		(11 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10		(12 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUV444_10		(13 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_10	(14 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_10		(15 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_10		(16 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24_10		(17 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YONLY_10		(18 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420		(19 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420	(20 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_420	(21 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420_10	(22 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420_10	(23 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_420_10	(24 << 0)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_SHIFT		8
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_MASK		(0xf << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888		(0 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888		(1 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB888		(2 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_BGR888		(3 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551		(4 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444		(5 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565		(6 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_8BPP		(7 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_4BPP		(8 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_2BPP		(9 << 8)
+#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_1BPP		(10 << 8)
+#define ZYNQMP_DISP_AV_BUF_NON_LIVE_LATENCY		0x8
+#define ZYNQMP_DISP_AV_BUF_CHBUF			0x10
+#define ZYNQMP_DISP_AV_BUF_CHBUF_EN			BIT(0)
+#define ZYNQMP_DISP_AV_BUF_CHBUF_FLUSH			BIT(1)
+#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT	2
+#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MASK		(0xf << 2)
+#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MAX		0xf
+#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_AUD_MAX	0x3
+#define ZYNQMP_DISP_AV_BUF_STATUS			0x28
+#define ZYNQMP_DISP_AV_BUF_STC_CTRL			0x2c
+#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EN			BIT(0)
+#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_SHIFT		1
+#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_VSYNC	0
+#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_VID	1
+#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_AUD	2
+#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_INT_VSYNC	3
+#define ZYNQMP_DISP_AV_BUF_STC_INIT_VALUE0		0x30
+#define ZYNQMP_DISP_AV_BUF_STC_INIT_VALUE1		0x34
+#define ZYNQMP_DISP_AV_BUF_STC_ADJ			0x38
+#define ZYNQMP_DISP_AV_BUF_STC_VID_VSYNC_TS0		0x3c
+#define ZYNQMP_DISP_AV_BUF_STC_VID_VSYNC_TS1		0x40
+#define ZYNQMP_DISP_AV_BUF_STC_EXT_VSYNC_TS0		0x44
+#define ZYNQMP_DISP_AV_BUF_STC_EXT_VSYNC_TS1		0x48
+#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT_TS0		0x4c
+#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT_TS1		0x50
+#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT2_TS0	0x54
+#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT2_TS1	0x58
+#define ZYNQMP_DISP_AV_BUF_STC_SNAPSHOT0		0x60
+#define ZYNQMP_DISP_AV_BUF_STC_SNAPSHOT1		0x64
+#define ZYNQMP_DISP_AV_BUF_OUTPUT			0x70
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_SHIFT		0
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK		(0x3 << 0)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE		(0 << 0)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM		(1 << 0)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN		(2 << 0)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_NONE		(3 << 0)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_SHIFT		2
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK		(0x3 << 2)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_DISABLE		(0 << 2)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM		(1 << 2)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE		(2 << 2)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_NONE		(3 << 2)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_SHIFT		4
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK		(0x3 << 4)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_PL		(0 << 4)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MEM		(1 << 4)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_PATTERN		(2 << 4)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_DISABLE		(3 << 4)
+#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN		BIT(6)
+#define ZYNQMP_DISP_AV_BUF_HCOUNT_VCOUNT_INT0		0x74
+#define ZYNQMP_DISP_AV_BUF_HCOUNT_VCOUNT_INT1		0x78
+#define ZYNQMP_DISP_AV_BUF_PATTERN_GEN_SELECT		0x100
+#define ZYNQMP_DISP_AV_BUF_CLK_SRC			0x120
+#define ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS		BIT(0)
+#define ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS		BIT(1)
+#define ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING	BIT(2)
+#define ZYNQMP_DISP_AV_BUF_SRST_REG			0x124
+#define ZYNQMP_DISP_AV_BUF_SRST_REG_VID_RST		BIT(1)
+#define ZYNQMP_DISP_AV_BUF_AUDIO_CH_CONFIG		0x12c
+#define ZYNQMP_DISP_AV_BUF_GFX_COMP0_SF			0x200
+#define ZYNQMP_DISP_AV_BUF_GFX_COMP1_SF			0x204
+#define ZYNQMP_DISP_AV_BUF_GFX_COMP2_SF			0x208
+#define ZYNQMP_DISP_AV_BUF_VID_COMP0_SF			0x20c
+#define ZYNQMP_DISP_AV_BUF_VID_COMP1_SF			0x210
+#define ZYNQMP_DISP_AV_BUF_VID_COMP2_SF			0x214
+#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP0_SF		0x218
+#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP1_SF		0x21c
+#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP2_SF		0x220
+#define ZYNQMP_DISP_AV_BUF_LIVE_VID_CONFIG		0x224
+#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP0_SF		0x228
+#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP1_SF		0x22c
+#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP2_SF		0x230
+#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_CONFIG		0x234
+#define ZYNQMP_DISP_AV_BUF_4BIT_SF			0x11111
+#define ZYNQMP_DISP_AV_BUF_5BIT_SF			0x10842
+#define ZYNQMP_DISP_AV_BUF_6BIT_SF			0x10410
+#define ZYNQMP_DISP_AV_BUF_8BIT_SF			0x10101
+#define ZYNQMP_DISP_AV_BUF_10BIT_SF			0x10040
+#define ZYNQMP_DISP_AV_BUF_NULL_SF			0
+#define ZYNQMP_DISP_AV_BUF_NUM_SF			3
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_6		0x0
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8		0x1
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_10		0x2
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_12		0x3
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_MASK		GENMASK(2, 0)
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB		0x0
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV444	0x1
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422	0x2
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YONLY	0x3
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_MASK		GENMASK(5, 4)
+#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_CB_FIRST		BIT(8)
+#define ZYNQMP_DISP_AV_BUF_PALETTE_MEMORY		0x400
+
+/* Audio registers */
+#define ZYNQMP_DISP_AUD_MIXER_VOLUME			0x0
+#define ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE		0x20002000
+#define ZYNQMP_DISP_AUD_MIXER_META_DATA			0x4
+#define ZYNQMP_DISP_AUD_CH_STATUS0			0x8
+#define ZYNQMP_DISP_AUD_CH_STATUS1			0xc
+#define ZYNQMP_DISP_AUD_CH_STATUS2			0x10
+#define ZYNQMP_DISP_AUD_CH_STATUS3			0x14
+#define ZYNQMP_DISP_AUD_CH_STATUS4			0x18
+#define ZYNQMP_DISP_AUD_CH_STATUS5			0x1c
+#define ZYNQMP_DISP_AUD_CH_A_DATA0			0x20
+#define ZYNQMP_DISP_AUD_CH_A_DATA1			0x24
+#define ZYNQMP_DISP_AUD_CH_A_DATA2			0x28
+#define ZYNQMP_DISP_AUD_CH_A_DATA3			0x2c
+#define ZYNQMP_DISP_AUD_CH_A_DATA4			0x30
+#define ZYNQMP_DISP_AUD_CH_A_DATA5			0x34
+#define ZYNQMP_DISP_AUD_CH_B_DATA0			0x38
+#define ZYNQMP_DISP_AUD_CH_B_DATA1			0x3c
+#define ZYNQMP_DISP_AUD_CH_B_DATA2			0x40
+#define ZYNQMP_DISP_AUD_CH_B_DATA3			0x44
+#define ZYNQMP_DISP_AUD_CH_B_DATA4			0x48
+#define ZYNQMP_DISP_AUD_CH_B_DATA5			0x4c
+#define ZYNQMP_DISP_AUD_SOFT_RESET			0xc00
+#define ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST		BIT(0)
+
+#define ZYNQMP_DISP_AV_BUF_NUM_VID_GFX_BUFFERS		4
+#define ZYNQMP_DISP_AV_BUF_NUM_BUFFERS			6
+
+#define ZYNQMP_DISP_NUM_LAYERS				2
+#define ZYNQMP_DISP_MAX_NUM_SUB_PLANES			3
+/*
+ * 3840x2160 is advertised max resolution, but almost any resolutions under
+ * 300Mhz pixel rate would work. Thus put 4096 as maximum width and height.
+ */
+#define ZYNQMP_DISP_MAX_WIDTH				4096
+#define ZYNQMP_DISP_MAX_HEIGHT				4096
+/* 44 bit addressing. This is acutally DPDMA limitation */
+#define ZYNQMP_DISP_MAX_DMA_BIT				44
+
+/**
+ * enum zynqmp_disp_layer_type - Layer type (can be used for hw ID)
+ * @ZYNQMP_DISP_LAYER_VID: Video layer
+ * @ZYNQMP_DISP_LAYER_GFX: Graphics layer
+ */
+enum zynqmp_disp_layer_type {
+	ZYNQMP_DISP_LAYER_VID,
+	ZYNQMP_DISP_LAYER_GFX
+};
+
+/**
+ * enum zynqmp_disp_layer_mode - Layer mode
+ * @ZYNQMP_DISP_LAYER_NONLIVE: non-live (memory) mode
+ * @ZYNQMP_DISP_LAYER_LIVE: live (stream) mode
+ */
+enum zynqmp_disp_layer_mode {
+	ZYNQMP_DISP_LAYER_NONLIVE,
+	ZYNQMP_DISP_LAYER_LIVE
+};
+
+/**
+ * struct zynqmp_disp_layer_dma - struct for DMA engine
+ * @chan: DMA channel
+ * @is_active: flag if the DMA is active
+ * @xt: Interleaved desc config container
+ * @sgl: Data chunk for dma_interleaved_template
+ */
+struct zynqmp_disp_layer_dma {
+	struct dma_chan *chan;
+	bool is_active;
+	struct dma_interleaved_template xt;
+	struct data_chunk sgl[1];
+};
+
+/**
+ * struct zynqmp_disp_layer - Display subsystem layer
+ * @plane: DRM plane
+ * @of_node: device node
+ * @dma: struct for DMA engine
+ * @num_chan: Number of DMA channel
+ * @id: Layer ID
+ * @offset: Layer offset in the register space
+ * @enabled: flag if enabled
+ * @fmt: Current format descriptor
+ * @drm_fmts: Array of supported DRM formats
+ * @num_fmts: Number of supported DRM formats
+ * @bus_fmts: Array of supported bus formats
+ * @num_bus_fmts: Number of supported bus formats
+ * @w: Width
+ * @h: Height
+ * @mode: the operation mode
+ * @other: other layer
+ * @disp: back pointer to struct zynqmp_disp
+ */
+struct zynqmp_disp_layer {
+	struct drm_plane plane;
+	struct device_node *of_node;
+	struct zynqmp_disp_layer_dma dma[ZYNQMP_DISP_MAX_NUM_SUB_PLANES];
+	unsigned int num_chan;
+	enum zynqmp_disp_layer_type id;
+	u32 offset;
+	u8 enabled;
+	const struct zynqmp_disp_fmt *fmt;
+	u32 *drm_fmts;
+	unsigned int num_fmts;
+	u32 *bus_fmts;
+	unsigned int num_bus_fmts;
+	u32 w;
+	u32 h;
+	enum zynqmp_disp_layer_mode mode;
+	struct zynqmp_disp_layer *other;
+	struct zynqmp_disp *disp;
+};
+
+/**
+ * struct zynqmp_disp_blend - Blender
+ * @base: Base address offset
+ */
+struct zynqmp_disp_blend {
+	void __iomem *base;
+};
+
+/**
+ * struct zynqmp_disp_av_buf - AV buffer manager
+ * @base: Base address offset
+ */
+struct zynqmp_disp_av_buf {
+	void __iomem *base;
+};
+
+/**
+ * struct zynqmp_disp_aud - Audio
+ * @base: Base address offset
+ */
+struct zynqmp_disp_aud {
+	void __iomem *base;
+};
+
+/**
+ * struct zynqmp_disp - Display subsystem
+ * @xlnx_crtc: Xilinx DRM crtc
+ * @dev: device structure
+ * @dpsub: Display subsystem
+ * @drm: DRM core
+ * @enabled: flag if enabled
+ * @blend: Blender block
+ * @av_buf: AV buffer manager block
+ * @aud:Audio block
+ * @layers: layers
+ * @g_alpha_prop: global alpha property
+ * @alpha: current global alpha value
+ * @g_alpha_en_prop: the global alpha enable property
+ * @alpha_en: flag if the global alpha is enabled
+ * @color_prop: output color format property
+ * @color: current output color value
+ * @bg_c0_prop: 1st component of background color property
+ * @bg_c0: current value of 1st background color component
+ * @bg_c1_prop: 2nd component of background color property
+ * @bg_c1: current value of 2nd background color component
+ * @bg_c2_prop: 3rd component of background color property
+ * @bg_c2: current value of 3rd background color component
+ * @tpg_prop: Test Pattern Generation mode property
+ * @tpg_on: current TPG mode state
+ * @event: pending vblank event request
+ * @_ps_pclk: Pixel clock from PS
+ * @_pl_pclk: Pixel clock from PL
+ * @pclk: Pixel clock
+ * @pclk_en: Flag if the pixel clock is enabled
+ * @_ps_audclk: Audio clock from PS
+ * @_pl_audclk: Audio clock from PL
+ * @audclk: Audio clock
+ * @audclk_en: Flag if the audio clock is enabled
+ * @aclk: APB clock
+ * @aclk_en: Flag if the APB clock is enabled
+ */
+struct zynqmp_disp {
+	struct xlnx_crtc xlnx_crtc;
+	struct device *dev;
+	struct zynqmp_dpsub *dpsub;
+	struct drm_device *drm;
+	bool enabled;
+	struct zynqmp_disp_blend blend;
+	struct zynqmp_disp_av_buf av_buf;
+	struct zynqmp_disp_aud aud;
+	struct zynqmp_disp_layer layers[ZYNQMP_DISP_NUM_LAYERS];
+	struct drm_property *g_alpha_prop;
+	u32 alpha;
+	struct drm_property *g_alpha_en_prop;
+	bool alpha_en;
+	struct drm_property *color_prop;
+	unsigned int color;
+	struct drm_property *bg_c0_prop;
+	u32 bg_c0;
+	struct drm_property *bg_c1_prop;
+	u32 bg_c1;
+	struct drm_property *bg_c2_prop;
+	u32 bg_c2;
+	struct drm_property *tpg_prop;
+	bool tpg_on;
+	struct drm_pending_vblank_event *event;
+	/* Don't operate directly on _ps_ */
+	struct clk *_ps_pclk;
+	struct clk *_pl_pclk;
+	struct clk *pclk;
+	bool pclk_en;
+	struct clk *_ps_audclk;
+	struct clk *_pl_audclk;
+	struct clk *audclk;
+	bool audclk_en;
+	struct clk *aclk;
+	bool aclk_en;
+};
+
+/**
+ * struct zynqmp_disp_fmt - Display subsystem format mapping
+ * @drm_fmt: drm format
+ * @disp_fmt: Display subsystem format
+ * @bus_fmt: Bus formats (live formats)
+ * @rgb: flag for RGB formats
+ * @swap: flag to swap r & b for rgb formats, and u & v for yuv formats
+ * @chroma_sub: flag for chroma subsampled formats
+ * @sf: scaling factors for upto 3 color components
+ */
+struct zynqmp_disp_fmt {
+	u32 drm_fmt;
+	u32 disp_fmt;
+	u32 bus_fmt;
+	bool rgb;
+	bool swap;
+	bool chroma_sub;
+	u32 sf[3];
+};
+
+static void zynqmp_disp_write(void __iomem *base, int offset, u32 val)
+{
+	writel(val, base + offset);
+}
+
+static u32 zynqmp_disp_read(void __iomem *base, int offset)
+{
+	return readl(base + offset);
+}
+
+static void zynqmp_disp_clr(void __iomem *base, int offset, u32 clr)
+{
+	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) & ~clr);
+}
+
+static void zynqmp_disp_set(void __iomem *base, int offset, u32 set)
+{
+	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) | set);
+}
+
+/*
+ * Clock functions
+ */
+
+/**
+ * zynqmp_disp_clk_enable - Enable the clock if needed
+ * @clk: clk device
+ * @flag: flag if the clock is enabled
+ *
+ * Enable the clock only if it's not enabled @flag.
+ *
+ * Return: value from clk_prepare_enable().
+ */
+static int zynqmp_disp_clk_enable(struct clk *clk, bool *flag)
+{
+	int ret = 0;
+
+	if (!*flag) {
+		ret = clk_prepare_enable(clk);
+		if (!ret)
+			*flag = true;
+	}
+
+	return ret;
+}
+
+/**
+ * zynqmp_disp_clk_enable - Enable the clock if needed
+ * @clk: clk device
+ * @flag: flag if the clock is enabled
+ *
+ * Disable the clock only if it's enabled @flag.
+ */
+static void zynqmp_disp_clk_disable(struct clk *clk, bool *flag)
+{
+	if (*flag) {
+		clk_disable_unprepare(clk);
+		*flag = false;
+	}
+}
+
+/**
+ * zynqmp_disp_clk_enable - Enable and disable the clock
+ * @clk: clk device
+ * @flag: flag if the clock is enabled
+ *
+ * This is to ensure the clock is disabled. The initial hardware state is
+ * unknown, and this makes sure that the clock is disabled.
+ *
+ * Return: value from clk_prepare_enable().
+ */
+static int zynqmp_disp_clk_enable_disable(struct clk *clk, bool *flag)
+{
+	int ret = 0;
+
+	if (!*flag) {
+		ret = clk_prepare_enable(clk);
+		clk_disable_unprepare(clk);
+	}
+
+	return ret;
+}
+
+/*
+ * Blender functions
+ */
+
+/**
+ * zynqmp_disp_blend_set_output_fmt - Set the output format of the blend
+ * @blend: blend object
+ * @fmt: output format
+ *
+ * Set the output format to @fmt.
+ */
+static void
+zynqmp_disp_blend_set_output_fmt(struct zynqmp_disp_blend *blend, u32 fmt)
+{
+	u16 reset_coeffs[] = { 0x1000, 0x0, 0x0,
+			       0x0, 0x1000, 0x0,
+			       0x0, 0x0, 0x1000 };
+	u32 reset_offsets[] = { 0x0, 0x0, 0x0 };
+	u16 sdtv_coeffs[] = { 0x4c9, 0x864, 0x1d3,
+			      0x7d4d, 0x7ab3, 0x800,
+			      0x800, 0x794d, 0x7eb3 };
+	u32 full_range_offsets[] = { 0x0, 0x8000000, 0x8000000 };
+	u16 *coeffs;
+	u32 *offsets;
+	u32 offset, i;
+
+	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
+	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
+		coeffs = reset_coeffs;
+		offsets = reset_offsets;
+	} else {
+		/* Hardcode Full-range SDTV values. Can be runtime config */
+		coeffs = sdtv_coeffs;
+		offsets = full_range_offsets;
+	}
+
+	offset = ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF0;
+	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
+		zynqmp_disp_write(blend->base, offset + i * 4, coeffs[i]);
+
+	offset = ZYNQMP_DISP_V_BLEND_LUMA_OUTCSC_OFFSET;
+	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
+		zynqmp_disp_write(blend->base, offset + i * 4, offsets[i]);
+}
+
+/**
+ * zynqmp_disp_blend_layer_enable - Enable a layer
+ * @blend: blend object
+ * @layer: layer to enable
+ *
+ * Enable a layer @layer.
+ */
+static void zynqmp_disp_blend_layer_enable(struct zynqmp_disp_blend *blend,
+					   struct zynqmp_disp_layer *layer)
+{
+	u32 reg, offset, i, s0, s1;
+	u16 sdtv_coeffs[] = { 0x1000, 0x166f, 0x0,
+			      0x1000, 0x7483, 0x7a7f,
+			      0x1000, 0x0, 0x1c5a };
+	u16 swap_coeffs[] = { 0x1000, 0x0, 0x0,
+			      0x0, 0x1000, 0x0,
+			      0x0, 0x0, 0x1000 };
+	u16 *coeffs;
+	u32 offsets[] = { 0x0, 0x1800, 0x1800 };
+
+	reg = layer->fmt->rgb ? ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_RGB : 0;
+	reg |= layer->fmt->chroma_sub ?
+	       ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_EN_US : 0;
+
+	zynqmp_disp_write(blend->base,
+			  ZYNQMP_DISP_V_BLEND_LAYER_CONTROL + layer->offset,
+			  reg);
+
+	if (layer->id == ZYNQMP_DISP_LAYER_VID)
+		offset = ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0;
+	else
+		offset = ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0;
+
+	if (!layer->fmt->rgb) {
+		coeffs = sdtv_coeffs;
+		s0 = 1;
+		s1 = 2;
+	} else {
+		coeffs = swap_coeffs;
+		s0 = 0;
+		s1 = 2;
+
+		/* No offset for RGB formats */
+		for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
+			offsets[i] = 0;
+	}
+
+	if (layer->fmt->swap) {
+		for (i = 0; i < 3; i++) {
+			coeffs[i * 3 + s0] ^= coeffs[i * 3 + s1];
+			coeffs[i * 3 + s1] ^= coeffs[i * 3 + s0];
+			coeffs[i * 3 + s0] ^= coeffs[i * 3 + s1];
+		}
+	}
+
+	/* Program coefficients. Can be runtime configurable */
+	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
+		zynqmp_disp_write(blend->base, offset + i * 4, coeffs[i]);
+
+	if (layer->id == ZYNQMP_DISP_LAYER_VID)
+		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET;
+	else
+		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET;
+
+	/* Program offsets. Can be runtime configurable */
+	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
+		zynqmp_disp_write(blend->base, offset + i * 4, offsets[i]);
+}
+
+/**
+ * zynqmp_disp_blend_layer_disable - Disable a layer
+ * @blend: blend object
+ * @layer: layer to disable
+ *
+ * Disable a layer @layer.
+ */
+static void zynqmp_disp_blend_layer_disable(struct zynqmp_disp_blend *blend,
+					    struct zynqmp_disp_layer *layer)
+{
+	u32 offset;
+	unsigned int i;
+
+	zynqmp_disp_write(blend->base,
+			  ZYNQMP_DISP_V_BLEND_LAYER_CONTROL + layer->offset, 0);
+
+	if (layer->id == ZYNQMP_DISP_LAYER_VID)
+		offset = ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0;
+	else
+		offset = ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0;
+	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
+		zynqmp_disp_write(blend->base, offset + i * 4, 0);
+
+	if (layer->id == ZYNQMP_DISP_LAYER_VID)
+		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET;
+	else
+		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET;
+
+	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
+		zynqmp_disp_write(blend->base, offset + i * 4, 0);
+}
+
+/**
+ * zynqmp_disp_blend_set_bg_color - Set the background color
+ * @blend: blend object
+ * @c0: color component 0
+ * @c1: color component 1
+ * @c2: color component 2
+ *
+ * Set the background color.
+ */
+static void zynqmp_disp_blend_set_bg_color(struct zynqmp_disp_blend *blend,
+					   u32 c0, u32 c1, u32 c2)
+{
+	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_BG_CLR_0, c0);
+	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_BG_CLR_1, c1);
+	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_BG_CLR_2, c2);
+}
+
+/**
+ * zynqmp_disp_blend_set_alpha - Set the alpha for blending
+ * @blend: blend object
+ * @alpha: alpha value to be used
+ *
+ * Set the alpha for blending.
+ */
+static void
+zynqmp_disp_blend_set_alpha(struct zynqmp_disp_blend *blend, u32 alpha)
+{
+	u32 reg;
+
+	reg = zynqmp_disp_read(blend->base,
+			       ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA);
+	reg &= ~ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MASK;
+	reg |= alpha << 1;
+	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA,
+			  reg);
+}
+
+/**
+ * zynqmp_disp_blend_enable_alpha - Enable/disable the global alpha
+ * @blend: blend object
+ * @enable: flag to enable or disable alpha blending
+ *
+ * Enable/disable the global alpha blending based on @enable.
+ */
+static void
+zynqmp_disp_blend_enable_alpha(struct zynqmp_disp_blend *blend, bool enable)
+{
+	if (enable)
+		zynqmp_disp_set(blend->base,
+				ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA, BIT(0));
+	else
+		zynqmp_disp_clr(blend->base,
+				ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA, BIT(0));
+}
+
+/* List of blend output formats */
+/* The id / order should be aligned with zynqmp_disp_color_enum */
+static const struct zynqmp_disp_fmt blend_output_fmts[] = {
+	{
+		.disp_fmt	= ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB,
+	}, {
+		.disp_fmt	= ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444,
+	}, {
+		.disp_fmt	= ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422,
+	}, {
+		.disp_fmt	= ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY,
+	}
+};
+
+/*
+ * AV buffer manager functions
+ */
+
+/* List of video layer formats */
+#define ZYNQMP_DISP_AV_BUF_VID_FMT_YUYV	2
+static const struct zynqmp_disp_fmt av_buf_vid_fmts[] = {
+	{
+		.drm_fmt	= DRM_FORMAT_VYUY,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY,
+		.rgb		= false,
+		.swap		= true,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_UYVY,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_YUYV,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_YVYU,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV,
+		.rgb		= false,
+		.swap		= true,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_YUV422,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_YVU422,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16,
+		.rgb		= false,
+		.swap		= true,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_YUV444,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_YVU444,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24,
+		.rgb		= false,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_NV16,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_NV61,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI,
+		.rgb		= false,
+		.swap		= true,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_BGR888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_RGB888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888,
+		.rgb		= true,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_XBGR8888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_XRGB8888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880,
+		.rgb		= true,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_XBGR2101010,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_XRGB2101010,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10,
+		.rgb		= true,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_YUV420,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_YVU420,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420,
+		.rgb		= false,
+		.swap		= true,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_NV12,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_NV21,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420,
+		.rgb		= false,
+		.swap		= true,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}
+};
+
+/* List of graphics layer formats */
+#define ZYNQMP_DISP_AV_BUF_GFX_FMT_RGB565	10
+static const struct zynqmp_disp_fmt av_buf_gfx_fmts[] = {
+	{
+		.drm_fmt	= DRM_FORMAT_ABGR8888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_ARGB8888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888,
+		.rgb		= true,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_RGBA8888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_BGRA8888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888,
+		.rgb		= true,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_BGR888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB888,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_RGB888,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_BGR888,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_RGBA5551,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_BGRA5551,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551,
+		.rgb		= true,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_RGBA4444,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_BGRA4444,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444,
+		.rgb		= true,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_RGB565,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+	}, {
+		.drm_fmt	= DRM_FORMAT_BGR565,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565,
+		.rgb		= true,
+		.swap		= true,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
+	}
+};
+
+/* List of live formats */
+/* Format can be combination of color, bpc, and cb-cr order.
+ * - Color: RGB / YUV444 / YUV422 / Y only
+ * - BPC: 6, 8, 10, 12
+ * - Swap: Cb and Cr swap
+ * which can be 32 bus formats. Only list the subset of those for now.
+ */
+static const struct zynqmp_disp_fmt av_buf_live_fmts[] = {
+	{
+		.bus_fmt	= MEDIA_BUS_FMT_RGB666_1X18,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_6 ||
+				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
+	}, {
+		.bus_fmt	= MEDIA_BUS_FMT_RBG888_1X24,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
+				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB,
+		.rgb		= true,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.bus_fmt	= MEDIA_BUS_FMT_UYVY8_1X16,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
+				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.bus_fmt	= MEDIA_BUS_FMT_VUY8_1X24,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
+				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV444,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= false,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
+	}, {
+		.bus_fmt	= MEDIA_BUS_FMT_UYVY10_1X20,
+		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_10 ||
+				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422,
+		.rgb		= false,
+		.swap		= false,
+		.chroma_sub	= true,
+		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
+	}
+};
+
+/**
+ * zynqmp_disp_av_buf_set_fmt - Set the input formats
+ * @av_buf: av buffer manager
+ * @fmt: formats
+ *
+ * Set the av buffer manager format to @fmt. @fmt should have valid values
+ * for both video and graphics layer.
+ */
+static void
+zynqmp_disp_av_buf_set_fmt(struct zynqmp_disp_av_buf *av_buf, u32 fmt)
+{
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_FMT, fmt);
+}
+
+/**
+ * zynqmp_disp_av_buf_get_fmt - Get the input formats
+ * @av_buf: av buffer manager
+ *
+ * Get the input formats (which include video and graphics) of
+ * av buffer manager.
+ *
+ * Return: value of ZYNQMP_DISP_AV_BUF_FMT register.
+ */
+static u32
+zynqmp_disp_av_buf_get_fmt(struct zynqmp_disp_av_buf *av_buf)
+{
+	return zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_FMT);
+}
+
+/**
+ * zynqmp_disp_av_buf_set_vid_clock_src - Set the video clock source
+ * @av_buf: av buffer manager
+ * @from_ps: flag if the video clock is from ps
+ *
+ * Set the video clock source based on @from_ps. It can come from either PS or
+ * PL.
+ */
+static void
+zynqmp_disp_av_buf_set_vid_clock_src(struct zynqmp_disp_av_buf *av_buf,
+				     bool from_ps)
+{
+	u32 reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC);
+
+	if (from_ps)
+		reg |= ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS;
+	else
+		reg &= ~ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS;
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_set_vid_timing_src - Set the video timing source
+ * @av_buf: av buffer manager
+ * @internal: flag if the video timing is generated internally
+ *
+ * Set the video timing source based on @internal. It can come externally or
+ * be generated internally.
+ */
+static void
+zynqmp_disp_av_buf_set_vid_timing_src(struct zynqmp_disp_av_buf *av_buf,
+				      bool internal)
+{
+	u32 reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC);
+
+	if (internal)
+		reg |= ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING;
+	else
+		reg &= ~ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING;
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_set_aud_clock_src - Set the audio clock source
+ * @av_buf: av buffer manager
+ * @from_ps: flag if the video clock is from ps
+ *
+ * Set the audio clock source based on @from_ps. It can come from either PS or
+ * PL.
+ */
+static void
+zynqmp_disp_av_buf_set_aud_clock_src(struct zynqmp_disp_av_buf *av_buf,
+				     bool from_ps)
+{
+	u32 reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC);
+
+	if (from_ps)
+		reg |= ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS;
+	else
+		reg &= ~ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS;
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_enable_buf - Enable buffers
+ * @av_buf: av buffer manager
+ *
+ * Enable all (video and audio) buffers.
+ */
+static void
+zynqmp_disp_av_buf_enable_buf(struct zynqmp_disp_av_buf *av_buf)
+{
+	u32 reg, i;
+
+	reg = ZYNQMP_DISP_AV_BUF_CHBUF_EN;
+	reg |= ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MAX <<
+	       ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT;
+
+	for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_VID_GFX_BUFFERS; i++)
+		zynqmp_disp_write(av_buf->base,
+				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4, reg);
+
+	reg = ZYNQMP_DISP_AV_BUF_CHBUF_EN;
+	reg |= ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_AUD_MAX <<
+	       ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT;
+
+	for (; i < ZYNQMP_DISP_AV_BUF_NUM_BUFFERS; i++)
+		zynqmp_disp_write(av_buf->base,
+				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_disable_buf - Disable buffers
+ * @av_buf: av buffer manager
+ *
+ * Disable all (video and audio) buffers.
+ */
+static void
+zynqmp_disp_av_buf_disable_buf(struct zynqmp_disp_av_buf *av_buf)
+{
+	u32 reg, i;
+
+	reg = ZYNQMP_DISP_AV_BUF_CHBUF_FLUSH & ~ZYNQMP_DISP_AV_BUF_CHBUF_EN;
+	for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_BUFFERS; i++)
+		zynqmp_disp_write(av_buf->base,
+				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_enable_aud - Enable audio
+ * @av_buf: av buffer manager
+ *
+ * Enable all audio buffers.
+ */
+static void
+zynqmp_disp_av_buf_enable_aud(struct zynqmp_disp_av_buf *av_buf)
+{
+	u32 reg;
+
+	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
+	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK;
+	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MEM;
+	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN;
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_enable - Enable the video pipe
+ * @av_buf: av buffer manager
+ *
+ * De-assert the video pipe reset
+ */
+static void
+zynqmp_disp_av_buf_enable(struct zynqmp_disp_av_buf *av_buf)
+{
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_SRST_REG, 0);
+}
+
+/**
+ * zynqmp_disp_av_buf_disable - Disable the video pipe
+ * @av_buf: av buffer manager
+ *
+ * Assert the video pipe reset
+ */
+static void
+zynqmp_disp_av_buf_disable(struct zynqmp_disp_av_buf *av_buf)
+{
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_SRST_REG,
+			  ZYNQMP_DISP_AV_BUF_SRST_REG_VID_RST);
+}
+
+/**
+ * zynqmp_disp_av_buf_disable_aud - Disable audio
+ * @av_buf: av buffer manager
+ *
+ * Disable all audio buffers.
+ */
+static void
+zynqmp_disp_av_buf_disable_aud(struct zynqmp_disp_av_buf *av_buf)
+{
+	u32 reg;
+
+	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
+	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK;
+	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_DISABLE;
+	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN;
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_set_tpg - Set TPG mode
+ * @av_buf: av buffer manager
+ * @tpg_on: if TPG should be on
+ *
+ * Set the TPG mode based on @tpg_on.
+ */
+static void zynqmp_disp_av_buf_set_tpg(struct zynqmp_disp_av_buf *av_buf,
+				       bool tpg_on)
+{
+	u32 reg;
+
+	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
+	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
+	if (tpg_on)
+		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN;
+	else
+		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN;
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_enable_vid - Enable the video layer buffer
+ * @av_buf: av buffer manager
+ * @layer: layer to enable
+ * @mode: operation mode of layer
+ *
+ * Enable the video/graphics buffer for @layer.
+ */
+static void zynqmp_disp_av_buf_enable_vid(struct zynqmp_disp_av_buf *av_buf,
+					  struct zynqmp_disp_layer *layer,
+					  enum zynqmp_disp_layer_mode mode)
+{
+	u32 reg;
+
+	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
+	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
+		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
+		if (mode == ZYNQMP_DISP_LAYER_NONLIVE)
+			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM;
+		else
+			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE;
+	} else {
+		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK;
+		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM;
+		if (mode == ZYNQMP_DISP_LAYER_NONLIVE)
+			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM;
+		else
+			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE;
+	}
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_disable_vid - Disable the video layer buffer
+ * @av_buf: av buffer manager
+ * @layer: layer to disable
+ *
+ * Disable the video/graphics buffer for @layer.
+ */
+static void
+zynqmp_disp_av_buf_disable_vid(struct zynqmp_disp_av_buf *av_buf,
+			       struct zynqmp_disp_layer *layer)
+{
+	u32 reg;
+
+	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
+	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
+		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
+		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_NONE;
+	} else {
+		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK;
+		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_DISABLE;
+	}
+	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
+}
+
+/**
+ * zynqmp_disp_av_buf_init_sf - Initialize scaling factors
+ * @av_buf: av buffer manager
+ * @vid_fmt: video format descriptor
+ * @gfx_fmt: graphics format descriptor
+ *
+ * Initialize scaling factors for both video and graphics layers.
+ * If the format descriptor is NULL, the function skips the programming.
+ */
+static void zynqmp_disp_av_buf_init_sf(struct zynqmp_disp_av_buf *av_buf,
+				       const struct zynqmp_disp_fmt *vid_fmt,
+				       const struct zynqmp_disp_fmt *gfx_fmt)
+{
+	unsigned int i;
+	u32 offset;
+
+	if (gfx_fmt) {
+		offset = ZYNQMP_DISP_AV_BUF_GFX_COMP0_SF;
+		for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_SF; i++)
+			zynqmp_disp_write(av_buf->base, offset + i * 4,
+					  gfx_fmt->sf[i]);
+	}
+
+	if (vid_fmt) {
+		offset = ZYNQMP_DISP_AV_BUF_VID_COMP0_SF;
+		for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_SF; i++)
+			zynqmp_disp_write(av_buf->base, offset + i * 4,
+					  vid_fmt->sf[i]);
+	}
+}
+
+/*
+ * Audio functions
+ */
+
+/**
+ * zynqmp_disp_aud_init - Initialize the audio
+ * @aud: audio
+ *
+ * Initialize the audio with default mixer volume. The de-assertion will
+ * initialize the audio states.
+ */
+static void zynqmp_disp_aud_init(struct zynqmp_disp_aud *aud)
+{
+	/* Clear the audio soft reset register as it's an non-reset flop */
+	zynqmp_disp_write(aud->base, ZYNQMP_DISP_AUD_SOFT_RESET, 0);
+	zynqmp_disp_write(aud->base, ZYNQMP_DISP_AUD_MIXER_VOLUME,
+			  ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE);
+}
+
+/**
+ * zynqmp_disp_aud_deinit - De-initialize the audio
+ * @aud: audio
+ *
+ * Put the audio in reset.
+ */
+static void zynqmp_disp_aud_deinit(struct zynqmp_disp_aud *aud)
+{
+	zynqmp_disp_set(aud->base, ZYNQMP_DISP_AUD_SOFT_RESET,
+			ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST);
+}
+
+/*
+ * ZynqMP Display layer functions
+ */
+
+/**
+ * zynqmp_disp_layer_check_size - Verify width and height for the layer
+ * @disp: Display subsystem
+ * @layer: layer
+ * @width: width
+ * @height: height
+ *
+ * The Display subsystem has the limitation that both layers should have
+ * identical size. This function stores width and height of @layer, and verifies
+ * if the size (width and height) is valid.
+ *
+ * Return: 0 on success, or -EINVAL if width or/and height is invalid.
+ */
+static int zynqmp_disp_layer_check_size(struct zynqmp_disp *disp,
+					struct zynqmp_disp_layer *layer,
+					u32 width, u32 height)
+{
+	struct zynqmp_disp_layer *other = layer->other;
+
+	if (other->enabled && (other->w != width || other->h != height)) {
+		dev_err(disp->dev, "Layer width:height must be %d:%d\n",
+			other->w, other->h);
+		return -EINVAL;
+	}
+
+	layer->w = width;
+	layer->h = height;
+
+	return 0;
+}
+
+/**
+ * zynqmp_disp_map_fmt - Find the Display subsystem format for given drm format
+ * @fmts: format table to look up
+ * @size: size of the table @fmts
+ * @drm_fmt: DRM format to search
+ *
+ * Search a Display subsystem format corresponding to the given DRM format
+ * @drm_fmt, and return the format descriptor which contains the Display
+ * subsystem format value.
+ *
+ * Return: a Display subsystem format descriptor on success, or NULL.
+ */
+static const struct zynqmp_disp_fmt *
+zynqmp_disp_map_fmt(const struct zynqmp_disp_fmt fmts[],
+		    unsigned int size, uint32_t drm_fmt)
+{
+	unsigned int i;
+
+	for (i = 0; i < size; i++)
+		if (fmts[i].drm_fmt == drm_fmt)
+			return &fmts[i];
+
+	return NULL;
+}
+
+/**
+ * zynqmp_disp_set_fmt - Set the format of the layer
+ * @disp: Display subsystem
+ * @layer: layer to set the format
+ * @drm_fmt: DRM format to set
+ *
+ * Set the format of the given layer to @drm_fmt.
+ *
+ * Return: 0 on success. -EINVAL if @drm_fmt is not supported by the layer.
+ */
+static int zynqmp_disp_layer_set_fmt(struct zynqmp_disp *disp,
+				     struct zynqmp_disp_layer *layer,
+				     uint32_t drm_fmt)
+{
+	const struct zynqmp_disp_fmt *fmt;
+	const struct zynqmp_disp_fmt *vid_fmt = NULL, *gfx_fmt = NULL;
+	u32 size, fmts, mask;
+
+	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
+		size = ARRAY_SIZE(av_buf_vid_fmts);
+		mask = ~ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MASK;
+		fmt = zynqmp_disp_map_fmt(av_buf_vid_fmts, size, drm_fmt);
+		vid_fmt = fmt;
+	} else {
+		size = ARRAY_SIZE(av_buf_gfx_fmts);
+		mask = ~ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_MASK;
+		fmt = zynqmp_disp_map_fmt(av_buf_gfx_fmts, size, drm_fmt);
+		gfx_fmt = fmt;
+	}
+
+	if (!fmt)
+		return -EINVAL;
+
+	fmts = zynqmp_disp_av_buf_get_fmt(&disp->av_buf);
+	fmts &= mask;
+	fmts |= fmt->disp_fmt;
+	zynqmp_disp_av_buf_set_fmt(&disp->av_buf, fmts);
+	zynqmp_disp_av_buf_init_sf(&disp->av_buf, vid_fmt, gfx_fmt);
+	layer->fmt = fmt;
+
+	return 0;
+}
+
+/**
+ * zynqmp_disp_set_tpg - Enable or disable TPG
+ * @disp: Display subsystem
+ * @layer: Video layer
+ * @tpg_on: flag if TPG needs to be enabled or disabled
+ *
+ * Enable / disable the TPG mode on the video layer @layer depending on
+ * @tpg_on. The video layer should be disabled prior to enable request.
+ *
+ * Return: 0 on success. -ENODEV if it's not video layer. -EIO if
+ * the video layer is enabled.
+ */
+static int zynqmp_disp_layer_set_tpg(struct zynqmp_disp *disp,
+				     struct zynqmp_disp_layer *layer,
+				     bool tpg_on)
+{
+	if (layer->id != ZYNQMP_DISP_LAYER_VID) {
+		dev_err(disp->dev,
+			"only the video layer has the tpg mode\n");
+		return -ENODEV;
+	}
+
+	if (layer->enabled) {
+		dev_err(disp->dev,
+			"the video layer should be disabled for tpg mode\n");
+		return -EIO;
+	}
+
+	zynqmp_disp_av_buf_set_tpg(&disp->av_buf, tpg_on);
+	disp->tpg_on = tpg_on;
+
+	return 0;
+}
+
+/**
+ * zynqmp_disp_get_tpg - Get the TPG mode status
+ * @disp: Display subsystem
+ * @layer: Video layer
+ *
+ * Return if the TPG is enabled or not.
+ *
+ * Return: true if TPG is on, otherwise false
+ */
+static bool zynqmp_disp_layer_get_tpg(struct zynqmp_disp *disp,
+				      struct zynqmp_disp_layer *layer)
+{
+	return disp->tpg_on;
+}
+
+/**
+ * zynqmp_disp_get_fmt - Get the supported DRM formats of the layer
+ * @disp: Display subsystem
+ * @layer: layer to get the formats
+ * @drm_fmts: pointer to array of DRM format strings
+ * @num_fmts: pointer to number of returned DRM formats
+ *
+ * Get the supported DRM formats of the given layer.
+ */
+static void zynqmp_disp_layer_get_fmts(struct zynqmp_disp *disp,
+				       struct zynqmp_disp_layer *layer,
+				       u32 **drm_fmts, unsigned int *num_fmts)
+{
+	*drm_fmts = layer->drm_fmts;
+	*num_fmts = layer->num_fmts;
+}
+
+/**
+ * zynqmp_disp_layer_enable - Enable the layer
+ * @disp: Display subsystem
+ * @layer: layer to esable
+ * @mode: operation mode
+ *
+ * Enable the layer @layer.
+ *
+ * Return: 0 on success, otherwise error code.
+ */
+static int zynqmp_disp_layer_enable(struct zynqmp_disp *disp,
+				    struct zynqmp_disp_layer *layer,
+				    enum zynqmp_disp_layer_mode mode)
+{
+	struct device *dev = disp->dev;
+	struct dma_async_tx_descriptor *desc;
+	enum dma_ctrl_flags flags;
+	unsigned int i;
+
+	if (layer->enabled && layer->mode != mode) {
+		dev_err(dev, "layer is already enabled in different mode\n");
+		return -EBUSY;
+	}
+
+	zynqmp_disp_av_buf_enable_vid(&disp->av_buf, layer, mode);
+	zynqmp_disp_blend_layer_enable(&disp->blend, layer);
+
+	layer->enabled = true;
+	layer->mode = mode;
+
+	if (mode == ZYNQMP_DISP_LAYER_LIVE)
+		return 0;
+
+	for (i = 0; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++) {
+		struct zynqmp_disp_layer_dma *dma = &layer->dma[i];
+
+		if (dma->chan && dma->is_active) {
+			flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
+			desc = dmaengine_prep_interleaved_dma(dma->chan,
+							      &dma->xt, flags);
+			if (!desc) {
+				dev_err(dev, "failed to prep DMA descriptor\n");
+				return -ENOMEM;
+			}
+
+			dmaengine_submit(desc);
+			dma_async_issue_pending(dma->chan);
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * zynqmp_disp_layer_disable - Disable the layer
+ * @disp: Display subsystem
+ * @layer: layer to disable
+ * @mode: operation mode
+ *
+ * Disable the layer @layer.
+ *
+ * Return: 0 on success, or -EBUSY if the layer is in different mode.
+ */
+static int zynqmp_disp_layer_disable(struct zynqmp_disp *disp,
+				     struct zynqmp_disp_layer *layer,
+				     enum zynqmp_disp_layer_mode mode)
+{
+	struct device *dev = disp->dev;
+	unsigned int i;
+
+	if (layer->mode != mode) {
+		dev_err(dev, "the layer is operating in different mode\n");
+		return -EBUSY;
+	}
+
+	for (i = 0; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++)
+		if (layer->dma[i].chan && layer->dma[i].is_active)
+			dmaengine_terminate_sync(layer->dma[i].chan);
+
+	zynqmp_disp_av_buf_disable_vid(&disp->av_buf, layer);
+	zynqmp_disp_blend_layer_disable(&disp->blend, layer);
+	layer->enabled = false;
+
+	return 0;
+}
+
+/**
+ * zynqmp_disp_layer_request_dma - Request DMA channels for a layer
+ * @disp: Display subsystem
+ * @layer: layer to request DMA channels
+ * @name: identifier string for layer type
+ *
+ * Request DMA engine channels for corresponding layer.
+ *
+ * Return: 0 on success, or err value from of_dma_request_slave_channel().
+ */
+static int
+zynqmp_disp_layer_request_dma(struct zynqmp_disp *disp,
+			      struct zynqmp_disp_layer *layer, const char *name)
+{
+	struct zynqmp_disp_layer_dma *dma;
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < layer->num_chan; i++) {
+		char temp[16];
+
+		dma = &layer->dma[i];
+		snprintf(temp, sizeof(temp), "%s%d", name, i);
+		dma->chan = of_dma_request_slave_channel(layer->of_node,
+							 temp);
+		if (IS_ERR(dma->chan)) {
+			dev_err(disp->dev, "failed to request dma channel\n");
+			ret = PTR_ERR(dma->chan);
+			dma->chan = NULL;
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * zynqmp_disp_layer_release_dma - Release DMA channels for a layer
+ * @disp: Display subsystem
+ * @layer: layer to release DMA channels
+ *
+ * Release the dma channels associated with @layer.
+ */
+static void zynqmp_disp_layer_release_dma(struct zynqmp_disp *disp,
+					  struct zynqmp_disp_layer *layer)
+{
+	unsigned int i;
+
+	for (i = 0; i < layer->num_chan; i++) {
+		if (layer->dma[i].chan) {
+			/* Make sure the channel is terminated before release */
+			dmaengine_terminate_all(layer->dma[i].chan);
+			dma_release_channel(layer->dma[i].chan);
+		}
+	}
+}
+
+/**
+ * zynqmp_disp_layer_is_live - if any layer is live
+ * @disp: Display subsystem
+ *
+ * Return: true if any layer is live
+ */
+static bool zynqmp_disp_layer_is_live(struct zynqmp_disp *disp)
+{
+	unsigned int i;
+
+	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
+		if (disp->layers[i].enabled &&
+		    disp->layers[i].mode == ZYNQMP_DISP_LAYER_LIVE)
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * zynqmp_disp_layer_is_enabled - if any layer is enabled
+ * @disp: Display subsystem
+ *
+ * Return: true if any layer is enabled
+ */
+static bool zynqmp_disp_layer_is_enabled(struct zynqmp_disp *disp)
+{
+	unsigned int i;
+
+	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
+		if (disp->layers[i].enabled)
+			return true;
+
+	return false;
+}
+
+/**
+ * zynqmp_disp_layer_destroy - Destroy all layers
+ * @disp: Display subsystem
+ *
+ * Destroy all layers.
+ */
+static void zynqmp_disp_layer_destroy(struct zynqmp_disp *disp)
+{
+	unsigned int i;
+
+	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
+		zynqmp_disp_layer_release_dma(disp, &disp->layers[i]);
+		if (disp->layers[i].of_node)
+			of_node_put(disp->layers[i].of_node);
+	}
+}
+
+/**
+ * zynqmp_disp_layer_create - Create all layers
+ * @disp: Display subsystem
+ *
+ * Create all layers.
+ *
+ * Return: 0 on success, otherwise error code from failed function
+ */
+static int zynqmp_disp_layer_create(struct zynqmp_disp *disp)
+{
+	struct zynqmp_disp_layer *layer;
+	unsigned int i;
+	int num_chans[ZYNQMP_DISP_NUM_LAYERS] = { 3, 1 };
+	const char * const dma_name[] = { "vid", "gfx" };
+	int ret;
+
+	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
+		char temp[16];
+
+		layer = &disp->layers[i];
+		layer->id = i;
+		layer->offset = i * 4;
+		layer->other = &disp->layers[!i];
+		layer->num_chan = num_chans[i];
+		snprintf(temp, sizeof(temp), "%s-layer", dma_name[i]);
+		layer->of_node = of_get_child_by_name(disp->dev->of_node, temp);
+		if (!layer->of_node)
+			goto err;
+		ret = zynqmp_disp_layer_request_dma(disp, layer, dma_name[i]);
+		if (ret)
+			goto err;
+		layer->disp = disp;
+	}
+
+	return 0;
+
+err:
+	zynqmp_disp_layer_destroy(disp);
+	return ret;
+}
+
+/*
+ * ZynqMP Display internal functions
+ */
+
+/*
+ * Output format enumeration for DRM property.
+ * The ID should be aligned with blend_output_fmts.
+ * The string should be aligned with how zynqmp_dp_set_color() decodes.
+ */
+static struct drm_prop_enum_list zynqmp_disp_color_enum[] = {
+	{ 0, "rgb" },
+	{ 1, "ycrcb444" },
+	{ 2, "ycrcb422" },
+	{ 3, "yonly" },
+};
+
+/**
+ * zynqmp_disp_set_output_fmt - Set the output format
+ * @disp: Display subsystem
+ * @id: the format ID. Refer to zynqmp_disp_color_enum[].
+ *
+ * This function sets the output format of the display / blender as well as
+ * the format of DP controller. The @id should be aligned with
+ * zynqmp_disp_color_enum, thus function needs to be used for DRM property.
+ */
+static void
+zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int id)
+{
+	const struct zynqmp_disp_fmt *fmt = &blend_output_fmts[id];
+
+	zynqmp_dp_set_color(disp->dpsub->dp, zynqmp_disp_color_enum[id].name);
+	zynqmp_disp_blend_set_output_fmt(&disp->blend, fmt->disp_fmt);
+}
+
+/**
+ * zynqmp_disp_set_bg_color - Set the background color
+ * @disp: Display subsystem
+ * @c0: color component 0
+ * @c1: color component 1
+ * @c2: color component 2
+ *
+ * Set the background color with given color components (@c0, @c1, @c2).
+ */
+static void zynqmp_disp_set_bg_color(struct zynqmp_disp *disp,
+				     u32 c0, u32 c1, u32 c2)
+{
+	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
+}
+
+/**
+ * zynqmp_disp_set_alpha - Set the alpha value
+ * @disp: Display subsystem
+ * @alpha: alpha value to set
+ *
+ * Set the alpha value for blending.
+ */
+static void zynqmp_disp_set_alpha(struct zynqmp_disp *disp, u32 alpha)
+{
+	disp->alpha = alpha;
+	zynqmp_disp_blend_set_alpha(&disp->blend, alpha);
+}
+
+/**
+ * zynqmp_disp_get_alpha - Get the alpha value
+ * @disp: Display subsystem
+ *
+ * Get the alpha value for blending.
+ *
+ * Return: current alpha value.
+ */
+static u32 zynqmp_disp_get_alpha(struct zynqmp_disp *disp)
+{
+	return disp->alpha;
+}
+
+/**
+ * zynqmp_disp_set_g_alpha - Enable/disable the global alpha blending
+ * @disp: Display subsystem
+ * @enable: flag to enable or disable alpha blending
+ *
+ * Set the alpha value for blending.
+ */
+static void zynqmp_disp_set_g_alpha(struct zynqmp_disp *disp, bool enable)
+{
+	disp->alpha_en = enable;
+	zynqmp_disp_blend_enable_alpha(&disp->blend, enable);
+}
+
+/**
+ * zynqmp_disp_get_g_alpha - Get the global alpha status
+ * @disp: Display subsystem
+ *
+ * Get the global alpha statue.
+ *
+ * Return: true if global alpha is enabled, or false.
+ */
+static bool zynqmp_disp_get_g_alpha(struct zynqmp_disp *disp)
+{
+	return disp->alpha_en;
+}
+
+/**
+ * zynqmp_disp_enable - Enable the Display subsystem
+ * @disp: Display subsystem
+ *
+ * Enable the Display subsystem.
+ */
+static void zynqmp_disp_enable(struct zynqmp_disp *disp)
+{
+	bool live;
+
+	if (disp->enabled)
+		return;
+
+	zynqmp_disp_av_buf_enable(&disp->av_buf);
+	/* Choose clock source based on the DT clock handle */
+	zynqmp_disp_av_buf_set_vid_clock_src(&disp->av_buf, !!disp->_ps_pclk);
+	zynqmp_disp_av_buf_set_aud_clock_src(&disp->av_buf, !!disp->_ps_audclk);
+	live = zynqmp_disp_layer_is_live(disp);
+	zynqmp_disp_av_buf_set_vid_timing_src(&disp->av_buf, !live);
+	zynqmp_disp_av_buf_enable_buf(&disp->av_buf);
+	zynqmp_disp_av_buf_enable_aud(&disp->av_buf);
+	zynqmp_disp_aud_init(&disp->aud);
+	disp->enabled = true;
+}
+
+/**
+ * zynqmp_disp_disable - Disable the Display subsystem
+ * @disp: Display subsystem
+ * @force: flag to disable forcefully
+ *
+ * Disable the Display subsystem.
+ */
+static void zynqmp_disp_disable(struct zynqmp_disp *disp, bool force)
+{
+	struct drm_crtc *crtc = &disp->xlnx_crtc.crtc;
+
+	if (!force && (!disp->enabled || zynqmp_disp_layer_is_enabled(disp)))
+		return;
+
+	zynqmp_disp_aud_deinit(&disp->aud);
+	zynqmp_disp_av_buf_disable_aud(&disp->av_buf);
+	zynqmp_disp_av_buf_disable_buf(&disp->av_buf);
+	zynqmp_disp_av_buf_disable(&disp->av_buf);
+
+	/* Mark the flip is done as crtc is disabled anyway */
+	if (crtc->state->event) {
+		complete_all(crtc->state->event->base.completion);
+		crtc->state->event = NULL;
+	}
+
+	disp->enabled = false;
+}
+
+/*
+ * ZynqMP Display external functions for zynqmp_dp
+ */
+
+/**
+ * zynqmp_disp_handle_vblank - Handle the vblank event
+ * @disp: Display subsystem
+ *
+ * This function handles the vblank interrupt, and sends an event to
+ * CRTC object. This will be called by the DP vblank interrupt handler.
+ */
+void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp)
+{
+	struct drm_crtc *crtc = &disp->xlnx_crtc.crtc;
+	struct drm_device *drm = crtc->dev;
+	struct drm_pending_vblank_event *event;
+	unsigned long flags;
+
+	drm_crtc_handle_vblank(crtc);
+
+	/* Finish page flip */
+	spin_lock_irqsave(&drm->event_lock, flags);
+	event = disp->event;
+	disp->event = NULL;
+	if (event) {
+		drm_crtc_send_vblank_event(crtc, event);
+		drm_crtc_vblank_put(crtc);
+	}
+	spin_unlock_irqrestore(&drm->event_lock, flags);
+}
+
+/**
+ * zynqmp_disp_get_apb_clk_rate - Get the current APB clock rate
+ * @disp: Display subsystem
+ *
+ * Return: the current APB clock rate.
+ */
+unsigned int zynqmp_disp_get_apb_clk_rate(struct zynqmp_disp *disp)
+{
+	return clk_get_rate(disp->aclk);
+}
+
+/**
+ * zynqmp_disp_aud_enabled - If the audio is enabled
+ * @disp: Display subsystem
+ *
+ * Return if the audio is enabled depending on the audio clock.
+ *
+ * Return: true if audio is enabled, or false.
+ */
+bool zynqmp_disp_aud_enabled(struct zynqmp_disp *disp)
+{
+	return !!disp->audclk;
+}
+
+/**
+ * zynqmp_disp_get_aud_clk_rate - Get the current audio clock rate
+ * @disp: Display subsystem
+ *
+ * Return: the current audio clock rate.
+ */
+unsigned int zynqmp_disp_get_aud_clk_rate(struct zynqmp_disp *disp)
+{
+	if (zynqmp_disp_aud_enabled(disp))
+		return 0;
+	return clk_get_rate(disp->aclk);
+}
+
+/**
+ * zynqmp_disp_get_crtc_mask - Return the CRTC bit mask
+ * @disp: Display subsystem
+ *
+ * Return: the crtc mask of the zyqnmp_disp CRTC.
+ */
+uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp)
+{
+	return drm_crtc_mask(&disp->xlnx_crtc.crtc);
+}
+
+/*
+ * DRM property functions
+ */
+
+static void zynqmp_disp_attach_vid_plane_property(struct zynqmp_disp *disp,
+						  struct drm_mode_object *obj)
+{
+	drm_object_attach_property(obj, disp->tpg_prop, false);
+}
+
+static void zynqmp_disp_attach_gfx_plane_property(struct zynqmp_disp *disp,
+						  struct drm_mode_object *obj)
+{
+	drm_object_attach_property(obj, disp->g_alpha_prop,
+				   ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX);
+	disp->alpha = ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX;
+	/* Enable the global alpha as default */
+	drm_object_attach_property(obj, disp->g_alpha_en_prop, true);
+	disp->alpha_en = true;
+}
+
+static void zynqmp_disp_attach_crtc_property(struct zynqmp_disp *disp,
+					     struct drm_mode_object *obj)
+{
+	drm_object_attach_property(obj, disp->color_prop, 0);
+	zynqmp_dp_set_color(disp->dpsub->dp, zynqmp_disp_color_enum[0].name);
+	drm_object_attach_property(obj, disp->bg_c0_prop, 0);
+	drm_object_attach_property(obj, disp->bg_c1_prop, 0);
+	drm_object_attach_property(obj, disp->bg_c2_prop, 0);
+}
+
+static void zynqmp_disp_create_property(struct zynqmp_disp *disp)
+{
+	int num;
+	u64 max;
+
+	max = ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX;
+	disp->g_alpha_prop = drm_property_create_range(disp->drm, 0, "alpha", 0,
+						       max);
+	disp->g_alpha_en_prop = drm_property_create_bool(disp->drm, 0,
+							 "g_alpha_en");
+	num = ARRAY_SIZE(zynqmp_disp_color_enum);
+	disp->color_prop = drm_property_create_enum(disp->drm, 0,
+						    "output_color",
+						    zynqmp_disp_color_enum,
+						    num);
+	max = ZYNQMP_DISP_V_BLEND_BG_MAX;
+	disp->bg_c0_prop = drm_property_create_range(disp->drm, 0, "bg_c0", 0,
+						     max);
+	disp->bg_c1_prop = drm_property_create_range(disp->drm, 0, "bg_c1", 0,
+						     max);
+	disp->bg_c2_prop = drm_property_create_range(disp->drm, 0, "bg_c2", 0,
+						     max);
+	disp->tpg_prop = drm_property_create_bool(disp->drm, 0, "tpg");
+}
+
+static void zynqmp_disp_destroy_property(struct zynqmp_disp *disp)
+{
+	drm_property_destroy(disp->drm, disp->bg_c2_prop);
+	drm_property_destroy(disp->drm, disp->bg_c1_prop);
+	drm_property_destroy(disp->drm, disp->bg_c0_prop);
+	drm_property_destroy(disp->drm, disp->color_prop);
+	drm_property_destroy(disp->drm, disp->g_alpha_en_prop);
+	drm_property_destroy(disp->drm, disp->g_alpha_prop);
+}
+
+/*
+ * DRM plane functions
+ */
+
+static inline struct zynqmp_disp_layer *plane_to_layer(struct drm_plane *plane)
+{
+	return container_of(plane, struct zynqmp_disp_layer, plane);
+}
+
+static int zynqmp_disp_plane_enable(struct drm_plane *plane)
+{
+	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
+	struct zynqmp_disp *disp = layer->disp;
+	int ret;
+
+	zynqmp_disp_set_g_alpha(disp, disp->alpha_en);
+	zynqmp_disp_set_alpha(disp, disp->alpha);
+	ret = zynqmp_disp_layer_enable(layer->disp, layer,
+				       ZYNQMP_DISP_LAYER_NONLIVE);
+	if (ret)
+		return ret;
+
+	if (layer->id == ZYNQMP_DISP_LAYER_GFX && disp->tpg_on) {
+		layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
+		zynqmp_disp_layer_set_tpg(disp, layer, disp->tpg_on);
+	}
+
+	return 0;
+}
+
+static int zynqmp_disp_plane_disable(struct drm_plane *plane)
+{
+	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
+	struct zynqmp_disp *disp = layer->disp;
+
+	zynqmp_disp_layer_disable(disp, layer, ZYNQMP_DISP_LAYER_NONLIVE);
+	if (layer->id == ZYNQMP_DISP_LAYER_VID && disp->tpg_on)
+		zynqmp_disp_layer_set_tpg(disp, layer, disp->tpg_on);
+
+	return 0;
+}
+
+static int zynqmp_disp_plane_mode_set(struct drm_plane *plane,
+				      struct drm_framebuffer *fb,
+				      int crtc_x, int crtc_y,
+				      unsigned int crtc_w, unsigned int crtc_h,
+				      u32 src_x, u32 src_y,
+				      u32 src_w, u32 src_h)
+{
+	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
+	const struct drm_format_info *info = fb->format;
+	struct drm_format_name_buf format_name;
+	struct device *dev = layer->disp->dev;
+	dma_addr_t paddr;
+	size_t offset;
+	unsigned int i;
+	int ret;
+
+	if (!info) {
+		dev_err(dev, "unsupported framebuffer format %s\n",
+			drm_get_format_name(info->format, &format_name));
+		return -EINVAL;
+	}
+
+	ret = zynqmp_disp_layer_check_size(layer->disp, layer, src_w, src_h);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < info->num_planes; i++) {
+		unsigned int width = src_w / (i ? info->hsub : 1);
+		unsigned int height = src_h / (i ? info->vsub : 1);
+
+		paddr = xlnx_fb_get_paddr(fb, i);
+		if (!paddr) {
+			dev_err(dev, "failed to get a paddr\n");
+			return -EINVAL;
+		}
+
+		layer->dma[i].xt.numf = height;
+		layer->dma[i].sgl[0].size = width * info->cpp[i];
+		layer->dma[i].sgl[0].icg = fb->pitches[i] -
+					   layer->dma[i].sgl[0].size;
+		offset = src_x * info->cpp[i] + src_y * fb->pitches[i];
+		offset += fb->offsets[i];
+		layer->dma[i].xt.src_start = paddr + offset;
+		layer->dma[i].xt.frame_size = 1;
+		layer->dma[i].xt.dir = DMA_MEM_TO_DEV;
+		layer->dma[i].xt.src_sgl = true;
+		layer->dma[i].xt.dst_sgl = false;
+		layer->dma[i].is_active = true;
+	}
+
+	for (; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++)
+		layer->dma[i].is_active = false;
+
+	ret = zynqmp_disp_layer_set_fmt(layer->disp,  layer, info->format);
+	if (ret)
+		dev_err(dev, "failed to set dp_sub layer fmt\n");
+
+	return ret;
+}
+
+static void zynqmp_disp_plane_destroy(struct drm_plane *plane)
+{
+	drm_plane_cleanup(plane);
+}
+
+static int
+zynqmp_disp_plane_atomic_set_property(struct drm_plane *plane,
+				      struct drm_plane_state *state,
+				      struct drm_property *property, u64 val)
+{
+	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
+	struct zynqmp_disp *disp = layer->disp;
+	int ret = 0;
+
+	if (property == disp->g_alpha_prop)
+		zynqmp_disp_set_alpha(disp, val);
+	else if (property == disp->g_alpha_en_prop)
+		zynqmp_disp_set_g_alpha(disp, val);
+	else if (property == disp->tpg_prop)
+		ret = zynqmp_disp_layer_set_tpg(disp, layer, val);
+	else
+		return -EINVAL;
+
+	return ret;
+}
+
+static int
+zynqmp_disp_plane_atomic_get_property(struct drm_plane *plane,
+				      const struct drm_plane_state *state,
+				      struct drm_property *property,
+				      uint64_t *val)
+{
+	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
+	struct zynqmp_disp *disp = layer->disp;
+	int ret = 0;
+
+	if (property == disp->g_alpha_prop)
+		*val = zynqmp_disp_get_alpha(disp);
+	else if (property == disp->g_alpha_en_prop)
+		*val = zynqmp_disp_get_g_alpha(disp);
+	else if (property == disp->tpg_prop)
+		*val = zynqmp_disp_layer_get_tpg(disp, layer);
+	else
+		return -EINVAL;
+
+	return ret;
+}
+
+static struct drm_plane_funcs zynqmp_disp_plane_funcs = {
+	.update_plane		= drm_atomic_helper_update_plane,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.atomic_set_property	= zynqmp_disp_plane_atomic_set_property,
+	.atomic_get_property	= zynqmp_disp_plane_atomic_get_property,
+	.destroy		= zynqmp_disp_plane_destroy,
+	.reset			= drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+};
+
+static int zynqmp_disp_plane_prepare_fb(struct drm_plane *plane,
+					struct drm_plane_state *new_state)
+{
+	return 0;
+}
+
+static void zynqmp_disp_plane_cleanup_fb(struct drm_plane *plane,
+					 struct drm_plane_state *old_state)
+{
+}
+
+static int zynqmp_disp_plane_atomic_check(struct drm_plane *plane,
+					  struct drm_plane_state *state)
+{
+	return 0;
+}
+
+static void
+zynqmp_disp_plane_atomic_update(struct drm_plane *plane,
+				struct drm_plane_state *old_state)
+{
+	int ret;
+
+	if (!plane->state->crtc || !plane->state->fb)
+		return;
+
+	ret = zynqmp_disp_plane_mode_set(plane, plane->state->fb,
+					 plane->state->crtc_x,
+					 plane->state->crtc_y,
+					 plane->state->crtc_w,
+					 plane->state->crtc_h,
+					 plane->state->src_x >> 16,
+					 plane->state->src_y >> 16,
+					 plane->state->src_w >> 16,
+					 plane->state->src_h >> 16);
+	if (ret)
+		return;
+
+	zynqmp_disp_plane_enable(plane);
+}
+
+static void
+zynqmp_disp_plane_atomic_disable(struct drm_plane *plane,
+				 struct drm_plane_state *old_state)
+{
+	zynqmp_disp_plane_disable(plane);
+}
+
+static const struct drm_plane_helper_funcs zynqmp_disp_plane_helper_funcs = {
+	.prepare_fb	= zynqmp_disp_plane_prepare_fb,
+	.cleanup_fb	= zynqmp_disp_plane_cleanup_fb,
+	.atomic_check	= zynqmp_disp_plane_atomic_check,
+	.atomic_update	= zynqmp_disp_plane_atomic_update,
+	.atomic_disable	= zynqmp_disp_plane_atomic_disable,
+};
+
+static int zynqmp_disp_create_plane(struct zynqmp_disp *disp)
+{
+	struct zynqmp_disp_layer *layer;
+	unsigned int i;
+	u32 *fmts = NULL;
+	unsigned int num_fmts = 0;
+	enum drm_plane_type type;
+	int ret;
+
+	/* graphics layer is primary, and video layer is overaly */
+	type = DRM_PLANE_TYPE_OVERLAY;
+	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
+		layer = &disp->layers[i];
+		zynqmp_disp_layer_get_fmts(disp, layer, &fmts, &num_fmts);
+		ret = drm_universal_plane_init(disp->drm, &layer->plane, 0,
+					       &zynqmp_disp_plane_funcs, fmts,
+					       num_fmts, NULL, type, NULL);
+		if (ret)
+			goto err_plane;
+		drm_plane_helper_add(&layer->plane,
+				     &zynqmp_disp_plane_helper_funcs);
+		type = DRM_PLANE_TYPE_PRIMARY;
+	}
+
+	/* Attach properties to each layers */
+	zynqmp_disp_attach_gfx_plane_property(disp, &layer->plane.base);
+	layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
+	zynqmp_disp_attach_vid_plane_property(disp, &layer->plane.base);
+
+	return ret;
+
+err_plane:
+	if (i)
+		drm_plane_cleanup(&disp->layers[0].plane);
+	return ret;
+}
+
+static void zynqmp_disp_destroy_plane(struct zynqmp_disp *disp)
+{
+	unsigned int i;
+
+	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
+		zynqmp_disp_plane_destroy(&disp->layers[i].plane);
+}
+
+/*
+ * Xlnx crtc functions
+ */
+
+static inline struct zynqmp_disp *xlnx_crtc_to_disp(struct xlnx_crtc *xlnx_crtc)
+{
+	return container_of(xlnx_crtc, struct zynqmp_disp, xlnx_crtc);
+}
+
+static int zynqmp_disp_enable_vblank(struct xlnx_crtc *xlnx_crtc)
+{
+	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
+
+	zynqmp_dp_enable_vblank(disp->dpsub->dp);
+	return 0;
+}
+
+static void zynqmp_disp_disable_vblank(struct xlnx_crtc *xlnx_crtc)
+{
+	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
+
+	zynqmp_dp_disable_vblank(disp->dpsub->dp);
+}
+
+static int zynqmp_disp_get_max_width(struct xlnx_crtc *xlnx_crtc)
+{
+	return ZYNQMP_DISP_MAX_WIDTH;
+}
+
+static int zynqmp_disp_get_max_height(struct xlnx_crtc *xlnx_crtc)
+{
+	return ZYNQMP_DISP_MAX_HEIGHT;
+}
+
+static uint32_t zynqmp_disp_get_format(struct xlnx_crtc *xlnx_crtc)
+{
+	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
+
+	return disp->layers[ZYNQMP_DISP_LAYER_GFX].fmt->drm_fmt;
+}
+
+static unsigned int zynqmp_disp_get_align(struct xlnx_crtc *xlnx_crtc)
+{
+	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
+	struct zynqmp_disp_layer *layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
+
+	return 1 << layer->dma->chan->device->copy_align;
+}
+
+static u64 zynqmp_disp_get_dma_mask(struct xlnx_crtc *xlnx_crtc)
+{
+	return DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT);
+}
+
+/*
+ * DRM crtc functions
+ */
+
+static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc)
+{
+	struct xlnx_crtc *xlnx_crtc = to_xlnx_crtc(crtc);
+
+	return xlnx_crtc_to_disp(xlnx_crtc);
+}
+
+static int zynqmp_disp_crtc_mode_set(struct drm_crtc *crtc,
+				     struct drm_display_mode *mode,
+				     struct drm_display_mode *adjusted_mode,
+				     int x, int y,
+				     struct drm_framebuffer *old_fb)
+{
+	struct zynqmp_disp *disp = crtc_to_disp(crtc);
+	unsigned long rate;
+	long diff;
+	int ret;
+
+	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
+	ret = clk_set_rate(disp->pclk, adjusted_mode->clock * 1000);
+	if (ret) {
+		dev_err(disp->dev, "failed to set a pixel clock\n");
+		return ret;
+	}
+
+	rate = clk_get_rate(disp->pclk);
+	diff = rate - adjusted_mode->clock * 1000;
+	if (abs(diff) > (adjusted_mode->clock * 1000) / 20) {
+		dev_info(disp->dev, "request pixel rate: %d actual rate: %lu\n",
+			 adjusted_mode->clock, rate);
+	} else {
+		dev_dbg(disp->dev, "request pixel rate: %d actual rate: %lu\n",
+			adjusted_mode->clock, rate);
+	}
+
+	/* The timing register should be programmed always */
+	zynqmp_dp_encoder_mode_set_stream(disp->dpsub->dp, adjusted_mode);
+
+	return 0;
+}
+
+static void
+zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc,
+			       struct drm_crtc_state *old_crtc_state)
+{
+	struct zynqmp_disp *disp = crtc_to_disp(crtc);
+	struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode;
+	int ret, vrefresh;
+
+	zynqmp_disp_crtc_mode_set(crtc, &crtc->state->mode,
+				  adjusted_mode, crtc->x, crtc->y, NULL);
+
+	pm_runtime_get_sync(disp->dev);
+	ret = zynqmp_disp_clk_enable(disp->pclk, &disp->pclk_en);
+	if (ret) {
+		dev_err(disp->dev, "failed to enable a pixel clock\n");
+		return;
+	}
+	zynqmp_disp_set_output_fmt(disp, disp->color);
+	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp->bg_c2);
+	zynqmp_disp_enable(disp);
+	/* Delay of 3 vblank intervals for timing gen to be stable */
+	vrefresh = (adjusted_mode->clock * 1000) /
+		   (adjusted_mode->vtotal * adjusted_mode->htotal);
+	msleep(3 * 1000 / vrefresh);
+}
+
+static void
+zynqmp_disp_crtc_atomic_disable(struct drm_crtc *crtc,
+				struct drm_crtc_state *old_crtc_state)
+{
+	struct zynqmp_disp *disp = crtc_to_disp(crtc);
+
+	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
+	zynqmp_disp_plane_disable(crtc->primary);
+	zynqmp_disp_disable(disp, true);
+	pm_runtime_put_sync(disp->dev);
+}
+
+static void zynqmp_disp_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+}
+
+static int zynqmp_disp_crtc_atomic_check(struct drm_crtc *crtc,
+					 struct drm_crtc_state *state)
+{
+	return drm_atomic_add_affected_planes(state->state, crtc);
+}
+
+static void
+zynqmp_disp_crtc_atomic_begin(struct drm_crtc *crtc,
+			      struct drm_crtc_state *old_crtc_state)
+{
+	/* Don't rely on vblank when disabling crtc */
+	if (crtc->primary->state->fb && crtc->state->event) {
+		struct zynqmp_disp *disp = crtc_to_disp(crtc);
+
+		/* Consume the flip_done event from atomic helper */
+		crtc->state->event->pipe = drm_crtc_index(crtc);
+		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+		disp->event = crtc->state->event;
+		crtc->state->event = NULL;
+	}
+}
+
+static struct drm_crtc_helper_funcs zynqmp_disp_crtc_helper_funcs = {
+	.atomic_enable	= zynqmp_disp_crtc_atomic_enable,
+	.atomic_disable	= zynqmp_disp_crtc_atomic_disable,
+	.mode_set_nofb	= zynqmp_disp_crtc_mode_set_nofb,
+	.atomic_check	= zynqmp_disp_crtc_atomic_check,
+	.atomic_begin	= zynqmp_disp_crtc_atomic_begin,
+};
+
+static void zynqmp_disp_crtc_destroy(struct drm_crtc *crtc)
+{
+	zynqmp_disp_crtc_atomic_disable(crtc, NULL);
+	drm_crtc_cleanup(crtc);
+}
+
+static int
+zynqmp_disp_crtc_atomic_set_property(struct drm_crtc *crtc,
+				     struct drm_crtc_state *state,
+				     struct drm_property *property,
+				     uint64_t val)
+{
+	struct zynqmp_disp *disp = crtc_to_disp(crtc);
+
+	/*
+	 * CRTC prop values are just stored here and applied when CRTC gets
+	 * enabled
+	 */
+	if (property == disp->color_prop)
+		disp->color = val;
+	else if (property == disp->bg_c0_prop)
+		disp->bg_c0 = val;
+	else if (property == disp->bg_c1_prop)
+		disp->bg_c1 = val;
+	else if (property == disp->bg_c2_prop)
+		disp->bg_c2 = val;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+zynqmp_disp_crtc_atomic_get_property(struct drm_crtc *crtc,
+				     const struct drm_crtc_state *state,
+				     struct drm_property *property,
+				     uint64_t *val)
+{
+	struct zynqmp_disp *disp = crtc_to_disp(crtc);
+
+	if (property == disp->color_prop)
+		*val = disp->color;
+	else if (property == disp->bg_c0_prop)
+		*val = disp->bg_c0;
+	else if (property == disp->bg_c1_prop)
+		*val = disp->bg_c1;
+	else if (property == disp->bg_c2_prop)
+		*val = disp->bg_c2;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static struct drm_crtc_funcs zynqmp_disp_crtc_funcs = {
+	.destroy		= zynqmp_disp_crtc_destroy,
+	.set_config		= drm_atomic_helper_set_config,
+	.page_flip		= drm_atomic_helper_page_flip,
+	.atomic_set_property	= zynqmp_disp_crtc_atomic_set_property,
+	.atomic_get_property	= zynqmp_disp_crtc_atomic_get_property,
+	.reset			= drm_atomic_helper_crtc_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
+};
+
+static void zynqmp_disp_create_crtc(struct zynqmp_disp *disp)
+{
+	struct drm_plane *plane = &disp->layers[ZYNQMP_DISP_LAYER_GFX].plane;
+	int ret;
+
+	ret = drm_crtc_init_with_planes(disp->drm, &disp->xlnx_crtc.crtc, plane,
+					NULL, &zynqmp_disp_crtc_funcs, NULL);
+	drm_crtc_helper_add(&disp->xlnx_crtc.crtc,
+			    &zynqmp_disp_crtc_helper_funcs);
+	zynqmp_disp_attach_crtc_property(disp, &disp->xlnx_crtc.crtc.base);
+
+	disp->xlnx_crtc.enable_vblank = &zynqmp_disp_enable_vblank;
+	disp->xlnx_crtc.disable_vblank = &zynqmp_disp_disable_vblank;
+	disp->xlnx_crtc.get_max_width = &zynqmp_disp_get_max_width;
+	disp->xlnx_crtc.get_max_height = &zynqmp_disp_get_max_height;
+	disp->xlnx_crtc.get_format = &zynqmp_disp_get_format;
+	disp->xlnx_crtc.get_align = &zynqmp_disp_get_align;
+	disp->xlnx_crtc.get_dma_mask = &zynqmp_disp_get_dma_mask;
+	xlnx_crtc_register(disp->drm, &disp->xlnx_crtc);
+}
+
+static void zynqmp_disp_destroy_crtc(struct zynqmp_disp *disp)
+{
+	xlnx_crtc_unregister(disp->drm, &disp->xlnx_crtc);
+	zynqmp_disp_crtc_destroy(&disp->xlnx_crtc.crtc);
+}
+
+static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp)
+{
+	u32 possible_crtcs = drm_crtc_mask(&disp->xlnx_crtc.crtc);
+	unsigned int i;
+
+	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
+		disp->layers[i].plane.possible_crtcs = possible_crtcs;
+}
+
+/*
+ * Component functions
+ */
+
+int zynqmp_disp_bind(struct device *dev, struct device *master, void *data)
+{
+	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
+	struct zynqmp_disp *disp = dpsub->disp;
+	struct drm_device *drm = data;
+	int ret;
+
+	disp->drm = drm;
+	zynqmp_disp_create_property(disp);
+	ret = zynqmp_disp_create_plane(disp);
+	if (ret)
+		return ret;
+	zynqmp_disp_create_crtc(disp);
+	zynqmp_disp_map_crtc_to_plane(disp);
+
+	return 0;
+}
+
+void zynqmp_disp_unbind(struct device *dev, struct device *master, void *data)
+{
+	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
+	struct zynqmp_disp *disp = dpsub->disp;
+
+	zynqmp_disp_destroy_crtc(disp);
+	zynqmp_disp_destroy_plane(disp);
+	zynqmp_disp_destroy_property(disp);
+}
+
+/*
+ * Platform initialization functions
+ */
+
+static int zynqmp_disp_enumerate_fmts(struct zynqmp_disp *disp)
+{
+	struct zynqmp_disp_layer *layer;
+	u32 *bus_fmts;
+	u32 i, size, num_bus_fmts;
+
+	num_bus_fmts = ARRAY_SIZE(av_buf_live_fmts);
+	bus_fmts = devm_kzalloc(disp->dev, sizeof(*bus_fmts) * num_bus_fmts,
+				GFP_KERNEL);
+	if (!bus_fmts)
+		return -ENOMEM;
+	for (i = 0; i < num_bus_fmts; i++)
+		bus_fmts[i] = av_buf_live_fmts[i].bus_fmt;
+
+	layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
+	layer->num_bus_fmts = num_bus_fmts;
+	layer->bus_fmts = bus_fmts;
+	size = ARRAY_SIZE(av_buf_vid_fmts);
+	layer->num_fmts = size;
+	layer->drm_fmts = devm_kzalloc(disp->dev,
+				       sizeof(*layer->drm_fmts) * size,
+				       GFP_KERNEL);
+	if (!layer->drm_fmts)
+		return -ENOMEM;
+	for (i = 0; i < layer->num_fmts; i++)
+		layer->drm_fmts[i] = av_buf_vid_fmts[i].drm_fmt;
+	layer->fmt = &av_buf_vid_fmts[ZYNQMP_DISP_AV_BUF_VID_FMT_YUYV];
+
+	layer = &disp->layers[ZYNQMP_DISP_LAYER_GFX];
+	layer->num_bus_fmts = num_bus_fmts;
+	layer->bus_fmts = bus_fmts;
+	size = ARRAY_SIZE(av_buf_gfx_fmts);
+	layer->num_fmts = size;
+	layer->drm_fmts = devm_kzalloc(disp->dev,
+				       sizeof(*layer->drm_fmts) * size,
+				       GFP_KERNEL);
+	if (!layer->drm_fmts)
+		return -ENOMEM;
+
+	for (i = 0; i < layer->num_fmts; i++)
+		layer->drm_fmts[i] = av_buf_gfx_fmts[i].drm_fmt;
+	layer->fmt = &av_buf_gfx_fmts[ZYNQMP_DISP_AV_BUF_GFX_FMT_RGB565];
+
+	return 0;
+}
+
+int zynqmp_disp_probe(struct platform_device *pdev)
+{
+	struct zynqmp_dpsub *dpsub;
+	struct zynqmp_disp *disp;
+	struct resource *res;
+	int ret;
+
+	disp = devm_kzalloc(&pdev->dev, sizeof(*disp), GFP_KERNEL);
+	if (!disp)
+		return -ENOMEM;
+	disp->dev = &pdev->dev;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "blend");
+	disp->blend.base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(disp->blend.base))
+		return PTR_ERR(disp->blend.base);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "av_buf");
+	disp->av_buf.base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(disp->av_buf.base))
+		return PTR_ERR(disp->av_buf.base);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aud");
+	disp->aud.base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(disp->aud.base))
+		return PTR_ERR(disp->aud.base);
+
+	dpsub = platform_get_drvdata(pdev);
+	dpsub->disp = disp;
+	disp->dpsub = dpsub;
+
+	ret = zynqmp_disp_enumerate_fmts(disp);
+	if (ret)
+		return ret;
+
+	/* Try the live PL video clock */
+	disp->_pl_pclk = devm_clk_get(disp->dev, "dp_live_video_in_clk");
+	if (!IS_ERR(disp->_pl_pclk)) {
+		disp->pclk = disp->_pl_pclk;
+		ret = zynqmp_disp_clk_enable_disable(disp->pclk,
+						     &disp->pclk_en);
+		if (ret)
+			disp->pclk = NULL;
+	} else if (PTR_ERR(disp->_pl_pclk) == -EPROBE_DEFER) {
+		return PTR_ERR(disp->_pl_pclk);
+	}
+
+	/* If the live PL video clock is not valid, fall back to PS clock */
+	if (!disp->pclk) {
+		disp->_ps_pclk = devm_clk_get(disp->dev, "dp_vtc_pixel_clk_in");
+		if (IS_ERR(disp->_ps_pclk)) {
+			dev_err(disp->dev, "failed to init any video clock\n");
+			return PTR_ERR(disp->_ps_pclk);
+		}
+		disp->pclk = disp->_ps_pclk;
+		ret = zynqmp_disp_clk_enable_disable(disp->pclk,
+						     &disp->pclk_en);
+		if (ret) {
+			dev_err(disp->dev, "failed to init any video clock\n");
+			return ret;
+		}
+	}
+
+	disp->aclk = devm_clk_get(disp->dev, "dp_apb_clk");
+	if (IS_ERR(disp->aclk))
+		return PTR_ERR(disp->aclk);
+	ret = zynqmp_disp_clk_enable(disp->aclk, &disp->aclk_en);
+	if (ret) {
+		dev_err(disp->dev, "failed to enable the APB clk\n");
+		return ret;
+	}
+
+	/* Try the live PL audio clock */
+	disp->_pl_audclk = devm_clk_get(disp->dev, "dp_live_audio_aclk");
+	if (!IS_ERR(disp->_pl_audclk)) {
+		disp->audclk = disp->_pl_audclk;
+		ret = zynqmp_disp_clk_enable_disable(disp->audclk,
+						     &disp->audclk_en);
+		if (ret)
+			disp->audclk = NULL;
+	}
+
+	/* If the live PL audio clock is not valid, fall back to PS clock */
+	if (!disp->audclk) {
+		disp->_ps_audclk = devm_clk_get(disp->dev, "dp_aud_clk");
+		if (!IS_ERR(disp->_ps_audclk)) {
+			disp->audclk = disp->_ps_audclk;
+			ret = zynqmp_disp_clk_enable_disable(disp->audclk,
+							     &disp->audclk_en);
+			if (ret)
+				disp->audclk = NULL;
+		}
+
+		if (!disp->audclk) {
+			dev_err(disp->dev,
+				"audio is disabled due to clock failure\n");
+		}
+	}
+
+	ret = zynqmp_disp_layer_create(disp);
+	if (ret)
+		goto error_aclk;
+
+	return 0;
+
+error_aclk:
+	zynqmp_disp_clk_disable(disp->aclk, &disp->aclk_en);
+	return ret;
+}
+
+int zynqmp_disp_remove(struct platform_device *pdev)
+{
+	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
+	struct zynqmp_disp *disp = dpsub->disp;
+
+	zynqmp_disp_layer_destroy(disp);
+	if (disp->audclk)
+		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
+	zynqmp_disp_clk_disable(disp->aclk, &disp->aclk_en);
+	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
+	dpsub->disp = NULL;
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h b/drivers/gpu/drm/xlnx/zynqmp_disp.h
new file mode 100644
index 0000000..0291fc2
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h
@@ -0,0 +1,28 @@
+/*
+ * ZynqMP Display Driver
+ *
+ *  Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _ZYNQMP_DISP_H_
+#define _ZYNQMP_DISP_H_
+
+struct zynqmp_disp;
+
+void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp);
+unsigned int zynqmp_disp_get_apb_clk_rate(struct zynqmp_disp *disp);
+bool zynqmp_disp_aud_enabled(struct zynqmp_disp *disp);
+unsigned int zynqmp_disp_get_aud_clk_rate(struct zynqmp_disp *disp);
+uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp);
+
+int zynqmp_disp_bind(struct device *dev, struct device *master, void *data);
+void zynqmp_disp_unbind(struct device *dev, struct device *master, void *data);
+
+int zynqmp_disp_probe(struct platform_device *pdev);
+int zynqmp_disp_remove(struct platform_device *pdev);
+
+#endif /* _ZYNQMP_DISP_H_ */
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 08/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DisplayPort
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
                     ` (6 preceding siblings ...)
  2018-01-05  2:05   ` [PATCH 07/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
  2018-01-05  2:05   ` [PATCH 09/10] drm: xlnx: ZynqMP DP subsystem DRM KMS driver Hyun Kwon
                     ` (2 subsequent siblings)
  10 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

This driver creates DRM encoder and connector for ZynqMP DisplayPort.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 drivers/gpu/drm/xlnx/zynqmp_dp.c | 1864 ++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/zynqmp_dp.h |   29 +
 2 files changed, 1893 insertions(+)
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.c
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.h

diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
new file mode 100644
index 0000000..ce3c7c5
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -0,0 +1,1864 @@
+/*
+ * ZynqMP DisplayPort Driver
+ *
+ *  Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_of.h>
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/uaccess.h>
+
+#include "zynqmp_disp.h"
+#include "zynqmp_dpsub.h"
+
+static uint zynqmp_dp_aux_timeout_ms = 50;
+module_param_named(aux_timeout_ms, zynqmp_dp_aux_timeout_ms, uint, 0444);
+MODULE_PARM_DESC(aux_timeout_ms, "DP aux timeout value in msec (default: 50)");
+
+/*
+ * Some sink requires a delay after power on request
+ */
+static uint zynqmp_dp_power_on_delay_ms = 4;
+module_param_named(power_on_delay_ms, zynqmp_dp_power_on_delay_ms, uint, 0444);
+MODULE_PARM_DESC(aux_timeout_ms, "DP power on delay in msec (default: 4)");
+
+/* Link configuration registers */
+#define ZYNQMP_DP_TX_LINK_BW_SET			0x0
+#define ZYNQMP_DP_TX_LANE_CNT_SET			0x4
+#define ZYNQMP_DP_TX_ENHANCED_FRAME_EN			0x8
+#define ZYNQMP_DP_TX_TRAINING_PATTERN_SET		0xc
+#define ZYNQMP_DP_TX_SCRAMBLING_DISABLE			0x14
+#define ZYNQMP_DP_TX_DOWNSPREAD_CTL			0x18
+#define ZYNQMP_DP_TX_SW_RESET				0x1c
+#define ZYNQMP_DP_TX_SW_RESET_STREAM1			BIT(0)
+#define ZYNQMP_DP_TX_SW_RESET_STREAM2			BIT(1)
+#define ZYNQMP_DP_TX_SW_RESET_STREAM3			BIT(2)
+#define ZYNQMP_DP_TX_SW_RESET_STREAM4			BIT(3)
+#define ZYNQMP_DP_TX_SW_RESET_AUX			BIT(7)
+#define ZYNQMP_DP_TX_SW_RESET_ALL			(ZYNQMP_DP_TX_SW_RESET_STREAM1 | \
+							 ZYNQMP_DP_TX_SW_RESET_STREAM2 | \
+							 ZYNQMP_DP_TX_SW_RESET_STREAM3 | \
+							 ZYNQMP_DP_TX_SW_RESET_STREAM4 | \
+							 ZYNQMP_DP_TX_SW_RESET_AUX)
+
+/* Core enable registers */
+#define ZYNQMP_DP_TX_ENABLE				0x80
+#define ZYNQMP_DP_TX_ENABLE_MAIN_STREAM			0x84
+#define ZYNQMP_DP_TX_FORCE_SCRAMBLER_RESET		0xc0
+#define ZYNQMP_DP_TX_VERSION				0xf8
+#define ZYNQMP_DP_TX_VERSION_MAJOR_MASK			GENMASK(31, 24)
+#define ZYNQMP_DP_TX_VERSION_MAJOR_SHIFT		24
+#define ZYNQMP_DP_TX_VERSION_MINOR_MASK			GENMASK(23, 16)
+#define ZYNQMP_DP_TX_VERSION_MINOR_SHIFT		16
+#define ZYNQMP_DP_TX_VERSION_REVISION_MASK		GENMASK(15, 12)
+#define ZYNQMP_DP_TX_VERSION_REVISION_SHIFT		12
+#define ZYNQMP_DP_TX_VERSION_PATCH_MASK			GENMASK(11, 8)
+#define ZYNQMP_DP_TX_VERSION_PATCH_SHIFT		8
+#define ZYNQMP_DP_TX_VERSION_INTERNAL_MASK		GENMASK(7, 0)
+#define ZYNQMP_DP_TX_VERSION_INTERNAL_SHIFT		0
+
+/* Core ID registers */
+#define ZYNQMP_DP_TX_CORE_ID				0xfc
+#define ZYNQMP_DP_TX_CORE_ID_MAJOR_MASK			GENMASK(31, 24)
+#define ZYNQMP_DP_TX_CORE_ID_MAJOR_SHIFT		24
+#define ZYNQMP_DP_TX_CORE_ID_MINOR_MASK			GENMASK(23, 16)
+#define ZYNQMP_DP_TX_CORE_ID_MINOR_SHIFT		16
+#define ZYNQMP_DP_TX_CORE_ID_REVISION_MASK		GENMASK(15, 8)
+#define ZYNQMP_DP_TX_CORE_ID_REVISION_SHIFT		8
+#define ZYNQMP_DP_TX_CORE_ID_DIRECTION			GENMASK(1)
+
+/* AUX channel interface registers */
+#define ZYNQMP_DP_TX_AUX_COMMAND			0x100
+#define ZYNQMP_DP_TX_AUX_COMMAND_CMD_SHIFT		8
+#define ZYNQMP_DP_TX_AUX_COMMAND_ADDRESS_ONLY		BIT(12)
+#define ZYNQMP_DP_TX_AUX_COMMAND_BYTES_SHIFT		0
+#define ZYNQMP_DP_TX_AUX_WRITE_FIFO			0x104
+#define ZYNQMP_DP_TX_AUX_ADDRESS			0x108
+#define ZYNQMP_DP_TX_CLK_DIVIDER			0x10c
+#define ZYNQMP_DP_TX_CLK_DIVIDER_MHZ			1000000
+#define ZYNQMP_DP_TX_CLK_DIVIDER_AUX_FILTER_SHIFT	8
+#define ZYNQMP_DP_TX_INTR_SIGNAL_STATE			0x130
+#define ZYNQMP_DP_TX_INTR_SIGNAL_STATE_HPD		BIT(0)
+#define ZYNQMP_DP_TX_INTR_SIGNAL_STATE_REQUEST		BIT(1)
+#define ZYNQMP_DP_TX_INTR_SIGNAL_STATE_REPLY		BIT(2)
+#define ZYNQMP_DP_TX_INTR_SIGNAL_STATE_REPLY_TIMEOUT	BIT(3)
+#define ZYNQMP_DP_TX_AUX_REPLY_DATA			0x134
+#define ZYNQMP_DP_TX_AUX_REPLY_CODE			0x138
+#define ZYNQMP_DP_TX_AUX_REPLY_CODE_AUX_ACK		(0)
+#define ZYNQMP_DP_TX_AUX_REPLY_CODE_AUX_NACK		BIT(0)
+#define ZYNQMP_DP_TX_AUX_REPLY_CODE_AUX_DEFER		BIT(1)
+#define ZYNQMP_DP_TX_AUX_REPLY_CODE_I2C_ACK		(0)
+#define ZYNQMP_DP_TX_AUX_REPLY_CODE_I2C_NACK		BIT(2)
+#define ZYNQMP_DP_TX_AUX_REPLY_CODE_I2C_DEFER		BIT(3)
+#define ZYNQMP_DP_TX_AUX_REPLY_CNT			0x13c
+#define ZYNQMP_DP_TX_AUX_REPLY_CNT_MASK			0xff
+#define ZYNQMP_DP_TX_INTR_STATUS			0x140
+#define ZYNQMP_DP_TX_INTR_MASK				0x144
+#define ZYNQMP_DP_TX_INTR_HPD_IRQ			BIT(0)
+#define ZYNQMP_DP_TX_INTR_HPD_EVENT			BIT(1)
+#define ZYNQMP_DP_TX_INTR_REPLY_RECV			BIT(2)
+#define ZYNQMP_DP_TX_INTR_REPLY_TIMEOUT			BIT(3)
+#define ZYNQMP_DP_TX_INTR_HPD_PULSE			BIT(4)
+#define ZYNQMP_DP_TX_INTR_EXT_PKT_TXD			BIT(5)
+#define ZYNQMP_DP_TX_INTR_LIV_ABUF_UNDRFLW		BIT(12)
+#define ZYNQMP_DP_TX_INTR_VBLANK_START			BIT(13)
+#define ZYNQMP_DP_TX_INTR_PIXEL0_MATCH			BIT(14)
+#define ZYNQMP_DP_TX_INTR_PIXEL1_MATCH			BIT(15)
+#define ZYNQMP_DP_TX_INTR_CHBUF_UNDERFLW_MASK		0x3f0000
+#define ZYNQMP_DP_TX_INTR_CHBUF_OVERFLW_MASK		0xfc00000
+#define ZYNQMP_DP_TX_INTR_CUST_TS_2			BIT(28)
+#define ZYNQMP_DP_TX_INTR_CUST_TS			BIT(29)
+#define ZYNQMP_DP_TX_INTR_EXT_VSYNC_TS			BIT(30)
+#define ZYNQMP_DP_TX_INTR_VSYNC_TS			BIT(31)
+#define ZYNQMP_DP_TX_INTR_ALL				(ZYNQMP_DP_TX_INTR_HPD_IRQ | \
+							 ZYNQMP_DP_TX_INTR_HPD_EVENT | \
+							 ZYNQMP_DP_TX_INTR_REPLY_RECV | \
+							 ZYNQMP_DP_TX_INTR_REPLY_TIMEOUT | \
+							 ZYNQMP_DP_TX_INTR_HPD_PULSE | \
+							 ZYNQMP_DP_TX_INTR_EXT_PKT_TXD | \
+							 ZYNQMP_DP_TX_INTR_LIV_ABUF_UNDRFLW | \
+							 ZYNQMP_DP_TX_INTR_CHBUF_UNDERFLW_MASK | \
+							 ZYNQMP_DP_TX_INTR_CHBUF_OVERFLW_MASK)
+#define ZYNQMP_DP_TX_NO_INTR_ALL			(ZYNQMP_DP_TX_INTR_PIXEL0_MATCH | \
+							 ZYNQMP_DP_TX_INTR_PIXEL1_MATCH | \
+							 ZYNQMP_DP_TX_INTR_CUST_TS_2 | \
+							 ZYNQMP_DP_TX_INTR_CUST_TS | \
+							 ZYNQMP_DP_TX_INTR_EXT_VSYNC_TS | \
+							 ZYNQMP_DP_TX_INTR_VSYNC_TS)
+#define ZYNQMP_DP_TX_REPLY_DATA_CNT			0x148
+#define ZYNQMP_DP_SUB_TX_INTR_STATUS			0x3a0
+#define ZYNQMP_DP_SUB_TX_INTR_MASK			0x3a4
+#define ZYNQMP_DP_SUB_TX_INTR_EN			0x3a8
+#define ZYNQMP_DP_SUB_TX_INTR_DS			0x3ac
+
+/* Main stream attribute registers */
+#define ZYNQMP_DP_TX_MAIN_STREAM_HTOTAL			0x180
+#define ZYNQMP_DP_TX_MAIN_STREAM_VTOTAL			0x184
+#define ZYNQMP_DP_TX_MAIN_STREAM_POLARITY		0x188
+#define ZYNQMP_DP_TX_MAIN_STREAM_POLARITY_HSYNC_SHIFT	0
+#define ZYNQMP_DP_TX_MAIN_STREAM_POLARITY_VSYNC_SHIFT	1
+#define ZYNQMP_DP_TX_MAIN_STREAM_HSWIDTH		0x18c
+#define ZYNQMP_DP_TX_MAIN_STREAM_VSWIDTH		0x190
+#define ZYNQMP_DP_TX_MAIN_STREAM_HRES			0x194
+#define ZYNQMP_DP_TX_MAIN_STREAM_VRES			0x198
+#define ZYNQMP_DP_TX_MAIN_STREAM_HSTART			0x19c
+#define ZYNQMP_DP_TX_MAIN_STREAM_VSTART			0x1a0
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC0			0x1a4
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC0_SYNC		BIT(0)
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC0_FORMAT_SHIFT	1
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC0_DYNAMIC_RANGE	BIT(3)
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC0_YCBCR_COLRIMETRY	BIT(4)
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC0_BPC_SHIFT	5
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC1			0x1a8
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC0_INTERLACED_VERT	BIT(0)
+#define ZYNQMP_DP_TX_MAIN_STREAM_MISC0_STEREO_VID_SHIFT	1
+#define ZYNQMP_DP_TX_M_VID				0x1ac
+#define ZYNQMP_DP_TX_TRANSFER_UNIT_SIZE			0x1b0
+#define ZYNQMP_DP_TX_DEF_TRANSFER_UNIT_SIZE		64
+#define ZYNQMP_DP_TX_N_VID				0x1b4
+#define ZYNQMP_DP_TX_USER_PIXEL_WIDTH			0x1b8
+#define ZYNQMP_DP_TX_USER_DATA_CNT_PER_LANE		0x1bc
+#define ZYNQMP_DP_TX_MIN_BYTES_PER_TU			0x1c4
+#define ZYNQMP_DP_TX_FRAC_BYTES_PER_TU			0x1c8
+#define ZYNQMP_DP_TX_INIT_WAIT				0x1cc
+
+/* PHY configuration and status registers */
+#define ZYNQMP_DP_TX_PHY_CONFIG				0x200
+#define ZYNQMP_DP_TX_PHY_CONFIG_PHY_RESET		BIT(0)
+#define ZYNQMP_DP_TX_PHY_CONFIG_GTTX_RESET		BIT(1)
+#define ZYNQMP_DP_TX_PHY_CONFIG_PHY_PMA_RESET		BIT(8)
+#define ZYNQMP_DP_TX_PHY_CONFIG_PHY_PCS_RESET		BIT(9)
+#define ZYNQMP_DP_TX_PHY_CONFIG_ALL_RESET		(ZYNQMP_DP_TX_PHY_CONFIG_PHY_RESET | \
+							 ZYNQMP_DP_TX_PHY_CONFIG_GTTX_RESET | \
+							 ZYNQMP_DP_TX_PHY_CONFIG_PHY_PMA_RESET | \
+							 ZYNQMP_DP_TX_PHY_CONFIG_PHY_PCS_RESET)
+#define ZYNQMP_DP_TX_PHY_PREEMPHASIS_LANE_0		0x210
+#define ZYNQMP_DP_TX_PHY_PREEMPHASIS_LANE_1		0x214
+#define ZYNQMP_DP_TX_PHY_PREEMPHASIS_LANE_2		0x218
+#define ZYNQMP_DP_TX_PHY_PREEMPHASIS_LANE_3		0x21c
+#define ZYNQMP_DP_TX_PHY_VOLTAGE_DIFF_LANE_0		0x220
+#define ZYNQMP_DP_TX_PHY_VOLTAGE_DIFF_LANE_1		0x224
+#define ZYNQMP_DP_TX_PHY_VOLTAGE_DIFF_LANE_2		0x228
+#define ZYNQMP_DP_TX_PHY_VOLTAGE_DIFF_LANE_3		0x22c
+#define ZYNQMP_DP_TX_PHY_CLOCK_FEEDBACK_SETTING		0x234
+#define ZYNQMP_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_162	0x1
+#define ZYNQMP_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_270	0x3
+#define ZYNQMP_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_540	0x5
+#define ZYNQMP_DP_TX_PHY_POWER_DOWN			0x238
+#define ZYNQMP_DP_TX_PHY_POWER_DOWN_LANE_0		BIT(0)
+#define ZYNQMP_DP_TX_PHY_POWER_DOWN_LANE_1		BIT(1)
+#define ZYNQMP_DP_TX_PHY_POWER_DOWN_LANE_2		BIT(2)
+#define ZYNQMP_DP_TX_PHY_POWER_DOWN_LANE_3		BIT(3)
+#define ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL			0xf
+#define ZYNQMP_DP_TX_PHY_PRECURSOR_LANE_0		0x23c
+#define ZYNQMP_DP_TX_PHY_PRECURSOR_LANE_1		0x240
+#define ZYNQMP_DP_TX_PHY_PRECURSOR_LANE_2		0x244
+#define ZYNQMP_DP_TX_PHY_PRECURSOR_LANE_3		0x248
+#define ZYNQMP_DP_TX_PHY_POSTCURSOR_LANE_0		0x24c
+#define ZYNQMP_DP_TX_PHY_POSTCURSOR_LANE_1		0x250
+#define ZYNQMP_DP_TX_PHY_POSTCURSOR_LANE_2		0x254
+#define ZYNQMP_DP_TX_PHY_POSTCURSOR_LANE_3		0x258
+#define ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0		0x24c
+#define ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_1		0x250
+#define ZYNQMP_DP_TX_PHY_STATUS				0x280
+#define ZYNQMP_DP_TX_PHY_STATUS_PLL_LOCKED_SHIFT	4
+#define ZYNQMP_DP_TX_PHY_STATUS_FPGA_PLL_LOCKED		BIT(6)
+
+/* Audio registers */
+#define ZYNQMP_DP_TX_AUDIO_CONTROL			0x300
+#define ZYNQMP_DP_TX_AUDIO_CHANNELS			0x304
+#define ZYNQMP_DP_TX_AUDIO_INFO_DATA			0x308
+#define ZYNQMP_DP_TX_AUDIO_M_AUD			0x328
+#define ZYNQMP_DP_TX_AUDIO_N_AUD			0x32c
+#define ZYNQMP_DP_TX_AUDIO_EXT_DATA			0x330
+
+#define ZYNQMP_DP_MISC0_RGB				(0)
+#define ZYNQMP_DP_MISC0_YCRCB_422			(5 << 1)
+#define ZYNQMP_DP_MISC0_YCRCB_444			(6 << 1)
+#define ZYNQMP_DP_MISC0_FORMAT_MASK			0xe
+#define ZYNQMP_DP_MISC0_BPC_6				(0 << 5)
+#define ZYNQMP_DP_MISC0_BPC_8				(1 << 5)
+#define ZYNQMP_DP_MISC0_BPC_10				(2 << 5)
+#define ZYNQMP_DP_MISC0_BPC_12				(3 << 5)
+#define ZYNQMP_DP_MISC0_BPC_16				(4 << 5)
+#define ZYNQMP_DP_MISC0_BPC_MASK			0xe0
+#define ZYNQMP_DP_MISC1_Y_ONLY				(1 << 7)
+
+#define ZYNQMP_DP_MAX_LANES				2
+#define ZYNQMP_MAX_FREQ					3000000
+
+#define DP_REDUCED_BIT_RATE				162000
+#define DP_HIGH_BIT_RATE				270000
+#define DP_HIGH_BIT_RATE2				540000
+#define DP_MAX_TRAINING_TRIES				5
+#define DP_V1_2						0x12
+
+/**
+ * struct zynqmp_dp_link_config - Common link config between source and sink
+ * @max_rate: maximum link rate
+ * @max_lanes: maximum number of lanes
+ */
+struct zynqmp_dp_link_config {
+	int max_rate;
+	u8 max_lanes;
+};
+
+/**
+ * struct zynqmp_dp_mode - Configured mode of DisplayPort
+ * @bw_code: code for bandwidth(link rate)
+ * @lane_cnt: number of lanes
+ * @pclock: pixel clock frequency of current mode
+ * @fmt: format identifier string
+ */
+struct zynqmp_dp_mode {
+	u8 bw_code;
+	u8 lane_cnt;
+	int pclock;
+	const char *fmt;
+};
+
+/**
+ * struct zynqmp_dp_config - Configuration of DisplayPort from DTS
+ * @misc0: misc0 configuration (per DP v1.2 spec)
+ * @misc1: misc1 configuration (per DP v1.2 spec)
+ * @bpp: bits per pixel
+ * @bpc: bits per component
+ * @num_colors: number of color components
+ */
+struct zynqmp_dp_config {
+	u8 misc0;
+	u8 misc1;
+	u8 bpp;
+	u8 bpc;
+	u8 num_colors;
+};
+
+/**
+ * struct zynqmp_dp - Xilinx DisplayPort core
+ * @encoder: the drm encoder structure
+ * @connector: the drm connector structure
+ * @sync_prop: synchronous mode property
+ * @bpc_prop: bpc mode property
+ * @dev: device structure
+ * @dpsub: Display subsystem
+ * @drm: DRM core
+ * @iomem: device I/O memory for register access
+ * @irq: irq
+ * @config: IP core configuration from DTS
+ * @aux: aux channel
+ * @phy: PHY handles for DP lanes
+ * @num_lanes: number of enabled phy lanes
+ * @hpd_work: hot plug detection worker
+ * @status: connection status
+ * @enabled: flag to indicate if the device is enabled
+ * @dpms: current dpms state
+ * @dpcd: DP configuration data from currently connected sink device
+ * @link_config: common link configuration between IP core and sink device
+ * @mode: current mode between IP core and sink device
+ * @train_set: set of training data
+ */
+struct zynqmp_dp {
+	struct drm_encoder encoder;
+	struct drm_connector connector;
+	struct drm_property *sync_prop;
+	struct drm_property *bpc_prop;
+	struct device *dev;
+	struct zynqmp_dpsub *dpsub;
+	struct drm_device *drm;
+	void __iomem *iomem;
+	int irq;
+
+	struct zynqmp_dp_config config;
+	struct drm_dp_aux aux;
+	struct phy *phy[ZYNQMP_DP_MAX_LANES];
+	u8 num_lanes;
+	struct delayed_work hpd_work;
+	enum drm_connector_status status;
+	bool enabled;
+
+	int dpms;
+	u8 dpcd[DP_RECEIVER_CAP_SIZE];
+	struct zynqmp_dp_link_config link_config;
+	struct zynqmp_dp_mode mode;
+	u8 train_set[ZYNQMP_DP_MAX_LANES];
+};
+
+static inline struct zynqmp_dp *encoder_to_dp(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct zynqmp_dp, encoder);
+}
+
+static inline struct zynqmp_dp *connector_to_dp(struct drm_connector *connector)
+{
+	return container_of(connector, struct zynqmp_dp, connector);
+}
+
+static void zynqmp_dp_write(void __iomem *base, int offset, u32 val)
+{
+	writel(val, base + offset);
+}
+
+static u32 zynqmp_dp_read(void __iomem *base, int offset)
+{
+	return readl(base + offset);
+}
+
+static void zynqmp_dp_clr(void __iomem *base, int offset, u32 clr)
+{
+	zynqmp_dp_write(base, offset, zynqmp_dp_read(base, offset) & ~clr);
+}
+
+static void zynqmp_dp_set(void __iomem *base, int offset, u32 set)
+{
+	zynqmp_dp_write(base, offset, zynqmp_dp_read(base, offset) | set);
+}
+
+/*
+ * Internal functions: used by zynqmp_disp.c
+ */
+
+/**
+ * zynqmp_dp_update_bpp - Update the current bpp config
+ * @dp: DisplayPort IP core structure
+ *
+ * Update the current bpp based on the color format: bpc & num_colors.
+ * Any function that changes bpc or num_colors should call this
+ * to keep the bpp value in sync.
+ */
+static void zynqmp_dp_update_bpp(struct zynqmp_dp *dp)
+{
+	struct zynqmp_dp_config *config = &dp->config;
+
+	config->bpp = dp->config.bpc * dp->config.num_colors;
+}
+
+/**
+ * zynqmp_dp_set_color - Set the color
+ * @dp: DisplayPort IP core structure
+ * @color: color string, from zynqmp_disp_color_enum
+ *
+ * Update misc register values based on @color string.
+ *
+ * Return: 0 on success, or -EINVAL.
+ */
+int zynqmp_dp_set_color(struct zynqmp_dp *dp, const char *color)
+{
+	struct zynqmp_dp_config *config = &dp->config;
+
+	config->misc0 &= ~ZYNQMP_DP_MISC0_FORMAT_MASK;
+	config->misc1 &= ~ZYNQMP_DP_MISC1_Y_ONLY;
+	if (strcmp(color, "rgb") == 0) {
+		config->misc0 |= ZYNQMP_DP_MISC0_RGB;
+		config->num_colors = 3;
+	} else if (strcmp(color, "ycrcb422") == 0) {
+		config->misc0 |= ZYNQMP_DP_MISC0_YCRCB_422;
+		config->num_colors = 2;
+	} else if (strcmp(color, "ycrcb444") == 0) {
+		config->misc0 |= ZYNQMP_DP_MISC0_YCRCB_444;
+		config->num_colors = 3;
+	} else if (strcmp(color, "yonly") == 0) {
+		config->misc1 |= ZYNQMP_DP_MISC1_Y_ONLY;
+		config->num_colors = 1;
+	} else {
+		dev_err(dp->dev, "Invalid colormetry in DT\n");
+		return -EINVAL;
+	}
+	zynqmp_dp_update_bpp(dp);
+
+	return 0;
+}
+
+/**
+ * zynqmp_dp_enable_vblank - Enable vblank
+ * @dp: DisplayPort IP core structure
+ *
+ * Enable vblank interrupt
+ */
+void zynqmp_dp_enable_vblank(struct zynqmp_dp *dp)
+{
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_EN,
+			ZYNQMP_DP_TX_INTR_VBLANK_START);
+}
+
+/**
+ * zynqmp_dp_disable_vblank - Disable vblank
+ * @dp: DisplayPort IP core structure
+ *
+ * Disable vblank interrupt
+ */
+void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp)
+{
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_DS,
+			ZYNQMP_DP_TX_INTR_VBLANK_START);
+}
+
+/*
+ * DP PHY functions
+ */
+
+/**
+ * zynqmp_dp_init_phy - Initialize the phy
+ * @dp: DisplayPort IP core structure
+ *
+ * Initialize the phy.
+ *
+ * Return: 0 if the phy instances are initialized correctly, or the error code
+ * returned from the callee functions.
+ */
+static int zynqmp_dp_init_phy(struct zynqmp_dp *dp)
+{
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < ZYNQMP_DP_MAX_LANES; i++) {
+		ret = phy_init(dp->phy[i]);
+		if (ret) {
+			dev_err(dp->dev, "failed to init phy lane %d\n", i);
+			return ret;
+		}
+	}
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_DS,
+			ZYNQMP_DP_TX_INTR_ALL);
+	zynqmp_dp_clr(dp->iomem, ZYNQMP_DP_TX_PHY_CONFIG,
+		      ZYNQMP_DP_TX_PHY_CONFIG_ALL_RESET);
+
+	return 0;
+}
+
+/**
+ * zynqmp_dp_exit_phy - Exit the phy
+ * @dp: DisplayPort IP core structure
+ *
+ * Exit the phy.
+ */
+static void zynqmp_dp_exit_phy(struct zynqmp_dp *dp)
+{
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < ZYNQMP_DP_MAX_LANES; i++) {
+		ret = phy_exit(dp->phy[i]);
+		if (ret)
+			dev_err(dp->dev, "failed to exit phy(%d) %d\n", i, ret);
+	}
+}
+
+/**
+ * zynqmp_dp_phy_ready - Check if PHY is ready
+ * @dp: DisplayPort IP core structure
+ *
+ * Check if PHY is ready. If PHY is not ready, wait 1ms to check for 100 times.
+ * This amount of delay was suggested by IP designer.
+ *
+ * Return: 0 if PHY is ready, or -ENODEV if PHY is not ready.
+ */
+static int zynqmp_dp_phy_ready(struct zynqmp_dp *dp)
+{
+	u32 i, reg, ready;
+
+	ready = (1 << ZYNQMP_DP_MAX_LANES) - 1;
+
+	/* Wait for 100 * 1ms. This should be enough time for PHY to be ready */
+	for (i = 0; ; i++) {
+		reg = zynqmp_dp_read(dp->iomem, ZYNQMP_DP_TX_PHY_STATUS);
+		if ((reg & ready) == ready)
+			return 0;
+
+		if (i == 100) {
+			dev_err(dp->dev, "PHY isn't ready\n");
+			return -ENODEV;
+		}
+
+		usleep_range(1000, 1100);
+	}
+
+	return 0;
+}
+
+/*
+ * DP functions
+ */
+
+/**
+ * zynqmp_dp_max_rate - Calculate and return available max pixel clock
+ * @link_rate: link rate (Kilo-bytes / sec)
+ * @lane_num: number of lanes
+ * @bpp: bits per pixel
+ *
+ * Return: max pixel clock (KHz) supported by current link config.
+ */
+static inline int zynqmp_dp_max_rate(int link_rate, u8 lane_num, u8 bpp)
+{
+	return link_rate * lane_num * 8 / bpp;
+}
+
+/**
+ * zynqmp_dp_mode_configure - Configure the link values
+ * @dp: DisplayPort IP core structure
+ * @pclock: pixel clock for requested display mode
+ * @current_bw: current link rate
+ *
+ * Find the link configuration values, rate and lane count for requested pixel
+ * clock @pclock. The @pclock is stored in the mode to be used in other
+ * functions later. The returned rate is downshifted from the current rate
+ * @current_bw.
+ *
+ * Return: Current link rate code, or -EINVAL.
+ */
+static int zynqmp_dp_mode_configure(struct zynqmp_dp *dp, int pclock,
+				    u8 current_bw)
+{
+	int max_rate = dp->link_config.max_rate;
+	u8 bws[3] = { DP_LINK_BW_1_62, DP_LINK_BW_2_7, DP_LINK_BW_5_4 };
+	u8 max_lanes = dp->link_config.max_lanes;
+	u8 max_link_rate_code = drm_dp_link_rate_to_bw_code(max_rate);
+	u8 bpp = dp->config.bpp;
+	u8 lane_cnt;
+	s8 i;
+
+	if (current_bw == DP_LINK_BW_1_62) {
+		dev_err(dp->dev, "can't downshift. already lowest link rate\n");
+		return -EINVAL;
+	}
+
+	for (i = ARRAY_SIZE(bws) - 1; i >= 0; i--) {
+		if (current_bw && bws[i] >= current_bw)
+			continue;
+
+		if (bws[i] <= max_link_rate_code)
+			break;
+	}
+
+	for (lane_cnt = 1; lane_cnt <= max_lanes; lane_cnt <<= 1) {
+		int bw;
+		u32 rate;
+
+		bw = drm_dp_bw_code_to_link_rate(bws[i]);
+		rate = zynqmp_dp_max_rate(bw, lane_cnt, bpp);
+		if (pclock <= rate) {
+			dp->mode.bw_code = bws[i];
+			dp->mode.lane_cnt = lane_cnt;
+			dp->mode.pclock = pclock;
+			return dp->mode.bw_code;
+		}
+	}
+
+	dev_err(dp->dev, "failed to configure link values\n");
+
+	return -EINVAL;
+}
+
+/**
+ * zynqmp_dp_adjust_train - Adjust train values
+ * @dp: DisplayPort IP core structure
+ * @link_status: link status from sink which contains requested training values
+ */
+static void zynqmp_dp_adjust_train(struct zynqmp_dp *dp,
+				   u8 link_status[DP_LINK_STATUS_SIZE])
+{
+	u8 *train_set = dp->train_set;
+	u8 voltage = 0, preemphasis = 0;
+	u8 i;
+
+	for (i = 0; i < dp->mode.lane_cnt; i++) {
+		u8 v = drm_dp_get_adjust_request_voltage(link_status, i);
+		u8 p = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+
+		if (v > voltage)
+			voltage = v;
+
+		if (p > preemphasis)
+			preemphasis = p;
+	}
+
+	if (voltage >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3)
+		voltage |= DP_TRAIN_MAX_SWING_REACHED;
+
+	if (preemphasis >= DP_TRAIN_PRE_EMPH_LEVEL_2)
+		preemphasis |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
+
+	for (i = 0; i < dp->mode.lane_cnt; i++)
+		train_set[i] = voltage | preemphasis;
+}
+
+/**
+ * zynqmp_dp_update_vs_emph - Update the training values
+ * @dp: DisplayPort IP core structure
+ *
+ * Update the training values based on the request from sink. The mapped values
+ * are predefined, and values(vs, pe, pc) are from the device manual.
+ *
+ * Return: 0 if vs and emph are updated successfully, or the error code returned
+ * by drm_dp_dpcd_write().
+ */
+static int zynqmp_dp_update_vs_emph(struct zynqmp_dp *dp)
+{
+	u8 *train_set = dp->train_set;
+	u8 i, v_level, p_level;
+	int ret;
+
+	ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, train_set,
+				dp->mode.lane_cnt);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < dp->mode.lane_cnt; i++) {
+		u32 reg = ZYNQMP_DP_SUB_TX_PHY_PRECURSOR_LANE_0 + i * 4;
+
+		v_level = (train_set[i] & DP_TRAIN_VOLTAGE_SWING_MASK) >>
+			  DP_TRAIN_VOLTAGE_SWING_SHIFT;
+		p_level = (train_set[i] & DP_TRAIN_PRE_EMPHASIS_MASK) >>
+			  DP_TRAIN_PRE_EMPHASIS_SHIFT;
+
+		zynqmp_dp_write(dp->iomem, reg, 0x2);
+	}
+
+	return 0;
+}
+
+/**
+ * zynqmp_dp_link_train_cr - Train clock recovery
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: 0 if clock recovery train is done successfully, or corresponding
+ * error code.
+ */
+static int zynqmp_dp_link_train_cr(struct zynqmp_dp *dp)
+{
+	u8 link_status[DP_LINK_STATUS_SIZE];
+	u8 lane_cnt = dp->mode.lane_cnt;
+	u8 vs = 0, tries = 0;
+	u16 max_tries, i;
+	bool cr_done;
+	int ret;
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_TRAINING_PATTERN_SET,
+			DP_TRAINING_PATTERN_1);
+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET,
+				 DP_TRAINING_PATTERN_1 |
+				 DP_LINK_SCRAMBLING_DISABLE);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * 256 loops should be maximum iterations for 4 lanes and 4 values.
+	 * So, This loop should exit before 512 iterations
+	 */
+	for (max_tries = 0; max_tries < 512; max_tries++) {
+		ret = zynqmp_dp_update_vs_emph(dp);
+		if (ret)
+			return ret;
+
+		drm_dp_link_train_clock_recovery_delay(dp->dpcd);
+		ret = drm_dp_dpcd_read_link_status(&dp->aux, link_status);
+		if (ret < 0)
+			return ret;
+
+		cr_done = drm_dp_clock_recovery_ok(link_status, lane_cnt);
+		if (cr_done)
+			break;
+
+		for (i = 0; i < lane_cnt; i++)
+			if (!(dp->train_set[i] & DP_TRAIN_MAX_SWING_REACHED))
+				break;
+		if (i == lane_cnt)
+			break;
+
+		if ((dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == vs)
+			tries++;
+		else
+			tries = 0;
+
+		if (tries == DP_MAX_TRAINING_TRIES)
+			break;
+
+		vs = dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK;
+		zynqmp_dp_adjust_train(dp, link_status);
+	}
+
+	if (!cr_done)
+		return -EIO;
+
+	return 0;
+}
+
+/**
+ * zynqmp_dp_link_train_ce - Train channel equalization
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: 0 if channel equalization train is done successfully, or
+ * corresponding error code.
+ */
+static int zynqmp_dp_link_train_ce(struct zynqmp_dp *dp)
+{
+	u8 link_status[DP_LINK_STATUS_SIZE];
+	u8 lane_cnt = dp->mode.lane_cnt;
+	u32 pat, tries;
+	int ret;
+	bool ce_done;
+
+	if (dp->dpcd[DP_DPCD_REV] >= DP_V1_2 &&
+	    dp->dpcd[DP_MAX_LANE_COUNT] & DP_TPS3_SUPPORTED)
+		pat = DP_TRAINING_PATTERN_3;
+	else
+		pat = DP_TRAINING_PATTERN_2;
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_TRAINING_PATTERN_SET, pat);
+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET,
+				 pat | DP_LINK_SCRAMBLING_DISABLE);
+	if (ret < 0)
+		return ret;
+
+	for (tries = 0; tries < DP_MAX_TRAINING_TRIES; tries++) {
+		ret = zynqmp_dp_update_vs_emph(dp);
+		if (ret)
+			return ret;
+
+		drm_dp_link_train_channel_eq_delay(dp->dpcd);
+		ret = drm_dp_dpcd_read_link_status(&dp->aux, link_status);
+		if (ret < 0)
+			return ret;
+
+		ce_done = drm_dp_channel_eq_ok(link_status, lane_cnt);
+		if (ce_done)
+			break;
+
+		zynqmp_dp_adjust_train(dp, link_status);
+	}
+
+	if (!ce_done)
+		return -EIO;
+
+	return 0;
+}
+
+/**
+ * zynqmp_dp_link_train - Train the link
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: 0 if all trains are done successfully, or corresponding error code.
+ */
+static int zynqmp_dp_train(struct zynqmp_dp *dp)
+{
+	u32 reg;
+	u8 bw_code = dp->mode.bw_code;
+	u8 lane_cnt = dp->mode.lane_cnt;
+	u8 aux_lane_cnt = lane_cnt;
+	bool enhanced;
+	int ret;
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_LANE_CNT_SET, lane_cnt);
+	enhanced = drm_dp_enhanced_frame_cap(dp->dpcd);
+	if (enhanced) {
+		zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENHANCED_FRAME_EN, 1);
+		aux_lane_cnt |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
+	}
+
+	if (dp->dpcd[3] & 0x1) {
+		zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_DOWNSPREAD_CTL, 1);
+		drm_dp_dpcd_writeb(&dp->aux, DP_DOWNSPREAD_CTRL,
+				   DP_SPREAD_AMP_0_5);
+	} else {
+		zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_DOWNSPREAD_CTL, 0);
+		drm_dp_dpcd_writeb(&dp->aux, DP_DOWNSPREAD_CTRL, 0);
+	}
+
+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_LANE_COUNT_SET, aux_lane_cnt);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to set lane count\n");
+		return ret;
+	}
+
+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_MAIN_LINK_CHANNEL_CODING_SET,
+				 DP_SET_ANSI_8B10B);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to set ANSI 8B/10B encoding\n");
+		return ret;
+	}
+
+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_LINK_BW_SET, bw_code);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to set DP bandwidth\n");
+		return ret;
+	}
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_LINK_BW_SET, bw_code);
+	switch (bw_code) {
+	case DP_LINK_BW_1_62:
+		reg = ZYNQMP_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_162;
+		break;
+	case DP_LINK_BW_2_7:
+		reg = ZYNQMP_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_270;
+		break;
+	case DP_LINK_BW_5_4:
+	default:
+		reg = ZYNQMP_DP_TX_PHY_CLOCK_FEEDBACK_SETTING_540;
+		break;
+	}
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_PHY_CLOCK_FEEDBACK_SETTING,
+			reg);
+	ret = zynqmp_dp_phy_ready(dp);
+	if (ret < 0)
+		return ret;
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_SCRAMBLING_DISABLE, 1);
+	memset(dp->train_set, 0, 4);
+	ret = zynqmp_dp_link_train_cr(dp);
+	if (ret)
+		return ret;
+
+	ret = zynqmp_dp_link_train_ce(dp);
+	if (ret)
+		return ret;
+
+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET,
+				 DP_TRAINING_PATTERN_DISABLE);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to disable training pattern\n");
+		return ret;
+	}
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_TRAINING_PATTERN_SET,
+			DP_TRAINING_PATTERN_DISABLE);
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_SCRAMBLING_DISABLE, 0);
+
+	return 0;
+}
+
+/**
+ * zynqmp_dp_train_loop - Downshift the link rate during training
+ * @dp: DisplayPort IP core structure
+ *
+ * Train the link by downshifting the link rate if training is not successful.
+ */
+static void zynqmp_dp_train_loop(struct zynqmp_dp *dp)
+{
+	struct zynqmp_dp_mode *mode = &dp->mode;
+	u8 bw = mode->bw_code;
+	int ret;
+
+	do {
+		if (dp->status == connector_status_disconnected ||
+		    !dp->enabled)
+			return;
+
+		ret = zynqmp_dp_train(dp);
+		if (!ret)
+			return;
+
+		ret = zynqmp_dp_mode_configure(dp, mode->pclock, bw);
+		if (ret < 0)
+			goto err_out;
+
+		bw = ret;
+	} while (bw >= DP_LINK_BW_1_62);
+
+err_out:
+	dev_err(dp->dev, "failed to train the DP link\n");
+}
+
+/*
+ * DP Aux functions
+ */
+
+#define AUX_READ_BIT	0x1
+
+/**
+ * zynqmp_dp_aux_cmd_submit - Submit aux command
+ * @dp: DisplayPort IP core structure
+ * @cmd: aux command
+ * @addr: aux address
+ * @buf: buffer for command data
+ * @bytes: number of bytes for @buf
+ * @reply: reply code to be returned
+ *
+ * Submit an aux command. All aux related commands, native or i2c aux
+ * read/write, are submitted through this function. The function is mapped to
+ * the transfer function of struct drm_dp_aux. This function involves in
+ * multiple register reads/writes, thus synchronization is needed, and it is
+ * done by drm_dp_helper using @hw_mutex. The calling thread goes into sleep
+ * if there's no immediate reply to the command submission. The reply code is
+ * returned at @reply if @reply != NULL.
+ *
+ * Return: 0 if the command is submitted properly, or corresponding error code:
+ * -EBUSY when there is any request already being processed
+ * -ETIMEDOUT when receiving reply is timed out
+ * -EIO when received bytes are less than requested
+ */
+static int zynqmp_dp_aux_cmd_submit(struct zynqmp_dp *dp, u32 cmd, u16 addr,
+				    u8 *buf, u8 bytes, u8 *reply)
+{
+	bool is_read = (cmd & AUX_READ_BIT) ? true : false;
+	void __iomem *iomem = dp->iomem;
+	u32 reg, i;
+
+	reg = zynqmp_dp_read(iomem, ZYNQMP_DP_TX_INTR_SIGNAL_STATE);
+	if (reg & ZYNQMP_DP_TX_INTR_SIGNAL_STATE_REQUEST)
+		return -EBUSY;
+
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_AUX_ADDRESS, addr);
+	if (!is_read)
+		for (i = 0; i < bytes; i++)
+			zynqmp_dp_write(iomem, ZYNQMP_DP_TX_AUX_WRITE_FIFO,
+					buf[i]);
+
+	reg = cmd << ZYNQMP_DP_TX_AUX_COMMAND_CMD_SHIFT;
+	if (!buf || !bytes)
+		reg |= ZYNQMP_DP_TX_AUX_COMMAND_ADDRESS_ONLY;
+	else
+		reg |= (bytes - 1) << ZYNQMP_DP_TX_AUX_COMMAND_BYTES_SHIFT;
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_AUX_COMMAND, reg);
+
+	/* Wait for reply to be delivered upto 2ms */
+	for (i = 0; ; i++) {
+		reg = zynqmp_dp_read(iomem, ZYNQMP_DP_TX_INTR_SIGNAL_STATE);
+		if (reg & ZYNQMP_DP_TX_INTR_SIGNAL_STATE_REPLY)
+			break;
+
+		if (reg & ZYNQMP_DP_TX_INTR_SIGNAL_STATE_REPLY_TIMEOUT ||
+		    i == 2)
+			return -ETIMEDOUT;
+
+		usleep_range(1000, 1100);
+	}
+
+	reg = zynqmp_dp_read(iomem, ZYNQMP_DP_TX_AUX_REPLY_CODE);
+	if (reply)
+		*reply = reg;
+
+	if (is_read &&
+	    (reg == ZYNQMP_DP_TX_AUX_REPLY_CODE_AUX_ACK ||
+	     reg == ZYNQMP_DP_TX_AUX_REPLY_CODE_I2C_ACK)) {
+		reg = zynqmp_dp_read(iomem, ZYNQMP_DP_TX_REPLY_DATA_CNT);
+		if ((reg & ZYNQMP_DP_TX_AUX_REPLY_CNT_MASK) != bytes)
+			return -EIO;
+
+		for (i = 0; i < bytes; i++) {
+			buf[i] = zynqmp_dp_read(iomem,
+						ZYNQMP_DP_TX_AUX_REPLY_DATA);
+		}
+	}
+
+	return 0;
+}
+
+static ssize_t
+zynqmp_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
+{
+	struct zynqmp_dp *dp = container_of(aux, struct zynqmp_dp, aux);
+	int ret;
+	unsigned int i, iter;
+
+	/* Number of loops = timeout in msec / aux delay (400 usec) */
+	iter = zynqmp_dp_aux_timeout_ms * 1000 / 400;
+	iter = iter ? iter : 1;
+
+	for (i = 0; i < iter; i++) {
+		ret = zynqmp_dp_aux_cmd_submit(dp, msg->request, msg->address,
+					       msg->buffer, msg->size,
+					       &msg->reply);
+		if (!ret) {
+			dev_dbg(dp->dev, "aux %d retries\n", i);
+			return msg->size;
+		}
+
+		if (dp->status == connector_status_disconnected) {
+			dev_dbg(dp->dev, "no connected aux device\n");
+			return -ENODEV;
+		}
+
+		usleep_range(400, 500);
+	}
+
+	dev_dbg(dp->dev, "failed to do aux transfer (%d)\n", ret);
+
+	return ret;
+}
+
+/**
+ * zynqmp_dp_init_aux - Initialize the DP aux
+ * @dp: DisplayPort IP core structure
+ *
+ * Initialize the DP aux. The aux clock is derived from the axi clock, so
+ * this function gets the axi clock frequency and calculates the filter
+ * value. Additionally, the interrupts and transmitter are enabled.
+ *
+ * Return: 0 on success, error value otherwise
+ */
+static int zynqmp_dp_init_aux(struct zynqmp_dp *dp)
+{
+	unsigned int rate;
+	u32 reg, w;
+
+	rate = zynqmp_disp_get_apb_clk_rate(dp->dpsub->disp);
+	if (rate < ZYNQMP_DP_TX_CLK_DIVIDER_MHZ) {
+		dev_err(dp->dev, "aclk should be higher than 1MHz\n");
+		return -EINVAL;
+	}
+
+	/* Allowable values for this register are: 8, 16, 24, 32, 40, 48 */
+	for (w = 8; w <= 48; w += 8) {
+		/* AUX pulse width should be between 0.4 to 0.6 usec */
+		if (w >= (4 * rate / 10000000) &&
+		    w <= (6 * rate / 10000000))
+			break;
+	}
+
+	if (w > 48) {
+		dev_err(dp->dev, "aclk frequency too high\n");
+		return -EINVAL;
+	}
+	reg = w << ZYNQMP_DP_TX_CLK_DIVIDER_AUX_FILTER_SHIFT;
+	reg |= rate / ZYNQMP_DP_TX_CLK_DIVIDER_MHZ;
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_CLK_DIVIDER, reg);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_EN,
+			ZYNQMP_DP_TX_INTR_ALL);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_DS,
+			ZYNQMP_DP_TX_NO_INTR_ALL);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 1);
+
+	return 0;
+}
+
+/**
+ * zynqmp_dp_exit_aux - De-initialize the DP aux
+ * @dp: DisplayPort IP core structure
+ *
+ * De-initialize the DP aux. Disable all interrupts which are enabled
+ * through aux initialization, as well as the transmitter.
+ */
+static void zynqmp_dp_exit_aux(struct zynqmp_dp *dp)
+{
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_DS, 0xffffffff);
+}
+
+/*
+ * Generic DP functions
+ */
+
+/**
+ * zynqmp_dp_update_misc - Write the misc registers
+ * @dp: DisplayPort IP core structure
+ *
+ * The misc register values are stored in the structure, and this
+ * function applies the values into the registers.
+ */
+static void zynqmp_dp_update_misc(struct zynqmp_dp *dp)
+{
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_MAIN_STREAM_MISC0,
+			dp->config.misc0);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_MAIN_STREAM_MISC1,
+			dp->config.misc1);
+}
+
+/**
+ * zynqmp_dp_set_sync_mode - Set the sync mode bit in the software misc state
+ * @dp: DisplayPort IP core structure
+ * @mode: flag if the sync mode should be on or off
+ *
+ * Set the bit in software misc state. To apply to hardware,
+ * zynqmp_dp_update_misc() should be called.
+ */
+static void zynqmp_dp_set_sync_mode(struct zynqmp_dp *dp, bool mode)
+{
+	struct zynqmp_dp_config *config = &dp->config;
+
+	if (mode)
+		config->misc0 |= ZYNQMP_DP_TX_MAIN_STREAM_MISC0_SYNC;
+	else
+		config->misc0 &= ~ZYNQMP_DP_TX_MAIN_STREAM_MISC0_SYNC;
+}
+
+/**
+ * zynqmp_dp_get_sync_mode - Get the sync mode state
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: true if the sync mode is on, or false
+ */
+static bool zynqmp_dp_get_sync_mode(struct zynqmp_dp *dp)
+{
+	struct zynqmp_dp_config *config = &dp->config;
+
+	return !!(config->misc0 & ZYNQMP_DP_TX_MAIN_STREAM_MISC0_SYNC);
+}
+
+/**
+ * zynqmp_dp_set_bpc - Set bpc value in software misc state
+ * @dp: DisplayPort IP core structure
+ * @bpc: bits per component
+ *
+ * Return: 0 on success, or the fallback bpc value
+ */
+static u8 zynqmp_dp_set_bpc(struct zynqmp_dp *dp, u8 bpc)
+{
+	struct zynqmp_dp_config *config = &dp->config;
+	u8 ret = 0;
+
+	if (dp->connector.display_info.bpc &&
+	    dp->connector.display_info.bpc != bpc) {
+		dev_err(dp->dev, "requested bpc (%u) != display info (%u)\n",
+			bpc, dp->connector.display_info.bpc);
+		bpc = dp->connector.display_info.bpc;
+	}
+
+	config->misc0 &= ~ZYNQMP_DP_MISC0_BPC_MASK;
+	switch (bpc) {
+	case 6:
+		config->misc0 |= ZYNQMP_DP_MISC0_BPC_6;
+		break;
+	case 8:
+		config->misc0 |= ZYNQMP_DP_MISC0_BPC_8;
+		break;
+	case 10:
+		config->misc0 |= ZYNQMP_DP_MISC0_BPC_10;
+		break;
+	case 12:
+		config->misc0 |= ZYNQMP_DP_MISC0_BPC_12;
+		break;
+	case 16:
+		config->misc0 |= ZYNQMP_DP_MISC0_BPC_16;
+		break;
+	default:
+		dev_err(dp->dev, "Not supported bpc (%u). fall back to 8bpc\n",
+			bpc);
+		config->misc0 |= ZYNQMP_DP_MISC0_BPC_8;
+		ret = 8;
+		break;
+	}
+	config->bpc = bpc;
+	zynqmp_dp_update_bpp(dp);
+
+	return ret;
+}
+
+/**
+ * zynqmp_dp_get_bpc - Set bpc value from software state
+ * @dp: DisplayPort IP core structure
+ *
+ * Return: current bpc value
+ */
+static u8 zynqmp_dp_get_bpc(struct zynqmp_dp *dp)
+{
+	return dp->config.bpc;
+}
+
+/**
+ * zynqmp_dp_encoder_mode_set_transfer_unit - Set the transfer unit values
+ * @dp: DisplayPort IP core structure
+ * @mode: requested display mode
+ *
+ * Set the transfer unit, and caculate all transfer unit size related values.
+ * Calculation is based on DP and IP core specification.
+ */
+static void
+zynqmp_dp_encoder_mode_set_transfer_unit(struct zynqmp_dp *dp,
+					 struct drm_display_mode *mode)
+{
+	u32 tu = ZYNQMP_DP_TX_DEF_TRANSFER_UNIT_SIZE;
+	u32 bw, vid_kbytes, avg_bytes_per_tu, init_wait;
+
+	/* Use the max transfer unit size (default) */
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_TRANSFER_UNIT_SIZE, tu);
+
+	vid_kbytes = mode->clock * (dp->config.bpp / 8);
+	bw = drm_dp_bw_code_to_link_rate(dp->mode.bw_code);
+	avg_bytes_per_tu = vid_kbytes * tu / (dp->mode.lane_cnt * bw / 1000);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_MIN_BYTES_PER_TU,
+			avg_bytes_per_tu / 1000);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_FRAC_BYTES_PER_TU,
+			avg_bytes_per_tu % 1000);
+
+	/* Configure the initial wait cycle based on transfer unit size */
+	if (tu < (avg_bytes_per_tu / 1000))
+		init_wait = 0;
+	else if ((avg_bytes_per_tu / 1000) <= 4)
+		init_wait = tu;
+	else
+		init_wait = tu - avg_bytes_per_tu / 1000;
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_INIT_WAIT, init_wait);
+}
+
+/**
+ * zynqmp_dp_encoder_mode_set_stream - Configure the main stream
+ * @dp: DisplayPort IP core structure
+ * @mode: requested display mode
+ *
+ * Configure the main stream based on the requested mode @mode. Calculation is
+ * based on IP core specification.
+ */
+void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp,
+				       struct drm_display_mode *mode)
+{
+	void __iomem *iomem = dp->iomem;
+	u8 lane_cnt = dp->mode.lane_cnt;
+	u32 reg, wpl;
+	unsigned int rate;
+
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_HTOTAL, mode->htotal);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_VTOTAL, mode->vtotal);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_POLARITY,
+			(!!(mode->flags & DRM_MODE_FLAG_PVSYNC) <<
+			 ZYNQMP_DP_TX_MAIN_STREAM_POLARITY_VSYNC_SHIFT) |
+			(!!(mode->flags & DRM_MODE_FLAG_PHSYNC) <<
+			 ZYNQMP_DP_TX_MAIN_STREAM_POLARITY_HSYNC_SHIFT));
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_HSWIDTH,
+			mode->hsync_end - mode->hsync_start);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_VSWIDTH,
+			mode->vsync_end - mode->vsync_start);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_HRES, mode->hdisplay);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_VRES, mode->vdisplay);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_HSTART,
+			mode->htotal - mode->hsync_start);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_MAIN_STREAM_VSTART,
+			mode->vtotal - mode->vsync_start);
+
+	/* In synchronous mode, set the diviers */
+	if (dp->config.misc0 & ZYNQMP_DP_TX_MAIN_STREAM_MISC0_SYNC) {
+		reg = drm_dp_bw_code_to_link_rate(dp->mode.bw_code);
+		zynqmp_dp_write(iomem, ZYNQMP_DP_TX_N_VID, reg);
+		zynqmp_dp_write(iomem, ZYNQMP_DP_TX_M_VID, mode->clock);
+		rate = zynqmp_disp_get_aud_clk_rate(dp->dpsub->disp);
+		if (rate) {
+			dev_dbg(dp->dev, "Audio rate: %d\n", rate / 512);
+			zynqmp_dp_write(iomem, ZYNQMP_DP_TX_AUDIO_N_AUD, reg);
+			zynqmp_dp_write(iomem, ZYNQMP_DP_TX_AUDIO_M_AUD,
+					rate / 1000);
+		}
+	}
+
+	/* Only 2 channel audio is supported now */
+	if (zynqmp_disp_aud_enabled(dp->dpsub->disp))
+		zynqmp_dp_write(iomem, ZYNQMP_DP_TX_AUDIO_CHANNELS, 1);
+
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_USER_PIXEL_WIDTH, 1);
+
+	/* Translate to the native 16 bit datapath based on IP core spec */
+	wpl = (mode->hdisplay * dp->config.bpp + 15) / 16;
+	reg = wpl + wpl % lane_cnt - lane_cnt;
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_USER_DATA_CNT_PER_LANE, reg);
+}
+
+/*
+ * DRM property functions
+ */
+
+static struct drm_prop_enum_list zynqmp_dp_bpc_enum[] = {
+	{ 6, "6BPC" },
+	{ 8, "8BPC" },
+	{ 10, "10BPC" },
+	{ 12, "12BPC" },
+};
+
+static void
+zynqmp_dp_attach_property(struct zynqmp_dp *dp, struct drm_mode_object *obj)
+{
+	struct zynqmp_dp_config *config = &dp->config;
+	u8 ret;
+
+	config->misc0 &= ~ZYNQMP_DP_TX_MAIN_STREAM_MISC0_SYNC;
+	drm_object_attach_property(obj, dp->sync_prop, false);
+	ret = zynqmp_dp_set_bpc(dp, 8);
+	drm_object_attach_property(obj, dp->bpc_prop, ret ? ret : 8);
+	zynqmp_dp_update_bpp(dp);
+}
+
+static void zynqmp_dp_create_property(struct zynqmp_dp *dp)
+{
+	int num;
+
+	dp->sync_prop = drm_property_create_bool(dp->drm, 0, "sync");
+	num = ARRAY_SIZE(zynqmp_dp_bpc_enum);
+	dp->bpc_prop = drm_property_create_enum(dp->drm, 0, "bpc",
+						zynqmp_dp_bpc_enum, num);
+}
+
+static void zynqmp_dp_destroy_property(struct zynqmp_dp *dp)
+{
+	drm_property_destroy(dp->drm, dp->bpc_prop);
+	drm_property_destroy(dp->drm, dp->sync_prop);
+}
+
+/*
+ * DRM connector functions
+ */
+
+static enum drm_connector_status
+zynqmp_dp_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct zynqmp_dp *dp = connector_to_dp(connector);
+	struct zynqmp_dp_link_config *link_config = &dp->link_config;
+	u32 state, i;
+	int ret;
+
+	/*
+	 * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to
+	 * get the HPD signal with some monitors.
+	 */
+	for (i = 0; i < 10; i++) {
+		state = zynqmp_dp_read(dp->iomem,
+				       ZYNQMP_DP_TX_INTR_SIGNAL_STATE);
+		if (state & ZYNQMP_DP_TX_INTR_SIGNAL_STATE_HPD)
+			break;
+		msleep(100);
+	}
+
+	if (state & ZYNQMP_DP_TX_INTR_SIGNAL_STATE_HPD) {
+		ret = drm_dp_dpcd_read(&dp->aux, 0x0, dp->dpcd,
+				       sizeof(dp->dpcd));
+		if (ret < 0) {
+			dev_dbg(dp->dev, "DPCD read failes");
+			goto disconnected;
+		}
+
+		link_config->max_rate = min_t(int,
+					      drm_dp_max_link_rate(dp->dpcd),
+					      DP_HIGH_BIT_RATE2);
+		link_config->max_lanes = min_t(u8,
+					       drm_dp_max_lane_count(dp->dpcd),
+					       dp->num_lanes);
+
+		dp->status = connector_status_connected;
+		return connector_status_connected;
+	}
+
+disconnected:
+	dp->status = connector_status_disconnected;
+	return connector_status_disconnected;
+}
+
+static int zynqmp_dp_connector_get_modes(struct drm_connector *connector)
+{
+	struct zynqmp_dp *dp = connector_to_dp(connector);
+	struct edid *edid;
+	int ret;
+
+	edid = drm_get_edid(connector, &dp->aux.ddc);
+	if (!edid)
+		return 0;
+
+	drm_mode_connector_update_edid_property(connector, edid);
+	ret = drm_add_edid_modes(connector, edid);
+	kfree(edid);
+
+	return ret;
+}
+
+static struct drm_encoder *
+zynqmp_dp_connector_best_encoder(struct drm_connector *connector)
+{
+	struct zynqmp_dp *dp = connector_to_dp(connector);
+
+	return &dp->encoder;
+}
+
+static int zynqmp_dp_connector_mode_valid(struct drm_connector *connector,
+					  struct drm_display_mode *mode)
+{
+	struct zynqmp_dp *dp = connector_to_dp(connector);
+	u8 max_lanes = dp->link_config.max_lanes;
+	u8 bpp = dp->config.bpp;
+	int max_rate = dp->link_config.max_rate;
+	int rate;
+
+	if (mode->clock > ZYNQMP_MAX_FREQ) {
+		dev_dbg(dp->dev, "filtered the mode, %s,for high pixel rate\n",
+			mode->name);
+		drm_mode_debug_printmodeline(mode);
+		return MODE_CLOCK_HIGH;
+	}
+
+	/* Check with link rate and lane count */
+	rate = zynqmp_dp_max_rate(max_rate, max_lanes, bpp);
+	if (mode->clock > rate) {
+		dev_dbg(dp->dev, "filtered the mode, %s,for high pixel rate\n",
+			mode->name);
+		drm_mode_debug_printmodeline(mode);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return MODE_OK;
+}
+
+static void zynqmp_dp_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static int
+zynqmp_dp_connector_atomic_set_property(struct drm_connector *connector,
+					struct drm_connector_state *state,
+					struct drm_property *property,
+					uint64_t val)
+{
+	struct zynqmp_dp *dp = connector_to_dp(connector);
+	int ret;
+
+	if (property == dp->sync_prop) {
+		zynqmp_dp_set_sync_mode(dp, val);
+	} else if (property == dp->bpc_prop) {
+		u8 bpc;
+
+		bpc = zynqmp_dp_set_bpc(dp, val);
+		if (bpc) {
+			drm_object_property_set_value(&connector->base,
+						      property, bpc);
+			ret = -EINVAL;
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+zynqmp_dp_connector_atomic_get_property(struct drm_connector *connector,
+					const struct drm_connector_state *state,
+					struct drm_property *property,
+					uint64_t *val)
+{
+	struct zynqmp_dp *dp = connector_to_dp(connector);
+
+	if (property == dp->sync_prop)
+		*val = zynqmp_dp_get_sync_mode(dp);
+	else if (property == dp->bpc_prop)
+		*val =  zynqmp_dp_get_bpc(dp);
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct drm_connector_funcs zynqmp_dp_connector_funcs = {
+	.detect			= zynqmp_dp_connector_detect,
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.destroy		= zynqmp_dp_connector_destroy,
+	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
+	.reset			= drm_atomic_helper_connector_reset,
+	.atomic_set_property	= zynqmp_dp_connector_atomic_set_property,
+	.atomic_get_property	= zynqmp_dp_connector_atomic_get_property,
+};
+
+static struct drm_connector_helper_funcs zynqmp_dp_connector_helper_funcs = {
+	.get_modes	= zynqmp_dp_connector_get_modes,
+	.best_encoder	= zynqmp_dp_connector_best_encoder,
+	.mode_valid	= zynqmp_dp_connector_mode_valid,
+};
+
+/*
+ * DRM encoder functions
+ */
+
+static void zynqmp_dp_encoder_enable(struct drm_encoder *encoder)
+{
+	struct zynqmp_dp *dp = encoder_to_dp(encoder);
+	void __iomem *iomem = dp->iomem;
+	unsigned int i;
+	int ret = 0;
+
+	pm_runtime_get_sync(dp->dev);
+	dp->enabled = true;
+	zynqmp_dp_init_aux(dp);
+	zynqmp_dp_update_misc(dp);
+	if (zynqmp_disp_aud_enabled(dp->dpsub->disp))
+		zynqmp_dp_write(iomem, ZYNQMP_DP_TX_AUDIO_CONTROL, 1);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_PHY_POWER_DOWN, 0);
+	if (dp->status == connector_status_connected) {
+		for (i = 0; i < 3; i++) {
+			ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER,
+						 DP_SET_POWER_D0);
+			if (ret == 1)
+				break;
+			usleep_range(300, 500);
+		}
+		/* Some monitors take time to wake up properly */
+		msleep(zynqmp_dp_power_on_delay_ms);
+	}
+	if (ret != 1)
+		dev_dbg(dp->dev, "DP aux failed\n");
+	else
+		zynqmp_dp_train_loop(dp);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_SW_RESET,
+			ZYNQMP_DP_TX_SW_RESET_ALL);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_ENABLE_MAIN_STREAM, 1);
+}
+
+static void zynqmp_dp_encoder_disable(struct drm_encoder *encoder)
+{
+	struct zynqmp_dp *dp = encoder_to_dp(encoder);
+	void __iomem *iomem = dp->iomem;
+
+	dp->enabled = false;
+	cancel_delayed_work(&dp->hpd_work);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_ENABLE_MAIN_STREAM, 0);
+	drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D3);
+	zynqmp_dp_write(iomem, ZYNQMP_DP_TX_PHY_POWER_DOWN,
+			ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL);
+	if (zynqmp_disp_aud_enabled(dp->dpsub->disp))
+		zynqmp_dp_write(iomem, ZYNQMP_DP_TX_AUDIO_CONTROL, 0);
+	pm_runtime_put_sync(dp->dev);
+}
+
+static void
+zynqmp_dp_encoder_atomic_mode_set(struct drm_encoder *encoder,
+				  struct drm_crtc_state *crtc_state,
+				  struct drm_connector_state *connector_state)
+{
+	struct zynqmp_dp *dp = encoder_to_dp(encoder);
+	struct drm_display_mode *mode = &crtc_state->mode;
+	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+	u8 max_lanes = dp->link_config.max_lanes;
+	u8 bpp = dp->config.bpp;
+	int rate, max_rate = dp->link_config.max_rate;
+	int ret;
+
+	/* Check again as bpp or format might have been chagned */
+	rate = zynqmp_dp_max_rate(max_rate, max_lanes, bpp);
+	if (mode->clock > rate) {
+		dev_err(dp->dev, "the mode, %s,has too high pixel rate\n",
+			mode->name);
+		drm_mode_debug_printmodeline(mode);
+	}
+
+	ret = zynqmp_dp_mode_configure(dp, adjusted_mode->clock, 0);
+	if (ret < 0)
+		return;
+
+	zynqmp_dp_encoder_mode_set_transfer_unit(dp, adjusted_mode);
+}
+
+#define ZYNQMP_DP_MIN_H_BACKPORCH	20
+
+static int
+zynqmp_dp_encoder_atomic_check(struct drm_encoder *encoder,
+			       struct drm_crtc_state *crtc_state,
+			       struct drm_connector_state *conn_state)
+{
+	struct drm_display_mode *mode = &crtc_state->mode;
+	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+	int diff = mode->htotal - mode->hsync_end;
+
+	/*
+	 * ZynqMP DP requires horizontal backporch to be greater than 12.
+	 * This limitation may not be compatible with the sink device.
+	 */
+	if (diff < ZYNQMP_DP_MIN_H_BACKPORCH) {
+		int vrefresh = (adjusted_mode->clock * 1000) /
+			       (adjusted_mode->vtotal * adjusted_mode->htotal);
+
+		dev_dbg(encoder->dev->dev, "hbackporch adjusted: %d to %d",
+			diff, ZYNQMP_DP_MIN_H_BACKPORCH - diff);
+		diff = ZYNQMP_DP_MIN_H_BACKPORCH - diff;
+		adjusted_mode->htotal += diff;
+		adjusted_mode->clock = adjusted_mode->vtotal *
+				       adjusted_mode->htotal * vrefresh / 1000;
+	}
+
+	return 0;
+}
+
+static const struct drm_encoder_funcs zynqmp_dp_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static const struct drm_encoder_helper_funcs zynqmp_dp_encoder_helper_funcs = {
+	.enable			= zynqmp_dp_encoder_enable,
+	.disable		= zynqmp_dp_encoder_disable,
+	.atomic_mode_set	= zynqmp_dp_encoder_atomic_mode_set,
+	.atomic_check		= zynqmp_dp_encoder_atomic_check,
+};
+
+/*
+ * Component functions
+ */
+
+static void zynqmp_dp_hpd_work_func(struct work_struct *work)
+{
+	struct zynqmp_dp *dp;
+
+	dp = container_of(work, struct zynqmp_dp, hpd_work.work);
+
+	if (dp->drm)
+		drm_helper_hpd_irq_event(dp->drm);
+}
+
+int zynqmp_dp_bind(struct device *dev, struct device *master, void *data)
+{
+	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
+	struct zynqmp_dp *dp = dpsub->dp;
+	struct drm_encoder *encoder = &dp->encoder;
+	struct drm_connector *connector = &dp->connector;
+	struct drm_device *drm = data;
+	struct device_node *port;
+	int ret;
+
+	encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp);
+	for_each_child_of_node(dev->of_node, port) {
+		if (!port->name || of_node_cmp(port->name, "port"))
+			continue;
+		encoder->possible_crtcs |= drm_of_find_possible_crtcs(drm,
+								      port);
+	}
+	drm_encoder_init(drm, encoder, &zynqmp_dp_encoder_funcs,
+			 DRM_MODE_ENCODER_TMDS, NULL);
+	drm_encoder_helper_add(encoder, &zynqmp_dp_encoder_helper_funcs);
+
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+	ret = drm_connector_init(encoder->dev, connector,
+				 &zynqmp_dp_connector_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		dev_err(dp->dev, "failed to initialize the drm connector");
+		goto error_encoder;
+	}
+
+	drm_connector_helper_add(connector, &zynqmp_dp_connector_helper_funcs);
+	drm_connector_register(connector);
+	drm_mode_connector_attach_encoder(connector, encoder);
+	connector->dpms = DRM_MODE_DPMS_OFF;
+
+	dp->drm = drm;
+	zynqmp_dp_create_property(dp);
+	zynqmp_dp_attach_property(dp, &connector->base);
+
+	/* This enables interrupts, so should be called after DRM init */
+	ret = zynqmp_dp_init_aux(dp);
+	if (ret) {
+		dev_err(dp->dev, "failed to initialize DP aux");
+		goto error_prop;
+	}
+	INIT_DELAYED_WORK(&dp->hpd_work, zynqmp_dp_hpd_work_func);
+
+	return 0;
+
+error_prop:
+	zynqmp_dp_destroy_property(dp);
+	zynqmp_dp_connector_destroy(&dp->connector);
+error_encoder:
+	drm_encoder_cleanup(&dp->encoder);
+	return ret;
+}
+
+void zynqmp_dp_unbind(struct device *dev, struct device *master, void *data)
+{
+	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
+	struct zynqmp_dp *dp = dpsub->dp;
+
+	cancel_delayed_work_sync(&dp->hpd_work);
+	disable_irq(dp->irq);
+	zynqmp_dp_exit_aux(dp);
+	zynqmp_dp_destroy_property(dp);
+	zynqmp_dp_connector_destroy(&dp->connector);
+	drm_encoder_cleanup(&dp->encoder);
+}
+
+/*
+ * Platform functions
+ */
+
+static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data)
+{
+	struct zynqmp_dp *dp = (struct zynqmp_dp *)data;
+	u32 status, mask;
+
+	status = zynqmp_dp_read(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_STATUS);
+	mask = zynqmp_dp_read(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_MASK);
+	if (!(status & ~mask))
+		return IRQ_NONE;
+
+	/* dbg for diagnostic, but not much that the driver can do */
+	if (status & ZYNQMP_DP_TX_INTR_CHBUF_UNDERFLW_MASK)
+		dev_dbg_ratelimited(dp->dev, "underflow interrupt\n");
+	if (status & ZYNQMP_DP_TX_INTR_CHBUF_OVERFLW_MASK)
+		dev_dbg_ratelimited(dp->dev, "overflow interrupt\n");
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_SUB_TX_INTR_STATUS, status);
+
+	/* The DP vblank will not be enabled with remote crtc device */
+	if (status & ZYNQMP_DP_TX_INTR_VBLANK_START)
+		zynqmp_disp_handle_vblank(dp->dpsub->disp);
+
+	if (status & ZYNQMP_DP_TX_INTR_HPD_EVENT)
+		schedule_delayed_work(&dp->hpd_work, 0);
+
+	if (status & ZYNQMP_DP_TX_INTR_HPD_IRQ) {
+		int ret;
+		u8 status[DP_LINK_STATUS_SIZE + 2];
+
+		ret = drm_dp_dpcd_read(&dp->aux, DP_SINK_COUNT, status,
+				       DP_LINK_STATUS_SIZE + 2);
+		if (ret < 0)
+			goto handled;
+
+		if (status[4] & DP_LINK_STATUS_UPDATED ||
+		    !drm_dp_clock_recovery_ok(&status[2], dp->mode.lane_cnt) ||
+		    !drm_dp_channel_eq_ok(&status[2], dp->mode.lane_cnt)) {
+			zynqmp_dp_train_loop(dp);
+		}
+	}
+
+handled:
+	return IRQ_HANDLED;
+}
+
+int zynqmp_dp_probe(struct platform_device *pdev)
+{
+	struct zynqmp_dpsub *dpsub;
+	struct zynqmp_dp *dp;
+	struct resource *res;
+	unsigned int i;
+	int irq, ret;
+
+	dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL);
+	if (!dp)
+		return -ENOMEM;
+
+	dp->dpms = DRM_MODE_DPMS_OFF;
+	dp->status = connector_status_disconnected;
+	dp->dev = &pdev->dev;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp");
+	dp->iomem = devm_ioremap_resource(dp->dev, res);
+	if (IS_ERR(dp->iomem))
+		return PTR_ERR(dp->iomem);
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_PHY_POWER_DOWN,
+			ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL);
+	zynqmp_dp_set(dp->iomem, ZYNQMP_DP_TX_PHY_CONFIG,
+		      ZYNQMP_DP_TX_PHY_CONFIG_ALL_RESET);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_FORCE_SCRAMBLER_RESET, 1);
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
+
+	dp->num_lanes = 2;
+	for (i = 0; i < ZYNQMP_DP_MAX_LANES; i++) {
+		char phy_name[16];
+
+		snprintf(phy_name, sizeof(phy_name), "dp-phy%d", i);
+		dp->phy[i] = devm_phy_get(dp->dev, phy_name);
+		if (IS_ERR(dp->phy[i])) {
+			/* 2nd lane is optional */
+			if (i == 0 || PTR_ERR(dp->phy[i]) != -ENODEV) {
+				dev_err(dp->dev, "failed to get phy lane\n");
+				ret = PTR_ERR(dp->phy[i]);
+				dp->phy[i] = NULL;
+				return ret;
+			}
+			dp->phy[i] = NULL;
+			dp->num_lanes = 1;
+		}
+	}
+
+	ret = zynqmp_dp_init_phy(dp);
+	if (ret)
+		goto error_phy;
+
+	dp->aux.name = "ZynqMP DP AUX";
+	dp->aux.dev = dp->dev;
+	dp->aux.transfer = zynqmp_dp_aux_transfer;
+	ret = drm_dp_aux_register(&dp->aux);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to initialize DP aux\n");
+		goto error;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		ret = irq;
+		goto error;
+	}
+
+	ret = devm_request_threaded_irq(dp->dev, irq, NULL,
+					zynqmp_dp_irq_handler, IRQF_ONESHOT,
+					dev_name(dp->dev), dp);
+	if (ret < 0)
+		goto error;
+	dp->irq = irq;
+
+	dpsub = platform_get_drvdata(pdev);
+	dpsub->dp = dp;
+	dp->dpsub = dpsub;
+
+	return 0;
+
+error:
+	drm_dp_aux_unregister(&dp->aux);
+error_phy:
+	zynqmp_dp_exit_phy(dp);
+	return ret;
+}
+
+int zynqmp_dp_remove(struct platform_device *pdev)
+{
+	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
+	struct zynqmp_dp *dp = dpsub->dp;
+
+	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
+	drm_dp_aux_unregister(&dp->aux);
+	zynqmp_dp_exit_phy(dp);
+	dpsub->dp = NULL;
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.h b/drivers/gpu/drm/xlnx/zynqmp_dp.h
new file mode 100644
index 0000000..e5fcb66
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.h
@@ -0,0 +1,29 @@
+/*
+ * ZynqMP DisplayPort Driver
+ *
+ *  Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _ZYNQMP_DP_H_
+#define _ZYNQMP_DP_H_
+
+struct zynqmp_dp;
+struct drm_display_mode;
+
+const int zynqmp_dp_set_color(struct zynqmp_dp *dp, const char *color);
+void zynqmp_dp_enable_vblank(struct zynqmp_dp *dp);
+void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp);
+void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp,
+				       struct drm_display_mode *mode);
+
+int zynqmp_dp_bind(struct device *dev, struct device *master, void *data);
+void zynqmp_dp_unbind(struct device *dev, struct device *master, void *data);
+
+int zynqmp_dp_probe(struct platform_device *pdev);
+int zynqmp_dp_remove(struct platform_device *pdev);
+
+#endif /* _ZYNQMP_DP_H_ */
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 09/10] drm: xlnx: ZynqMP DP subsystem DRM KMS driver
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
                     ` (7 preceding siblings ...)
  2018-01-05  2:05   ` [PATCH 08/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DisplayPort Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
  2018-01-05  2:05   ` [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs Hyun Kwon
  2018-01-09  9:56   ` [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver Daniel Vetter
  10 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon

This is a wrapper around the ZynqMP Display and DisplayPort drivers.

Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 drivers/gpu/drm/xlnx/Kconfig        |  11 +++
 drivers/gpu/drm/xlnx/Makefile       |   3 +
 drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 141 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/zynqmp_dpsub.h |  19 +++++
 4 files changed, 174 insertions(+)
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.c
 create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.h

diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
index 19fd7cd..7c5529c 100644
--- a/drivers/gpu/drm/xlnx/Kconfig
+++ b/drivers/gpu/drm/xlnx/Kconfig
@@ -10,3 +10,14 @@ config DRM_XLNX
 	  display pipeline using Xilinx IPs in FPGA. This module
 	  provides the kernel mode setting functionalities
 	  for Xilinx display drivers.
+
+config DRM_ZYNQMP_DPSUB
+	tristate "ZynqMP DP Subsystem Driver"
+	depends on ARCH_ZYNQMP && OF && DRM_XLNX && COMMON_CLK
+	select DMA_ENGINE
+	select GENERIC_PHY
+	help
+	  DRM KMS driver for ZynqMP DP Subsystem controller. Choose
+	  this option if you have a Xilinx ZynqMP SoC with DisplayPort
+	  subsystem. The driver provides the kernel mode setting
+	  functionlaities for ZynqMP DP subsystem.
diff --git a/drivers/gpu/drm/xlnx/Makefile b/drivers/gpu/drm/xlnx/Makefile
index c60a281..064a05a 100644
--- a/drivers/gpu/drm/xlnx/Makefile
+++ b/drivers/gpu/drm/xlnx/Makefile
@@ -1,2 +1,5 @@
 xlnx_drm-objs += xlnx_crtc.o xlnx_drv.o xlnx_fb.o xlnx_gem.o
 obj-$(CONFIG_DRM_XLNX) += xlnx_drm.o
+
+zynqmp-dpsub-objs += zynqmp_disp.o zynqmp_dpsub.o zynqmp_dp.o
+obj-$(CONFIG_DRM_ZYNQMP_DPSUB) += zynqmp-dpsub.o
diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
new file mode 100644
index 0000000..ccca798
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c
@@ -0,0 +1,141 @@
+/*
+ * ZynqMP DP Subsystem Driver
+ *
+ *  Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include "zynqmp_disp.h"
+#include "zynqmp_dp.h"
+#include "zynqmp_dpsub.h"
+
+static int
+zynqmp_dpsub_bind(struct device *dev, struct device *master, void *data)
+{
+	int ret;
+
+	ret = zynqmp_disp_bind(dev, master, data);
+	if (ret)
+		return ret;
+
+	/* zynqmp_disp should bind first, so zynqmp_dp encoder can find crtc */
+	ret = zynqmp_dp_bind(dev, master, data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void
+zynqmp_dpsub_unbind(struct device *dev, struct device *master, void *data)
+{
+	zynqmp_dp_unbind(dev, master, data);
+	zynqmp_disp_unbind(dev, master, data);
+}
+
+static const struct component_ops zynqmp_dpsub_component_ops = {
+	.bind	= zynqmp_dpsub_bind,
+	.unbind	= zynqmp_dpsub_unbind,
+};
+
+static int zynqmp_dpsub_probe(struct platform_device *pdev)
+{
+	struct zynqmp_dpsub *dpsub;
+	int ret;
+
+	dpsub = devm_kzalloc(&pdev->dev, sizeof(*dpsub), GFP_KERNEL);
+	if (!dpsub)
+		return -ENOMEM;
+
+	/* Sub-driver will access dpsub from drvdata */
+	platform_set_drvdata(pdev, dpsub);
+	pm_runtime_enable(&pdev->dev);
+
+	/*
+	 * DP should be probed first so that the zynqmp_disp can set the output
+	 * format accordingly.
+	 */
+	ret = zynqmp_dp_probe(pdev);
+	if (ret)
+		goto err_pm;
+
+	ret = zynqmp_disp_probe(pdev);
+	if (ret)
+		goto err_dp;
+
+	ret = component_add(&pdev->dev, &zynqmp_dpsub_component_ops);
+	if (ret)
+		goto err_disp;
+
+	/* Populate the sound child nodes */
+	ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to populate child nodes\n");
+		goto err_component;
+	}
+
+	dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed");
+
+	return 0;
+
+err_component:
+	component_del(&pdev->dev, &zynqmp_dpsub_component_ops);
+err_disp:
+	zynqmp_disp_remove(pdev);
+err_dp:
+	zynqmp_dp_remove(pdev);
+err_pm:
+	pm_runtime_disable(&pdev->dev);
+	return ret;
+}
+
+static int zynqmp_dpsub_remove(struct platform_device *pdev)
+{
+	int err, ret = 0;
+
+	of_platform_depopulate(&pdev->dev);
+	component_del(&pdev->dev, &zynqmp_dpsub_component_ops);
+
+	err = zynqmp_disp_remove(pdev);
+	if (err)
+		ret = -EIO;
+
+	err = zynqmp_dp_remove(pdev);
+	if (err)
+		ret = -EIO;
+
+	pm_runtime_disable(&pdev->dev);
+
+	return err;
+}
+
+static const struct of_device_id zynqmp_dpsub_of_match[] = {
+	{ .compatible = "xlnx,zynqmp-dpsub-1.7", },
+	{ /* end of table */ },
+};
+MODULE_DEVICE_TABLE(of, zynqmp_dpsub_of_match);
+
+static struct platform_driver zynqmp_dpsub_driver = {
+	.probe			= zynqmp_dpsub_probe,
+	.remove			= zynqmp_dpsub_remove,
+	.driver			= {
+		.owner		= THIS_MODULE,
+		.name		= "zynqmp-display",
+		.of_match_table	= zynqmp_dpsub_of_match,
+	},
+};
+
+module_platform_driver(zynqmp_dpsub_driver);
+
+MODULE_AUTHOR("Xilinx, Inc.");
+MODULE_DESCRIPTION("ZynqMP DP Subsystem Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
new file mode 100644
index 0000000..df5713a
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h
@@ -0,0 +1,19 @@
+/*
+ * ZynqMP DPSUB Subsystem Driver
+ *
+ *  Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _ZYNQMP_DPSUB_H_
+#define _ZYNQMP_DPSUB_H_
+
+struct zynqmp_dpsub {
+	struct zynqmp_dp *dp;
+	struct zynqmp_disp *disp;
+};
+
+#endif /* _ZYNQMP_DPSUB_H_ */
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
                     ` (8 preceding siblings ...)
  2018-01-05  2:05   ` [PATCH 09/10] drm: xlnx: ZynqMP DP subsystem DRM KMS driver Hyun Kwon
@ 2018-01-05  2:05   ` Hyun Kwon
  2018-01-09  9:54     ` Daniel Vetter
  2018-01-09  9:56   ` [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver Daniel Vetter
  10 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-05  2:05 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Michal Simek, Hyun Kwon, Tejas Upadhyay

Debugfs can be used to exploit some specific setting. Main purpose
is for testing and debug diagnostic.

Signed-off-by: Tejas Upadhyay <tejasu-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
---
 drivers/gpu/drm/xlnx/Kconfig       |  21 +++
 drivers/gpu/drm/xlnx/zynqmp_disp.c | 326 +++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/xlnx/zynqmp_dp.c   | 304 ++++++++++++++++++++++++++++++++++
 3 files changed, 651 insertions(+)

diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
index 7c5529c..befce0f 100644
--- a/drivers/gpu/drm/xlnx/Kconfig
+++ b/drivers/gpu/drm/xlnx/Kconfig
@@ -21,3 +21,24 @@ config DRM_ZYNQMP_DPSUB
 	  this option if you have a Xilinx ZynqMP SoC with DisplayPort
 	  subsystem. The driver provides the kernel mode setting
 	  functionlaities for ZynqMP DP subsystem.
+
+config DRM_ZYNQMP_DISP_DEBUG_FS
+	bool "ZynqMP Display debugfs"
+	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
+	select DRM_ZYNQMP_DP_DEBUG_FS
+	help
+	  Enable the debugfs code for DP Sub driver. The debugfs code
+	  enables debugging or testing related features. It exposes some
+	  low level controls to the user space to help testing automation,
+	  as well as can enable additional diagnostic or statistical
+	  information.
+
+config DRM_ZYNQMP_DP_DEBUG_FS
+	bool "ZynqMP DP debugfs"
+	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
+	help
+	  Enable the debugfs code for DP driver. The debugfs code
+	  enables debugging or testing related features. It exposes some
+	  low level controls to the user space to help testing automation,
+	  as well as can enable additional diagnostic or statistical
+	  information.
diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c
index 68f829c..9fe6d49 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
@@ -17,6 +17,7 @@
 #include <drm/drm_plane_helper.h>
 
 #include <linux/clk.h>
+#include <linux/debugfs.h>
 #include <linux/device.h>
 #include <linux/dmaengine.h>
 #include <linux/interrupt.h>
@@ -508,6 +509,325 @@ static void zynqmp_disp_set(void __iomem *base, int offset, u32 set)
 	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) | set);
 }
 
+#ifdef CONFIG_DRM_ZYNQMP_DISP_DEBUG_FS
+
+#define ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE	32UL
+#define ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL	0xFFF
+#define IN_RANGE(x, min, max) ({		\
+		typeof(x) _x = (x);		\
+		_x >= (min) && _x <= (max); })
+
+/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
+enum zynqmp_disp_testcases {
+	DP_SUB_TC_BG_COLOR,
+	DP_SUB_TC_OUTPUT_FMT,
+	DP_SUB_TC_NONE
+};
+
+struct zynqmp_disp_debugfs {
+	enum zynqmp_disp_testcases testcase;
+	u16 r_value;
+	u16 g_value;
+	u16 b_value;
+	u32 output_fmt;
+	struct zynqmp_disp *zynqmp_disp;
+};
+
+static struct dentry *zynqmp_disp_debugfs_dir;
+struct zynqmp_disp_debugfs disp_debugfs;
+struct zynqmp_disp_debugfs_request {
+	const char *req;
+	enum zynqmp_disp_testcases tc;
+	ssize_t (*read_handler)(char **kern_buff);
+	ssize_t (*write_handler)(char **cmd);
+};
+
+static void
+zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int id);
+static s64 zynqmp_disp_debugfs_argument_value(char *arg)
+{
+	s64 value;
+
+	if (!arg)
+		return -1;
+
+	if (!kstrtos64(arg, 0, &value))
+		return value;
+
+	return -1;
+}
+
+static ssize_t
+zynqmp_disp_debugfs_background_color_write(char **disp_test_arg)
+{
+	char *r_color, *g_color, *b_color;
+	s64 r_val, g_val, b_val;
+
+	r_color = strsep(disp_test_arg, " ");
+	g_color = strsep(disp_test_arg, " ");
+	b_color = strsep(disp_test_arg, " ");
+
+	/* char * to int conversion */
+	r_val = zynqmp_disp_debugfs_argument_value(r_color);
+	g_val = zynqmp_disp_debugfs_argument_value(g_color);
+	b_val = zynqmp_disp_debugfs_argument_value(b_color);
+
+	if (!(IN_RANGE(r_val, 0, ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
+	      IN_RANGE(g_val, 0, ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
+	      IN_RANGE(b_val, 0, ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL)))
+		return -EINVAL;
+
+	disp_debugfs.r_value = r_val;
+	disp_debugfs.g_value = g_val;
+	disp_debugfs.b_value = b_val;
+
+	disp_debugfs.testcase = DP_SUB_TC_BG_COLOR;
+
+	return 0;
+}
+
+static ssize_t
+zynqmp_disp_debugfs_output_display_format_write(char **disp_test_arg)
+{
+	char *output_format;
+	struct zynqmp_disp *disp = disp_debugfs.zynqmp_disp;
+
+	/* Read the value from a user value */
+	output_format = strsep(disp_test_arg, " ");
+	if (strncmp(output_format, "rgb", 3) == 0) {
+		disp_debugfs.output_fmt =
+			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB;
+	} else if (strncmp(output_format, "ycbcr444", 8) == 0) {
+		disp_debugfs.output_fmt =
+			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444;
+	} else if (strncmp(output_format, "ycbcr422", 8) == 0) {
+		disp_debugfs.output_fmt =
+			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422;
+	} else if (strncmp(output_format, "yonly", 5) == 0) {
+		disp_debugfs.output_fmt =
+			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY;
+	} else {
+		dev_err(disp->dev, "Invalid output format\n");
+		return -EINVAL;
+	}
+
+	disp_debugfs.testcase = DP_SUB_TC_OUTPUT_FMT;
+
+	return 0;
+}
+
+static ssize_t
+zynqmp_disp_debugfs_output_display_format_read(char **kern_buff)
+{
+	size_t out_str_len;
+
+	disp_debugfs.testcase = DP_SUB_TC_NONE;
+	disp_debugfs.output_fmt = 0;
+
+	out_str_len = strlen("Success");
+	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE, out_str_len);
+	snprintf(*kern_buff, out_str_len, "%s", "Success");
+
+	return 0;
+}
+
+static ssize_t
+zynqmp_disp_debugfs_background_color_read(char **kern_buff)
+{
+	size_t out_str_len;
+
+	disp_debugfs.testcase = DP_SUB_TC_NONE;
+	disp_debugfs.r_value = 0;
+	disp_debugfs.g_value = 0;
+	disp_debugfs.b_value = 0;
+
+	out_str_len = strlen("Success");
+	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE, out_str_len);
+	snprintf(*kern_buff, out_str_len, "%s", "Success");
+
+	return 0;
+}
+
+/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
+struct zynqmp_disp_debugfs_request disp_debugfs_reqs[] = {
+	{"BACKGROUND_COLOR", DP_SUB_TC_BG_COLOR,
+		zynqmp_disp_debugfs_background_color_read,
+		zynqmp_disp_debugfs_background_color_write},
+	{"OUTPUT_DISPLAY_FORMAT", DP_SUB_TC_OUTPUT_FMT,
+		zynqmp_disp_debugfs_output_display_format_read,
+		zynqmp_disp_debugfs_output_display_format_write},
+};
+
+static ssize_t
+zynqmp_disp_debugfs_write(struct file *f, const char __user *buf,
+			  size_t size, loff_t *pos)
+{
+	char *kern_buff, *disp_test_req, *kern_buff_start;
+	int ret;
+	unsigned int i;
+
+	if (*pos != 0 || size <= 0)
+		return -EINVAL;
+
+	if (disp_debugfs.testcase != DP_SUB_TC_NONE)
+		return -EBUSY;
+
+	kern_buff = kzalloc(size, GFP_KERNEL);
+	if (!kern_buff)
+		return -ENOMEM;
+	kern_buff_start = kern_buff;
+
+	ret = strncpy_from_user(kern_buff, buf, size);
+	if (ret < 0) {
+		kfree(kern_buff_start);
+		return ret;
+	}
+
+	/* Read the testcase name and argument from a user request */
+	disp_test_req = strsep(&kern_buff, " ");
+
+	for (i = 0; i < ARRAY_SIZE(disp_debugfs_reqs); i++) {
+		if (!strcasecmp(disp_test_req, disp_debugfs_reqs[i].req))
+			if (!disp_debugfs_reqs[i].write_handler(&kern_buff)) {
+				kfree(kern_buff_start);
+				return size;
+			}
+	}
+	kfree(kern_buff_start);
+	return -EINVAL;
+}
+
+static ssize_t zynqmp_disp_debugfs_read(struct file *f, char __user *buf,
+					size_t size, loff_t *pos)
+{
+	char *kern_buff = NULL;
+	size_t kern_buff_len, out_str_len;
+	enum zynqmp_disp_testcases tc;
+	int ret;
+
+	if (size <= 0)
+		return -EINVAL;
+
+	if (*pos != 0)
+		return 0;
+
+	kern_buff = kzalloc(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE, GFP_KERNEL);
+	if (!kern_buff) {
+		disp_debugfs.testcase = DP_SUB_TC_NONE;
+		return -ENOMEM;
+	}
+
+	tc = disp_debugfs.testcase;
+	if (tc == DP_SUB_TC_NONE) {
+		out_str_len = strlen("No testcase executed");
+		out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
+				  out_str_len);
+		snprintf(kern_buff, out_str_len, "%s", "No testcase executed");
+	} else {
+		ret = disp_debugfs_reqs[tc].read_handler(&kern_buff);
+		if (ret) {
+			kfree(kern_buff);
+			return ret;
+		}
+	}
+
+	kern_buff_len = strlen(kern_buff);
+	size = min(size, kern_buff_len);
+
+	ret = copy_to_user(buf, kern_buff, size);
+
+	kfree(kern_buff);
+	if (ret)
+		return ret;
+
+	*pos = size + 1;
+	return size;
+}
+
+static const struct file_operations fops_zynqmp_disp_dbgfs = {
+	.owner = THIS_MODULE,
+	.read = zynqmp_disp_debugfs_read,
+	.write = zynqmp_disp_debugfs_write,
+};
+
+static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
+{
+	int err;
+	struct dentry *zynqmp_disp_debugfs_file;
+
+	disp_debugfs.testcase = DP_SUB_TC_NONE;
+	disp_debugfs.zynqmp_disp = disp;
+
+	zynqmp_disp_debugfs_dir = debugfs_create_dir("disp", NULL);
+	if (!zynqmp_disp_debugfs_dir) {
+		dev_err(disp->dev, "debugfs_create_dir failed\n");
+		return -ENODEV;
+	}
+
+	zynqmp_disp_debugfs_file =
+		debugfs_create_file("testcase", 0444,
+				    zynqmp_disp_debugfs_dir, NULL,
+				    &fops_zynqmp_disp_dbgfs);
+	if (!zynqmp_disp_debugfs_file) {
+		dev_err(disp->dev, "debugfs_create_file testcase failed\n");
+		err = -ENODEV;
+		goto err_dbgfs;
+	}
+	return 0;
+
+err_dbgfs:
+	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
+	zynqmp_disp_debugfs_dir = NULL;
+	return err;
+}
+
+static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
+{
+	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
+	zynqmp_disp_debugfs_dir = NULL;
+}
+
+static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
+{
+	if (disp_debugfs.testcase == DP_SUB_TC_BG_COLOR) {
+		zynqmp_disp_write(disp->blend.base,
+				  ZYNQMP_DISP_V_BLEND_BG_CLR_0,
+				  disp_debugfs.r_value);
+		zynqmp_disp_write(disp->blend.base,
+				  ZYNQMP_DISP_V_BLEND_BG_CLR_1,
+				  disp_debugfs.g_value);
+		zynqmp_disp_write(disp->blend.base,
+				  ZYNQMP_DISP_V_BLEND_BG_CLR_2,
+				  disp_debugfs.b_value);
+	}
+}
+
+static void
+zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
+{
+	if (disp_debugfs.testcase == DP_SUB_TC_OUTPUT_FMT)
+		zynqmp_disp_set_output_fmt(disp, disp_debugfs.output_fmt);
+}
+#else
+static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
+{
+}
+
+static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
+{
+	return 0;
+}
+
+static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
+{
+}
+
+static void
+zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
+{
+}
+#endif /* CONFIG_DP_DEBUG_FS */
+
 /*
  * Clock functions
  */
@@ -597,6 +917,8 @@ zynqmp_disp_blend_set_output_fmt(struct zynqmp_disp_blend *blend, u32 fmt)
 	u32 *offsets;
 	u32 offset, i;
 
+	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422)
+		fmt |= ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE;
 	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
 	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
 		coeffs = reset_coeffs;
@@ -1941,6 +2263,7 @@ static void zynqmp_disp_set_bg_color(struct zynqmp_disp *disp,
 				     u32 c0, u32 c1, u32 c2)
 {
 	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
+	zynqmp_disp_debugfs_bg_color(disp);
 }
 
 /**
@@ -2572,6 +2895,7 @@ zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc,
 		return;
 	}
 	zynqmp_disp_set_output_fmt(disp, disp->color);
+	zynqmp_disp_set_debugfs_output_fmt(disp);
 	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp->bg_c2);
 	zynqmp_disp_enable(disp);
 	/* Delay of 3 vblank intervals for timing gen to be stable */
@@ -2911,6 +3235,7 @@ int zynqmp_disp_probe(struct platform_device *pdev)
 	ret = zynqmp_disp_layer_create(disp);
 	if (ret)
 		goto error_aclk;
+	zynqmp_disp_debugfs_init(disp);
 
 	return 0;
 
@@ -2924,6 +3249,7 @@ int zynqmp_disp_remove(struct platform_device *pdev)
 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
 	struct zynqmp_disp *disp = dpsub->disp;
 
+	zynqmp_disp_debugfs_exit(disp);
 	zynqmp_disp_layer_destroy(disp);
 	if (disp->audclk)
 		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
index ce3c7c5..66fbad0 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
@@ -16,6 +16,7 @@
 #include <drm/drm_dp_helper.h>
 #include <drm/drm_of.h>
 
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/module.h>
@@ -371,6 +372,306 @@ static void zynqmp_dp_set(void __iomem *base, int offset, u32 set)
 }
 
 /*
+ * Debugfs functions
+ */
+
+#ifdef CONFIG_DRM_ZYNQMP_DP_DEBUG_FS
+
+#define ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE	32UL
+#define ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR	"255"
+#define IN_RANGE(x, min, max) ({		\
+		typeof(x) _x = (x);		\
+		_x >= (min) && _x <= (max); })
+
+/* Match zynqmp_dp_testcases vs debugfs_reqs[] entry */
+enum zynqmp_dp_testcases {
+	DP_TC_LINK_RATE,
+	DP_TC_LANE_COUNT,
+	DP_TC_OUTPUT_FMT,
+	DP_TC_NONE
+};
+
+struct zynqmp_dp_debugfs {
+	enum zynqmp_dp_testcases testcase;
+	u8 link_rate;
+	u8 lane_cnt;
+	u8 old_output_fmt;
+	struct zynqmp_dp *dp;
+};
+
+static struct dentry *zynqmp_dp_debugfs_dir;
+static struct zynqmp_dp_debugfs dp_debugfs;
+struct zynqmp_dp_debugfs_request {
+	const char *req;
+	enum zynqmp_dp_testcases tc;
+	ssize_t (*read_handler)(char **kern_buff);
+	ssize_t (*write_handler)(char **cmd);
+};
+
+static s64 zynqmp_dp_debugfs_argument_value(char *arg)
+{
+	s64 value;
+
+	if (!arg)
+		return -1;
+
+	if (!kstrtos64(arg, 0, &value))
+		return value;
+
+	return -1;
+}
+
+static ssize_t zynqmp_dp_debugfs_max_linkrate_write(char **dp_test_arg)
+{
+	char *link_rate_arg;
+	s64 link_rate;
+
+	link_rate_arg = strsep(dp_test_arg, " ");
+	link_rate = zynqmp_dp_debugfs_argument_value(link_rate_arg);
+	if (link_rate < 0 || (link_rate != DP_HIGH_BIT_RATE2 &&
+			      link_rate != DP_HIGH_BIT_RATE &&
+			      link_rate != DP_REDUCED_BIT_RATE))
+		return -EINVAL;
+
+	dp_debugfs.link_rate = drm_dp_link_rate_to_bw_code(link_rate);
+	dp_debugfs.testcase = DP_TC_LINK_RATE;
+
+	return 0;
+}
+
+static ssize_t zynqmp_dp_debugfs_max_lanecnt_write(char **dp_test_arg)
+{
+	char *lane_cnt_arg;
+	s64 lane_count;
+
+	lane_cnt_arg = strsep(dp_test_arg, " ");
+	lane_count = zynqmp_dp_debugfs_argument_value(lane_cnt_arg);
+	if (lane_count < 0 || !IN_RANGE(lane_count, 1,
+					ZYNQMP_DP_MAX_LANES))
+		return -EINVAL;
+
+	dp_debugfs.lane_cnt = lane_count;
+	dp_debugfs.testcase = DP_TC_LANE_COUNT;
+
+	return 0;
+}
+
+static ssize_t zynqmp_dp_debugfs_max_linkrate_read(char **kern_buff)
+{
+	struct zynqmp_dp *dp = dp_debugfs.dp;
+	size_t output_str_len;
+	u8 dpcd_link_bw;
+	int ret;
+
+	dp_debugfs.testcase = DP_TC_NONE;
+	dp_debugfs.link_rate = 0;
+
+	/* Getting Sink Side Link Rate */
+	ret = drm_dp_dpcd_readb(&dp->aux, DP_LINK_BW_SET, &dpcd_link_bw);
+	if (ret < 0) {
+		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
+		kfree(*kern_buff);
+		return ret;
+	}
+
+	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
+	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE, output_str_len);
+	snprintf(*kern_buff, output_str_len, "%u", dpcd_link_bw);
+
+	return 0;
+}
+
+static ssize_t zynqmp_dp_debugfs_max_lanecnt_read(char **kern_buff)
+{
+	struct zynqmp_dp *dp = dp_debugfs.dp;
+	size_t output_str_len;
+	u8 dpcd_lane_cnt;
+	int ret;
+
+	dp_debugfs.testcase = DP_TC_NONE;
+	dp_debugfs.lane_cnt = 0;
+
+	/* Getting Sink Side Lane Count */
+	ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_COUNT_SET, &dpcd_lane_cnt);
+	if (ret < 0) {
+		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
+		kfree(*kern_buff);
+		return ret;
+	}
+
+	dpcd_lane_cnt &= DP_LANE_COUNT_MASK;
+	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
+	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE, output_str_len);
+	snprintf(*kern_buff, output_str_len, "%u", dpcd_lane_cnt);
+
+	return 0;
+}
+
+/* Match zynqmp_dp_testcases vs dp_debugfs_reqs[] entry */
+static struct zynqmp_dp_debugfs_request debugfs_reqs[] = {
+	{"LINK_RATE", DP_TC_LINK_RATE,
+			zynqmp_dp_debugfs_max_linkrate_read,
+			zynqmp_dp_debugfs_max_linkrate_write},
+	{"LANE_COUNT", DP_TC_LANE_COUNT,
+			zynqmp_dp_debugfs_max_lanecnt_read,
+			zynqmp_dp_debugfs_max_lanecnt_write},
+};
+
+static ssize_t zynqmp_dp_debugfs_read(struct file *f, char __user *buf,
+				      size_t size, loff_t *pos)
+{
+	char *kern_buff = NULL;
+	size_t kern_buff_len, out_str_len;
+	enum zynqmp_dp_testcases tc;
+	int ret;
+
+	if (size <= 0)
+		return -EINVAL;
+
+	if (*pos != 0)
+		return 0;
+
+	kern_buff = kzalloc(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE, GFP_KERNEL);
+	if (!kern_buff) {
+		dp_debugfs.testcase = DP_TC_NONE;
+		return -ENOMEM;
+	}
+
+	tc = dp_debugfs.testcase;
+	if (tc == DP_TC_NONE) {
+		out_str_len = strlen("No testcase executed");
+		out_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE, out_str_len);
+		snprintf(kern_buff, out_str_len, "%s", "No testcase executed");
+	} else {
+		ret = debugfs_reqs[tc].read_handler(&kern_buff);
+		if (ret) {
+			kfree(kern_buff);
+			return ret;
+		}
+	}
+
+	kern_buff_len = strlen(kern_buff);
+	size = min(size, kern_buff_len);
+
+	ret = copy_to_user(buf, kern_buff, size);
+
+	kfree(kern_buff);
+	if (ret)
+		return ret;
+
+	*pos = size + 1;
+	return size;
+}
+
+static ssize_t
+zynqmp_dp_debugfs_write(struct file *f, const char __user *buf,
+			size_t size, loff_t *pos)
+{
+	char *kern_buff, *kern_buff_start;
+	char *dp_test_req;
+	int ret;
+	int i;
+
+	if (*pos != 0 || size <= 0)
+		return -EINVAL;
+
+	if (dp_debugfs.testcase != DP_TC_NONE)
+		return -EBUSY;
+
+	kern_buff = kzalloc(size, GFP_KERNEL);
+	if (!kern_buff)
+		return -ENOMEM;
+	kern_buff_start = kern_buff;
+
+	ret = strncpy_from_user(kern_buff, buf, size);
+	if (ret < 0) {
+		kfree(kern_buff_start);
+		return ret;
+	}
+
+	/* Read the testcase name and argument from a user request */
+	dp_test_req = strsep(&kern_buff, " ");
+
+	for (i = 0; i < ARRAY_SIZE(debugfs_reqs); i++) {
+		if (!strcasecmp(dp_test_req, debugfs_reqs[i].req))
+			if (!debugfs_reqs[i].write_handler(&kern_buff)) {
+				kfree(kern_buff_start);
+				return size;
+			}
+	}
+
+	kfree(kern_buff_start);
+	return -EINVAL;
+}
+
+static const struct file_operations fops_zynqmp_dp_dbgfs = {
+	.owner = THIS_MODULE,
+	.read = zynqmp_dp_debugfs_read,
+	.write = zynqmp_dp_debugfs_write,
+};
+
+static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
+{
+	int err;
+	struct dentry *zynqmp_dp_debugfs_file;
+
+	dp_debugfs.testcase = DP_TC_NONE;
+	dp_debugfs.dp = dp;
+
+	zynqmp_dp_debugfs_dir = debugfs_create_dir("dp", NULL);
+	if (!zynqmp_dp_debugfs_dir) {
+		dev_err(dp->dev, "debugfs_create_dir failed\n");
+		return -ENODEV;
+	}
+
+	zynqmp_dp_debugfs_file =
+		debugfs_create_file("testcase", 0444, zynqmp_dp_debugfs_dir,
+				    NULL, &fops_zynqmp_dp_dbgfs);
+	if (!zynqmp_dp_debugfs_file) {
+		dev_err(dp->dev, "debugfs_create_file testcase failed\n");
+		err = -ENODEV;
+		goto err_dbgfs;
+	}
+	return 0;
+
+err_dbgfs:
+	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
+	zynqmp_dp_debugfs_dir = NULL;
+	return err;
+}
+
+static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
+{
+	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
+	zynqmp_dp_debugfs_dir = NULL;
+}
+
+static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
+{
+	dp->mode.bw_code =
+		dp_debugfs.link_rate ? dp_debugfs.link_rate : dp->mode.bw_code;
+	dp->mode.lane_cnt =
+		dp_debugfs.lane_cnt ? dp_debugfs.lane_cnt : dp->mode.lane_cnt;
+}
+
+#else /* DRM_ZYNQMP_DP_DEBUG_FS */
+
+static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
+{
+	return 0;
+}
+
+static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
+{
+}
+
+static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
+{
+}
+
+#endif /* DRM_ZYNQMP_DP_DEBUG_FS */
+
+/*
  * Internal functions: used by zynqmp_disp.c
  */
 
@@ -597,6 +898,7 @@ static int zynqmp_dp_mode_configure(struct zynqmp_dp *dp, int pclock,
 			dp->mode.bw_code = bws[i];
 			dp->mode.lane_cnt = lane_cnt;
 			dp->mode.pclock = pclock;
+			zynqmp_dp_debugfs_mode_config(dp);
 			return dp->mode.bw_code;
 		}
 	}
@@ -1840,6 +2142,7 @@ int zynqmp_dp_probe(struct platform_device *pdev)
 	dpsub = platform_get_drvdata(pdev);
 	dpsub->dp = dp;
 	dp->dpsub = dpsub;
+	zynqmp_dp_debugfs_init(dp);
 
 	return 0;
 
@@ -1855,6 +2158,7 @@ int zynqmp_dp_remove(struct platform_device *pdev)
 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
 	struct zynqmp_dp *dp = dpsub->dp;
 
+	zynqmp_dp_debugfs_exit(dp);
 	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
 	drm_dp_aux_unregister(&dp->aux);
 	zynqmp_dp_exit_phy(dp);
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms bindings
       [not found]     ` <1515117959-18068-2-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
@ 2018-01-09  4:00       ` Rob Herring
  2018-01-11  2:04         ` Hyun Kwon
  0 siblings, 1 reply; 34+ messages in thread
From: Rob Herring @ 2018-01-09  4:00 UTC (permalink / raw)
  To: Hyun Kwon
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

On Thu, Jan 04, 2018 at 06:05:50PM -0800, Hyun Kwon wrote:
> The dt binding for Xilinx DRM KMS driver.

Bindings are for h/w, not drivers.

> 
> Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> ---
>  .../devicetree/bindings/display/xlnx/xlnx,kms.txt    | 20 ++++++++++++++++++++
>  1 file changed, 20 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> 
> diff --git a/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> new file mode 100644
> index 0000000..8dcd552
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> @@ -0,0 +1,20 @@
> +Xilinx KMS Pipeline
> +-------------------
> +
> +Xilinx display pipelines can be designed with hardened video IPs and soft video
> +IPs in programmable logic. This KMS module provides the common functionality
> +of individual subdevice drivers, and glue logics between them.
> +
> +Required properties:
> +
> +- compatible: Must be "xlnx,kms".
> +
> +- ports: phandles for CRTC ports, using the DT bindings defined in
> +  Documentation/devicetree/bindings/graph.txt.

This use of ports is not part of the graph binding.

> +
> +Example:
> +
> +	xlnx_drm: xlnx_drm {
> +		compatible = "xlnx,kms";

drm and kms are Linuxisms.

Why do you need this node?

> +		ports = <&crtc_port>;
> +	};
> -- 
> 2.7.4
> 
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 06/10] dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings
  2018-01-05  2:05   ` [PATCH 06/10] dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings Hyun Kwon
@ 2018-01-09  4:07     ` Rob Herring
  2018-01-11  2:06       ` Hyun Kwon
  0 siblings, 1 reply; 34+ messages in thread
From: Rob Herring @ 2018-01-09  4:07 UTC (permalink / raw)
  To: Hyun Kwon; +Cc: devicetree, Michal Simek, dri-devel

On Thu, Jan 04, 2018 at 06:05:55PM -0800, Hyun Kwon wrote:
> This add a dt binding for ZynqMP DP subsystem.
> 
> Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> ---
>  .../bindings/display/xlnx/xlnx,zynqmp-dpsub.txt    | 94 ++++++++++++++++++++++
>  1 file changed, 94 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
> 
> diff --git a/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt b/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
> new file mode 100644
> index 0000000..4e478ca
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
> @@ -0,0 +1,94 @@
> +Xilinx ZynqMP DisplayPort subsystem
> +-----------------------------------
> +
> +Required properties:
> +
> +- compatible: Must be "xlnx,zynqmp-dpsub-1.7".
> +
> +- reg: Physical base address and length of the registers set for the device.
> +- reg-names: Must be "dp", "blend", "av_buf", and "aud" to map logical register
> +  partitions.
> +
> +- interrupts: Interrupt number.
> +- interrupts-parent: phandle for interrupt controller.
> +
> +- clocks: phandles for axi, audio, non-live video, and live video clocks.
> +  axi clock is required. Audio clock is optional. If not present, audio will
> +  be disabled. One of non-live or live video clock should be present.
> +- clock-names: The identification strings are required. "aclk" for axi clock.
> +  "dp_aud_clk" for audio clock. "dp_vtc_pixel_clk_in" for non-live video clock.
> +  "dp_live_video_in_clk" for live video clock (clock from programmable logic).

"_clk" is redundant.

> +
> +- phys: phandles for phy specifier.
> +- phy-names: The identifier strings. "dp-phy" followed by index.
> +
> +- power-domains: phandle for the corresponding power domain
> +
> +- ports: crtc and encoder ports are required using DT bindings defined in
> +  Documentation/devicetree/bindings/graph.txt.

Isn't the connection crtc->encoder?

Also, crtc is pretty much a DRM term. Don't use that in bindings. 
Describe the h/w blocks.

> +
> +- vid-layer, gfx-layer: Required to represent available layers
> +
> +Required layer properties
> +
> +- dmas: phandles for DMA channels as defined in
> +  Documentation/devicetree/bindings/dma/dma.txt.
> +- dma-names: The identifier strings are required. "graphics0" for graphics
> +  layer. "video" followed by index for video layer
> +
> +Optional child node
> +
> +- The driver populates any child device node in this node. This can be used,
> +  for example, to populate the sound device from the DisplayPort subsystem
> +  driver.
> +
> +Example:
> +	zynqmp_dpsub: zynqmp_dpsub@fd4a0000 {

display-controller@...

> +		compatible = "xlnx,zynqmp-dpsub-1.7";
> +		reg = <0x0 0xfd4a0000 0x0 0x1000>,
> +		      <0x0 0xfd4aa000 0x0 0x1000>,
> +		      <0x0 0xfd4ab000 0x0 0x1000>,
> +		      <0x0 0xfd4ac000 0x0 0x1000>;
> +		reg-names = "dp", "blend", "av_buf", "aud";
> +		interrupts = <0 119 4>;
> +		interrupt-parent = <&gic>;
> +
> +		clock-names = "dp_apb_clk", "dp_aud_clk", "dp_live_video_in_clk";
> +		clocks = <&dp_aclk>, <&clkc 17>, <&si570_1>;
> +
> +		phys = <&lane1 PHY_TYPE_DP 0 1 27000000>,
> +		       <&lane0 PHY_TYPE_DP 1 1 27000000>;
> +		phy-names = "dp-phy0", "dp-phy1";
> +
> +		power-domains = <&pd_dp>;
> +
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		vid-layer {
> +			dma-names = "vid0", "vid1", "vid2";
> +			dmas = <&xlnx_dpdma 0>,
> +			       <&xlnx_dpdma 1>,
> +			       <&xlnx_dpdma 2>;
> +		};
> +
> +		gfx-layer {

These 2 nodes don't seem necessary. Just make "dmas" take 4 values and 
define where the gfx0 channel is located (index 0 or 3?).

> +			dma-names = "gfx0";
> +			dmas = <&xlnx_dpdma 3>;
> +		};
> +
> +		crtc_port: port@0 {
> +			reg = <0>;
> +			crtc: endpoint {
> +				remote-endpoint = <&encoder>;
> +			};
> +		};
> +		port@1 {

Multiple port nodes should be under a ports node especially if you have 
other child nodes.

> +			reg = <1>;
> +			encoder: endpoint {
> +				remote-endpoint = <&crtc>;
> +			};
> +		};
> +	};
> +};
> +
> -- 
> 2.7.4
> 
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 03/10] drm: xlnx: Add xlnx fb of Xilinx DRM KMS
  2018-01-05  2:05   ` [PATCH 03/10] drm: xlnx: Add xlnx fb " Hyun Kwon
@ 2018-01-09  9:35     ` Daniel Vetter
  2018-01-11  2:04       ` Hyun Kwon
  0 siblings, 1 reply; 34+ messages in thread
From: Daniel Vetter @ 2018-01-09  9:35 UTC (permalink / raw)
  To: Hyun Kwon; +Cc: devicetree, Michal Simek, dri-devel

On Thu, Jan 04, 2018 at 06:05:52PM -0800, Hyun Kwon wrote:
> Helpers for framebuffers backed by cma allocator.
> 
> Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>

Please take a look at the very new drm_gem_framebuffer_helper.c file.
There's lots of helpers in there that you can use to remove almost the
entire file here :-) You might even want to entirely drop this if it
becomes too small.
-Daniel

> ---
>  drivers/gpu/drm/xlnx/xlnx_fb.c | 467 +++++++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/xlnx/xlnx_fb.h |  30 +++
>  2 files changed, 497 insertions(+)
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.c
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.h
> 
> diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.c b/drivers/gpu/drm/xlnx/xlnx_fb.c
> new file mode 100644
> index 0000000..dbe9fbf
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/xlnx_fb.c
> @@ -0,0 +1,467 @@
> +/*
> + * Xilinx DRM KMS Framebuffer helper
> + *
> + *  Copyright (C) 2015 - 2018 Xilinx, Inc.
> + *
> + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> + *
> + * Based on drm_fb_cma_helper.c
> + *
> + *  Copyright (C) 2012 Analog Device Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +
> +#include "xlnx_fb.h"
> +
> +#define XLNX_MAX_PLANES	4
> +
> +struct xlnx_fb {
> +	struct drm_framebuffer		base;
> +	struct drm_gem_cma_object	*obj[XLNX_MAX_PLANES];
> +};
> +
> +struct xlnx_fbdev {
> +	struct drm_fb_helper fb_helper;
> +	struct xlnx_fb	*fb;
> +	unsigned int align;
> +	unsigned int vres_mult;
> +};
> +
> +static inline struct xlnx_fbdev *to_fbdev(struct drm_fb_helper *fb_helper)
> +{
> +	return container_of(fb_helper, struct xlnx_fbdev, fb_helper);
> +}
> +
> +static inline struct xlnx_fb *to_fb(struct drm_framebuffer *base_fb)
> +{
> +	return container_of(base_fb, struct xlnx_fb, base);
> +}
> +
> +static void xlnx_fb_destroy(struct drm_framebuffer *base_fb)
> +{
> +	struct xlnx_fb *fb = to_fb(base_fb);
> +	int i;
> +
> +	for (i = 0; i < XLNX_MAX_PLANES; i++)
> +		if (fb->obj[i])
> +			drm_gem_object_unreference_unlocked(&fb->obj[i]->base);
> +
> +	drm_framebuffer_cleanup(base_fb);
> +	kfree(fb);
> +}
> +
> +static int xlnx_fb_create_handle(struct drm_framebuffer *base_fb,
> +				 struct drm_file *file_priv,
> +				 unsigned int *handle)
> +{
> +	struct xlnx_fb *fb = to_fb(base_fb);
> +
> +	return drm_gem_handle_create(file_priv, &fb->obj[0]->base, handle);
> +}
> +
> +static struct drm_framebuffer_funcs xlnx_fb_funcs = {
> +	.destroy	= xlnx_fb_destroy,
> +	.create_handle	= xlnx_fb_create_handle,
> +};
> +
> +/**
> + * xlnx_fb_alloc - Allocate a xlnx_fb
> + * @drm: DRM object
> + * @mode_cmd: drm_mode_fb_cmd2 struct
> + * @obj: pointers for returned drm_gem_cma_objects
> + * @num_planes: number of planes to be allocated
> + *
> + * This function is based on drm_fb_cma_alloc().
> + *
> + * Return: a xlnx_fb object, or ERR_PTR.
> + */
> +static struct xlnx_fb *
> +xlnx_fb_alloc(struct drm_device *drm,
> +	      const struct drm_mode_fb_cmd2 *mode_cmd,
> +	      struct drm_gem_cma_object **obj, unsigned int num_planes)
> +{
> +	struct xlnx_fb *fb;
> +	int ret;
> +	int i;
> +
> +	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
> +	if (!fb)
> +		return ERR_PTR(-ENOMEM);
> +
> +	drm_helper_mode_fill_fb_struct(drm, &fb->base, mode_cmd);
> +
> +	for (i = 0; i < num_planes; i++)
> +		fb->obj[i] = obj[i];
> +
> +	ret = drm_framebuffer_init(drm, &fb->base, &xlnx_fb_funcs);
> +	if (ret) {
> +		dev_err(drm->dev, "Failed to initialize fb: %d\n", ret);
> +		kfree(fb);
> +		return ERR_PTR(ret);
> +	}
> +
> +	return fb;
> +}
> +
> +/**
> + * xlnx_fb_get_paddr - Get physycal address of framebuffer
> + * @base_fb: the framebuffer
> + * @plane: which plane
> + *
> + * This function is based on drm_fb_cma_get_gem_obj().
> + *
> + * Return: a physical address of the plane, or 0
> + */
> +dma_addr_t
> +xlnx_fb_get_paddr(struct drm_framebuffer *base_fb, unsigned int plane)
> +{
> +	struct xlnx_fb *fb = to_fb(base_fb);
> +
> +	if (plane >= XLNX_MAX_PLANES)
> +		return 0;
> +
> +	return fb->obj[plane]->paddr;
> +}
> +EXPORT_SYMBOL_GPL(xlnx_fb_get_paddr);
> +
> +static int
> +xlnx_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
> +{
> +	struct drm_fb_helper *fb_helper = info->par;
> +	unsigned int i;
> +	int ret = 0;
> +
> +	switch (cmd) {
> +	case FBIO_WAITFORVSYNC:
> +		for (i = 0; i < fb_helper->crtc_count; i++) {
> +			struct drm_mode_set *mode_set;
> +			struct drm_crtc *crtc;
> +
> +			mode_set = &fb_helper->crtc_info[i].mode_set;
> +			crtc = mode_set->crtc;
> +			ret = drm_crtc_vblank_get(crtc);
> +			if (!ret) {
> +				drm_crtc_wait_one_vblank(crtc);
> +				drm_crtc_vblank_put(crtc);
> +			}
> +		}
> +		return ret;
> +	default:
> +		return -ENOTTY;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct fb_ops xlnx_fbdev_ops = {
> +	.owner		= THIS_MODULE,
> +	.fb_fillrect	= sys_fillrect,
> +	.fb_copyarea	= sys_copyarea,
> +	.fb_imageblit	= sys_imageblit,
> +	.fb_check_var	= drm_fb_helper_check_var,
> +	.fb_set_par	= drm_fb_helper_set_par,
> +	.fb_blank	= drm_fb_helper_blank,
> +	.fb_pan_display	= drm_fb_helper_pan_display,
> +	.fb_setcmap	= drm_fb_helper_setcmap,
> +	.fb_ioctl	= xlnx_fb_ioctl,
> +};
> +
> +/**
> + * xlnx_fbdev_create - Create the fbdev with a framebuffer
> + * @fb_helper: fb helper structure
> + * @size: framebuffer size info
> + *
> + * This function is based on drm_fbdev_cma_create().
> + *
> + * Return: 0 if successful, or the error code.
> + */
> +static int xlnx_fbdev_create(struct drm_fb_helper *fb_helper,
> +			     struct drm_fb_helper_surface_size *size)
> +{
> +	struct xlnx_fbdev *fbdev = to_fbdev(fb_helper);
> +	struct drm_mode_fb_cmd2 mode_cmd = { 0 };
> +	struct drm_device *drm = fb_helper->dev;
> +	struct drm_gem_cma_object *obj;
> +	struct drm_framebuffer *base_fb;
> +	unsigned int bytes_per_pixel;
> +	unsigned long offset;
> +	struct fb_info *fbi;
> +	size_t bytes;
> +	int ret;
> +
> +	dev_dbg(drm->dev, "surface width(%d), height(%d) and bpp(%d)\n",
> +		size->surface_width, size->surface_height, size->surface_bpp);
> +
> +	bytes_per_pixel = DIV_ROUND_UP(size->surface_bpp, 8);
> +
> +	mode_cmd.width = size->surface_width;
> +	mode_cmd.height = size->surface_height;
> +	mode_cmd.pitches[0] = ALIGN(size->surface_width * bytes_per_pixel,
> +				    fbdev->align);
> +	mode_cmd.pixel_format = xlnx_get_format(drm);
> +
> +	mode_cmd.height *= fbdev->vres_mult;
> +	bytes = mode_cmd.pitches[0] * mode_cmd.height;
> +	obj = drm_gem_cma_create(drm, bytes);
> +	if (IS_ERR(obj))
> +		return PTR_ERR(obj);
> +
> +	fbi = framebuffer_alloc(0, drm->dev);
> +	if (!fbi) {
> +		dev_err(drm->dev, "Failed to allocate framebuffer info.\n");
> +		ret = -ENOMEM;
> +		goto err_drm_gem_cma_free_object;
> +	}
> +
> +	fbdev->fb = xlnx_fb_alloc(drm, &mode_cmd, &obj, 1);
> +	if (IS_ERR(fbdev->fb)) {
> +		dev_err(drm->dev, "Failed to allocate DRM framebuffer.\n");
> +		ret = PTR_ERR(fbdev->fb);
> +		goto err_framebuffer_release;
> +	}
> +
> +	base_fb = &fbdev->fb->base;
> +	fb_helper->fb = base_fb;
> +	fb_helper->fbdev = fbi;
> +	fbi->par = fb_helper;
> +	fbi->flags = FBINFO_FLAG_DEFAULT;
> +	fbi->fbops = &xlnx_fbdev_ops;
> +
> +	ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
> +	if (ret) {
> +		dev_err(drm->dev, "Failed to allocate color map.\n");
> +		goto err_xlnx_fb_destroy;
> +	}
> +
> +	drm_fb_helper_fill_fix(fbi, base_fb->pitches[0],
> +			       base_fb->format->depth);
> +	drm_fb_helper_fill_var(fbi, fb_helper, base_fb->width, base_fb->height);
> +	fbi->var.yres = base_fb->height / fbdev->vres_mult;
> +
> +	offset = fbi->var.xoffset * bytes_per_pixel;
> +	offset += fbi->var.yoffset * base_fb->pitches[0];
> +
> +	drm->mode_config.fb_base = (resource_size_t)obj->paddr;
> +	fbi->screen_base = (char __iomem *)(obj->vaddr + offset);
> +	fbi->fix.smem_start = (unsigned long)(obj->paddr + offset);
> +	fbi->screen_size = bytes;
> +	fbi->fix.smem_len = bytes;
> +
> +	return 0;
> +
> +err_xlnx_fb_destroy:
> +	drm_framebuffer_unregister_private(base_fb);
> +	xlnx_fb_destroy(base_fb);
> +err_framebuffer_release:
> +	framebuffer_release(fbi);
> +err_drm_gem_cma_free_object:
> +	drm_gem_cma_free_object(&obj->base);
> +	return ret;
> +}
> +
> +static struct drm_fb_helper_funcs xlnx_fb_helper_funcs = {
> +	.fb_probe = xlnx_fbdev_create,
> +};
> +
> +/**
> + * xlnx_fb_init - Allocate and initializes the Xilinx framebuffer
> + * @drm: DRM device
> + * @preferred_bpp: preferred bits per pixel for the device
> + * @max_conn_count: maximum number of connectors
> + * @align: alignment value for pitch
> + * @vres_mult: multiplier for virtual resolution
> + *
> + * This function is based on drm_fbdev_cma_init().
> + *
> + * Return: a newly allocated drm_fb_helper struct or a ERR_PTR.
> + */
> +struct drm_fb_helper *
> +xlnx_fb_init(struct drm_device *drm, int preferred_bpp,
> +	     unsigned int max_conn_count, unsigned int align,
> +	     unsigned int vres_mult)
> +{
> +	struct xlnx_fbdev *fbdev;
> +	struct drm_fb_helper *fb_helper;
> +	int ret;
> +
> +	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
> +	if (!fbdev)
> +		return ERR_PTR(-ENOMEM);
> +
> +	fbdev->vres_mult = vres_mult;
> +	fbdev->align = align;
> +	fb_helper = &fbdev->fb_helper;
> +	drm_fb_helper_prepare(drm, fb_helper, &xlnx_fb_helper_funcs);
> +
> +	ret = drm_fb_helper_init(drm, fb_helper, max_conn_count);
> +	if (ret < 0) {
> +		dev_err(drm->dev, "Failed to initialize drm fb helper.\n");
> +		goto err_free;
> +	}
> +
> +	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
> +	if (ret < 0) {
> +		dev_err(drm->dev, "Failed to add connectors.\n");
> +		goto err_drm_fb_helper_fini;
> +	}
> +
> +	ret = drm_fb_helper_initial_config(fb_helper, preferred_bpp);
> +	if (ret < 0) {
> +		dev_err(drm->dev, "Failed to set initial hw configuration.\n");
> +		goto err_drm_fb_helper_fini;
> +	}
> +
> +	return fb_helper;
> +
> +err_drm_fb_helper_fini:
> +	drm_fb_helper_fini(fb_helper);
> +err_free:
> +	kfree(fbdev);
> +	return ERR_PTR(ret);
> +}
> +
> +/**
> + * xlnx_fbdev_defio_fini - Free the defio fb
> + * @fbi: fb_info struct
> + *
> + * This function is based on drm_fbdev_cma_defio_fini().
> + */
> +static void xlnx_fbdev_defio_fini(struct fb_info *fbi)
> +{
> +	if (!fbi->fbdefio)
> +		return;
> +
> +	fb_deferred_io_cleanup(fbi);
> +	kfree(fbi->fbdefio);
> +	kfree(fbi->fbops);
> +}
> +
> +/**
> + * xlnx_fbdev_fini - Free the Xilinx framebuffer
> + * @fb_helper: drm_fb_helper struct
> + *
> + * This function is based on drm_fbdev_cma_fini().
> + */
> +void xlnx_fb_fini(struct drm_fb_helper *fb_helper)
> +{
> +	struct xlnx_fbdev *fbdev = to_fbdev(fb_helper);
> +
> +	drm_fb_helper_unregister_fbi(&fbdev->fb_helper);
> +	if (fbdev->fb_helper.fbdev)
> +		xlnx_fbdev_defio_fini(fbdev->fb_helper.fbdev);
> +
> +	if (fbdev->fb_helper.fb)
> +		drm_framebuffer_remove(fbdev->fb_helper.fb);
> +
> +	drm_fb_helper_fini(&fbdev->fb_helper);
> +	kfree(fbdev);
> +}
> +
> +/**
> + * xlnx_fb_restore_mode - Restores initial framebuffer mode
> + * @fb_helper: drm_fb_helper struct, may be NULL
> + *
> + * This function is based on drm_fbdev_cma_restore_mode() and usually called
> + * from the Xilinx DRM drivers lastclose callback.
> + */
> +void xlnx_fb_restore_mode(struct drm_fb_helper *fb_helper)
> +{
> +	if (!fb_helper)
> +		return;
> +	drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
> +}
> +
> +/**
> + * xlnx_fb_create - (struct drm_mode_config_funcs *)->fb_create callback
> + * @drm: DRM device
> + * @file_priv: drm file private data
> + * @mode_cmd: mode command for fb creation
> + *
> + * This functions creates a drm_framebuffer for given mode @mode_cmd. This
> + * functions is intended to be used for the fb_create callback function of
> + * drm_mode_config_funcs.
> + *
> + * Return: a drm_framebuffer object if successful, or ERR_PTR.
> + */
> +struct drm_framebuffer *
> +xlnx_fb_create(struct drm_device *drm, struct drm_file *file_priv,
> +	       const struct drm_mode_fb_cmd2 *mode_cmd)
> +{
> +	struct xlnx_fb *fb;
> +	struct drm_gem_cma_object *objs[XLNX_MAX_PLANES];
> +	struct drm_gem_object *obj;
> +	const struct drm_format_info *info;
> +	struct drm_format_name_buf format_name;
> +	int ret;
> +	int i;
> +
> +	info = drm_format_info(mode_cmd->pixel_format);
> +	if (!info) {
> +		dev_err(drm->dev, "unsupported framebuffer format %s\n",
> +			drm_get_format_name(mode_cmd->pixel_format,
> +					    &format_name));
> +		ret = -EINVAL;
> +		goto err_out;
> +	}
> +
> +	for (i = 0; i < info->num_planes; i++) {
> +		unsigned int width = mode_cmd->width / (i ? info->hsub : 1);
> +		unsigned int height = mode_cmd->height / (i ? info->vsub : 1);
> +		unsigned int min_size;
> +
> +		obj = drm_gem_object_lookup(file_priv,
> +					    mode_cmd->handles[i]);
> +		if (!obj) {
> +			dev_err(drm->dev, "Failed to lookup GEM object\n");
> +			ret = -ENXIO;
> +			goto err_gem_object_unreference;
> +		}
> +
> +		min_size = (height - 1) * mode_cmd->pitches[i] + width *
> +			   info->cpp[i] + mode_cmd->offsets[i];
> +
> +		if (obj->size < min_size) {
> +			drm_gem_object_unreference_unlocked(obj);
> +			ret = -EINVAL;
> +			goto err_gem_object_unreference;
> +		}
> +		objs[i] = to_drm_gem_cma_obj(obj);
> +	}
> +
> +	fb = xlnx_fb_alloc(drm, mode_cmd, objs, i);
> +	if (IS_ERR(fb)) {
> +		ret = PTR_ERR(fb);
> +		goto err_gem_object_unreference;
> +	}
> +
> +	fb->base.format = info;
> +
> +	return &fb->base;
> +
> +err_gem_object_unreference:
> +	for (i--; i >= 0; i--)
> +		drm_gem_object_unreference_unlocked(&objs[i]->base);
> +err_out:
> +	return ERR_PTR(ret);
> +}
> +
> +/**
> + * xlnx_fb_hotplug_event - Poll for hotpulug events
> + * @fb_helper: drm_fb_helper struct, may be NULL
> + *
> + * This function is based on drm_fbdev_cma_hotplug_event() and usually called
> + * from the Xilinx DRM drivers output_poll_changed callback.
> + */
> +void xlnx_fb_hotplug_event(struct drm_fb_helper *fb_helper)
> +{
> +	if (!fb_helper)
> +		return;
> +	drm_fb_helper_hotplug_event(fb_helper);
> +}
> diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.h b/drivers/gpu/drm/xlnx/xlnx_fb.h
> new file mode 100644
> index 0000000..3f7e962
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/xlnx_fb.h
> @@ -0,0 +1,30 @@
> +/*
> + * Xilinx DRM KMS Framebuffer helper header
> + *
> + *  Copyright (C) 2015 - 2018 Xilinx, Inc.
> + *
> + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#ifndef _XLNX_FB_H_
> +#define _XLNX_FB_H_
> +
> +struct drm_fb_helper;
> +
> +dma_addr_t
> +xlnx_fb_get_paddr(struct drm_framebuffer *base_fb, unsigned int plane);
> +
> +void xlnx_fb_restore_mode(struct drm_fb_helper *fb_helper);
> +struct drm_framebuffer *
> +xlnx_fb_create(struct drm_device *drm, struct drm_file *file_priv,
> +	       const struct drm_mode_fb_cmd2 *mode_cmd);
> +void xlnx_fb_hotplug_event(struct drm_fb_helper *fb_helper);
> +struct drm_fb_helper *
> +xlnx_fb_init(struct drm_device *drm, int preferred_bpp,
> +	     unsigned int max_conn_count, unsigned int align,
> +	     unsigned int vres_mult);
> +void xlnx_fb_fini(struct drm_fb_helper *fb_helper);
> +
> +#endif /* _XLNX_FB_H_ */
> -- 
> 2.7.4
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
  2018-01-05  2:05   ` [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS Hyun Kwon
@ 2018-01-09  9:37     ` Daniel Vetter
       [not found]       ` <20180109093733.GG26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Daniel Vetter @ 2018-01-09  9:37 UTC (permalink / raw)
  To: Hyun Kwon; +Cc: devicetree, Michal Simek, dri-devel

On Thu, Jan 04, 2018 at 06:05:51PM -0800, Hyun Kwon wrote:
> xlnx_crtc is a part of Xilinx DRM KMS and a layer that
> provides some interface between the Xilinx DRM KMS and
> crtc drivers.
> 
> Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>

Personal style, but I don't see much value in these small helpers.
Splitting them from the main driver at least makes reading the patches a
bit harder. But no strong opinion, just a bikeshed, feel free to ignore
:-)
-Daniel

> ---
>  drivers/gpu/drm/xlnx/xlnx_crtc.c | 194 +++++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/xlnx/xlnx_crtc.h |  70 ++++++++++++++
>  2 files changed, 264 insertions(+)
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.c
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.h
> 
> diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.c b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> new file mode 100644
> index 0000000..57ee939
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> @@ -0,0 +1,194 @@
> +/*
> + * Xilinx DRM crtc driver
> + *
> + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> + *
> + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#include <drm/drmP.h>
> +
> +#include <linux/list.h>
> +
> +#include "xlnx_crtc.h"
> +
> +/*
> + * Overview
> + * --------
> + *
> + * The Xilinx CRTC layer is to enable the custom interface to CRTC drivers.
> + * The interface is used by Xilinx DRM driver where it needs CRTC
> + * functionailty. CRTC drivers should attach the desired callbacks
> + * to struct xlnx_crtc and register the xlnx_crtc with correcsponding
> + * drm_device. It's highly recommended CRTC drivers register all callbacks
> + * even though many of them are optional.
> + * The CRTC helper simply walks through the registered CRTC device,
> + * and call the callbacks.
> + */
> +
> +/**
> + * struct xlnx_crtc_helper - Xilinx CRTC helper
> + * @xlnx_crtcs: list of Xilinx CRTC devices
> + * @lock: lock to protect @xlnx_crtcs
> + * @drm: back pointer to DRM core
> + */
> +struct xlnx_crtc_helper {
> +	struct list_head xlnx_crtcs;
> +	struct mutex lock; /* lock for @xlnx_crtcs */
> +	struct drm_device *drm;
> +};
> +
> +#define XLNX_CRTC_MAX_HEIGHT_WIDTH	UINT_MAX
> +
> +int xlnx_crtc_helper_enable_vblank(struct xlnx_crtc_helper *helper,
> +				   unsigned int crtc_id)
> +{
> +	struct xlnx_crtc *crtc;
> +
> +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list)
> +		if (drm_crtc_index(&crtc->crtc) == crtc_id)
> +			if (crtc->enable_vblank)
> +				return crtc->enable_vblank(crtc);
> +	return -ENODEV;
> +}
> +
> +void xlnx_crtc_helper_disable_vblank(struct xlnx_crtc_helper *helper,
> +				     unsigned int crtc_id)
> +{
> +	struct xlnx_crtc *crtc;
> +
> +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> +		if (drm_crtc_index(&crtc->crtc) == crtc_id) {
> +			if (crtc->disable_vblank)
> +				crtc->disable_vblank(crtc);
> +			return;
> +		}
> +	}
> +}
> +
> +unsigned int xlnx_crtc_helper_get_align(struct xlnx_crtc_helper *helper)
> +{
> +	struct xlnx_crtc *crtc;
> +	unsigned int align = 1, tmp;
> +
> +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> +		if (crtc->get_align) {
> +			tmp = crtc->get_align(crtc);
> +			align = ALIGN(align, tmp);
> +		}
> +	}
> +
> +	return align;
> +}
> +
> +u64 xlnx_crtc_helper_get_dma_mask(struct xlnx_crtc_helper *helper)
> +{
> +	struct xlnx_crtc *crtc;
> +	u64 mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8), tmp;
> +
> +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> +		if (crtc->get_dma_mask) {
> +			tmp = crtc->get_dma_mask(crtc);
> +			mask = min(mask, tmp);
> +		}
> +	}
> +
> +	return mask;
> +}
> +
> +int xlnx_crtc_helper_get_max_width(struct xlnx_crtc_helper *helper)
> +{
> +	struct xlnx_crtc *crtc;
> +	unsigned int width = XLNX_CRTC_MAX_HEIGHT_WIDTH, tmp;
> +
> +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> +		if (crtc->get_max_width) {
> +			tmp = crtc->get_max_width(crtc);
> +			width = min(width, tmp);
> +		}
> +	}
> +
> +	return width;
> +}
> +
> +int xlnx_crtc_helper_get_max_height(struct xlnx_crtc_helper *helper)
> +{
> +	struct xlnx_crtc *crtc;
> +	unsigned int height = XLNX_CRTC_MAX_HEIGHT_WIDTH, tmp;
> +
> +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> +		if (crtc->get_max_height) {
> +			tmp = crtc->get_max_height(crtc);
> +			height = min(height, tmp);
> +		}
> +	}
> +
> +	return height;
> +}
> +
> +uint32_t xlnx_crtc_helper_get_format(struct xlnx_crtc_helper *helper)
> +{
> +	struct xlnx_crtc *crtc;
> +	u32 format = 0, tmp;
> +
> +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> +		if (crtc->get_format) {
> +			tmp = crtc->get_format(crtc);
> +			if (format && format != tmp)
> +				return 0;
> +			format = tmp;
> +		}
> +	}
> +
> +	return format;
> +}
> +
> +struct xlnx_crtc_helper *xlnx_crtc_helper_init(struct drm_device *drm)
> +{
> +	struct xlnx_crtc_helper *helper;
> +
> +	helper = devm_kzalloc(drm->dev, sizeof(*helper), GFP_KERNEL);
> +	if (!helper)
> +		return ERR_PTR(-ENOMEM);
> +
> +	INIT_LIST_HEAD(&helper->xlnx_crtcs);
> +	mutex_init(&helper->lock);
> +	helper->drm = drm;
> +
> +	return helper;
> +}
> +
> +void xlnx_crtc_helper_fini(struct drm_device *drm,
> +			   struct xlnx_crtc_helper *helper)
> +{
> +	if (WARN_ON(helper->drm != drm))
> +		return;
> +
> +	if (WARN_ON(!list_empty(&helper->xlnx_crtcs)))
> +		return;
> +
> +	mutex_destroy(&helper->lock);
> +	devm_kfree(drm->dev, helper);
> +}
> +
> +void xlnx_crtc_register(struct drm_device *drm, struct xlnx_crtc *crtc)
> +{
> +	struct xlnx_crtc_helper *helper = xlnx_get_crtc_helper(drm);
> +
> +	mutex_lock(&helper->lock);
> +	list_add_tail(&crtc->list, &helper->xlnx_crtcs);
> +	mutex_unlock(&helper->lock);
> +}
> +EXPORT_SYMBOL_GPL(xlnx_crtc_register);
> +
> +void xlnx_crtc_unregister(struct drm_device *drm, struct xlnx_crtc *crtc)
> +{
> +	struct xlnx_crtc_helper *helper = xlnx_get_crtc_helper(drm);
> +
> +	mutex_lock(&helper->lock);
> +	list_del(&crtc->list);
> +	mutex_unlock(&helper->lock);
> +}
> +EXPORT_SYMBOL_GPL(xlnx_crtc_unregister);
> diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.h b/drivers/gpu/drm/xlnx/xlnx_crtc.h
> new file mode 100644
> index 0000000..db7404e
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/xlnx_crtc.h
> @@ -0,0 +1,70 @@
> +/*
> + * Xilinx DRM crtc header
> + *
> + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> + *
> + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#ifndef _XLNX_CRTC_H_
> +#define _XLNX_CRTC_H_
> +
> +/**
> + * struct xlnx_crtc - Xilinx CRTC device
> + * @crtc: DRM CRTC device
> + * @list: list node for Xilinx CRTC device list
> + * @enable_vblank: Enable vblank
> + * @disable_vblank: Disable vblank
> + * @get_align: Get the alignment requirement of CRTC device
> + * @get_dma_mask: Get the dma mask of CRTC device
> + * @get_max_width: Get the maximum supported width
> + * @get_max_height: Get the maximum supported height
> + * @get_format: Get the current format of CRTC device
> + */
> +struct xlnx_crtc {
> +	struct drm_crtc crtc;
> +	struct list_head list;
> +	int (*enable_vblank)(struct xlnx_crtc *crtc);
> +	void (*disable_vblank)(struct xlnx_crtc *crtc);
> +	unsigned int (*get_align)(struct xlnx_crtc *crtc);
> +	u64 (*get_dma_mask)(struct xlnx_crtc *crtc);
> +	int (*get_max_width)(struct xlnx_crtc *crtc);
> +	int (*get_max_height)(struct xlnx_crtc *crtc);
> +	uint32_t (*get_format)(struct xlnx_crtc *crtc);
> +};
> +
> +/*
> + * Helper functions: used within Xlnx DRM
> + */
> +
> +struct xlnx_crtc_helper;
> +
> +int xlnx_crtc_helper_enable_vblank(struct xlnx_crtc_helper *helper,
> +				   unsigned int crtc_id);
> +void xlnx_crtc_helper_disable_vblank(struct xlnx_crtc_helper *helper,
> +				     unsigned int crtc_id);
> +unsigned int xlnx_crtc_helper_get_align(struct xlnx_crtc_helper *helper);
> +u64 xlnx_crtc_helper_get_dma_mask(struct xlnx_crtc_helper *helper);
> +int xlnx_crtc_helper_get_max_width(struct xlnx_crtc_helper *helper);
> +int xlnx_crtc_helper_get_max_height(struct xlnx_crtc_helper *helper);
> +uint32_t xlnx_crtc_helper_get_format(struct xlnx_crtc_helper *helper);
> +
> +struct xlnx_crtc_helper *xlnx_crtc_helper_init(struct drm_device *drm);
> +void xlnx_crtc_helper_fini(struct drm_device *drm,
> +			   struct xlnx_crtc_helper *helper);
> +
> +/*
> + * CRTC registration: used by other sub-driver modules
> + */
> +
> +static inline struct xlnx_crtc *to_xlnx_crtc(struct drm_crtc *crtc)
> +{
> +	return container_of(crtc, struct xlnx_crtc, crtc);
> +}
> +
> +void xlnx_crtc_register(struct drm_device *drm, struct xlnx_crtc *crtc);
> +void xlnx_crtc_unregister(struct drm_device *drm, struct xlnx_crtc *crtc);
> +
> +#endif /* _XLNX_CRTC_H_ */
> -- 
> 2.7.4
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 07/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display
       [not found]     ` <1515117959-18068-8-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
@ 2018-01-09  9:46       ` Daniel Vetter
       [not found]         ` <20180109094652.GH26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Daniel Vetter @ 2018-01-09  9:46 UTC (permalink / raw)
  To: Hyun Kwon
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

On Thu, Jan 04, 2018 at 06:05:56PM -0800, Hyun Kwon wrote:
> Xilinx ZynqMP has a hardened display pipeline. The pipeline can
> be logically partitioned into 2 parts: display and DisplayPort.
> This driver handles the display part of the pipeline that handles
> buffer management and blending.
> 
> Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> ---
>  drivers/gpu/drm/xlnx/zynqmp_disp.c | 2935 ++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/xlnx/zynqmp_disp.h |   28 +
>  2 files changed, 2963 insertions(+)
>  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.c
>  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.h
> 
> diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> new file mode 100644
> index 0000000..68f829c
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> @@ -0,0 +1,2935 @@
> +/*
> + * ZynqMP Display Controller Driver
> + *
> + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> + *
> + *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_plane_helper.h>
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/dmaengine.h>
> +#include <linux/interrupt.h>
> +#include <linux/irqreturn.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_dma.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/spinlock.h>
> +#include <linux/uaccess.h>
> +
> +#include "xlnx_crtc.h"
> +#include "xlnx_fb.h"
> +#include "zynqmp_disp.h"
> +#include "zynqmp_dp.h"
> +#include "zynqmp_dpsub.h"
> +
> +/*
> + * Overview
> + * --------
> + *
> + * The display part of ZynqMP DP subsystem. Internally, the device
> + * is partitioned into 3 blocks: AV buffer manager, Blender, Audio.
> + * The driver creates the DRM crtc and plane objectes and maps the DRM
> + * interface into those 3 blocks. In high level, the driver is layered
> + * in the following way:
> + *
> + * zynqmp_disp_crtc & zynqmp_disp_plane
> + * |->zynqmp_disp
> + *	|->zynqmp_disp_aud
> + *	|->zynqmp_disp_blend
> + *	|->zynqmp_disp_av_buf
> + *
> + * The driver APIs are used externally by
> + * - zynqmp_dpsub: Top level ZynqMP DP subsystem driver
> + * - zynqmp_dp: ZynqMP DP driver
> + * - xlnx_crtc: Xilinx DRM specific crtc functions
> + */
> +
> +/* Blender registers */
> +#define ZYNQMP_DISP_V_BLEND_BG_CLR_0			0x0
> +#define ZYNQMP_DISP_V_BLEND_BG_CLR_1			0x4
> +#define ZYNQMP_DISP_V_BLEND_BG_CLR_2			0x8
> +#define ZYNQMP_DISP_V_BLEND_BG_MAX			0xfff
> +#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA		0xc
> +#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MASK	0x1fe
> +#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX	0xff
> +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT		0x14
> +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB		0x0
> +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444	0x1
> +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422	0x2
> +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY	0x3
> +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_XVYCC	0x4
> +#define ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE	BIT(4)
> +#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL		0x18
> +#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_EN_US		BIT(0)
> +#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_RGB		BIT(1)
> +#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_BYPASS	BIT(8)
> +#define ZYNQMP_DISP_V_BLEND_NUM_COEFF			9
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF0		0x20
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF1		0x24
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF2		0x28
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF3		0x2c
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF4		0x30
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF5		0x34
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF6		0x38
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF7		0x3c
> +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF8		0x40
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0		0x44
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF1		0x48
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF2		0x4c
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF3		0x50
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF4		0x54
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF5		0x58
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF6		0x5c
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF7		0x60
> +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF8		0x64
> +#define ZYNQMP_DISP_V_BLEND_NUM_OFFSET			3
> +#define ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET		0x68
> +#define ZYNQMP_DISP_V_BLEND_CR_IN1CSC_OFFSET		0x6c
> +#define ZYNQMP_DISP_V_BLEND_CB_IN1CSC_OFFSET		0x70
> +#define ZYNQMP_DISP_V_BLEND_LUMA_OUTCSC_OFFSET		0x74
> +#define ZYNQMP_DISP_V_BLEND_CR_OUTCSC_OFFSET		0x78
> +#define ZYNQMP_DISP_V_BLEND_CB_OUTCSC_OFFSET		0x7c
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0		0x80
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF1		0x84
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF2		0x88
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF3		0x8c
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF4		0x90
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF5		0x94
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF6		0x98
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF7		0x9c
> +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF8		0xa0
> +#define ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET		0xa4
> +#define ZYNQMP_DISP_V_BLEND_CR_IN2CSC_OFFSET		0xa8
> +#define ZYNQMP_DISP_V_BLEND_CB_IN2CSC_OFFSET		0xac
> +#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_ENABLE		0x1d0
> +#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP1		0x1d4
> +#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP2		0x1d8
> +#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP3		0x1dc
> +
> +/* AV buffer manager registers */
> +#define ZYNQMP_DISP_AV_BUF_FMT				0x0
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_SHIFT		0
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MASK		(0x1f << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_UYVY		(0 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY		(1 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YVYU		(2 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV		(3 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16		(4 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24		(5 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI		(6 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MONO		(7 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2		(8 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUV444		(9 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888		(10 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880		(11 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10		(12 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUV444_10		(13 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_10	(14 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_10		(15 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_10		(16 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24_10		(17 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YONLY_10		(18 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420		(19 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420	(20 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_420	(21 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420_10	(22 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420_10	(23 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_420_10	(24 << 0)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_SHIFT		8
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_MASK		(0xf << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888		(0 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888		(1 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB888		(2 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_BGR888		(3 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551		(4 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444		(5 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565		(6 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_8BPP		(7 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_4BPP		(8 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_2BPP		(9 << 8)
> +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_1BPP		(10 << 8)
> +#define ZYNQMP_DISP_AV_BUF_NON_LIVE_LATENCY		0x8
> +#define ZYNQMP_DISP_AV_BUF_CHBUF			0x10
> +#define ZYNQMP_DISP_AV_BUF_CHBUF_EN			BIT(0)
> +#define ZYNQMP_DISP_AV_BUF_CHBUF_FLUSH			BIT(1)
> +#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT	2
> +#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MASK		(0xf << 2)
> +#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MAX		0xf
> +#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_AUD_MAX	0x3
> +#define ZYNQMP_DISP_AV_BUF_STATUS			0x28
> +#define ZYNQMP_DISP_AV_BUF_STC_CTRL			0x2c
> +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EN			BIT(0)
> +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_SHIFT		1
> +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_VSYNC	0
> +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_VID	1
> +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_AUD	2
> +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_INT_VSYNC	3
> +#define ZYNQMP_DISP_AV_BUF_STC_INIT_VALUE0		0x30
> +#define ZYNQMP_DISP_AV_BUF_STC_INIT_VALUE1		0x34
> +#define ZYNQMP_DISP_AV_BUF_STC_ADJ			0x38
> +#define ZYNQMP_DISP_AV_BUF_STC_VID_VSYNC_TS0		0x3c
> +#define ZYNQMP_DISP_AV_BUF_STC_VID_VSYNC_TS1		0x40
> +#define ZYNQMP_DISP_AV_BUF_STC_EXT_VSYNC_TS0		0x44
> +#define ZYNQMP_DISP_AV_BUF_STC_EXT_VSYNC_TS1		0x48
> +#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT_TS0		0x4c
> +#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT_TS1		0x50
> +#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT2_TS0	0x54
> +#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT2_TS1	0x58
> +#define ZYNQMP_DISP_AV_BUF_STC_SNAPSHOT0		0x60
> +#define ZYNQMP_DISP_AV_BUF_STC_SNAPSHOT1		0x64
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT			0x70
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_SHIFT		0
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK		(0x3 << 0)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE		(0 << 0)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM		(1 << 0)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN		(2 << 0)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_NONE		(3 << 0)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_SHIFT		2
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK		(0x3 << 2)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_DISABLE		(0 << 2)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM		(1 << 2)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE		(2 << 2)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_NONE		(3 << 2)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_SHIFT		4
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK		(0x3 << 4)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_PL		(0 << 4)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MEM		(1 << 4)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_PATTERN		(2 << 4)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_DISABLE		(3 << 4)
> +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN		BIT(6)
> +#define ZYNQMP_DISP_AV_BUF_HCOUNT_VCOUNT_INT0		0x74
> +#define ZYNQMP_DISP_AV_BUF_HCOUNT_VCOUNT_INT1		0x78
> +#define ZYNQMP_DISP_AV_BUF_PATTERN_GEN_SELECT		0x100
> +#define ZYNQMP_DISP_AV_BUF_CLK_SRC			0x120
> +#define ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS		BIT(0)
> +#define ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS		BIT(1)
> +#define ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING	BIT(2)
> +#define ZYNQMP_DISP_AV_BUF_SRST_REG			0x124
> +#define ZYNQMP_DISP_AV_BUF_SRST_REG_VID_RST		BIT(1)
> +#define ZYNQMP_DISP_AV_BUF_AUDIO_CH_CONFIG		0x12c
> +#define ZYNQMP_DISP_AV_BUF_GFX_COMP0_SF			0x200
> +#define ZYNQMP_DISP_AV_BUF_GFX_COMP1_SF			0x204
> +#define ZYNQMP_DISP_AV_BUF_GFX_COMP2_SF			0x208
> +#define ZYNQMP_DISP_AV_BUF_VID_COMP0_SF			0x20c
> +#define ZYNQMP_DISP_AV_BUF_VID_COMP1_SF			0x210
> +#define ZYNQMP_DISP_AV_BUF_VID_COMP2_SF			0x214
> +#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP0_SF		0x218
> +#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP1_SF		0x21c
> +#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP2_SF		0x220
> +#define ZYNQMP_DISP_AV_BUF_LIVE_VID_CONFIG		0x224
> +#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP0_SF		0x228
> +#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP1_SF		0x22c
> +#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP2_SF		0x230
> +#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_CONFIG		0x234
> +#define ZYNQMP_DISP_AV_BUF_4BIT_SF			0x11111
> +#define ZYNQMP_DISP_AV_BUF_5BIT_SF			0x10842
> +#define ZYNQMP_DISP_AV_BUF_6BIT_SF			0x10410
> +#define ZYNQMP_DISP_AV_BUF_8BIT_SF			0x10101
> +#define ZYNQMP_DISP_AV_BUF_10BIT_SF			0x10040
> +#define ZYNQMP_DISP_AV_BUF_NULL_SF			0
> +#define ZYNQMP_DISP_AV_BUF_NUM_SF			3
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_6		0x0
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8		0x1
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_10		0x2
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_12		0x3
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_MASK		GENMASK(2, 0)
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB		0x0
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV444	0x1
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422	0x2
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YONLY	0x3
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_MASK		GENMASK(5, 4)
> +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_CB_FIRST		BIT(8)
> +#define ZYNQMP_DISP_AV_BUF_PALETTE_MEMORY		0x400
> +
> +/* Audio registers */
> +#define ZYNQMP_DISP_AUD_MIXER_VOLUME			0x0
> +#define ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE		0x20002000
> +#define ZYNQMP_DISP_AUD_MIXER_META_DATA			0x4
> +#define ZYNQMP_DISP_AUD_CH_STATUS0			0x8
> +#define ZYNQMP_DISP_AUD_CH_STATUS1			0xc
> +#define ZYNQMP_DISP_AUD_CH_STATUS2			0x10
> +#define ZYNQMP_DISP_AUD_CH_STATUS3			0x14
> +#define ZYNQMP_DISP_AUD_CH_STATUS4			0x18
> +#define ZYNQMP_DISP_AUD_CH_STATUS5			0x1c
> +#define ZYNQMP_DISP_AUD_CH_A_DATA0			0x20
> +#define ZYNQMP_DISP_AUD_CH_A_DATA1			0x24
> +#define ZYNQMP_DISP_AUD_CH_A_DATA2			0x28
> +#define ZYNQMP_DISP_AUD_CH_A_DATA3			0x2c
> +#define ZYNQMP_DISP_AUD_CH_A_DATA4			0x30
> +#define ZYNQMP_DISP_AUD_CH_A_DATA5			0x34
> +#define ZYNQMP_DISP_AUD_CH_B_DATA0			0x38
> +#define ZYNQMP_DISP_AUD_CH_B_DATA1			0x3c
> +#define ZYNQMP_DISP_AUD_CH_B_DATA2			0x40
> +#define ZYNQMP_DISP_AUD_CH_B_DATA3			0x44
> +#define ZYNQMP_DISP_AUD_CH_B_DATA4			0x48
> +#define ZYNQMP_DISP_AUD_CH_B_DATA5			0x4c
> +#define ZYNQMP_DISP_AUD_SOFT_RESET			0xc00
> +#define ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST		BIT(0)
> +
> +#define ZYNQMP_DISP_AV_BUF_NUM_VID_GFX_BUFFERS		4
> +#define ZYNQMP_DISP_AV_BUF_NUM_BUFFERS			6
> +
> +#define ZYNQMP_DISP_NUM_LAYERS				2
> +#define ZYNQMP_DISP_MAX_NUM_SUB_PLANES			3
> +/*
> + * 3840x2160 is advertised max resolution, but almost any resolutions under
> + * 300Mhz pixel rate would work. Thus put 4096 as maximum width and height.
> + */
> +#define ZYNQMP_DISP_MAX_WIDTH				4096
> +#define ZYNQMP_DISP_MAX_HEIGHT				4096
> +/* 44 bit addressing. This is acutally DPDMA limitation */
> +#define ZYNQMP_DISP_MAX_DMA_BIT				44
> +
> +/**
> + * enum zynqmp_disp_layer_type - Layer type (can be used for hw ID)
> + * @ZYNQMP_DISP_LAYER_VID: Video layer
> + * @ZYNQMP_DISP_LAYER_GFX: Graphics layer
> + */
> +enum zynqmp_disp_layer_type {
> +	ZYNQMP_DISP_LAYER_VID,
> +	ZYNQMP_DISP_LAYER_GFX
> +};
> +
> +/**
> + * enum zynqmp_disp_layer_mode - Layer mode
> + * @ZYNQMP_DISP_LAYER_NONLIVE: non-live (memory) mode
> + * @ZYNQMP_DISP_LAYER_LIVE: live (stream) mode
> + */
> +enum zynqmp_disp_layer_mode {
> +	ZYNQMP_DISP_LAYER_NONLIVE,
> +	ZYNQMP_DISP_LAYER_LIVE
> +};
> +
> +/**
> + * struct zynqmp_disp_layer_dma - struct for DMA engine
> + * @chan: DMA channel
> + * @is_active: flag if the DMA is active
> + * @xt: Interleaved desc config container
> + * @sgl: Data chunk for dma_interleaved_template
> + */
> +struct zynqmp_disp_layer_dma {
> +	struct dma_chan *chan;
> +	bool is_active;
> +	struct dma_interleaved_template xt;
> +	struct data_chunk sgl[1];
> +};
> +
> +/**
> + * struct zynqmp_disp_layer - Display subsystem layer
> + * @plane: DRM plane
> + * @of_node: device node
> + * @dma: struct for DMA engine
> + * @num_chan: Number of DMA channel
> + * @id: Layer ID
> + * @offset: Layer offset in the register space
> + * @enabled: flag if enabled
> + * @fmt: Current format descriptor
> + * @drm_fmts: Array of supported DRM formats
> + * @num_fmts: Number of supported DRM formats
> + * @bus_fmts: Array of supported bus formats
> + * @num_bus_fmts: Number of supported bus formats
> + * @w: Width
> + * @h: Height
> + * @mode: the operation mode
> + * @other: other layer
> + * @disp: back pointer to struct zynqmp_disp
> + */
> +struct zynqmp_disp_layer {
> +	struct drm_plane plane;
> +	struct device_node *of_node;
> +	struct zynqmp_disp_layer_dma dma[ZYNQMP_DISP_MAX_NUM_SUB_PLANES];
> +	unsigned int num_chan;
> +	enum zynqmp_disp_layer_type id;
> +	u32 offset;
> +	u8 enabled;
> +	const struct zynqmp_disp_fmt *fmt;
> +	u32 *drm_fmts;
> +	unsigned int num_fmts;
> +	u32 *bus_fmts;
> +	unsigned int num_bus_fmts;
> +	u32 w;
> +	u32 h;
> +	enum zynqmp_disp_layer_mode mode;
> +	struct zynqmp_disp_layer *other;
> +	struct zynqmp_disp *disp;
> +};
> +
> +/**
> + * struct zynqmp_disp_blend - Blender
> + * @base: Base address offset
> + */
> +struct zynqmp_disp_blend {
> +	void __iomem *base;
> +};
> +
> +/**
> + * struct zynqmp_disp_av_buf - AV buffer manager
> + * @base: Base address offset
> + */
> +struct zynqmp_disp_av_buf {
> +	void __iomem *base;
> +};
> +
> +/**
> + * struct zynqmp_disp_aud - Audio
> + * @base: Base address offset
> + */
> +struct zynqmp_disp_aud {
> +	void __iomem *base;
> +};
> +
> +/**
> + * struct zynqmp_disp - Display subsystem
> + * @xlnx_crtc: Xilinx DRM crtc
> + * @dev: device structure
> + * @dpsub: Display subsystem
> + * @drm: DRM core
> + * @enabled: flag if enabled
> + * @blend: Blender block
> + * @av_buf: AV buffer manager block
> + * @aud:Audio block
> + * @layers: layers
> + * @g_alpha_prop: global alpha property
> + * @alpha: current global alpha value
> + * @g_alpha_en_prop: the global alpha enable property
> + * @alpha_en: flag if the global alpha is enabled
> + * @color_prop: output color format property
> + * @color: current output color value
> + * @bg_c0_prop: 1st component of background color property
> + * @bg_c0: current value of 1st background color component
> + * @bg_c1_prop: 2nd component of background color property
> + * @bg_c1: current value of 2nd background color component
> + * @bg_c2_prop: 3rd component of background color property
> + * @bg_c2: current value of 3rd background color component
> + * @tpg_prop: Test Pattern Generation mode property
> + * @tpg_on: current TPG mode state
> + * @event: pending vblank event request
> + * @_ps_pclk: Pixel clock from PS
> + * @_pl_pclk: Pixel clock from PL
> + * @pclk: Pixel clock
> + * @pclk_en: Flag if the pixel clock is enabled
> + * @_ps_audclk: Audio clock from PS
> + * @_pl_audclk: Audio clock from PL
> + * @audclk: Audio clock
> + * @audclk_en: Flag if the audio clock is enabled
> + * @aclk: APB clock
> + * @aclk_en: Flag if the APB clock is enabled
> + */
> +struct zynqmp_disp {
> +	struct xlnx_crtc xlnx_crtc;
> +	struct device *dev;
> +	struct zynqmp_dpsub *dpsub;
> +	struct drm_device *drm;
> +	bool enabled;
> +	struct zynqmp_disp_blend blend;
> +	struct zynqmp_disp_av_buf av_buf;
> +	struct zynqmp_disp_aud aud;
> +	struct zynqmp_disp_layer layers[ZYNQMP_DISP_NUM_LAYERS];
> +	struct drm_property *g_alpha_prop;
> +	u32 alpha;
> +	struct drm_property *g_alpha_en_prop;
> +	bool alpha_en;
> +	struct drm_property *color_prop;
> +	unsigned int color;
> +	struct drm_property *bg_c0_prop;
> +	u32 bg_c0;
> +	struct drm_property *bg_c1_prop;
> +	u32 bg_c1;
> +	struct drm_property *bg_c2_prop;
> +	u32 bg_c2;
> +	struct drm_property *tpg_prop;
> +	bool tpg_on;
> +	struct drm_pending_vblank_event *event;
> +	/* Don't operate directly on _ps_ */
> +	struct clk *_ps_pclk;
> +	struct clk *_pl_pclk;
> +	struct clk *pclk;
> +	bool pclk_en;
> +	struct clk *_ps_audclk;
> +	struct clk *_pl_audclk;
> +	struct clk *audclk;
> +	bool audclk_en;
> +	struct clk *aclk;
> +	bool aclk_en;
> +};
> +
> +/**
> + * struct zynqmp_disp_fmt - Display subsystem format mapping
> + * @drm_fmt: drm format
> + * @disp_fmt: Display subsystem format
> + * @bus_fmt: Bus formats (live formats)
> + * @rgb: flag for RGB formats
> + * @swap: flag to swap r & b for rgb formats, and u & v for yuv formats
> + * @chroma_sub: flag for chroma subsampled formats
> + * @sf: scaling factors for upto 3 color components
> + */
> +struct zynqmp_disp_fmt {
> +	u32 drm_fmt;
> +	u32 disp_fmt;
> +	u32 bus_fmt;
> +	bool rgb;
> +	bool swap;
> +	bool chroma_sub;
> +	u32 sf[3];
> +};
> +
> +static void zynqmp_disp_write(void __iomem *base, int offset, u32 val)
> +{
> +	writel(val, base + offset);
> +}
> +
> +static u32 zynqmp_disp_read(void __iomem *base, int offset)
> +{
> +	return readl(base + offset);
> +}
> +
> +static void zynqmp_disp_clr(void __iomem *base, int offset, u32 clr)
> +{
> +	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) & ~clr);
> +}
> +
> +static void zynqmp_disp_set(void __iomem *base, int offset, u32 set)
> +{
> +	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) | set);
> +}
> +
> +/*
> + * Clock functions
> + */
> +
> +/**
> + * zynqmp_disp_clk_enable - Enable the clock if needed
> + * @clk: clk device
> + * @flag: flag if the clock is enabled
> + *
> + * Enable the clock only if it's not enabled @flag.
> + *
> + * Return: value from clk_prepare_enable().
> + */
> +static int zynqmp_disp_clk_enable(struct clk *clk, bool *flag)
> +{
> +	int ret = 0;
> +
> +	if (!*flag) {
> +		ret = clk_prepare_enable(clk);
> +		if (!ret)
> +			*flag = true;
> +	}
> +
> +	return ret;
> +}
> +
> +/**
> + * zynqmp_disp_clk_enable - Enable the clock if needed
> + * @clk: clk device
> + * @flag: flag if the clock is enabled
> + *
> + * Disable the clock only if it's enabled @flag.
> + */
> +static void zynqmp_disp_clk_disable(struct clk *clk, bool *flag)
> +{
> +	if (*flag) {
> +		clk_disable_unprepare(clk);
> +		*flag = false;
> +	}
> +}
> +
> +/**
> + * zynqmp_disp_clk_enable - Enable and disable the clock
> + * @clk: clk device
> + * @flag: flag if the clock is enabled
> + *
> + * This is to ensure the clock is disabled. The initial hardware state is
> + * unknown, and this makes sure that the clock is disabled.
> + *
> + * Return: value from clk_prepare_enable().
> + */
> +static int zynqmp_disp_clk_enable_disable(struct clk *clk, bool *flag)
> +{
> +	int ret = 0;
> +
> +	if (!*flag) {
> +		ret = clk_prepare_enable(clk);
> +		clk_disable_unprepare(clk);
> +	}
> +
> +	return ret;
> +}
> +
> +/*
> + * Blender functions
> + */
> +
> +/**
> + * zynqmp_disp_blend_set_output_fmt - Set the output format of the blend
> + * @blend: blend object
> + * @fmt: output format
> + *
> + * Set the output format to @fmt.
> + */
> +static void
> +zynqmp_disp_blend_set_output_fmt(struct zynqmp_disp_blend *blend, u32 fmt)
> +{
> +	u16 reset_coeffs[] = { 0x1000, 0x0, 0x0,
> +			       0x0, 0x1000, 0x0,
> +			       0x0, 0x0, 0x1000 };
> +	u32 reset_offsets[] = { 0x0, 0x0, 0x0 };
> +	u16 sdtv_coeffs[] = { 0x4c9, 0x864, 0x1d3,
> +			      0x7d4d, 0x7ab3, 0x800,
> +			      0x800, 0x794d, 0x7eb3 };
> +	u32 full_range_offsets[] = { 0x0, 0x8000000, 0x8000000 };
> +	u16 *coeffs;
> +	u32 *offsets;
> +	u32 offset, i;
> +
> +	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
> +	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
> +		coeffs = reset_coeffs;
> +		offsets = reset_offsets;
> +	} else {
> +		/* Hardcode Full-range SDTV values. Can be runtime config */
> +		coeffs = sdtv_coeffs;
> +		offsets = full_range_offsets;
> +	}
> +
> +	offset = ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF0;
> +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
> +		zynqmp_disp_write(blend->base, offset + i * 4, coeffs[i]);
> +
> +	offset = ZYNQMP_DISP_V_BLEND_LUMA_OUTCSC_OFFSET;
> +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
> +		zynqmp_disp_write(blend->base, offset + i * 4, offsets[i]);
> +}
> +
> +/**
> + * zynqmp_disp_blend_layer_enable - Enable a layer
> + * @blend: blend object
> + * @layer: layer to enable
> + *
> + * Enable a layer @layer.
> + */
> +static void zynqmp_disp_blend_layer_enable(struct zynqmp_disp_blend *blend,
> +					   struct zynqmp_disp_layer *layer)
> +{
> +	u32 reg, offset, i, s0, s1;
> +	u16 sdtv_coeffs[] = { 0x1000, 0x166f, 0x0,
> +			      0x1000, 0x7483, 0x7a7f,
> +			      0x1000, 0x0, 0x1c5a };
> +	u16 swap_coeffs[] = { 0x1000, 0x0, 0x0,
> +			      0x0, 0x1000, 0x0,
> +			      0x0, 0x0, 0x1000 };
> +	u16 *coeffs;
> +	u32 offsets[] = { 0x0, 0x1800, 0x1800 };
> +
> +	reg = layer->fmt->rgb ? ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_RGB : 0;
> +	reg |= layer->fmt->chroma_sub ?
> +	       ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_EN_US : 0;
> +
> +	zynqmp_disp_write(blend->base,
> +			  ZYNQMP_DISP_V_BLEND_LAYER_CONTROL + layer->offset,
> +			  reg);
> +
> +	if (layer->id == ZYNQMP_DISP_LAYER_VID)
> +		offset = ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0;
> +	else
> +		offset = ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0;
> +
> +	if (!layer->fmt->rgb) {
> +		coeffs = sdtv_coeffs;
> +		s0 = 1;
> +		s1 = 2;
> +	} else {
> +		coeffs = swap_coeffs;
> +		s0 = 0;
> +		s1 = 2;
> +
> +		/* No offset for RGB formats */
> +		for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
> +			offsets[i] = 0;
> +	}
> +
> +	if (layer->fmt->swap) {
> +		for (i = 0; i < 3; i++) {
> +			coeffs[i * 3 + s0] ^= coeffs[i * 3 + s1];
> +			coeffs[i * 3 + s1] ^= coeffs[i * 3 + s0];
> +			coeffs[i * 3 + s0] ^= coeffs[i * 3 + s1];
> +		}
> +	}
> +
> +	/* Program coefficients. Can be runtime configurable */
> +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
> +		zynqmp_disp_write(blend->base, offset + i * 4, coeffs[i]);
> +
> +	if (layer->id == ZYNQMP_DISP_LAYER_VID)
> +		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET;
> +	else
> +		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET;
> +
> +	/* Program offsets. Can be runtime configurable */
> +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
> +		zynqmp_disp_write(blend->base, offset + i * 4, offsets[i]);
> +}
> +
> +/**
> + * zynqmp_disp_blend_layer_disable - Disable a layer
> + * @blend: blend object
> + * @layer: layer to disable
> + *
> + * Disable a layer @layer.
> + */
> +static void zynqmp_disp_blend_layer_disable(struct zynqmp_disp_blend *blend,
> +					    struct zynqmp_disp_layer *layer)
> +{
> +	u32 offset;
> +	unsigned int i;
> +
> +	zynqmp_disp_write(blend->base,
> +			  ZYNQMP_DISP_V_BLEND_LAYER_CONTROL + layer->offset, 0);
> +
> +	if (layer->id == ZYNQMP_DISP_LAYER_VID)
> +		offset = ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0;
> +	else
> +		offset = ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0;
> +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
> +		zynqmp_disp_write(blend->base, offset + i * 4, 0);
> +
> +	if (layer->id == ZYNQMP_DISP_LAYER_VID)
> +		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET;
> +	else
> +		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET;
> +
> +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
> +		zynqmp_disp_write(blend->base, offset + i * 4, 0);
> +}
> +
> +/**
> + * zynqmp_disp_blend_set_bg_color - Set the background color
> + * @blend: blend object
> + * @c0: color component 0
> + * @c1: color component 1
> + * @c2: color component 2
> + *
> + * Set the background color.
> + */
> +static void zynqmp_disp_blend_set_bg_color(struct zynqmp_disp_blend *blend,
> +					   u32 c0, u32 c1, u32 c2)
> +{
> +	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_BG_CLR_0, c0);
> +	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_BG_CLR_1, c1);
> +	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_BG_CLR_2, c2);
> +}
> +
> +/**
> + * zynqmp_disp_blend_set_alpha - Set the alpha for blending
> + * @blend: blend object
> + * @alpha: alpha value to be used
> + *
> + * Set the alpha for blending.
> + */
> +static void
> +zynqmp_disp_blend_set_alpha(struct zynqmp_disp_blend *blend, u32 alpha)
> +{
> +	u32 reg;
> +
> +	reg = zynqmp_disp_read(blend->base,
> +			       ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA);
> +	reg &= ~ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MASK;
> +	reg |= alpha << 1;
> +	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA,
> +			  reg);
> +}
> +
> +/**
> + * zynqmp_disp_blend_enable_alpha - Enable/disable the global alpha
> + * @blend: blend object
> + * @enable: flag to enable or disable alpha blending
> + *
> + * Enable/disable the global alpha blending based on @enable.
> + */
> +static void
> +zynqmp_disp_blend_enable_alpha(struct zynqmp_disp_blend *blend, bool enable)
> +{
> +	if (enable)
> +		zynqmp_disp_set(blend->base,
> +				ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA, BIT(0));
> +	else
> +		zynqmp_disp_clr(blend->base,
> +				ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA, BIT(0));
> +}
> +
> +/* List of blend output formats */
> +/* The id / order should be aligned with zynqmp_disp_color_enum */
> +static const struct zynqmp_disp_fmt blend_output_fmts[] = {
> +	{
> +		.disp_fmt	= ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB,
> +	}, {
> +		.disp_fmt	= ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444,
> +	}, {
> +		.disp_fmt	= ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422,
> +	}, {
> +		.disp_fmt	= ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY,
> +	}
> +};
> +
> +/*
> + * AV buffer manager functions
> + */
> +
> +/* List of video layer formats */
> +#define ZYNQMP_DISP_AV_BUF_VID_FMT_YUYV	2
> +static const struct zynqmp_disp_fmt av_buf_vid_fmts[] = {
> +	{
> +		.drm_fmt	= DRM_FORMAT_VYUY,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY,
> +		.rgb		= false,
> +		.swap		= true,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_UYVY,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_YUYV,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_YVYU,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV,
> +		.rgb		= false,
> +		.swap		= true,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_YUV422,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_YVU422,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16,
> +		.rgb		= false,
> +		.swap		= true,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_YUV444,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_YVU444,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24,
> +		.rgb		= false,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_NV16,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_NV61,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI,
> +		.rgb		= false,
> +		.swap		= true,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_BGR888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_RGB888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888,
> +		.rgb		= true,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_XBGR8888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_XRGB8888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880,
> +		.rgb		= true,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_XBGR2101010,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_XRGB2101010,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10,
> +		.rgb		= true,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_YUV420,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_YVU420,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420,
> +		.rgb		= false,
> +		.swap		= true,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_NV12,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_NV21,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420,
> +		.rgb		= false,
> +		.swap		= true,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}
> +};
> +
> +/* List of graphics layer formats */
> +#define ZYNQMP_DISP_AV_BUF_GFX_FMT_RGB565	10
> +static const struct zynqmp_disp_fmt av_buf_gfx_fmts[] = {
> +	{
> +		.drm_fmt	= DRM_FORMAT_ABGR8888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_ARGB8888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888,
> +		.rgb		= true,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_RGBA8888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_BGRA8888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888,
> +		.rgb		= true,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_BGR888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB888,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_RGB888,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_BGR888,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_RGBA5551,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_BGRA5551,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551,
> +		.rgb		= true,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_RGBA4444,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_BGRA4444,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444,
> +		.rgb		= true,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_RGB565,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +	}, {
> +		.drm_fmt	= DRM_FORMAT_BGR565,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565,
> +		.rgb		= true,
> +		.swap		= true,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> +	}
> +};
> +
> +/* List of live formats */
> +/* Format can be combination of color, bpc, and cb-cr order.
> + * - Color: RGB / YUV444 / YUV422 / Y only
> + * - BPC: 6, 8, 10, 12
> + * - Swap: Cb and Cr swap
> + * which can be 32 bus formats. Only list the subset of those for now.
> + */
> +static const struct zynqmp_disp_fmt av_buf_live_fmts[] = {
> +	{
> +		.bus_fmt	= MEDIA_BUS_FMT_RGB666_1X18,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_6 ||
> +				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> +	}, {
> +		.bus_fmt	= MEDIA_BUS_FMT_RBG888_1X24,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
> +				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB,
> +		.rgb		= true,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.bus_fmt	= MEDIA_BUS_FMT_UYVY8_1X16,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
> +				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.bus_fmt	= MEDIA_BUS_FMT_VUY8_1X24,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
> +				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV444,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= false,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> +	}, {
> +		.bus_fmt	= MEDIA_BUS_FMT_UYVY10_1X20,
> +		.disp_fmt	= ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_10 ||
> +				  ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422,
> +		.rgb		= false,
> +		.swap		= false,
> +		.chroma_sub	= true,
> +		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> +	}
> +};
> +
> +/**
> + * zynqmp_disp_av_buf_set_fmt - Set the input formats
> + * @av_buf: av buffer manager
> + * @fmt: formats
> + *
> + * Set the av buffer manager format to @fmt. @fmt should have valid values
> + * for both video and graphics layer.
> + */
> +static void
> +zynqmp_disp_av_buf_set_fmt(struct zynqmp_disp_av_buf *av_buf, u32 fmt)
> +{
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_FMT, fmt);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_get_fmt - Get the input formats
> + * @av_buf: av buffer manager
> + *
> + * Get the input formats (which include video and graphics) of
> + * av buffer manager.
> + *
> + * Return: value of ZYNQMP_DISP_AV_BUF_FMT register.
> + */
> +static u32
> +zynqmp_disp_av_buf_get_fmt(struct zynqmp_disp_av_buf *av_buf)
> +{
> +	return zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_FMT);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_set_vid_clock_src - Set the video clock source
> + * @av_buf: av buffer manager
> + * @from_ps: flag if the video clock is from ps
> + *
> + * Set the video clock source based on @from_ps. It can come from either PS or
> + * PL.
> + */
> +static void
> +zynqmp_disp_av_buf_set_vid_clock_src(struct zynqmp_disp_av_buf *av_buf,
> +				     bool from_ps)
> +{
> +	u32 reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC);
> +
> +	if (from_ps)
> +		reg |= ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS;
> +	else
> +		reg &= ~ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS;
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_set_vid_timing_src - Set the video timing source
> + * @av_buf: av buffer manager
> + * @internal: flag if the video timing is generated internally
> + *
> + * Set the video timing source based on @internal. It can come externally or
> + * be generated internally.
> + */
> +static void
> +zynqmp_disp_av_buf_set_vid_timing_src(struct zynqmp_disp_av_buf *av_buf,
> +				      bool internal)
> +{
> +	u32 reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC);
> +
> +	if (internal)
> +		reg |= ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING;
> +	else
> +		reg &= ~ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING;
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_set_aud_clock_src - Set the audio clock source
> + * @av_buf: av buffer manager
> + * @from_ps: flag if the video clock is from ps
> + *
> + * Set the audio clock source based on @from_ps. It can come from either PS or
> + * PL.
> + */
> +static void
> +zynqmp_disp_av_buf_set_aud_clock_src(struct zynqmp_disp_av_buf *av_buf,
> +				     bool from_ps)
> +{
> +	u32 reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC);
> +
> +	if (from_ps)
> +		reg |= ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS;
> +	else
> +		reg &= ~ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS;
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_enable_buf - Enable buffers
> + * @av_buf: av buffer manager
> + *
> + * Enable all (video and audio) buffers.
> + */
> +static void
> +zynqmp_disp_av_buf_enable_buf(struct zynqmp_disp_av_buf *av_buf)
> +{
> +	u32 reg, i;
> +
> +	reg = ZYNQMP_DISP_AV_BUF_CHBUF_EN;
> +	reg |= ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MAX <<
> +	       ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT;
> +
> +	for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_VID_GFX_BUFFERS; i++)
> +		zynqmp_disp_write(av_buf->base,
> +				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4, reg);
> +
> +	reg = ZYNQMP_DISP_AV_BUF_CHBUF_EN;
> +	reg |= ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_AUD_MAX <<
> +	       ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT;
> +
> +	for (; i < ZYNQMP_DISP_AV_BUF_NUM_BUFFERS; i++)
> +		zynqmp_disp_write(av_buf->base,
> +				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_disable_buf - Disable buffers
> + * @av_buf: av buffer manager
> + *
> + * Disable all (video and audio) buffers.
> + */
> +static void
> +zynqmp_disp_av_buf_disable_buf(struct zynqmp_disp_av_buf *av_buf)
> +{
> +	u32 reg, i;
> +
> +	reg = ZYNQMP_DISP_AV_BUF_CHBUF_FLUSH & ~ZYNQMP_DISP_AV_BUF_CHBUF_EN;
> +	for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_BUFFERS; i++)
> +		zynqmp_disp_write(av_buf->base,
> +				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_enable_aud - Enable audio
> + * @av_buf: av buffer manager
> + *
> + * Enable all audio buffers.
> + */
> +static void
> +zynqmp_disp_av_buf_enable_aud(struct zynqmp_disp_av_buf *av_buf)
> +{
> +	u32 reg;
> +
> +	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
> +	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK;
> +	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MEM;
> +	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN;
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_enable - Enable the video pipe
> + * @av_buf: av buffer manager
> + *
> + * De-assert the video pipe reset
> + */
> +static void
> +zynqmp_disp_av_buf_enable(struct zynqmp_disp_av_buf *av_buf)
> +{
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_SRST_REG, 0);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_disable - Disable the video pipe
> + * @av_buf: av buffer manager
> + *
> + * Assert the video pipe reset
> + */
> +static void
> +zynqmp_disp_av_buf_disable(struct zynqmp_disp_av_buf *av_buf)
> +{
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_SRST_REG,
> +			  ZYNQMP_DISP_AV_BUF_SRST_REG_VID_RST);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_disable_aud - Disable audio
> + * @av_buf: av buffer manager
> + *
> + * Disable all audio buffers.
> + */
> +static void
> +zynqmp_disp_av_buf_disable_aud(struct zynqmp_disp_av_buf *av_buf)
> +{
> +	u32 reg;
> +
> +	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
> +	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK;
> +	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_DISABLE;
> +	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN;
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_set_tpg - Set TPG mode
> + * @av_buf: av buffer manager
> + * @tpg_on: if TPG should be on
> + *
> + * Set the TPG mode based on @tpg_on.
> + */
> +static void zynqmp_disp_av_buf_set_tpg(struct zynqmp_disp_av_buf *av_buf,
> +				       bool tpg_on)
> +{
> +	u32 reg;
> +
> +	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
> +	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
> +	if (tpg_on)
> +		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN;
> +	else
> +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN;
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_enable_vid - Enable the video layer buffer
> + * @av_buf: av buffer manager
> + * @layer: layer to enable
> + * @mode: operation mode of layer
> + *
> + * Enable the video/graphics buffer for @layer.
> + */
> +static void zynqmp_disp_av_buf_enable_vid(struct zynqmp_disp_av_buf *av_buf,
> +					  struct zynqmp_disp_layer *layer,
> +					  enum zynqmp_disp_layer_mode mode)
> +{
> +	u32 reg;
> +
> +	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
> +	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
> +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
> +		if (mode == ZYNQMP_DISP_LAYER_NONLIVE)
> +			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM;
> +		else
> +			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE;
> +	} else {
> +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK;
> +		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM;
> +		if (mode == ZYNQMP_DISP_LAYER_NONLIVE)
> +			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM;
> +		else
> +			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE;
> +	}
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_disable_vid - Disable the video layer buffer
> + * @av_buf: av buffer manager
> + * @layer: layer to disable
> + *
> + * Disable the video/graphics buffer for @layer.
> + */
> +static void
> +zynqmp_disp_av_buf_disable_vid(struct zynqmp_disp_av_buf *av_buf,
> +			       struct zynqmp_disp_layer *layer)
> +{
> +	u32 reg;
> +
> +	reg = zynqmp_disp_read(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT);
> +	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
> +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
> +		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_NONE;
> +	} else {
> +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK;
> +		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_DISABLE;
> +	}
> +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> +}
> +
> +/**
> + * zynqmp_disp_av_buf_init_sf - Initialize scaling factors
> + * @av_buf: av buffer manager
> + * @vid_fmt: video format descriptor
> + * @gfx_fmt: graphics format descriptor
> + *
> + * Initialize scaling factors for both video and graphics layers.
> + * If the format descriptor is NULL, the function skips the programming.
> + */
> +static void zynqmp_disp_av_buf_init_sf(struct zynqmp_disp_av_buf *av_buf,
> +				       const struct zynqmp_disp_fmt *vid_fmt,
> +				       const struct zynqmp_disp_fmt *gfx_fmt)
> +{
> +	unsigned int i;
> +	u32 offset;
> +
> +	if (gfx_fmt) {
> +		offset = ZYNQMP_DISP_AV_BUF_GFX_COMP0_SF;
> +		for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_SF; i++)
> +			zynqmp_disp_write(av_buf->base, offset + i * 4,
> +					  gfx_fmt->sf[i]);
> +	}
> +
> +	if (vid_fmt) {
> +		offset = ZYNQMP_DISP_AV_BUF_VID_COMP0_SF;
> +		for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_SF; i++)
> +			zynqmp_disp_write(av_buf->base, offset + i * 4,
> +					  vid_fmt->sf[i]);
> +	}
> +}
> +
> +/*
> + * Audio functions
> + */
> +
> +/**
> + * zynqmp_disp_aud_init - Initialize the audio
> + * @aud: audio
> + *
> + * Initialize the audio with default mixer volume. The de-assertion will
> + * initialize the audio states.
> + */
> +static void zynqmp_disp_aud_init(struct zynqmp_disp_aud *aud)
> +{
> +	/* Clear the audio soft reset register as it's an non-reset flop */
> +	zynqmp_disp_write(aud->base, ZYNQMP_DISP_AUD_SOFT_RESET, 0);
> +	zynqmp_disp_write(aud->base, ZYNQMP_DISP_AUD_MIXER_VOLUME,
> +			  ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE);
> +}
> +
> +/**
> + * zynqmp_disp_aud_deinit - De-initialize the audio
> + * @aud: audio
> + *
> + * Put the audio in reset.
> + */
> +static void zynqmp_disp_aud_deinit(struct zynqmp_disp_aud *aud)
> +{
> +	zynqmp_disp_set(aud->base, ZYNQMP_DISP_AUD_SOFT_RESET,
> +			ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST);
> +}
> +
> +/*
> + * ZynqMP Display layer functions
> + */
> +
> +/**
> + * zynqmp_disp_layer_check_size - Verify width and height for the layer
> + * @disp: Display subsystem
> + * @layer: layer
> + * @width: width
> + * @height: height
> + *
> + * The Display subsystem has the limitation that both layers should have
> + * identical size. This function stores width and height of @layer, and verifies
> + * if the size (width and height) is valid.
> + *
> + * Return: 0 on success, or -EINVAL if width or/and height is invalid.
> + */
> +static int zynqmp_disp_layer_check_size(struct zynqmp_disp *disp,
> +					struct zynqmp_disp_layer *layer,
> +					u32 width, u32 height)
> +{
> +	struct zynqmp_disp_layer *other = layer->other;
> +
> +	if (other->enabled && (other->w != width || other->h != height)) {
> +		dev_err(disp->dev, "Layer width:height must be %d:%d\n",
> +			other->w, other->h);
> +		return -EINVAL;
> +	}
> +
> +	layer->w = width;
> +	layer->h = height;
> +
> +	return 0;
> +}
> +
> +/**
> + * zynqmp_disp_map_fmt - Find the Display subsystem format for given drm format
> + * @fmts: format table to look up
> + * @size: size of the table @fmts
> + * @drm_fmt: DRM format to search
> + *
> + * Search a Display subsystem format corresponding to the given DRM format
> + * @drm_fmt, and return the format descriptor which contains the Display
> + * subsystem format value.
> + *
> + * Return: a Display subsystem format descriptor on success, or NULL.
> + */
> +static const struct zynqmp_disp_fmt *
> +zynqmp_disp_map_fmt(const struct zynqmp_disp_fmt fmts[],
> +		    unsigned int size, uint32_t drm_fmt)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < size; i++)
> +		if (fmts[i].drm_fmt == drm_fmt)
> +			return &fmts[i];
> +
> +	return NULL;
> +}
> +
> +/**
> + * zynqmp_disp_set_fmt - Set the format of the layer
> + * @disp: Display subsystem
> + * @layer: layer to set the format
> + * @drm_fmt: DRM format to set
> + *
> + * Set the format of the given layer to @drm_fmt.
> + *
> + * Return: 0 on success. -EINVAL if @drm_fmt is not supported by the layer.
> + */
> +static int zynqmp_disp_layer_set_fmt(struct zynqmp_disp *disp,
> +				     struct zynqmp_disp_layer *layer,
> +				     uint32_t drm_fmt)
> +{
> +	const struct zynqmp_disp_fmt *fmt;
> +	const struct zynqmp_disp_fmt *vid_fmt = NULL, *gfx_fmt = NULL;
> +	u32 size, fmts, mask;
> +
> +	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
> +		size = ARRAY_SIZE(av_buf_vid_fmts);
> +		mask = ~ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MASK;
> +		fmt = zynqmp_disp_map_fmt(av_buf_vid_fmts, size, drm_fmt);
> +		vid_fmt = fmt;
> +	} else {
> +		size = ARRAY_SIZE(av_buf_gfx_fmts);
> +		mask = ~ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_MASK;
> +		fmt = zynqmp_disp_map_fmt(av_buf_gfx_fmts, size, drm_fmt);
> +		gfx_fmt = fmt;
> +	}
> +
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	fmts = zynqmp_disp_av_buf_get_fmt(&disp->av_buf);
> +	fmts &= mask;
> +	fmts |= fmt->disp_fmt;
> +	zynqmp_disp_av_buf_set_fmt(&disp->av_buf, fmts);
> +	zynqmp_disp_av_buf_init_sf(&disp->av_buf, vid_fmt, gfx_fmt);
> +	layer->fmt = fmt;
> +
> +	return 0;
> +}
> +
> +/**
> + * zynqmp_disp_set_tpg - Enable or disable TPG
> + * @disp: Display subsystem
> + * @layer: Video layer
> + * @tpg_on: flag if TPG needs to be enabled or disabled
> + *
> + * Enable / disable the TPG mode on the video layer @layer depending on
> + * @tpg_on. The video layer should be disabled prior to enable request.
> + *
> + * Return: 0 on success. -ENODEV if it's not video layer. -EIO if
> + * the video layer is enabled.
> + */
> +static int zynqmp_disp_layer_set_tpg(struct zynqmp_disp *disp,
> +				     struct zynqmp_disp_layer *layer,
> +				     bool tpg_on)
> +{
> +	if (layer->id != ZYNQMP_DISP_LAYER_VID) {
> +		dev_err(disp->dev,
> +			"only the video layer has the tpg mode\n");
> +		return -ENODEV;
> +	}
> +
> +	if (layer->enabled) {
> +		dev_err(disp->dev,
> +			"the video layer should be disabled for tpg mode\n");
> +		return -EIO;
> +	}
> +
> +	zynqmp_disp_av_buf_set_tpg(&disp->av_buf, tpg_on);
> +	disp->tpg_on = tpg_on;
> +
> +	return 0;
> +}
> +
> +/**
> + * zynqmp_disp_get_tpg - Get the TPG mode status
> + * @disp: Display subsystem
> + * @layer: Video layer
> + *
> + * Return if the TPG is enabled or not.
> + *
> + * Return: true if TPG is on, otherwise false
> + */
> +static bool zynqmp_disp_layer_get_tpg(struct zynqmp_disp *disp,
> +				      struct zynqmp_disp_layer *layer)
> +{
> +	return disp->tpg_on;
> +}
> +
> +/**
> + * zynqmp_disp_get_fmt - Get the supported DRM formats of the layer
> + * @disp: Display subsystem
> + * @layer: layer to get the formats
> + * @drm_fmts: pointer to array of DRM format strings
> + * @num_fmts: pointer to number of returned DRM formats
> + *
> + * Get the supported DRM formats of the given layer.
> + */
> +static void zynqmp_disp_layer_get_fmts(struct zynqmp_disp *disp,
> +				       struct zynqmp_disp_layer *layer,
> +				       u32 **drm_fmts, unsigned int *num_fmts)
> +{
> +	*drm_fmts = layer->drm_fmts;
> +	*num_fmts = layer->num_fmts;
> +}
> +
> +/**
> + * zynqmp_disp_layer_enable - Enable the layer
> + * @disp: Display subsystem
> + * @layer: layer to esable
> + * @mode: operation mode
> + *
> + * Enable the layer @layer.
> + *
> + * Return: 0 on success, otherwise error code.
> + */
> +static int zynqmp_disp_layer_enable(struct zynqmp_disp *disp,
> +				    struct zynqmp_disp_layer *layer,
> +				    enum zynqmp_disp_layer_mode mode)
> +{
> +	struct device *dev = disp->dev;
> +	struct dma_async_tx_descriptor *desc;
> +	enum dma_ctrl_flags flags;
> +	unsigned int i;
> +
> +	if (layer->enabled && layer->mode != mode) {
> +		dev_err(dev, "layer is already enabled in different mode\n");
> +		return -EBUSY;
> +	}
> +
> +	zynqmp_disp_av_buf_enable_vid(&disp->av_buf, layer, mode);
> +	zynqmp_disp_blend_layer_enable(&disp->blend, layer);
> +
> +	layer->enabled = true;
> +	layer->mode = mode;
> +
> +	if (mode == ZYNQMP_DISP_LAYER_LIVE)
> +		return 0;
> +
> +	for (i = 0; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++) {
> +		struct zynqmp_disp_layer_dma *dma = &layer->dma[i];
> +
> +		if (dma->chan && dma->is_active) {
> +			flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
> +			desc = dmaengine_prep_interleaved_dma(dma->chan,
> +							      &dma->xt, flags);
> +			if (!desc) {
> +				dev_err(dev, "failed to prep DMA descriptor\n");
> +				return -ENOMEM;
> +			}
> +
> +			dmaengine_submit(desc);
> +			dma_async_issue_pending(dma->chan);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * zynqmp_disp_layer_disable - Disable the layer
> + * @disp: Display subsystem
> + * @layer: layer to disable
> + * @mode: operation mode
> + *
> + * Disable the layer @layer.
> + *
> + * Return: 0 on success, or -EBUSY if the layer is in different mode.
> + */
> +static int zynqmp_disp_layer_disable(struct zynqmp_disp *disp,
> +				     struct zynqmp_disp_layer *layer,
> +				     enum zynqmp_disp_layer_mode mode)
> +{
> +	struct device *dev = disp->dev;
> +	unsigned int i;
> +
> +	if (layer->mode != mode) {
> +		dev_err(dev, "the layer is operating in different mode\n");
> +		return -EBUSY;
> +	}
> +
> +	for (i = 0; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++)
> +		if (layer->dma[i].chan && layer->dma[i].is_active)
> +			dmaengine_terminate_sync(layer->dma[i].chan);
> +
> +	zynqmp_disp_av_buf_disable_vid(&disp->av_buf, layer);
> +	zynqmp_disp_blend_layer_disable(&disp->blend, layer);
> +	layer->enabled = false;
> +
> +	return 0;
> +}
> +
> +/**
> + * zynqmp_disp_layer_request_dma - Request DMA channels for a layer
> + * @disp: Display subsystem
> + * @layer: layer to request DMA channels
> + * @name: identifier string for layer type
> + *
> + * Request DMA engine channels for corresponding layer.
> + *
> + * Return: 0 on success, or err value from of_dma_request_slave_channel().
> + */
> +static int
> +zynqmp_disp_layer_request_dma(struct zynqmp_disp *disp,
> +			      struct zynqmp_disp_layer *layer, const char *name)
> +{
> +	struct zynqmp_disp_layer_dma *dma;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < layer->num_chan; i++) {
> +		char temp[16];
> +
> +		dma = &layer->dma[i];
> +		snprintf(temp, sizeof(temp), "%s%d", name, i);
> +		dma->chan = of_dma_request_slave_channel(layer->of_node,
> +							 temp);
> +		if (IS_ERR(dma->chan)) {
> +			dev_err(disp->dev, "failed to request dma channel\n");
> +			ret = PTR_ERR(dma->chan);
> +			dma->chan = NULL;
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * zynqmp_disp_layer_release_dma - Release DMA channels for a layer
> + * @disp: Display subsystem
> + * @layer: layer to release DMA channels
> + *
> + * Release the dma channels associated with @layer.
> + */
> +static void zynqmp_disp_layer_release_dma(struct zynqmp_disp *disp,
> +					  struct zynqmp_disp_layer *layer)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < layer->num_chan; i++) {
> +		if (layer->dma[i].chan) {
> +			/* Make sure the channel is terminated before release */
> +			dmaengine_terminate_all(layer->dma[i].chan);
> +			dma_release_channel(layer->dma[i].chan);
> +		}
> +	}
> +}
> +
> +/**
> + * zynqmp_disp_layer_is_live - if any layer is live
> + * @disp: Display subsystem
> + *
> + * Return: true if any layer is live
> + */
> +static bool zynqmp_disp_layer_is_live(struct zynqmp_disp *disp)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
> +		if (disp->layers[i].enabled &&
> +		    disp->layers[i].mode == ZYNQMP_DISP_LAYER_LIVE)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/**
> + * zynqmp_disp_layer_is_enabled - if any layer is enabled
> + * @disp: Display subsystem
> + *
> + * Return: true if any layer is enabled
> + */
> +static bool zynqmp_disp_layer_is_enabled(struct zynqmp_disp *disp)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
> +		if (disp->layers[i].enabled)
> +			return true;
> +
> +	return false;
> +}
> +
> +/**
> + * zynqmp_disp_layer_destroy - Destroy all layers
> + * @disp: Display subsystem
> + *
> + * Destroy all layers.
> + */
> +static void zynqmp_disp_layer_destroy(struct zynqmp_disp *disp)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
> +		zynqmp_disp_layer_release_dma(disp, &disp->layers[i]);
> +		if (disp->layers[i].of_node)
> +			of_node_put(disp->layers[i].of_node);
> +	}
> +}
> +
> +/**
> + * zynqmp_disp_layer_create - Create all layers
> + * @disp: Display subsystem
> + *
> + * Create all layers.
> + *
> + * Return: 0 on success, otherwise error code from failed function
> + */
> +static int zynqmp_disp_layer_create(struct zynqmp_disp *disp)
> +{
> +	struct zynqmp_disp_layer *layer;
> +	unsigned int i;
> +	int num_chans[ZYNQMP_DISP_NUM_LAYERS] = { 3, 1 };
> +	const char * const dma_name[] = { "vid", "gfx" };
> +	int ret;
> +
> +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
> +		char temp[16];
> +
> +		layer = &disp->layers[i];
> +		layer->id = i;
> +		layer->offset = i * 4;
> +		layer->other = &disp->layers[!i];
> +		layer->num_chan = num_chans[i];
> +		snprintf(temp, sizeof(temp), "%s-layer", dma_name[i]);
> +		layer->of_node = of_get_child_by_name(disp->dev->of_node, temp);
> +		if (!layer->of_node)
> +			goto err;
> +		ret = zynqmp_disp_layer_request_dma(disp, layer, dma_name[i]);
> +		if (ret)
> +			goto err;
> +		layer->disp = disp;
> +	}
> +
> +	return 0;
> +
> +err:
> +	zynqmp_disp_layer_destroy(disp);
> +	return ret;
> +}
> +
> +/*
> + * ZynqMP Display internal functions
> + */
> +
> +/*
> + * Output format enumeration for DRM property.
> + * The ID should be aligned with blend_output_fmts.
> + * The string should be aligned with how zynqmp_dp_set_color() decodes.
> + */
> +static struct drm_prop_enum_list zynqmp_disp_color_enum[] = {
> +	{ 0, "rgb" },
> +	{ 1, "ycrcb444" },
> +	{ 2, "ycrcb422" },
> +	{ 3, "yonly" },
> +};
> +
> +/**
> + * zynqmp_disp_set_output_fmt - Set the output format
> + * @disp: Display subsystem
> + * @id: the format ID. Refer to zynqmp_disp_color_enum[].
> + *
> + * This function sets the output format of the display / blender as well as
> + * the format of DP controller. The @id should be aligned with
> + * zynqmp_disp_color_enum, thus function needs to be used for DRM property.
> + */
> +static void
> +zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int id)
> +{
> +	const struct zynqmp_disp_fmt *fmt = &blend_output_fmts[id];
> +
> +	zynqmp_dp_set_color(disp->dpsub->dp, zynqmp_disp_color_enum[id].name);
> +	zynqmp_disp_blend_set_output_fmt(&disp->blend, fmt->disp_fmt);
> +}
> +
> +/**
> + * zynqmp_disp_set_bg_color - Set the background color
> + * @disp: Display subsystem
> + * @c0: color component 0
> + * @c1: color component 1
> + * @c2: color component 2
> + *
> + * Set the background color with given color components (@c0, @c1, @c2).
> + */
> +static void zynqmp_disp_set_bg_color(struct zynqmp_disp *disp,
> +				     u32 c0, u32 c1, u32 c2)
> +{
> +	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
> +}
> +
> +/**
> + * zynqmp_disp_set_alpha - Set the alpha value
> + * @disp: Display subsystem
> + * @alpha: alpha value to set
> + *
> + * Set the alpha value for blending.
> + */
> +static void zynqmp_disp_set_alpha(struct zynqmp_disp *disp, u32 alpha)
> +{
> +	disp->alpha = alpha;
> +	zynqmp_disp_blend_set_alpha(&disp->blend, alpha);
> +}
> +
> +/**
> + * zynqmp_disp_get_alpha - Get the alpha value
> + * @disp: Display subsystem
> + *
> + * Get the alpha value for blending.
> + *
> + * Return: current alpha value.
> + */
> +static u32 zynqmp_disp_get_alpha(struct zynqmp_disp *disp)
> +{
> +	return disp->alpha;
> +}
> +
> +/**
> + * zynqmp_disp_set_g_alpha - Enable/disable the global alpha blending
> + * @disp: Display subsystem
> + * @enable: flag to enable or disable alpha blending
> + *
> + * Set the alpha value for blending.
> + */
> +static void zynqmp_disp_set_g_alpha(struct zynqmp_disp *disp, bool enable)
> +{
> +	disp->alpha_en = enable;
> +	zynqmp_disp_blend_enable_alpha(&disp->blend, enable);
> +}
> +
> +/**
> + * zynqmp_disp_get_g_alpha - Get the global alpha status
> + * @disp: Display subsystem
> + *
> + * Get the global alpha statue.
> + *
> + * Return: true if global alpha is enabled, or false.
> + */
> +static bool zynqmp_disp_get_g_alpha(struct zynqmp_disp *disp)
> +{
> +	return disp->alpha_en;
> +}
> +
> +/**
> + * zynqmp_disp_enable - Enable the Display subsystem
> + * @disp: Display subsystem
> + *
> + * Enable the Display subsystem.
> + */
> +static void zynqmp_disp_enable(struct zynqmp_disp *disp)
> +{
> +	bool live;
> +
> +	if (disp->enabled)
> +		return;
> +
> +	zynqmp_disp_av_buf_enable(&disp->av_buf);
> +	/* Choose clock source based on the DT clock handle */
> +	zynqmp_disp_av_buf_set_vid_clock_src(&disp->av_buf, !!disp->_ps_pclk);
> +	zynqmp_disp_av_buf_set_aud_clock_src(&disp->av_buf, !!disp->_ps_audclk);
> +	live = zynqmp_disp_layer_is_live(disp);
> +	zynqmp_disp_av_buf_set_vid_timing_src(&disp->av_buf, !live);
> +	zynqmp_disp_av_buf_enable_buf(&disp->av_buf);
> +	zynqmp_disp_av_buf_enable_aud(&disp->av_buf);
> +	zynqmp_disp_aud_init(&disp->aud);
> +	disp->enabled = true;
> +}
> +
> +/**
> + * zynqmp_disp_disable - Disable the Display subsystem
> + * @disp: Display subsystem
> + * @force: flag to disable forcefully
> + *
> + * Disable the Display subsystem.
> + */
> +static void zynqmp_disp_disable(struct zynqmp_disp *disp, bool force)
> +{
> +	struct drm_crtc *crtc = &disp->xlnx_crtc.crtc;
> +
> +	if (!force && (!disp->enabled || zynqmp_disp_layer_is_enabled(disp)))
> +		return;
> +
> +	zynqmp_disp_aud_deinit(&disp->aud);
> +	zynqmp_disp_av_buf_disable_aud(&disp->av_buf);
> +	zynqmp_disp_av_buf_disable_buf(&disp->av_buf);
> +	zynqmp_disp_av_buf_disable(&disp->av_buf);
> +
> +	/* Mark the flip is done as crtc is disabled anyway */
> +	if (crtc->state->event) {
> +		complete_all(crtc->state->event->base.completion);
> +		crtc->state->event = NULL;
> +	}
> +
> +	disp->enabled = false;
> +}
> +
> +/*
> + * ZynqMP Display external functions for zynqmp_dp
> + */
> +
> +/**
> + * zynqmp_disp_handle_vblank - Handle the vblank event
> + * @disp: Display subsystem
> + *
> + * This function handles the vblank interrupt, and sends an event to
> + * CRTC object. This will be called by the DP vblank interrupt handler.
> + */
> +void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp)
> +{
> +	struct drm_crtc *crtc = &disp->xlnx_crtc.crtc;
> +	struct drm_device *drm = crtc->dev;
> +	struct drm_pending_vblank_event *event;
> +	unsigned long flags;
> +
> +	drm_crtc_handle_vblank(crtc);
> +
> +	/* Finish page flip */
> +	spin_lock_irqsave(&drm->event_lock, flags);
> +	event = disp->event;
> +	disp->event = NULL;
> +	if (event) {
> +		drm_crtc_send_vblank_event(crtc, event);
> +		drm_crtc_vblank_put(crtc);
> +	}
> +	spin_unlock_irqrestore(&drm->event_lock, flags);

Please take a look at drm_crtc_arm_vblank - that implements the exact
logic you're open-coding here.

> +}
> +
> +/**
> + * zynqmp_disp_get_apb_clk_rate - Get the current APB clock rate
> + * @disp: Display subsystem
> + *
> + * Return: the current APB clock rate.
> + */
> +unsigned int zynqmp_disp_get_apb_clk_rate(struct zynqmp_disp *disp)
> +{
> +	return clk_get_rate(disp->aclk);
> +}
> +
> +/**
> + * zynqmp_disp_aud_enabled - If the audio is enabled
> + * @disp: Display subsystem
> + *
> + * Return if the audio is enabled depending on the audio clock.
> + *
> + * Return: true if audio is enabled, or false.
> + */
> +bool zynqmp_disp_aud_enabled(struct zynqmp_disp *disp)
> +{
> +	return !!disp->audclk;
> +}
> +
> +/**
> + * zynqmp_disp_get_aud_clk_rate - Get the current audio clock rate
> + * @disp: Display subsystem
> + *
> + * Return: the current audio clock rate.
> + */
> +unsigned int zynqmp_disp_get_aud_clk_rate(struct zynqmp_disp *disp)
> +{
> +	if (zynqmp_disp_aud_enabled(disp))
> +		return 0;
> +	return clk_get_rate(disp->aclk);
> +}
> +
> +/**
> + * zynqmp_disp_get_crtc_mask - Return the CRTC bit mask
> + * @disp: Display subsystem
> + *
> + * Return: the crtc mask of the zyqnmp_disp CRTC.
> + */
> +uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp)
> +{
> +	return drm_crtc_mask(&disp->xlnx_crtc.crtc);
> +}
> +
> +/*
> + * DRM property functions
> + */
> +
> +static void zynqmp_disp_attach_vid_plane_property(struct zynqmp_disp *disp,
> +						  struct drm_mode_object *obj)
> +{
> +	drm_object_attach_property(obj, disp->tpg_prop, false);
> +}

You have a lot of wrappers for core functions (clk_get_rate above is
similar). This makes your code neater for you, but it makes it harder for
linux-people to read since they always need to jump around. Please just
directly call core functions like these instead of wrapping them.

> +
> +static void zynqmp_disp_attach_gfx_plane_property(struct zynqmp_disp *disp,
> +						  struct drm_mode_object *obj)
> +{
> +	drm_object_attach_property(obj, disp->g_alpha_prop,
> +				   ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX);
> +	disp->alpha = ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX;
> +	/* Enable the global alpha as default */
> +	drm_object_attach_property(obj, disp->g_alpha_en_prop, true);
> +	disp->alpha_en = true;
> +}
> +
> +static void zynqmp_disp_attach_crtc_property(struct zynqmp_disp *disp,
> +					     struct drm_mode_object *obj)
> +{
> +	drm_object_attach_property(obj, disp->color_prop, 0);
> +	zynqmp_dp_set_color(disp->dpsub->dp, zynqmp_disp_color_enum[0].name);
> +	drm_object_attach_property(obj, disp->bg_c0_prop, 0);
> +	drm_object_attach_property(obj, disp->bg_c1_prop, 0);
> +	drm_object_attach_property(obj, disp->bg_c2_prop, 0);
> +}
> +
> +static void zynqmp_disp_create_property(struct zynqmp_disp *disp)
> +{
> +	int num;
> +	u64 max;
> +
> +	max = ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX;
> +	disp->g_alpha_prop = drm_property_create_range(disp->drm, 0, "alpha", 0,
> +						       max);
> +	disp->g_alpha_en_prop = drm_property_create_bool(disp->drm, 0,
> +							 "g_alpha_en");
> +	num = ARRAY_SIZE(zynqmp_disp_color_enum);
> +	disp->color_prop = drm_property_create_enum(disp->drm, 0,
> +						    "output_color",
> +						    zynqmp_disp_color_enum,
> +						    num);
> +	max = ZYNQMP_DISP_V_BLEND_BG_MAX;
> +	disp->bg_c0_prop = drm_property_create_range(disp->drm, 0, "bg_c0", 0,
> +						     max);
> +	disp->bg_c1_prop = drm_property_create_range(disp->drm, 0, "bg_c1", 0,
> +						     max);
> +	disp->bg_c2_prop = drm_property_create_range(disp->drm, 0, "bg_c2", 0,
> +						     max);
> +	disp->tpg_prop = drm_property_create_bool(disp->drm, 0, "tpg");
> +}
> +
> +static void zynqmp_disp_destroy_property(struct zynqmp_disp *disp)
> +{
> +	drm_property_destroy(disp->drm, disp->bg_c2_prop);
> +	drm_property_destroy(disp->drm, disp->bg_c1_prop);
> +	drm_property_destroy(disp->drm, disp->bg_c0_prop);
> +	drm_property_destroy(disp->drm, disp->color_prop);
> +	drm_property_destroy(disp->drm, disp->g_alpha_en_prop);
> +	drm_property_destroy(disp->drm, disp->g_alpha_prop);
> +}

For all the property functions: Please split this out into a separate
patch (probably even want a separate follow-up patch series). This is new
uapi, and we have very strict requirements for that:

https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html#open-source-userspace-requirements

Especially around plane blending properties we already have a huge mess,
adding more driver-private and nonstandard properties isn't a good idea.

You can leave all your driver backend implementation in place, just don't
register the properties and decode them in atomic_get/set_property.

> +
> +/*
> + * DRM plane functions
> + */
> +
> +static inline struct zynqmp_disp_layer *plane_to_layer(struct drm_plane *plane)
> +{
> +	return container_of(plane, struct zynqmp_disp_layer, plane);
> +}
> +
> +static int zynqmp_disp_plane_enable(struct drm_plane *plane)
> +{
> +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> +	struct zynqmp_disp *disp = layer->disp;
> +	int ret;
> +
> +	zynqmp_disp_set_g_alpha(disp, disp->alpha_en);
> +	zynqmp_disp_set_alpha(disp, disp->alpha);
> +	ret = zynqmp_disp_layer_enable(layer->disp, layer,
> +				       ZYNQMP_DISP_LAYER_NONLIVE);
> +	if (ret)
> +		return ret;
> +
> +	if (layer->id == ZYNQMP_DISP_LAYER_GFX && disp->tpg_on) {
> +		layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
> +		zynqmp_disp_layer_set_tpg(disp, layer, disp->tpg_on);
> +	}
> +
> +	return 0;
> +}
> +
> +static int zynqmp_disp_plane_disable(struct drm_plane *plane)
> +{
> +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> +	struct zynqmp_disp *disp = layer->disp;
> +
> +	zynqmp_disp_layer_disable(disp, layer, ZYNQMP_DISP_LAYER_NONLIVE);
> +	if (layer->id == ZYNQMP_DISP_LAYER_VID && disp->tpg_on)
> +		zynqmp_disp_layer_set_tpg(disp, layer, disp->tpg_on);
> +
> +	return 0;
> +}
> +
> +static int zynqmp_disp_plane_mode_set(struct drm_plane *plane,
> +				      struct drm_framebuffer *fb,
> +				      int crtc_x, int crtc_y,
> +				      unsigned int crtc_w, unsigned int crtc_h,
> +				      u32 src_x, u32 src_y,
> +				      u32 src_w, u32 src_h)
> +{
> +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> +	const struct drm_format_info *info = fb->format;
> +	struct drm_format_name_buf format_name;
> +	struct device *dev = layer->disp->dev;
> +	dma_addr_t paddr;
> +	size_t offset;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!info) {
> +		dev_err(dev, "unsupported framebuffer format %s\n",
> +			drm_get_format_name(info->format, &format_name));
> +		return -EINVAL;
> +	}
> +
> +	ret = zynqmp_disp_layer_check_size(layer->disp, layer, src_w, src_h);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->num_planes; i++) {
> +		unsigned int width = src_w / (i ? info->hsub : 1);
> +		unsigned int height = src_h / (i ? info->vsub : 1);
> +
> +		paddr = xlnx_fb_get_paddr(fb, i);
> +		if (!paddr) {
> +			dev_err(dev, "failed to get a paddr\n");
> +			return -EINVAL;
> +		}
> +
> +		layer->dma[i].xt.numf = height;
> +		layer->dma[i].sgl[0].size = width * info->cpp[i];
> +		layer->dma[i].sgl[0].icg = fb->pitches[i] -
> +					   layer->dma[i].sgl[0].size;
> +		offset = src_x * info->cpp[i] + src_y * fb->pitches[i];
> +		offset += fb->offsets[i];
> +		layer->dma[i].xt.src_start = paddr + offset;
> +		layer->dma[i].xt.frame_size = 1;
> +		layer->dma[i].xt.dir = DMA_MEM_TO_DEV;
> +		layer->dma[i].xt.src_sgl = true;
> +		layer->dma[i].xt.dst_sgl = false;
> +		layer->dma[i].is_active = true;
> +	}
> +
> +	for (; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++)
> +		layer->dma[i].is_active = false;
> +
> +	ret = zynqmp_disp_layer_set_fmt(layer->disp,  layer, info->format);
> +	if (ret)
> +		dev_err(dev, "failed to set dp_sub layer fmt\n");
> +
> +	return ret;
> +}
> +
> +static void zynqmp_disp_plane_destroy(struct drm_plane *plane)
> +{
> +	drm_plane_cleanup(plane);
> +}
> +
> +static int
> +zynqmp_disp_plane_atomic_set_property(struct drm_plane *plane,
> +				      struct drm_plane_state *state,
> +				      struct drm_property *property, u64 val)
> +{
> +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> +	struct zynqmp_disp *disp = layer->disp;
> +	int ret = 0;
> +
> +	if (property == disp->g_alpha_prop)
> +		zynqmp_disp_set_alpha(disp, val);
> +	else if (property == disp->g_alpha_en_prop)
> +		zynqmp_disp_set_g_alpha(disp, val);
> +	else if (property == disp->tpg_prop)
> +		ret = zynqmp_disp_layer_set_tpg(disp, layer, val);
> +	else
> +		return -EINVAL;
> +
> +	return ret;
> +}
> +
> +static int
> +zynqmp_disp_plane_atomic_get_property(struct drm_plane *plane,
> +				      const struct drm_plane_state *state,
> +				      struct drm_property *property,
> +				      uint64_t *val)
> +{
> +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> +	struct zynqmp_disp *disp = layer->disp;
> +	int ret = 0;
> +
> +	if (property == disp->g_alpha_prop)
> +		*val = zynqmp_disp_get_alpha(disp);
> +	else if (property == disp->g_alpha_en_prop)
> +		*val = zynqmp_disp_get_g_alpha(disp);
> +	else if (property == disp->tpg_prop)
> +		*val = zynqmp_disp_layer_get_tpg(disp, layer);
> +	else
> +		return -EINVAL;
> +
> +	return ret;
> +}

Please also move the above 2 functions into the separate property enabling
patch series.

> +
> +static struct drm_plane_funcs zynqmp_disp_plane_funcs = {
> +	.update_plane		= drm_atomic_helper_update_plane,
> +	.disable_plane		= drm_atomic_helper_disable_plane,
> +	.atomic_set_property	= zynqmp_disp_plane_atomic_set_property,
> +	.atomic_get_property	= zynqmp_disp_plane_atomic_get_property,
> +	.destroy		= zynqmp_disp_plane_destroy,
> +	.reset			= drm_atomic_helper_plane_reset,
> +	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
> +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
> +};
> +
> +static int zynqmp_disp_plane_prepare_fb(struct drm_plane *plane,
> +					struct drm_plane_state *new_state)
> +{
> +	return 0;
> +}
> +
> +static void zynqmp_disp_plane_cleanup_fb(struct drm_plane *plane,
> +					 struct drm_plane_state *old_state)
> +{
> +}
> +
> +static int zynqmp_disp_plane_atomic_check(struct drm_plane *plane,
> +					  struct drm_plane_state *state)
> +{
> +	return 0;
> +}

Please remove the above dummy functions, the helpers should all have
fallbacks if a callback isn't set. This is general advise (in case I've
missed a few of them). If you find a place where a callback is mandatory,
we need to fix the core code.

> +
> +static void
> +zynqmp_disp_plane_atomic_update(struct drm_plane *plane,
> +				struct drm_plane_state *old_state)
> +{
> +	int ret;
> +
> +	if (!plane->state->crtc || !plane->state->fb)
> +		return;
> +
> +	ret = zynqmp_disp_plane_mode_set(plane, plane->state->fb,
> +					 plane->state->crtc_x,
> +					 plane->state->crtc_y,
> +					 plane->state->crtc_w,
> +					 plane->state->crtc_h,
> +					 plane->state->src_x >> 16,
> +					 plane->state->src_y >> 16,
> +					 plane->state->src_w >> 16,
> +					 plane->state->src_h >> 16);
> +	if (ret)
> +		return;
> +
> +	zynqmp_disp_plane_enable(plane);
> +}
> +
> +static void
> +zynqmp_disp_plane_atomic_disable(struct drm_plane *plane,
> +				 struct drm_plane_state *old_state)
> +{
> +	zynqmp_disp_plane_disable(plane);
> +}
> +
> +static const struct drm_plane_helper_funcs zynqmp_disp_plane_helper_funcs = {
> +	.prepare_fb	= zynqmp_disp_plane_prepare_fb,
> +	.cleanup_fb	= zynqmp_disp_plane_cleanup_fb,
> +	.atomic_check	= zynqmp_disp_plane_atomic_check,
> +	.atomic_update	= zynqmp_disp_plane_atomic_update,
> +	.atomic_disable	= zynqmp_disp_plane_atomic_disable,
> +};
> +
> +static int zynqmp_disp_create_plane(struct zynqmp_disp *disp)
> +{
> +	struct zynqmp_disp_layer *layer;
> +	unsigned int i;
> +	u32 *fmts = NULL;
> +	unsigned int num_fmts = 0;
> +	enum drm_plane_type type;
> +	int ret;
> +
> +	/* graphics layer is primary, and video layer is overaly */
> +	type = DRM_PLANE_TYPE_OVERLAY;
> +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
> +		layer = &disp->layers[i];
> +		zynqmp_disp_layer_get_fmts(disp, layer, &fmts, &num_fmts);
> +		ret = drm_universal_plane_init(disp->drm, &layer->plane, 0,
> +					       &zynqmp_disp_plane_funcs, fmts,
> +					       num_fmts, NULL, type, NULL);
> +		if (ret)
> +			goto err_plane;
> +		drm_plane_helper_add(&layer->plane,
> +				     &zynqmp_disp_plane_helper_funcs);
> +		type = DRM_PLANE_TYPE_PRIMARY;
> +	}
> +
> +	/* Attach properties to each layers */
> +	zynqmp_disp_attach_gfx_plane_property(disp, &layer->plane.base);
> +	layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
> +	zynqmp_disp_attach_vid_plane_property(disp, &layer->plane.base);
> +
> +	return ret;
> +
> +err_plane:
> +	if (i)
> +		drm_plane_cleanup(&disp->layers[0].plane);
> +	return ret;
> +}
> +
> +static void zynqmp_disp_destroy_plane(struct zynqmp_disp *disp)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
> +		zynqmp_disp_plane_destroy(&disp->layers[i].plane);
> +}
> +
> +/*
> + * Xlnx crtc functions
> + */
> +
> +static inline struct zynqmp_disp *xlnx_crtc_to_disp(struct xlnx_crtc *xlnx_crtc)
> +{
> +	return container_of(xlnx_crtc, struct zynqmp_disp, xlnx_crtc);
> +}
> +
> +static int zynqmp_disp_enable_vblank(struct xlnx_crtc *xlnx_crtc)
> +{
> +	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
> +
> +	zynqmp_dp_enable_vblank(disp->dpsub->dp);
> +	return 0;
> +}
> +
> +static void zynqmp_disp_disable_vblank(struct xlnx_crtc *xlnx_crtc)
> +{
> +	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
> +
> +	zynqmp_dp_disable_vblank(disp->dpsub->dp);
> +}
> +
> +static int zynqmp_disp_get_max_width(struct xlnx_crtc *xlnx_crtc)
> +{
> +	return ZYNQMP_DISP_MAX_WIDTH;
> +}
> +
> +static int zynqmp_disp_get_max_height(struct xlnx_crtc *xlnx_crtc)
> +{
> +	return ZYNQMP_DISP_MAX_HEIGHT;
> +}

Bikeshed, feel free to ignore: This is a bit much indirection for my
taste.

> +
> +static uint32_t zynqmp_disp_get_format(struct xlnx_crtc *xlnx_crtc)
> +{
> +	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
> +
> +	return disp->layers[ZYNQMP_DISP_LAYER_GFX].fmt->drm_fmt;
> +}
> +
> +static unsigned int zynqmp_disp_get_align(struct xlnx_crtc *xlnx_crtc)
> +{
> +	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
> +	struct zynqmp_disp_layer *layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
> +
> +	return 1 << layer->dma->chan->device->copy_align;
> +}
> +
> +static u64 zynqmp_disp_get_dma_mask(struct xlnx_crtc *xlnx_crtc)
> +{
> +	return DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT);
> +}
> +
> +/*
> + * DRM crtc functions
> + */
> +
> +static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc)
> +{
> +	struct xlnx_crtc *xlnx_crtc = to_xlnx_crtc(crtc);
> +
> +	return xlnx_crtc_to_disp(xlnx_crtc);
> +}
> +
> +static int zynqmp_disp_crtc_mode_set(struct drm_crtc *crtc,
> +				     struct drm_display_mode *mode,
> +				     struct drm_display_mode *adjusted_mode,
> +				     int x, int y,
> +				     struct drm_framebuffer *old_fb)
> +{
> +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> +	unsigned long rate;
> +	long diff;
> +	int ret;
> +
> +	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
> +	ret = clk_set_rate(disp->pclk, adjusted_mode->clock * 1000);
> +	if (ret) {
> +		dev_err(disp->dev, "failed to set a pixel clock\n");
> +		return ret;
> +	}
> +
> +	rate = clk_get_rate(disp->pclk);
> +	diff = rate - adjusted_mode->clock * 1000;
> +	if (abs(diff) > (adjusted_mode->clock * 1000) / 20) {
> +		dev_info(disp->dev, "request pixel rate: %d actual rate: %lu\n",
> +			 adjusted_mode->clock, rate);
> +	} else {
> +		dev_dbg(disp->dev, "request pixel rate: %d actual rate: %lu\n",
> +			adjusted_mode->clock, rate);
> +	}
> +
> +	/* The timing register should be programmed always */
> +	zynqmp_dp_encoder_mode_set_stream(disp->dpsub->dp, adjusted_mode);
> +
> +	return 0;
> +}
> +
> +static void
> +zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc,
> +			       struct drm_crtc_state *old_crtc_state)
> +{
> +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> +	struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode;
> +	int ret, vrefresh;
> +
> +	zynqmp_disp_crtc_mode_set(crtc, &crtc->state->mode,
> +				  adjusted_mode, crtc->x, crtc->y, NULL);
> +
> +	pm_runtime_get_sync(disp->dev);
> +	ret = zynqmp_disp_clk_enable(disp->pclk, &disp->pclk_en);
> +	if (ret) {
> +		dev_err(disp->dev, "failed to enable a pixel clock\n");
> +		return;
> +	}
> +	zynqmp_disp_set_output_fmt(disp, disp->color);
> +	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp->bg_c2);
> +	zynqmp_disp_enable(disp);
> +	/* Delay of 3 vblank intervals for timing gen to be stable */
> +	vrefresh = (adjusted_mode->clock * 1000) /
> +		   (adjusted_mode->vtotal * adjusted_mode->htotal);
> +	msleep(3 * 1000 / vrefresh);
> +}
> +
> +static void
> +zynqmp_disp_crtc_atomic_disable(struct drm_crtc *crtc,
> +				struct drm_crtc_state *old_crtc_state)
> +{
> +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> +
> +	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
> +	zynqmp_disp_plane_disable(crtc->primary);
> +	zynqmp_disp_disable(disp, true);
> +	pm_runtime_put_sync(disp->dev);
> +}
> +
> +static void zynqmp_disp_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> +}

Again please remove the unecessary dumy functions.

> +
> +static int zynqmp_disp_crtc_atomic_check(struct drm_crtc *crtc,
> +					 struct drm_crtc_state *state)
> +{
> +	return drm_atomic_add_affected_planes(state->state, crtc);
> +}
> +
> +static void
> +zynqmp_disp_crtc_atomic_begin(struct drm_crtc *crtc,
> +			      struct drm_crtc_state *old_crtc_state)
> +{
> +	/* Don't rely on vblank when disabling crtc */
> +	if (crtc->primary->state->fb && crtc->state->event) {
> +		struct zynqmp_disp *disp = crtc_to_disp(crtc);
> +
> +		/* Consume the flip_done event from atomic helper */
> +		crtc->state->event->pipe = drm_crtc_index(crtc);
> +		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
> +		disp->event = crtc->state->event;
> +		crtc->state->event = NULL;
> +	}
> +}
> +
> +static struct drm_crtc_helper_funcs zynqmp_disp_crtc_helper_funcs = {
> +	.atomic_enable	= zynqmp_disp_crtc_atomic_enable,
> +	.atomic_disable	= zynqmp_disp_crtc_atomic_disable,
> +	.mode_set_nofb	= zynqmp_disp_crtc_mode_set_nofb,
> +	.atomic_check	= zynqmp_disp_crtc_atomic_check,
> +	.atomic_begin	= zynqmp_disp_crtc_atomic_begin,
> +};
> +
> +static void zynqmp_disp_crtc_destroy(struct drm_crtc *crtc)
> +{
> +	zynqmp_disp_crtc_atomic_disable(crtc, NULL);
> +	drm_crtc_cleanup(crtc);
> +}
> +
> +static int
> +zynqmp_disp_crtc_atomic_set_property(struct drm_crtc *crtc,
> +				     struct drm_crtc_state *state,
> +				     struct drm_property *property,
> +				     uint64_t val)
> +{
> +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> +
> +	/*
> +	 * CRTC prop values are just stored here and applied when CRTC gets
> +	 * enabled
> +	 */
> +	if (property == disp->color_prop)
> +		disp->color = val;
> +	else if (property == disp->bg_c0_prop)
> +		disp->bg_c0 = val;
> +	else if (property == disp->bg_c1_prop)
> +		disp->bg_c1 = val;
> +	else if (property == disp->bg_c2_prop)
> +		disp->bg_c2 = val;
> +	else
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int
> +zynqmp_disp_crtc_atomic_get_property(struct drm_crtc *crtc,
> +				     const struct drm_crtc_state *state,
> +				     struct drm_property *property,
> +				     uint64_t *val)
> +{
> +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> +
> +	if (property == disp->color_prop)
> +		*val = disp->color;
> +	else if (property == disp->bg_c0_prop)
> +		*val = disp->bg_c0;
> +	else if (property == disp->bg_c1_prop)
> +		*val = disp->bg_c1;
> +	else if (property == disp->bg_c2_prop)
> +		*val = disp->bg_c2;
> +	else
> +		return -EINVAL;
> +
> +	return 0;
> +}

Again property enabling into a separate patch series pls.

> +
> +static struct drm_crtc_funcs zynqmp_disp_crtc_funcs = {
> +	.destroy		= zynqmp_disp_crtc_destroy,
> +	.set_config		= drm_atomic_helper_set_config,
> +	.page_flip		= drm_atomic_helper_page_flip,
> +	.atomic_set_property	= zynqmp_disp_crtc_atomic_set_property,
> +	.atomic_get_property	= zynqmp_disp_crtc_atomic_get_property,
> +	.reset			= drm_atomic_helper_crtc_reset,
> +	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
> +	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
> +};
> +
> +static void zynqmp_disp_create_crtc(struct zynqmp_disp *disp)
> +{
> +	struct drm_plane *plane = &disp->layers[ZYNQMP_DISP_LAYER_GFX].plane;
> +	int ret;
> +
> +	ret = drm_crtc_init_with_planes(disp->drm, &disp->xlnx_crtc.crtc, plane,
> +					NULL, &zynqmp_disp_crtc_funcs, NULL);
> +	drm_crtc_helper_add(&disp->xlnx_crtc.crtc,
> +			    &zynqmp_disp_crtc_helper_funcs);
> +	zynqmp_disp_attach_crtc_property(disp, &disp->xlnx_crtc.crtc.base);
> +
> +	disp->xlnx_crtc.enable_vblank = &zynqmp_disp_enable_vblank;
> +	disp->xlnx_crtc.disable_vblank = &zynqmp_disp_disable_vblank;
> +	disp->xlnx_crtc.get_max_width = &zynqmp_disp_get_max_width;
> +	disp->xlnx_crtc.get_max_height = &zynqmp_disp_get_max_height;
> +	disp->xlnx_crtc.get_format = &zynqmp_disp_get_format;
> +	disp->xlnx_crtc.get_align = &zynqmp_disp_get_align;
> +	disp->xlnx_crtc.get_dma_mask = &zynqmp_disp_get_dma_mask;
> +	xlnx_crtc_register(disp->drm, &disp->xlnx_crtc);
> +}
> +
> +static void zynqmp_disp_destroy_crtc(struct zynqmp_disp *disp)
> +{
> +	xlnx_crtc_unregister(disp->drm, &disp->xlnx_crtc);
> +	zynqmp_disp_crtc_destroy(&disp->xlnx_crtc.crtc);
> +}
> +
> +static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp)
> +{
> +	u32 possible_crtcs = drm_crtc_mask(&disp->xlnx_crtc.crtc);
> +	unsigned int i;
> +
> +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
> +		disp->layers[i].plane.possible_crtcs = possible_crtcs;
> +}
> +
> +/*
> + * Component functions
> + */
> +
> +int zynqmp_disp_bind(struct device *dev, struct device *master, void *data)
> +{
> +	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
> +	struct zynqmp_disp *disp = dpsub->disp;
> +	struct drm_device *drm = data;
> +	int ret;
> +
> +	disp->drm = drm;
> +	zynqmp_disp_create_property(disp);
> +	ret = zynqmp_disp_create_plane(disp);
> +	if (ret)
> +		return ret;
> +	zynqmp_disp_create_crtc(disp);
> +	zynqmp_disp_map_crtc_to_plane(disp);
> +
> +	return 0;
> +}
> +
> +void zynqmp_disp_unbind(struct device *dev, struct device *master, void *data)
> +{
> +	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
> +	struct zynqmp_disp *disp = dpsub->disp;
> +
> +	zynqmp_disp_destroy_crtc(disp);
> +	zynqmp_disp_destroy_plane(disp);
> +	zynqmp_disp_destroy_property(disp);
> +}
> +
> +/*
> + * Platform initialization functions
> + */
> +
> +static int zynqmp_disp_enumerate_fmts(struct zynqmp_disp *disp)
> +{
> +	struct zynqmp_disp_layer *layer;
> +	u32 *bus_fmts;
> +	u32 i, size, num_bus_fmts;
> +
> +	num_bus_fmts = ARRAY_SIZE(av_buf_live_fmts);
> +	bus_fmts = devm_kzalloc(disp->dev, sizeof(*bus_fmts) * num_bus_fmts,
> +				GFP_KERNEL);
> +	if (!bus_fmts)
> +		return -ENOMEM;
> +	for (i = 0; i < num_bus_fmts; i++)
> +		bus_fmts[i] = av_buf_live_fmts[i].bus_fmt;
> +
> +	layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
> +	layer->num_bus_fmts = num_bus_fmts;
> +	layer->bus_fmts = bus_fmts;
> +	size = ARRAY_SIZE(av_buf_vid_fmts);
> +	layer->num_fmts = size;
> +	layer->drm_fmts = devm_kzalloc(disp->dev,
> +				       sizeof(*layer->drm_fmts) * size,
> +				       GFP_KERNEL);
> +	if (!layer->drm_fmts)
> +		return -ENOMEM;
> +	for (i = 0; i < layer->num_fmts; i++)
> +		layer->drm_fmts[i] = av_buf_vid_fmts[i].drm_fmt;
> +	layer->fmt = &av_buf_vid_fmts[ZYNQMP_DISP_AV_BUF_VID_FMT_YUYV];
> +
> +	layer = &disp->layers[ZYNQMP_DISP_LAYER_GFX];
> +	layer->num_bus_fmts = num_bus_fmts;
> +	layer->bus_fmts = bus_fmts;
> +	size = ARRAY_SIZE(av_buf_gfx_fmts);
> +	layer->num_fmts = size;
> +	layer->drm_fmts = devm_kzalloc(disp->dev,
> +				       sizeof(*layer->drm_fmts) * size,
> +				       GFP_KERNEL);
> +	if (!layer->drm_fmts)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < layer->num_fmts; i++)
> +		layer->drm_fmts[i] = av_buf_gfx_fmts[i].drm_fmt;
> +	layer->fmt = &av_buf_gfx_fmts[ZYNQMP_DISP_AV_BUF_GFX_FMT_RGB565];
> +
> +	return 0;
> +}
> +
> +int zynqmp_disp_probe(struct platform_device *pdev)
> +{
> +	struct zynqmp_dpsub *dpsub;
> +	struct zynqmp_disp *disp;
> +	struct resource *res;
> +	int ret;
> +
> +	disp = devm_kzalloc(&pdev->dev, sizeof(*disp), GFP_KERNEL);
> +	if (!disp)
> +		return -ENOMEM;
> +	disp->dev = &pdev->dev;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "blend");
> +	disp->blend.base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(disp->blend.base))
> +		return PTR_ERR(disp->blend.base);
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "av_buf");
> +	disp->av_buf.base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(disp->av_buf.base))
> +		return PTR_ERR(disp->av_buf.base);
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aud");
> +	disp->aud.base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(disp->aud.base))
> +		return PTR_ERR(disp->aud.base);
> +
> +	dpsub = platform_get_drvdata(pdev);
> +	dpsub->disp = disp;
> +	disp->dpsub = dpsub;
> +
> +	ret = zynqmp_disp_enumerate_fmts(disp);
> +	if (ret)
> +		return ret;
> +
> +	/* Try the live PL video clock */
> +	disp->_pl_pclk = devm_clk_get(disp->dev, "dp_live_video_in_clk");
> +	if (!IS_ERR(disp->_pl_pclk)) {
> +		disp->pclk = disp->_pl_pclk;
> +		ret = zynqmp_disp_clk_enable_disable(disp->pclk,
> +						     &disp->pclk_en);
> +		if (ret)
> +			disp->pclk = NULL;
> +	} else if (PTR_ERR(disp->_pl_pclk) == -EPROBE_DEFER) {
> +		return PTR_ERR(disp->_pl_pclk);
> +	}
> +
> +	/* If the live PL video clock is not valid, fall back to PS clock */
> +	if (!disp->pclk) {
> +		disp->_ps_pclk = devm_clk_get(disp->dev, "dp_vtc_pixel_clk_in");
> +		if (IS_ERR(disp->_ps_pclk)) {
> +			dev_err(disp->dev, "failed to init any video clock\n");
> +			return PTR_ERR(disp->_ps_pclk);
> +		}
> +		disp->pclk = disp->_ps_pclk;
> +		ret = zynqmp_disp_clk_enable_disable(disp->pclk,
> +						     &disp->pclk_en);
> +		if (ret) {
> +			dev_err(disp->dev, "failed to init any video clock\n");
> +			return ret;
> +		}
> +	}
> +
> +	disp->aclk = devm_clk_get(disp->dev, "dp_apb_clk");
> +	if (IS_ERR(disp->aclk))
> +		return PTR_ERR(disp->aclk);
> +	ret = zynqmp_disp_clk_enable(disp->aclk, &disp->aclk_en);
> +	if (ret) {
> +		dev_err(disp->dev, "failed to enable the APB clk\n");
> +		return ret;
> +	}
> +
> +	/* Try the live PL audio clock */
> +	disp->_pl_audclk = devm_clk_get(disp->dev, "dp_live_audio_aclk");
> +	if (!IS_ERR(disp->_pl_audclk)) {
> +		disp->audclk = disp->_pl_audclk;
> +		ret = zynqmp_disp_clk_enable_disable(disp->audclk,
> +						     &disp->audclk_en);
> +		if (ret)
> +			disp->audclk = NULL;
> +	}
> +
> +	/* If the live PL audio clock is not valid, fall back to PS clock */
> +	if (!disp->audclk) {
> +		disp->_ps_audclk = devm_clk_get(disp->dev, "dp_aud_clk");
> +		if (!IS_ERR(disp->_ps_audclk)) {
> +			disp->audclk = disp->_ps_audclk;
> +			ret = zynqmp_disp_clk_enable_disable(disp->audclk,
> +							     &disp->audclk_en);
> +			if (ret)
> +				disp->audclk = NULL;
> +		}
> +
> +		if (!disp->audclk) {
> +			dev_err(disp->dev,
> +				"audio is disabled due to clock failure\n");
> +		}
> +	}
> +
> +	ret = zynqmp_disp_layer_create(disp);
> +	if (ret)
> +		goto error_aclk;
> +
> +	return 0;
> +
> +error_aclk:
> +	zynqmp_disp_clk_disable(disp->aclk, &disp->aclk_en);
> +	return ret;
> +}
> +
> +int zynqmp_disp_remove(struct platform_device *pdev)
> +{
> +	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> +	struct zynqmp_disp *disp = dpsub->disp;
> +
> +	zynqmp_disp_layer_destroy(disp);
> +	if (disp->audclk)
> +		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
> +	zynqmp_disp_clk_disable(disp->aclk, &disp->aclk_en);
> +	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
> +	dpsub->disp = NULL;
> +
> +	return 0;
> +}
> diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h b/drivers/gpu/drm/xlnx/zynqmp_disp.h
> new file mode 100644
> index 0000000..0291fc2
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h
> @@ -0,0 +1,28 @@
> +/*
> + * ZynqMP Display Driver
> + *
> + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> + *
> + *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#ifndef _ZYNQMP_DISP_H_
> +#define _ZYNQMP_DISP_H_
> +
> +struct zynqmp_disp;
> +
> +void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp);
> +unsigned int zynqmp_disp_get_apb_clk_rate(struct zynqmp_disp *disp);
> +bool zynqmp_disp_aud_enabled(struct zynqmp_disp *disp);
> +unsigned int zynqmp_disp_get_aud_clk_rate(struct zynqmp_disp *disp);
> +uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp);
> +
> +int zynqmp_disp_bind(struct device *dev, struct device *master, void *data);
> +void zynqmp_disp_unbind(struct device *dev, struct device *master, void *data);
> +
> +int zynqmp_disp_probe(struct platform_device *pdev);
> +int zynqmp_disp_remove(struct platform_device *pdev);
> +
> +#endif /* _ZYNQMP_DISP_H_ */
> -- 
> 2.7.4
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 05/10] drm: xlnx: Xilinx DRM KMS driver
       [not found]     ` <1515117959-18068-6-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
@ 2018-01-09  9:51       ` Daniel Vetter
  2018-01-11  2:05         ` Hyun Kwon
  0 siblings, 1 reply; 34+ messages in thread
From: Daniel Vetter @ 2018-01-09  9:51 UTC (permalink / raw)
  To: Hyun Kwon
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

On Thu, Jan 04, 2018 at 06:05:54PM -0800, Hyun Kwon wrote:
> Xilinx has various platforms for display, where users can create
> using multiple IPs in the programmable FPGA fabric, or where
> some hardened piepline is available on the chip. Furthermore,
> hardened pipeline can also interact with soft logics in FPGA.
> 
> The Xilinx DRM KMS is a softwrae layer to glue subdevice drivers
> with DRM core and integrate multiple subdevice drivers together.
> 
> Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> ---
>  MAINTAINERS                      |   8 +
>  drivers/gpu/drm/Kconfig          |   2 +
>  drivers/gpu/drm/Makefile         |   1 +
>  drivers/gpu/drm/xlnx/Kconfig     |  12 ++
>  drivers/gpu/drm/xlnx/Makefile    |   2 +
>  drivers/gpu/drm/xlnx/xlnx_crtc.c |   1 +
>  drivers/gpu/drm/xlnx/xlnx_drv.c  | 436 +++++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/xlnx/xlnx_drv.h  |  22 ++
>  drivers/gpu/drm/xlnx/xlnx_fb.c   |   1 +
>  drivers/gpu/drm/xlnx/xlnx_gem.c  |   1 +
>  10 files changed, 486 insertions(+)
>  create mode 100644 drivers/gpu/drm/xlnx/Kconfig
>  create mode 100644 drivers/gpu/drm/xlnx/Makefile
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.c
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d4b1635..101a3e6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4785,6 +4785,14 @@ F:	drivers/gpu/drm/etnaviv/
>  F:	include/uapi/drm/etnaviv_drm.h
>  F:	Documentation/devicetree/bindings/display/etnaviv/
>  
> +DRM DRIVERS FOR XILINX
> +M:	Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> +L:	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> +S:	Maintained
> +F:	drivers/gpu/drm/xlnx/
> +F:	Documentation/devicetree/bindings/display/xlnx/
> +T:	git git://anongit.freedesktop.org/drm/drm-misc
> +
>  DRM DRIVERS FOR ZTE ZX
>  M:	Shawn Guo <shawnguo-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
>  L:	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 0bc3744..82b7fc3 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -293,6 +293,8 @@ source "drivers/gpu/drm/pl111/Kconfig"
>  
>  source "drivers/gpu/drm/tve200/Kconfig"
>  
> +source "drivers/gpu/drm/xlnx/Kconfig"
> +
>  # Keep legacy drivers last
>  
>  menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index dd5ae67..72ee1a1 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
>  obj-$(CONFIG_DRM_PL111) += pl111/
>  obj-$(CONFIG_DRM_TVE200) += tve200/
>  obj-$(CONFIG_DRM_SCHED)	+= scheduler/
> +obj-$(CONFIG_DRM_XLNX)	+= xlnx/
> diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
> new file mode 100644
> index 0000000..19fd7cd
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/Kconfig
> @@ -0,0 +1,12 @@
> +config DRM_XLNX
> +	tristate "Xilinx DRM KMS Driver"
> +	depends on DRM && OF
> +	select DRM_KMS_HELPER
> +	select DRM_KMS_CMA_HELPER
> +	select DRM_GEM_CMA_HELPER
> +	help
> +	  Xilinx DRM KMS driver. Choose this option if you have
> +	  a Xilinx SoCs with hardened display pipeline or soft
> +	  display pipeline using Xilinx IPs in FPGA. This module
> +	  provides the kernel mode setting functionalities
> +	  for Xilinx display drivers.
> diff --git a/drivers/gpu/drm/xlnx/Makefile b/drivers/gpu/drm/xlnx/Makefile
> new file mode 100644
> index 0000000..c60a281
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/Makefile
> @@ -0,0 +1,2 @@
> +xlnx_drm-objs += xlnx_crtc.o xlnx_drv.o xlnx_fb.o xlnx_gem.o
> +obj-$(CONFIG_DRM_XLNX) += xlnx_drm.o
> diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.c b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> index 57ee939..8387e1e 100644
> --- a/drivers/gpu/drm/xlnx/xlnx_crtc.c
> +++ b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> @@ -13,6 +13,7 @@
>  #include <linux/list.h>
>  
>  #include "xlnx_crtc.h"
> +#include "xlnx_drv.h"
>  
>  /*
>   * Overview
> diff --git a/drivers/gpu/drm/xlnx/xlnx_drv.c b/drivers/gpu/drm/xlnx/xlnx_drv.c
> new file mode 100644
> index 0000000..273420b
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/xlnx_drv.c
> @@ -0,0 +1,436 @@
> +/*
> + * Xilinx DRM KMS Driver
> + *
> + *  Copyright (C) 2013 - 2018 Xilinx, Inc.
> + *
> + *  Author: Hyun Woo Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_of.h>
> +
> +#include <linux/component.h>
> +#include <linux/device.h>
> +#include <linux/dma-buf.h>
> +#include <linux/module.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/reservation.h>
> +
> +#include "xlnx_crtc.h"
> +#include "xlnx_fb.h"
> +#include "xlnx_gem.h"
> +
> +#define DRIVER_NAME	"xlnx"
> +#define DRIVER_DESC	"Xilinx DRM KMS Driver"
> +#define DRIVER_DATE	"20130509"
> +#define DRIVER_MAJOR	1
> +#define DRIVER_MINOR	0
> +
> +static uint xlnx_fbdev_vres = 2;
> +module_param_named(fbdev_vres, xlnx_fbdev_vres, uint, 0444);
> +MODULE_PARM_DESC(fbdev_vres,
> +		 "fbdev virtual resolution multiplier for fb (default: 2)");
> +
> +/**
> + * struct xlnx_drm - Xilinx DRM private data
> + * @drm: DRM core
> + * @crtc: Xilinx DRM CRTC helper
> + * @fb: DRM fb helper
> + * @pdev: platform device
> + * @suspend_state: atomic state for suspend / resume
> + */
> +struct xlnx_drm {
> +	struct drm_device *drm;
> +	struct xlnx_crtc_helper *crtc;
> +	struct drm_fb_helper *fb;
> +	struct platform_device *pdev;
> +	struct drm_atomic_state *suspend_state;
> +};
> +
> +/**
> + * xlnx_get_crtc_helper - Return the crtc helper instance
> + * @drm: DRM device
> + *
> + * Return: the crtc helper instance
> + */
> +struct xlnx_crtc_helper *xlnx_get_crtc_helper(struct drm_device *drm)
> +{
> +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> +
> +	return xlnx_drm->crtc;
> +}
> +
> +/**
> + * xlnx_get_align - Return the align requirement through CRTC helper
> + * @drm: DRM device
> + *
> + * Return: the alignment requirement
> + */
> +unsigned int xlnx_get_align(struct drm_device *drm)
> +{
> +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> +
> +	return xlnx_crtc_helper_get_align(xlnx_drm->crtc);
> +}
> +
> +/**
> + * xlnx_get_format - Return the current format of CRTC
> + * @drm: DRM device
> + *
> + * Return: the current CRTC format
> + */
> +uint32_t xlnx_get_format(struct drm_device *drm)
> +{
> +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> +
> +	return xlnx_crtc_helper_get_format(xlnx_drm->crtc);
> +}
> +
> +static void xlnx_output_poll_changed(struct drm_device *drm)
> +{
> +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> +
> +	xlnx_fb_hotplug_event(xlnx_drm->fb);
> +}
> +
> +static const struct drm_mode_config_funcs xlnx_mode_config_funcs = {
> +	.fb_create		= xlnx_fb_create,
> +	.output_poll_changed	= xlnx_output_poll_changed,
> +	.atomic_check		= drm_atomic_helper_check,
> +	.atomic_commit		= drm_atomic_helper_commit,
> +};
> +
> +static struct drm_mode_config_helper_funcs xlnx_mode_config_helper_funcs = {
> +	.atomic_commit_tail = drm_atomic_helper_commit_tail,

Hm, I thought this was optional and it should be the default commit_tail
callback. Please remove (and if that doesn't work, pls type a core patch
to make it the default).

> +};
> +
> +static int xlnx_enable_vblank(struct drm_device *drm, unsigned int crtc)
> +{
> +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> +
> +	return xlnx_crtc_helper_enable_vblank(xlnx_drm->crtc, crtc);
> +}
> +
> +static void xlnx_disable_vblank(struct drm_device *drm, unsigned int crtc)
> +{
> +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> +
> +	xlnx_crtc_helper_disable_vblank(xlnx_drm->crtc, crtc);
> +}

This is a bit backwards since you introduce the crtc helper only in a
later patch. Good reason to move the vblank stuff in there, and maybe even
fold the crtc helpers into the main kms patch :-)
-Daniel

> +
> +static void xlnx_mode_config_init(struct drm_device *drm)
> +{
> +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> +	struct xlnx_crtc_helper *crtc = xlnx_drm->crtc;
> +
> +	drm->mode_config.min_width = 0;
> +	drm->mode_config.min_height = 0;
> +	drm->mode_config.max_width = xlnx_crtc_helper_get_max_width(crtc);
> +	drm->mode_config.max_height = xlnx_crtc_helper_get_max_height(crtc);
> +}
> +
> +static void xlnx_lastclose(struct drm_device *drm)
> +{
> +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> +
> +	xlnx_fb_restore_mode(xlnx_drm->fb);
> +}
> +
> +static const struct file_operations xlnx_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= drm_open,
> +	.release	= drm_release,
> +	.unlocked_ioctl	= drm_ioctl,
> +	.mmap		= drm_gem_cma_mmap,
> +	.poll		= drm_poll,
> +	.read		= drm_read,
> +#ifdef CONFIG_COMPAT
> +	.compat_ioctl	= drm_compat_ioctl,
> +#endif
> +	.llseek		= noop_llseek,
> +};
> +
> +static struct drm_driver xlnx_drm_driver = {
> +	.driver_features		= DRIVER_MODESET | DRIVER_GEM |
> +					  DRIVER_ATOMIC | DRIVER_PRIME,
> +	.lastclose			= xlnx_lastclose,
> +
> +	.enable_vblank			= xlnx_enable_vblank,
> +	.disable_vblank			= xlnx_disable_vblank,

Please use the new callbacks in drm_crtc_ops instead, these here are
deprecated.

> +
> +	.prime_handle_to_fd		= drm_gem_prime_handle_to_fd,
> +	.prime_fd_to_handle		= drm_gem_prime_fd_to_handle,
> +	.gem_prime_export		= drm_gem_prime_export,
> +	.gem_prime_import		= drm_gem_prime_import,
> +	.gem_prime_get_sg_table		= drm_gem_cma_prime_get_sg_table,
> +	.gem_prime_import_sg_table	= drm_gem_cma_prime_import_sg_table,
> +	.gem_prime_vmap			= drm_gem_cma_prime_vmap,
> +	.gem_prime_vunmap		= drm_gem_cma_prime_vunmap,
> +	.gem_prime_mmap			= drm_gem_cma_prime_mmap,
> +	.gem_free_object		= drm_gem_cma_free_object,
> +	.gem_vm_ops			= &drm_gem_cma_vm_ops,
> +	.dumb_create			= xlnx_gem_cma_dumb_create,
> +	.dumb_destroy			= drm_gem_dumb_destroy,
> +
> +	.fops				= &xlnx_fops,
> +
> +	.name				= DRIVER_NAME,
> +	.desc				= DRIVER_DESC,
> +	.date				= DRIVER_DATE,
> +	.major				= DRIVER_MAJOR,
> +	.minor				= DRIVER_MINOR,
> +};
> +
> +static int xlnx_bind(struct device *dev)
> +{
> +	struct xlnx_drm *xlnx_drm;
> +	struct drm_device *drm;
> +	const struct drm_format_info *info;
> +	struct platform_device *pdev = to_platform_device(dev);
> +	int ret;
> +	u32 format;
> +
> +	drm = drm_dev_alloc(&xlnx_drm_driver, &pdev->dev);
> +	if (IS_ERR(drm))
> +		return PTR_ERR(drm);
> +
> +	xlnx_drm = devm_kzalloc(drm->dev, sizeof(*xlnx_drm), GFP_KERNEL);
> +	if (!xlnx_drm) {
> +		ret = -ENOMEM;
> +		goto err_drm;
> +	}
> +
> +	drm_mode_config_init(drm);
> +	drm->mode_config.funcs = &xlnx_mode_config_funcs;
> +	drm->mode_config.helper_private = &xlnx_mode_config_helper_funcs;
> +
> +	ret = drm_vblank_init(drm, 1);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to initialize vblank\n");
> +		goto err_xlnx_drm;
> +	}
> +
> +	drm->irq_enabled = 1;
> +	drm->dev_private = xlnx_drm;
> +	xlnx_drm->drm = drm;
> +	drm_kms_helper_poll_init(drm);
> +	platform_set_drvdata(pdev, xlnx_drm);
> +
> +	xlnx_drm->crtc = xlnx_crtc_helper_init(drm);
> +	if (IS_ERR(xlnx_drm->crtc)) {
> +		ret = PTR_ERR(xlnx_drm->crtc);
> +		goto err_xlnx_drm;
> +	}
> +
> +	ret = component_bind_all(drm->dev, drm);
> +	if (ret)
> +		goto err_crtc;
> +
> +	xlnx_mode_config_init(drm);
> +	drm_mode_config_reset(drm);
> +	dma_set_mask(drm->dev, xlnx_crtc_helper_get_dma_mask(xlnx_drm->crtc));
> +
> +	format = xlnx_crtc_helper_get_format(xlnx_drm->crtc);
> +	info = drm_format_info(format);
> +	if (info && info->depth && info->cpp[0]) {
> +		unsigned int align;
> +
> +		align = xlnx_crtc_helper_get_align(xlnx_drm->crtc);
> +		xlnx_drm->fb = xlnx_fb_init(drm, info->cpp[0] * 8, 1, align,
> +					    xlnx_fbdev_vres);
> +		if (IS_ERR(xlnx_drm->fb))
> +			dev_err(&pdev->dev,
> +				"failed to initialize drm fb\n");
> +	} else {
> +		/* fbdev emulation is optional */
> +		dev_info(&pdev->dev, "fbdev is not initialized\n");
> +	}
> +
> +	ret = drm_dev_register(drm, 0);
> +	if (ret < 0)
> +		goto err_fb;
> +
> +	return 0;
> +
> +err_fb:
> +	if (xlnx_drm->fb)
> +		xlnx_fb_fini(xlnx_drm->fb);
> +	component_unbind_all(drm->dev, drm);
> +err_crtc:
> +	xlnx_crtc_helper_fini(drm, xlnx_drm->crtc);
> +err_xlnx_drm:
> +	drm_mode_config_cleanup(drm);
> +err_drm:
> +	drm_dev_unref(drm);
> +	return ret;
> +}
> +
> +static void xlnx_unbind(struct device *dev)
> +{
> +	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
> +	struct drm_device *drm = xlnx_drm->drm;
> +
> +	drm_dev_unregister(drm);
> +	if (xlnx_drm->fb)
> +		xlnx_fb_fini(xlnx_drm->fb);
> +	component_unbind_all(drm->dev, drm);
> +	xlnx_crtc_helper_fini(drm, xlnx_drm->crtc);
> +	drm_kms_helper_poll_fini(drm);
> +	drm_mode_config_cleanup(drm);
> +	drm_dev_unref(drm);
> +}
> +
> +static const struct component_master_ops xlnx_master_ops = {
> +	.bind	= xlnx_bind,
> +	.unbind	= xlnx_unbind,
> +};
> +
> +static int xlnx_of_component_probe(struct device *dev,
> +				   int (*compare_of)(struct device *, void *),
> +				   const struct component_master_ops *m_ops)
> +{
> +	struct device_node *ep, *port, *remote;
> +	struct component_match *match = NULL;
> +	int i;
> +
> +	if (!dev->of_node)
> +		return -EINVAL;
> +
> +	for (i = 0; ; i++) {
> +		port = of_parse_phandle(dev->of_node, "ports", i);
> +		if (!port)
> +			break;
> +
> +		if (!of_device_is_available(port->parent)) {
> +			of_node_put(port);
> +			continue;
> +		}
> +
> +		component_match_add(dev, &match, compare_of, port->parent);
> +		of_node_put(port);
> +	}
> +
> +	if (i == 0) {
> +		dev_err(dev, "missing 'ports' property\n");
> +		return -ENODEV;
> +	}
> +
> +	if (!match) {
> +		dev_err(dev, "no available port\n");
> +		return -ENODEV;
> +	}
> +
> +	for (i = 0; ; i++) {
> +		port = of_parse_phandle(dev->of_node, "ports", i);
> +		if (!port)
> +			break;
> +
> +		if (!of_device_is_available(port->parent)) {
> +			of_node_put(port);
> +			continue;
> +		}
> +
> +		for_each_child_of_node(port, ep) {
> +			remote = of_graph_get_remote_port_parent(ep);
> +			if (!remote || !of_device_is_available(remote)) {
> +				of_node_put(remote);
> +				continue;
> +			} else if (!of_device_is_available(remote->parent)) {
> +				dev_warn(dev, "parent dev of %s unavailable\n",
> +					 remote->full_name);
> +				of_node_put(remote);
> +				continue;
> +			}
> +			component_match_add(dev, &match, compare_of, remote);
> +			of_node_put(remote);
> +		}
> +		of_node_put(port);
> +	}
> +
> +	return component_master_add_with_match(dev, m_ops, match);
> +}
> +
> +static int xlnx_compare_of(struct device *dev, void *data)
> +{
> +	return dev->of_node == data;
> +}
> +
> +static int xlnx_platform_probe(struct platform_device *pdev)
> +{
> +	return xlnx_of_component_probe(&pdev->dev, xlnx_compare_of,
> +				       &xlnx_master_ops);
> +}
> +
> +static int xlnx_platform_remove(struct platform_device *pdev)
> +{
> +	component_master_del(&pdev->dev, &xlnx_master_ops);
> +	return 0;
> +}
> +
> +static void xlnx_platform_shutdown(struct platform_device *pdev)
> +{
> +	struct xlnx_drm *xlnx_drm = platform_get_drvdata(pdev);
> +
> +	drm_put_dev(xlnx_drm->drm);
> +}
> +
> +static int __maybe_unused xlnx_pm_suspend(struct device *dev)
> +{
> +	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
> +	struct drm_device *drm = xlnx_drm->drm;
> +
> +	drm_kms_helper_poll_disable(drm);
> +
> +	xlnx_drm->suspend_state = drm_atomic_helper_suspend(drm);
> +	if (IS_ERR(xlnx_drm->suspend_state)) {
> +		drm_kms_helper_poll_enable(drm);
> +		return PTR_ERR(xlnx_drm->suspend_state);
> +	}
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused xlnx_pm_resume(struct device *dev)
> +{
> +	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
> +	struct drm_device *drm = xlnx_drm->drm;
> +
> +	drm_atomic_helper_resume(drm, xlnx_drm->suspend_state);
> +	drm_kms_helper_poll_enable(drm);
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops xlnx_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(xlnx_pm_suspend, xlnx_pm_resume)
> +};
> +
> +static const struct of_device_id xlnx_of_match[] = {
> +	{ .compatible = "xlnx,kms", },
> +	{ /* end of table */ },
> +};
> +MODULE_DEVICE_TABLE(of, xlnx_of_match);
> +
> +static struct platform_driver xlnx_driver = {
> +	.probe			= xlnx_platform_probe,
> +	.remove			= xlnx_platform_remove,
> +	.shutdown		= xlnx_platform_shutdown,
> +	.driver			= {
> +		.name		= "xlnx-drm",
> +		.pm		= &xlnx_pm_ops,
> +		.of_match_table	= xlnx_of_match,
> +	},
> +};
> +
> +module_platform_driver(xlnx_driver);
> +
> +MODULE_AUTHOR("Xilinx, Inc.");
> +MODULE_DESCRIPTION("Xilinx DRM KMS Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/xlnx/xlnx_drv.h b/drivers/gpu/drm/xlnx/xlnx_drv.h
> new file mode 100644
> index 0000000..6ec285c
> --- /dev/null
> +++ b/drivers/gpu/drm/xlnx/xlnx_drv.h
> @@ -0,0 +1,22 @@
> +/*
> + * Xilinx DRM KMS Header for Xilinx
> + *
> + *  Copyright (C) 2013 - 2018 Xilinx, Inc.
> + *
> + *  Author: Hyun Woo Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#ifndef _XLNX_DRV_H_
> +#define _XLNX_DRV_H_
> +
> +struct drm_device;
> +struct xlnx_crtc_helper;
> +
> +uint32_t xlnx_get_format(struct drm_device *drm);
> +unsigned int xlnx_get_align(struct drm_device *drm);
> +struct xlnx_crtc_helper *xlnx_get_crtc_helper(struct drm_device *drm);
> +struct xlnx_bridge_helper *xlnx_get_bridge_helper(struct drm_device *drm);
> +
> +#endif /* _XLNX_DRV_H_ */
> diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.c b/drivers/gpu/drm/xlnx/xlnx_fb.c
> index dbe9fbf..029a4d3 100644
> --- a/drivers/gpu/drm/xlnx/xlnx_fb.c
> +++ b/drivers/gpu/drm/xlnx/xlnx_fb.c
> @@ -18,6 +18,7 @@
>  #include <drm/drm_fb_helper.h>
>  #include <drm/drm_gem_cma_helper.h>
>  
> +#include "xlnx_drv.h"
>  #include "xlnx_fb.h"
>  
>  #define XLNX_MAX_PLANES	4
> diff --git a/drivers/gpu/drm/xlnx/xlnx_gem.c b/drivers/gpu/drm/xlnx/xlnx_gem.c
> index af1abdc..6395f65 100644
> --- a/drivers/gpu/drm/xlnx/xlnx_gem.c
> +++ b/drivers/gpu/drm/xlnx/xlnx_gem.c
> @@ -11,6 +11,7 @@
>  #include <drm/drmP.h>
>  #include <drm/drm_gem_cma_helper.h>
>  
> +#include "xlnx_drv.h"
>  #include "xlnx_gem.h"
>  
>  /*
> -- 
> 2.7.4
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
  2018-01-05  2:05   ` [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs Hyun Kwon
@ 2018-01-09  9:54     ` Daniel Vetter
  2018-01-11  2:05       ` Hyun Kwon
  0 siblings, 1 reply; 34+ messages in thread
From: Daniel Vetter @ 2018-01-09  9:54 UTC (permalink / raw)
  To: Hyun Kwon; +Cc: Tejas Upadhyay, devicetree, Michal Simek, dri-devel

On Thu, Jan 04, 2018 at 06:05:59PM -0800, Hyun Kwon wrote:
> Debugfs can be used to exploit some specific setting. Main purpose
> is for testing and debug diagnostic.
> 
> Signed-off-by: Tejas Upadhyay <tejasu@xilinx.com>
> Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>

Hm, not sure what's the use, it seems to just be for setting/getting
your driver-private properties. Usually people use modetest and similar
tools, but if there's demand for setting properties in debugfs (we already
have all the infrastructure for dumping the entire kms state, see the
various atomic_print_state callbacks) I think that should be done with
generic drm code.

I'd drop this patch for now (if there's no other reason for it).
-Daniel

> ---
>  drivers/gpu/drm/xlnx/Kconfig       |  21 +++
>  drivers/gpu/drm/xlnx/zynqmp_disp.c | 326 +++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/xlnx/zynqmp_dp.c   | 304 ++++++++++++++++++++++++++++++++++
>  3 files changed, 651 insertions(+)
> 
> diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
> index 7c5529c..befce0f 100644
> --- a/drivers/gpu/drm/xlnx/Kconfig
> +++ b/drivers/gpu/drm/xlnx/Kconfig
> @@ -21,3 +21,24 @@ config DRM_ZYNQMP_DPSUB
>  	  this option if you have a Xilinx ZynqMP SoC with DisplayPort
>  	  subsystem. The driver provides the kernel mode setting
>  	  functionlaities for ZynqMP DP subsystem.
> +
> +config DRM_ZYNQMP_DISP_DEBUG_FS
> +	bool "ZynqMP Display debugfs"
> +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> +	select DRM_ZYNQMP_DP_DEBUG_FS
> +	help
> +	  Enable the debugfs code for DP Sub driver. The debugfs code
> +	  enables debugging or testing related features. It exposes some
> +	  low level controls to the user space to help testing automation,
> +	  as well as can enable additional diagnostic or statistical
> +	  information.
> +
> +config DRM_ZYNQMP_DP_DEBUG_FS
> +	bool "ZynqMP DP debugfs"
> +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> +	help
> +	  Enable the debugfs code for DP driver. The debugfs code
> +	  enables debugging or testing related features. It exposes some
> +	  low level controls to the user space to help testing automation,
> +	  as well as can enable additional diagnostic or statistical
> +	  information.
> diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> index 68f829c..9fe6d49 100644
> --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> @@ -17,6 +17,7 @@
>  #include <drm/drm_plane_helper.h>
>  
>  #include <linux/clk.h>
> +#include <linux/debugfs.h>
>  #include <linux/device.h>
>  #include <linux/dmaengine.h>
>  #include <linux/interrupt.h>
> @@ -508,6 +509,325 @@ static void zynqmp_disp_set(void __iomem *base, int offset, u32 set)
>  	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) | set);
>  }
>  
> +#ifdef CONFIG_DRM_ZYNQMP_DISP_DEBUG_FS
> +
> +#define ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE	32UL
> +#define ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL	0xFFF
> +#define IN_RANGE(x, min, max) ({		\
> +		typeof(x) _x = (x);		\
> +		_x >= (min) && _x <= (max); })
> +
> +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> +enum zynqmp_disp_testcases {
> +	DP_SUB_TC_BG_COLOR,
> +	DP_SUB_TC_OUTPUT_FMT,
> +	DP_SUB_TC_NONE
> +};
> +
> +struct zynqmp_disp_debugfs {
> +	enum zynqmp_disp_testcases testcase;
> +	u16 r_value;
> +	u16 g_value;
> +	u16 b_value;
> +	u32 output_fmt;
> +	struct zynqmp_disp *zynqmp_disp;
> +};
> +
> +static struct dentry *zynqmp_disp_debugfs_dir;
> +struct zynqmp_disp_debugfs disp_debugfs;
> +struct zynqmp_disp_debugfs_request {
> +	const char *req;
> +	enum zynqmp_disp_testcases tc;
> +	ssize_t (*read_handler)(char **kern_buff);
> +	ssize_t (*write_handler)(char **cmd);
> +};
> +
> +static void
> +zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int id);
> +static s64 zynqmp_disp_debugfs_argument_value(char *arg)
> +{
> +	s64 value;
> +
> +	if (!arg)
> +		return -1;
> +
> +	if (!kstrtos64(arg, 0, &value))
> +		return value;
> +
> +	return -1;
> +}
> +
> +static ssize_t
> +zynqmp_disp_debugfs_background_color_write(char **disp_test_arg)
> +{
> +	char *r_color, *g_color, *b_color;
> +	s64 r_val, g_val, b_val;
> +
> +	r_color = strsep(disp_test_arg, " ");
> +	g_color = strsep(disp_test_arg, " ");
> +	b_color = strsep(disp_test_arg, " ");
> +
> +	/* char * to int conversion */
> +	r_val = zynqmp_disp_debugfs_argument_value(r_color);
> +	g_val = zynqmp_disp_debugfs_argument_value(g_color);
> +	b_val = zynqmp_disp_debugfs_argument_value(b_color);
> +
> +	if (!(IN_RANGE(r_val, 0, ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> +	      IN_RANGE(g_val, 0, ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> +	      IN_RANGE(b_val, 0, ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL)))
> +		return -EINVAL;
> +
> +	disp_debugfs.r_value = r_val;
> +	disp_debugfs.g_value = g_val;
> +	disp_debugfs.b_value = b_val;
> +
> +	disp_debugfs.testcase = DP_SUB_TC_BG_COLOR;
> +
> +	return 0;
> +}
> +
> +static ssize_t
> +zynqmp_disp_debugfs_output_display_format_write(char **disp_test_arg)
> +{
> +	char *output_format;
> +	struct zynqmp_disp *disp = disp_debugfs.zynqmp_disp;
> +
> +	/* Read the value from a user value */
> +	output_format = strsep(disp_test_arg, " ");
> +	if (strncmp(output_format, "rgb", 3) == 0) {
> +		disp_debugfs.output_fmt =
> +			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB;
> +	} else if (strncmp(output_format, "ycbcr444", 8) == 0) {
> +		disp_debugfs.output_fmt =
> +			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444;
> +	} else if (strncmp(output_format, "ycbcr422", 8) == 0) {
> +		disp_debugfs.output_fmt =
> +			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422;
> +	} else if (strncmp(output_format, "yonly", 5) == 0) {
> +		disp_debugfs.output_fmt =
> +			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY;
> +	} else {
> +		dev_err(disp->dev, "Invalid output format\n");
> +		return -EINVAL;
> +	}
> +
> +	disp_debugfs.testcase = DP_SUB_TC_OUTPUT_FMT;
> +
> +	return 0;
> +}
> +
> +static ssize_t
> +zynqmp_disp_debugfs_output_display_format_read(char **kern_buff)
> +{
> +	size_t out_str_len;
> +
> +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> +	disp_debugfs.output_fmt = 0;
> +
> +	out_str_len = strlen("Success");
> +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE, out_str_len);
> +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> +
> +	return 0;
> +}
> +
> +static ssize_t
> +zynqmp_disp_debugfs_background_color_read(char **kern_buff)
> +{
> +	size_t out_str_len;
> +
> +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> +	disp_debugfs.r_value = 0;
> +	disp_debugfs.g_value = 0;
> +	disp_debugfs.b_value = 0;
> +
> +	out_str_len = strlen("Success");
> +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE, out_str_len);
> +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> +
> +	return 0;
> +}
> +
> +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> +struct zynqmp_disp_debugfs_request disp_debugfs_reqs[] = {
> +	{"BACKGROUND_COLOR", DP_SUB_TC_BG_COLOR,
> +		zynqmp_disp_debugfs_background_color_read,
> +		zynqmp_disp_debugfs_background_color_write},
> +	{"OUTPUT_DISPLAY_FORMAT", DP_SUB_TC_OUTPUT_FMT,
> +		zynqmp_disp_debugfs_output_display_format_read,
> +		zynqmp_disp_debugfs_output_display_format_write},
> +};
> +
> +static ssize_t
> +zynqmp_disp_debugfs_write(struct file *f, const char __user *buf,
> +			  size_t size, loff_t *pos)
> +{
> +	char *kern_buff, *disp_test_req, *kern_buff_start;
> +	int ret;
> +	unsigned int i;
> +
> +	if (*pos != 0 || size <= 0)
> +		return -EINVAL;
> +
> +	if (disp_debugfs.testcase != DP_SUB_TC_NONE)
> +		return -EBUSY;
> +
> +	kern_buff = kzalloc(size, GFP_KERNEL);
> +	if (!kern_buff)
> +		return -ENOMEM;
> +	kern_buff_start = kern_buff;
> +
> +	ret = strncpy_from_user(kern_buff, buf, size);
> +	if (ret < 0) {
> +		kfree(kern_buff_start);
> +		return ret;
> +	}
> +
> +	/* Read the testcase name and argument from a user request */
> +	disp_test_req = strsep(&kern_buff, " ");
> +
> +	for (i = 0; i < ARRAY_SIZE(disp_debugfs_reqs); i++) {
> +		if (!strcasecmp(disp_test_req, disp_debugfs_reqs[i].req))
> +			if (!disp_debugfs_reqs[i].write_handler(&kern_buff)) {
> +				kfree(kern_buff_start);
> +				return size;
> +			}
> +	}
> +	kfree(kern_buff_start);
> +	return -EINVAL;
> +}
> +
> +static ssize_t zynqmp_disp_debugfs_read(struct file *f, char __user *buf,
> +					size_t size, loff_t *pos)
> +{
> +	char *kern_buff = NULL;
> +	size_t kern_buff_len, out_str_len;
> +	enum zynqmp_disp_testcases tc;
> +	int ret;
> +
> +	if (size <= 0)
> +		return -EINVAL;
> +
> +	if (*pos != 0)
> +		return 0;
> +
> +	kern_buff = kzalloc(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE, GFP_KERNEL);
> +	if (!kern_buff) {
> +		disp_debugfs.testcase = DP_SUB_TC_NONE;
> +		return -ENOMEM;
> +	}
> +
> +	tc = disp_debugfs.testcase;
> +	if (tc == DP_SUB_TC_NONE) {
> +		out_str_len = strlen("No testcase executed");
> +		out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> +				  out_str_len);
> +		snprintf(kern_buff, out_str_len, "%s", "No testcase executed");
> +	} else {
> +		ret = disp_debugfs_reqs[tc].read_handler(&kern_buff);
> +		if (ret) {
> +			kfree(kern_buff);
> +			return ret;
> +		}
> +	}
> +
> +	kern_buff_len = strlen(kern_buff);
> +	size = min(size, kern_buff_len);
> +
> +	ret = copy_to_user(buf, kern_buff, size);
> +
> +	kfree(kern_buff);
> +	if (ret)
> +		return ret;
> +
> +	*pos = size + 1;
> +	return size;
> +}
> +
> +static const struct file_operations fops_zynqmp_disp_dbgfs = {
> +	.owner = THIS_MODULE,
> +	.read = zynqmp_disp_debugfs_read,
> +	.write = zynqmp_disp_debugfs_write,
> +};
> +
> +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> +{
> +	int err;
> +	struct dentry *zynqmp_disp_debugfs_file;
> +
> +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> +	disp_debugfs.zynqmp_disp = disp;
> +
> +	zynqmp_disp_debugfs_dir = debugfs_create_dir("disp", NULL);
> +	if (!zynqmp_disp_debugfs_dir) {
> +		dev_err(disp->dev, "debugfs_create_dir failed\n");
> +		return -ENODEV;
> +	}
> +
> +	zynqmp_disp_debugfs_file =
> +		debugfs_create_file("testcase", 0444,
> +				    zynqmp_disp_debugfs_dir, NULL,
> +				    &fops_zynqmp_disp_dbgfs);
> +	if (!zynqmp_disp_debugfs_file) {
> +		dev_err(disp->dev, "debugfs_create_file testcase failed\n");
> +		err = -ENODEV;
> +		goto err_dbgfs;
> +	}
> +	return 0;
> +
> +err_dbgfs:
> +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> +	zynqmp_disp_debugfs_dir = NULL;
> +	return err;
> +}
> +
> +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> +{
> +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> +	zynqmp_disp_debugfs_dir = NULL;
> +}
> +
> +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> +{
> +	if (disp_debugfs.testcase == DP_SUB_TC_BG_COLOR) {
> +		zynqmp_disp_write(disp->blend.base,
> +				  ZYNQMP_DISP_V_BLEND_BG_CLR_0,
> +				  disp_debugfs.r_value);
> +		zynqmp_disp_write(disp->blend.base,
> +				  ZYNQMP_DISP_V_BLEND_BG_CLR_1,
> +				  disp_debugfs.g_value);
> +		zynqmp_disp_write(disp->blend.base,
> +				  ZYNQMP_DISP_V_BLEND_BG_CLR_2,
> +				  disp_debugfs.b_value);
> +	}
> +}
> +
> +static void
> +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> +{
> +	if (disp_debugfs.testcase == DP_SUB_TC_OUTPUT_FMT)
> +		zynqmp_disp_set_output_fmt(disp, disp_debugfs.output_fmt);
> +}
> +#else
> +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> +{
> +}
> +
> +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> +{
> +	return 0;
> +}
> +
> +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> +{
> +}
> +
> +static void
> +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> +{
> +}
> +#endif /* CONFIG_DP_DEBUG_FS */
> +
>  /*
>   * Clock functions
>   */
> @@ -597,6 +917,8 @@ zynqmp_disp_blend_set_output_fmt(struct zynqmp_disp_blend *blend, u32 fmt)
>  	u32 *offsets;
>  	u32 offset, i;
>  
> +	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422)
> +		fmt |= ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE;
>  	zynqmp_disp_write(blend->base, ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
>  	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
>  		coeffs = reset_coeffs;
> @@ -1941,6 +2263,7 @@ static void zynqmp_disp_set_bg_color(struct zynqmp_disp *disp,
>  				     u32 c0, u32 c1, u32 c2)
>  {
>  	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
> +	zynqmp_disp_debugfs_bg_color(disp);
>  }
>  
>  /**
> @@ -2572,6 +2895,7 @@ zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc,
>  		return;
>  	}
>  	zynqmp_disp_set_output_fmt(disp, disp->color);
> +	zynqmp_disp_set_debugfs_output_fmt(disp);
>  	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp->bg_c2);
>  	zynqmp_disp_enable(disp);
>  	/* Delay of 3 vblank intervals for timing gen to be stable */
> @@ -2911,6 +3235,7 @@ int zynqmp_disp_probe(struct platform_device *pdev)
>  	ret = zynqmp_disp_layer_create(disp);
>  	if (ret)
>  		goto error_aclk;
> +	zynqmp_disp_debugfs_init(disp);
>  
>  	return 0;
>  
> @@ -2924,6 +3249,7 @@ int zynqmp_disp_remove(struct platform_device *pdev)
>  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
>  	struct zynqmp_disp *disp = dpsub->disp;
>  
> +	zynqmp_disp_debugfs_exit(disp);
>  	zynqmp_disp_layer_destroy(disp);
>  	if (disp->audclk)
>  		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
> diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> index ce3c7c5..66fbad0 100644
> --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> @@ -16,6 +16,7 @@
>  #include <drm/drm_dp_helper.h>
>  #include <drm/drm_of.h>
>  
> +#include <linux/debugfs.h>
>  #include <linux/delay.h>
>  #include <linux/device.h>
>  #include <linux/module.h>
> @@ -371,6 +372,306 @@ static void zynqmp_dp_set(void __iomem *base, int offset, u32 set)
>  }
>  
>  /*
> + * Debugfs functions
> + */
> +
> +#ifdef CONFIG_DRM_ZYNQMP_DP_DEBUG_FS
> +
> +#define ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE	32UL
> +#define ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR	"255"
> +#define IN_RANGE(x, min, max) ({		\
> +		typeof(x) _x = (x);		\
> +		_x >= (min) && _x <= (max); })
> +
> +/* Match zynqmp_dp_testcases vs debugfs_reqs[] entry */
> +enum zynqmp_dp_testcases {
> +	DP_TC_LINK_RATE,
> +	DP_TC_LANE_COUNT,
> +	DP_TC_OUTPUT_FMT,
> +	DP_TC_NONE
> +};
> +
> +struct zynqmp_dp_debugfs {
> +	enum zynqmp_dp_testcases testcase;
> +	u8 link_rate;
> +	u8 lane_cnt;
> +	u8 old_output_fmt;
> +	struct zynqmp_dp *dp;
> +};
> +
> +static struct dentry *zynqmp_dp_debugfs_dir;
> +static struct zynqmp_dp_debugfs dp_debugfs;
> +struct zynqmp_dp_debugfs_request {
> +	const char *req;
> +	enum zynqmp_dp_testcases tc;
> +	ssize_t (*read_handler)(char **kern_buff);
> +	ssize_t (*write_handler)(char **cmd);
> +};
> +
> +static s64 zynqmp_dp_debugfs_argument_value(char *arg)
> +{
> +	s64 value;
> +
> +	if (!arg)
> +		return -1;
> +
> +	if (!kstrtos64(arg, 0, &value))
> +		return value;
> +
> +	return -1;
> +}
> +
> +static ssize_t zynqmp_dp_debugfs_max_linkrate_write(char **dp_test_arg)
> +{
> +	char *link_rate_arg;
> +	s64 link_rate;
> +
> +	link_rate_arg = strsep(dp_test_arg, " ");
> +	link_rate = zynqmp_dp_debugfs_argument_value(link_rate_arg);
> +	if (link_rate < 0 || (link_rate != DP_HIGH_BIT_RATE2 &&
> +			      link_rate != DP_HIGH_BIT_RATE &&
> +			      link_rate != DP_REDUCED_BIT_RATE))
> +		return -EINVAL;
> +
> +	dp_debugfs.link_rate = drm_dp_link_rate_to_bw_code(link_rate);
> +	dp_debugfs.testcase = DP_TC_LINK_RATE;
> +
> +	return 0;
> +}
> +
> +static ssize_t zynqmp_dp_debugfs_max_lanecnt_write(char **dp_test_arg)
> +{
> +	char *lane_cnt_arg;
> +	s64 lane_count;
> +
> +	lane_cnt_arg = strsep(dp_test_arg, " ");
> +	lane_count = zynqmp_dp_debugfs_argument_value(lane_cnt_arg);
> +	if (lane_count < 0 || !IN_RANGE(lane_count, 1,
> +					ZYNQMP_DP_MAX_LANES))
> +		return -EINVAL;
> +
> +	dp_debugfs.lane_cnt = lane_count;
> +	dp_debugfs.testcase = DP_TC_LANE_COUNT;
> +
> +	return 0;
> +}
> +
> +static ssize_t zynqmp_dp_debugfs_max_linkrate_read(char **kern_buff)
> +{
> +	struct zynqmp_dp *dp = dp_debugfs.dp;
> +	size_t output_str_len;
> +	u8 dpcd_link_bw;
> +	int ret;
> +
> +	dp_debugfs.testcase = DP_TC_NONE;
> +	dp_debugfs.link_rate = 0;
> +
> +	/* Getting Sink Side Link Rate */
> +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LINK_BW_SET, &dpcd_link_bw);
> +	if (ret < 0) {
> +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> +		kfree(*kern_buff);
> +		return ret;
> +	}
> +
> +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE, output_str_len);
> +	snprintf(*kern_buff, output_str_len, "%u", dpcd_link_bw);
> +
> +	return 0;
> +}
> +
> +static ssize_t zynqmp_dp_debugfs_max_lanecnt_read(char **kern_buff)
> +{
> +	struct zynqmp_dp *dp = dp_debugfs.dp;
> +	size_t output_str_len;
> +	u8 dpcd_lane_cnt;
> +	int ret;
> +
> +	dp_debugfs.testcase = DP_TC_NONE;
> +	dp_debugfs.lane_cnt = 0;
> +
> +	/* Getting Sink Side Lane Count */
> +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_COUNT_SET, &dpcd_lane_cnt);
> +	if (ret < 0) {
> +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> +		kfree(*kern_buff);
> +		return ret;
> +	}
> +
> +	dpcd_lane_cnt &= DP_LANE_COUNT_MASK;
> +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE, output_str_len);
> +	snprintf(*kern_buff, output_str_len, "%u", dpcd_lane_cnt);
> +
> +	return 0;
> +}
> +
> +/* Match zynqmp_dp_testcases vs dp_debugfs_reqs[] entry */
> +static struct zynqmp_dp_debugfs_request debugfs_reqs[] = {
> +	{"LINK_RATE", DP_TC_LINK_RATE,
> +			zynqmp_dp_debugfs_max_linkrate_read,
> +			zynqmp_dp_debugfs_max_linkrate_write},
> +	{"LANE_COUNT", DP_TC_LANE_COUNT,
> +			zynqmp_dp_debugfs_max_lanecnt_read,
> +			zynqmp_dp_debugfs_max_lanecnt_write},
> +};
> +
> +static ssize_t zynqmp_dp_debugfs_read(struct file *f, char __user *buf,
> +				      size_t size, loff_t *pos)
> +{
> +	char *kern_buff = NULL;
> +	size_t kern_buff_len, out_str_len;
> +	enum zynqmp_dp_testcases tc;
> +	int ret;
> +
> +	if (size <= 0)
> +		return -EINVAL;
> +
> +	if (*pos != 0)
> +		return 0;
> +
> +	kern_buff = kzalloc(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE, GFP_KERNEL);
> +	if (!kern_buff) {
> +		dp_debugfs.testcase = DP_TC_NONE;
> +		return -ENOMEM;
> +	}
> +
> +	tc = dp_debugfs.testcase;
> +	if (tc == DP_TC_NONE) {
> +		out_str_len = strlen("No testcase executed");
> +		out_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE, out_str_len);
> +		snprintf(kern_buff, out_str_len, "%s", "No testcase executed");
> +	} else {
> +		ret = debugfs_reqs[tc].read_handler(&kern_buff);
> +		if (ret) {
> +			kfree(kern_buff);
> +			return ret;
> +		}
> +	}
> +
> +	kern_buff_len = strlen(kern_buff);
> +	size = min(size, kern_buff_len);
> +
> +	ret = copy_to_user(buf, kern_buff, size);
> +
> +	kfree(kern_buff);
> +	if (ret)
> +		return ret;
> +
> +	*pos = size + 1;
> +	return size;
> +}
> +
> +static ssize_t
> +zynqmp_dp_debugfs_write(struct file *f, const char __user *buf,
> +			size_t size, loff_t *pos)
> +{
> +	char *kern_buff, *kern_buff_start;
> +	char *dp_test_req;
> +	int ret;
> +	int i;
> +
> +	if (*pos != 0 || size <= 0)
> +		return -EINVAL;
> +
> +	if (dp_debugfs.testcase != DP_TC_NONE)
> +		return -EBUSY;
> +
> +	kern_buff = kzalloc(size, GFP_KERNEL);
> +	if (!kern_buff)
> +		return -ENOMEM;
> +	kern_buff_start = kern_buff;
> +
> +	ret = strncpy_from_user(kern_buff, buf, size);
> +	if (ret < 0) {
> +		kfree(kern_buff_start);
> +		return ret;
> +	}
> +
> +	/* Read the testcase name and argument from a user request */
> +	dp_test_req = strsep(&kern_buff, " ");
> +
> +	for (i = 0; i < ARRAY_SIZE(debugfs_reqs); i++) {
> +		if (!strcasecmp(dp_test_req, debugfs_reqs[i].req))
> +			if (!debugfs_reqs[i].write_handler(&kern_buff)) {
> +				kfree(kern_buff_start);
> +				return size;
> +			}
> +	}
> +
> +	kfree(kern_buff_start);
> +	return -EINVAL;
> +}
> +
> +static const struct file_operations fops_zynqmp_dp_dbgfs = {
> +	.owner = THIS_MODULE,
> +	.read = zynqmp_dp_debugfs_read,
> +	.write = zynqmp_dp_debugfs_write,
> +};
> +
> +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> +{
> +	int err;
> +	struct dentry *zynqmp_dp_debugfs_file;
> +
> +	dp_debugfs.testcase = DP_TC_NONE;
> +	dp_debugfs.dp = dp;
> +
> +	zynqmp_dp_debugfs_dir = debugfs_create_dir("dp", NULL);
> +	if (!zynqmp_dp_debugfs_dir) {
> +		dev_err(dp->dev, "debugfs_create_dir failed\n");
> +		return -ENODEV;
> +	}
> +
> +	zynqmp_dp_debugfs_file =
> +		debugfs_create_file("testcase", 0444, zynqmp_dp_debugfs_dir,
> +				    NULL, &fops_zynqmp_dp_dbgfs);
> +	if (!zynqmp_dp_debugfs_file) {
> +		dev_err(dp->dev, "debugfs_create_file testcase failed\n");
> +		err = -ENODEV;
> +		goto err_dbgfs;
> +	}
> +	return 0;
> +
> +err_dbgfs:
> +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> +	zynqmp_dp_debugfs_dir = NULL;
> +	return err;
> +}
> +
> +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> +{
> +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> +	zynqmp_dp_debugfs_dir = NULL;
> +}
> +
> +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> +{
> +	dp->mode.bw_code =
> +		dp_debugfs.link_rate ? dp_debugfs.link_rate : dp->mode.bw_code;
> +	dp->mode.lane_cnt =
> +		dp_debugfs.lane_cnt ? dp_debugfs.lane_cnt : dp->mode.lane_cnt;
> +}
> +
> +#else /* DRM_ZYNQMP_DP_DEBUG_FS */
> +
> +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> +{
> +	return 0;
> +}
> +
> +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> +{
> +}
> +
> +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> +{
> +}
> +
> +#endif /* DRM_ZYNQMP_DP_DEBUG_FS */
> +
> +/*
>   * Internal functions: used by zynqmp_disp.c
>   */
>  
> @@ -597,6 +898,7 @@ static int zynqmp_dp_mode_configure(struct zynqmp_dp *dp, int pclock,
>  			dp->mode.bw_code = bws[i];
>  			dp->mode.lane_cnt = lane_cnt;
>  			dp->mode.pclock = pclock;
> +			zynqmp_dp_debugfs_mode_config(dp);
>  			return dp->mode.bw_code;
>  		}
>  	}
> @@ -1840,6 +2142,7 @@ int zynqmp_dp_probe(struct platform_device *pdev)
>  	dpsub = platform_get_drvdata(pdev);
>  	dpsub->dp = dp;
>  	dp->dpsub = dpsub;
> +	zynqmp_dp_debugfs_init(dp);
>  
>  	return 0;
>  
> @@ -1855,6 +2158,7 @@ int zynqmp_dp_remove(struct platform_device *pdev)
>  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
>  	struct zynqmp_dp *dp = dpsub->dp;
>  
> +	zynqmp_dp_debugfs_exit(dp);
>  	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
>  	drm_dp_aux_unregister(&dp->aux);
>  	zynqmp_dp_exit_phy(dp);
> -- 
> 2.7.4
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver
       [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
                     ` (9 preceding siblings ...)
  2018-01-05  2:05   ` [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs Hyun Kwon
@ 2018-01-09  9:56   ` Daniel Vetter
       [not found]     ` <20180109095649.GK26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
  10 siblings, 1 reply; 34+ messages in thread
From: Daniel Vetter @ 2018-01-09  9:56 UTC (permalink / raw)
  To: Hyun Kwon
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

On Thu, Jan 04, 2018 at 06:05:49PM -0800, Hyun Kwon wrote:
> Hi,
> 
> This patchset adds the DRM KMS driver for Xilinx ZynqMP DisplayPort
> subsystem. The Xilinx ZynqMP SoC has a hardened full display pipeline
> which supports blending of up to 2 planes, and the encoder is
> DisplayPort v1.2 compatible.
> 
> This series mainly includes 2 sets: Xilinx DRM KMS (patch 1/10 - 5/10)
> and ZynqMP DP subsystem drivers (patch 6/10 - 10/10).
> 
> The Xilinx DRM KMS is intended as a common layer shared across other
> (upcoming) Xilinx sub-drivers. It helps sub-drivers for both hardened as
> well as soft IPs interoperate together.
> 
> ZynqMP DP subsystem driver is a sub-driver that implements corresponding
> drm objects (crtc, plane, encoder, connector,,,) for ZynqMP SoC display
> pipeline. The entire pipeline is mainly partitioned into 2 blocks:
> generic display logic (zynqmp_disp.c) such as blending, csc,,, and the
> DP transmitter logic (zynqmp_dp.c).

I read through it all (well mostly the drm relevant bits, not your backend
code) and looks fairly resonable. Few minor clenaups and code removals
tbh.

Wrt merging/maintianing, do you want to maintain it as part of the
drm-misc small drivers group? Highly recommended imo. See

https://01.org/linuxgraphics/gfx-docs/maintainer-tools/drm-misc.html#small-drivers

for details. Ideally we'd need 2 xilinx maintainers to be able to push
patches & cross-review stuff.
-Daniel

> 
> Thanks,
> -hyun
> 
> Hyun Kwon (10):
>   dt-bindings: display: xlnx: Add Xilinx kms bindings
>   drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
>   drm: xlnx: Add xlnx fb of Xilinx DRM KMS
>   drm: xlnx: Add xlnx gem of Xilinx DRM KMS
>   drm: xlnx: Xilinx DRM KMS driver
>   dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings
>   drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display
>   drm: xlnx: DRM KMS driver for Xilinx ZynqMP DisplayPort
>   drm: xlnx: ZynqMP DP subsystem DRM KMS driver
>   drm: xlnx: zynqmp: Add debugfs
> 
>  .../devicetree/bindings/display/xlnx/xlnx,kms.txt  |   20 +
>  .../bindings/display/xlnx/xlnx,zynqmp-dpsub.txt    |   94 +
>  MAINTAINERS                                        |    8 +
>  drivers/gpu/drm/Kconfig                            |    2 +
>  drivers/gpu/drm/Makefile                           |    1 +
>  drivers/gpu/drm/xlnx/Kconfig                       |   44 +
>  drivers/gpu/drm/xlnx/Makefile                      |    5 +
>  drivers/gpu/drm/xlnx/xlnx_crtc.c                   |  195 ++
>  drivers/gpu/drm/xlnx/xlnx_crtc.h                   |   70 +
>  drivers/gpu/drm/xlnx/xlnx_drv.c                    |  436 +++
>  drivers/gpu/drm/xlnx/xlnx_drv.h                    |   22 +
>  drivers/gpu/drm/xlnx/xlnx_fb.c                     |  468 +++
>  drivers/gpu/drm/xlnx/xlnx_fb.h                     |   30 +
>  drivers/gpu/drm/xlnx/xlnx_gem.c                    |   39 +
>  drivers/gpu/drm/xlnx/xlnx_gem.h                    |   18 +
>  drivers/gpu/drm/xlnx/zynqmp_disp.c                 | 3261 ++++++++++++++++++++
>  drivers/gpu/drm/xlnx/zynqmp_disp.h                 |   28 +
>  drivers/gpu/drm/xlnx/zynqmp_dp.c                   | 2168 +++++++++++++
>  drivers/gpu/drm/xlnx/zynqmp_dp.h                   |   29 +
>  drivers/gpu/drm/xlnx/zynqmp_dpsub.c                |  141 +
>  drivers/gpu/drm/xlnx/zynqmp_dpsub.h                |   19 +
>  21 files changed, 7098 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
>  create mode 100644 Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
>  create mode 100644 drivers/gpu/drm/xlnx/Kconfig
>  create mode 100644 drivers/gpu/drm/xlnx/Makefile
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.c
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.h
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.c
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.h
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.c
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.h
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.c
>  create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.h
>  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.c
>  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.h
>  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.c
>  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.h
>  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.c
>  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.h
> 
> -- 
> 2.7.4
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms bindings
  2018-01-09  4:00       ` Rob Herring
@ 2018-01-11  2:04         ` Hyun Kwon
       [not found]           ` <BY1PR0201MB1000969ECDC38A62F68B7238D6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11  2:04 UTC (permalink / raw)
  To: Rob Herring
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

Hi Rob,

Thanks for the feedback.

> -----Original Message-----
> From: Rob Herring [mailto:robh@kernel.org]
> Sent: Monday, January 08, 2018 8:00 PM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> Simek <michal.simek@xilinx.com>
> Subject: Re: [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms
> bindings
> 
> On Thu, Jan 04, 2018 at 06:05:50PM -0800, Hyun Kwon wrote:
> > The dt binding for Xilinx DRM KMS driver.
> 
> Bindings are for h/w, not drivers.

I'll rephrase this.

> 
> >
> > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> > ---
> >  .../devicetree/bindings/display/xlnx/xlnx,kms.txt    | 20
> ++++++++++++++++++++
> >  1 file changed, 20 insertions(+)
> >  create mode 100644
> Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> >
> > diff --git a/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> > new file mode 100644
> > index 0000000..8dcd552
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> > @@ -0,0 +1,20 @@
> > +Xilinx KMS Pipeline
> > +-------------------
> > +
> > +Xilinx display pipelines can be designed with hardened video IPs and soft
> video
> > +IPs in programmable logic. This KMS module provides the common
> functionality
> > +of individual subdevice drivers, and glue logics between them.
> > +
> > +Required properties:
> > +
> > +- compatible: Must be "xlnx,kms".

I'll also rephrase the description and rename this to xlnx,display.

> > +
> > +- ports: phandles for CRTC ports, using the DT bindings defined in
> > +  Documentation/devicetree/bindings/graph.txt.
> 
> This use of ports is not part of the graph binding.

I'll add more details in the description.

> 
> > +
> > +Example:
> > +
> > +	xlnx_drm: xlnx_drm {
> > +		compatible = "xlnx,kms";
> 
> drm and kms are Linuxisms.

I agree. I'll remove linux subsystem specific terms.

> 
> Why do you need this node?

This node is used to represent a display pipeline as a single entity, which can consist of multiple components / IPs. I'll elaborate more per your suggestion.

Thanks,
-hyun

> 
> > +		ports = <&crtc_port>;
> > +	};
> > --
> > 2.7.4
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe devicetree" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 03/10] drm: xlnx: Add xlnx fb of Xilinx DRM KMS
  2018-01-09  9:35     ` Daniel Vetter
@ 2018-01-11  2:04       ` Hyun Kwon
  0 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11  2:04 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: devicetree, Michal Simek, dri-devel

Hi Daniel,

> -----Original Message-----
> From: Daniel Vetter [mailto:daniel.vetter@ffwll.ch] On Behalf Of Daniel
> Vetter
> Sent: Tuesday, January 09, 2018 1:36 AM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> Simek <michal.simek@xilinx.com>
> Subject: Re: [PATCH 03/10] drm: xlnx: Add xlnx fb of Xilinx DRM KMS
> 
> On Thu, Jan 04, 2018 at 06:05:52PM -0800, Hyun Kwon wrote:
> > Helpers for framebuffers backed by cma allocator.
> >
> > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> 
> Please take a look at the very new drm_gem_framebuffer_helper.c file.
> There's lots of helpers in there that you can use to remove almost the
> entire file here :-) You might even want to entirely drop this if it
> becomes too small.

Thanks for the pointer. I replaced some, but some still remain.

Thanks,
-hyun

> -Daniel
> 
> > ---
> >  drivers/gpu/drm/xlnx/xlnx_fb.c | 467
> +++++++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/xlnx/xlnx_fb.h |  30 +++
> >  2 files changed, 497 insertions(+)
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.c
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.h
> >
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.c
> b/drivers/gpu/drm/xlnx/xlnx_fb.c
> > new file mode 100644
> > index 0000000..dbe9fbf
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/xlnx_fb.c
> > @@ -0,0 +1,467 @@
> > +/*
> > + * Xilinx DRM KMS Framebuffer helper
> > + *
> > + *  Copyright (C) 2015 - 2018 Xilinx, Inc.
> > + *
> > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > + *
> > + * Based on drm_fb_cma_helper.c
> > + *
> > + *  Copyright (C) 2012 Analog Device Inc.
> > + *
> > + * SPDX-License-Identifier: GPL-2.0
> > + */
> > +
> > +#include <drm/drmP.h>
> > +#include <drm/drm_crtc.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_fb_helper.h>
> > +#include <drm/drm_gem_cma_helper.h>
> > +
> > +#include "xlnx_fb.h"
> > +
> > +#define XLNX_MAX_PLANES	4
> > +
> > +struct xlnx_fb {
> > +	struct drm_framebuffer		base;
> > +	struct drm_gem_cma_object	*obj[XLNX_MAX_PLANES];
> > +};
> > +
> > +struct xlnx_fbdev {
> > +	struct drm_fb_helper fb_helper;
> > +	struct xlnx_fb	*fb;
> > +	unsigned int align;
> > +	unsigned int vres_mult;
> > +};
> > +
> > +static inline struct xlnx_fbdev *to_fbdev(struct drm_fb_helper
> *fb_helper)
> > +{
> > +	return container_of(fb_helper, struct xlnx_fbdev, fb_helper);
> > +}
> > +
> > +static inline struct xlnx_fb *to_fb(struct drm_framebuffer *base_fb)
> > +{
> > +	return container_of(base_fb, struct xlnx_fb, base);
> > +}
> > +
> > +static void xlnx_fb_destroy(struct drm_framebuffer *base_fb)
> > +{
> > +	struct xlnx_fb *fb = to_fb(base_fb);
> > +	int i;
> > +
> > +	for (i = 0; i < XLNX_MAX_PLANES; i++)
> > +		if (fb->obj[i])
> > +			drm_gem_object_unreference_unlocked(&fb-
> >obj[i]->base);
> > +
> > +	drm_framebuffer_cleanup(base_fb);
> > +	kfree(fb);
> > +}
> > +
> > +static int xlnx_fb_create_handle(struct drm_framebuffer *base_fb,
> > +				 struct drm_file *file_priv,
> > +				 unsigned int *handle)
> > +{
> > +	struct xlnx_fb *fb = to_fb(base_fb);
> > +
> > +	return drm_gem_handle_create(file_priv, &fb->obj[0]->base,
> handle);
> > +}
> > +
> > +static struct drm_framebuffer_funcs xlnx_fb_funcs = {
> > +	.destroy	= xlnx_fb_destroy,
> > +	.create_handle	= xlnx_fb_create_handle,
> > +};
> > +
> > +/**
> > + * xlnx_fb_alloc - Allocate a xlnx_fb
> > + * @drm: DRM object
> > + * @mode_cmd: drm_mode_fb_cmd2 struct
> > + * @obj: pointers for returned drm_gem_cma_objects
> > + * @num_planes: number of planes to be allocated
> > + *
> > + * This function is based on drm_fb_cma_alloc().
> > + *
> > + * Return: a xlnx_fb object, or ERR_PTR.
> > + */
> > +static struct xlnx_fb *
> > +xlnx_fb_alloc(struct drm_device *drm,
> > +	      const struct drm_mode_fb_cmd2 *mode_cmd,
> > +	      struct drm_gem_cma_object **obj, unsigned int num_planes)
> > +{
> > +	struct xlnx_fb *fb;
> > +	int ret;
> > +	int i;
> > +
> > +	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
> > +	if (!fb)
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	drm_helper_mode_fill_fb_struct(drm, &fb->base, mode_cmd);
> > +
> > +	for (i = 0; i < num_planes; i++)
> > +		fb->obj[i] = obj[i];
> > +
> > +	ret = drm_framebuffer_init(drm, &fb->base, &xlnx_fb_funcs);
> > +	if (ret) {
> > +		dev_err(drm->dev, "Failed to initialize fb: %d\n", ret);
> > +		kfree(fb);
> > +		return ERR_PTR(ret);
> > +	}
> > +
> > +	return fb;
> > +}
> > +
> > +/**
> > + * xlnx_fb_get_paddr - Get physycal address of framebuffer
> > + * @base_fb: the framebuffer
> > + * @plane: which plane
> > + *
> > + * This function is based on drm_fb_cma_get_gem_obj().
> > + *
> > + * Return: a physical address of the plane, or 0
> > + */
> > +dma_addr_t
> > +xlnx_fb_get_paddr(struct drm_framebuffer *base_fb, unsigned int plane)
> > +{
> > +	struct xlnx_fb *fb = to_fb(base_fb);
> > +
> > +	if (plane >= XLNX_MAX_PLANES)
> > +		return 0;
> > +
> > +	return fb->obj[plane]->paddr;
> > +}
> > +EXPORT_SYMBOL_GPL(xlnx_fb_get_paddr);
> > +
> > +static int
> > +xlnx_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
> > +{
> > +	struct drm_fb_helper *fb_helper = info->par;
> > +	unsigned int i;
> > +	int ret = 0;
> > +
> > +	switch (cmd) {
> > +	case FBIO_WAITFORVSYNC:
> > +		for (i = 0; i < fb_helper->crtc_count; i++) {
> > +			struct drm_mode_set *mode_set;
> > +			struct drm_crtc *crtc;
> > +
> > +			mode_set = &fb_helper->crtc_info[i].mode_set;
> > +			crtc = mode_set->crtc;
> > +			ret = drm_crtc_vblank_get(crtc);
> > +			if (!ret) {
> > +				drm_crtc_wait_one_vblank(crtc);
> > +				drm_crtc_vblank_put(crtc);
> > +			}
> > +		}
> > +		return ret;
> > +	default:
> > +		return -ENOTTY;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static struct fb_ops xlnx_fbdev_ops = {
> > +	.owner		= THIS_MODULE,
> > +	.fb_fillrect	= sys_fillrect,
> > +	.fb_copyarea	= sys_copyarea,
> > +	.fb_imageblit	= sys_imageblit,
> > +	.fb_check_var	= drm_fb_helper_check_var,
> > +	.fb_set_par	= drm_fb_helper_set_par,
> > +	.fb_blank	= drm_fb_helper_blank,
> > +	.fb_pan_display	= drm_fb_helper_pan_display,
> > +	.fb_setcmap	= drm_fb_helper_setcmap,
> > +	.fb_ioctl	= xlnx_fb_ioctl,
> > +};
> > +
> > +/**
> > + * xlnx_fbdev_create - Create the fbdev with a framebuffer
> > + * @fb_helper: fb helper structure
> > + * @size: framebuffer size info
> > + *
> > + * This function is based on drm_fbdev_cma_create().
> > + *
> > + * Return: 0 if successful, or the error code.
> > + */
> > +static int xlnx_fbdev_create(struct drm_fb_helper *fb_helper,
> > +			     struct drm_fb_helper_surface_size *size)
> > +{
> > +	struct xlnx_fbdev *fbdev = to_fbdev(fb_helper);
> > +	struct drm_mode_fb_cmd2 mode_cmd = { 0 };
> > +	struct drm_device *drm = fb_helper->dev;
> > +	struct drm_gem_cma_object *obj;
> > +	struct drm_framebuffer *base_fb;
> > +	unsigned int bytes_per_pixel;
> > +	unsigned long offset;
> > +	struct fb_info *fbi;
> > +	size_t bytes;
> > +	int ret;
> > +
> > +	dev_dbg(drm->dev, "surface width(%d), height(%d) and bpp(%d)\n",
> > +		size->surface_width, size->surface_height, size-
> >surface_bpp);
> > +
> > +	bytes_per_pixel = DIV_ROUND_UP(size->surface_bpp, 8);
> > +
> > +	mode_cmd.width = size->surface_width;
> > +	mode_cmd.height = size->surface_height;
> > +	mode_cmd.pitches[0] = ALIGN(size->surface_width *
> bytes_per_pixel,
> > +				    fbdev->align);
> > +	mode_cmd.pixel_format = xlnx_get_format(drm);
> > +
> > +	mode_cmd.height *= fbdev->vres_mult;
> > +	bytes = mode_cmd.pitches[0] * mode_cmd.height;
> > +	obj = drm_gem_cma_create(drm, bytes);
> > +	if (IS_ERR(obj))
> > +		return PTR_ERR(obj);
> > +
> > +	fbi = framebuffer_alloc(0, drm->dev);
> > +	if (!fbi) {
> > +		dev_err(drm->dev, "Failed to allocate framebuffer info.\n");
> > +		ret = -ENOMEM;
> > +		goto err_drm_gem_cma_free_object;
> > +	}
> > +
> > +	fbdev->fb = xlnx_fb_alloc(drm, &mode_cmd, &obj, 1);
> > +	if (IS_ERR(fbdev->fb)) {
> > +		dev_err(drm->dev, "Failed to allocate DRM framebuffer.\n");
> > +		ret = PTR_ERR(fbdev->fb);
> > +		goto err_framebuffer_release;
> > +	}
> > +
> > +	base_fb = &fbdev->fb->base;
> > +	fb_helper->fb = base_fb;
> > +	fb_helper->fbdev = fbi;
> > +	fbi->par = fb_helper;
> > +	fbi->flags = FBINFO_FLAG_DEFAULT;
> > +	fbi->fbops = &xlnx_fbdev_ops;
> > +
> > +	ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
> > +	if (ret) {
> > +		dev_err(drm->dev, "Failed to allocate color map.\n");
> > +		goto err_xlnx_fb_destroy;
> > +	}
> > +
> > +	drm_fb_helper_fill_fix(fbi, base_fb->pitches[0],
> > +			       base_fb->format->depth);
> > +	drm_fb_helper_fill_var(fbi, fb_helper, base_fb->width, base_fb-
> >height);
> > +	fbi->var.yres = base_fb->height / fbdev->vres_mult;
> > +
> > +	offset = fbi->var.xoffset * bytes_per_pixel;
> > +	offset += fbi->var.yoffset * base_fb->pitches[0];
> > +
> > +	drm->mode_config.fb_base = (resource_size_t)obj->paddr;
> > +	fbi->screen_base = (char __iomem *)(obj->vaddr + offset);
> > +	fbi->fix.smem_start = (unsigned long)(obj->paddr + offset);
> > +	fbi->screen_size = bytes;
> > +	fbi->fix.smem_len = bytes;
> > +
> > +	return 0;
> > +
> > +err_xlnx_fb_destroy:
> > +	drm_framebuffer_unregister_private(base_fb);
> > +	xlnx_fb_destroy(base_fb);
> > +err_framebuffer_release:
> > +	framebuffer_release(fbi);
> > +err_drm_gem_cma_free_object:
> > +	drm_gem_cma_free_object(&obj->base);
> > +	return ret;
> > +}
> > +
> > +static struct drm_fb_helper_funcs xlnx_fb_helper_funcs = {
> > +	.fb_probe = xlnx_fbdev_create,
> > +};
> > +
> > +/**
> > + * xlnx_fb_init - Allocate and initializes the Xilinx framebuffer
> > + * @drm: DRM device
> > + * @preferred_bpp: preferred bits per pixel for the device
> > + * @max_conn_count: maximum number of connectors
> > + * @align: alignment value for pitch
> > + * @vres_mult: multiplier for virtual resolution
> > + *
> > + * This function is based on drm_fbdev_cma_init().
> > + *
> > + * Return: a newly allocated drm_fb_helper struct or a ERR_PTR.
> > + */
> > +struct drm_fb_helper *
> > +xlnx_fb_init(struct drm_device *drm, int preferred_bpp,
> > +	     unsigned int max_conn_count, unsigned int align,
> > +	     unsigned int vres_mult)
> > +{
> > +	struct xlnx_fbdev *fbdev;
> > +	struct drm_fb_helper *fb_helper;
> > +	int ret;
> > +
> > +	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
> > +	if (!fbdev)
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	fbdev->vres_mult = vres_mult;
> > +	fbdev->align = align;
> > +	fb_helper = &fbdev->fb_helper;
> > +	drm_fb_helper_prepare(drm, fb_helper, &xlnx_fb_helper_funcs);
> > +
> > +	ret = drm_fb_helper_init(drm, fb_helper, max_conn_count);
> > +	if (ret < 0) {
> > +		dev_err(drm->dev, "Failed to initialize drm fb helper.\n");
> > +		goto err_free;
> > +	}
> > +
> > +	ret = drm_fb_helper_single_add_all_connectors(fb_helper);
> > +	if (ret < 0) {
> > +		dev_err(drm->dev, "Failed to add connectors.\n");
> > +		goto err_drm_fb_helper_fini;
> > +	}
> > +
> > +	ret = drm_fb_helper_initial_config(fb_helper, preferred_bpp);
> > +	if (ret < 0) {
> > +		dev_err(drm->dev, "Failed to set initial hw
> configuration.\n");
> > +		goto err_drm_fb_helper_fini;
> > +	}
> > +
> > +	return fb_helper;
> > +
> > +err_drm_fb_helper_fini:
> > +	drm_fb_helper_fini(fb_helper);
> > +err_free:
> > +	kfree(fbdev);
> > +	return ERR_PTR(ret);
> > +}
> > +
> > +/**
> > + * xlnx_fbdev_defio_fini - Free the defio fb
> > + * @fbi: fb_info struct
> > + *
> > + * This function is based on drm_fbdev_cma_defio_fini().
> > + */
> > +static void xlnx_fbdev_defio_fini(struct fb_info *fbi)
> > +{
> > +	if (!fbi->fbdefio)
> > +		return;
> > +
> > +	fb_deferred_io_cleanup(fbi);
> > +	kfree(fbi->fbdefio);
> > +	kfree(fbi->fbops);
> > +}
> > +
> > +/**
> > + * xlnx_fbdev_fini - Free the Xilinx framebuffer
> > + * @fb_helper: drm_fb_helper struct
> > + *
> > + * This function is based on drm_fbdev_cma_fini().
> > + */
> > +void xlnx_fb_fini(struct drm_fb_helper *fb_helper)
> > +{
> > +	struct xlnx_fbdev *fbdev = to_fbdev(fb_helper);
> > +
> > +	drm_fb_helper_unregister_fbi(&fbdev->fb_helper);
> > +	if (fbdev->fb_helper.fbdev)
> > +		xlnx_fbdev_defio_fini(fbdev->fb_helper.fbdev);
> > +
> > +	if (fbdev->fb_helper.fb)
> > +		drm_framebuffer_remove(fbdev->fb_helper.fb);
> > +
> > +	drm_fb_helper_fini(&fbdev->fb_helper);
> > +	kfree(fbdev);
> > +}
> > +
> > +/**
> > + * xlnx_fb_restore_mode - Restores initial framebuffer mode
> > + * @fb_helper: drm_fb_helper struct, may be NULL
> > + *
> > + * This function is based on drm_fbdev_cma_restore_mode() and
> usually called
> > + * from the Xilinx DRM drivers lastclose callback.
> > + */
> > +void xlnx_fb_restore_mode(struct drm_fb_helper *fb_helper)
> > +{
> > +	if (!fb_helper)
> > +		return;
> > +	drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
> > +}
> > +
> > +/**
> > + * xlnx_fb_create - (struct drm_mode_config_funcs *)->fb_create
> callback
> > + * @drm: DRM device
> > + * @file_priv: drm file private data
> > + * @mode_cmd: mode command for fb creation
> > + *
> > + * This functions creates a drm_framebuffer for given mode
> @mode_cmd. This
> > + * functions is intended to be used for the fb_create callback function of
> > + * drm_mode_config_funcs.
> > + *
> > + * Return: a drm_framebuffer object if successful, or ERR_PTR.
> > + */
> > +struct drm_framebuffer *
> > +xlnx_fb_create(struct drm_device *drm, struct drm_file *file_priv,
> > +	       const struct drm_mode_fb_cmd2 *mode_cmd)
> > +{
> > +	struct xlnx_fb *fb;
> > +	struct drm_gem_cma_object *objs[XLNX_MAX_PLANES];
> > +	struct drm_gem_object *obj;
> > +	const struct drm_format_info *info;
> > +	struct drm_format_name_buf format_name;
> > +	int ret;
> > +	int i;
> > +
> > +	info = drm_format_info(mode_cmd->pixel_format);
> > +	if (!info) {
> > +		dev_err(drm->dev, "unsupported framebuffer format %s\n",
> > +			drm_get_format_name(mode_cmd->pixel_format,
> > +					    &format_name));
> > +		ret = -EINVAL;
> > +		goto err_out;
> > +	}
> > +
> > +	for (i = 0; i < info->num_planes; i++) {
> > +		unsigned int width = mode_cmd->width / (i ? info->hsub :
> 1);
> > +		unsigned int height = mode_cmd->height / (i ? info->vsub :
> 1);
> > +		unsigned int min_size;
> > +
> > +		obj = drm_gem_object_lookup(file_priv,
> > +					    mode_cmd->handles[i]);
> > +		if (!obj) {
> > +			dev_err(drm->dev, "Failed to lookup GEM object\n");
> > +			ret = -ENXIO;
> > +			goto err_gem_object_unreference;
> > +		}
> > +
> > +		min_size = (height - 1) * mode_cmd->pitches[i] + width *
> > +			   info->cpp[i] + mode_cmd->offsets[i];
> > +
> > +		if (obj->size < min_size) {
> > +			drm_gem_object_unreference_unlocked(obj);
> > +			ret = -EINVAL;
> > +			goto err_gem_object_unreference;
> > +		}
> > +		objs[i] = to_drm_gem_cma_obj(obj);
> > +	}
> > +
> > +	fb = xlnx_fb_alloc(drm, mode_cmd, objs, i);
> > +	if (IS_ERR(fb)) {
> > +		ret = PTR_ERR(fb);
> > +		goto err_gem_object_unreference;
> > +	}
> > +
> > +	fb->base.format = info;
> > +
> > +	return &fb->base;
> > +
> > +err_gem_object_unreference:
> > +	for (i--; i >= 0; i--)
> > +		drm_gem_object_unreference_unlocked(&objs[i]->base);
> > +err_out:
> > +	return ERR_PTR(ret);
> > +}
> > +
> > +/**
> > + * xlnx_fb_hotplug_event - Poll for hotpulug events
> > + * @fb_helper: drm_fb_helper struct, may be NULL
> > + *
> > + * This function is based on drm_fbdev_cma_hotplug_event() and
> usually called
> > + * from the Xilinx DRM drivers output_poll_changed callback.
> > + */
> > +void xlnx_fb_hotplug_event(struct drm_fb_helper *fb_helper)
> > +{
> > +	if (!fb_helper)
> > +		return;
> > +	drm_fb_helper_hotplug_event(fb_helper);
> > +}
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.h
> b/drivers/gpu/drm/xlnx/xlnx_fb.h
> > new file mode 100644
> > index 0000000..3f7e962
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/xlnx_fb.h
> > @@ -0,0 +1,30 @@
> > +/*
> > + * Xilinx DRM KMS Framebuffer helper header
> > + *
> > + *  Copyright (C) 2015 - 2018 Xilinx, Inc.
> > + *
> > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0
> > + */
> > +
> > +#ifndef _XLNX_FB_H_
> > +#define _XLNX_FB_H_
> > +
> > +struct drm_fb_helper;
> > +
> > +dma_addr_t
> > +xlnx_fb_get_paddr(struct drm_framebuffer *base_fb, unsigned int
> plane);
> > +
> > +void xlnx_fb_restore_mode(struct drm_fb_helper *fb_helper);
> > +struct drm_framebuffer *
> > +xlnx_fb_create(struct drm_device *drm, struct drm_file *file_priv,
> > +	       const struct drm_mode_fb_cmd2 *mode_cmd);
> > +void xlnx_fb_hotplug_event(struct drm_fb_helper *fb_helper);
> > +struct drm_fb_helper *
> > +xlnx_fb_init(struct drm_device *drm, int preferred_bpp,
> > +	     unsigned int max_conn_count, unsigned int align,
> > +	     unsigned int vres_mult);
> > +void xlnx_fb_fini(struct drm_fb_helper *fb_helper);
> > +
> > +#endif /* _XLNX_FB_H_ */
> > --
> > 2.7.4
> >
> > _______________________________________________
> > dri-devel mailing list
> > dri-devel@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
       [not found]       ` <20180109093733.GG26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
@ 2018-01-11  2:04         ` Hyun Kwon
  2018-01-11  7:48           ` Daniel Vetter
  0 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11  2:04 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

Hi Daniel,

> -----Original Message-----
> From: Daniel Vetter [mailto:daniel.vetter@ffwll.ch] On Behalf Of Daniel
> Vetter
> Sent: Tuesday, January 09, 2018 1:38 AM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> Simek <michal.simek@xilinx.com>
> Subject: Re: [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
> 
> On Thu, Jan 04, 2018 at 06:05:51PM -0800, Hyun Kwon wrote:
> > xlnx_crtc is a part of Xilinx DRM KMS and a layer that
> > provides some interface between the Xilinx DRM KMS and
> > crtc drivers.
> >
> > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> 
> Personal style, but I don't see much value in these small helpers.
> Splitting them from the main driver at least makes reading the patches a
> bit harder. But no strong opinion, just a bikeshed, feel free to ignore
> :-)

I also don't, but some reviewers prefer this way. I'll leave it this way for now. :-)

Thanks,
-hyun

> -Daniel
> 
> > ---
> >  drivers/gpu/drm/xlnx/xlnx_crtc.c | 194
> +++++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/xlnx/xlnx_crtc.h |  70 ++++++++++++++
> >  2 files changed, 264 insertions(+)
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.c
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.h
> >
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.c
> b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> > new file mode 100644
> > index 0000000..57ee939
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> > @@ -0,0 +1,194 @@
> > +/*
> > + * Xilinx DRM crtc driver
> > + *
> > + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> > + *
> > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0
> > + */
> > +
> > +#include <drm/drmP.h>
> > +
> > +#include <linux/list.h>
> > +
> > +#include "xlnx_crtc.h"
> > +
> > +/*
> > + * Overview
> > + * --------
> > + *
> > + * The Xilinx CRTC layer is to enable the custom interface to CRTC drivers.
> > + * The interface is used by Xilinx DRM driver where it needs CRTC
> > + * functionailty. CRTC drivers should attach the desired callbacks
> > + * to struct xlnx_crtc and register the xlnx_crtc with correcsponding
> > + * drm_device. It's highly recommended CRTC drivers register all
> callbacks
> > + * even though many of them are optional.
> > + * The CRTC helper simply walks through the registered CRTC device,
> > + * and call the callbacks.
> > + */
> > +
> > +/**
> > + * struct xlnx_crtc_helper - Xilinx CRTC helper
> > + * @xlnx_crtcs: list of Xilinx CRTC devices
> > + * @lock: lock to protect @xlnx_crtcs
> > + * @drm: back pointer to DRM core
> > + */
> > +struct xlnx_crtc_helper {
> > +	struct list_head xlnx_crtcs;
> > +	struct mutex lock; /* lock for @xlnx_crtcs */
> > +	struct drm_device *drm;
> > +};
> > +
> > +#define XLNX_CRTC_MAX_HEIGHT_WIDTH	UINT_MAX
> > +
> > +int xlnx_crtc_helper_enable_vblank(struct xlnx_crtc_helper *helper,
> > +				   unsigned int crtc_id)
> > +{
> > +	struct xlnx_crtc *crtc;
> > +
> > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list)
> > +		if (drm_crtc_index(&crtc->crtc) == crtc_id)
> > +			if (crtc->enable_vblank)
> > +				return crtc->enable_vblank(crtc);
> > +	return -ENODEV;
> > +}
> > +
> > +void xlnx_crtc_helper_disable_vblank(struct xlnx_crtc_helper *helper,
> > +				     unsigned int crtc_id)
> > +{
> > +	struct xlnx_crtc *crtc;
> > +
> > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > +		if (drm_crtc_index(&crtc->crtc) == crtc_id) {
> > +			if (crtc->disable_vblank)
> > +				crtc->disable_vblank(crtc);
> > +			return;
> > +		}
> > +	}
> > +}
> > +
> > +unsigned int xlnx_crtc_helper_get_align(struct xlnx_crtc_helper *helper)
> > +{
> > +	struct xlnx_crtc *crtc;
> > +	unsigned int align = 1, tmp;
> > +
> > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > +		if (crtc->get_align) {
> > +			tmp = crtc->get_align(crtc);
> > +			align = ALIGN(align, tmp);
> > +		}
> > +	}
> > +
> > +	return align;
> > +}
> > +
> > +u64 xlnx_crtc_helper_get_dma_mask(struct xlnx_crtc_helper *helper)
> > +{
> > +	struct xlnx_crtc *crtc;
> > +	u64 mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8), tmp;
> > +
> > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > +		if (crtc->get_dma_mask) {
> > +			tmp = crtc->get_dma_mask(crtc);
> > +			mask = min(mask, tmp);
> > +		}
> > +	}
> > +
> > +	return mask;
> > +}
> > +
> > +int xlnx_crtc_helper_get_max_width(struct xlnx_crtc_helper *helper)
> > +{
> > +	struct xlnx_crtc *crtc;
> > +	unsigned int width = XLNX_CRTC_MAX_HEIGHT_WIDTH, tmp;
> > +
> > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > +		if (crtc->get_max_width) {
> > +			tmp = crtc->get_max_width(crtc);
> > +			width = min(width, tmp);
> > +		}
> > +	}
> > +
> > +	return width;
> > +}
> > +
> > +int xlnx_crtc_helper_get_max_height(struct xlnx_crtc_helper *helper)
> > +{
> > +	struct xlnx_crtc *crtc;
> > +	unsigned int height = XLNX_CRTC_MAX_HEIGHT_WIDTH, tmp;
> > +
> > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > +		if (crtc->get_max_height) {
> > +			tmp = crtc->get_max_height(crtc);
> > +			height = min(height, tmp);
> > +		}
> > +	}
> > +
> > +	return height;
> > +}
> > +
> > +uint32_t xlnx_crtc_helper_get_format(struct xlnx_crtc_helper *helper)
> > +{
> > +	struct xlnx_crtc *crtc;
> > +	u32 format = 0, tmp;
> > +
> > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > +		if (crtc->get_format) {
> > +			tmp = crtc->get_format(crtc);
> > +			if (format && format != tmp)
> > +				return 0;
> > +			format = tmp;
> > +		}
> > +	}
> > +
> > +	return format;
> > +}
> > +
> > +struct xlnx_crtc_helper *xlnx_crtc_helper_init(struct drm_device *drm)
> > +{
> > +	struct xlnx_crtc_helper *helper;
> > +
> > +	helper = devm_kzalloc(drm->dev, sizeof(*helper), GFP_KERNEL);
> > +	if (!helper)
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	INIT_LIST_HEAD(&helper->xlnx_crtcs);
> > +	mutex_init(&helper->lock);
> > +	helper->drm = drm;
> > +
> > +	return helper;
> > +}
> > +
> > +void xlnx_crtc_helper_fini(struct drm_device *drm,
> > +			   struct xlnx_crtc_helper *helper)
> > +{
> > +	if (WARN_ON(helper->drm != drm))
> > +		return;
> > +
> > +	if (WARN_ON(!list_empty(&helper->xlnx_crtcs)))
> > +		return;
> > +
> > +	mutex_destroy(&helper->lock);
> > +	devm_kfree(drm->dev, helper);
> > +}
> > +
> > +void xlnx_crtc_register(struct drm_device *drm, struct xlnx_crtc *crtc)
> > +{
> > +	struct xlnx_crtc_helper *helper = xlnx_get_crtc_helper(drm);
> > +
> > +	mutex_lock(&helper->lock);
> > +	list_add_tail(&crtc->list, &helper->xlnx_crtcs);
> > +	mutex_unlock(&helper->lock);
> > +}
> > +EXPORT_SYMBOL_GPL(xlnx_crtc_register);
> > +
> > +void xlnx_crtc_unregister(struct drm_device *drm, struct xlnx_crtc *crtc)
> > +{
> > +	struct xlnx_crtc_helper *helper = xlnx_get_crtc_helper(drm);
> > +
> > +	mutex_lock(&helper->lock);
> > +	list_del(&crtc->list);
> > +	mutex_unlock(&helper->lock);
> > +}
> > +EXPORT_SYMBOL_GPL(xlnx_crtc_unregister);
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.h
> b/drivers/gpu/drm/xlnx/xlnx_crtc.h
> > new file mode 100644
> > index 0000000..db7404e
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/xlnx_crtc.h
> > @@ -0,0 +1,70 @@
> > +/*
> > + * Xilinx DRM crtc header
> > + *
> > + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> > + *
> > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0
> > + */
> > +
> > +#ifndef _XLNX_CRTC_H_
> > +#define _XLNX_CRTC_H_
> > +
> > +/**
> > + * struct xlnx_crtc - Xilinx CRTC device
> > + * @crtc: DRM CRTC device
> > + * @list: list node for Xilinx CRTC device list
> > + * @enable_vblank: Enable vblank
> > + * @disable_vblank: Disable vblank
> > + * @get_align: Get the alignment requirement of CRTC device
> > + * @get_dma_mask: Get the dma mask of CRTC device
> > + * @get_max_width: Get the maximum supported width
> > + * @get_max_height: Get the maximum supported height
> > + * @get_format: Get the current format of CRTC device
> > + */
> > +struct xlnx_crtc {
> > +	struct drm_crtc crtc;
> > +	struct list_head list;
> > +	int (*enable_vblank)(struct xlnx_crtc *crtc);
> > +	void (*disable_vblank)(struct xlnx_crtc *crtc);
> > +	unsigned int (*get_align)(struct xlnx_crtc *crtc);
> > +	u64 (*get_dma_mask)(struct xlnx_crtc *crtc);
> > +	int (*get_max_width)(struct xlnx_crtc *crtc);
> > +	int (*get_max_height)(struct xlnx_crtc *crtc);
> > +	uint32_t (*get_format)(struct xlnx_crtc *crtc);
> > +};
> > +
> > +/*
> > + * Helper functions: used within Xlnx DRM
> > + */
> > +
> > +struct xlnx_crtc_helper;
> > +
> > +int xlnx_crtc_helper_enable_vblank(struct xlnx_crtc_helper *helper,
> > +				   unsigned int crtc_id);
> > +void xlnx_crtc_helper_disable_vblank(struct xlnx_crtc_helper *helper,
> > +				     unsigned int crtc_id);
> > +unsigned int xlnx_crtc_helper_get_align(struct xlnx_crtc_helper *helper);
> > +u64 xlnx_crtc_helper_get_dma_mask(struct xlnx_crtc_helper *helper);
> > +int xlnx_crtc_helper_get_max_width(struct xlnx_crtc_helper *helper);
> > +int xlnx_crtc_helper_get_max_height(struct xlnx_crtc_helper *helper);
> > +uint32_t xlnx_crtc_helper_get_format(struct xlnx_crtc_helper *helper);
> > +
> > +struct xlnx_crtc_helper *xlnx_crtc_helper_init(struct drm_device *drm);
> > +void xlnx_crtc_helper_fini(struct drm_device *drm,
> > +			   struct xlnx_crtc_helper *helper);
> > +
> > +/*
> > + * CRTC registration: used by other sub-driver modules
> > + */
> > +
> > +static inline struct xlnx_crtc *to_xlnx_crtc(struct drm_crtc *crtc)
> > +{
> > +	return container_of(crtc, struct xlnx_crtc, crtc);
> > +}
> > +
> > +void xlnx_crtc_register(struct drm_device *drm, struct xlnx_crtc *crtc);
> > +void xlnx_crtc_unregister(struct drm_device *drm, struct xlnx_crtc
> *crtc);
> > +
> > +#endif /* _XLNX_CRTC_H_ */
> > --
> > 2.7.4
> >
> > _______________________________________________
> > dri-devel mailing list
> > dri-devel@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 07/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display
       [not found]         ` <20180109094652.GH26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
@ 2018-01-11  2:04           ` Hyun Kwon
  0 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11  2:04 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

Hi Daniel,

> -----Original Message-----
> From: Daniel Vetter [mailto:daniel.vetter@ffwll.ch] On Behalf Of Daniel
> Vetter
> Sent: Tuesday, January 09, 2018 1:47 AM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> Simek <michal.simek@xilinx.com>
> Subject: Re: [PATCH 07/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP
> DP subsystem display
> 
> On Thu, Jan 04, 2018 at 06:05:56PM -0800, Hyun Kwon wrote:
> > Xilinx ZynqMP has a hardened display pipeline. The pipeline can
> > be logically partitioned into 2 parts: display and DisplayPort.
> > This driver handles the display part of the pipeline that handles
> > buffer management and blending.
> >
> > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> > ---
> >  drivers/gpu/drm/xlnx/zynqmp_disp.c | 2935
> ++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/xlnx/zynqmp_disp.h |   28 +
> >  2 files changed, 2963 insertions(+)
> >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.c
> >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.h
> >
> > diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > new file mode 100644
> > index 0000000..68f829c
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > @@ -0,0 +1,2935 @@
> > +/*
> > + * ZynqMP Display Controller Driver
> > + *
> > + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> > + *
> > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0
> > + */
> > +
> > +#include <drm/drmP.h>
> > +#include <drm/drm_atomic.h>
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_crtc.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_fourcc.h>
> > +#include <drm/drm_plane_helper.h>
> > +
> > +#include <linux/clk.h>
> > +#include <linux/device.h>
> > +#include <linux/dmaengine.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/irqreturn.h>
> > +#include <linux/list.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of.h>
> > +#include <linux/of_dma.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/uaccess.h>
> > +
> > +#include "xlnx_crtc.h"
> > +#include "xlnx_fb.h"
> > +#include "zynqmp_disp.h"
> > +#include "zynqmp_dp.h"
> > +#include "zynqmp_dpsub.h"
> > +
> > +/*
> > + * Overview
> > + * --------
> > + *
> > + * The display part of ZynqMP DP subsystem. Internally, the device
> > + * is partitioned into 3 blocks: AV buffer manager, Blender, Audio.
> > + * The driver creates the DRM crtc and plane objectes and maps the DRM
> > + * interface into those 3 blocks. In high level, the driver is layered
> > + * in the following way:
> > + *
> > + * zynqmp_disp_crtc & zynqmp_disp_plane
> > + * |->zynqmp_disp
> > + *	|->zynqmp_disp_aud
> > + *	|->zynqmp_disp_blend
> > + *	|->zynqmp_disp_av_buf
> > + *
> > + * The driver APIs are used externally by
> > + * - zynqmp_dpsub: Top level ZynqMP DP subsystem driver
> > + * - zynqmp_dp: ZynqMP DP driver
> > + * - xlnx_crtc: Xilinx DRM specific crtc functions
> > + */
> > +
> > +/* Blender registers */
> > +#define ZYNQMP_DISP_V_BLEND_BG_CLR_0			0x0
> > +#define ZYNQMP_DISP_V_BLEND_BG_CLR_1			0x4
> > +#define ZYNQMP_DISP_V_BLEND_BG_CLR_2			0x8
> > +#define ZYNQMP_DISP_V_BLEND_BG_MAX			0xfff
> > +#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA		0xc
> > +#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MASK	0x1fe
> > +#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX	0xff
> > +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT		0x14
> > +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB
> 	0x0
> > +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444	0x1
> > +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422	0x2
> > +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY	0x3
> > +#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_XVYCC	0x4
> > +#define ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE	BIT(4)
> > +#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL		0x18
> > +#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_EN_US
> 	BIT(0)
> > +#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_RGB		BIT(1)
> > +#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_BYPASS	BIT(8)
> > +#define ZYNQMP_DISP_V_BLEND_NUM_COEFF			9
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF0		0x20
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF1		0x24
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF2		0x28
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF3		0x2c
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF4		0x30
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF5		0x34
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF6		0x38
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF7		0x3c
> > +#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF8		0x40
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0		0x44
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF1		0x48
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF2		0x4c
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF3		0x50
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF4		0x54
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF5		0x58
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF6		0x5c
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF7		0x60
> > +#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF8		0x64
> > +#define ZYNQMP_DISP_V_BLEND_NUM_OFFSET			3
> > +#define ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET		0x68
> > +#define ZYNQMP_DISP_V_BLEND_CR_IN1CSC_OFFSET		0x6c
> > +#define ZYNQMP_DISP_V_BLEND_CB_IN1CSC_OFFSET		0x70
> > +#define ZYNQMP_DISP_V_BLEND_LUMA_OUTCSC_OFFSET		0x74
> > +#define ZYNQMP_DISP_V_BLEND_CR_OUTCSC_OFFSET		0x78
> > +#define ZYNQMP_DISP_V_BLEND_CB_OUTCSC_OFFSET		0x7c
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0		0x80
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF1		0x84
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF2		0x88
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF3		0x8c
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF4		0x90
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF5		0x94
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF6		0x98
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF7		0x9c
> > +#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF8		0xa0
> > +#define ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET		0xa4
> > +#define ZYNQMP_DISP_V_BLEND_CR_IN2CSC_OFFSET		0xa8
> > +#define ZYNQMP_DISP_V_BLEND_CB_IN2CSC_OFFSET		0xac
> > +#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_ENABLE		0x1d0
> > +#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP1		0x1d4
> > +#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP2		0x1d8
> > +#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP3		0x1dc
> > +
> > +/* AV buffer manager registers */
> > +#define ZYNQMP_DISP_AV_BUF_FMT				0x0
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_SHIFT		0
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MASK		(0x1f
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_UYVY		(0 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY		(1 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YVYU		(2 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV		(3 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16		(4 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24		(5 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI		(6 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MONO		(7 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2		(8 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUV444		(9 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888		(10
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880		(11
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10
> 	(12 << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUV444_10
> 	(13 << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_10	(14
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_10
> 	(15 << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_10		(16
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24_10		(17
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YONLY_10		(18
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420		(19
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420	(20
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_420	(21
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420_10	(22
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420_10	(23
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_420_10	(24
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_SHIFT		8
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_MASK		(0xf
> << 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888
> 	(0 << 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888
> 	(1 << 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB888		(2 <<
> 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_BGR888		(3 <<
> 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551
> 	(4 << 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444
> 	(5 << 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565		(6 <<
> 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_8BPP		(7 <<
> 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_4BPP		(8 <<
> 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_2BPP		(9 <<
> 8)
> > +#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_1BPP		(10
> << 8)
> > +#define ZYNQMP_DISP_AV_BUF_NON_LIVE_LATENCY		0x8
> > +#define ZYNQMP_DISP_AV_BUF_CHBUF			0x10
> > +#define ZYNQMP_DISP_AV_BUF_CHBUF_EN			BIT(0)
> > +#define ZYNQMP_DISP_AV_BUF_CHBUF_FLUSH			BIT(1)
> > +#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT	2
> > +#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MASK
> 	(0xf << 2)
> > +#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MAX
> 	0xf
> > +#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_AUD_MAX	0x3
> > +#define ZYNQMP_DISP_AV_BUF_STATUS			0x28
> > +#define ZYNQMP_DISP_AV_BUF_STC_CTRL			0x2c
> > +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EN			BIT(0)
> > +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_SHIFT		1
> > +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_VSYNC	0
> > +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_VID	1
> > +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_AUD	2
> > +#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_INT_VSYNC	3
> > +#define ZYNQMP_DISP_AV_BUF_STC_INIT_VALUE0		0x30
> > +#define ZYNQMP_DISP_AV_BUF_STC_INIT_VALUE1		0x34
> > +#define ZYNQMP_DISP_AV_BUF_STC_ADJ			0x38
> > +#define ZYNQMP_DISP_AV_BUF_STC_VID_VSYNC_TS0		0x3c
> > +#define ZYNQMP_DISP_AV_BUF_STC_VID_VSYNC_TS1		0x40
> > +#define ZYNQMP_DISP_AV_BUF_STC_EXT_VSYNC_TS0		0x44
> > +#define ZYNQMP_DISP_AV_BUF_STC_EXT_VSYNC_TS1		0x48
> > +#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT_TS0
> 	0x4c
> > +#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT_TS1
> 	0x50
> > +#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT2_TS0	0x54
> > +#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT2_TS1	0x58
> > +#define ZYNQMP_DISP_AV_BUF_STC_SNAPSHOT0		0x60
> > +#define ZYNQMP_DISP_AV_BUF_STC_SNAPSHOT1		0x64
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT			0x70
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_SHIFT		0
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK		(0x3
> << 0)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE		(0 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM		(1 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN		(2 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_NONE		(3 <<
> 0)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_SHIFT		2
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK		(0x3
> << 2)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_DISABLE		(0 <<
> 2)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM		(1 <<
> 2)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE		(2 <<
> 2)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_NONE		(3 <<
> 2)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_SHIFT		4
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK		(0x3
> << 4)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_PL		(0 <<
> 4)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MEM		(1 <<
> 4)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_PATTERN
> 	(2 << 4)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_DISABLE		(3 <<
> 4)
> > +#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN		BIT(6)
> > +#define ZYNQMP_DISP_AV_BUF_HCOUNT_VCOUNT_INT0		0x74
> > +#define ZYNQMP_DISP_AV_BUF_HCOUNT_VCOUNT_INT1		0x78
> > +#define ZYNQMP_DISP_AV_BUF_PATTERN_GEN_SELECT		0x100
> > +#define ZYNQMP_DISP_AV_BUF_CLK_SRC			0x120
> > +#define ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS		BIT(0)
> > +#define ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS
> 	BIT(1)
> > +#define ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING
> 	BIT(2)
> > +#define ZYNQMP_DISP_AV_BUF_SRST_REG			0x124
> > +#define ZYNQMP_DISP_AV_BUF_SRST_REG_VID_RST		BIT(1)
> > +#define ZYNQMP_DISP_AV_BUF_AUDIO_CH_CONFIG		0x12c
> > +#define ZYNQMP_DISP_AV_BUF_GFX_COMP0_SF			0x200
> > +#define ZYNQMP_DISP_AV_BUF_GFX_COMP1_SF			0x204
> > +#define ZYNQMP_DISP_AV_BUF_GFX_COMP2_SF			0x208
> > +#define ZYNQMP_DISP_AV_BUF_VID_COMP0_SF			0x20c
> > +#define ZYNQMP_DISP_AV_BUF_VID_COMP1_SF			0x210
> > +#define ZYNQMP_DISP_AV_BUF_VID_COMP2_SF			0x214
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP0_SF		0x218
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP1_SF		0x21c
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_VID_COMP2_SF		0x220
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_VID_CONFIG		0x224
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP0_SF		0x228
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP1_SF		0x22c
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_COMP2_SF		0x230
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_CONFIG		0x234
> > +#define ZYNQMP_DISP_AV_BUF_4BIT_SF			0x11111
> > +#define ZYNQMP_DISP_AV_BUF_5BIT_SF			0x10842
> > +#define ZYNQMP_DISP_AV_BUF_6BIT_SF			0x10410
> > +#define ZYNQMP_DISP_AV_BUF_8BIT_SF			0x10101
> > +#define ZYNQMP_DISP_AV_BUF_10BIT_SF			0x10040
> > +#define ZYNQMP_DISP_AV_BUF_NULL_SF			0
> > +#define ZYNQMP_DISP_AV_BUF_NUM_SF			3
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_6		0x0
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8		0x1
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_10		0x2
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_12		0x3
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_MASK
> 	GENMASK(2, 0)
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB		0x0
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV444	0x1
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422	0x2
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YONLY	0x3
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_MASK
> 	GENMASK(5, 4)
> > +#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_CB_FIRST		BIT(8)
> > +#define ZYNQMP_DISP_AV_BUF_PALETTE_MEMORY		0x400
> > +
> > +/* Audio registers */
> > +#define ZYNQMP_DISP_AUD_MIXER_VOLUME			0x0
> > +#define ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE
> 	0x20002000
> > +#define ZYNQMP_DISP_AUD_MIXER_META_DATA			0x4
> > +#define ZYNQMP_DISP_AUD_CH_STATUS0			0x8
> > +#define ZYNQMP_DISP_AUD_CH_STATUS1			0xc
> > +#define ZYNQMP_DISP_AUD_CH_STATUS2			0x10
> > +#define ZYNQMP_DISP_AUD_CH_STATUS3			0x14
> > +#define ZYNQMP_DISP_AUD_CH_STATUS4			0x18
> > +#define ZYNQMP_DISP_AUD_CH_STATUS5			0x1c
> > +#define ZYNQMP_DISP_AUD_CH_A_DATA0			0x20
> > +#define ZYNQMP_DISP_AUD_CH_A_DATA1			0x24
> > +#define ZYNQMP_DISP_AUD_CH_A_DATA2			0x28
> > +#define ZYNQMP_DISP_AUD_CH_A_DATA3			0x2c
> > +#define ZYNQMP_DISP_AUD_CH_A_DATA4			0x30
> > +#define ZYNQMP_DISP_AUD_CH_A_DATA5			0x34
> > +#define ZYNQMP_DISP_AUD_CH_B_DATA0			0x38
> > +#define ZYNQMP_DISP_AUD_CH_B_DATA1			0x3c
> > +#define ZYNQMP_DISP_AUD_CH_B_DATA2			0x40
> > +#define ZYNQMP_DISP_AUD_CH_B_DATA3			0x44
> > +#define ZYNQMP_DISP_AUD_CH_B_DATA4			0x48
> > +#define ZYNQMP_DISP_AUD_CH_B_DATA5			0x4c
> > +#define ZYNQMP_DISP_AUD_SOFT_RESET			0xc00
> > +#define ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST		BIT(0)
> > +
> > +#define ZYNQMP_DISP_AV_BUF_NUM_VID_GFX_BUFFERS
> 	4
> > +#define ZYNQMP_DISP_AV_BUF_NUM_BUFFERS			6
> > +
> > +#define ZYNQMP_DISP_NUM_LAYERS				2
> > +#define ZYNQMP_DISP_MAX_NUM_SUB_PLANES			3
> > +/*
> > + * 3840x2160 is advertised max resolution, but almost any resolutions
> under
> > + * 300Mhz pixel rate would work. Thus put 4096 as maximum width and
> height.
> > + */
> > +#define ZYNQMP_DISP_MAX_WIDTH				4096
> > +#define ZYNQMP_DISP_MAX_HEIGHT				4096
> > +/* 44 bit addressing. This is acutally DPDMA limitation */
> > +#define ZYNQMP_DISP_MAX_DMA_BIT				44
> > +
> > +/**
> > + * enum zynqmp_disp_layer_type - Layer type (can be used for hw ID)
> > + * @ZYNQMP_DISP_LAYER_VID: Video layer
> > + * @ZYNQMP_DISP_LAYER_GFX: Graphics layer
> > + */
> > +enum zynqmp_disp_layer_type {
> > +	ZYNQMP_DISP_LAYER_VID,
> > +	ZYNQMP_DISP_LAYER_GFX
> > +};
> > +
> > +/**
> > + * enum zynqmp_disp_layer_mode - Layer mode
> > + * @ZYNQMP_DISP_LAYER_NONLIVE: non-live (memory) mode
> > + * @ZYNQMP_DISP_LAYER_LIVE: live (stream) mode
> > + */
> > +enum zynqmp_disp_layer_mode {
> > +	ZYNQMP_DISP_LAYER_NONLIVE,
> > +	ZYNQMP_DISP_LAYER_LIVE
> > +};
> > +
> > +/**
> > + * struct zynqmp_disp_layer_dma - struct for DMA engine
> > + * @chan: DMA channel
> > + * @is_active: flag if the DMA is active
> > + * @xt: Interleaved desc config container
> > + * @sgl: Data chunk for dma_interleaved_template
> > + */
> > +struct zynqmp_disp_layer_dma {
> > +	struct dma_chan *chan;
> > +	bool is_active;
> > +	struct dma_interleaved_template xt;
> > +	struct data_chunk sgl[1];
> > +};
> > +
> > +/**
> > + * struct zynqmp_disp_layer - Display subsystem layer
> > + * @plane: DRM plane
> > + * @of_node: device node
> > + * @dma: struct for DMA engine
> > + * @num_chan: Number of DMA channel
> > + * @id: Layer ID
> > + * @offset: Layer offset in the register space
> > + * @enabled: flag if enabled
> > + * @fmt: Current format descriptor
> > + * @drm_fmts: Array of supported DRM formats
> > + * @num_fmts: Number of supported DRM formats
> > + * @bus_fmts: Array of supported bus formats
> > + * @num_bus_fmts: Number of supported bus formats
> > + * @w: Width
> > + * @h: Height
> > + * @mode: the operation mode
> > + * @other: other layer
> > + * @disp: back pointer to struct zynqmp_disp
> > + */
> > +struct zynqmp_disp_layer {
> > +	struct drm_plane plane;
> > +	struct device_node *of_node;
> > +	struct zynqmp_disp_layer_dma
> dma[ZYNQMP_DISP_MAX_NUM_SUB_PLANES];
> > +	unsigned int num_chan;
> > +	enum zynqmp_disp_layer_type id;
> > +	u32 offset;
> > +	u8 enabled;
> > +	const struct zynqmp_disp_fmt *fmt;
> > +	u32 *drm_fmts;
> > +	unsigned int num_fmts;
> > +	u32 *bus_fmts;
> > +	unsigned int num_bus_fmts;
> > +	u32 w;
> > +	u32 h;
> > +	enum zynqmp_disp_layer_mode mode;
> > +	struct zynqmp_disp_layer *other;
> > +	struct zynqmp_disp *disp;
> > +};
> > +
> > +/**
> > + * struct zynqmp_disp_blend - Blender
> > + * @base: Base address offset
> > + */
> > +struct zynqmp_disp_blend {
> > +	void __iomem *base;
> > +};
> > +
> > +/**
> > + * struct zynqmp_disp_av_buf - AV buffer manager
> > + * @base: Base address offset
> > + */
> > +struct zynqmp_disp_av_buf {
> > +	void __iomem *base;
> > +};
> > +
> > +/**
> > + * struct zynqmp_disp_aud - Audio
> > + * @base: Base address offset
> > + */
> > +struct zynqmp_disp_aud {
> > +	void __iomem *base;
> > +};
> > +
> > +/**
> > + * struct zynqmp_disp - Display subsystem
> > + * @xlnx_crtc: Xilinx DRM crtc
> > + * @dev: device structure
> > + * @dpsub: Display subsystem
> > + * @drm: DRM core
> > + * @enabled: flag if enabled
> > + * @blend: Blender block
> > + * @av_buf: AV buffer manager block
> > + * @aud:Audio block
> > + * @layers: layers
> > + * @g_alpha_prop: global alpha property
> > + * @alpha: current global alpha value
> > + * @g_alpha_en_prop: the global alpha enable property
> > + * @alpha_en: flag if the global alpha is enabled
> > + * @color_prop: output color format property
> > + * @color: current output color value
> > + * @bg_c0_prop: 1st component of background color property
> > + * @bg_c0: current value of 1st background color component
> > + * @bg_c1_prop: 2nd component of background color property
> > + * @bg_c1: current value of 2nd background color component
> > + * @bg_c2_prop: 3rd component of background color property
> > + * @bg_c2: current value of 3rd background color component
> > + * @tpg_prop: Test Pattern Generation mode property
> > + * @tpg_on: current TPG mode state
> > + * @event: pending vblank event request
> > + * @_ps_pclk: Pixel clock from PS
> > + * @_pl_pclk: Pixel clock from PL
> > + * @pclk: Pixel clock
> > + * @pclk_en: Flag if the pixel clock is enabled
> > + * @_ps_audclk: Audio clock from PS
> > + * @_pl_audclk: Audio clock from PL
> > + * @audclk: Audio clock
> > + * @audclk_en: Flag if the audio clock is enabled
> > + * @aclk: APB clock
> > + * @aclk_en: Flag if the APB clock is enabled
> > + */
> > +struct zynqmp_disp {
> > +	struct xlnx_crtc xlnx_crtc;
> > +	struct device *dev;
> > +	struct zynqmp_dpsub *dpsub;
> > +	struct drm_device *drm;
> > +	bool enabled;
> > +	struct zynqmp_disp_blend blend;
> > +	struct zynqmp_disp_av_buf av_buf;
> > +	struct zynqmp_disp_aud aud;
> > +	struct zynqmp_disp_layer layers[ZYNQMP_DISP_NUM_LAYERS];
> > +	struct drm_property *g_alpha_prop;
> > +	u32 alpha;
> > +	struct drm_property *g_alpha_en_prop;
> > +	bool alpha_en;
> > +	struct drm_property *color_prop;
> > +	unsigned int color;
> > +	struct drm_property *bg_c0_prop;
> > +	u32 bg_c0;
> > +	struct drm_property *bg_c1_prop;
> > +	u32 bg_c1;
> > +	struct drm_property *bg_c2_prop;
> > +	u32 bg_c2;
> > +	struct drm_property *tpg_prop;
> > +	bool tpg_on;
> > +	struct drm_pending_vblank_event *event;
> > +	/* Don't operate directly on _ps_ */
> > +	struct clk *_ps_pclk;
> > +	struct clk *_pl_pclk;
> > +	struct clk *pclk;
> > +	bool pclk_en;
> > +	struct clk *_ps_audclk;
> > +	struct clk *_pl_audclk;
> > +	struct clk *audclk;
> > +	bool audclk_en;
> > +	struct clk *aclk;
> > +	bool aclk_en;
> > +};
> > +
> > +/**
> > + * struct zynqmp_disp_fmt - Display subsystem format mapping
> > + * @drm_fmt: drm format
> > + * @disp_fmt: Display subsystem format
> > + * @bus_fmt: Bus formats (live formats)
> > + * @rgb: flag for RGB formats
> > + * @swap: flag to swap r & b for rgb formats, and u & v for yuv formats
> > + * @chroma_sub: flag for chroma subsampled formats
> > + * @sf: scaling factors for upto 3 color components
> > + */
> > +struct zynqmp_disp_fmt {
> > +	u32 drm_fmt;
> > +	u32 disp_fmt;
> > +	u32 bus_fmt;
> > +	bool rgb;
> > +	bool swap;
> > +	bool chroma_sub;
> > +	u32 sf[3];
> > +};
> > +
> > +static void zynqmp_disp_write(void __iomem *base, int offset, u32 val)
> > +{
> > +	writel(val, base + offset);
> > +}
> > +
> > +static u32 zynqmp_disp_read(void __iomem *base, int offset)
> > +{
> > +	return readl(base + offset);
> > +}
> > +
> > +static void zynqmp_disp_clr(void __iomem *base, int offset, u32 clr)
> > +{
> > +	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) &
> ~clr);
> > +}
> > +
> > +static void zynqmp_disp_set(void __iomem *base, int offset, u32 set)
> > +{
> > +	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) |
> set);
> > +}
> > +
> > +/*
> > + * Clock functions
> > + */
> > +
> > +/**
> > + * zynqmp_disp_clk_enable - Enable the clock if needed
> > + * @clk: clk device
> > + * @flag: flag if the clock is enabled
> > + *
> > + * Enable the clock only if it's not enabled @flag.
> > + *
> > + * Return: value from clk_prepare_enable().
> > + */
> > +static int zynqmp_disp_clk_enable(struct clk *clk, bool *flag)
> > +{
> > +	int ret = 0;
> > +
> > +	if (!*flag) {
> > +		ret = clk_prepare_enable(clk);
> > +		if (!ret)
> > +			*flag = true;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_clk_enable - Enable the clock if needed
> > + * @clk: clk device
> > + * @flag: flag if the clock is enabled
> > + *
> > + * Disable the clock only if it's enabled @flag.
> > + */
> > +static void zynqmp_disp_clk_disable(struct clk *clk, bool *flag)
> > +{
> > +	if (*flag) {
> > +		clk_disable_unprepare(clk);
> > +		*flag = false;
> > +	}
> > +}
> > +
> > +/**
> > + * zynqmp_disp_clk_enable - Enable and disable the clock
> > + * @clk: clk device
> > + * @flag: flag if the clock is enabled
> > + *
> > + * This is to ensure the clock is disabled. The initial hardware state is
> > + * unknown, and this makes sure that the clock is disabled.
> > + *
> > + * Return: value from clk_prepare_enable().
> > + */
> > +static int zynqmp_disp_clk_enable_disable(struct clk *clk, bool *flag)
> > +{
> > +	int ret = 0;
> > +
> > +	if (!*flag) {
> > +		ret = clk_prepare_enable(clk);
> > +		clk_disable_unprepare(clk);
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +/*
> > + * Blender functions
> > + */
> > +
> > +/**
> > + * zynqmp_disp_blend_set_output_fmt - Set the output format of the
> blend
> > + * @blend: blend object
> > + * @fmt: output format
> > + *
> > + * Set the output format to @fmt.
> > + */
> > +static void
> > +zynqmp_disp_blend_set_output_fmt(struct zynqmp_disp_blend *blend,
> u32 fmt)
> > +{
> > +	u16 reset_coeffs[] = { 0x1000, 0x0, 0x0,
> > +			       0x0, 0x1000, 0x0,
> > +			       0x0, 0x0, 0x1000 };
> > +	u32 reset_offsets[] = { 0x0, 0x0, 0x0 };
> > +	u16 sdtv_coeffs[] = { 0x4c9, 0x864, 0x1d3,
> > +			      0x7d4d, 0x7ab3, 0x800,
> > +			      0x800, 0x794d, 0x7eb3 };
> > +	u32 full_range_offsets[] = { 0x0, 0x8000000, 0x8000000 };
> > +	u16 *coeffs;
> > +	u32 *offsets;
> > +	u32 offset, i;
> > +
> > +	zynqmp_disp_write(blend->base,
> ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
> > +	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
> > +		coeffs = reset_coeffs;
> > +		offsets = reset_offsets;
> > +	} else {
> > +		/* Hardcode Full-range SDTV values. Can be runtime config
> */
> > +		coeffs = sdtv_coeffs;
> > +		offsets = full_range_offsets;
> > +	}
> > +
> > +	offset = ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF0;
> > +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
> > +		zynqmp_disp_write(blend->base, offset + i * 4, coeffs[i]);
> > +
> > +	offset = ZYNQMP_DISP_V_BLEND_LUMA_OUTCSC_OFFSET;
> > +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
> > +		zynqmp_disp_write(blend->base, offset + i * 4, offsets[i]);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_blend_layer_enable - Enable a layer
> > + * @blend: blend object
> > + * @layer: layer to enable
> > + *
> > + * Enable a layer @layer.
> > + */
> > +static void zynqmp_disp_blend_layer_enable(struct
> zynqmp_disp_blend *blend,
> > +					   struct zynqmp_disp_layer *layer)
> > +{
> > +	u32 reg, offset, i, s0, s1;
> > +	u16 sdtv_coeffs[] = { 0x1000, 0x166f, 0x0,
> > +			      0x1000, 0x7483, 0x7a7f,
> > +			      0x1000, 0x0, 0x1c5a };
> > +	u16 swap_coeffs[] = { 0x1000, 0x0, 0x0,
> > +			      0x0, 0x1000, 0x0,
> > +			      0x0, 0x0, 0x1000 };
> > +	u16 *coeffs;
> > +	u32 offsets[] = { 0x0, 0x1800, 0x1800 };
> > +
> > +	reg = layer->fmt->rgb ?
> ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_RGB : 0;
> > +	reg |= layer->fmt->chroma_sub ?
> > +	       ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_EN_US : 0;
> > +
> > +	zynqmp_disp_write(blend->base,
> > +			  ZYNQMP_DISP_V_BLEND_LAYER_CONTROL + layer-
> >offset,
> > +			  reg);
> > +
> > +	if (layer->id == ZYNQMP_DISP_LAYER_VID)
> > +		offset = ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0;
> > +	else
> > +		offset = ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0;
> > +
> > +	if (!layer->fmt->rgb) {
> > +		coeffs = sdtv_coeffs;
> > +		s0 = 1;
> > +		s1 = 2;
> > +	} else {
> > +		coeffs = swap_coeffs;
> > +		s0 = 0;
> > +		s1 = 2;
> > +
> > +		/* No offset for RGB formats */
> > +		for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
> > +			offsets[i] = 0;
> > +	}
> > +
> > +	if (layer->fmt->swap) {
> > +		for (i = 0; i < 3; i++) {
> > +			coeffs[i * 3 + s0] ^= coeffs[i * 3 + s1];
> > +			coeffs[i * 3 + s1] ^= coeffs[i * 3 + s0];
> > +			coeffs[i * 3 + s0] ^= coeffs[i * 3 + s1];
> > +		}
> > +	}
> > +
> > +	/* Program coefficients. Can be runtime configurable */
> > +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
> > +		zynqmp_disp_write(blend->base, offset + i * 4, coeffs[i]);
> > +
> > +	if (layer->id == ZYNQMP_DISP_LAYER_VID)
> > +		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET;
> > +	else
> > +		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET;
> > +
> > +	/* Program offsets. Can be runtime configurable */
> > +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
> > +		zynqmp_disp_write(blend->base, offset + i * 4, offsets[i]);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_blend_layer_disable - Disable a layer
> > + * @blend: blend object
> > + * @layer: layer to disable
> > + *
> > + * Disable a layer @layer.
> > + */
> > +static void zynqmp_disp_blend_layer_disable(struct
> zynqmp_disp_blend *blend,
> > +					    struct zynqmp_disp_layer *layer)
> > +{
> > +	u32 offset;
> > +	unsigned int i;
> > +
> > +	zynqmp_disp_write(blend->base,
> > +			  ZYNQMP_DISP_V_BLEND_LAYER_CONTROL + layer-
> >offset, 0);
> > +
> > +	if (layer->id == ZYNQMP_DISP_LAYER_VID)
> > +		offset = ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF0;
> > +	else
> > +		offset = ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF0;
> > +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_COEFF; i++)
> > +		zynqmp_disp_write(blend->base, offset + i * 4, 0);
> > +
> > +	if (layer->id == ZYNQMP_DISP_LAYER_VID)
> > +		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN1CSC_OFFSET;
> > +	else
> > +		offset = ZYNQMP_DISP_V_BLEND_LUMA_IN2CSC_OFFSET;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_V_BLEND_NUM_OFFSET; i++)
> > +		zynqmp_disp_write(blend->base, offset + i * 4, 0);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_blend_set_bg_color - Set the background color
> > + * @blend: blend object
> > + * @c0: color component 0
> > + * @c1: color component 1
> > + * @c2: color component 2
> > + *
> > + * Set the background color.
> > + */
> > +static void zynqmp_disp_blend_set_bg_color(struct
> zynqmp_disp_blend *blend,
> > +					   u32 c0, u32 c1, u32 c2)
> > +{
> > +	zynqmp_disp_write(blend->base,
> ZYNQMP_DISP_V_BLEND_BG_CLR_0, c0);
> > +	zynqmp_disp_write(blend->base,
> ZYNQMP_DISP_V_BLEND_BG_CLR_1, c1);
> > +	zynqmp_disp_write(blend->base,
> ZYNQMP_DISP_V_BLEND_BG_CLR_2, c2);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_blend_set_alpha - Set the alpha for blending
> > + * @blend: blend object
> > + * @alpha: alpha value to be used
> > + *
> > + * Set the alpha for blending.
> > + */
> > +static void
> > +zynqmp_disp_blend_set_alpha(struct zynqmp_disp_blend *blend, u32
> alpha)
> > +{
> > +	u32 reg;
> > +
> > +	reg = zynqmp_disp_read(blend->base,
> > +			       ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA);
> > +	reg &= ~ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MASK;
> > +	reg |= alpha << 1;
> > +	zynqmp_disp_write(blend->base,
> ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA,
> > +			  reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_blend_enable_alpha - Enable/disable the global alpha
> > + * @blend: blend object
> > + * @enable: flag to enable or disable alpha blending
> > + *
> > + * Enable/disable the global alpha blending based on @enable.
> > + */
> > +static void
> > +zynqmp_disp_blend_enable_alpha(struct zynqmp_disp_blend *blend,
> bool enable)
> > +{
> > +	if (enable)
> > +		zynqmp_disp_set(blend->base,
> > +
> 	ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA, BIT(0));
> > +	else
> > +		zynqmp_disp_clr(blend->base,
> > +
> 	ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA, BIT(0));
> > +}
> > +
> > +/* List of blend output formats */
> > +/* The id / order should be aligned with zynqmp_disp_color_enum */
> > +static const struct zynqmp_disp_fmt blend_output_fmts[] = {
> > +	{
> > +		.disp_fmt	=
> ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB,
> > +	}, {
> > +		.disp_fmt	=
> ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444,
> > +	}, {
> > +		.disp_fmt	=
> ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422,
> > +	}, {
> > +		.disp_fmt	=
> ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY,
> > +	}
> > +};
> > +
> > +/*
> > + * AV buffer manager functions
> > + */
> > +
> > +/* List of video layer formats */
> > +#define ZYNQMP_DISP_AV_BUF_VID_FMT_YUYV	2
> > +static const struct zynqmp_disp_fmt av_buf_vid_fmts[] = {
> > +	{
> > +		.drm_fmt	= DRM_FORMAT_VYUY,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY,
> > +		.rgb		= false,
> > +		.swap		= true,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_UYVY,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_YUYV,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_YVYU,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV,
> > +		.rgb		= false,
> > +		.swap		= true,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_YUV422,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_YVU422,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16,
> > +		.rgb		= false,
> > +		.swap		= true,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_YUV444,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_YVU444,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24,
> > +		.rgb		= false,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_NV16,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_NV61,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI,
> > +		.rgb		= false,
> > +		.swap		= true,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_BGR888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_RGB888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888,
> > +		.rgb		= true,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_XBGR8888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_XRGB8888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880,
> > +		.rgb		= true,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_XBGR2101010,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_XRGB2101010,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10,
> > +		.rgb		= true,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_YUV420,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_YVU420,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420,
> > +		.rgb		= false,
> > +		.swap		= true,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_NV12,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_NV21,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420,
> > +		.rgb		= false,
> > +		.swap		= true,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}
> > +};
> > +
> > +/* List of graphics layer formats */
> > +#define ZYNQMP_DISP_AV_BUF_GFX_FMT_RGB565	10
> > +static const struct zynqmp_disp_fmt av_buf_gfx_fmts[] = {
> > +	{
> > +		.drm_fmt	= DRM_FORMAT_ABGR8888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_ARGB8888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888,
> > +		.rgb		= true,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_RGBA8888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_BGRA8888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888,
> > +		.rgb		= true,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_BGR888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB888,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_RGB888,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_BGR888,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_RGBA5551,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_BGRA5551,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551,
> > +		.rgb		= true,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_RGBA4444,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_BGRA4444,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444,
> > +		.rgb		= true,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_4BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_RGB565,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +	}, {
> > +		.drm_fmt	= DRM_FORMAT_BGR565,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565,
> > +		.rgb		= true,
> > +		.swap		= true,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_5BIT_SF,
> > +	}
> > +};
> > +
> > +/* List of live formats */
> > +/* Format can be combination of color, bpc, and cb-cr order.
> > + * - Color: RGB / YUV444 / YUV422 / Y only
> > + * - BPC: 6, 8, 10, 12
> > + * - Swap: Cb and Cr swap
> > + * which can be 32 bus formats. Only list the subset of those for now.
> > + */
> > +static const struct zynqmp_disp_fmt av_buf_live_fmts[] = {
> > +	{
> > +		.bus_fmt	= MEDIA_BUS_FMT_RGB666_1X18,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_6 ||
> > +
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_6BIT_SF,
> > +	}, {
> > +		.bus_fmt	= MEDIA_BUS_FMT_RBG888_1X24,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
> > +
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB,
> > +		.rgb		= true,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.bus_fmt	= MEDIA_BUS_FMT_UYVY8_1X16,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
> > +
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.bus_fmt	= MEDIA_BUS_FMT_VUY8_1X24,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 ||
> > +
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV444,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= false,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_8BIT_SF,
> > +	}, {
> > +		.bus_fmt	= MEDIA_BUS_FMT_UYVY10_1X20,
> > +		.disp_fmt	=
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_10 ||
> > +
> ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422,
> > +		.rgb		= false,
> > +		.swap		= false,
> > +		.chroma_sub	= true,
> > +		.sf[0]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +		.sf[1]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +		.sf[2]		= ZYNQMP_DISP_AV_BUF_10BIT_SF,
> > +	}
> > +};
> > +
> > +/**
> > + * zynqmp_disp_av_buf_set_fmt - Set the input formats
> > + * @av_buf: av buffer manager
> > + * @fmt: formats
> > + *
> > + * Set the av buffer manager format to @fmt. @fmt should have valid
> values
> > + * for both video and graphics layer.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_set_fmt(struct zynqmp_disp_av_buf *av_buf,
> u32 fmt)
> > +{
> > +	zynqmp_disp_write(av_buf->base, ZYNQMP_DISP_AV_BUF_FMT,
> fmt);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_get_fmt - Get the input formats
> > + * @av_buf: av buffer manager
> > + *
> > + * Get the input formats (which include video and graphics) of
> > + * av buffer manager.
> > + *
> > + * Return: value of ZYNQMP_DISP_AV_BUF_FMT register.
> > + */
> > +static u32
> > +zynqmp_disp_av_buf_get_fmt(struct zynqmp_disp_av_buf *av_buf)
> > +{
> > +	return zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_FMT);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_set_vid_clock_src - Set the video clock source
> > + * @av_buf: av buffer manager
> > + * @from_ps: flag if the video clock is from ps
> > + *
> > + * Set the video clock source based on @from_ps. It can come from
> either PS or
> > + * PL.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_set_vid_clock_src(struct zynqmp_disp_av_buf
> *av_buf,
> > +				     bool from_ps)
> > +{
> > +	u32 reg = zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_CLK_SRC);
> > +
> > +	if (from_ps)
> > +		reg |= ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS;
> > +	else
> > +		reg &= ~ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS;
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_set_vid_timing_src - Set the video timing
> source
> > + * @av_buf: av buffer manager
> > + * @internal: flag if the video timing is generated internally
> > + *
> > + * Set the video timing source based on @internal. It can come externally
> or
> > + * be generated internally.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_set_vid_timing_src(struct zynqmp_disp_av_buf
> *av_buf,
> > +				      bool internal)
> > +{
> > +	u32 reg = zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_CLK_SRC);
> > +
> > +	if (internal)
> > +		reg |=
> ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING;
> > +	else
> > +		reg &=
> ~ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING;
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_set_aud_clock_src - Set the audio clock source
> > + * @av_buf: av buffer manager
> > + * @from_ps: flag if the video clock is from ps
> > + *
> > + * Set the audio clock source based on @from_ps. It can come from
> either PS or
> > + * PL.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_set_aud_clock_src(struct zynqmp_disp_av_buf
> *av_buf,
> > +				     bool from_ps)
> > +{
> > +	u32 reg = zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_CLK_SRC);
> > +
> > +	if (from_ps)
> > +		reg |= ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS;
> > +	else
> > +		reg &= ~ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS;
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_CLK_SRC, reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_enable_buf - Enable buffers
> > + * @av_buf: av buffer manager
> > + *
> > + * Enable all (video and audio) buffers.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_enable_buf(struct zynqmp_disp_av_buf *av_buf)
> > +{
> > +	u32 reg, i;
> > +
> > +	reg = ZYNQMP_DISP_AV_BUF_CHBUF_EN;
> > +	reg |= ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MAX <<
> > +	       ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_VID_GFX_BUFFERS; i++)
> > +		zynqmp_disp_write(av_buf->base,
> > +				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4,
> reg);
> > +
> > +	reg = ZYNQMP_DISP_AV_BUF_CHBUF_EN;
> > +	reg |= ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_AUD_MAX <<
> > +	       ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT;
> > +
> > +	for (; i < ZYNQMP_DISP_AV_BUF_NUM_BUFFERS; i++)
> > +		zynqmp_disp_write(av_buf->base,
> > +				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4,
> reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_disable_buf - Disable buffers
> > + * @av_buf: av buffer manager
> > + *
> > + * Disable all (video and audio) buffers.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_disable_buf(struct zynqmp_disp_av_buf *av_buf)
> > +{
> > +	u32 reg, i;
> > +
> > +	reg = ZYNQMP_DISP_AV_BUF_CHBUF_FLUSH &
> ~ZYNQMP_DISP_AV_BUF_CHBUF_EN;
> > +	for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_BUFFERS; i++)
> > +		zynqmp_disp_write(av_buf->base,
> > +				  ZYNQMP_DISP_AV_BUF_CHBUF + i * 4,
> reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_enable_aud - Enable audio
> > + * @av_buf: av buffer manager
> > + *
> > + * Enable all audio buffers.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_enable_aud(struct zynqmp_disp_av_buf *av_buf)
> > +{
> > +	u32 reg;
> > +
> > +	reg = zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT);
> > +	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK;
> > +	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MEM;
> > +	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN;
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_enable - Enable the video pipe
> > + * @av_buf: av buffer manager
> > + *
> > + * De-assert the video pipe reset
> > + */
> > +static void
> > +zynqmp_disp_av_buf_enable(struct zynqmp_disp_av_buf *av_buf)
> > +{
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_SRST_REG, 0);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_disable - Disable the video pipe
> > + * @av_buf: av buffer manager
> > + *
> > + * Assert the video pipe reset
> > + */
> > +static void
> > +zynqmp_disp_av_buf_disable(struct zynqmp_disp_av_buf *av_buf)
> > +{
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_SRST_REG,
> > +			  ZYNQMP_DISP_AV_BUF_SRST_REG_VID_RST);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_disable_aud - Disable audio
> > + * @av_buf: av buffer manager
> > + *
> > + * Disable all audio buffers.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_disable_aud(struct zynqmp_disp_av_buf *av_buf)
> > +{
> > +	u32 reg;
> > +
> > +	reg = zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT);
> > +	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK;
> > +	reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_DISABLE;
> > +	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN;
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_set_tpg - Set TPG mode
> > + * @av_buf: av buffer manager
> > + * @tpg_on: if TPG should be on
> > + *
> > + * Set the TPG mode based on @tpg_on.
> > + */
> > +static void zynqmp_disp_av_buf_set_tpg(struct zynqmp_disp_av_buf
> *av_buf,
> > +				       bool tpg_on)
> > +{
> > +	u32 reg;
> > +
> > +	reg = zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT);
> > +	reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
> > +	if (tpg_on)
> > +		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN;
> > +	else
> > +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN;
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_enable_vid - Enable the video layer buffer
> > + * @av_buf: av buffer manager
> > + * @layer: layer to enable
> > + * @mode: operation mode of layer
> > + *
> > + * Enable the video/graphics buffer for @layer.
> > + */
> > +static void zynqmp_disp_av_buf_enable_vid(struct
> zynqmp_disp_av_buf *av_buf,
> > +					  struct zynqmp_disp_layer *layer,
> > +					  enum zynqmp_disp_layer_mode
> mode)
> > +{
> > +	u32 reg;
> > +
> > +	reg = zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT);
> > +	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
> > +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
> > +		if (mode == ZYNQMP_DISP_LAYER_NONLIVE)
> > +			reg |=
> ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM;
> > +		else
> > +			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE;
> > +	} else {
> > +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK;
> > +		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM;
> > +		if (mode == ZYNQMP_DISP_LAYER_NONLIVE)
> > +			reg |=
> ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM;
> > +		else
> > +			reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE;
> > +	}
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_disable_vid - Disable the video layer buffer
> > + * @av_buf: av buffer manager
> > + * @layer: layer to disable
> > + *
> > + * Disable the video/graphics buffer for @layer.
> > + */
> > +static void
> > +zynqmp_disp_av_buf_disable_vid(struct zynqmp_disp_av_buf *av_buf,
> > +			       struct zynqmp_disp_layer *layer)
> > +{
> > +	u32 reg;
> > +
> > +	reg = zynqmp_disp_read(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT);
> > +	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
> > +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK;
> > +		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_NONE;
> > +	} else {
> > +		reg &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK;
> > +		reg |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_DISABLE;
> > +	}
> > +	zynqmp_disp_write(av_buf->base,
> ZYNQMP_DISP_AV_BUF_OUTPUT, reg);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_av_buf_init_sf - Initialize scaling factors
> > + * @av_buf: av buffer manager
> > + * @vid_fmt: video format descriptor
> > + * @gfx_fmt: graphics format descriptor
> > + *
> > + * Initialize scaling factors for both video and graphics layers.
> > + * If the format descriptor is NULL, the function skips the programming.
> > + */
> > +static void zynqmp_disp_av_buf_init_sf(struct zynqmp_disp_av_buf
> *av_buf,
> > +				       const struct zynqmp_disp_fmt *vid_fmt,
> > +				       const struct zynqmp_disp_fmt *gfx_fmt)
> > +{
> > +	unsigned int i;
> > +	u32 offset;
> > +
> > +	if (gfx_fmt) {
> > +		offset = ZYNQMP_DISP_AV_BUF_GFX_COMP0_SF;
> > +		for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_SF; i++)
> > +			zynqmp_disp_write(av_buf->base, offset + i * 4,
> > +					  gfx_fmt->sf[i]);
> > +	}
> > +
> > +	if (vid_fmt) {
> > +		offset = ZYNQMP_DISP_AV_BUF_VID_COMP0_SF;
> > +		for (i = 0; i < ZYNQMP_DISP_AV_BUF_NUM_SF; i++)
> > +			zynqmp_disp_write(av_buf->base, offset + i * 4,
> > +					  vid_fmt->sf[i]);
> > +	}
> > +}
> > +
> > +/*
> > + * Audio functions
> > + */
> > +
> > +/**
> > + * zynqmp_disp_aud_init - Initialize the audio
> > + * @aud: audio
> > + *
> > + * Initialize the audio with default mixer volume. The de-assertion will
> > + * initialize the audio states.
> > + */
> > +static void zynqmp_disp_aud_init(struct zynqmp_disp_aud *aud)
> > +{
> > +	/* Clear the audio soft reset register as it's an non-reset flop */
> > +	zynqmp_disp_write(aud->base, ZYNQMP_DISP_AUD_SOFT_RESET,
> 0);
> > +	zynqmp_disp_write(aud->base,
> ZYNQMP_DISP_AUD_MIXER_VOLUME,
> > +			  ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_aud_deinit - De-initialize the audio
> > + * @aud: audio
> > + *
> > + * Put the audio in reset.
> > + */
> > +static void zynqmp_disp_aud_deinit(struct zynqmp_disp_aud *aud)
> > +{
> > +	zynqmp_disp_set(aud->base, ZYNQMP_DISP_AUD_SOFT_RESET,
> > +			ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST);
> > +}
> > +
> > +/*
> > + * ZynqMP Display layer functions
> > + */
> > +
> > +/**
> > + * zynqmp_disp_layer_check_size - Verify width and height for the layer
> > + * @disp: Display subsystem
> > + * @layer: layer
> > + * @width: width
> > + * @height: height
> > + *
> > + * The Display subsystem has the limitation that both layers should have
> > + * identical size. This function stores width and height of @layer, and
> verifies
> > + * if the size (width and height) is valid.
> > + *
> > + * Return: 0 on success, or -EINVAL if width or/and height is invalid.
> > + */
> > +static int zynqmp_disp_layer_check_size(struct zynqmp_disp *disp,
> > +					struct zynqmp_disp_layer *layer,
> > +					u32 width, u32 height)
> > +{
> > +	struct zynqmp_disp_layer *other = layer->other;
> > +
> > +	if (other->enabled && (other->w != width || other->h != height)) {
> > +		dev_err(disp->dev, "Layer width:height must be %d:%d\n",
> > +			other->w, other->h);
> > +		return -EINVAL;
> > +	}
> > +
> > +	layer->w = width;
> > +	layer->h = height;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_map_fmt - Find the Display subsystem format for given
> drm format
> > + * @fmts: format table to look up
> > + * @size: size of the table @fmts
> > + * @drm_fmt: DRM format to search
> > + *
> > + * Search a Display subsystem format corresponding to the given DRM
> format
> > + * @drm_fmt, and return the format descriptor which contains the
> Display
> > + * subsystem format value.
> > + *
> > + * Return: a Display subsystem format descriptor on success, or NULL.
> > + */
> > +static const struct zynqmp_disp_fmt *
> > +zynqmp_disp_map_fmt(const struct zynqmp_disp_fmt fmts[],
> > +		    unsigned int size, uint32_t drm_fmt)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < size; i++)
> > +		if (fmts[i].drm_fmt == drm_fmt)
> > +			return &fmts[i];
> > +
> > +	return NULL;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_set_fmt - Set the format of the layer
> > + * @disp: Display subsystem
> > + * @layer: layer to set the format
> > + * @drm_fmt: DRM format to set
> > + *
> > + * Set the format of the given layer to @drm_fmt.
> > + *
> > + * Return: 0 on success. -EINVAL if @drm_fmt is not supported by the
> layer.
> > + */
> > +static int zynqmp_disp_layer_set_fmt(struct zynqmp_disp *disp,
> > +				     struct zynqmp_disp_layer *layer,
> > +				     uint32_t drm_fmt)
> > +{
> > +	const struct zynqmp_disp_fmt *fmt;
> > +	const struct zynqmp_disp_fmt *vid_fmt = NULL, *gfx_fmt = NULL;
> > +	u32 size, fmts, mask;
> > +
> > +	if (layer->id == ZYNQMP_DISP_LAYER_VID) {
> > +		size = ARRAY_SIZE(av_buf_vid_fmts);
> > +		mask = ~ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MASK;
> > +		fmt = zynqmp_disp_map_fmt(av_buf_vid_fmts, size,
> drm_fmt);
> > +		vid_fmt = fmt;
> > +	} else {
> > +		size = ARRAY_SIZE(av_buf_gfx_fmts);
> > +		mask = ~ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_MASK;
> > +		fmt = zynqmp_disp_map_fmt(av_buf_gfx_fmts, size,
> drm_fmt);
> > +		gfx_fmt = fmt;
> > +	}
> > +
> > +	if (!fmt)
> > +		return -EINVAL;
> > +
> > +	fmts = zynqmp_disp_av_buf_get_fmt(&disp->av_buf);
> > +	fmts &= mask;
> > +	fmts |= fmt->disp_fmt;
> > +	zynqmp_disp_av_buf_set_fmt(&disp->av_buf, fmts);
> > +	zynqmp_disp_av_buf_init_sf(&disp->av_buf, vid_fmt, gfx_fmt);
> > +	layer->fmt = fmt;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_set_tpg - Enable or disable TPG
> > + * @disp: Display subsystem
> > + * @layer: Video layer
> > + * @tpg_on: flag if TPG needs to be enabled or disabled
> > + *
> > + * Enable / disable the TPG mode on the video layer @layer depending
> on
> > + * @tpg_on. The video layer should be disabled prior to enable request.
> > + *
> > + * Return: 0 on success. -ENODEV if it's not video layer. -EIO if
> > + * the video layer is enabled.
> > + */
> > +static int zynqmp_disp_layer_set_tpg(struct zynqmp_disp *disp,
> > +				     struct zynqmp_disp_layer *layer,
> > +				     bool tpg_on)
> > +{
> > +	if (layer->id != ZYNQMP_DISP_LAYER_VID) {
> > +		dev_err(disp->dev,
> > +			"only the video layer has the tpg mode\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	if (layer->enabled) {
> > +		dev_err(disp->dev,
> > +			"the video layer should be disabled for tpg mode\n");
> > +		return -EIO;
> > +	}
> > +
> > +	zynqmp_disp_av_buf_set_tpg(&disp->av_buf, tpg_on);
> > +	disp->tpg_on = tpg_on;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_get_tpg - Get the TPG mode status
> > + * @disp: Display subsystem
> > + * @layer: Video layer
> > + *
> > + * Return if the TPG is enabled or not.
> > + *
> > + * Return: true if TPG is on, otherwise false
> > + */
> > +static bool zynqmp_disp_layer_get_tpg(struct zynqmp_disp *disp,
> > +				      struct zynqmp_disp_layer *layer)
> > +{
> > +	return disp->tpg_on;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_get_fmt - Get the supported DRM formats of the layer
> > + * @disp: Display subsystem
> > + * @layer: layer to get the formats
> > + * @drm_fmts: pointer to array of DRM format strings
> > + * @num_fmts: pointer to number of returned DRM formats
> > + *
> > + * Get the supported DRM formats of the given layer.
> > + */
> > +static void zynqmp_disp_layer_get_fmts(struct zynqmp_disp *disp,
> > +				       struct zynqmp_disp_layer *layer,
> > +				       u32 **drm_fmts, unsigned int
> *num_fmts)
> > +{
> > +	*drm_fmts = layer->drm_fmts;
> > +	*num_fmts = layer->num_fmts;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_layer_enable - Enable the layer
> > + * @disp: Display subsystem
> > + * @layer: layer to esable
> > + * @mode: operation mode
> > + *
> > + * Enable the layer @layer.
> > + *
> > + * Return: 0 on success, otherwise error code.
> > + */
> > +static int zynqmp_disp_layer_enable(struct zynqmp_disp *disp,
> > +				    struct zynqmp_disp_layer *layer,
> > +				    enum zynqmp_disp_layer_mode mode)
> > +{
> > +	struct device *dev = disp->dev;
> > +	struct dma_async_tx_descriptor *desc;
> > +	enum dma_ctrl_flags flags;
> > +	unsigned int i;
> > +
> > +	if (layer->enabled && layer->mode != mode) {
> > +		dev_err(dev, "layer is already enabled in different mode\n");
> > +		return -EBUSY;
> > +	}
> > +
> > +	zynqmp_disp_av_buf_enable_vid(&disp->av_buf, layer, mode);
> > +	zynqmp_disp_blend_layer_enable(&disp->blend, layer);
> > +
> > +	layer->enabled = true;
> > +	layer->mode = mode;
> > +
> > +	if (mode == ZYNQMP_DISP_LAYER_LIVE)
> > +		return 0;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++) {
> > +		struct zynqmp_disp_layer_dma *dma = &layer->dma[i];
> > +
> > +		if (dma->chan && dma->is_active) {
> > +			flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
> > +			desc = dmaengine_prep_interleaved_dma(dma-
> >chan,
> > +							      &dma->xt, flags);
> > +			if (!desc) {
> > +				dev_err(dev, "failed to prep DMA
> descriptor\n");
> > +				return -ENOMEM;
> > +			}
> > +
> > +			dmaengine_submit(desc);
> > +			dma_async_issue_pending(dma->chan);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_layer_disable - Disable the layer
> > + * @disp: Display subsystem
> > + * @layer: layer to disable
> > + * @mode: operation mode
> > + *
> > + * Disable the layer @layer.
> > + *
> > + * Return: 0 on success, or -EBUSY if the layer is in different mode.
> > + */
> > +static int zynqmp_disp_layer_disable(struct zynqmp_disp *disp,
> > +				     struct zynqmp_disp_layer *layer,
> > +				     enum zynqmp_disp_layer_mode mode)
> > +{
> > +	struct device *dev = disp->dev;
> > +	unsigned int i;
> > +
> > +	if (layer->mode != mode) {
> > +		dev_err(dev, "the layer is operating in different mode\n");
> > +		return -EBUSY;
> > +	}
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++)
> > +		if (layer->dma[i].chan && layer->dma[i].is_active)
> > +			dmaengine_terminate_sync(layer->dma[i].chan);
> > +
> > +	zynqmp_disp_av_buf_disable_vid(&disp->av_buf, layer);
> > +	zynqmp_disp_blend_layer_disable(&disp->blend, layer);
> > +	layer->enabled = false;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_layer_request_dma - Request DMA channels for a layer
> > + * @disp: Display subsystem
> > + * @layer: layer to request DMA channels
> > + * @name: identifier string for layer type
> > + *
> > + * Request DMA engine channels for corresponding layer.
> > + *
> > + * Return: 0 on success, or err value from
> of_dma_request_slave_channel().
> > + */
> > +static int
> > +zynqmp_disp_layer_request_dma(struct zynqmp_disp *disp,
> > +			      struct zynqmp_disp_layer *layer, const char
> *name)
> > +{
> > +	struct zynqmp_disp_layer_dma *dma;
> > +	unsigned int i;
> > +	int ret;
> > +
> > +	for (i = 0; i < layer->num_chan; i++) {
> > +		char temp[16];
> > +
> > +		dma = &layer->dma[i];
> > +		snprintf(temp, sizeof(temp), "%s%d", name, i);
> > +		dma->chan = of_dma_request_slave_channel(layer-
> >of_node,
> > +							 temp);
> > +		if (IS_ERR(dma->chan)) {
> > +			dev_err(disp->dev, "failed to request dma
> channel\n");
> > +			ret = PTR_ERR(dma->chan);
> > +			dma->chan = NULL;
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_layer_release_dma - Release DMA channels for a layer
> > + * @disp: Display subsystem
> > + * @layer: layer to release DMA channels
> > + *
> > + * Release the dma channels associated with @layer.
> > + */
> > +static void zynqmp_disp_layer_release_dma(struct zynqmp_disp *disp,
> > +					  struct zynqmp_disp_layer *layer)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < layer->num_chan; i++) {
> > +		if (layer->dma[i].chan) {
> > +			/* Make sure the channel is terminated before
> release */
> > +			dmaengine_terminate_all(layer->dma[i].chan);
> > +			dma_release_channel(layer->dma[i].chan);
> > +		}
> > +	}
> > +}
> > +
> > +/**
> > + * zynqmp_disp_layer_is_live - if any layer is live
> > + * @disp: Display subsystem
> > + *
> > + * Return: true if any layer is live
> > + */
> > +static bool zynqmp_disp_layer_is_live(struct zynqmp_disp *disp)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
> > +		if (disp->layers[i].enabled &&
> > +		    disp->layers[i].mode == ZYNQMP_DISP_LAYER_LIVE)
> > +			return true;
> > +	}
> > +
> > +	return false;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_layer_is_enabled - if any layer is enabled
> > + * @disp: Display subsystem
> > + *
> > + * Return: true if any layer is enabled
> > + */
> > +static bool zynqmp_disp_layer_is_enabled(struct zynqmp_disp *disp)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
> > +		if (disp->layers[i].enabled)
> > +			return true;
> > +
> > +	return false;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_layer_destroy - Destroy all layers
> > + * @disp: Display subsystem
> > + *
> > + * Destroy all layers.
> > + */
> > +static void zynqmp_disp_layer_destroy(struct zynqmp_disp *disp)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
> > +		zynqmp_disp_layer_release_dma(disp, &disp->layers[i]);
> > +		if (disp->layers[i].of_node)
> > +			of_node_put(disp->layers[i].of_node);
> > +	}
> > +}
> > +
> > +/**
> > + * zynqmp_disp_layer_create - Create all layers
> > + * @disp: Display subsystem
> > + *
> > + * Create all layers.
> > + *
> > + * Return: 0 on success, otherwise error code from failed function
> > + */
> > +static int zynqmp_disp_layer_create(struct zynqmp_disp *disp)
> > +{
> > +	struct zynqmp_disp_layer *layer;
> > +	unsigned int i;
> > +	int num_chans[ZYNQMP_DISP_NUM_LAYERS] = { 3, 1 };
> > +	const char * const dma_name[] = { "vid", "gfx" };
> > +	int ret;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
> > +		char temp[16];
> > +
> > +		layer = &disp->layers[i];
> > +		layer->id = i;
> > +		layer->offset = i * 4;
> > +		layer->other = &disp->layers[!i];
> > +		layer->num_chan = num_chans[i];
> > +		snprintf(temp, sizeof(temp), "%s-layer", dma_name[i]);
> > +		layer->of_node = of_get_child_by_name(disp->dev-
> >of_node, temp);
> > +		if (!layer->of_node)
> > +			goto err;
> > +		ret = zynqmp_disp_layer_request_dma(disp, layer,
> dma_name[i]);
> > +		if (ret)
> > +			goto err;
> > +		layer->disp = disp;
> > +	}
> > +
> > +	return 0;
> > +
> > +err:
> > +	zynqmp_disp_layer_destroy(disp);
> > +	return ret;
> > +}
> > +
> > +/*
> > + * ZynqMP Display internal functions
> > + */
> > +
> > +/*
> > + * Output format enumeration for DRM property.
> > + * The ID should be aligned with blend_output_fmts.
> > + * The string should be aligned with how zynqmp_dp_set_color()
> decodes.
> > + */
> > +static struct drm_prop_enum_list zynqmp_disp_color_enum[] = {
> > +	{ 0, "rgb" },
> > +	{ 1, "ycrcb444" },
> > +	{ 2, "ycrcb422" },
> > +	{ 3, "yonly" },
> > +};
> > +
> > +/**
> > + * zynqmp_disp_set_output_fmt - Set the output format
> > + * @disp: Display subsystem
> > + * @id: the format ID. Refer to zynqmp_disp_color_enum[].
> > + *
> > + * This function sets the output format of the display / blender as well as
> > + * the format of DP controller. The @id should be aligned with
> > + * zynqmp_disp_color_enum, thus function needs to be used for DRM
> property.
> > + */
> > +static void
> > +zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int
> id)
> > +{
> > +	const struct zynqmp_disp_fmt *fmt = &blend_output_fmts[id];
> > +
> > +	zynqmp_dp_set_color(disp->dpsub->dp,
> zynqmp_disp_color_enum[id].name);
> > +	zynqmp_disp_blend_set_output_fmt(&disp->blend, fmt-
> >disp_fmt);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_set_bg_color - Set the background color
> > + * @disp: Display subsystem
> > + * @c0: color component 0
> > + * @c1: color component 1
> > + * @c2: color component 2
> > + *
> > + * Set the background color with given color components (@c0, @c1,
> @c2).
> > + */
> > +static void zynqmp_disp_set_bg_color(struct zynqmp_disp *disp,
> > +				     u32 c0, u32 c1, u32 c2)
> > +{
> > +	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_set_alpha - Set the alpha value
> > + * @disp: Display subsystem
> > + * @alpha: alpha value to set
> > + *
> > + * Set the alpha value for blending.
> > + */
> > +static void zynqmp_disp_set_alpha(struct zynqmp_disp *disp, u32
> alpha)
> > +{
> > +	disp->alpha = alpha;
> > +	zynqmp_disp_blend_set_alpha(&disp->blend, alpha);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_get_alpha - Get the alpha value
> > + * @disp: Display subsystem
> > + *
> > + * Get the alpha value for blending.
> > + *
> > + * Return: current alpha value.
> > + */
> > +static u32 zynqmp_disp_get_alpha(struct zynqmp_disp *disp)
> > +{
> > +	return disp->alpha;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_set_g_alpha - Enable/disable the global alpha blending
> > + * @disp: Display subsystem
> > + * @enable: flag to enable or disable alpha blending
> > + *
> > + * Set the alpha value for blending.
> > + */
> > +static void zynqmp_disp_set_g_alpha(struct zynqmp_disp *disp, bool
> enable)
> > +{
> > +	disp->alpha_en = enable;
> > +	zynqmp_disp_blend_enable_alpha(&disp->blend, enable);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_get_g_alpha - Get the global alpha status
> > + * @disp: Display subsystem
> > + *
> > + * Get the global alpha statue.
> > + *
> > + * Return: true if global alpha is enabled, or false.
> > + */
> > +static bool zynqmp_disp_get_g_alpha(struct zynqmp_disp *disp)
> > +{
> > +	return disp->alpha_en;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_enable - Enable the Display subsystem
> > + * @disp: Display subsystem
> > + *
> > + * Enable the Display subsystem.
> > + */
> > +static void zynqmp_disp_enable(struct zynqmp_disp *disp)
> > +{
> > +	bool live;
> > +
> > +	if (disp->enabled)
> > +		return;
> > +
> > +	zynqmp_disp_av_buf_enable(&disp->av_buf);
> > +	/* Choose clock source based on the DT clock handle */
> > +	zynqmp_disp_av_buf_set_vid_clock_src(&disp->av_buf, !!disp-
> >_ps_pclk);
> > +	zynqmp_disp_av_buf_set_aud_clock_src(&disp->av_buf, !!disp-
> >_ps_audclk);
> > +	live = zynqmp_disp_layer_is_live(disp);
> > +	zynqmp_disp_av_buf_set_vid_timing_src(&disp->av_buf, !live);
> > +	zynqmp_disp_av_buf_enable_buf(&disp->av_buf);
> > +	zynqmp_disp_av_buf_enable_aud(&disp->av_buf);
> > +	zynqmp_disp_aud_init(&disp->aud);
> > +	disp->enabled = true;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_disable - Disable the Display subsystem
> > + * @disp: Display subsystem
> > + * @force: flag to disable forcefully
> > + *
> > + * Disable the Display subsystem.
> > + */
> > +static void zynqmp_disp_disable(struct zynqmp_disp *disp, bool force)
> > +{
> > +	struct drm_crtc *crtc = &disp->xlnx_crtc.crtc;
> > +
> > +	if (!force && (!disp->enabled ||
> zynqmp_disp_layer_is_enabled(disp)))
> > +		return;
> > +
> > +	zynqmp_disp_aud_deinit(&disp->aud);
> > +	zynqmp_disp_av_buf_disable_aud(&disp->av_buf);
> > +	zynqmp_disp_av_buf_disable_buf(&disp->av_buf);
> > +	zynqmp_disp_av_buf_disable(&disp->av_buf);
> > +
> > +	/* Mark the flip is done as crtc is disabled anyway */
> > +	if (crtc->state->event) {
> > +		complete_all(crtc->state->event->base.completion);
> > +		crtc->state->event = NULL;
> > +	}
> > +
> > +	disp->enabled = false;
> > +}
> > +
> > +/*
> > + * ZynqMP Display external functions for zynqmp_dp
> > + */
> > +
> > +/**
> > + * zynqmp_disp_handle_vblank - Handle the vblank event
> > + * @disp: Display subsystem
> > + *
> > + * This function handles the vblank interrupt, and sends an event to
> > + * CRTC object. This will be called by the DP vblank interrupt handler.
> > + */
> > +void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp)
> > +{
> > +	struct drm_crtc *crtc = &disp->xlnx_crtc.crtc;
> > +	struct drm_device *drm = crtc->dev;
> > +	struct drm_pending_vblank_event *event;
> > +	unsigned long flags;
> > +
> > +	drm_crtc_handle_vblank(crtc);
> > +
> > +	/* Finish page flip */
> > +	spin_lock_irqsave(&drm->event_lock, flags);
> > +	event = disp->event;
> > +	disp->event = NULL;
> > +	if (event) {
> > +		drm_crtc_send_vblank_event(crtc, event);
> > +		drm_crtc_vblank_put(crtc);
> > +	}
> > +	spin_unlock_irqrestore(&drm->event_lock, flags);
> 
> Please take a look at drm_crtc_arm_vblank - that implements the exact
> logic you're open-coding here.
> 

Sure. Will fix it.

> > +}
> > +
> > +/**
> > + * zynqmp_disp_get_apb_clk_rate - Get the current APB clock rate
> > + * @disp: Display subsystem
> > + *
> > + * Return: the current APB clock rate.
> > + */
> > +unsigned int zynqmp_disp_get_apb_clk_rate(struct zynqmp_disp *disp)
> > +{
> > +	return clk_get_rate(disp->aclk);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_aud_enabled - If the audio is enabled
> > + * @disp: Display subsystem
> > + *
> > + * Return if the audio is enabled depending on the audio clock.
> > + *
> > + * Return: true if audio is enabled, or false.
> > + */
> > +bool zynqmp_disp_aud_enabled(struct zynqmp_disp *disp)
> > +{
> > +	return !!disp->audclk;
> > +}
> > +
> > +/**
> > + * zynqmp_disp_get_aud_clk_rate - Get the current audio clock rate
> > + * @disp: Display subsystem
> > + *
> > + * Return: the current audio clock rate.
> > + */
> > +unsigned int zynqmp_disp_get_aud_clk_rate(struct zynqmp_disp *disp)
> > +{
> > +	if (zynqmp_disp_aud_enabled(disp))
> > +		return 0;
> > +	return clk_get_rate(disp->aclk);
> > +}
> > +
> > +/**
> > + * zynqmp_disp_get_crtc_mask - Return the CRTC bit mask
> > + * @disp: Display subsystem
> > + *
> > + * Return: the crtc mask of the zyqnmp_disp CRTC.
> > + */
> > +uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp)
> > +{
> > +	return drm_crtc_mask(&disp->xlnx_crtc.crtc);
> > +}
> > +
> > +/*
> > + * DRM property functions
> > + */
> > +
> > +static void zynqmp_disp_attach_vid_plane_property(struct
> zynqmp_disp *disp,
> > +						  struct drm_mode_object
> *obj)
> > +{
> > +	drm_object_attach_property(obj, disp->tpg_prop, false);
> > +}
> 
> You have a lot of wrappers for core functions (clk_get_rate above is
> similar). This makes your code neater for you, but it makes it harder for
> linux-people to read since they always need to jump around. Please just
> directly call core functions like these instead of wrapping them.

Will remove the unnecessary ones, such as wrappers around drm properties. Some of them are for inter-module calls, so I'll leave those only.

> 
> > +
> > +static void zynqmp_disp_attach_gfx_plane_property(struct
> zynqmp_disp *disp,
> > +						  struct drm_mode_object
> *obj)
> > +{
> > +	drm_object_attach_property(obj, disp->g_alpha_prop,
> > +
> ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX);
> > +	disp->alpha = ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX;
> > +	/* Enable the global alpha as default */
> > +	drm_object_attach_property(obj, disp->g_alpha_en_prop, true);
> > +	disp->alpha_en = true;
> > +}
> > +
> > +static void zynqmp_disp_attach_crtc_property(struct zynqmp_disp
> *disp,
> > +					     struct drm_mode_object *obj)
> > +{
> > +	drm_object_attach_property(obj, disp->color_prop, 0);
> > +	zynqmp_dp_set_color(disp->dpsub->dp,
> zynqmp_disp_color_enum[0].name);
> > +	drm_object_attach_property(obj, disp->bg_c0_prop, 0);
> > +	drm_object_attach_property(obj, disp->bg_c1_prop, 0);
> > +	drm_object_attach_property(obj, disp->bg_c2_prop, 0);
> > +}
> > +
> > +static void zynqmp_disp_create_property(struct zynqmp_disp *disp)
> > +{
> > +	int num;
> > +	u64 max;
> > +
> > +	max = ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_MAX;
> > +	disp->g_alpha_prop = drm_property_create_range(disp->drm, 0,
> "alpha", 0,
> > +						       max);
> > +	disp->g_alpha_en_prop = drm_property_create_bool(disp->drm, 0,
> > +							 "g_alpha_en");
> > +	num = ARRAY_SIZE(zynqmp_disp_color_enum);
> > +	disp->color_prop = drm_property_create_enum(disp->drm, 0,
> > +						    "output_color",
> > +						    zynqmp_disp_color_enum,
> > +						    num);
> > +	max = ZYNQMP_DISP_V_BLEND_BG_MAX;
> > +	disp->bg_c0_prop = drm_property_create_range(disp->drm, 0,
> "bg_c0", 0,
> > +						     max);
> > +	disp->bg_c1_prop = drm_property_create_range(disp->drm, 0,
> "bg_c1", 0,
> > +						     max);
> > +	disp->bg_c2_prop = drm_property_create_range(disp->drm, 0,
> "bg_c2", 0,
> > +						     max);
> > +	disp->tpg_prop = drm_property_create_bool(disp->drm, 0, "tpg");
> > +}
> > +
> > +static void zynqmp_disp_destroy_property(struct zynqmp_disp *disp)
> > +{
> > +	drm_property_destroy(disp->drm, disp->bg_c2_prop);
> > +	drm_property_destroy(disp->drm, disp->bg_c1_prop);
> > +	drm_property_destroy(disp->drm, disp->bg_c0_prop);
> > +	drm_property_destroy(disp->drm, disp->color_prop);
> > +	drm_property_destroy(disp->drm, disp->g_alpha_en_prop);
> > +	drm_property_destroy(disp->drm, disp->g_alpha_prop);
> > +}
> 
> For all the property functions: Please split this out into a separate
> patch (probably even want a separate follow-up patch series). This is new
> uapi, and we have very strict requirements for that:
> 
> https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html#open-source-
> userspace-requirements
> 
> Especially around plane blending properties we already have a huge mess,
> adding more driver-private and nonstandard properties isn't a good idea.
> 
> You can leave all your driver backend implementation in place, just don't
> register the properties and decode them in atomic_get/set_property.

Ok. Will split the property stuff into a separate patch.

> 
> > +
> > +/*
> > + * DRM plane functions
> > + */
> > +
> > +static inline struct zynqmp_disp_layer *plane_to_layer(struct
> drm_plane *plane)
> > +{
> > +	return container_of(plane, struct zynqmp_disp_layer, plane);
> > +}
> > +
> > +static int zynqmp_disp_plane_enable(struct drm_plane *plane)
> > +{
> > +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> > +	struct zynqmp_disp *disp = layer->disp;
> > +	int ret;
> > +
> > +	zynqmp_disp_set_g_alpha(disp, disp->alpha_en);
> > +	zynqmp_disp_set_alpha(disp, disp->alpha);
> > +	ret = zynqmp_disp_layer_enable(layer->disp, layer,
> > +				       ZYNQMP_DISP_LAYER_NONLIVE);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (layer->id == ZYNQMP_DISP_LAYER_GFX && disp->tpg_on) {
> > +		layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
> > +		zynqmp_disp_layer_set_tpg(disp, layer, disp->tpg_on);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int zynqmp_disp_plane_disable(struct drm_plane *plane)
> > +{
> > +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> > +	struct zynqmp_disp *disp = layer->disp;
> > +
> > +	zynqmp_disp_layer_disable(disp, layer,
> ZYNQMP_DISP_LAYER_NONLIVE);
> > +	if (layer->id == ZYNQMP_DISP_LAYER_VID && disp->tpg_on)
> > +		zynqmp_disp_layer_set_tpg(disp, layer, disp->tpg_on);
> > +
> > +	return 0;
> > +}
> > +
> > +static int zynqmp_disp_plane_mode_set(struct drm_plane *plane,
> > +				      struct drm_framebuffer *fb,
> > +				      int crtc_x, int crtc_y,
> > +				      unsigned int crtc_w, unsigned int crtc_h,
> > +				      u32 src_x, u32 src_y,
> > +				      u32 src_w, u32 src_h)
> > +{
> > +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> > +	const struct drm_format_info *info = fb->format;
> > +	struct drm_format_name_buf format_name;
> > +	struct device *dev = layer->disp->dev;
> > +	dma_addr_t paddr;
> > +	size_t offset;
> > +	unsigned int i;
> > +	int ret;
> > +
> > +	if (!info) {
> > +		dev_err(dev, "unsupported framebuffer format %s\n",
> > +			drm_get_format_name(info->format,
> &format_name));
> > +		return -EINVAL;
> > +	}
> > +
> > +	ret = zynqmp_disp_layer_check_size(layer->disp, layer, src_w,
> src_h);
> > +	if (ret)
> > +		return ret;
> > +
> > +	for (i = 0; i < info->num_planes; i++) {
> > +		unsigned int width = src_w / (i ? info->hsub : 1);
> > +		unsigned int height = src_h / (i ? info->vsub : 1);
> > +
> > +		paddr = xlnx_fb_get_paddr(fb, i);
> > +		if (!paddr) {
> > +			dev_err(dev, "failed to get a paddr\n");
> > +			return -EINVAL;
> > +		}
> > +
> > +		layer->dma[i].xt.numf = height;
> > +		layer->dma[i].sgl[0].size = width * info->cpp[i];
> > +		layer->dma[i].sgl[0].icg = fb->pitches[i] -
> > +					   layer->dma[i].sgl[0].size;
> > +		offset = src_x * info->cpp[i] + src_y * fb->pitches[i];
> > +		offset += fb->offsets[i];
> > +		layer->dma[i].xt.src_start = paddr + offset;
> > +		layer->dma[i].xt.frame_size = 1;
> > +		layer->dma[i].xt.dir = DMA_MEM_TO_DEV;
> > +		layer->dma[i].xt.src_sgl = true;
> > +		layer->dma[i].xt.dst_sgl = false;
> > +		layer->dma[i].is_active = true;
> > +	}
> > +
> > +	for (; i < ZYNQMP_DISP_MAX_NUM_SUB_PLANES; i++)
> > +		layer->dma[i].is_active = false;
> > +
> > +	ret = zynqmp_disp_layer_set_fmt(layer->disp,  layer, info->format);
> > +	if (ret)
> > +		dev_err(dev, "failed to set dp_sub layer fmt\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static void zynqmp_disp_plane_destroy(struct drm_plane *plane)
> > +{
> > +	drm_plane_cleanup(plane);
> > +}
> > +
> > +static int
> > +zynqmp_disp_plane_atomic_set_property(struct drm_plane *plane,
> > +				      struct drm_plane_state *state,
> > +				      struct drm_property *property, u64 val)
> > +{
> > +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> > +	struct zynqmp_disp *disp = layer->disp;
> > +	int ret = 0;
> > +
> > +	if (property == disp->g_alpha_prop)
> > +		zynqmp_disp_set_alpha(disp, val);
> > +	else if (property == disp->g_alpha_en_prop)
> > +		zynqmp_disp_set_g_alpha(disp, val);
> > +	else if (property == disp->tpg_prop)
> > +		ret = zynqmp_disp_layer_set_tpg(disp, layer, val);
> > +	else
> > +		return -EINVAL;
> > +
> > +	return ret;
> > +}
> > +
> > +static int
> > +zynqmp_disp_plane_atomic_get_property(struct drm_plane *plane,
> > +				      const struct drm_plane_state *state,
> > +				      struct drm_property *property,
> > +				      uint64_t *val)
> > +{
> > +	struct zynqmp_disp_layer *layer = plane_to_layer(plane);
> > +	struct zynqmp_disp *disp = layer->disp;
> > +	int ret = 0;
> > +
> > +	if (property == disp->g_alpha_prop)
> > +		*val = zynqmp_disp_get_alpha(disp);
> > +	else if (property == disp->g_alpha_en_prop)
> > +		*val = zynqmp_disp_get_g_alpha(disp);
> > +	else if (property == disp->tpg_prop)
> > +		*val = zynqmp_disp_layer_get_tpg(disp, layer);
> > +	else
> > +		return -EINVAL;
> > +
> > +	return ret;
> > +}
> 
> Please also move the above 2 functions into the separate property enabling
> patch series.

Will do.

> 
> > +
> > +static struct drm_plane_funcs zynqmp_disp_plane_funcs = {
> > +	.update_plane		= drm_atomic_helper_update_plane,
> > +	.disable_plane		= drm_atomic_helper_disable_plane,
> > +	.atomic_set_property	= zynqmp_disp_plane_atomic_set_property,
> > +	.atomic_get_property	= zynqmp_disp_plane_atomic_get_property,
> > +	.destroy		= zynqmp_disp_plane_destroy,
> > +	.reset			= drm_atomic_helper_plane_reset,
> > +	.atomic_duplicate_state	=
> drm_atomic_helper_plane_duplicate_state,
> > +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
> > +};
> > +
> > +static int zynqmp_disp_plane_prepare_fb(struct drm_plane *plane,
> > +					struct drm_plane_state *new_state)
> > +{
> > +	return 0;
> > +}
> > +
> > +static void zynqmp_disp_plane_cleanup_fb(struct drm_plane *plane,
> > +					 struct drm_plane_state *old_state)
> > +{
> > +}
> > +
> > +static int zynqmp_disp_plane_atomic_check(struct drm_plane *plane,
> > +					  struct drm_plane_state *state)
> > +{
> > +	return 0;
> > +}
> 
> Please remove the above dummy functions, the helpers should all have
> fallbacks if a callback isn't set. This is general advise (in case I've
> missed a few of them). If you find a place where a callback is mandatory,
> we need to fix the core code.

Sure. Will remove.

> 
> > +
> > +static void
> > +zynqmp_disp_plane_atomic_update(struct drm_plane *plane,
> > +				struct drm_plane_state *old_state)
> > +{
> > +	int ret;
> > +
> > +	if (!plane->state->crtc || !plane->state->fb)
> > +		return;
> > +
> > +	ret = zynqmp_disp_plane_mode_set(plane, plane->state->fb,
> > +					 plane->state->crtc_x,
> > +					 plane->state->crtc_y,
> > +					 plane->state->crtc_w,
> > +					 plane->state->crtc_h,
> > +					 plane->state->src_x >> 16,
> > +					 plane->state->src_y >> 16,
> > +					 plane->state->src_w >> 16,
> > +					 plane->state->src_h >> 16);
> > +	if (ret)
> > +		return;
> > +
> > +	zynqmp_disp_plane_enable(plane);
> > +}
> > +
> > +static void
> > +zynqmp_disp_plane_atomic_disable(struct drm_plane *plane,
> > +				 struct drm_plane_state *old_state)
> > +{
> > +	zynqmp_disp_plane_disable(plane);
> > +}
> > +
> > +static const struct drm_plane_helper_funcs
> zynqmp_disp_plane_helper_funcs = {
> > +	.prepare_fb	= zynqmp_disp_plane_prepare_fb,
> > +	.cleanup_fb	= zynqmp_disp_plane_cleanup_fb,
> > +	.atomic_check	= zynqmp_disp_plane_atomic_check,
> > +	.atomic_update	= zynqmp_disp_plane_atomic_update,
> > +	.atomic_disable	= zynqmp_disp_plane_atomic_disable,
> > +};
> > +
> > +static int zynqmp_disp_create_plane(struct zynqmp_disp *disp)
> > +{
> > +	struct zynqmp_disp_layer *layer;
> > +	unsigned int i;
> > +	u32 *fmts = NULL;
> > +	unsigned int num_fmts = 0;
> > +	enum drm_plane_type type;
> > +	int ret;
> > +
> > +	/* graphics layer is primary, and video layer is overaly */
> > +	type = DRM_PLANE_TYPE_OVERLAY;
> > +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) {
> > +		layer = &disp->layers[i];
> > +		zynqmp_disp_layer_get_fmts(disp, layer, &fmts,
> &num_fmts);
> > +		ret = drm_universal_plane_init(disp->drm, &layer->plane, 0,
> > +					       &zynqmp_disp_plane_funcs,
> fmts,
> > +					       num_fmts, NULL, type, NULL);
> > +		if (ret)
> > +			goto err_plane;
> > +		drm_plane_helper_add(&layer->plane,
> > +				     &zynqmp_disp_plane_helper_funcs);
> > +		type = DRM_PLANE_TYPE_PRIMARY;
> > +	}
> > +
> > +	/* Attach properties to each layers */
> > +	zynqmp_disp_attach_gfx_plane_property(disp, &layer->plane.base);
> > +	layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
> > +	zynqmp_disp_attach_vid_plane_property(disp, &layer->plane.base);
> > +
> > +	return ret;
> > +
> > +err_plane:
> > +	if (i)
> > +		drm_plane_cleanup(&disp->layers[0].plane);
> > +	return ret;
> > +}
> > +
> > +static void zynqmp_disp_destroy_plane(struct zynqmp_disp *disp)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
> > +		zynqmp_disp_plane_destroy(&disp->layers[i].plane);
> > +}
> > +
> > +/*
> > + * Xlnx crtc functions
> > + */
> > +
> > +static inline struct zynqmp_disp *xlnx_crtc_to_disp(struct xlnx_crtc
> *xlnx_crtc)
> > +{
> > +	return container_of(xlnx_crtc, struct zynqmp_disp, xlnx_crtc);
> > +}
> > +
> > +static int zynqmp_disp_enable_vblank(struct xlnx_crtc *xlnx_crtc)
> > +{
> > +	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
> > +
> > +	zynqmp_dp_enable_vblank(disp->dpsub->dp);
> > +	return 0;
> > +}
> > +
> > +static void zynqmp_disp_disable_vblank(struct xlnx_crtc *xlnx_crtc)
> > +{
> > +	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
> > +
> > +	zynqmp_dp_disable_vblank(disp->dpsub->dp);
> > +}
> > +
> > +static int zynqmp_disp_get_max_width(struct xlnx_crtc *xlnx_crtc)
> > +{
> > +	return ZYNQMP_DISP_MAX_WIDTH;
> > +}
> > +
> > +static int zynqmp_disp_get_max_height(struct xlnx_crtc *xlnx_crtc)
> > +{
> > +	return ZYNQMP_DISP_MAX_HEIGHT;
> > +}
> 
> Bikeshed, feel free to ignore: This is a bit much indirection for my
> taste.
> 
> > +
> > +static uint32_t zynqmp_disp_get_format(struct xlnx_crtc *xlnx_crtc)
> > +{
> > +	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
> > +
> > +	return disp->layers[ZYNQMP_DISP_LAYER_GFX].fmt->drm_fmt;
> > +}
> > +
> > +static unsigned int zynqmp_disp_get_align(struct xlnx_crtc *xlnx_crtc)
> > +{
> > +	struct zynqmp_disp *disp = xlnx_crtc_to_disp(xlnx_crtc);
> > +	struct zynqmp_disp_layer *layer = &disp-
> >layers[ZYNQMP_DISP_LAYER_VID];
> > +
> > +	return 1 << layer->dma->chan->device->copy_align;
> > +}
> > +
> > +static u64 zynqmp_disp_get_dma_mask(struct xlnx_crtc *xlnx_crtc)
> > +{
> > +	return DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT);
> > +}
> > +
> > +/*
> > + * DRM crtc functions
> > + */
> > +
> > +static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc)
> > +{
> > +	struct xlnx_crtc *xlnx_crtc = to_xlnx_crtc(crtc);
> > +
> > +	return xlnx_crtc_to_disp(xlnx_crtc);
> > +}
> > +
> > +static int zynqmp_disp_crtc_mode_set(struct drm_crtc *crtc,
> > +				     struct drm_display_mode *mode,
> > +				     struct drm_display_mode
> *adjusted_mode,
> > +				     int x, int y,
> > +				     struct drm_framebuffer *old_fb)
> > +{
> > +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> > +	unsigned long rate;
> > +	long diff;
> > +	int ret;
> > +
> > +	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
> > +	ret = clk_set_rate(disp->pclk, adjusted_mode->clock * 1000);
> > +	if (ret) {
> > +		dev_err(disp->dev, "failed to set a pixel clock\n");
> > +		return ret;
> > +	}
> > +
> > +	rate = clk_get_rate(disp->pclk);
> > +	diff = rate - adjusted_mode->clock * 1000;
> > +	if (abs(diff) > (adjusted_mode->clock * 1000) / 20) {
> > +		dev_info(disp->dev, "request pixel rate: %d actual
> rate: %lu\n",
> > +			 adjusted_mode->clock, rate);
> > +	} else {
> > +		dev_dbg(disp->dev, "request pixel rate: %d actual
> rate: %lu\n",
> > +			adjusted_mode->clock, rate);
> > +	}
> > +
> > +	/* The timing register should be programmed always */
> > +	zynqmp_dp_encoder_mode_set_stream(disp->dpsub->dp,
> adjusted_mode);
> > +
> > +	return 0;
> > +}
> > +
> > +static void
> > +zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc,
> > +			       struct drm_crtc_state *old_crtc_state)
> > +{
> > +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> > +	struct drm_display_mode *adjusted_mode = &crtc->state-
> >adjusted_mode;
> > +	int ret, vrefresh;
> > +
> > +	zynqmp_disp_crtc_mode_set(crtc, &crtc->state->mode,
> > +				  adjusted_mode, crtc->x, crtc->y, NULL);
> > +
> > +	pm_runtime_get_sync(disp->dev);
> > +	ret = zynqmp_disp_clk_enable(disp->pclk, &disp->pclk_en);
> > +	if (ret) {
> > +		dev_err(disp->dev, "failed to enable a pixel clock\n");
> > +		return;
> > +	}
> > +	zynqmp_disp_set_output_fmt(disp, disp->color);
> > +	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp-
> >bg_c2);
> > +	zynqmp_disp_enable(disp);
> > +	/* Delay of 3 vblank intervals for timing gen to be stable */
> > +	vrefresh = (adjusted_mode->clock * 1000) /
> > +		   (adjusted_mode->vtotal * adjusted_mode->htotal);
> > +	msleep(3 * 1000 / vrefresh);
> > +}
> > +
> > +static void
> > +zynqmp_disp_crtc_atomic_disable(struct drm_crtc *crtc,
> > +				struct drm_crtc_state *old_crtc_state)
> > +{
> > +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> > +
> > +	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
> > +	zynqmp_disp_plane_disable(crtc->primary);
> > +	zynqmp_disp_disable(disp, true);
> > +	pm_runtime_put_sync(disp->dev);
> > +}
> > +
> > +static void zynqmp_disp_crtc_mode_set_nofb(struct drm_crtc *crtc)
> > +{
> > +}
> 
> Again please remove the unecessary dumy functions.
> 

Will do.

> > +
> > +static int zynqmp_disp_crtc_atomic_check(struct drm_crtc *crtc,
> > +					 struct drm_crtc_state *state)
> > +{
> > +	return drm_atomic_add_affected_planes(state->state, crtc);
> > +}
> > +
> > +static void
> > +zynqmp_disp_crtc_atomic_begin(struct drm_crtc *crtc,
> > +			      struct drm_crtc_state *old_crtc_state)
> > +{
> > +	/* Don't rely on vblank when disabling crtc */
> > +	if (crtc->primary->state->fb && crtc->state->event) {
> > +		struct zynqmp_disp *disp = crtc_to_disp(crtc);
> > +
> > +		/* Consume the flip_done event from atomic helper */
> > +		crtc->state->event->pipe = drm_crtc_index(crtc);
> > +		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
> > +		disp->event = crtc->state->event;
> > +		crtc->state->event = NULL;
> > +	}
> > +}
> > +
> > +static struct drm_crtc_helper_funcs zynqmp_disp_crtc_helper_funcs = {
> > +	.atomic_enable	= zynqmp_disp_crtc_atomic_enable,
> > +	.atomic_disable	= zynqmp_disp_crtc_atomic_disable,
> > +	.mode_set_nofb	= zynqmp_disp_crtc_mode_set_nofb,
> > +	.atomic_check	= zynqmp_disp_crtc_atomic_check,
> > +	.atomic_begin	= zynqmp_disp_crtc_atomic_begin,
> > +};
> > +
> > +static void zynqmp_disp_crtc_destroy(struct drm_crtc *crtc)
> > +{
> > +	zynqmp_disp_crtc_atomic_disable(crtc, NULL);
> > +	drm_crtc_cleanup(crtc);
> > +}
> > +
> > +static int
> > +zynqmp_disp_crtc_atomic_set_property(struct drm_crtc *crtc,
> > +				     struct drm_crtc_state *state,
> > +				     struct drm_property *property,
> > +				     uint64_t val)
> > +{
> > +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> > +
> > +	/*
> > +	 * CRTC prop values are just stored here and applied when CRTC
> gets
> > +	 * enabled
> > +	 */
> > +	if (property == disp->color_prop)
> > +		disp->color = val;
> > +	else if (property == disp->bg_c0_prop)
> > +		disp->bg_c0 = val;
> > +	else if (property == disp->bg_c1_prop)
> > +		disp->bg_c1 = val;
> > +	else if (property == disp->bg_c2_prop)
> > +		disp->bg_c2 = val;
> > +	else
> > +		return -EINVAL;
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +zynqmp_disp_crtc_atomic_get_property(struct drm_crtc *crtc,
> > +				     const struct drm_crtc_state *state,
> > +				     struct drm_property *property,
> > +				     uint64_t *val)
> > +{
> > +	struct zynqmp_disp *disp = crtc_to_disp(crtc);
> > +
> > +	if (property == disp->color_prop)
> > +		*val = disp->color;
> > +	else if (property == disp->bg_c0_prop)
> > +		*val = disp->bg_c0;
> > +	else if (property == disp->bg_c1_prop)
> > +		*val = disp->bg_c1;
> > +	else if (property == disp->bg_c2_prop)
> > +		*val = disp->bg_c2;
> > +	else
> > +		return -EINVAL;
> > +
> > +	return 0;
> > +}
> 
> Again property enabling into a separate patch series pls.
> 

Sure.

Thanks,
-hyun

> > +
> > +static struct drm_crtc_funcs zynqmp_disp_crtc_funcs = {
> > +	.destroy		= zynqmp_disp_crtc_destroy,
> > +	.set_config		= drm_atomic_helper_set_config,
> > +	.page_flip		= drm_atomic_helper_page_flip,
> > +	.atomic_set_property	= zynqmp_disp_crtc_atomic_set_property,
> > +	.atomic_get_property	= zynqmp_disp_crtc_atomic_get_property,
> > +	.reset			= drm_atomic_helper_crtc_reset,
> > +	.atomic_duplicate_state	=
> drm_atomic_helper_crtc_duplicate_state,
> > +	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
> > +};
> > +
> > +static void zynqmp_disp_create_crtc(struct zynqmp_disp *disp)
> > +{
> > +	struct drm_plane *plane = &disp-
> >layers[ZYNQMP_DISP_LAYER_GFX].plane;
> > +	int ret;
> > +
> > +	ret = drm_crtc_init_with_planes(disp->drm, &disp->xlnx_crtc.crtc,
> plane,
> > +					NULL, &zynqmp_disp_crtc_funcs,
> NULL);
> > +	drm_crtc_helper_add(&disp->xlnx_crtc.crtc,
> > +			    &zynqmp_disp_crtc_helper_funcs);
> > +	zynqmp_disp_attach_crtc_property(disp, &disp-
> >xlnx_crtc.crtc.base);
> > +
> > +	disp->xlnx_crtc.enable_vblank = &zynqmp_disp_enable_vblank;
> > +	disp->xlnx_crtc.disable_vblank = &zynqmp_disp_disable_vblank;
> > +	disp->xlnx_crtc.get_max_width = &zynqmp_disp_get_max_width;
> > +	disp->xlnx_crtc.get_max_height = &zynqmp_disp_get_max_height;
> > +	disp->xlnx_crtc.get_format = &zynqmp_disp_get_format;
> > +	disp->xlnx_crtc.get_align = &zynqmp_disp_get_align;
> > +	disp->xlnx_crtc.get_dma_mask = &zynqmp_disp_get_dma_mask;
> > +	xlnx_crtc_register(disp->drm, &disp->xlnx_crtc);
> > +}
> > +
> > +static void zynqmp_disp_destroy_crtc(struct zynqmp_disp *disp)
> > +{
> > +	xlnx_crtc_unregister(disp->drm, &disp->xlnx_crtc);
> > +	zynqmp_disp_crtc_destroy(&disp->xlnx_crtc.crtc);
> > +}
> > +
> > +static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp)
> > +{
> > +	u32 possible_crtcs = drm_crtc_mask(&disp->xlnx_crtc.crtc);
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++)
> > +		disp->layers[i].plane.possible_crtcs = possible_crtcs;
> > +}
> > +
> > +/*
> > + * Component functions
> > + */
> > +
> > +int zynqmp_disp_bind(struct device *dev, struct device *master, void
> *data)
> > +{
> > +	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
> > +	struct zynqmp_disp *disp = dpsub->disp;
> > +	struct drm_device *drm = data;
> > +	int ret;
> > +
> > +	disp->drm = drm;
> > +	zynqmp_disp_create_property(disp);
> > +	ret = zynqmp_disp_create_plane(disp);
> > +	if (ret)
> > +		return ret;
> > +	zynqmp_disp_create_crtc(disp);
> > +	zynqmp_disp_map_crtc_to_plane(disp);
> > +
> > +	return 0;
> > +}
> > +
> > +void zynqmp_disp_unbind(struct device *dev, struct device *master,
> void *data)
> > +{
> > +	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
> > +	struct zynqmp_disp *disp = dpsub->disp;
> > +
> > +	zynqmp_disp_destroy_crtc(disp);
> > +	zynqmp_disp_destroy_plane(disp);
> > +	zynqmp_disp_destroy_property(disp);
> > +}
> > +
> > +/*
> > + * Platform initialization functions
> > + */
> > +
> > +static int zynqmp_disp_enumerate_fmts(struct zynqmp_disp *disp)
> > +{
> > +	struct zynqmp_disp_layer *layer;
> > +	u32 *bus_fmts;
> > +	u32 i, size, num_bus_fmts;
> > +
> > +	num_bus_fmts = ARRAY_SIZE(av_buf_live_fmts);
> > +	bus_fmts = devm_kzalloc(disp->dev, sizeof(*bus_fmts) *
> num_bus_fmts,
> > +				GFP_KERNEL);
> > +	if (!bus_fmts)
> > +		return -ENOMEM;
> > +	for (i = 0; i < num_bus_fmts; i++)
> > +		bus_fmts[i] = av_buf_live_fmts[i].bus_fmt;
> > +
> > +	layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
> > +	layer->num_bus_fmts = num_bus_fmts;
> > +	layer->bus_fmts = bus_fmts;
> > +	size = ARRAY_SIZE(av_buf_vid_fmts);
> > +	layer->num_fmts = size;
> > +	layer->drm_fmts = devm_kzalloc(disp->dev,
> > +				       sizeof(*layer->drm_fmts) * size,
> > +				       GFP_KERNEL);
> > +	if (!layer->drm_fmts)
> > +		return -ENOMEM;
> > +	for (i = 0; i < layer->num_fmts; i++)
> > +		layer->drm_fmts[i] = av_buf_vid_fmts[i].drm_fmt;
> > +	layer->fmt =
> &av_buf_vid_fmts[ZYNQMP_DISP_AV_BUF_VID_FMT_YUYV];
> > +
> > +	layer = &disp->layers[ZYNQMP_DISP_LAYER_GFX];
> > +	layer->num_bus_fmts = num_bus_fmts;
> > +	layer->bus_fmts = bus_fmts;
> > +	size = ARRAY_SIZE(av_buf_gfx_fmts);
> > +	layer->num_fmts = size;
> > +	layer->drm_fmts = devm_kzalloc(disp->dev,
> > +				       sizeof(*layer->drm_fmts) * size,
> > +				       GFP_KERNEL);
> > +	if (!layer->drm_fmts)
> > +		return -ENOMEM;
> > +
> > +	for (i = 0; i < layer->num_fmts; i++)
> > +		layer->drm_fmts[i] = av_buf_gfx_fmts[i].drm_fmt;
> > +	layer->fmt =
> &av_buf_gfx_fmts[ZYNQMP_DISP_AV_BUF_GFX_FMT_RGB565];
> > +
> > +	return 0;
> > +}
> > +
> > +int zynqmp_disp_probe(struct platform_device *pdev)
> > +{
> > +	struct zynqmp_dpsub *dpsub;
> > +	struct zynqmp_disp *disp;
> > +	struct resource *res;
> > +	int ret;
> > +
> > +	disp = devm_kzalloc(&pdev->dev, sizeof(*disp), GFP_KERNEL);
> > +	if (!disp)
> > +		return -ENOMEM;
> > +	disp->dev = &pdev->dev;
> > +
> > +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> "blend");
> > +	disp->blend.base = devm_ioremap_resource(&pdev->dev, res);
> > +	if (IS_ERR(disp->blend.base))
> > +		return PTR_ERR(disp->blend.base);
> > +
> > +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> "av_buf");
> > +	disp->av_buf.base = devm_ioremap_resource(&pdev->dev, res);
> > +	if (IS_ERR(disp->av_buf.base))
> > +		return PTR_ERR(disp->av_buf.base);
> > +
> > +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> "aud");
> > +	disp->aud.base = devm_ioremap_resource(&pdev->dev, res);
> > +	if (IS_ERR(disp->aud.base))
> > +		return PTR_ERR(disp->aud.base);
> > +
> > +	dpsub = platform_get_drvdata(pdev);
> > +	dpsub->disp = disp;
> > +	disp->dpsub = dpsub;
> > +
> > +	ret = zynqmp_disp_enumerate_fmts(disp);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* Try the live PL video clock */
> > +	disp->_pl_pclk = devm_clk_get(disp->dev, "dp_live_video_in_clk");
> > +	if (!IS_ERR(disp->_pl_pclk)) {
> > +		disp->pclk = disp->_pl_pclk;
> > +		ret = zynqmp_disp_clk_enable_disable(disp->pclk,
> > +						     &disp->pclk_en);
> > +		if (ret)
> > +			disp->pclk = NULL;
> > +	} else if (PTR_ERR(disp->_pl_pclk) == -EPROBE_DEFER) {
> > +		return PTR_ERR(disp->_pl_pclk);
> > +	}
> > +
> > +	/* If the live PL video clock is not valid, fall back to PS clock */
> > +	if (!disp->pclk) {
> > +		disp->_ps_pclk = devm_clk_get(disp->dev,
> "dp_vtc_pixel_clk_in");
> > +		if (IS_ERR(disp->_ps_pclk)) {
> > +			dev_err(disp->dev, "failed to init any video clock\n");
> > +			return PTR_ERR(disp->_ps_pclk);
> > +		}
> > +		disp->pclk = disp->_ps_pclk;
> > +		ret = zynqmp_disp_clk_enable_disable(disp->pclk,
> > +						     &disp->pclk_en);
> > +		if (ret) {
> > +			dev_err(disp->dev, "failed to init any video clock\n");
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	disp->aclk = devm_clk_get(disp->dev, "dp_apb_clk");
> > +	if (IS_ERR(disp->aclk))
> > +		return PTR_ERR(disp->aclk);
> > +	ret = zynqmp_disp_clk_enable(disp->aclk, &disp->aclk_en);
> > +	if (ret) {
> > +		dev_err(disp->dev, "failed to enable the APB clk\n");
> > +		return ret;
> > +	}
> > +
> > +	/* Try the live PL audio clock */
> > +	disp->_pl_audclk = devm_clk_get(disp->dev, "dp_live_audio_aclk");
> > +	if (!IS_ERR(disp->_pl_audclk)) {
> > +		disp->audclk = disp->_pl_audclk;
> > +		ret = zynqmp_disp_clk_enable_disable(disp->audclk,
> > +						     &disp->audclk_en);
> > +		if (ret)
> > +			disp->audclk = NULL;
> > +	}
> > +
> > +	/* If the live PL audio clock is not valid, fall back to PS clock */
> > +	if (!disp->audclk) {
> > +		disp->_ps_audclk = devm_clk_get(disp->dev, "dp_aud_clk");
> > +		if (!IS_ERR(disp->_ps_audclk)) {
> > +			disp->audclk = disp->_ps_audclk;
> > +			ret = zynqmp_disp_clk_enable_disable(disp->audclk,
> > +							     &disp->audclk_en);
> > +			if (ret)
> > +				disp->audclk = NULL;
> > +		}
> > +
> > +		if (!disp->audclk) {
> > +			dev_err(disp->dev,
> > +				"audio is disabled due to clock failure\n");
> > +		}
> > +	}
> > +
> > +	ret = zynqmp_disp_layer_create(disp);
> > +	if (ret)
> > +		goto error_aclk;
> > +
> > +	return 0;
> > +
> > +error_aclk:
> > +	zynqmp_disp_clk_disable(disp->aclk, &disp->aclk_en);
> > +	return ret;
> > +}
> > +
> > +int zynqmp_disp_remove(struct platform_device *pdev)
> > +{
> > +	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> > +	struct zynqmp_disp *disp = dpsub->disp;
> > +
> > +	zynqmp_disp_layer_destroy(disp);
> > +	if (disp->audclk)
> > +		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
> > +	zynqmp_disp_clk_disable(disp->aclk, &disp->aclk_en);
> > +	zynqmp_disp_clk_disable(disp->pclk, &disp->pclk_en);
> > +	dpsub->disp = NULL;
> > +
> > +	return 0;
> > +}
> > diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h
> b/drivers/gpu/drm/xlnx/zynqmp_disp.h
> > new file mode 100644
> > index 0000000..0291fc2
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h
> > @@ -0,0 +1,28 @@
> > +/*
> > + * ZynqMP Display Driver
> > + *
> > + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> > + *
> > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0
> > + */
> > +
> > +#ifndef _ZYNQMP_DISP_H_
> > +#define _ZYNQMP_DISP_H_
> > +
> > +struct zynqmp_disp;
> > +
> > +void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp);
> > +unsigned int zynqmp_disp_get_apb_clk_rate(struct zynqmp_disp *disp);
> > +bool zynqmp_disp_aud_enabled(struct zynqmp_disp *disp);
> > +unsigned int zynqmp_disp_get_aud_clk_rate(struct zynqmp_disp *disp);
> > +uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp);
> > +
> > +int zynqmp_disp_bind(struct device *dev, struct device *master, void
> *data);
> > +void zynqmp_disp_unbind(struct device *dev, struct device *master,
> void *data);
> > +
> > +int zynqmp_disp_probe(struct platform_device *pdev);
> > +int zynqmp_disp_remove(struct platform_device *pdev);
> > +
> > +#endif /* _ZYNQMP_DISP_H_ */
> > --
> > 2.7.4
> >
> > _______________________________________________
> > dri-devel mailing list
> > dri-devel@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 05/10] drm: xlnx: Xilinx DRM KMS driver
  2018-01-09  9:51       ` Daniel Vetter
@ 2018-01-11  2:05         ` Hyun Kwon
  0 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11  2:05 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: devicetree, Michal Simek, dri-devel

Hi Daniel,

Thanks for the review.

> -----Original Message-----
> From: Daniel Vetter [mailto:daniel.vetter@ffwll.ch] On Behalf Of Daniel
> Vetter
> Sent: Tuesday, January 09, 2018 1:51 AM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> Simek <michal.simek@xilinx.com>
> Subject: Re: [PATCH 05/10] drm: xlnx: Xilinx DRM KMS driver
> 
> On Thu, Jan 04, 2018 at 06:05:54PM -0800, Hyun Kwon wrote:
> > Xilinx has various platforms for display, where users can create
> > using multiple IPs in the programmable FPGA fabric, or where
> > some hardened piepline is available on the chip. Furthermore,
> > hardened pipeline can also interact with soft logics in FPGA.
> >
> > The Xilinx DRM KMS is a softwrae layer to glue subdevice drivers
> > with DRM core and integrate multiple subdevice drivers together.
> >
> > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> > ---
> >  MAINTAINERS                      |   8 +
> >  drivers/gpu/drm/Kconfig          |   2 +
> >  drivers/gpu/drm/Makefile         |   1 +
> >  drivers/gpu/drm/xlnx/Kconfig     |  12 ++
> >  drivers/gpu/drm/xlnx/Makefile    |   2 +
> >  drivers/gpu/drm/xlnx/xlnx_crtc.c |   1 +
> >  drivers/gpu/drm/xlnx/xlnx_drv.c  | 436
> +++++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/xlnx/xlnx_drv.h  |  22 ++
> >  drivers/gpu/drm/xlnx/xlnx_fb.c   |   1 +
> >  drivers/gpu/drm/xlnx/xlnx_gem.c  |   1 +
> >  10 files changed, 486 insertions(+)
> >  create mode 100644 drivers/gpu/drm/xlnx/Kconfig
> >  create mode 100644 drivers/gpu/drm/xlnx/Makefile
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.c
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index d4b1635..101a3e6 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -4785,6 +4785,14 @@ F:	drivers/gpu/drm/etnaviv/
> >  F:	include/uapi/drm/etnaviv_drm.h
> >  F:	Documentation/devicetree/bindings/display/etnaviv/
> >
> > +DRM DRIVERS FOR XILINX
> > +M:	Hyun Kwon <hyun.kwon@xilinx.com>
> > +L:	dri-devel@lists.freedesktop.org
> > +S:	Maintained
> > +F:	drivers/gpu/drm/xlnx/
> > +F:	Documentation/devicetree/bindings/display/xlnx/
> > +T:	git git://anongit.freedesktop.org/drm/drm-misc
> > +
> >  DRM DRIVERS FOR ZTE ZX
> >  M:	Shawn Guo <shawnguo@kernel.org>
> >  L:	dri-devel@lists.freedesktop.org
> > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> > index 0bc3744..82b7fc3 100644
> > --- a/drivers/gpu/drm/Kconfig
> > +++ b/drivers/gpu/drm/Kconfig
> > @@ -293,6 +293,8 @@ source "drivers/gpu/drm/pl111/Kconfig"
> >
> >  source "drivers/gpu/drm/tve200/Kconfig"
> >
> > +source "drivers/gpu/drm/xlnx/Kconfig"
> > +
> >  # Keep legacy drivers last
> >
> >  menuconfig DRM_LEGACY
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index dd5ae67..72ee1a1 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
> >  obj-$(CONFIG_DRM_PL111) += pl111/
> >  obj-$(CONFIG_DRM_TVE200) += tve200/
> >  obj-$(CONFIG_DRM_SCHED)	+= scheduler/
> > +obj-$(CONFIG_DRM_XLNX)	+= xlnx/
> > diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
> > new file mode 100644
> > index 0000000..19fd7cd
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/Kconfig
> > @@ -0,0 +1,12 @@
> > +config DRM_XLNX
> > +	tristate "Xilinx DRM KMS Driver"
> > +	depends on DRM && OF
> > +	select DRM_KMS_HELPER
> > +	select DRM_KMS_CMA_HELPER
> > +	select DRM_GEM_CMA_HELPER
> > +	help
> > +	  Xilinx DRM KMS driver. Choose this option if you have
> > +	  a Xilinx SoCs with hardened display pipeline or soft
> > +	  display pipeline using Xilinx IPs in FPGA. This module
> > +	  provides the kernel mode setting functionalities
> > +	  for Xilinx display drivers.
> > diff --git a/drivers/gpu/drm/xlnx/Makefile
> b/drivers/gpu/drm/xlnx/Makefile
> > new file mode 100644
> > index 0000000..c60a281
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/Makefile
> > @@ -0,0 +1,2 @@
> > +xlnx_drm-objs += xlnx_crtc.o xlnx_drv.o xlnx_fb.o xlnx_gem.o
> > +obj-$(CONFIG_DRM_XLNX) += xlnx_drm.o
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.c
> b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> > index 57ee939..8387e1e 100644
> > --- a/drivers/gpu/drm/xlnx/xlnx_crtc.c
> > +++ b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> > @@ -13,6 +13,7 @@
> >  #include <linux/list.h>
> >
> >  #include "xlnx_crtc.h"
> > +#include "xlnx_drv.h"
> >
> >  /*
> >   * Overview
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_drv.c
> b/drivers/gpu/drm/xlnx/xlnx_drv.c
> > new file mode 100644
> > index 0000000..273420b
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/xlnx_drv.c
> > @@ -0,0 +1,436 @@
> > +/*
> > + * Xilinx DRM KMS Driver
> > + *
> > + *  Copyright (C) 2013 - 2018 Xilinx, Inc.
> > + *
> > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0
> > + */
> > +
> > +#include <drm/drmP.h>
> > +#include <drm/drm_atomic.h>
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_gem_cma_helper.h>
> > +#include <drm/drm_of.h>
> > +
> > +#include <linux/component.h>
> > +#include <linux/device.h>
> > +#include <linux/dma-buf.h>
> > +#include <linux/module.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/reservation.h>
> > +
> > +#include "xlnx_crtc.h"
> > +#include "xlnx_fb.h"
> > +#include "xlnx_gem.h"
> > +
> > +#define DRIVER_NAME	"xlnx"
> > +#define DRIVER_DESC	"Xilinx DRM KMS Driver"
> > +#define DRIVER_DATE	"20130509"
> > +#define DRIVER_MAJOR	1
> > +#define DRIVER_MINOR	0
> > +
> > +static uint xlnx_fbdev_vres = 2;
> > +module_param_named(fbdev_vres, xlnx_fbdev_vres, uint, 0444);
> > +MODULE_PARM_DESC(fbdev_vres,
> > +		 "fbdev virtual resolution multiplier for fb (default: 2)");
> > +
> > +/**
> > + * struct xlnx_drm - Xilinx DRM private data
> > + * @drm: DRM core
> > + * @crtc: Xilinx DRM CRTC helper
> > + * @fb: DRM fb helper
> > + * @pdev: platform device
> > + * @suspend_state: atomic state for suspend / resume
> > + */
> > +struct xlnx_drm {
> > +	struct drm_device *drm;
> > +	struct xlnx_crtc_helper *crtc;
> > +	struct drm_fb_helper *fb;
> > +	struct platform_device *pdev;
> > +	struct drm_atomic_state *suspend_state;
> > +};
> > +
> > +/**
> > + * xlnx_get_crtc_helper - Return the crtc helper instance
> > + * @drm: DRM device
> > + *
> > + * Return: the crtc helper instance
> > + */
> > +struct xlnx_crtc_helper *xlnx_get_crtc_helper(struct drm_device *drm)
> > +{
> > +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> > +
> > +	return xlnx_drm->crtc;
> > +}
> > +
> > +/**
> > + * xlnx_get_align - Return the align requirement through CRTC helper
> > + * @drm: DRM device
> > + *
> > + * Return: the alignment requirement
> > + */
> > +unsigned int xlnx_get_align(struct drm_device *drm)
> > +{
> > +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> > +
> > +	return xlnx_crtc_helper_get_align(xlnx_drm->crtc);
> > +}
> > +
> > +/**
> > + * xlnx_get_format - Return the current format of CRTC
> > + * @drm: DRM device
> > + *
> > + * Return: the current CRTC format
> > + */
> > +uint32_t xlnx_get_format(struct drm_device *drm)
> > +{
> > +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> > +
> > +	return xlnx_crtc_helper_get_format(xlnx_drm->crtc);
> > +}
> > +
> > +static void xlnx_output_poll_changed(struct drm_device *drm)
> > +{
> > +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> > +
> > +	xlnx_fb_hotplug_event(xlnx_drm->fb);
> > +}
> > +
> > +static const struct drm_mode_config_funcs xlnx_mode_config_funcs = {
> > +	.fb_create		= xlnx_fb_create,
> > +	.output_poll_changed	= xlnx_output_poll_changed,
> > +	.atomic_check		= drm_atomic_helper_check,
> > +	.atomic_commit		= drm_atomic_helper_commit,
> > +};
> > +
> > +static struct drm_mode_config_helper_funcs
> xlnx_mode_config_helper_funcs = {
> > +	.atomic_commit_tail = drm_atomic_helper_commit_tail,
> 
> Hm, I thought this was optional and it should be the default commit_tail
> callback. Please remove (and if that doesn't work, pls type a core patch
> to make it the default).
> 

You are correct. Will remove.

> > +};
> > +
> > +static int xlnx_enable_vblank(struct drm_device *drm, unsigned int crtc)
> > +{
> > +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> > +
> > +	return xlnx_crtc_helper_enable_vblank(xlnx_drm->crtc, crtc);
> > +}
> > +
> > +static void xlnx_disable_vblank(struct drm_device *drm, unsigned int
> crtc)
> > +{
> > +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> > +
> > +	xlnx_crtc_helper_disable_vblank(xlnx_drm->crtc, crtc);
> > +}
> 
> This is a bit backwards since you introduce the crtc helper only in a
> later patch. Good reason to move the vblank stuff in there, and maybe even
> fold the crtc helpers into the main kms patch :-)

The crtc patch is 2/10. The mail delivery seems mixed up when going through company mail server. As replied, I was thinking to leave in a separate patch, but now I changed my mind. I'll merge them together.

> -Daniel
> 
> > +
> > +static void xlnx_mode_config_init(struct drm_device *drm)
> > +{
> > +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> > +	struct xlnx_crtc_helper *crtc = xlnx_drm->crtc;
> > +
> > +	drm->mode_config.min_width = 0;
> > +	drm->mode_config.min_height = 0;
> > +	drm->mode_config.max_width =
> xlnx_crtc_helper_get_max_width(crtc);
> > +	drm->mode_config.max_height =
> xlnx_crtc_helper_get_max_height(crtc);
> > +}
> > +
> > +static void xlnx_lastclose(struct drm_device *drm)
> > +{
> > +	struct xlnx_drm *xlnx_drm = drm->dev_private;
> > +
> > +	xlnx_fb_restore_mode(xlnx_drm->fb);
> > +}
> > +
> > +static const struct file_operations xlnx_fops = {
> > +	.owner		= THIS_MODULE,
> > +	.open		= drm_open,
> > +	.release	= drm_release,
> > +	.unlocked_ioctl	= drm_ioctl,
> > +	.mmap		= drm_gem_cma_mmap,
> > +	.poll		= drm_poll,
> > +	.read		= drm_read,
> > +#ifdef CONFIG_COMPAT
> > +	.compat_ioctl	= drm_compat_ioctl,
> > +#endif
> > +	.llseek		= noop_llseek,
> > +};
> > +
> > +static struct drm_driver xlnx_drm_driver = {
> > +	.driver_features		= DRIVER_MODESET | DRIVER_GEM
> |
> > +					  DRIVER_ATOMIC | DRIVER_PRIME,
> > +	.lastclose			= xlnx_lastclose,
> > +
> > +	.enable_vblank			= xlnx_enable_vblank,
> > +	.disable_vblank			= xlnx_disable_vblank,
> 
> Please use the new callbacks in drm_crtc_ops instead, these here are
> deprecated.
> 
> > +
> > +	.prime_handle_to_fd		= drm_gem_prime_handle_to_fd,
> > +	.prime_fd_to_handle		= drm_gem_prime_fd_to_handle,
> > +	.gem_prime_export		= drm_gem_prime_export,
> > +	.gem_prime_import		= drm_gem_prime_import,
> > +	.gem_prime_get_sg_table		=
> drm_gem_cma_prime_get_sg_table,
> > +	.gem_prime_import_sg_table	=
> drm_gem_cma_prime_import_sg_table,
> > +	.gem_prime_vmap			=
> drm_gem_cma_prime_vmap,
> > +	.gem_prime_vunmap		= drm_gem_cma_prime_vunmap,
> > +	.gem_prime_mmap			=
> drm_gem_cma_prime_mmap,
> > +	.gem_free_object		= drm_gem_cma_free_object,
> > +	.gem_vm_ops			= &drm_gem_cma_vm_ops,
> > +	.dumb_create			= xlnx_gem_cma_dumb_create,
> > +	.dumb_destroy			= drm_gem_dumb_destroy,
> > +
> > +	.fops				= &xlnx_fops,
> > +
> > +	.name				= DRIVER_NAME,
> > +	.desc				= DRIVER_DESC,
> > +	.date				= DRIVER_DATE,
> > +	.major				= DRIVER_MAJOR,
> > +	.minor				= DRIVER_MINOR,
> > +};
> > +
> > +static int xlnx_bind(struct device *dev)
> > +{
> > +	struct xlnx_drm *xlnx_drm;
> > +	struct drm_device *drm;
> > +	const struct drm_format_info *info;
> > +	struct platform_device *pdev = to_platform_device(dev);
> > +	int ret;
> > +	u32 format;
> > +
> > +	drm = drm_dev_alloc(&xlnx_drm_driver, &pdev->dev);
> > +	if (IS_ERR(drm))
> > +		return PTR_ERR(drm);
> > +
> > +	xlnx_drm = devm_kzalloc(drm->dev, sizeof(*xlnx_drm),
> GFP_KERNEL);
> > +	if (!xlnx_drm) {
> > +		ret = -ENOMEM;
> > +		goto err_drm;
> > +	}
> > +
> > +	drm_mode_config_init(drm);
> > +	drm->mode_config.funcs = &xlnx_mode_config_funcs;
> > +	drm->mode_config.helper_private =
> &xlnx_mode_config_helper_funcs;
> > +
> > +	ret = drm_vblank_init(drm, 1);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "failed to initialize vblank\n");
> > +		goto err_xlnx_drm;
> > +	}
> > +
> > +	drm->irq_enabled = 1;
> > +	drm->dev_private = xlnx_drm;
> > +	xlnx_drm->drm = drm;
> > +	drm_kms_helper_poll_init(drm);
> > +	platform_set_drvdata(pdev, xlnx_drm);
> > +
> > +	xlnx_drm->crtc = xlnx_crtc_helper_init(drm);
> > +	if (IS_ERR(xlnx_drm->crtc)) {
> > +		ret = PTR_ERR(xlnx_drm->crtc);
> > +		goto err_xlnx_drm;
> > +	}
> > +
> > +	ret = component_bind_all(drm->dev, drm);
> > +	if (ret)
> > +		goto err_crtc;
> > +
> > +	xlnx_mode_config_init(drm);
> > +	drm_mode_config_reset(drm);
> > +	dma_set_mask(drm->dev,
> xlnx_crtc_helper_get_dma_mask(xlnx_drm->crtc));
> > +
> > +	format = xlnx_crtc_helper_get_format(xlnx_drm->crtc);
> > +	info = drm_format_info(format);
> > +	if (info && info->depth && info->cpp[0]) {
> > +		unsigned int align;
> > +
> > +		align = xlnx_crtc_helper_get_align(xlnx_drm->crtc);
> > +		xlnx_drm->fb = xlnx_fb_init(drm, info->cpp[0] * 8, 1, align,
> > +					    xlnx_fbdev_vres);
> > +		if (IS_ERR(xlnx_drm->fb))
> > +			dev_err(&pdev->dev,
> > +				"failed to initialize drm fb\n");
> > +	} else {
> > +		/* fbdev emulation is optional */
> > +		dev_info(&pdev->dev, "fbdev is not initialized\n");
> > +	}
> > +
> > +	ret = drm_dev_register(drm, 0);
> > +	if (ret < 0)
> > +		goto err_fb;
> > +
> > +	return 0;
> > +
> > +err_fb:
> > +	if (xlnx_drm->fb)
> > +		xlnx_fb_fini(xlnx_drm->fb);
> > +	component_unbind_all(drm->dev, drm);
> > +err_crtc:
> > +	xlnx_crtc_helper_fini(drm, xlnx_drm->crtc);
> > +err_xlnx_drm:
> > +	drm_mode_config_cleanup(drm);
> > +err_drm:
> > +	drm_dev_unref(drm);
> > +	return ret;
> > +}
> > +
> > +static void xlnx_unbind(struct device *dev)
> > +{
> > +	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
> > +	struct drm_device *drm = xlnx_drm->drm;
> > +
> > +	drm_dev_unregister(drm);
> > +	if (xlnx_drm->fb)
> > +		xlnx_fb_fini(xlnx_drm->fb);
> > +	component_unbind_all(drm->dev, drm);
> > +	xlnx_crtc_helper_fini(drm, xlnx_drm->crtc);
> > +	drm_kms_helper_poll_fini(drm);
> > +	drm_mode_config_cleanup(drm);
> > +	drm_dev_unref(drm);
> > +}
> > +
> > +static const struct component_master_ops xlnx_master_ops = {
> > +	.bind	= xlnx_bind,
> > +	.unbind	= xlnx_unbind,
> > +};
> > +
> > +static int xlnx_of_component_probe(struct device *dev,
> > +				   int (*compare_of)(struct device *, void *),
> > +				   const struct component_master_ops
> *m_ops)
> > +{
> > +	struct device_node *ep, *port, *remote;
> > +	struct component_match *match = NULL;
> > +	int i;
> > +
> > +	if (!dev->of_node)
> > +		return -EINVAL;
> > +
> > +	for (i = 0; ; i++) {
> > +		port = of_parse_phandle(dev->of_node, "ports", i);
> > +		if (!port)
> > +			break;
> > +
> > +		if (!of_device_is_available(port->parent)) {
> > +			of_node_put(port);
> > +			continue;
> > +		}
> > +
> > +		component_match_add(dev, &match, compare_of, port-
> >parent);
> > +		of_node_put(port);
> > +	}
> > +
> > +	if (i == 0) {
> > +		dev_err(dev, "missing 'ports' property\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	if (!match) {
> > +		dev_err(dev, "no available port\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	for (i = 0; ; i++) {
> > +		port = of_parse_phandle(dev->of_node, "ports", i);
> > +		if (!port)
> > +			break;
> > +
> > +		if (!of_device_is_available(port->parent)) {
> > +			of_node_put(port);
> > +			continue;
> > +		}
> > +
> > +		for_each_child_of_node(port, ep) {
> > +			remote = of_graph_get_remote_port_parent(ep);
> > +			if (!remote || !of_device_is_available(remote)) {
> > +				of_node_put(remote);
> > +				continue;
> > +			} else if (!of_device_is_available(remote->parent)) {
> > +				dev_warn(dev, "parent dev of %s
> unavailable\n",
> > +					 remote->full_name);
> > +				of_node_put(remote);
> > +				continue;
> > +			}
> > +			component_match_add(dev, &match, compare_of,
> remote);
> > +			of_node_put(remote);
> > +		}
> > +		of_node_put(port);
> > +	}
> > +
> > +	return component_master_add_with_match(dev, m_ops, match);
> > +}
> > +
> > +static int xlnx_compare_of(struct device *dev, void *data)
> > +{
> > +	return dev->of_node == data;
> > +}
> > +
> > +static int xlnx_platform_probe(struct platform_device *pdev)
> > +{
> > +	return xlnx_of_component_probe(&pdev->dev, xlnx_compare_of,
> > +				       &xlnx_master_ops);
> > +}
> > +
> > +static int xlnx_platform_remove(struct platform_device *pdev)
> > +{
> > +	component_master_del(&pdev->dev, &xlnx_master_ops);
> > +	return 0;
> > +}
> > +
> > +static void xlnx_platform_shutdown(struct platform_device *pdev)
> > +{
> > +	struct xlnx_drm *xlnx_drm = platform_get_drvdata(pdev);
> > +
> > +	drm_put_dev(xlnx_drm->drm);
> > +}
> > +
> > +static int __maybe_unused xlnx_pm_suspend(struct device *dev)
> > +{
> > +	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
> > +	struct drm_device *drm = xlnx_drm->drm;
> > +
> > +	drm_kms_helper_poll_disable(drm);
> > +
> > +	xlnx_drm->suspend_state = drm_atomic_helper_suspend(drm);
> > +	if (IS_ERR(xlnx_drm->suspend_state)) {
> > +		drm_kms_helper_poll_enable(drm);
> > +		return PTR_ERR(xlnx_drm->suspend_state);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int __maybe_unused xlnx_pm_resume(struct device *dev)
> > +{
> > +	struct xlnx_drm *xlnx_drm = dev_get_drvdata(dev);
> > +	struct drm_device *drm = xlnx_drm->drm;
> > +
> > +	drm_atomic_helper_resume(drm, xlnx_drm->suspend_state);
> > +	drm_kms_helper_poll_enable(drm);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct dev_pm_ops xlnx_pm_ops = {
> > +	SET_SYSTEM_SLEEP_PM_OPS(xlnx_pm_suspend, xlnx_pm_resume)
> > +};
> > +
> > +static const struct of_device_id xlnx_of_match[] = {
> > +	{ .compatible = "xlnx,kms", },
> > +	{ /* end of table */ },
> > +};
> > +MODULE_DEVICE_TABLE(of, xlnx_of_match);
> > +
> > +static struct platform_driver xlnx_driver = {
> > +	.probe			= xlnx_platform_probe,
> > +	.remove			= xlnx_platform_remove,
> > +	.shutdown		= xlnx_platform_shutdown,
> > +	.driver			= {
> > +		.name		= "xlnx-drm",
> > +		.pm		= &xlnx_pm_ops,
> > +		.of_match_table	= xlnx_of_match,
> > +	},
> > +};
> > +
> > +module_platform_driver(xlnx_driver);
> > +
> > +MODULE_AUTHOR("Xilinx, Inc.");
> > +MODULE_DESCRIPTION("Xilinx DRM KMS Driver");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_drv.h
> b/drivers/gpu/drm/xlnx/xlnx_drv.h
> > new file mode 100644
> > index 0000000..6ec285c
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xlnx/xlnx_drv.h
> > @@ -0,0 +1,22 @@
> > +/*
> > + * Xilinx DRM KMS Header for Xilinx
> > + *
> > + *  Copyright (C) 2013 - 2018 Xilinx, Inc.
> > + *
> > + *  Author: Hyun Woo Kwon <hyunk@xilinx.com>
> > + *
> > + * SPDX-License-Identifier: GPL-2.0
> > + */
> > +
> > +#ifndef _XLNX_DRV_H_
> > +#define _XLNX_DRV_H_
> > +
> > +struct drm_device;
> > +struct xlnx_crtc_helper;
> > +
> > +uint32_t xlnx_get_format(struct drm_device *drm);
> > +unsigned int xlnx_get_align(struct drm_device *drm);
> > +struct xlnx_crtc_helper *xlnx_get_crtc_helper(struct drm_device *drm);
> > +struct xlnx_bridge_helper *xlnx_get_bridge_helper(struct drm_device
> *drm);
> > +
> > +#endif /* _XLNX_DRV_H_ */
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_fb.c
> b/drivers/gpu/drm/xlnx/xlnx_fb.c
> > index dbe9fbf..029a4d3 100644
> > --- a/drivers/gpu/drm/xlnx/xlnx_fb.c
> > +++ b/drivers/gpu/drm/xlnx/xlnx_fb.c
> > @@ -18,6 +18,7 @@
> >  #include <drm/drm_fb_helper.h>
> >  #include <drm/drm_gem_cma_helper.h>
> >
> > +#include "xlnx_drv.h"
> >  #include "xlnx_fb.h"
> >
> >  #define XLNX_MAX_PLANES	4
> > diff --git a/drivers/gpu/drm/xlnx/xlnx_gem.c
> b/drivers/gpu/drm/xlnx/xlnx_gem.c
> > index af1abdc..6395f65 100644
> > --- a/drivers/gpu/drm/xlnx/xlnx_gem.c
> > +++ b/drivers/gpu/drm/xlnx/xlnx_gem.c
> > @@ -11,6 +11,7 @@
> >  #include <drm/drmP.h>
> >  #include <drm/drm_gem_cma_helper.h>
> >
> > +#include "xlnx_drv.h"
> >  #include "xlnx_gem.h"
> >
> >  /*
> > --
> > 2.7.4
> >
> > _______________________________________________
> > dri-devel mailing list
> > dri-devel@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
  2018-01-09  9:54     ` Daniel Vetter
@ 2018-01-11  2:05       ` Hyun Kwon
       [not found]         ` <BY1PR0201MB10001A1C38398BFBAEC56D39D6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11  2:05 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: Tejas Upadhyay, devicetree, Michal Simek, dri-devel

Hi Daniel,

> -----Original Message-----
> From: Daniel Vetter [mailto:daniel.vetter@ffwll.ch] On Behalf Of Daniel
> Vetter
> Sent: Tuesday, January 09, 2018 1:54 AM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Tejas
> Upadhyay <TEJASU@xilinx.com>; Michal Simek <michal.simek@xilinx.com>
> Subject: Re: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
> 
> On Thu, Jan 04, 2018 at 06:05:59PM -0800, Hyun Kwon wrote:
> > Debugfs can be used to exploit some specific setting. Main purpose
> > is for testing and debug diagnostic.
> >
> > Signed-off-by: Tejas Upadhyay <tejasu@xilinx.com>
> > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> 
> Hm, not sure what's the use, it seems to just be for setting/getting
> your driver-private properties. Usually people use modetest and similar
> tools, but if there's demand for setting properties in debugfs (we already
> have all the infrastructure for dumping the entire kms state, see the
> various atomic_print_state callbacks) I think that should be done with
> generic drm code.
> 
> I'd drop this patch for now (if there's no other reason for it).

Thanks for the input. It'd be helpful, for my own understanding, if this can be elaborated a little more. We are using standard tools as well as custom script/tool, but some specific properties / controls are hard to be executed with modetest only unless we change the entire set up / design between each run. The debugfs is used to run all (or as much as possible) properties in a single run, and from what I understand, that doesn't violate intended debugfs usage as long as it's not treated as a stable ABI. The intention is to help isolate issues and enhance the diagnostics. I agree, in the long term, this kind of stuff should be handled in generic way, but would it be still reasonable to keep it driver specific in the meantime?

Thanks,
-hyun

> -Daniel
> 
> > ---
> >  drivers/gpu/drm/xlnx/Kconfig       |  21 +++
> >  drivers/gpu/drm/xlnx/zynqmp_disp.c | 326
> +++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/xlnx/zynqmp_dp.c   | 304
> ++++++++++++++++++++++++++++++++++
> >  3 files changed, 651 insertions(+)
> >
> > diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
> > index 7c5529c..befce0f 100644
> > --- a/drivers/gpu/drm/xlnx/Kconfig
> > +++ b/drivers/gpu/drm/xlnx/Kconfig
> > @@ -21,3 +21,24 @@ config DRM_ZYNQMP_DPSUB
> >  	  this option if you have a Xilinx ZynqMP SoC with DisplayPort
> >  	  subsystem. The driver provides the kernel mode setting
> >  	  functionlaities for ZynqMP DP subsystem.
> > +
> > +config DRM_ZYNQMP_DISP_DEBUG_FS
> > +	bool "ZynqMP Display debugfs"
> > +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> > +	select DRM_ZYNQMP_DP_DEBUG_FS
> > +	help
> > +	  Enable the debugfs code for DP Sub driver. The debugfs code
> > +	  enables debugging or testing related features. It exposes some
> > +	  low level controls to the user space to help testing automation,
> > +	  as well as can enable additional diagnostic or statistical
> > +	  information.
> > +
> > +config DRM_ZYNQMP_DP_DEBUG_FS
> > +	bool "ZynqMP DP debugfs"
> > +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> > +	help
> > +	  Enable the debugfs code for DP driver. The debugfs code
> > +	  enables debugging or testing related features. It exposes some
> > +	  low level controls to the user space to help testing automation,
> > +	  as well as can enable additional diagnostic or statistical
> > +	  information.
> > diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > index 68f829c..9fe6d49 100644
> > --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > @@ -17,6 +17,7 @@
> >  #include <drm/drm_plane_helper.h>
> >
> >  #include <linux/clk.h>
> > +#include <linux/debugfs.h>
> >  #include <linux/device.h>
> >  #include <linux/dmaengine.h>
> >  #include <linux/interrupt.h>
> > @@ -508,6 +509,325 @@ static void zynqmp_disp_set(void __iomem
> *base, int offset, u32 set)
> >  	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) |
> set);
> >  }
> >
> > +#ifdef CONFIG_DRM_ZYNQMP_DISP_DEBUG_FS
> > +
> > +#define ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE	32UL
> > +#define ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL	0xFFF
> > +#define IN_RANGE(x, min, max) ({		\
> > +		typeof(x) _x = (x);		\
> > +		_x >= (min) && _x <= (max); })
> > +
> > +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> > +enum zynqmp_disp_testcases {
> > +	DP_SUB_TC_BG_COLOR,
> > +	DP_SUB_TC_OUTPUT_FMT,
> > +	DP_SUB_TC_NONE
> > +};
> > +
> > +struct zynqmp_disp_debugfs {
> > +	enum zynqmp_disp_testcases testcase;
> > +	u16 r_value;
> > +	u16 g_value;
> > +	u16 b_value;
> > +	u32 output_fmt;
> > +	struct zynqmp_disp *zynqmp_disp;
> > +};
> > +
> > +static struct dentry *zynqmp_disp_debugfs_dir;
> > +struct zynqmp_disp_debugfs disp_debugfs;
> > +struct zynqmp_disp_debugfs_request {
> > +	const char *req;
> > +	enum zynqmp_disp_testcases tc;
> > +	ssize_t (*read_handler)(char **kern_buff);
> > +	ssize_t (*write_handler)(char **cmd);
> > +};
> > +
> > +static void
> > +zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int
> id);
> > +static s64 zynqmp_disp_debugfs_argument_value(char *arg)
> > +{
> > +	s64 value;
> > +
> > +	if (!arg)
> > +		return -1;
> > +
> > +	if (!kstrtos64(arg, 0, &value))
> > +		return value;
> > +
> > +	return -1;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_background_color_write(char **disp_test_arg)
> > +{
> > +	char *r_color, *g_color, *b_color;
> > +	s64 r_val, g_val, b_val;
> > +
> > +	r_color = strsep(disp_test_arg, " ");
> > +	g_color = strsep(disp_test_arg, " ");
> > +	b_color = strsep(disp_test_arg, " ");
> > +
> > +	/* char * to int conversion */
> > +	r_val = zynqmp_disp_debugfs_argument_value(r_color);
> > +	g_val = zynqmp_disp_debugfs_argument_value(g_color);
> > +	b_val = zynqmp_disp_debugfs_argument_value(b_color);
> > +
> > +	if (!(IN_RANGE(r_val, 0,
> ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> > +	      IN_RANGE(g_val, 0,
> ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> > +	      IN_RANGE(b_val, 0,
> ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL)))
> > +		return -EINVAL;
> > +
> > +	disp_debugfs.r_value = r_val;
> > +	disp_debugfs.g_value = g_val;
> > +	disp_debugfs.b_value = b_val;
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_BG_COLOR;
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_output_display_format_write(char
> **disp_test_arg)
> > +{
> > +	char *output_format;
> > +	struct zynqmp_disp *disp = disp_debugfs.zynqmp_disp;
> > +
> > +	/* Read the value from a user value */
> > +	output_format = strsep(disp_test_arg, " ");
> > +	if (strncmp(output_format, "rgb", 3) == 0) {
> > +		disp_debugfs.output_fmt =
> > +			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB;
> > +	} else if (strncmp(output_format, "ycbcr444", 8) == 0) {
> > +		disp_debugfs.output_fmt =
> > +
> 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444;
> > +	} else if (strncmp(output_format, "ycbcr422", 8) == 0) {
> > +		disp_debugfs.output_fmt =
> > +
> 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422;
> > +	} else if (strncmp(output_format, "yonly", 5) == 0) {
> > +		disp_debugfs.output_fmt =
> > +
> 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY;
> > +	} else {
> > +		dev_err(disp->dev, "Invalid output format\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_OUTPUT_FMT;
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_output_display_format_read(char **kern_buff)
> > +{
> > +	size_t out_str_len;
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > +	disp_debugfs.output_fmt = 0;
> > +
> > +	out_str_len = strlen("Success");
> > +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> out_str_len);
> > +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_background_color_read(char **kern_buff)
> > +{
> > +	size_t out_str_len;
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > +	disp_debugfs.r_value = 0;
> > +	disp_debugfs.g_value = 0;
> > +	disp_debugfs.b_value = 0;
> > +
> > +	out_str_len = strlen("Success");
> > +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> out_str_len);
> > +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> > +
> > +	return 0;
> > +}
> > +
> > +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> > +struct zynqmp_disp_debugfs_request disp_debugfs_reqs[] = {
> > +	{"BACKGROUND_COLOR", DP_SUB_TC_BG_COLOR,
> > +		zynqmp_disp_debugfs_background_color_read,
> > +		zynqmp_disp_debugfs_background_color_write},
> > +	{"OUTPUT_DISPLAY_FORMAT", DP_SUB_TC_OUTPUT_FMT,
> > +		zynqmp_disp_debugfs_output_display_format_read,
> > +		zynqmp_disp_debugfs_output_display_format_write},
> > +};
> > +
> > +static ssize_t
> > +zynqmp_disp_debugfs_write(struct file *f, const char __user *buf,
> > +			  size_t size, loff_t *pos)
> > +{
> > +	char *kern_buff, *disp_test_req, *kern_buff_start;
> > +	int ret;
> > +	unsigned int i;
> > +
> > +	if (*pos != 0 || size <= 0)
> > +		return -EINVAL;
> > +
> > +	if (disp_debugfs.testcase != DP_SUB_TC_NONE)
> > +		return -EBUSY;
> > +
> > +	kern_buff = kzalloc(size, GFP_KERNEL);
> > +	if (!kern_buff)
> > +		return -ENOMEM;
> > +	kern_buff_start = kern_buff;
> > +
> > +	ret = strncpy_from_user(kern_buff, buf, size);
> > +	if (ret < 0) {
> > +		kfree(kern_buff_start);
> > +		return ret;
> > +	}
> > +
> > +	/* Read the testcase name and argument from a user request */
> > +	disp_test_req = strsep(&kern_buff, " ");
> > +
> > +	for (i = 0; i < ARRAY_SIZE(disp_debugfs_reqs); i++) {
> > +		if (!strcasecmp(disp_test_req, disp_debugfs_reqs[i].req))
> > +			if (!disp_debugfs_reqs[i].write_handler(&kern_buff))
> {
> > +				kfree(kern_buff_start);
> > +				return size;
> > +			}
> > +	}
> > +	kfree(kern_buff_start);
> > +	return -EINVAL;
> > +}
> > +
> > +static ssize_t zynqmp_disp_debugfs_read(struct file *f, char __user *buf,
> > +					size_t size, loff_t *pos)
> > +{
> > +	char *kern_buff = NULL;
> > +	size_t kern_buff_len, out_str_len;
> > +	enum zynqmp_disp_testcases tc;
> > +	int ret;
> > +
> > +	if (size <= 0)
> > +		return -EINVAL;
> > +
> > +	if (*pos != 0)
> > +		return 0;
> > +
> > +	kern_buff = kzalloc(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> GFP_KERNEL);
> > +	if (!kern_buff) {
> > +		disp_debugfs.testcase = DP_SUB_TC_NONE;
> > +		return -ENOMEM;
> > +	}
> > +
> > +	tc = disp_debugfs.testcase;
> > +	if (tc == DP_SUB_TC_NONE) {
> > +		out_str_len = strlen("No testcase executed");
> > +		out_str_len =
> min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > +				  out_str_len);
> > +		snprintf(kern_buff, out_str_len, "%s", "No testcase
> executed");
> > +	} else {
> > +		ret = disp_debugfs_reqs[tc].read_handler(&kern_buff);
> > +		if (ret) {
> > +			kfree(kern_buff);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	kern_buff_len = strlen(kern_buff);
> > +	size = min(size, kern_buff_len);
> > +
> > +	ret = copy_to_user(buf, kern_buff, size);
> > +
> > +	kfree(kern_buff);
> > +	if (ret)
> > +		return ret;
> > +
> > +	*pos = size + 1;
> > +	return size;
> > +}
> > +
> > +static const struct file_operations fops_zynqmp_disp_dbgfs = {
> > +	.owner = THIS_MODULE,
> > +	.read = zynqmp_disp_debugfs_read,
> > +	.write = zynqmp_disp_debugfs_write,
> > +};
> > +
> > +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> > +{
> > +	int err;
> > +	struct dentry *zynqmp_disp_debugfs_file;
> > +
> > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > +	disp_debugfs.zynqmp_disp = disp;
> > +
> > +	zynqmp_disp_debugfs_dir = debugfs_create_dir("disp", NULL);
> > +	if (!zynqmp_disp_debugfs_dir) {
> > +		dev_err(disp->dev, "debugfs_create_dir failed\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	zynqmp_disp_debugfs_file =
> > +		debugfs_create_file("testcase", 0444,
> > +				    zynqmp_disp_debugfs_dir, NULL,
> > +				    &fops_zynqmp_disp_dbgfs);
> > +	if (!zynqmp_disp_debugfs_file) {
> > +		dev_err(disp->dev, "debugfs_create_file testcase failed\n");
> > +		err = -ENODEV;
> > +		goto err_dbgfs;
> > +	}
> > +	return 0;
> > +
> > +err_dbgfs:
> > +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> > +	zynqmp_disp_debugfs_dir = NULL;
> > +	return err;
> > +}
> > +
> > +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> > +{
> > +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> > +	zynqmp_disp_debugfs_dir = NULL;
> > +}
> > +
> > +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> > +{
> > +	if (disp_debugfs.testcase == DP_SUB_TC_BG_COLOR) {
> > +		zynqmp_disp_write(disp->blend.base,
> > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_0,
> > +				  disp_debugfs.r_value);
> > +		zynqmp_disp_write(disp->blend.base,
> > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_1,
> > +				  disp_debugfs.g_value);
> > +		zynqmp_disp_write(disp->blend.base,
> > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_2,
> > +				  disp_debugfs.b_value);
> > +	}
> > +}
> > +
> > +static void
> > +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> > +{
> > +	if (disp_debugfs.testcase == DP_SUB_TC_OUTPUT_FMT)
> > +		zynqmp_disp_set_output_fmt(disp,
> disp_debugfs.output_fmt);
> > +}
> > +#else
> > +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> > +{
> > +}
> > +
> > +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> > +{
> > +	return 0;
> > +}
> > +
> > +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> > +{
> > +}
> > +
> > +static void
> > +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> > +{
> > +}
> > +#endif /* CONFIG_DP_DEBUG_FS */
> > +
> >  /*
> >   * Clock functions
> >   */
> > @@ -597,6 +917,8 @@ zynqmp_disp_blend_set_output_fmt(struct
> zynqmp_disp_blend *blend, u32 fmt)
> >  	u32 *offsets;
> >  	u32 offset, i;
> >
> > +	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422)
> > +		fmt |=
> ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE;
> >  	zynqmp_disp_write(blend->base,
> ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
> >  	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
> >  		coeffs = reset_coeffs;
> > @@ -1941,6 +2263,7 @@ static void zynqmp_disp_set_bg_color(struct
> zynqmp_disp *disp,
> >  				     u32 c0, u32 c1, u32 c2)
> >  {
> >  	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
> > +	zynqmp_disp_debugfs_bg_color(disp);
> >  }
> >
> >  /**
> > @@ -2572,6 +2895,7 @@ zynqmp_disp_crtc_atomic_enable(struct
> drm_crtc *crtc,
> >  		return;
> >  	}
> >  	zynqmp_disp_set_output_fmt(disp, disp->color);
> > +	zynqmp_disp_set_debugfs_output_fmt(disp);
> >  	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp-
> >bg_c2);
> >  	zynqmp_disp_enable(disp);
> >  	/* Delay of 3 vblank intervals for timing gen to be stable */
> > @@ -2911,6 +3235,7 @@ int zynqmp_disp_probe(struct
> platform_device *pdev)
> >  	ret = zynqmp_disp_layer_create(disp);
> >  	if (ret)
> >  		goto error_aclk;
> > +	zynqmp_disp_debugfs_init(disp);
> >
> >  	return 0;
> >
> > @@ -2924,6 +3249,7 @@ int zynqmp_disp_remove(struct
> platform_device *pdev)
> >  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> >  	struct zynqmp_disp *disp = dpsub->disp;
> >
> > +	zynqmp_disp_debugfs_exit(disp);
> >  	zynqmp_disp_layer_destroy(disp);
> >  	if (disp->audclk)
> >  		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
> > diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > index ce3c7c5..66fbad0 100644
> > --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > @@ -16,6 +16,7 @@
> >  #include <drm/drm_dp_helper.h>
> >  #include <drm/drm_of.h>
> >
> > +#include <linux/debugfs.h>
> >  #include <linux/delay.h>
> >  #include <linux/device.h>
> >  #include <linux/module.h>
> > @@ -371,6 +372,306 @@ static void zynqmp_dp_set(void __iomem
> *base, int offset, u32 set)
> >  }
> >
> >  /*
> > + * Debugfs functions
> > + */
> > +
> > +#ifdef CONFIG_DRM_ZYNQMP_DP_DEBUG_FS
> > +
> > +#define ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE	32UL
> > +#define ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR	"255"
> > +#define IN_RANGE(x, min, max) ({		\
> > +		typeof(x) _x = (x);		\
> > +		_x >= (min) && _x <= (max); })
> > +
> > +/* Match zynqmp_dp_testcases vs debugfs_reqs[] entry */
> > +enum zynqmp_dp_testcases {
> > +	DP_TC_LINK_RATE,
> > +	DP_TC_LANE_COUNT,
> > +	DP_TC_OUTPUT_FMT,
> > +	DP_TC_NONE
> > +};
> > +
> > +struct zynqmp_dp_debugfs {
> > +	enum zynqmp_dp_testcases testcase;
> > +	u8 link_rate;
> > +	u8 lane_cnt;
> > +	u8 old_output_fmt;
> > +	struct zynqmp_dp *dp;
> > +};
> > +
> > +static struct dentry *zynqmp_dp_debugfs_dir;
> > +static struct zynqmp_dp_debugfs dp_debugfs;
> > +struct zynqmp_dp_debugfs_request {
> > +	const char *req;
> > +	enum zynqmp_dp_testcases tc;
> > +	ssize_t (*read_handler)(char **kern_buff);
> > +	ssize_t (*write_handler)(char **cmd);
> > +};
> > +
> > +static s64 zynqmp_dp_debugfs_argument_value(char *arg)
> > +{
> > +	s64 value;
> > +
> > +	if (!arg)
> > +		return -1;
> > +
> > +	if (!kstrtos64(arg, 0, &value))
> > +		return value;
> > +
> > +	return -1;
> > +}
> > +
> > +static ssize_t zynqmp_dp_debugfs_max_linkrate_write(char
> **dp_test_arg)
> > +{
> > +	char *link_rate_arg;
> > +	s64 link_rate;
> > +
> > +	link_rate_arg = strsep(dp_test_arg, " ");
> > +	link_rate = zynqmp_dp_debugfs_argument_value(link_rate_arg);
> > +	if (link_rate < 0 || (link_rate != DP_HIGH_BIT_RATE2 &&
> > +			      link_rate != DP_HIGH_BIT_RATE &&
> > +			      link_rate != DP_REDUCED_BIT_RATE))
> > +		return -EINVAL;
> > +
> > +	dp_debugfs.link_rate = drm_dp_link_rate_to_bw_code(link_rate);
> > +	dp_debugfs.testcase = DP_TC_LINK_RATE;
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t zynqmp_dp_debugfs_max_lanecnt_write(char
> **dp_test_arg)
> > +{
> > +	char *lane_cnt_arg;
> > +	s64 lane_count;
> > +
> > +	lane_cnt_arg = strsep(dp_test_arg, " ");
> > +	lane_count = zynqmp_dp_debugfs_argument_value(lane_cnt_arg);
> > +	if (lane_count < 0 || !IN_RANGE(lane_count, 1,
> > +					ZYNQMP_DP_MAX_LANES))
> > +		return -EINVAL;
> > +
> > +	dp_debugfs.lane_cnt = lane_count;
> > +	dp_debugfs.testcase = DP_TC_LANE_COUNT;
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t zynqmp_dp_debugfs_max_linkrate_read(char **kern_buff)
> > +{
> > +	struct zynqmp_dp *dp = dp_debugfs.dp;
> > +	size_t output_str_len;
> > +	u8 dpcd_link_bw;
> > +	int ret;
> > +
> > +	dp_debugfs.testcase = DP_TC_NONE;
> > +	dp_debugfs.link_rate = 0;
> > +
> > +	/* Getting Sink Side Link Rate */
> > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LINK_BW_SET,
> &dpcd_link_bw);
> > +	if (ret < 0) {
> > +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> > +		kfree(*kern_buff);
> > +		return ret;
> > +	}
> > +
> > +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> > +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> output_str_len);
> > +	snprintf(*kern_buff, output_str_len, "%u", dpcd_link_bw);
> > +
> > +	return 0;
> > +}
> > +
> > +static ssize_t zynqmp_dp_debugfs_max_lanecnt_read(char **kern_buff)
> > +{
> > +	struct zynqmp_dp *dp = dp_debugfs.dp;
> > +	size_t output_str_len;
> > +	u8 dpcd_lane_cnt;
> > +	int ret;
> > +
> > +	dp_debugfs.testcase = DP_TC_NONE;
> > +	dp_debugfs.lane_cnt = 0;
> > +
> > +	/* Getting Sink Side Lane Count */
> > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_COUNT_SET,
> &dpcd_lane_cnt);
> > +	if (ret < 0) {
> > +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> > +		kfree(*kern_buff);
> > +		return ret;
> > +	}
> > +
> > +	dpcd_lane_cnt &= DP_LANE_COUNT_MASK;
> > +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> > +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> output_str_len);
> > +	snprintf(*kern_buff, output_str_len, "%u", dpcd_lane_cnt);
> > +
> > +	return 0;
> > +}
> > +
> > +/* Match zynqmp_dp_testcases vs dp_debugfs_reqs[] entry */
> > +static struct zynqmp_dp_debugfs_request debugfs_reqs[] = {
> > +	{"LINK_RATE", DP_TC_LINK_RATE,
> > +			zynqmp_dp_debugfs_max_linkrate_read,
> > +			zynqmp_dp_debugfs_max_linkrate_write},
> > +	{"LANE_COUNT", DP_TC_LANE_COUNT,
> > +			zynqmp_dp_debugfs_max_lanecnt_read,
> > +			zynqmp_dp_debugfs_max_lanecnt_write},
> > +};
> > +
> > +static ssize_t zynqmp_dp_debugfs_read(struct file *f, char __user *buf,
> > +				      size_t size, loff_t *pos)
> > +{
> > +	char *kern_buff = NULL;
> > +	size_t kern_buff_len, out_str_len;
> > +	enum zynqmp_dp_testcases tc;
> > +	int ret;
> > +
> > +	if (size <= 0)
> > +		return -EINVAL;
> > +
> > +	if (*pos != 0)
> > +		return 0;
> > +
> > +	kern_buff = kzalloc(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> GFP_KERNEL);
> > +	if (!kern_buff) {
> > +		dp_debugfs.testcase = DP_TC_NONE;
> > +		return -ENOMEM;
> > +	}
> > +
> > +	tc = dp_debugfs.testcase;
> > +	if (tc == DP_TC_NONE) {
> > +		out_str_len = strlen("No testcase executed");
> > +		out_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> out_str_len);
> > +		snprintf(kern_buff, out_str_len, "%s", "No testcase
> executed");
> > +	} else {
> > +		ret = debugfs_reqs[tc].read_handler(&kern_buff);
> > +		if (ret) {
> > +			kfree(kern_buff);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	kern_buff_len = strlen(kern_buff);
> > +	size = min(size, kern_buff_len);
> > +
> > +	ret = copy_to_user(buf, kern_buff, size);
> > +
> > +	kfree(kern_buff);
> > +	if (ret)
> > +		return ret;
> > +
> > +	*pos = size + 1;
> > +	return size;
> > +}
> > +
> > +static ssize_t
> > +zynqmp_dp_debugfs_write(struct file *f, const char __user *buf,
> > +			size_t size, loff_t *pos)
> > +{
> > +	char *kern_buff, *kern_buff_start;
> > +	char *dp_test_req;
> > +	int ret;
> > +	int i;
> > +
> > +	if (*pos != 0 || size <= 0)
> > +		return -EINVAL;
> > +
> > +	if (dp_debugfs.testcase != DP_TC_NONE)
> > +		return -EBUSY;
> > +
> > +	kern_buff = kzalloc(size, GFP_KERNEL);
> > +	if (!kern_buff)
> > +		return -ENOMEM;
> > +	kern_buff_start = kern_buff;
> > +
> > +	ret = strncpy_from_user(kern_buff, buf, size);
> > +	if (ret < 0) {
> > +		kfree(kern_buff_start);
> > +		return ret;
> > +	}
> > +
> > +	/* Read the testcase name and argument from a user request */
> > +	dp_test_req = strsep(&kern_buff, " ");
> > +
> > +	for (i = 0; i < ARRAY_SIZE(debugfs_reqs); i++) {
> > +		if (!strcasecmp(dp_test_req, debugfs_reqs[i].req))
> > +			if (!debugfs_reqs[i].write_handler(&kern_buff)) {
> > +				kfree(kern_buff_start);
> > +				return size;
> > +			}
> > +	}
> > +
> > +	kfree(kern_buff_start);
> > +	return -EINVAL;
> > +}
> > +
> > +static const struct file_operations fops_zynqmp_dp_dbgfs = {
> > +	.owner = THIS_MODULE,
> > +	.read = zynqmp_dp_debugfs_read,
> > +	.write = zynqmp_dp_debugfs_write,
> > +};
> > +
> > +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> > +{
> > +	int err;
> > +	struct dentry *zynqmp_dp_debugfs_file;
> > +
> > +	dp_debugfs.testcase = DP_TC_NONE;
> > +	dp_debugfs.dp = dp;
> > +
> > +	zynqmp_dp_debugfs_dir = debugfs_create_dir("dp", NULL);
> > +	if (!zynqmp_dp_debugfs_dir) {
> > +		dev_err(dp->dev, "debugfs_create_dir failed\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	zynqmp_dp_debugfs_file =
> > +		debugfs_create_file("testcase", 0444,
> zynqmp_dp_debugfs_dir,
> > +				    NULL, &fops_zynqmp_dp_dbgfs);
> > +	if (!zynqmp_dp_debugfs_file) {
> > +		dev_err(dp->dev, "debugfs_create_file testcase failed\n");
> > +		err = -ENODEV;
> > +		goto err_dbgfs;
> > +	}
> > +	return 0;
> > +
> > +err_dbgfs:
> > +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> > +	zynqmp_dp_debugfs_dir = NULL;
> > +	return err;
> > +}
> > +
> > +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> > +{
> > +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> > +	zynqmp_dp_debugfs_dir = NULL;
> > +}
> > +
> > +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> > +{
> > +	dp->mode.bw_code =
> > +		dp_debugfs.link_rate ? dp_debugfs.link_rate : dp-
> >mode.bw_code;
> > +	dp->mode.lane_cnt =
> > +		dp_debugfs.lane_cnt ? dp_debugfs.lane_cnt : dp-
> >mode.lane_cnt;
> > +}
> > +
> > +#else /* DRM_ZYNQMP_DP_DEBUG_FS */
> > +
> > +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> > +{
> > +	return 0;
> > +}
> > +
> > +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> > +{
> > +}
> > +
> > +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> > +{
> > +}
> > +
> > +#endif /* DRM_ZYNQMP_DP_DEBUG_FS */
> > +
> > +/*
> >   * Internal functions: used by zynqmp_disp.c
> >   */
> >
> > @@ -597,6 +898,7 @@ static int zynqmp_dp_mode_configure(struct
> zynqmp_dp *dp, int pclock,
> >  			dp->mode.bw_code = bws[i];
> >  			dp->mode.lane_cnt = lane_cnt;
> >  			dp->mode.pclock = pclock;
> > +			zynqmp_dp_debugfs_mode_config(dp);
> >  			return dp->mode.bw_code;
> >  		}
> >  	}
> > @@ -1840,6 +2142,7 @@ int zynqmp_dp_probe(struct platform_device
> *pdev)
> >  	dpsub = platform_get_drvdata(pdev);
> >  	dpsub->dp = dp;
> >  	dp->dpsub = dpsub;
> > +	zynqmp_dp_debugfs_init(dp);
> >
> >  	return 0;
> >
> > @@ -1855,6 +2158,7 @@ int zynqmp_dp_remove(struct
> platform_device *pdev)
> >  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> >  	struct zynqmp_dp *dp = dpsub->dp;
> >
> > +	zynqmp_dp_debugfs_exit(dp);
> >  	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
> >  	drm_dp_aux_unregister(&dp->aux);
> >  	zynqmp_dp_exit_phy(dp);
> > --
> > 2.7.4
> >
> > _______________________________________________
> > dri-devel mailing list
> > dri-devel@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 06/10] dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings
  2018-01-09  4:07     ` Rob Herring
@ 2018-01-11  2:06       ` Hyun Kwon
  0 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11  2:06 UTC (permalink / raw)
  To: Rob Herring
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW

Hi Rob,

Thanks for the review.

> -----Original Message-----
> From: dri-devel [mailto:dri-devel-bounces@lists.freedesktop.org] On Behalf
> Of Rob Herring
> Sent: Monday, January 08, 2018 8:07 PM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: devicetree@vger.kernel.org; Michal Simek <michal.simek@xilinx.com>;
> dri-devel@lists.freedesktop.org
> Subject: Re: [PATCH 06/10] dt-bindings: display: xlnx: Add ZynqMP DP
> subsystem bindings
> 
> On Thu, Jan 04, 2018 at 06:05:55PM -0800, Hyun Kwon wrote:
> > This add a dt binding for ZynqMP DP subsystem.
> >
> > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> > ---
> >  .../bindings/display/xlnx/xlnx,zynqmp-dpsub.txt    | 94
> ++++++++++++++++++++++
> >  1 file changed, 94 insertions(+)
> >  create mode 100644
> Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
> >
> > diff --git a/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-
> dpsub.txt b/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-
> dpsub.txt
> > new file mode 100644
> > index 0000000..4e478ca
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-
> dpsub.txt
> > @@ -0,0 +1,94 @@
> > +Xilinx ZynqMP DisplayPort subsystem
> > +-----------------------------------
> > +
> > +Required properties:
> > +
> > +- compatible: Must be "xlnx,zynqmp-dpsub-1.7".
> > +
> > +- reg: Physical base address and length of the registers set for the device.
> > +- reg-names: Must be "dp", "blend", "av_buf", and "aud" to map logical
> register
> > +  partitions.
> > +
> > +- interrupts: Interrupt number.
> > +- interrupts-parent: phandle for interrupt controller.
> > +
> > +- clocks: phandles for axi, audio, non-live video, and live video clocks.
> > +  axi clock is required. Audio clock is optional. If not present, audio will
> > +  be disabled. One of non-live or live video clock should be present.
> > +- clock-names: The identification strings are required. "aclk" for axi clock.
> > +  "dp_aud_clk" for audio clock. "dp_vtc_pixel_clk_in" for non-live video
> clock.
> > +  "dp_live_video_in_clk" for live video clock (clock from programmable
> logic).
> 
> "_clk" is redundant.

This is to reflect the name of signal spelled out in the hardware spec, so I'd like to keep it this way unless you are still against it.

> 
> > +
> > +- phys: phandles for phy specifier.
> > +- phy-names: The identifier strings. "dp-phy" followed by index.
> > +
> > +- power-domains: phandle for the corresponding power domain
> > +
> > +- ports: crtc and encoder ports are required using DT bindings defined in
> > +  Documentation/devicetree/bindings/graph.txt.
> 
> Isn't the connection crtc->encoder?

It's bidirectional: crtc <-> encoder. Currently, as in this example, it's only connected between internal ports, but any of those ports can be connected with external ports too.

> 
> Also, crtc is pretty much a DRM term. Don't use that in bindings.
> Describe the h/w blocks.

Sure. Will fix.

> 
> > +
> > +- vid-layer, gfx-layer: Required to represent available layers
> > +
> > +Required layer properties
> > +
> > +- dmas: phandles for DMA channels as defined in
> > +  Documentation/devicetree/bindings/dma/dma.txt.
> > +- dma-names: The identifier strings are required. "graphics0" for graphics
> > +  layer. "video" followed by index for video layer
> > +
> > +Optional child node
> > +
> > +- The driver populates any child device node in this node. This can be
> used,
> > +  for example, to populate the sound device from the DisplayPort
> subsystem
> > +  driver.
> > +
> > +Example:
> > +	zynqmp_dpsub: zynqmp_dpsub@fd4a0000 {
> 
> display-controller@...
> 
> > +		compatible = "xlnx,zynqmp-dpsub-1.7";
> > +		reg = <0x0 0xfd4a0000 0x0 0x1000>,
> > +		      <0x0 0xfd4aa000 0x0 0x1000>,
> > +		      <0x0 0xfd4ab000 0x0 0x1000>,
> > +		      <0x0 0xfd4ac000 0x0 0x1000>;
> > +		reg-names = "dp", "blend", "av_buf", "aud";
> > +		interrupts = <0 119 4>;
> > +		interrupt-parent = <&gic>;
> > +
> > +		clock-names = "dp_apb_clk", "dp_aud_clk",
> "dp_live_video_in_clk";
> > +		clocks = <&dp_aclk>, <&clkc 17>, <&si570_1>;
> > +
> > +		phys = <&lane1 PHY_TYPE_DP 0 1 27000000>,
> > +		       <&lane0 PHY_TYPE_DP 1 1 27000000>;
> > +		phy-names = "dp-phy0", "dp-phy1";
> > +
> > +		power-domains = <&pd_dp>;
> > +
> > +		#address-cells = <1>;
> > +		#size-cells = <0>;
> > +
> > +		vid-layer {
> > +			dma-names = "vid0", "vid1", "vid2";
> > +			dmas = <&xlnx_dpdma 0>,
> > +			       <&xlnx_dpdma 1>,
> > +			       <&xlnx_dpdma 2>;
> > +		};
> > +
> > +		gfx-layer {
> 
> These 2 nodes don't seem necessary. Just make "dmas" take 4 values and
> define where the gfx0 channel is located (index 0 or 3?).
> 

That is correct, as of now. But, in the follow up patch / internal development, each layer needs to be referenced by external device node (ex, external device connected to each layer). That's why there's a child node for each layer. I can still remove these nodes for now, and add when those are needed. Please let me know, otherwise, I'll keep these nodes.

> > +			dma-names = "gfx0";
> > +			dmas = <&xlnx_dpdma 3>;
> > +		};
> > +
> > +		crtc_port: port@0 {
> > +			reg = <0>;
> > +			crtc: endpoint {
> > +				remote-endpoint = <&encoder>;
> > +			};
> > +		};
> > +		port@1 {
> 
> Multiple port nodes should be under a ports node especially if you have
> other child nodes.
> 

Will fix it.

Thanks,
-hyun

> > +			reg = <1>;
> > +			encoder: endpoint {
> > +				remote-endpoint = <&crtc>;
> > +			};
> > +		};
> > +	};
> > +};
> > +
> > --
> > 2.7.4
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe devicetree" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver
       [not found]     ` <20180109095649.GK26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
@ 2018-01-11  2:07       ` Hyun Kwon
       [not found]         ` <BY1PR0201MB10002CAFCC860052538BA14DD6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11  2:07 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="utf-8", Size: 5912 bytes --]

Hi Daniel,

> -----Original Message-----
> From: Daniel Vetter [mailto:daniel.vetter@ffwll.ch] On Behalf Of Daniel
> Vetter
> Sent: Tuesday, January 09, 2018 1:57 AM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> Simek <michal.simek@xilinx.com>
> Subject: Re: [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM
> KMS driver
> 
> On Thu, Jan 04, 2018 at 06:05:49PM -0800, Hyun Kwon wrote:
> > Hi,
> >
> > This patchset adds the DRM KMS driver for Xilinx ZynqMP DisplayPort
> > subsystem. The Xilinx ZynqMP SoC has a hardened full display pipeline
> > which supports blending of up to 2 planes, and the encoder is
> > DisplayPort v1.2 compatible.
> >
> > This series mainly includes 2 sets: Xilinx DRM KMS (patch 1/10 - 5/10)
> > and ZynqMP DP subsystem drivers (patch 6/10 - 10/10).
> >
> > The Xilinx DRM KMS is intended as a common layer shared across other
> > (upcoming) Xilinx sub-drivers. It helps sub-drivers for both hardened as
> > well as soft IPs interoperate together.
> >
> > ZynqMP DP subsystem driver is a sub-driver that implements
> corresponding
> > drm objects (crtc, plane, encoder, connector,,,) for ZynqMP SoC display
> > pipeline. The entire pipeline is mainly partitioned into 2 blocks:
> > generic display logic (zynqmp_disp.c) such as blending, csc,,, and the
> > DP transmitter logic (zynqmp_dp.c).
> 
> I read through it all (well mostly the drm relevant bits, not your backend
> code) and looks fairly resonable. Few minor clenaups and code removals
> tbh.
> 
> Wrt merging/maintianing, do you want to maintain it as part of the
> drm-misc small drivers group? Highly recommended imo. See
> 
> https://01.org/linuxgraphics/gfx-docs/maintainer-tools/drm-
> misc.html#small-drivers
> 
> for details. Ideally we'd need 2 xilinx maintainers to be able to push
> patches & cross-review stuff.

I don't have any preference on how to maintain, so I'll follow your suggestion. One thing that may be worth a note is that there is sizable amount of development within Xilinx, and those will come in near future (considering what can be done with FPGA :-)). I'll look for the 2nd reviewer, and specify that in the next patch if found.

Thanks,
-hyun

> -Daniel
> 
> >
> > Thanks,
> > -hyun
> >
> > Hyun Kwon (10):
> >   dt-bindings: display: xlnx: Add Xilinx kms bindings
> >   drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
> >   drm: xlnx: Add xlnx fb of Xilinx DRM KMS
> >   drm: xlnx: Add xlnx gem of Xilinx DRM KMS
> >   drm: xlnx: Xilinx DRM KMS driver
> >   dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings
> >   drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display
> >   drm: xlnx: DRM KMS driver for Xilinx ZynqMP DisplayPort
> >   drm: xlnx: ZynqMP DP subsystem DRM KMS driver
> >   drm: xlnx: zynqmp: Add debugfs
> >
> >  .../devicetree/bindings/display/xlnx/xlnx,kms.txt  |   20 +
> >  .../bindings/display/xlnx/xlnx,zynqmp-dpsub.txt    |   94 +
> >  MAINTAINERS                                        |    8 +
> >  drivers/gpu/drm/Kconfig                            |    2 +
> >  drivers/gpu/drm/Makefile                           |    1 +
> >  drivers/gpu/drm/xlnx/Kconfig                       |   44 +
> >  drivers/gpu/drm/xlnx/Makefile                      |    5 +
> >  drivers/gpu/drm/xlnx/xlnx_crtc.c                   |  195 ++
> >  drivers/gpu/drm/xlnx/xlnx_crtc.h                   |   70 +
> >  drivers/gpu/drm/xlnx/xlnx_drv.c                    |  436 +++
> >  drivers/gpu/drm/xlnx/xlnx_drv.h                    |   22 +
> >  drivers/gpu/drm/xlnx/xlnx_fb.c                     |  468 +++
> >  drivers/gpu/drm/xlnx/xlnx_fb.h                     |   30 +
> >  drivers/gpu/drm/xlnx/xlnx_gem.c                    |   39 +
> >  drivers/gpu/drm/xlnx/xlnx_gem.h                    |   18 +
> >  drivers/gpu/drm/xlnx/zynqmp_disp.c                 | 3261
> ++++++++++++++++++++
> >  drivers/gpu/drm/xlnx/zynqmp_disp.h                 |   28 +
> >  drivers/gpu/drm/xlnx/zynqmp_dp.c                   | 2168 +++++++++++++
> >  drivers/gpu/drm/xlnx/zynqmp_dp.h                   |   29 +
> >  drivers/gpu/drm/xlnx/zynqmp_dpsub.c                |  141 +
> >  drivers/gpu/drm/xlnx/zynqmp_dpsub.h                |   19 +
> >  21 files changed, 7098 insertions(+)
> >  create mode 100644
> Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> >  create mode 100644
> Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
> >  create mode 100644 drivers/gpu/drm/xlnx/Kconfig
> >  create mode 100644 drivers/gpu/drm/xlnx/Makefile
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.c
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.h
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.c
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.h
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.c
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.h
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.c
> >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.h
> >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.c
> >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.h
> >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.c
> >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.h
> >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.c
> >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.h
> >
> > --
> > 2.7.4
> >
> > _______________________________________________
> > dri-devel mailing list
> > dri-devel@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
N‹§²æìr¸›yúèšØb²X¬¶Ç§vØ^–)Þº{.nÇ+‰·zøœzÚÞz)í…æèw*\x1fjg¬±¨\x1e¶‰šŽŠÝ¢j.ïÛ°\½½MŽúgjÌæa×\x02››–' ™©Þ¢¸\f¢·¦j:+v‰¨ŠwèjØm¶Ÿÿ¾\a«‘êçzZ+ƒùšŽŠÝ¢j"ú!¶i

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
  2018-01-11  2:04         ` Hyun Kwon
@ 2018-01-11  7:48           ` Daniel Vetter
  0 siblings, 0 replies; 34+ messages in thread
From: Daniel Vetter @ 2018-01-11  7:48 UTC (permalink / raw)
  To: Hyun Kwon; +Cc: devicetree, dri-devel, Michal Simek

On Thu, Jan 11, 2018 at 02:04:47AM +0000, Hyun Kwon wrote:
> Hi Daniel,
> 
> > -----Original Message-----
> > From: Daniel Vetter [mailto:daniel.vetter@ffwll.ch] On Behalf Of Daniel
> > Vetter
> > Sent: Tuesday, January 09, 2018 1:38 AM
> > To: Hyun Kwon <hyunk@xilinx.com>
> > Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> > Simek <michal.simek@xilinx.com>
> > Subject: Re: [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
> > 
> > On Thu, Jan 04, 2018 at 06:05:51PM -0800, Hyun Kwon wrote:
> > > xlnx_crtc is a part of Xilinx DRM KMS and a layer that
> > > provides some interface between the Xilinx DRM KMS and
> > > crtc drivers.
> > >
> > > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> > 
> > Personal style, but I don't see much value in these small helpers.
> > Splitting them from the main driver at least makes reading the patches a
> > bit harder. But no strong opinion, just a bikeshed, feel free to ignore
> > :-)
> 
> I also don't, but some reviewers prefer this way. I'll leave it this way for now. :-)

Patch splitting is good imo, but I don't like free-standing new code. Imo
all functions you're adding need to have a caller (or at least be part of
a function table), so that it's clear how they're used. Without the users
being known it's not possible to review the code, rendering the splitting
pointless.

But splitting itself isn't a bad idea, just not splitting so much that the
individual patches dont' make sense anymore :-)
-Daniel
> 
> Thanks,
> -hyun
> 
> > -Daniel
> > 
> > > ---
> > >  drivers/gpu/drm/xlnx/xlnx_crtc.c | 194
> > +++++++++++++++++++++++++++++++++++++++
> > >  drivers/gpu/drm/xlnx/xlnx_crtc.h |  70 ++++++++++++++
> > >  2 files changed, 264 insertions(+)
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.c
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.h
> > >
> > > diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.c
> > b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> > > new file mode 100644
> > > index 0000000..57ee939
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/xlnx/xlnx_crtc.c
> > > @@ -0,0 +1,194 @@
> > > +/*
> > > + * Xilinx DRM crtc driver
> > > + *
> > > + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> > > + *
> > > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > > + *
> > > + * SPDX-License-Identifier: GPL-2.0
> > > + */
> > > +
> > > +#include <drm/drmP.h>
> > > +
> > > +#include <linux/list.h>
> > > +
> > > +#include "xlnx_crtc.h"
> > > +
> > > +/*
> > > + * Overview
> > > + * --------
> > > + *
> > > + * The Xilinx CRTC layer is to enable the custom interface to CRTC drivers.
> > > + * The interface is used by Xilinx DRM driver where it needs CRTC
> > > + * functionailty. CRTC drivers should attach the desired callbacks
> > > + * to struct xlnx_crtc and register the xlnx_crtc with correcsponding
> > > + * drm_device. It's highly recommended CRTC drivers register all
> > callbacks
> > > + * even though many of them are optional.
> > > + * The CRTC helper simply walks through the registered CRTC device,
> > > + * and call the callbacks.
> > > + */
> > > +
> > > +/**
> > > + * struct xlnx_crtc_helper - Xilinx CRTC helper
> > > + * @xlnx_crtcs: list of Xilinx CRTC devices
> > > + * @lock: lock to protect @xlnx_crtcs
> > > + * @drm: back pointer to DRM core
> > > + */
> > > +struct xlnx_crtc_helper {
> > > +	struct list_head xlnx_crtcs;
> > > +	struct mutex lock; /* lock for @xlnx_crtcs */
> > > +	struct drm_device *drm;
> > > +};
> > > +
> > > +#define XLNX_CRTC_MAX_HEIGHT_WIDTH	UINT_MAX
> > > +
> > > +int xlnx_crtc_helper_enable_vblank(struct xlnx_crtc_helper *helper,
> > > +				   unsigned int crtc_id)
> > > +{
> > > +	struct xlnx_crtc *crtc;
> > > +
> > > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list)
> > > +		if (drm_crtc_index(&crtc->crtc) == crtc_id)
> > > +			if (crtc->enable_vblank)
> > > +				return crtc->enable_vblank(crtc);
> > > +	return -ENODEV;
> > > +}
> > > +
> > > +void xlnx_crtc_helper_disable_vblank(struct xlnx_crtc_helper *helper,
> > > +				     unsigned int crtc_id)
> > > +{
> > > +	struct xlnx_crtc *crtc;
> > > +
> > > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > > +		if (drm_crtc_index(&crtc->crtc) == crtc_id) {
> > > +			if (crtc->disable_vblank)
> > > +				crtc->disable_vblank(crtc);
> > > +			return;
> > > +		}
> > > +	}
> > > +}
> > > +
> > > +unsigned int xlnx_crtc_helper_get_align(struct xlnx_crtc_helper *helper)
> > > +{
> > > +	struct xlnx_crtc *crtc;
> > > +	unsigned int align = 1, tmp;
> > > +
> > > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > > +		if (crtc->get_align) {
> > > +			tmp = crtc->get_align(crtc);
> > > +			align = ALIGN(align, tmp);
> > > +		}
> > > +	}
> > > +
> > > +	return align;
> > > +}
> > > +
> > > +u64 xlnx_crtc_helper_get_dma_mask(struct xlnx_crtc_helper *helper)
> > > +{
> > > +	struct xlnx_crtc *crtc;
> > > +	u64 mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8), tmp;
> > > +
> > > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > > +		if (crtc->get_dma_mask) {
> > > +			tmp = crtc->get_dma_mask(crtc);
> > > +			mask = min(mask, tmp);
> > > +		}
> > > +	}
> > > +
> > > +	return mask;
> > > +}
> > > +
> > > +int xlnx_crtc_helper_get_max_width(struct xlnx_crtc_helper *helper)
> > > +{
> > > +	struct xlnx_crtc *crtc;
> > > +	unsigned int width = XLNX_CRTC_MAX_HEIGHT_WIDTH, tmp;
> > > +
> > > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > > +		if (crtc->get_max_width) {
> > > +			tmp = crtc->get_max_width(crtc);
> > > +			width = min(width, tmp);
> > > +		}
> > > +	}
> > > +
> > > +	return width;
> > > +}
> > > +
> > > +int xlnx_crtc_helper_get_max_height(struct xlnx_crtc_helper *helper)
> > > +{
> > > +	struct xlnx_crtc *crtc;
> > > +	unsigned int height = XLNX_CRTC_MAX_HEIGHT_WIDTH, tmp;
> > > +
> > > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > > +		if (crtc->get_max_height) {
> > > +			tmp = crtc->get_max_height(crtc);
> > > +			height = min(height, tmp);
> > > +		}
> > > +	}
> > > +
> > > +	return height;
> > > +}
> > > +
> > > +uint32_t xlnx_crtc_helper_get_format(struct xlnx_crtc_helper *helper)
> > > +{
> > > +	struct xlnx_crtc *crtc;
> > > +	u32 format = 0, tmp;
> > > +
> > > +	list_for_each_entry(crtc, &helper->xlnx_crtcs, list) {
> > > +		if (crtc->get_format) {
> > > +			tmp = crtc->get_format(crtc);
> > > +			if (format && format != tmp)
> > > +				return 0;
> > > +			format = tmp;
> > > +		}
> > > +	}
> > > +
> > > +	return format;
> > > +}
> > > +
> > > +struct xlnx_crtc_helper *xlnx_crtc_helper_init(struct drm_device *drm)
> > > +{
> > > +	struct xlnx_crtc_helper *helper;
> > > +
> > > +	helper = devm_kzalloc(drm->dev, sizeof(*helper), GFP_KERNEL);
> > > +	if (!helper)
> > > +		return ERR_PTR(-ENOMEM);
> > > +
> > > +	INIT_LIST_HEAD(&helper->xlnx_crtcs);
> > > +	mutex_init(&helper->lock);
> > > +	helper->drm = drm;
> > > +
> > > +	return helper;
> > > +}
> > > +
> > > +void xlnx_crtc_helper_fini(struct drm_device *drm,
> > > +			   struct xlnx_crtc_helper *helper)
> > > +{
> > > +	if (WARN_ON(helper->drm != drm))
> > > +		return;
> > > +
> > > +	if (WARN_ON(!list_empty(&helper->xlnx_crtcs)))
> > > +		return;
> > > +
> > > +	mutex_destroy(&helper->lock);
> > > +	devm_kfree(drm->dev, helper);
> > > +}
> > > +
> > > +void xlnx_crtc_register(struct drm_device *drm, struct xlnx_crtc *crtc)
> > > +{
> > > +	struct xlnx_crtc_helper *helper = xlnx_get_crtc_helper(drm);
> > > +
> > > +	mutex_lock(&helper->lock);
> > > +	list_add_tail(&crtc->list, &helper->xlnx_crtcs);
> > > +	mutex_unlock(&helper->lock);
> > > +}
> > > +EXPORT_SYMBOL_GPL(xlnx_crtc_register);
> > > +
> > > +void xlnx_crtc_unregister(struct drm_device *drm, struct xlnx_crtc *crtc)
> > > +{
> > > +	struct xlnx_crtc_helper *helper = xlnx_get_crtc_helper(drm);
> > > +
> > > +	mutex_lock(&helper->lock);
> > > +	list_del(&crtc->list);
> > > +	mutex_unlock(&helper->lock);
> > > +}
> > > +EXPORT_SYMBOL_GPL(xlnx_crtc_unregister);
> > > diff --git a/drivers/gpu/drm/xlnx/xlnx_crtc.h
> > b/drivers/gpu/drm/xlnx/xlnx_crtc.h
> > > new file mode 100644
> > > index 0000000..db7404e
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/xlnx/xlnx_crtc.h
> > > @@ -0,0 +1,70 @@
> > > +/*
> > > + * Xilinx DRM crtc header
> > > + *
> > > + *  Copyright (C) 2017 - 2018 Xilinx, Inc.
> > > + *
> > > + *  Author: Hyun Woo Kwon <hyun.kwon@xilinx.com>
> > > + *
> > > + * SPDX-License-Identifier: GPL-2.0
> > > + */
> > > +
> > > +#ifndef _XLNX_CRTC_H_
> > > +#define _XLNX_CRTC_H_
> > > +
> > > +/**
> > > + * struct xlnx_crtc - Xilinx CRTC device
> > > + * @crtc: DRM CRTC device
> > > + * @list: list node for Xilinx CRTC device list
> > > + * @enable_vblank: Enable vblank
> > > + * @disable_vblank: Disable vblank
> > > + * @get_align: Get the alignment requirement of CRTC device
> > > + * @get_dma_mask: Get the dma mask of CRTC device
> > > + * @get_max_width: Get the maximum supported width
> > > + * @get_max_height: Get the maximum supported height
> > > + * @get_format: Get the current format of CRTC device
> > > + */
> > > +struct xlnx_crtc {
> > > +	struct drm_crtc crtc;
> > > +	struct list_head list;
> > > +	int (*enable_vblank)(struct xlnx_crtc *crtc);
> > > +	void (*disable_vblank)(struct xlnx_crtc *crtc);
> > > +	unsigned int (*get_align)(struct xlnx_crtc *crtc);
> > > +	u64 (*get_dma_mask)(struct xlnx_crtc *crtc);
> > > +	int (*get_max_width)(struct xlnx_crtc *crtc);
> > > +	int (*get_max_height)(struct xlnx_crtc *crtc);
> > > +	uint32_t (*get_format)(struct xlnx_crtc *crtc);
> > > +};
> > > +
> > > +/*
> > > + * Helper functions: used within Xlnx DRM
> > > + */
> > > +
> > > +struct xlnx_crtc_helper;
> > > +
> > > +int xlnx_crtc_helper_enable_vblank(struct xlnx_crtc_helper *helper,
> > > +				   unsigned int crtc_id);
> > > +void xlnx_crtc_helper_disable_vblank(struct xlnx_crtc_helper *helper,
> > > +				     unsigned int crtc_id);
> > > +unsigned int xlnx_crtc_helper_get_align(struct xlnx_crtc_helper *helper);
> > > +u64 xlnx_crtc_helper_get_dma_mask(struct xlnx_crtc_helper *helper);
> > > +int xlnx_crtc_helper_get_max_width(struct xlnx_crtc_helper *helper);
> > > +int xlnx_crtc_helper_get_max_height(struct xlnx_crtc_helper *helper);
> > > +uint32_t xlnx_crtc_helper_get_format(struct xlnx_crtc_helper *helper);
> > > +
> > > +struct xlnx_crtc_helper *xlnx_crtc_helper_init(struct drm_device *drm);
> > > +void xlnx_crtc_helper_fini(struct drm_device *drm,
> > > +			   struct xlnx_crtc_helper *helper);
> > > +
> > > +/*
> > > + * CRTC registration: used by other sub-driver modules
> > > + */
> > > +
> > > +static inline struct xlnx_crtc *to_xlnx_crtc(struct drm_crtc *crtc)
> > > +{
> > > +	return container_of(crtc, struct xlnx_crtc, crtc);
> > > +}
> > > +
> > > +void xlnx_crtc_register(struct drm_device *drm, struct xlnx_crtc *crtc);
> > > +void xlnx_crtc_unregister(struct drm_device *drm, struct xlnx_crtc
> > *crtc);
> > > +
> > > +#endif /* _XLNX_CRTC_H_ */
> > > --
> > > 2.7.4
> > >
> > > _______________________________________________
> > > dri-devel mailing list
> > > dri-devel@lists.freedesktop.org
> > > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> > 
> > --
> > Daniel Vetter
> > Software Engineer, Intel Corporation
> > http://blog.ffwll.ch

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
       [not found]         ` <BY1PR0201MB10001A1C38398BFBAEC56D39D6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
@ 2018-01-11  8:06           ` Daniel Vetter
       [not found]             ` <20180111080605.GB13066-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Daniel Vetter @ 2018-01-11  8:06 UTC (permalink / raw)
  To: Hyun Kwon
  Cc: Daniel Vetter, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Tejas Upadhyay, Michal Simek

On Thu, Jan 11, 2018 at 02:05:31AM +0000, Hyun Kwon wrote:
> Hi Daniel,
> 
> > -----Original Message-----
> > From: Daniel Vetter [mailto:daniel.vetter-/w4YWyX8dFk@public.gmane.org] On Behalf Of Daniel
> > Vetter
> > Sent: Tuesday, January 09, 2018 1:54 AM
> > To: Hyun Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; Tejas
> > Upadhyay <TEJASU-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>; Michal Simek <michal.simek-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > Subject: Re: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
> > 
> > On Thu, Jan 04, 2018 at 06:05:59PM -0800, Hyun Kwon wrote:
> > > Debugfs can be used to exploit some specific setting. Main purpose
> > > is for testing and debug diagnostic.
> > >
> > > Signed-off-by: Tejas Upadhyay <tejasu-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > > Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > 
> > Hm, not sure what's the use, it seems to just be for setting/getting
> > your driver-private properties. Usually people use modetest and similar
> > tools, but if there's demand for setting properties in debugfs (we already
> > have all the infrastructure for dumping the entire kms state, see the
> > various atomic_print_state callbacks) I think that should be done with
> > generic drm code.
> > 
> > I'd drop this patch for now (if there's no other reason for it).
> 
> Thanks for the input. It'd be helpful, for my own understanding, if this
> can be elaborated a little more. We are using standard tools as well as
> custom script/tool, but some specific properties / controls are hard to
> be executed with modetest only unless we change the entire set up /
> design between each run. The debugfs is used to run all (or as much as
> possible) properties in a single run, and from what I understand, that
> doesn't violate intended debugfs usage as long as it's not treated as a
> stable ABI. The intention is to help isolate issues and enhance the
> diagnostics. I agree, in the long term, this kind of stuff should be
> handled in generic way, but would it be still reasonable to keep it
> driver specific in the meantime?

Well since the property stuff needs more work anyway I think we could do
it properly (for upstream) from the start.

What exactly is the issue with modetest? For intel we don't use it, we do
all our testing using the igt gpu tests:

https://cgit.freedesktop.org/drm/igt

A big pile of these tests also run on non-intel (and we're definitely very
much appreciating such work). But if you want just a bit of scripting,
modetest should be able to do it. If not I guess we'll need patches.
-Daniel
> 
> Thanks,
> -hyun
> 
> > -Daniel
> > 
> > > ---
> > >  drivers/gpu/drm/xlnx/Kconfig       |  21 +++
> > >  drivers/gpu/drm/xlnx/zynqmp_disp.c | 326
> > +++++++++++++++++++++++++++++++++++++
> > >  drivers/gpu/drm/xlnx/zynqmp_dp.c   | 304
> > ++++++++++++++++++++++++++++++++++
> > >  3 files changed, 651 insertions(+)
> > >
> > > diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
> > > index 7c5529c..befce0f 100644
> > > --- a/drivers/gpu/drm/xlnx/Kconfig
> > > +++ b/drivers/gpu/drm/xlnx/Kconfig
> > > @@ -21,3 +21,24 @@ config DRM_ZYNQMP_DPSUB
> > >  	  this option if you have a Xilinx ZynqMP SoC with DisplayPort
> > >  	  subsystem. The driver provides the kernel mode setting
> > >  	  functionlaities for ZynqMP DP subsystem.
> > > +
> > > +config DRM_ZYNQMP_DISP_DEBUG_FS
> > > +	bool "ZynqMP Display debugfs"
> > > +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> > > +	select DRM_ZYNQMP_DP_DEBUG_FS
> > > +	help
> > > +	  Enable the debugfs code for DP Sub driver. The debugfs code
> > > +	  enables debugging or testing related features. It exposes some
> > > +	  low level controls to the user space to help testing automation,
> > > +	  as well as can enable additional diagnostic or statistical
> > > +	  information.
> > > +
> > > +config DRM_ZYNQMP_DP_DEBUG_FS
> > > +	bool "ZynqMP DP debugfs"
> > > +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> > > +	help
> > > +	  Enable the debugfs code for DP driver. The debugfs code
> > > +	  enables debugging or testing related features. It exposes some
> > > +	  low level controls to the user space to help testing automation,
> > > +	  as well as can enable additional diagnostic or statistical
> > > +	  information.
> > > diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > > index 68f829c..9fe6d49 100644
> > > --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > > +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > > @@ -17,6 +17,7 @@
> > >  #include <drm/drm_plane_helper.h>
> > >
> > >  #include <linux/clk.h>
> > > +#include <linux/debugfs.h>
> > >  #include <linux/device.h>
> > >  #include <linux/dmaengine.h>
> > >  #include <linux/interrupt.h>
> > > @@ -508,6 +509,325 @@ static void zynqmp_disp_set(void __iomem
> > *base, int offset, u32 set)
> > >  	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) |
> > set);
> > >  }
> > >
> > > +#ifdef CONFIG_DRM_ZYNQMP_DISP_DEBUG_FS
> > > +
> > > +#define ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE	32UL
> > > +#define ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL	0xFFF
> > > +#define IN_RANGE(x, min, max) ({		\
> > > +		typeof(x) _x = (x);		\
> > > +		_x >= (min) && _x <= (max); })
> > > +
> > > +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> > > +enum zynqmp_disp_testcases {
> > > +	DP_SUB_TC_BG_COLOR,
> > > +	DP_SUB_TC_OUTPUT_FMT,
> > > +	DP_SUB_TC_NONE
> > > +};
> > > +
> > > +struct zynqmp_disp_debugfs {
> > > +	enum zynqmp_disp_testcases testcase;
> > > +	u16 r_value;
> > > +	u16 g_value;
> > > +	u16 b_value;
> > > +	u32 output_fmt;
> > > +	struct zynqmp_disp *zynqmp_disp;
> > > +};
> > > +
> > > +static struct dentry *zynqmp_disp_debugfs_dir;
> > > +struct zynqmp_disp_debugfs disp_debugfs;
> > > +struct zynqmp_disp_debugfs_request {
> > > +	const char *req;
> > > +	enum zynqmp_disp_testcases tc;
> > > +	ssize_t (*read_handler)(char **kern_buff);
> > > +	ssize_t (*write_handler)(char **cmd);
> > > +};
> > > +
> > > +static void
> > > +zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int
> > id);
> > > +static s64 zynqmp_disp_debugfs_argument_value(char *arg)
> > > +{
> > > +	s64 value;
> > > +
> > > +	if (!arg)
> > > +		return -1;
> > > +
> > > +	if (!kstrtos64(arg, 0, &value))
> > > +		return value;
> > > +
> > > +	return -1;
> > > +}
> > > +
> > > +static ssize_t
> > > +zynqmp_disp_debugfs_background_color_write(char **disp_test_arg)
> > > +{
> > > +	char *r_color, *g_color, *b_color;
> > > +	s64 r_val, g_val, b_val;
> > > +
> > > +	r_color = strsep(disp_test_arg, " ");
> > > +	g_color = strsep(disp_test_arg, " ");
> > > +	b_color = strsep(disp_test_arg, " ");
> > > +
> > > +	/* char * to int conversion */
> > > +	r_val = zynqmp_disp_debugfs_argument_value(r_color);
> > > +	g_val = zynqmp_disp_debugfs_argument_value(g_color);
> > > +	b_val = zynqmp_disp_debugfs_argument_value(b_color);
> > > +
> > > +	if (!(IN_RANGE(r_val, 0,
> > ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> > > +	      IN_RANGE(g_val, 0,
> > ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> > > +	      IN_RANGE(b_val, 0,
> > ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL)))
> > > +		return -EINVAL;
> > > +
> > > +	disp_debugfs.r_value = r_val;
> > > +	disp_debugfs.g_value = g_val;
> > > +	disp_debugfs.b_value = b_val;
> > > +
> > > +	disp_debugfs.testcase = DP_SUB_TC_BG_COLOR;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static ssize_t
> > > +zynqmp_disp_debugfs_output_display_format_write(char
> > **disp_test_arg)
> > > +{
> > > +	char *output_format;
> > > +	struct zynqmp_disp *disp = disp_debugfs.zynqmp_disp;
> > > +
> > > +	/* Read the value from a user value */
> > > +	output_format = strsep(disp_test_arg, " ");
> > > +	if (strncmp(output_format, "rgb", 3) == 0) {
> > > +		disp_debugfs.output_fmt =
> > > +			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB;
> > > +	} else if (strncmp(output_format, "ycbcr444", 8) == 0) {
> > > +		disp_debugfs.output_fmt =
> > > +
> > 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444;
> > > +	} else if (strncmp(output_format, "ycbcr422", 8) == 0) {
> > > +		disp_debugfs.output_fmt =
> > > +
> > 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422;
> > > +	} else if (strncmp(output_format, "yonly", 5) == 0) {
> > > +		disp_debugfs.output_fmt =
> > > +
> > 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY;
> > > +	} else {
> > > +		dev_err(disp->dev, "Invalid output format\n");
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	disp_debugfs.testcase = DP_SUB_TC_OUTPUT_FMT;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static ssize_t
> > > +zynqmp_disp_debugfs_output_display_format_read(char **kern_buff)
> > > +{
> > > +	size_t out_str_len;
> > > +
> > > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > > +	disp_debugfs.output_fmt = 0;
> > > +
> > > +	out_str_len = strlen("Success");
> > > +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > out_str_len);
> > > +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static ssize_t
> > > +zynqmp_disp_debugfs_background_color_read(char **kern_buff)
> > > +{
> > > +	size_t out_str_len;
> > > +
> > > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > > +	disp_debugfs.r_value = 0;
> > > +	disp_debugfs.g_value = 0;
> > > +	disp_debugfs.b_value = 0;
> > > +
> > > +	out_str_len = strlen("Success");
> > > +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > out_str_len);
> > > +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> > > +struct zynqmp_disp_debugfs_request disp_debugfs_reqs[] = {
> > > +	{"BACKGROUND_COLOR", DP_SUB_TC_BG_COLOR,
> > > +		zynqmp_disp_debugfs_background_color_read,
> > > +		zynqmp_disp_debugfs_background_color_write},
> > > +	{"OUTPUT_DISPLAY_FORMAT", DP_SUB_TC_OUTPUT_FMT,
> > > +		zynqmp_disp_debugfs_output_display_format_read,
> > > +		zynqmp_disp_debugfs_output_display_format_write},
> > > +};
> > > +
> > > +static ssize_t
> > > +zynqmp_disp_debugfs_write(struct file *f, const char __user *buf,
> > > +			  size_t size, loff_t *pos)
> > > +{
> > > +	char *kern_buff, *disp_test_req, *kern_buff_start;
> > > +	int ret;
> > > +	unsigned int i;
> > > +
> > > +	if (*pos != 0 || size <= 0)
> > > +		return -EINVAL;
> > > +
> > > +	if (disp_debugfs.testcase != DP_SUB_TC_NONE)
> > > +		return -EBUSY;
> > > +
> > > +	kern_buff = kzalloc(size, GFP_KERNEL);
> > > +	if (!kern_buff)
> > > +		return -ENOMEM;
> > > +	kern_buff_start = kern_buff;
> > > +
> > > +	ret = strncpy_from_user(kern_buff, buf, size);
> > > +	if (ret < 0) {
> > > +		kfree(kern_buff_start);
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* Read the testcase name and argument from a user request */
> > > +	disp_test_req = strsep(&kern_buff, " ");
> > > +
> > > +	for (i = 0; i < ARRAY_SIZE(disp_debugfs_reqs); i++) {
> > > +		if (!strcasecmp(disp_test_req, disp_debugfs_reqs[i].req))
> > > +			if (!disp_debugfs_reqs[i].write_handler(&kern_buff))
> > {
> > > +				kfree(kern_buff_start);
> > > +				return size;
> > > +			}
> > > +	}
> > > +	kfree(kern_buff_start);
> > > +	return -EINVAL;
> > > +}
> > > +
> > > +static ssize_t zynqmp_disp_debugfs_read(struct file *f, char __user *buf,
> > > +					size_t size, loff_t *pos)
> > > +{
> > > +	char *kern_buff = NULL;
> > > +	size_t kern_buff_len, out_str_len;
> > > +	enum zynqmp_disp_testcases tc;
> > > +	int ret;
> > > +
> > > +	if (size <= 0)
> > > +		return -EINVAL;
> > > +
> > > +	if (*pos != 0)
> > > +		return 0;
> > > +
> > > +	kern_buff = kzalloc(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > GFP_KERNEL);
> > > +	if (!kern_buff) {
> > > +		disp_debugfs.testcase = DP_SUB_TC_NONE;
> > > +		return -ENOMEM;
> > > +	}
> > > +
> > > +	tc = disp_debugfs.testcase;
> > > +	if (tc == DP_SUB_TC_NONE) {
> > > +		out_str_len = strlen("No testcase executed");
> > > +		out_str_len =
> > min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > > +				  out_str_len);
> > > +		snprintf(kern_buff, out_str_len, "%s", "No testcase
> > executed");
> > > +	} else {
> > > +		ret = disp_debugfs_reqs[tc].read_handler(&kern_buff);
> > > +		if (ret) {
> > > +			kfree(kern_buff);
> > > +			return ret;
> > > +		}
> > > +	}
> > > +
> > > +	kern_buff_len = strlen(kern_buff);
> > > +	size = min(size, kern_buff_len);
> > > +
> > > +	ret = copy_to_user(buf, kern_buff, size);
> > > +
> > > +	kfree(kern_buff);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	*pos = size + 1;
> > > +	return size;
> > > +}
> > > +
> > > +static const struct file_operations fops_zynqmp_disp_dbgfs = {
> > > +	.owner = THIS_MODULE,
> > > +	.read = zynqmp_disp_debugfs_read,
> > > +	.write = zynqmp_disp_debugfs_write,
> > > +};
> > > +
> > > +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> > > +{
> > > +	int err;
> > > +	struct dentry *zynqmp_disp_debugfs_file;
> > > +
> > > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > > +	disp_debugfs.zynqmp_disp = disp;
> > > +
> > > +	zynqmp_disp_debugfs_dir = debugfs_create_dir("disp", NULL);
> > > +	if (!zynqmp_disp_debugfs_dir) {
> > > +		dev_err(disp->dev, "debugfs_create_dir failed\n");
> > > +		return -ENODEV;
> > > +	}
> > > +
> > > +	zynqmp_disp_debugfs_file =
> > > +		debugfs_create_file("testcase", 0444,
> > > +				    zynqmp_disp_debugfs_dir, NULL,
> > > +				    &fops_zynqmp_disp_dbgfs);
> > > +	if (!zynqmp_disp_debugfs_file) {
> > > +		dev_err(disp->dev, "debugfs_create_file testcase failed\n");
> > > +		err = -ENODEV;
> > > +		goto err_dbgfs;
> > > +	}
> > > +	return 0;
> > > +
> > > +err_dbgfs:
> > > +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> > > +	zynqmp_disp_debugfs_dir = NULL;
> > > +	return err;
> > > +}
> > > +
> > > +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> > > +{
> > > +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> > > +	zynqmp_disp_debugfs_dir = NULL;
> > > +}
> > > +
> > > +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> > > +{
> > > +	if (disp_debugfs.testcase == DP_SUB_TC_BG_COLOR) {
> > > +		zynqmp_disp_write(disp->blend.base,
> > > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_0,
> > > +				  disp_debugfs.r_value);
> > > +		zynqmp_disp_write(disp->blend.base,
> > > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_1,
> > > +				  disp_debugfs.g_value);
> > > +		zynqmp_disp_write(disp->blend.base,
> > > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_2,
> > > +				  disp_debugfs.b_value);
> > > +	}
> > > +}
> > > +
> > > +static void
> > > +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> > > +{
> > > +	if (disp_debugfs.testcase == DP_SUB_TC_OUTPUT_FMT)
> > > +		zynqmp_disp_set_output_fmt(disp,
> > disp_debugfs.output_fmt);
> > > +}
> > > +#else
> > > +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> > > +{
> > > +}
> > > +
> > > +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > > +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> > > +{
> > > +}
> > > +
> > > +static void
> > > +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> > > +{
> > > +}
> > > +#endif /* CONFIG_DP_DEBUG_FS */
> > > +
> > >  /*
> > >   * Clock functions
> > >   */
> > > @@ -597,6 +917,8 @@ zynqmp_disp_blend_set_output_fmt(struct
> > zynqmp_disp_blend *blend, u32 fmt)
> > >  	u32 *offsets;
> > >  	u32 offset, i;
> > >
> > > +	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422)
> > > +		fmt |=
> > ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE;
> > >  	zynqmp_disp_write(blend->base,
> > ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
> > >  	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
> > >  		coeffs = reset_coeffs;
> > > @@ -1941,6 +2263,7 @@ static void zynqmp_disp_set_bg_color(struct
> > zynqmp_disp *disp,
> > >  				     u32 c0, u32 c1, u32 c2)
> > >  {
> > >  	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
> > > +	zynqmp_disp_debugfs_bg_color(disp);
> > >  }
> > >
> > >  /**
> > > @@ -2572,6 +2895,7 @@ zynqmp_disp_crtc_atomic_enable(struct
> > drm_crtc *crtc,
> > >  		return;
> > >  	}
> > >  	zynqmp_disp_set_output_fmt(disp, disp->color);
> > > +	zynqmp_disp_set_debugfs_output_fmt(disp);
> > >  	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp-
> > >bg_c2);
> > >  	zynqmp_disp_enable(disp);
> > >  	/* Delay of 3 vblank intervals for timing gen to be stable */
> > > @@ -2911,6 +3235,7 @@ int zynqmp_disp_probe(struct
> > platform_device *pdev)
> > >  	ret = zynqmp_disp_layer_create(disp);
> > >  	if (ret)
> > >  		goto error_aclk;
> > > +	zynqmp_disp_debugfs_init(disp);
> > >
> > >  	return 0;
> > >
> > > @@ -2924,6 +3249,7 @@ int zynqmp_disp_remove(struct
> > platform_device *pdev)
> > >  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> > >  	struct zynqmp_disp *disp = dpsub->disp;
> > >
> > > +	zynqmp_disp_debugfs_exit(disp);
> > >  	zynqmp_disp_layer_destroy(disp);
> > >  	if (disp->audclk)
> > >  		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
> > > diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > > index ce3c7c5..66fbad0 100644
> > > --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > > +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > > @@ -16,6 +16,7 @@
> > >  #include <drm/drm_dp_helper.h>
> > >  #include <drm/drm_of.h>
> > >
> > > +#include <linux/debugfs.h>
> > >  #include <linux/delay.h>
> > >  #include <linux/device.h>
> > >  #include <linux/module.h>
> > > @@ -371,6 +372,306 @@ static void zynqmp_dp_set(void __iomem
> > *base, int offset, u32 set)
> > >  }
> > >
> > >  /*
> > > + * Debugfs functions
> > > + */
> > > +
> > > +#ifdef CONFIG_DRM_ZYNQMP_DP_DEBUG_FS
> > > +
> > > +#define ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE	32UL
> > > +#define ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR	"255"
> > > +#define IN_RANGE(x, min, max) ({		\
> > > +		typeof(x) _x = (x);		\
> > > +		_x >= (min) && _x <= (max); })
> > > +
> > > +/* Match zynqmp_dp_testcases vs debugfs_reqs[] entry */
> > > +enum zynqmp_dp_testcases {
> > > +	DP_TC_LINK_RATE,
> > > +	DP_TC_LANE_COUNT,
> > > +	DP_TC_OUTPUT_FMT,
> > > +	DP_TC_NONE
> > > +};
> > > +
> > > +struct zynqmp_dp_debugfs {
> > > +	enum zynqmp_dp_testcases testcase;
> > > +	u8 link_rate;
> > > +	u8 lane_cnt;
> > > +	u8 old_output_fmt;
> > > +	struct zynqmp_dp *dp;
> > > +};
> > > +
> > > +static struct dentry *zynqmp_dp_debugfs_dir;
> > > +static struct zynqmp_dp_debugfs dp_debugfs;
> > > +struct zynqmp_dp_debugfs_request {
> > > +	const char *req;
> > > +	enum zynqmp_dp_testcases tc;
> > > +	ssize_t (*read_handler)(char **kern_buff);
> > > +	ssize_t (*write_handler)(char **cmd);
> > > +};
> > > +
> > > +static s64 zynqmp_dp_debugfs_argument_value(char *arg)
> > > +{
> > > +	s64 value;
> > > +
> > > +	if (!arg)
> > > +		return -1;
> > > +
> > > +	if (!kstrtos64(arg, 0, &value))
> > > +		return value;
> > > +
> > > +	return -1;
> > > +}
> > > +
> > > +static ssize_t zynqmp_dp_debugfs_max_linkrate_write(char
> > **dp_test_arg)
> > > +{
> > > +	char *link_rate_arg;
> > > +	s64 link_rate;
> > > +
> > > +	link_rate_arg = strsep(dp_test_arg, " ");
> > > +	link_rate = zynqmp_dp_debugfs_argument_value(link_rate_arg);
> > > +	if (link_rate < 0 || (link_rate != DP_HIGH_BIT_RATE2 &&
> > > +			      link_rate != DP_HIGH_BIT_RATE &&
> > > +			      link_rate != DP_REDUCED_BIT_RATE))
> > > +		return -EINVAL;
> > > +
> > > +	dp_debugfs.link_rate = drm_dp_link_rate_to_bw_code(link_rate);
> > > +	dp_debugfs.testcase = DP_TC_LINK_RATE;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static ssize_t zynqmp_dp_debugfs_max_lanecnt_write(char
> > **dp_test_arg)
> > > +{
> > > +	char *lane_cnt_arg;
> > > +	s64 lane_count;
> > > +
> > > +	lane_cnt_arg = strsep(dp_test_arg, " ");
> > > +	lane_count = zynqmp_dp_debugfs_argument_value(lane_cnt_arg);
> > > +	if (lane_count < 0 || !IN_RANGE(lane_count, 1,
> > > +					ZYNQMP_DP_MAX_LANES))
> > > +		return -EINVAL;
> > > +
> > > +	dp_debugfs.lane_cnt = lane_count;
> > > +	dp_debugfs.testcase = DP_TC_LANE_COUNT;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static ssize_t zynqmp_dp_debugfs_max_linkrate_read(char **kern_buff)
> > > +{
> > > +	struct zynqmp_dp *dp = dp_debugfs.dp;
> > > +	size_t output_str_len;
> > > +	u8 dpcd_link_bw;
> > > +	int ret;
> > > +
> > > +	dp_debugfs.testcase = DP_TC_NONE;
> > > +	dp_debugfs.link_rate = 0;
> > > +
> > > +	/* Getting Sink Side Link Rate */
> > > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LINK_BW_SET,
> > &dpcd_link_bw);
> > > +	if (ret < 0) {
> > > +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> > > +		kfree(*kern_buff);
> > > +		return ret;
> > > +	}
> > > +
> > > +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> > > +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> > output_str_len);
> > > +	snprintf(*kern_buff, output_str_len, "%u", dpcd_link_bw);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static ssize_t zynqmp_dp_debugfs_max_lanecnt_read(char **kern_buff)
> > > +{
> > > +	struct zynqmp_dp *dp = dp_debugfs.dp;
> > > +	size_t output_str_len;
> > > +	u8 dpcd_lane_cnt;
> > > +	int ret;
> > > +
> > > +	dp_debugfs.testcase = DP_TC_NONE;
> > > +	dp_debugfs.lane_cnt = 0;
> > > +
> > > +	/* Getting Sink Side Lane Count */
> > > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_COUNT_SET,
> > &dpcd_lane_cnt);
> > > +	if (ret < 0) {
> > > +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> > > +		kfree(*kern_buff);
> > > +		return ret;
> > > +	}
> > > +
> > > +	dpcd_lane_cnt &= DP_LANE_COUNT_MASK;
> > > +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> > > +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> > output_str_len);
> > > +	snprintf(*kern_buff, output_str_len, "%u", dpcd_lane_cnt);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +/* Match zynqmp_dp_testcases vs dp_debugfs_reqs[] entry */
> > > +static struct zynqmp_dp_debugfs_request debugfs_reqs[] = {
> > > +	{"LINK_RATE", DP_TC_LINK_RATE,
> > > +			zynqmp_dp_debugfs_max_linkrate_read,
> > > +			zynqmp_dp_debugfs_max_linkrate_write},
> > > +	{"LANE_COUNT", DP_TC_LANE_COUNT,
> > > +			zynqmp_dp_debugfs_max_lanecnt_read,
> > > +			zynqmp_dp_debugfs_max_lanecnt_write},
> > > +};
> > > +
> > > +static ssize_t zynqmp_dp_debugfs_read(struct file *f, char __user *buf,
> > > +				      size_t size, loff_t *pos)
> > > +{
> > > +	char *kern_buff = NULL;
> > > +	size_t kern_buff_len, out_str_len;
> > > +	enum zynqmp_dp_testcases tc;
> > > +	int ret;
> > > +
> > > +	if (size <= 0)
> > > +		return -EINVAL;
> > > +
> > > +	if (*pos != 0)
> > > +		return 0;
> > > +
> > > +	kern_buff = kzalloc(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> > GFP_KERNEL);
> > > +	if (!kern_buff) {
> > > +		dp_debugfs.testcase = DP_TC_NONE;
> > > +		return -ENOMEM;
> > > +	}
> > > +
> > > +	tc = dp_debugfs.testcase;
> > > +	if (tc == DP_TC_NONE) {
> > > +		out_str_len = strlen("No testcase executed");
> > > +		out_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> > out_str_len);
> > > +		snprintf(kern_buff, out_str_len, "%s", "No testcase
> > executed");
> > > +	} else {
> > > +		ret = debugfs_reqs[tc].read_handler(&kern_buff);
> > > +		if (ret) {
> > > +			kfree(kern_buff);
> > > +			return ret;
> > > +		}
> > > +	}
> > > +
> > > +	kern_buff_len = strlen(kern_buff);
> > > +	size = min(size, kern_buff_len);
> > > +
> > > +	ret = copy_to_user(buf, kern_buff, size);
> > > +
> > > +	kfree(kern_buff);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	*pos = size + 1;
> > > +	return size;
> > > +}
> > > +
> > > +static ssize_t
> > > +zynqmp_dp_debugfs_write(struct file *f, const char __user *buf,
> > > +			size_t size, loff_t *pos)
> > > +{
> > > +	char *kern_buff, *kern_buff_start;
> > > +	char *dp_test_req;
> > > +	int ret;
> > > +	int i;
> > > +
> > > +	if (*pos != 0 || size <= 0)
> > > +		return -EINVAL;
> > > +
> > > +	if (dp_debugfs.testcase != DP_TC_NONE)
> > > +		return -EBUSY;
> > > +
> > > +	kern_buff = kzalloc(size, GFP_KERNEL);
> > > +	if (!kern_buff)
> > > +		return -ENOMEM;
> > > +	kern_buff_start = kern_buff;
> > > +
> > > +	ret = strncpy_from_user(kern_buff, buf, size);
> > > +	if (ret < 0) {
> > > +		kfree(kern_buff_start);
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* Read the testcase name and argument from a user request */
> > > +	dp_test_req = strsep(&kern_buff, " ");
> > > +
> > > +	for (i = 0; i < ARRAY_SIZE(debugfs_reqs); i++) {
> > > +		if (!strcasecmp(dp_test_req, debugfs_reqs[i].req))
> > > +			if (!debugfs_reqs[i].write_handler(&kern_buff)) {
> > > +				kfree(kern_buff_start);
> > > +				return size;
> > > +			}
> > > +	}
> > > +
> > > +	kfree(kern_buff_start);
> > > +	return -EINVAL;
> > > +}
> > > +
> > > +static const struct file_operations fops_zynqmp_dp_dbgfs = {
> > > +	.owner = THIS_MODULE,
> > > +	.read = zynqmp_dp_debugfs_read,
> > > +	.write = zynqmp_dp_debugfs_write,
> > > +};
> > > +
> > > +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> > > +{
> > > +	int err;
> > > +	struct dentry *zynqmp_dp_debugfs_file;
> > > +
> > > +	dp_debugfs.testcase = DP_TC_NONE;
> > > +	dp_debugfs.dp = dp;
> > > +
> > > +	zynqmp_dp_debugfs_dir = debugfs_create_dir("dp", NULL);
> > > +	if (!zynqmp_dp_debugfs_dir) {
> > > +		dev_err(dp->dev, "debugfs_create_dir failed\n");
> > > +		return -ENODEV;
> > > +	}
> > > +
> > > +	zynqmp_dp_debugfs_file =
> > > +		debugfs_create_file("testcase", 0444,
> > zynqmp_dp_debugfs_dir,
> > > +				    NULL, &fops_zynqmp_dp_dbgfs);
> > > +	if (!zynqmp_dp_debugfs_file) {
> > > +		dev_err(dp->dev, "debugfs_create_file testcase failed\n");
> > > +		err = -ENODEV;
> > > +		goto err_dbgfs;
> > > +	}
> > > +	return 0;
> > > +
> > > +err_dbgfs:
> > > +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> > > +	zynqmp_dp_debugfs_dir = NULL;
> > > +	return err;
> > > +}
> > > +
> > > +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> > > +{
> > > +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> > > +	zynqmp_dp_debugfs_dir = NULL;
> > > +}
> > > +
> > > +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> > > +{
> > > +	dp->mode.bw_code =
> > > +		dp_debugfs.link_rate ? dp_debugfs.link_rate : dp-
> > >mode.bw_code;
> > > +	dp->mode.lane_cnt =
> > > +		dp_debugfs.lane_cnt ? dp_debugfs.lane_cnt : dp-
> > >mode.lane_cnt;
> > > +}
> > > +
> > > +#else /* DRM_ZYNQMP_DP_DEBUG_FS */
> > > +
> > > +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> > > +{
> > > +	return 0;
> > > +}
> > > +
> > > +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> > > +{
> > > +}
> > > +
> > > +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> > > +{
> > > +}
> > > +
> > > +#endif /* DRM_ZYNQMP_DP_DEBUG_FS */
> > > +
> > > +/*
> > >   * Internal functions: used by zynqmp_disp.c
> > >   */
> > >
> > > @@ -597,6 +898,7 @@ static int zynqmp_dp_mode_configure(struct
> > zynqmp_dp *dp, int pclock,
> > >  			dp->mode.bw_code = bws[i];
> > >  			dp->mode.lane_cnt = lane_cnt;
> > >  			dp->mode.pclock = pclock;
> > > +			zynqmp_dp_debugfs_mode_config(dp);
> > >  			return dp->mode.bw_code;
> > >  		}
> > >  	}
> > > @@ -1840,6 +2142,7 @@ int zynqmp_dp_probe(struct platform_device
> > *pdev)
> > >  	dpsub = platform_get_drvdata(pdev);
> > >  	dpsub->dp = dp;
> > >  	dp->dpsub = dpsub;
> > > +	zynqmp_dp_debugfs_init(dp);
> > >
> > >  	return 0;
> > >
> > > @@ -1855,6 +2158,7 @@ int zynqmp_dp_remove(struct
> > platform_device *pdev)
> > >  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> > >  	struct zynqmp_dp *dp = dpsub->dp;
> > >
> > > +	zynqmp_dp_debugfs_exit(dp);
> > >  	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
> > >  	drm_dp_aux_unregister(&dp->aux);
> > >  	zynqmp_dp_exit_phy(dp);
> > > --
> > > 2.7.4
> > >
> > > _______________________________________________
> > > dri-devel mailing list
> > > dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> > > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> > 
> > --
> > Daniel Vetter
> > Software Engineer, Intel Corporation
> > http://blog.ffwll.ch

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver
       [not found]         ` <BY1PR0201MB10002CAFCC860052538BA14DD6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
@ 2018-01-11  8:07           ` Daniel Vetter
       [not found]             ` <20180111080738.GC13066-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Daniel Vetter @ 2018-01-11  8:07 UTC (permalink / raw)
  To: Hyun Kwon
  Cc: Daniel Vetter, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

On Thu, Jan 11, 2018 at 02:07:08AM +0000, Hyun Kwon wrote:
> Hi Daniel,
> 
> > -----Original Message-----
> > From: Daniel Vetter [mailto:daniel.vetter-/w4YWyX8dFk@public.gmane.org] On Behalf Of Daniel
> > Vetter
> > Sent: Tuesday, January 09, 2018 1:57 AM
> > To: Hyun Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; Michal
> > Simek <michal.simek-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > Subject: Re: [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM
> > KMS driver
> > 
> > On Thu, Jan 04, 2018 at 06:05:49PM -0800, Hyun Kwon wrote:
> > > Hi,
> > >
> > > This patchset adds the DRM KMS driver for Xilinx ZynqMP DisplayPort
> > > subsystem. The Xilinx ZynqMP SoC has a hardened full display pipeline
> > > which supports blending of up to 2 planes, and the encoder is
> > > DisplayPort v1.2 compatible.
> > >
> > > This series mainly includes 2 sets: Xilinx DRM KMS (patch 1/10 - 5/10)
> > > and ZynqMP DP subsystem drivers (patch 6/10 - 10/10).
> > >
> > > The Xilinx DRM KMS is intended as a common layer shared across other
> > > (upcoming) Xilinx sub-drivers. It helps sub-drivers for both hardened as
> > > well as soft IPs interoperate together.
> > >
> > > ZynqMP DP subsystem driver is a sub-driver that implements
> > corresponding
> > > drm objects (crtc, plane, encoder, connector,,,) for ZynqMP SoC display
> > > pipeline. The entire pipeline is mainly partitioned into 2 blocks:
> > > generic display logic (zynqmp_disp.c) such as blending, csc,,, and the
> > > DP transmitter logic (zynqmp_dp.c).
> > 
> > I read through it all (well mostly the drm relevant bits, not your backend
> > code) and looks fairly resonable. Few minor clenaups and code removals
> > tbh.
> > 
> > Wrt merging/maintianing, do you want to maintain it as part of the
> > drm-misc small drivers group? Highly recommended imo. See
> > 
> > https://01.org/linuxgraphics/gfx-docs/maintainer-tools/drm-
> > misc.html#small-drivers
> > 
> > for details. Ideally we'd need 2 xilinx maintainers to be able to push
> > patches & cross-review stuff.
> 
> I don't have any preference on how to maintain, so I'll follow your
> suggestion. One thing that may be worth a note is that there is sizable
> amount of development within Xilinx, and those will come in near future
> (considering what can be done with FPGA :-)). I'll look for the 2nd
> reviewer, and specify that in the next patch if found.

If the xilinx activity gets too much we can always split things up again.
But if it's just the occasional burst (around a new product for example),
then drm-misc has ample of bandwidth to absorb that.

And yes the idea is very much that all regular contributors would have
commit rights too. All to reduce friction and make it easier to
contribute.
-Daniel

> 
> Thanks,
> -hyun
> 
> > -Daniel
> > 
> > >
> > > Thanks,
> > > -hyun
> > >
> > > Hyun Kwon (10):
> > >   dt-bindings: display: xlnx: Add Xilinx kms bindings
> > >   drm: xlnx: Add xlnx crtc of Xilinx DRM KMS
> > >   drm: xlnx: Add xlnx fb of Xilinx DRM KMS
> > >   drm: xlnx: Add xlnx gem of Xilinx DRM KMS
> > >   drm: xlnx: Xilinx DRM KMS driver
> > >   dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings
> > >   drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display
> > >   drm: xlnx: DRM KMS driver for Xilinx ZynqMP DisplayPort
> > >   drm: xlnx: ZynqMP DP subsystem DRM KMS driver
> > >   drm: xlnx: zynqmp: Add debugfs
> > >
> > >  .../devicetree/bindings/display/xlnx/xlnx,kms.txt  |   20 +
> > >  .../bindings/display/xlnx/xlnx,zynqmp-dpsub.txt    |   94 +
> > >  MAINTAINERS                                        |    8 +
> > >  drivers/gpu/drm/Kconfig                            |    2 +
> > >  drivers/gpu/drm/Makefile                           |    1 +
> > >  drivers/gpu/drm/xlnx/Kconfig                       |   44 +
> > >  drivers/gpu/drm/xlnx/Makefile                      |    5 +
> > >  drivers/gpu/drm/xlnx/xlnx_crtc.c                   |  195 ++
> > >  drivers/gpu/drm/xlnx/xlnx_crtc.h                   |   70 +
> > >  drivers/gpu/drm/xlnx/xlnx_drv.c                    |  436 +++
> > >  drivers/gpu/drm/xlnx/xlnx_drv.h                    |   22 +
> > >  drivers/gpu/drm/xlnx/xlnx_fb.c                     |  468 +++
> > >  drivers/gpu/drm/xlnx/xlnx_fb.h                     |   30 +
> > >  drivers/gpu/drm/xlnx/xlnx_gem.c                    |   39 +
> > >  drivers/gpu/drm/xlnx/xlnx_gem.h                    |   18 +
> > >  drivers/gpu/drm/xlnx/zynqmp_disp.c                 | 3261
> > ++++++++++++++++++++
> > >  drivers/gpu/drm/xlnx/zynqmp_disp.h                 |   28 +
> > >  drivers/gpu/drm/xlnx/zynqmp_dp.c                   | 2168 +++++++++++++
> > >  drivers/gpu/drm/xlnx/zynqmp_dp.h                   |   29 +
> > >  drivers/gpu/drm/xlnx/zynqmp_dpsub.c                |  141 +
> > >  drivers/gpu/drm/xlnx/zynqmp_dpsub.h                |   19 +
> > >  21 files changed, 7098 insertions(+)
> > >  create mode 100644
> > Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> > >  create mode 100644
> > Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.txt
> > >  create mode 100644 drivers/gpu/drm/xlnx/Kconfig
> > >  create mode 100644 drivers/gpu/drm/xlnx/Makefile
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.c
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_crtc.h
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.c
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_drv.h
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.c
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_fb.h
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.c
> > >  create mode 100644 drivers/gpu/drm/xlnx/xlnx_gem.h
> > >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.c
> > >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_disp.h
> > >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.c
> > >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dp.h
> > >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.c
> > >  create mode 100644 drivers/gpu/drm/xlnx/zynqmp_dpsub.h
> > >
> > > --
> > > 2.7.4
> > >
> > > _______________________________________________
> > > dri-devel mailing list
> > > dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> > > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> > 
> > --
> > Daniel Vetter
> > Software Engineer, Intel Corporation
> > http://blog.ffwll.ch

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver
       [not found]             ` <20180111080738.GC13066-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
@ 2018-01-11  8:16               ` Michal Simek
  0 siblings, 0 replies; 34+ messages in thread
From: Michal Simek @ 2018-01-11  8:16 UTC (permalink / raw)
  To: Daniel Vetter, Hyun Kwon
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

On 11.1.2018 09:07, Daniel Vetter wrote:
> On Thu, Jan 11, 2018 at 02:07:08AM +0000, Hyun Kwon wrote:
>> Hi Daniel,
>>
>>> -----Original Message-----
>>> From: Daniel Vetter [mailto:daniel.vetter-/w4YWyX8dFk@public.gmane.org] On Behalf Of Daniel
>>> Vetter
>>> Sent: Tuesday, January 09, 2018 1:57 AM
>>> To: Hyun Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
>>> Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; Michal
>>> Simek <michal.simek-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
>>> Subject: Re: [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM
>>> KMS driver
>>>
>>> On Thu, Jan 04, 2018 at 06:05:49PM -0800, Hyun Kwon wrote:
>>>> Hi,
>>>>
>>>> This patchset adds the DRM KMS driver for Xilinx ZynqMP DisplayPort
>>>> subsystem. The Xilinx ZynqMP SoC has a hardened full display pipeline
>>>> which supports blending of up to 2 planes, and the encoder is
>>>> DisplayPort v1.2 compatible.
>>>>
>>>> This series mainly includes 2 sets: Xilinx DRM KMS (patch 1/10 - 5/10)
>>>> and ZynqMP DP subsystem drivers (patch 6/10 - 10/10).
>>>>
>>>> The Xilinx DRM KMS is intended as a common layer shared across other
>>>> (upcoming) Xilinx sub-drivers. It helps sub-drivers for both hardened as
>>>> well as soft IPs interoperate together.
>>>>
>>>> ZynqMP DP subsystem driver is a sub-driver that implements
>>> corresponding
>>>> drm objects (crtc, plane, encoder, connector,,,) for ZynqMP SoC display
>>>> pipeline. The entire pipeline is mainly partitioned into 2 blocks:
>>>> generic display logic (zynqmp_disp.c) such as blending, csc,,, and the
>>>> DP transmitter logic (zynqmp_dp.c).
>>>
>>> I read through it all (well mostly the drm relevant bits, not your backend
>>> code) and looks fairly resonable. Few minor clenaups and code removals
>>> tbh.
>>>
>>> Wrt merging/maintianing, do you want to maintain it as part of the
>>> drm-misc small drivers group? Highly recommended imo. See
>>>
>>> https://01.org/linuxgraphics/gfx-docs/maintainer-tools/drm-
>>> misc.html#small-drivers
>>>
>>> for details. Ideally we'd need 2 xilinx maintainers to be able to push
>>> patches & cross-review stuff.
>>
>> I don't have any preference on how to maintain, so I'll follow your
>> suggestion. One thing that may be worth a note is that there is sizable
>> amount of development within Xilinx, and those will come in near future
>> (considering what can be done with FPGA :-)). I'll look for the 2nd
>> reviewer, and specify that in the next patch if found.
> 
> If the xilinx activity gets too much we can always split things up again.
> But if it's just the occasional burst (around a new product for example),
> then drm-misc has ample of bandwidth to absorb that.
> 
> And yes the idea is very much that all regular contributors would have
> commit rights too. All to reduce friction and make it easier to
> contribute.

Right now I think it is visible that everything needs to be reviewed
properly to make sure that we are doing these stuff properly.
This topic can be refreshed in future but I don't think we are there yet.

Thanks,
Michal
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms bindings
       [not found]           ` <BY1PR0201MB1000969ECDC38A62F68B7238D6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
@ 2018-01-11 14:43             ` Rob Herring
       [not found]               ` <CAL_JsqJ_84qg=oJb=HzwgdP9T8osczNT-Eo+u5wjJfT3B8gAQQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 34+ messages in thread
From: Rob Herring @ 2018-01-11 14:43 UTC (permalink / raw)
  To: Hyun Kwon
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

On Wed, Jan 10, 2018 at 8:04 PM, Hyun Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org> wrote:
> Hi Rob,
>
> Thanks for the feedback.
>
>> -----Original Message-----
>> From: Rob Herring [mailto:robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org]
>> Sent: Monday, January 08, 2018 8:00 PM
>> To: Hyun Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
>> Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; Michal
>> Simek <michal.simek-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
>> Subject: Re: [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms
>> bindings
>>
>> On Thu, Jan 04, 2018 at 06:05:50PM -0800, Hyun Kwon wrote:
>> > The dt binding for Xilinx DRM KMS driver.
>>
>> Bindings are for h/w, not drivers.
>
> I'll rephrase this.
>
>>
>> >
>> > Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
>> > ---
>> >  .../devicetree/bindings/display/xlnx/xlnx,kms.txt    | 20
>> ++++++++++++++++++++
>> >  1 file changed, 20 insertions(+)
>> >  create mode 100644
>> Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
>> >
>> > diff --git a/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
>> b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
>> > new file mode 100644
>> > index 0000000..8dcd552
>> > --- /dev/null
>> > +++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
>> > @@ -0,0 +1,20 @@
>> > +Xilinx KMS Pipeline
>> > +-------------------
>> > +
>> > +Xilinx display pipelines can be designed with hardened video IPs and soft
>> video
>> > +IPs in programmable logic. This KMS module provides the common
>> functionality
>> > +of individual subdevice drivers, and glue logics between them.
>> > +
>> > +Required properties:
>> > +
>> > +- compatible: Must be "xlnx,kms".
>
> I'll also rephrase the description and rename this to xlnx,display.
>
>> > +
>> > +- ports: phandles for CRTC ports, using the DT bindings defined in
>> > +  Documentation/devicetree/bindings/graph.txt.
>>
>> This use of ports is not part of the graph binding.
>
> I'll add more details in the description.
>
>>
>> > +
>> > +Example:
>> > +
>> > +   xlnx_drm: xlnx_drm {
>> > +           compatible = "xlnx,kms";
>>
>> drm and kms are Linuxisms.
>
> I agree. I'll remove linux subsystem specific terms.
>
>>
>> Why do you need this node?
>
> This node is used to represent a display pipeline as a single entity, which can consist of multiple components / IPs. I'll elaborate more per your suggestion.

You generally don't need that. Just have the DRM driver match with the
first block in the display pipeline. Then use the OF graph to connect
to the other components in the pipeline. It would help to have a block
diagram showing the data pipelines and h/w blocks.

Rob
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
       [not found]             ` <20180111080605.GB13066-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
@ 2018-01-11 16:57               ` Hyun Kwon
  0 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11 16:57 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Tejas Upadhyay, Michal Simek

Hi Daniel,

> -----Original Message-----
> From: Daniel Vetter [mailto:daniel.vetter-/w4YWyX8dFk@public.gmane.org] On Behalf Of Daniel Vetter
> Sent: Thursday, January 11, 2018 12:06 AM
> To: Hyun Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> Cc: Daniel Vetter <daniel-/w4YWyX8dFk@public.gmane.org>; dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org;
> devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; Tejas Upadhyay <TEJASU-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>; Michal
> Simek <michal.simek-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> Subject: Re: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
> 
> On Thu, Jan 11, 2018 at 02:05:31AM +0000, Hyun Kwon wrote:
> > Hi Daniel,
> >
> > > -----Original Message-----
> > > From: Daniel Vetter [mailto:daniel.vetter-/w4YWyX8dFk@public.gmane.org] On Behalf Of Daniel
> > > Vetter
> > > Sent: Tuesday, January 09, 2018 1:54 AM
> > > To: Hyun Kwon <hyunk-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > > Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; Tejas
> > > Upadhyay <TEJASU-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>; Michal Simek <michal.simek-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > > Subject: Re: [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs
> > >
> > > On Thu, Jan 04, 2018 at 06:05:59PM -0800, Hyun Kwon wrote:
> > > > Debugfs can be used to exploit some specific setting. Main purpose
> > > > is for testing and debug diagnostic.
> > > >
> > > > Signed-off-by: Tejas Upadhyay <tejasu-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > > > Signed-off-by: Hyun Kwon <hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
> > >
> > > Hm, not sure what's the use, it seems to just be for setting/getting
> > > your driver-private properties. Usually people use modetest and similar
> > > tools, but if there's demand for setting properties in debugfs (we already
> > > have all the infrastructure for dumping the entire kms state, see the
> > > various atomic_print_state callbacks) I think that should be done with
> > > generic drm code.
> > >
> > > I'd drop this patch for now (if there's no other reason for it).
> >
> > Thanks for the input. It'd be helpful, for my own understanding, if this
> > can be elaborated a little more. We are using standard tools as well as
> > custom script/tool, but some specific properties / controls are hard to
> > be executed with modetest only unless we change the entire set up /
> > design between each run. The debugfs is used to run all (or as much as
> > possible) properties in a single run, and from what I understand, that
> > doesn't violate intended debugfs usage as long as it's not treated as a
> > stable ABI. The intention is to help isolate issues and enhance the
> > diagnostics. I agree, in the long term, this kind of stuff should be
> > handled in generic way, but would it be still reasonable to keep it
> > driver specific in the meantime?
> 
> Well since the property stuff needs more work anyway I think we could do
> it properly (for upstream) from the start.
> 
> What exactly is the issue with modetest? For intel we don't use it, we do
> all our testing using the igt gpu tests:
> 
> https://cgit.freedesktop.org/drm/igt
> 
> A big pile of these tests also run on non-intel (and we're definitely very
> much appreciating such work). But if you want just a bit of scripting,
> modetest should be able to do it. If not I guess we'll need patches.

For example, for DisplayPort, there are lane count / link rate, and the maximum values depend on the board or connected monitors. We are enforcing those values manually through debugfs before running tests. We will think how to remove the driver specific debugfs, and we can drop it for now. I'll take a look at the repo. Thanks for the pointer.

Thanks,
-hyun

> -Daniel
> >
> > Thanks,
> > -hyun
> >
> > > -Daniel
> > >
> > > > ---
> > > >  drivers/gpu/drm/xlnx/Kconfig       |  21 +++
> > > >  drivers/gpu/drm/xlnx/zynqmp_disp.c | 326
> > > +++++++++++++++++++++++++++++++++++++
> > > >  drivers/gpu/drm/xlnx/zynqmp_dp.c   | 304
> > > ++++++++++++++++++++++++++++++++++
> > > >  3 files changed, 651 insertions(+)
> > > >
> > > > diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig
> > > > index 7c5529c..befce0f 100644
> > > > --- a/drivers/gpu/drm/xlnx/Kconfig
> > > > +++ b/drivers/gpu/drm/xlnx/Kconfig
> > > > @@ -21,3 +21,24 @@ config DRM_ZYNQMP_DPSUB
> > > >  	  this option if you have a Xilinx ZynqMP SoC with DisplayPort
> > > >  	  subsystem. The driver provides the kernel mode setting
> > > >  	  functionlaities for ZynqMP DP subsystem.
> > > > +
> > > > +config DRM_ZYNQMP_DISP_DEBUG_FS
> > > > +	bool "ZynqMP Display debugfs"
> > > > +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> > > > +	select DRM_ZYNQMP_DP_DEBUG_FS
> > > > +	help
> > > > +	  Enable the debugfs code for DP Sub driver. The debugfs code
> > > > +	  enables debugging or testing related features. It exposes some
> > > > +	  low level controls to the user space to help testing automation,
> > > > +	  as well as can enable additional diagnostic or statistical
> > > > +	  information.
> > > > +
> > > > +config DRM_ZYNQMP_DP_DEBUG_FS
> > > > +	bool "ZynqMP DP debugfs"
> > > > +	depends on DEBUG_FS && DRM_ZYNQMP_DPSUB
> > > > +	help
> > > > +	  Enable the debugfs code for DP driver. The debugfs code
> > > > +	  enables debugging or testing related features. It exposes some
> > > > +	  low level controls to the user space to help testing automation,
> > > > +	  as well as can enable additional diagnostic or statistical
> > > > +	  information.
> > > > diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > > b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > > > index 68f829c..9fe6d49 100644
> > > > --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > > > +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c
> > > > @@ -17,6 +17,7 @@
> > > >  #include <drm/drm_plane_helper.h>
> > > >
> > > >  #include <linux/clk.h>
> > > > +#include <linux/debugfs.h>
> > > >  #include <linux/device.h>
> > > >  #include <linux/dmaengine.h>
> > > >  #include <linux/interrupt.h>
> > > > @@ -508,6 +509,325 @@ static void zynqmp_disp_set(void __iomem
> > > *base, int offset, u32 set)
> > > >  	zynqmp_disp_write(base, offset, zynqmp_disp_read(base, offset) |
> > > set);
> > > >  }
> > > >
> > > > +#ifdef CONFIG_DRM_ZYNQMP_DISP_DEBUG_FS
> > > > +
> > > > +#define ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE	32UL
> > > > +#define ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL	0xFFF
> > > > +#define IN_RANGE(x, min, max) ({		\
> > > > +		typeof(x) _x = (x);		\
> > > > +		_x >= (min) && _x <= (max); })
> > > > +
> > > > +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> > > > +enum zynqmp_disp_testcases {
> > > > +	DP_SUB_TC_BG_COLOR,
> > > > +	DP_SUB_TC_OUTPUT_FMT,
> > > > +	DP_SUB_TC_NONE
> > > > +};
> > > > +
> > > > +struct zynqmp_disp_debugfs {
> > > > +	enum zynqmp_disp_testcases testcase;
> > > > +	u16 r_value;
> > > > +	u16 g_value;
> > > > +	u16 b_value;
> > > > +	u32 output_fmt;
> > > > +	struct zynqmp_disp *zynqmp_disp;
> > > > +};
> > > > +
> > > > +static struct dentry *zynqmp_disp_debugfs_dir;
> > > > +struct zynqmp_disp_debugfs disp_debugfs;
> > > > +struct zynqmp_disp_debugfs_request {
> > > > +	const char *req;
> > > > +	enum zynqmp_disp_testcases tc;
> > > > +	ssize_t (*read_handler)(char **kern_buff);
> > > > +	ssize_t (*write_handler)(char **cmd);
> > > > +};
> > > > +
> > > > +static void
> > > > +zynqmp_disp_set_output_fmt(struct zynqmp_disp *disp, unsigned int
> > > id);
> > > > +static s64 zynqmp_disp_debugfs_argument_value(char *arg)
> > > > +{
> > > > +	s64 value;
> > > > +
> > > > +	if (!arg)
> > > > +		return -1;
> > > > +
> > > > +	if (!kstrtos64(arg, 0, &value))
> > > > +		return value;
> > > > +
> > > > +	return -1;
> > > > +}
> > > > +
> > > > +static ssize_t
> > > > +zynqmp_disp_debugfs_background_color_write(char **disp_test_arg)
> > > > +{
> > > > +	char *r_color, *g_color, *b_color;
> > > > +	s64 r_val, g_val, b_val;
> > > > +
> > > > +	r_color = strsep(disp_test_arg, " ");
> > > > +	g_color = strsep(disp_test_arg, " ");
> > > > +	b_color = strsep(disp_test_arg, " ");
> > > > +
> > > > +	/* char * to int conversion */
> > > > +	r_val = zynqmp_disp_debugfs_argument_value(r_color);
> > > > +	g_val = zynqmp_disp_debugfs_argument_value(g_color);
> > > > +	b_val = zynqmp_disp_debugfs_argument_value(b_color);
> > > > +
> > > > +	if (!(IN_RANGE(r_val, 0,
> > > ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> > > > +	      IN_RANGE(g_val, 0,
> > > ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL) &&
> > > > +	      IN_RANGE(b_val, 0,
> > > ZYNQMP_DISP_DEBUGFS_MAX_BG_COLOR_VAL)))
> > > > +		return -EINVAL;
> > > > +
> > > > +	disp_debugfs.r_value = r_val;
> > > > +	disp_debugfs.g_value = g_val;
> > > > +	disp_debugfs.b_value = b_val;
> > > > +
> > > > +	disp_debugfs.testcase = DP_SUB_TC_BG_COLOR;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static ssize_t
> > > > +zynqmp_disp_debugfs_output_display_format_write(char
> > > **disp_test_arg)
> > > > +{
> > > > +	char *output_format;
> > > > +	struct zynqmp_disp *disp = disp_debugfs.zynqmp_disp;
> > > > +
> > > > +	/* Read the value from a user value */
> > > > +	output_format = strsep(disp_test_arg, " ");
> > > > +	if (strncmp(output_format, "rgb", 3) == 0) {
> > > > +		disp_debugfs.output_fmt =
> > > > +			ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB;
> > > > +	} else if (strncmp(output_format, "ycbcr444", 8) == 0) {
> > > > +		disp_debugfs.output_fmt =
> > > > +
> > > 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444;
> > > > +	} else if (strncmp(output_format, "ycbcr422", 8) == 0) {
> > > > +		disp_debugfs.output_fmt =
> > > > +
> > > 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422;
> > > > +	} else if (strncmp(output_format, "yonly", 5) == 0) {
> > > > +		disp_debugfs.output_fmt =
> > > > +
> > > 	ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY;
> > > > +	} else {
> > > > +		dev_err(disp->dev, "Invalid output format\n");
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	disp_debugfs.testcase = DP_SUB_TC_OUTPUT_FMT;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static ssize_t
> > > > +zynqmp_disp_debugfs_output_display_format_read(char **kern_buff)
> > > > +{
> > > > +	size_t out_str_len;
> > > > +
> > > > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > > > +	disp_debugfs.output_fmt = 0;
> > > > +
> > > > +	out_str_len = strlen("Success");
> > > > +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > > out_str_len);
> > > > +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static ssize_t
> > > > +zynqmp_disp_debugfs_background_color_read(char **kern_buff)
> > > > +{
> > > > +	size_t out_str_len;
> > > > +
> > > > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > > > +	disp_debugfs.r_value = 0;
> > > > +	disp_debugfs.g_value = 0;
> > > > +	disp_debugfs.b_value = 0;
> > > > +
> > > > +	out_str_len = strlen("Success");
> > > > +	out_str_len = min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > > out_str_len);
> > > > +	snprintf(*kern_buff, out_str_len, "%s", "Success");
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +/* Match xilinx_dp_testcases vs dp_debugfs_reqs[] entry */
> > > > +struct zynqmp_disp_debugfs_request disp_debugfs_reqs[] = {
> > > > +	{"BACKGROUND_COLOR", DP_SUB_TC_BG_COLOR,
> > > > +		zynqmp_disp_debugfs_background_color_read,
> > > > +		zynqmp_disp_debugfs_background_color_write},
> > > > +	{"OUTPUT_DISPLAY_FORMAT", DP_SUB_TC_OUTPUT_FMT,
> > > > +		zynqmp_disp_debugfs_output_display_format_read,
> > > > +		zynqmp_disp_debugfs_output_display_format_write},
> > > > +};
> > > > +
> > > > +static ssize_t
> > > > +zynqmp_disp_debugfs_write(struct file *f, const char __user *buf,
> > > > +			  size_t size, loff_t *pos)
> > > > +{
> > > > +	char *kern_buff, *disp_test_req, *kern_buff_start;
> > > > +	int ret;
> > > > +	unsigned int i;
> > > > +
> > > > +	if (*pos != 0 || size <= 0)
> > > > +		return -EINVAL;
> > > > +
> > > > +	if (disp_debugfs.testcase != DP_SUB_TC_NONE)
> > > > +		return -EBUSY;
> > > > +
> > > > +	kern_buff = kzalloc(size, GFP_KERNEL);
> > > > +	if (!kern_buff)
> > > > +		return -ENOMEM;
> > > > +	kern_buff_start = kern_buff;
> > > > +
> > > > +	ret = strncpy_from_user(kern_buff, buf, size);
> > > > +	if (ret < 0) {
> > > > +		kfree(kern_buff_start);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* Read the testcase name and argument from a user request */
> > > > +	disp_test_req = strsep(&kern_buff, " ");
> > > > +
> > > > +	for (i = 0; i < ARRAY_SIZE(disp_debugfs_reqs); i++) {
> > > > +		if (!strcasecmp(disp_test_req, disp_debugfs_reqs[i].req))
> > > > +			if (!disp_debugfs_reqs[i].write_handler(&kern_buff))
> > > {
> > > > +				kfree(kern_buff_start);
> > > > +				return size;
> > > > +			}
> > > > +	}
> > > > +	kfree(kern_buff_start);
> > > > +	return -EINVAL;
> > > > +}
> > > > +
> > > > +static ssize_t zynqmp_disp_debugfs_read(struct file *f, char __user *buf,
> > > > +					size_t size, loff_t *pos)
> > > > +{
> > > > +	char *kern_buff = NULL;
> > > > +	size_t kern_buff_len, out_str_len;
> > > > +	enum zynqmp_disp_testcases tc;
> > > > +	int ret;
> > > > +
> > > > +	if (size <= 0)
> > > > +		return -EINVAL;
> > > > +
> > > > +	if (*pos != 0)
> > > > +		return 0;
> > > > +
> > > > +	kern_buff = kzalloc(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > > GFP_KERNEL);
> > > > +	if (!kern_buff) {
> > > > +		disp_debugfs.testcase = DP_SUB_TC_NONE;
> > > > +		return -ENOMEM;
> > > > +	}
> > > > +
> > > > +	tc = disp_debugfs.testcase;
> > > > +	if (tc == DP_SUB_TC_NONE) {
> > > > +		out_str_len = strlen("No testcase executed");
> > > > +		out_str_len =
> > > min(ZYNQMP_DISP_DEBUGFS_READ_MAX_SIZE,
> > > > +				  out_str_len);
> > > > +		snprintf(kern_buff, out_str_len, "%s", "No testcase
> > > executed");
> > > > +	} else {
> > > > +		ret = disp_debugfs_reqs[tc].read_handler(&kern_buff);
> > > > +		if (ret) {
> > > > +			kfree(kern_buff);
> > > > +			return ret;
> > > > +		}
> > > > +	}
> > > > +
> > > > +	kern_buff_len = strlen(kern_buff);
> > > > +	size = min(size, kern_buff_len);
> > > > +
> > > > +	ret = copy_to_user(buf, kern_buff, size);
> > > > +
> > > > +	kfree(kern_buff);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	*pos = size + 1;
> > > > +	return size;
> > > > +}
> > > > +
> > > > +static const struct file_operations fops_zynqmp_disp_dbgfs = {
> > > > +	.owner = THIS_MODULE,
> > > > +	.read = zynqmp_disp_debugfs_read,
> > > > +	.write = zynqmp_disp_debugfs_write,
> > > > +};
> > > > +
> > > > +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> > > > +{
> > > > +	int err;
> > > > +	struct dentry *zynqmp_disp_debugfs_file;
> > > > +
> > > > +	disp_debugfs.testcase = DP_SUB_TC_NONE;
> > > > +	disp_debugfs.zynqmp_disp = disp;
> > > > +
> > > > +	zynqmp_disp_debugfs_dir = debugfs_create_dir("disp", NULL);
> > > > +	if (!zynqmp_disp_debugfs_dir) {
> > > > +		dev_err(disp->dev, "debugfs_create_dir failed\n");
> > > > +		return -ENODEV;
> > > > +	}
> > > > +
> > > > +	zynqmp_disp_debugfs_file =
> > > > +		debugfs_create_file("testcase", 0444,
> > > > +				    zynqmp_disp_debugfs_dir, NULL,
> > > > +				    &fops_zynqmp_disp_dbgfs);
> > > > +	if (!zynqmp_disp_debugfs_file) {
> > > > +		dev_err(disp->dev, "debugfs_create_file testcase failed\n");
> > > > +		err = -ENODEV;
> > > > +		goto err_dbgfs;
> > > > +	}
> > > > +	return 0;
> > > > +
> > > > +err_dbgfs:
> > > > +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> > > > +	zynqmp_disp_debugfs_dir = NULL;
> > > > +	return err;
> > > > +}
> > > > +
> > > > +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> > > > +{
> > > > +	debugfs_remove_recursive(zynqmp_disp_debugfs_dir);
> > > > +	zynqmp_disp_debugfs_dir = NULL;
> > > > +}
> > > > +
> > > > +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> > > > +{
> > > > +	if (disp_debugfs.testcase == DP_SUB_TC_BG_COLOR) {
> > > > +		zynqmp_disp_write(disp->blend.base,
> > > > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_0,
> > > > +				  disp_debugfs.r_value);
> > > > +		zynqmp_disp_write(disp->blend.base,
> > > > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_1,
> > > > +				  disp_debugfs.g_value);
> > > > +		zynqmp_disp_write(disp->blend.base,
> > > > +				  ZYNQMP_DISP_V_BLEND_BG_CLR_2,
> > > > +				  disp_debugfs.b_value);
> > > > +	}
> > > > +}
> > > > +
> > > > +static void
> > > > +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> > > > +{
> > > > +	if (disp_debugfs.testcase == DP_SUB_TC_OUTPUT_FMT)
> > > > +		zynqmp_disp_set_output_fmt(disp,
> > > disp_debugfs.output_fmt);
> > > > +}
> > > > +#else
> > > > +static void zynqmp_disp_debugfs_exit(struct zynqmp_disp *disp)
> > > > +{
> > > > +}
> > > > +
> > > > +static int zynqmp_disp_debugfs_init(struct zynqmp_disp *disp)
> > > > +{
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static void zynqmp_disp_debugfs_bg_color(struct zynqmp_disp *disp)
> > > > +{
> > > > +}
> > > > +
> > > > +static void
> > > > +zynqmp_disp_set_debugfs_output_fmt(struct zynqmp_disp *disp)
> > > > +{
> > > > +}
> > > > +#endif /* CONFIG_DP_DEBUG_FS */
> > > > +
> > > >  /*
> > > >   * Clock functions
> > > >   */
> > > > @@ -597,6 +917,8 @@ zynqmp_disp_blend_set_output_fmt(struct
> > > zynqmp_disp_blend *blend, u32 fmt)
> > > >  	u32 *offsets;
> > > >  	u32 offset, i;
> > > >
> > > > +	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422)
> > > > +		fmt |=
> > > ZYNQMP_DISP_V_BLEND_OUTPUT_EN_DOWNSAMPLE;
> > > >  	zynqmp_disp_write(blend->base,
> > > ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT, fmt);
> > > >  	if (fmt == ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB) {
> > > >  		coeffs = reset_coeffs;
> > > > @@ -1941,6 +2263,7 @@ static void zynqmp_disp_set_bg_color(struct
> > > zynqmp_disp *disp,
> > > >  				     u32 c0, u32 c1, u32 c2)
> > > >  {
> > > >  	zynqmp_disp_blend_set_bg_color(&disp->blend, c0, c1, c2);
> > > > +	zynqmp_disp_debugfs_bg_color(disp);
> > > >  }
> > > >
> > > >  /**
> > > > @@ -2572,6 +2895,7 @@ zynqmp_disp_crtc_atomic_enable(struct
> > > drm_crtc *crtc,
> > > >  		return;
> > > >  	}
> > > >  	zynqmp_disp_set_output_fmt(disp, disp->color);
> > > > +	zynqmp_disp_set_debugfs_output_fmt(disp);
> > > >  	zynqmp_disp_set_bg_color(disp, disp->bg_c0, disp->bg_c1, disp-
> > > >bg_c2);
> > > >  	zynqmp_disp_enable(disp);
> > > >  	/* Delay of 3 vblank intervals for timing gen to be stable */
> > > > @@ -2911,6 +3235,7 @@ int zynqmp_disp_probe(struct
> > > platform_device *pdev)
> > > >  	ret = zynqmp_disp_layer_create(disp);
> > > >  	if (ret)
> > > >  		goto error_aclk;
> > > > +	zynqmp_disp_debugfs_init(disp);
> > > >
> > > >  	return 0;
> > > >
> > > > @@ -2924,6 +3249,7 @@ int zynqmp_disp_remove(struct
> > > platform_device *pdev)
> > > >  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> > > >  	struct zynqmp_disp *disp = dpsub->disp;
> > > >
> > > > +	zynqmp_disp_debugfs_exit(disp);
> > > >  	zynqmp_disp_layer_destroy(disp);
> > > >  	if (disp->audclk)
> > > >  		zynqmp_disp_clk_disable(disp->audclk, &disp->audclk_en);
> > > > diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > > b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > > > index ce3c7c5..66fbad0 100644
> > > > --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > > > +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c
> > > > @@ -16,6 +16,7 @@
> > > >  #include <drm/drm_dp_helper.h>
> > > >  #include <drm/drm_of.h>
> > > >
> > > > +#include <linux/debugfs.h>
> > > >  #include <linux/delay.h>
> > > >  #include <linux/device.h>
> > > >  #include <linux/module.h>
> > > > @@ -371,6 +372,306 @@ static void zynqmp_dp_set(void __iomem
> > > *base, int offset, u32 set)
> > > >  }
> > > >
> > > >  /*
> > > > + * Debugfs functions
> > > > + */
> > > > +
> > > > +#ifdef CONFIG_DRM_ZYNQMP_DP_DEBUG_FS
> > > > +
> > > > +#define ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE	32UL
> > > > +#define ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR	"255"
> > > > +#define IN_RANGE(x, min, max) ({		\
> > > > +		typeof(x) _x = (x);		\
> > > > +		_x >= (min) && _x <= (max); })
> > > > +
> > > > +/* Match zynqmp_dp_testcases vs debugfs_reqs[] entry */
> > > > +enum zynqmp_dp_testcases {
> > > > +	DP_TC_LINK_RATE,
> > > > +	DP_TC_LANE_COUNT,
> > > > +	DP_TC_OUTPUT_FMT,
> > > > +	DP_TC_NONE
> > > > +};
> > > > +
> > > > +struct zynqmp_dp_debugfs {
> > > > +	enum zynqmp_dp_testcases testcase;
> > > > +	u8 link_rate;
> > > > +	u8 lane_cnt;
> > > > +	u8 old_output_fmt;
> > > > +	struct zynqmp_dp *dp;
> > > > +};
> > > > +
> > > > +static struct dentry *zynqmp_dp_debugfs_dir;
> > > > +static struct zynqmp_dp_debugfs dp_debugfs;
> > > > +struct zynqmp_dp_debugfs_request {
> > > > +	const char *req;
> > > > +	enum zynqmp_dp_testcases tc;
> > > > +	ssize_t (*read_handler)(char **kern_buff);
> > > > +	ssize_t (*write_handler)(char **cmd);
> > > > +};
> > > > +
> > > > +static s64 zynqmp_dp_debugfs_argument_value(char *arg)
> > > > +{
> > > > +	s64 value;
> > > > +
> > > > +	if (!arg)
> > > > +		return -1;
> > > > +
> > > > +	if (!kstrtos64(arg, 0, &value))
> > > > +		return value;
> > > > +
> > > > +	return -1;
> > > > +}
> > > > +
> > > > +static ssize_t zynqmp_dp_debugfs_max_linkrate_write(char
> > > **dp_test_arg)
> > > > +{
> > > > +	char *link_rate_arg;
> > > > +	s64 link_rate;
> > > > +
> > > > +	link_rate_arg = strsep(dp_test_arg, " ");
> > > > +	link_rate = zynqmp_dp_debugfs_argument_value(link_rate_arg);
> > > > +	if (link_rate < 0 || (link_rate != DP_HIGH_BIT_RATE2 &&
> > > > +			      link_rate != DP_HIGH_BIT_RATE &&
> > > > +			      link_rate != DP_REDUCED_BIT_RATE))
> > > > +		return -EINVAL;
> > > > +
> > > > +	dp_debugfs.link_rate = drm_dp_link_rate_to_bw_code(link_rate);
> > > > +	dp_debugfs.testcase = DP_TC_LINK_RATE;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static ssize_t zynqmp_dp_debugfs_max_lanecnt_write(char
> > > **dp_test_arg)
> > > > +{
> > > > +	char *lane_cnt_arg;
> > > > +	s64 lane_count;
> > > > +
> > > > +	lane_cnt_arg = strsep(dp_test_arg, " ");
> > > > +	lane_count = zynqmp_dp_debugfs_argument_value(lane_cnt_arg);
> > > > +	if (lane_count < 0 || !IN_RANGE(lane_count, 1,
> > > > +					ZYNQMP_DP_MAX_LANES))
> > > > +		return -EINVAL;
> > > > +
> > > > +	dp_debugfs.lane_cnt = lane_count;
> > > > +	dp_debugfs.testcase = DP_TC_LANE_COUNT;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static ssize_t zynqmp_dp_debugfs_max_linkrate_read(char **kern_buff)
> > > > +{
> > > > +	struct zynqmp_dp *dp = dp_debugfs.dp;
> > > > +	size_t output_str_len;
> > > > +	u8 dpcd_link_bw;
> > > > +	int ret;
> > > > +
> > > > +	dp_debugfs.testcase = DP_TC_NONE;
> > > > +	dp_debugfs.link_rate = 0;
> > > > +
> > > > +	/* Getting Sink Side Link Rate */
> > > > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LINK_BW_SET,
> > > &dpcd_link_bw);
> > > > +	if (ret < 0) {
> > > > +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> > > > +		kfree(*kern_buff);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> > > > +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> > > output_str_len);
> > > > +	snprintf(*kern_buff, output_str_len, "%u", dpcd_link_bw);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static ssize_t zynqmp_dp_debugfs_max_lanecnt_read(char **kern_buff)
> > > > +{
> > > > +	struct zynqmp_dp *dp = dp_debugfs.dp;
> > > > +	size_t output_str_len;
> > > > +	u8 dpcd_lane_cnt;
> > > > +	int ret;
> > > > +
> > > > +	dp_debugfs.testcase = DP_TC_NONE;
> > > > +	dp_debugfs.lane_cnt = 0;
> > > > +
> > > > +	/* Getting Sink Side Lane Count */
> > > > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_COUNT_SET,
> > > &dpcd_lane_cnt);
> > > > +	if (ret < 0) {
> > > > +		dev_err(dp->dev, "Failed to read link rate via AUX.\n");
> > > > +		kfree(*kern_buff);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	dpcd_lane_cnt &= DP_LANE_COUNT_MASK;
> > > > +	output_str_len = strlen(ZYNQMP_DP_DEBUGFS_UINT8_MAX_STR);
> > > > +	output_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> > > output_str_len);
> > > > +	snprintf(*kern_buff, output_str_len, "%u", dpcd_lane_cnt);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +/* Match zynqmp_dp_testcases vs dp_debugfs_reqs[] entry */
> > > > +static struct zynqmp_dp_debugfs_request debugfs_reqs[] = {
> > > > +	{"LINK_RATE", DP_TC_LINK_RATE,
> > > > +			zynqmp_dp_debugfs_max_linkrate_read,
> > > > +			zynqmp_dp_debugfs_max_linkrate_write},
> > > > +	{"LANE_COUNT", DP_TC_LANE_COUNT,
> > > > +			zynqmp_dp_debugfs_max_lanecnt_read,
> > > > +			zynqmp_dp_debugfs_max_lanecnt_write},
> > > > +};
> > > > +
> > > > +static ssize_t zynqmp_dp_debugfs_read(struct file *f, char __user *buf,
> > > > +				      size_t size, loff_t *pos)
> > > > +{
> > > > +	char *kern_buff = NULL;
> > > > +	size_t kern_buff_len, out_str_len;
> > > > +	enum zynqmp_dp_testcases tc;
> > > > +	int ret;
> > > > +
> > > > +	if (size <= 0)
> > > > +		return -EINVAL;
> > > > +
> > > > +	if (*pos != 0)
> > > > +		return 0;
> > > > +
> > > > +	kern_buff = kzalloc(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> > > GFP_KERNEL);
> > > > +	if (!kern_buff) {
> > > > +		dp_debugfs.testcase = DP_TC_NONE;
> > > > +		return -ENOMEM;
> > > > +	}
> > > > +
> > > > +	tc = dp_debugfs.testcase;
> > > > +	if (tc == DP_TC_NONE) {
> > > > +		out_str_len = strlen("No testcase executed");
> > > > +		out_str_len = min(ZYNQMP_DP_DEBUGFS_READ_MAX_SIZE,
> > > out_str_len);
> > > > +		snprintf(kern_buff, out_str_len, "%s", "No testcase
> > > executed");
> > > > +	} else {
> > > > +		ret = debugfs_reqs[tc].read_handler(&kern_buff);
> > > > +		if (ret) {
> > > > +			kfree(kern_buff);
> > > > +			return ret;
> > > > +		}
> > > > +	}
> > > > +
> > > > +	kern_buff_len = strlen(kern_buff);
> > > > +	size = min(size, kern_buff_len);
> > > > +
> > > > +	ret = copy_to_user(buf, kern_buff, size);
> > > > +
> > > > +	kfree(kern_buff);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	*pos = size + 1;
> > > > +	return size;
> > > > +}
> > > > +
> > > > +static ssize_t
> > > > +zynqmp_dp_debugfs_write(struct file *f, const char __user *buf,
> > > > +			size_t size, loff_t *pos)
> > > > +{
> > > > +	char *kern_buff, *kern_buff_start;
> > > > +	char *dp_test_req;
> > > > +	int ret;
> > > > +	int i;
> > > > +
> > > > +	if (*pos != 0 || size <= 0)
> > > > +		return -EINVAL;
> > > > +
> > > > +	if (dp_debugfs.testcase != DP_TC_NONE)
> > > > +		return -EBUSY;
> > > > +
> > > > +	kern_buff = kzalloc(size, GFP_KERNEL);
> > > > +	if (!kern_buff)
> > > > +		return -ENOMEM;
> > > > +	kern_buff_start = kern_buff;
> > > > +
> > > > +	ret = strncpy_from_user(kern_buff, buf, size);
> > > > +	if (ret < 0) {
> > > > +		kfree(kern_buff_start);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* Read the testcase name and argument from a user request */
> > > > +	dp_test_req = strsep(&kern_buff, " ");
> > > > +
> > > > +	for (i = 0; i < ARRAY_SIZE(debugfs_reqs); i++) {
> > > > +		if (!strcasecmp(dp_test_req, debugfs_reqs[i].req))
> > > > +			if (!debugfs_reqs[i].write_handler(&kern_buff)) {
> > > > +				kfree(kern_buff_start);
> > > > +				return size;
> > > > +			}
> > > > +	}
> > > > +
> > > > +	kfree(kern_buff_start);
> > > > +	return -EINVAL;
> > > > +}
> > > > +
> > > > +static const struct file_operations fops_zynqmp_dp_dbgfs = {
> > > > +	.owner = THIS_MODULE,
> > > > +	.read = zynqmp_dp_debugfs_read,
> > > > +	.write = zynqmp_dp_debugfs_write,
> > > > +};
> > > > +
> > > > +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> > > > +{
> > > > +	int err;
> > > > +	struct dentry *zynqmp_dp_debugfs_file;
> > > > +
> > > > +	dp_debugfs.testcase = DP_TC_NONE;
> > > > +	dp_debugfs.dp = dp;
> > > > +
> > > > +	zynqmp_dp_debugfs_dir = debugfs_create_dir("dp", NULL);
> > > > +	if (!zynqmp_dp_debugfs_dir) {
> > > > +		dev_err(dp->dev, "debugfs_create_dir failed\n");
> > > > +		return -ENODEV;
> > > > +	}
> > > > +
> > > > +	zynqmp_dp_debugfs_file =
> > > > +		debugfs_create_file("testcase", 0444,
> > > zynqmp_dp_debugfs_dir,
> > > > +				    NULL, &fops_zynqmp_dp_dbgfs);
> > > > +	if (!zynqmp_dp_debugfs_file) {
> > > > +		dev_err(dp->dev, "debugfs_create_file testcase failed\n");
> > > > +		err = -ENODEV;
> > > > +		goto err_dbgfs;
> > > > +	}
> > > > +	return 0;
> > > > +
> > > > +err_dbgfs:
> > > > +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> > > > +	zynqmp_dp_debugfs_dir = NULL;
> > > > +	return err;
> > > > +}
> > > > +
> > > > +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> > > > +{
> > > > +	debugfs_remove_recursive(zynqmp_dp_debugfs_dir);
> > > > +	zynqmp_dp_debugfs_dir = NULL;
> > > > +}
> > > > +
> > > > +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> > > > +{
> > > > +	dp->mode.bw_code =
> > > > +		dp_debugfs.link_rate ? dp_debugfs.link_rate : dp-
> > > >mode.bw_code;
> > > > +	dp->mode.lane_cnt =
> > > > +		dp_debugfs.lane_cnt ? dp_debugfs.lane_cnt : dp-
> > > >mode.lane_cnt;
> > > > +}
> > > > +
> > > > +#else /* DRM_ZYNQMP_DP_DEBUG_FS */
> > > > +
> > > > +static int zynqmp_dp_debugfs_init(struct zynqmp_dp *dp)
> > > > +{
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static void zynqmp_dp_debugfs_exit(struct zynqmp_dp *dp)
> > > > +{
> > > > +}
> > > > +
> > > > +static void zynqmp_dp_debugfs_mode_config(struct zynqmp_dp *dp)
> > > > +{
> > > > +}
> > > > +
> > > > +#endif /* DRM_ZYNQMP_DP_DEBUG_FS */
> > > > +
> > > > +/*
> > > >   * Internal functions: used by zynqmp_disp.c
> > > >   */
> > > >
> > > > @@ -597,6 +898,7 @@ static int zynqmp_dp_mode_configure(struct
> > > zynqmp_dp *dp, int pclock,
> > > >  			dp->mode.bw_code = bws[i];
> > > >  			dp->mode.lane_cnt = lane_cnt;
> > > >  			dp->mode.pclock = pclock;
> > > > +			zynqmp_dp_debugfs_mode_config(dp);
> > > >  			return dp->mode.bw_code;
> > > >  		}
> > > >  	}
> > > > @@ -1840,6 +2142,7 @@ int zynqmp_dp_probe(struct platform_device
> > > *pdev)
> > > >  	dpsub = platform_get_drvdata(pdev);
> > > >  	dpsub->dp = dp;
> > > >  	dp->dpsub = dpsub;
> > > > +	zynqmp_dp_debugfs_init(dp);
> > > >
> > > >  	return 0;
> > > >
> > > > @@ -1855,6 +2158,7 @@ int zynqmp_dp_remove(struct
> > > platform_device *pdev)
> > > >  	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
> > > >  	struct zynqmp_dp *dp = dpsub->dp;
> > > >
> > > > +	zynqmp_dp_debugfs_exit(dp);
> > > >  	zynqmp_dp_write(dp->iomem, ZYNQMP_DP_TX_ENABLE, 0);
> > > >  	drm_dp_aux_unregister(&dp->aux);
> > > >  	zynqmp_dp_exit_phy(dp);
> > > > --
> > > > 2.7.4
> > > >
> > > > _______________________________________________
> > > > dri-devel mailing list
> > > > dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> > > > https://lists.freedesktop.org/mailman/listinfo/dri-devel
> > >
> > > --
> > > Daniel Vetter
> > > Software Engineer, Intel Corporation
> > > http://blog.ffwll.ch
> 
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 34+ messages in thread

* RE: [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms bindings
       [not found]               ` <CAL_JsqJ_84qg=oJb=HzwgdP9T8osczNT-Eo+u5wjJfT3B8gAQQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2018-01-11 19:22                 ` Hyun Kwon
  0 siblings, 0 replies; 34+ messages in thread
From: Hyun Kwon @ 2018-01-11 19:22 UTC (permalink / raw)
  To: Rob Herring
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Michal Simek

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="utf-8", Size: 4223 bytes --]

Hi Rob,

> -----Original Message-----
> From: Rob Herring [mailto:robh@kernel.org]
> Sent: Thursday, January 11, 2018 6:43 AM
> To: Hyun Kwon <hyunk@xilinx.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> Simek <michal.simek@xilinx.com>
> Subject: Re: [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms
> bindings
> 
> On Wed, Jan 10, 2018 at 8:04 PM, Hyun Kwon <hyunk@xilinx.com> wrote:
> > Hi Rob,
> >
> > Thanks for the feedback.
> >
> >> -----Original Message-----
> >> From: Rob Herring [mailto:robh@kernel.org]
> >> Sent: Monday, January 08, 2018 8:00 PM
> >> To: Hyun Kwon <hyunk@xilinx.com>
> >> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org; Michal
> >> Simek <michal.simek@xilinx.com>
> >> Subject: Re: [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms
> >> bindings
> >>
> >> On Thu, Jan 04, 2018 at 06:05:50PM -0800, Hyun Kwon wrote:
> >> > The dt binding for Xilinx DRM KMS driver.
> >>
> >> Bindings are for h/w, not drivers.
> >
> > I'll rephrase this.
> >
> >>
> >> >
> >> > Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
> >> > ---
> >> >  .../devicetree/bindings/display/xlnx/xlnx,kms.txt    | 20
> >> ++++++++++++++++++++
> >> >  1 file changed, 20 insertions(+)
> >> >  create mode 100644
> >> Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> >> >
> >> > diff --git
> a/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> >> b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> >> > new file mode 100644
> >> > index 0000000..8dcd552
> >> > --- /dev/null
> >> > +++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,kms.txt
> >> > @@ -0,0 +1,20 @@
> >> > +Xilinx KMS Pipeline
> >> > +-------------------
> >> > +
> >> > +Xilinx display pipelines can be designed with hardened video IPs and
> soft
> >> video
> >> > +IPs in programmable logic. This KMS module provides the common
> >> functionality
> >> > +of individual subdevice drivers, and glue logics between them.
> >> > +
> >> > +Required properties:
> >> > +
> >> > +- compatible: Must be "xlnx,kms".
> >
> > I'll also rephrase the description and rename this to xlnx,display.
> >
> >> > +
> >> > +- ports: phandles for CRTC ports, using the DT bindings defined in
> >> > +  Documentation/devicetree/bindings/graph.txt.
> >>
> >> This use of ports is not part of the graph binding.
> >
> > I'll add more details in the description.
> >
> >>
> >> > +
> >> > +Example:
> >> > +
> >> > +   xlnx_drm: xlnx_drm {
> >> > +           compatible = "xlnx,kms";
> >>
> >> drm and kms are Linuxisms.
> >
> > I agree. I'll remove linux subsystem specific terms.
> >
> >>
> >> Why do you need this node?
> >
> > This node is used to represent a display pipeline as a single entity, which
> can consist of multiple components / IPs. I'll elaborate more per your
> suggestion.
> 
> You generally don't need that. Just have the DRM driver match with the
> first block in the display pipeline. Then use the OF graph to connect
> to the other components in the pipeline. It would help to have a block
> diagram showing the data pipelines and h/w blocks.
> 

Some examples are as following. In simple case, a pipeline is linear with multiple blocks:

ZynqMP DisplayPort pipeline:

	ZynqMP DMA -> ZynqMP Display controller -> ZynqMP DP encoder

FPGA display pipeline where each block comes as a separate device:

	FPGA DMA -> FPGA mixer -> FPGA HDMI encoder

It gets more interesting as those can be mixed:

	ZynqMP DMA -> ZynqMP Display controller -> ZynqMP DP encoder
		            |
	FPAG DMA ->
or

	ZynqMP DMA -> ZynqMP Display controller -> ZynqMP DP encoder
						         |
						         -> FPGA HDMI encoder
		            
Or even,

			ZynqMP DMA -> ZynqMP Display controller -> ZynqMP DP encoder
				            |			          |
	FPAG DMA -> FPGA mixer ->				          -> FPGA HDMI encoder

This node is representation of such variations as a single pipeline device.

Thanks,
-hyun

> Rob
N‹§²æìr¸›yúèšØb²X¬¶Ç§vØ^–)Þº{.nÇ+‰·zøœzÚÞz)í…æèw*\x1fjg¬±¨\x1e¶‰šŽŠÝ¢j.ïÛ°\½½MŽúgjÌæa×\x02››–' ™©Þ¢¸\f¢·¦j:+v‰¨ŠwèjØm¶Ÿÿ¾\a«‘êçzZ+ƒùšŽŠÝ¢j"ú!¶i

^ permalink raw reply	[flat|nested] 34+ messages in thread

end of thread, other threads:[~2018-01-11 19:22 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-01-05  2:05 [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver Hyun Kwon
     [not found] ` <1515117959-18068-1-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
2018-01-05  2:05   ` [PATCH 01/10] dt-bindings: display: xlnx: Add Xilinx kms bindings Hyun Kwon
     [not found]     ` <1515117959-18068-2-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
2018-01-09  4:00       ` Rob Herring
2018-01-11  2:04         ` Hyun Kwon
     [not found]           ` <BY1PR0201MB1000969ECDC38A62F68B7238D6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
2018-01-11 14:43             ` Rob Herring
     [not found]               ` <CAL_JsqJ_84qg=oJb=HzwgdP9T8osczNT-Eo+u5wjJfT3B8gAQQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2018-01-11 19:22                 ` Hyun Kwon
2018-01-05  2:05   ` [PATCH 02/10] drm: xlnx: Add xlnx crtc of Xilinx DRM KMS Hyun Kwon
2018-01-09  9:37     ` Daniel Vetter
     [not found]       ` <20180109093733.GG26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
2018-01-11  2:04         ` Hyun Kwon
2018-01-11  7:48           ` Daniel Vetter
2018-01-05  2:05   ` [PATCH 03/10] drm: xlnx: Add xlnx fb " Hyun Kwon
2018-01-09  9:35     ` Daniel Vetter
2018-01-11  2:04       ` Hyun Kwon
2018-01-05  2:05   ` [PATCH 04/10] drm: xlnx: Add xlnx gem " Hyun Kwon
2018-01-05  2:05   ` [PATCH 05/10] drm: xlnx: Xilinx DRM KMS driver Hyun Kwon
     [not found]     ` <1515117959-18068-6-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
2018-01-09  9:51       ` Daniel Vetter
2018-01-11  2:05         ` Hyun Kwon
2018-01-05  2:05   ` [PATCH 06/10] dt-bindings: display: xlnx: Add ZynqMP DP subsystem bindings Hyun Kwon
2018-01-09  4:07     ` Rob Herring
2018-01-11  2:06       ` Hyun Kwon
2018-01-05  2:05   ` [PATCH 07/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DP subsystem display Hyun Kwon
     [not found]     ` <1515117959-18068-8-git-send-email-hyun.kwon-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org>
2018-01-09  9:46       ` Daniel Vetter
     [not found]         ` <20180109094652.GH26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
2018-01-11  2:04           ` Hyun Kwon
2018-01-05  2:05   ` [PATCH 08/10] drm: xlnx: DRM KMS driver for Xilinx ZynqMP DisplayPort Hyun Kwon
2018-01-05  2:05   ` [PATCH 09/10] drm: xlnx: ZynqMP DP subsystem DRM KMS driver Hyun Kwon
2018-01-05  2:05   ` [PATCH 10/10] drm: xlnx: zynqmp: Add debugfs Hyun Kwon
2018-01-09  9:54     ` Daniel Vetter
2018-01-11  2:05       ` Hyun Kwon
     [not found]         ` <BY1PR0201MB10001A1C38398BFBAEC56D39D6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
2018-01-11  8:06           ` Daniel Vetter
     [not found]             ` <20180111080605.GB13066-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
2018-01-11 16:57               ` Hyun Kwon
2018-01-09  9:56   ` [PATCH 00/10] Xilinx ZynqMP DisplayPort subsystem DRM KMS driver Daniel Vetter
     [not found]     ` <20180109095649.GK26573-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
2018-01-11  2:07       ` Hyun Kwon
     [not found]         ` <BY1PR0201MB10002CAFCC860052538BA14DD6160-QYJsKn8jqXK8fGmG9BO4UxrHTHEw16jenBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>
2018-01-11  8:07           ` Daniel Vetter
     [not found]             ` <20180111080738.GC13066-dv86pmgwkMBes7Z6vYuT8azUEOm+Xw19@public.gmane.org>
2018-01-11  8:16               ` Michal Simek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).