devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Bogdan Togorean <bogdan.togorean@analog.com>
To: <dri-devel@lists.freedesktop.org>
Cc: Lars-Peter Clausen <lars@metafoo.de>,
	Mike Looijmans <mike.looijmans@topic.nl>,
	Alexandru Ardelean <alexandru.ardelean@analog.com>,
	Bogdan Togorean <bogdan.togorean@analog.com>,
	David Airlie <airlied@linux.ie>, Daniel Vetter <daniel@ffwll.ch>,
	Rob Herring <robh+dt@kernel.org>,
	Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	Maxime Ripard <mripard@kernel.org>,
	Thomas Zimmermann <tzimmermann@suse.de>,
	<devicetree@vger.kernel.org>, <linux-kernel@vger.kernel.org>
Subject: [PATCH 1/2] drm/adi: axi-hdmi-tx: Add support for AXI HDMI TX IP core
Date: Mon, 5 Oct 2020 17:12:08 +0300	[thread overview]
Message-ID: <20201005141226.180655-1-bogdan.togorean@analog.com> (raw)

From: Lars-Peter Clausen <lars@metafoo.de>

The AXI HDMI HDL driver is the driver for the HDL graphics core which is
used on various FPGA designs. It's mostly used to interface with the
ADV7511 driver on some Zynq boards (e.g. ZC702 & ZedBoard).

Link: https://wiki.analog.com/resources/tools-software/linux-drivers/drm/hdl-axi-hdmi
Link: https://wiki.analog.com/resources/fpga/docs/axi_hdmi_tx

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Signed-off-by: Mike Looijmans <mike.looijmans@topic.nl>
Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com>
Signed-off-by: Bogdan Togorean <bogdan.togorean@analog.com>
---
 drivers/gpu/drm/Kconfig                   |   2 +
 drivers/gpu/drm/Makefile                  |   1 +
 drivers/gpu/drm/adi/Kconfig               |  16 +
 drivers/gpu/drm/adi/Makefile              |   4 +
 drivers/gpu/drm/adi/axi_hdmi_tx_crtc.c    | 219 ++++++++++++++
 drivers/gpu/drm/adi/axi_hdmi_tx_drv.c     | 225 ++++++++++++++
 drivers/gpu/drm/adi/axi_hdmi_tx_drv.h     |  45 +++
 drivers/gpu/drm/adi/axi_hdmi_tx_encoder.c | 344 ++++++++++++++++++++++
 8 files changed, 856 insertions(+)
 create mode 100644 drivers/gpu/drm/adi/Kconfig
 create mode 100644 drivers/gpu/drm/adi/Makefile
 create mode 100644 drivers/gpu/drm/adi/axi_hdmi_tx_crtc.c
 create mode 100644 drivers/gpu/drm/adi/axi_hdmi_tx_drv.c
 create mode 100644 drivers/gpu/drm/adi/axi_hdmi_tx_drv.h
 create mode 100644 drivers/gpu/drm/adi/axi_hdmi_tx_encoder.c

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 147d61b9674e..fca4c7e89fab 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -231,6 +231,8 @@ config DRM_SCHED
 
 source "drivers/gpu/drm/i2c/Kconfig"
 
+source "drivers/gpu/drm/adi/Kconfig"
+
 source "drivers/gpu/drm/arm/Kconfig"
 
 config DRM_RADEON
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 2f31579f91d4..6ef28d1422ee 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_DRM)	+= drm.o
 obj-$(CONFIG_DRM_MIPI_DBI) += drm_mipi_dbi.o
 obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
 obj-$(CONFIG_DRM_PANEL_ORIENTATION_QUIRKS) += drm_panel_orientation_quirks.o
+obj-y			+= adi/
 obj-y			+= arm/
 obj-$(CONFIG_DRM_TTM)	+= ttm/
 obj-$(CONFIG_DRM_SCHED)	+= scheduler/
diff --git a/drivers/gpu/drm/adi/Kconfig b/drivers/gpu/drm/adi/Kconfig
new file mode 100644
index 000000000000..bcb230e6d7de
--- /dev/null
+++ b/drivers/gpu/drm/adi/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_ADI_AXI_HDMI_TX
+	tristate "DRM Support for Analog Devices HDMI FPGA platforms"
+	depends on DRM
+	default	n
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DMA_CMA if HAVE_DMA_CONTIGUOUS
+	select CMA if HAVE_DMA_CONTIGUOUS
+	select AXI_DMAC
+	select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+	help
+	  Choose this option if you have an FPGA board using this AXI HDMI
+	  Controller (aka GFX).
+
+	  If M is selected this module will be called adi_axi_hdmi_tx.
diff --git a/drivers/gpu/drm/adi/Makefile b/drivers/gpu/drm/adi/Makefile
new file mode 100644
index 000000000000..2451cdc4480f
--- /dev/null
+++ b/drivers/gpu/drm/adi/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+adi_axi_hdmi_tx-y := axi_hdmi_tx_encoder.o axi_hdmi_tx_crtc.o axi_hdmi_tx_drv.o
+
+obj-$(CONFIG_DRM_ADI_AXI_HDMI_TX) += adi_axi_hdmi_tx.o
diff --git a/drivers/gpu/drm/adi/axi_hdmi_tx_crtc.c b/drivers/gpu/drm/adi/axi_hdmi_tx_crtc.c
new file mode 100644
index 000000000000..40dfe5c93f39
--- /dev/null
+++ b/drivers/gpu/drm/adi/axi_hdmi_tx_crtc.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices AXI HDMI TX DRM driver.
+ *
+ * Copyright 2012-2020 Analog Devices Inc.
+ *  Author: Lars-Peter Clausen <lars@metafoo.de>
+ */
+
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "axi_hdmi_tx_drv.h"
+
+struct axi_hdmi_tx_crtc {
+	struct drm_crtc drm_crtc;
+	struct drm_plane plane;
+
+	struct dma_chan *dma;
+	struct dma_interleaved_template *dma_template;
+};
+
+static struct axi_hdmi_tx_crtc *plane_to_axi_hdmi_tx_crtc(struct drm_plane *plane)
+{
+	return container_of(plane, struct axi_hdmi_tx_crtc, plane);
+}
+
+static struct axi_hdmi_tx_crtc *to_axi_hdmi_tx_crtc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct axi_hdmi_tx_crtc, drm_crtc);
+}
+
+static struct dma_async_tx_descriptor *axi_hdmi_tx_vdma_prep_interleaved_desc(
+	struct drm_plane *plane)
+{
+	struct axi_hdmi_tx_crtc *axi_hdmi_tx_crtc = plane_to_axi_hdmi_tx_crtc(plane);
+	struct drm_framebuffer *fb = plane->state->fb;
+	size_t offset, hw_row_size;
+	struct drm_gem_cma_object *obj;
+
+	obj = drm_fb_cma_get_gem_obj(plane->state->fb, 0);
+
+	offset = plane->state->crtc_x * fb->format->cpp[0] +
+		plane->state->crtc_y * fb->pitches[0];
+
+	/* Interleaved DMA is used that way:
+	 * Each interleaved frame is a row (hsize) implemented in ONE
+	 * chunk (sgl has len 1).
+	 * The number of interleaved frames is the number of rows (vsize).
+	 * The icg in used to pack data to the HW, so that the buffer len
+	 * is fb->piches[0], but the actual size for the hw is somewhat less
+	 */
+	axi_hdmi_tx_crtc->dma_template->dir = DMA_MEM_TO_DEV;
+	axi_hdmi_tx_crtc->dma_template->src_start = obj->paddr + offset;
+	/* sgl list have just one entry (each interleaved frame have 1 chunk) */
+	axi_hdmi_tx_crtc->dma_template->frame_size = 1;
+	/* the number of interleaved frame, each has the size specified in sgl */
+	axi_hdmi_tx_crtc->dma_template->numf = plane->state->crtc_h;
+	axi_hdmi_tx_crtc->dma_template->src_sgl = 1;
+	axi_hdmi_tx_crtc->dma_template->src_inc = 1;
+
+	/* vdma IP does not provide any addr to the hdmi IP, so dst_inc
+	 * and dst_sgl should make no any difference.
+	 */
+	axi_hdmi_tx_crtc->dma_template->dst_inc = 0;
+	axi_hdmi_tx_crtc->dma_template->dst_sgl = 0;
+
+	hw_row_size = plane->state->crtc_w * fb->format->cpp[0];
+	axi_hdmi_tx_crtc->dma_template->sgl[0].size = hw_row_size;
+
+	/* the vdma driver seems to look at icg, and not src_icg */
+	axi_hdmi_tx_crtc->dma_template->sgl[0].icg =
+		fb->pitches[0] - hw_row_size;
+
+	return dmaengine_prep_interleaved_dma(axi_hdmi_tx_crtc->dma,
+				axi_hdmi_tx_crtc->dma_template, DMA_CYCLIC);
+}
+
+static void axi_hdmi_tx_plane_atomic_update(struct drm_plane *plane,
+	struct drm_plane_state *old_state)
+{
+	struct axi_hdmi_tx_crtc *axi_hdmi_tx_crtc = plane_to_axi_hdmi_tx_crtc(plane);
+	struct dma_async_tx_descriptor *desc;
+
+	if (!plane->state->crtc || !plane->state->fb)
+		return;
+
+	dmaengine_terminate_all(axi_hdmi_tx_crtc->dma);
+
+	desc = axi_hdmi_tx_vdma_prep_interleaved_desc(plane);
+	if (!desc) {
+		pr_err("Failed to prepare DMA descriptor\n");
+		return;
+	}
+
+	dmaengine_submit(desc);
+	dma_async_issue_pending(axi_hdmi_tx_crtc->dma);
+}
+
+static void axi_hdmi_tx_crtc_enable(struct drm_crtc *crtc,
+				 struct drm_crtc_state *old_state)
+{
+}
+
+static void axi_hdmi_tx_crtc_disable(struct drm_crtc *crtc,
+				  struct drm_crtc_state *old_state)
+{
+	struct axi_hdmi_tx_crtc *axi_hdmi_tx_crtc = to_axi_hdmi_tx_crtc(crtc);
+
+	dmaengine_terminate_all(axi_hdmi_tx_crtc->dma);
+}
+
+static void axi_hdmi_tx_crtc_atomic_begin(struct drm_crtc *crtc,
+	struct drm_crtc_state *state)
+{
+	struct drm_pending_vblank_event *event = crtc->state->event;
+
+	if (event) {
+		crtc->state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+static const struct drm_crtc_helper_funcs axi_hdmi_tx_crtc_helper_funcs = {
+	.atomic_enable = axi_hdmi_tx_crtc_enable,
+	.atomic_disable = axi_hdmi_tx_crtc_disable,
+	.atomic_begin = axi_hdmi_tx_crtc_atomic_begin,
+};
+
+static const struct drm_crtc_funcs axi_hdmi_tx_crtc_funcs = {
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.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 const struct drm_plane_helper_funcs axi_hdmi_tx_plane_helper_funcs = {
+	.atomic_update = axi_hdmi_tx_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs axi_hdmi_tx_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = drm_plane_cleanup,
+	.reset = drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static const u32 axi_hdmi_tx_supported_formats[] = {
+	DRM_FORMAT_XRGB8888,
+};
+
+struct drm_crtc *axi_hdmi_tx_crtc_create(struct drm_device *ddev,
+					 struct device *parent)
+{
+	struct axi_hdmi_tx_private *p = ddev->dev_private;
+	struct axi_hdmi_tx_crtc *axi_hdmi_tx_crtc;
+	struct drm_crtc *crtc;
+	struct drm_plane *plane;
+	int ret;
+
+	if (!dma_has_cap(DMA_INTERLEAVE, p->dma->device->cap_mask)) {
+		DRM_ERROR("DMA needs to support interleaved transfers\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	axi_hdmi_tx_crtc = devm_kzalloc(parent, sizeof(*axi_hdmi_tx_crtc),
+					GFP_KERNEL);
+	if (!axi_hdmi_tx_crtc)
+		return ERR_PTR(-ENOMEM);
+
+	crtc = &axi_hdmi_tx_crtc->drm_crtc;
+	plane = &axi_hdmi_tx_crtc->plane;
+
+	/* we know we'll always use only one data chunk */
+	axi_hdmi_tx_crtc->dma_template = devm_kzalloc(parent,
+		sizeof(struct dma_interleaved_template) +
+		sizeof(struct data_chunk), GFP_KERNEL);
+	if (!axi_hdmi_tx_crtc->dma_template)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_universal_plane_init(ddev, plane, 0xff,
+		&axi_hdmi_tx_plane_funcs,
+		axi_hdmi_tx_supported_formats,
+		ARRAY_SIZE(axi_hdmi_tx_supported_formats), NULL,
+		DRM_PLANE_TYPE_PRIMARY, NULL);
+	if (ret)
+		return ERR_PTR(ret);
+
+	drm_plane_helper_add(plane, &axi_hdmi_tx_plane_helper_funcs);
+
+	axi_hdmi_tx_crtc->dma = p->dma;
+
+	ret = drm_crtc_init_with_planes(ddev, crtc, plane, NULL,
+		&axi_hdmi_tx_crtc_funcs, NULL);
+	if (ret)
+		goto err_plane_destroy;
+	drm_crtc_helper_add(crtc, &axi_hdmi_tx_crtc_helper_funcs);
+
+	return crtc;
+
+err_plane_destroy:
+	drm_plane_cleanup(plane);
+
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/adi/axi_hdmi_tx_drv.c b/drivers/gpu/drm/adi/axi_hdmi_tx_drv.c
new file mode 100644
index 000000000000..8ef3cb4a7dcd
--- /dev/null
+++ b/drivers/gpu/drm/adi/axi_hdmi_tx_drv.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices AXI HDMI TX DRM driver.
+ *
+ * Copyright 2012-2020 Analog Devices Inc.
+ *  Author: Lars-Peter Clausen <lars@metafoo.de>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/of_address.h>
+#include <linux/of_dma.h>
+#include <linux/of_graph.h>
+#include <linux/clk.h>
+
+#include <drm/drm.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_probe_helper.h>
+
+#include "axi_hdmi_tx_drv.h"
+
+#define DRIVER_NAME	"axi_hdmi_tx_drm"
+#define DRIVER_DESC	"AXI HDMI TX DRM"
+#define DRIVER_DATE	"20200910"
+#define DRIVER_MAJOR	1
+#define DRIVER_MINOR	0
+
+static struct drm_mode_config_funcs axi_hdmi_tx_mode_config_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.output_poll_changed = drm_fb_helper_output_poll_changed,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static void axi_hdmi_tx_mode_config_init(struct drm_device *dev)
+{
+	drm_mode_config_init(dev);
+
+	dev->mode_config.min_width = 0;
+	dev->mode_config.min_height = 0;
+
+	dev->mode_config.max_width = 4096;
+	dev->mode_config.max_height = 4096;
+
+	dev->mode_config.funcs = &axi_hdmi_tx_mode_config_funcs;
+}
+
+static int axi_hdmi_tx_init(struct drm_driver *ddrv, struct device *dev)
+{
+	struct axi_hdmi_tx_private *private = dev_get_drvdata(dev);
+	struct drm_device *ddev;
+	struct drm_encoder *encoder;
+	int ret;
+
+	ddev = drm_dev_alloc(ddrv, dev);
+	if (IS_ERR(ddev))
+		return PTR_ERR(ddev);
+
+	private->drm_dev = ddev;
+
+	ddev->dev_private = private;
+
+	axi_hdmi_tx_mode_config_init(ddev);
+
+	private->crtc = axi_hdmi_tx_crtc_create(ddev, dev);
+	if (IS_ERR(private->crtc))
+		goto err_crtc;
+
+	encoder = axi_hdmi_tx_encoder_create(ddev, dev);
+	if (IS_ERR(encoder))
+		goto err_crtc;
+
+	drm_mode_config_reset(ddev);
+
+	drm_kms_helper_poll_init(ddev);
+
+	ret = drm_dev_register(ddev, 0);
+	if (ret)
+		goto err_crtc;
+
+	drm_fbdev_generic_setup(ddev, 32);
+
+	return 0;
+err_crtc:
+	drm_mode_config_cleanup(ddev);
+
+	return ret;
+}
+
+static void axi_hdmi_tx_unload(struct drm_device *dev)
+{
+	drm_kms_helper_poll_fini(dev);
+	drm_mode_config_cleanup(dev);
+}
+
+static const struct file_operations axi_hdmi_tx_driver_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.mmap		= drm_gem_cma_mmap,
+	.poll		= drm_poll,
+	.read		= drm_read,
+	.unlocked_ioctl	= drm_ioctl,
+	.release	= drm_release,
+};
+
+static struct drm_driver axi_hdmi_tx_driver = {
+	.driver_features		= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+	.unload				= axi_hdmi_tx_unload,
+	.lastclose			= drm_fb_helper_lastclose,
+	.prime_handle_to_fd		= drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle		= drm_gem_prime_fd_to_handle,
+	.gem_prime_import		= drm_gem_prime_import,
+	.gem_prime_export		= drm_gem_prime_export,
+	.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,
+	.dumb_create			= drm_gem_cma_dumb_create,
+	.gem_free_object_unlocked	= drm_gem_cma_free_object,
+	.gem_vm_ops			= &drm_gem_cma_vm_ops,
+	.dumb_create			= drm_gem_cma_dumb_create,
+	.fops				= &axi_hdmi_tx_driver_fops,
+	.name				= DRIVER_NAME,
+	.desc				= DRIVER_DESC,
+	.date				= DRIVER_DATE,
+	.major				= DRIVER_MAJOR,
+	.minor				= DRIVER_MINOR,
+};
+
+static const struct of_device_id axi_hdmi_tx_encoder_of_match[] = {
+	{
+		.compatible = "adi,axi-hdmi-tx-1.00.a",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, axi_hdmi_tx_encoder_of_match);
+
+static int axi_hdmi_tx_platform_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct axi_hdmi_tx_private *private;
+	struct device_node *slave_node, *ep_node;
+	struct of_endpoint ep;
+	int ret;
+
+	private = devm_kzalloc(&pdev->dev, sizeof(*private), GFP_KERNEL);
+	if (!private)
+		return -ENOMEM;
+
+	private->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+	if (IS_ERR(private->base))
+		return PTR_ERR(private->base);
+
+	private->hdmi_clock = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(private->hdmi_clock))
+		return -EPROBE_DEFER;
+
+	ep_node = of_graph_get_next_endpoint(np, NULL);
+	if (ep_node) {
+		ret = of_graph_parse_endpoint(ep_node, &ep);
+		if (ret) {
+			of_node_put(ep_node);
+			return ret;
+		}
+		if (ep.port != 0 && ep.id != 0) {
+			of_node_put(ep_node);
+			return -EINVAL;
+		}
+		slave_node = of_graph_get_remote_port_parent(ep_node);
+		of_node_put(ep_node);
+	}
+
+	if (!slave_node)
+		return -EINVAL;
+
+	private->is_rgb = of_property_read_bool(np, "adi,is-rgb");
+
+	private->encoder_slave = of_find_i2c_device_by_node(slave_node);
+	of_node_put(slave_node);
+
+	if (!private->encoder_slave || !private->encoder_slave->dev.driver)
+		return -EPROBE_DEFER;
+
+	private->dma = dma_request_slave_channel(&pdev->dev, "video");
+	if (private->dma == NULL)
+		return -EPROBE_DEFER;
+
+	platform_set_drvdata(pdev, private);
+
+	return axi_hdmi_tx_init(&axi_hdmi_tx_driver, &pdev->dev);
+}
+
+static int axi_hdmi_tx_platform_remove(struct platform_device *pdev)
+{
+	struct axi_hdmi_tx_private *private = platform_get_drvdata(pdev);
+
+	drm_atomic_helper_shutdown(private->drm_dev);
+
+	drm_put_dev(private->drm_dev);
+	dma_release_channel(private->dma);
+	return 0;
+}
+
+static struct platform_driver axi_hdmi_tx_encoder_driver = {
+	.driver = {
+		.name = "axi-hdmi-tx",
+		.of_match_table = axi_hdmi_tx_encoder_of_match,
+	},
+	.probe = axi_hdmi_tx_platform_probe,
+	.remove = axi_hdmi_tx_platform_remove,
+};
+module_platform_driver(axi_hdmi_tx_encoder_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("Analog Devices HDMI TX HDL driver");
diff --git a/drivers/gpu/drm/adi/axi_hdmi_tx_drv.h b/drivers/gpu/drm/adi/axi_hdmi_tx_drv.h
new file mode 100644
index 000000000000..1f294d1e9fbf
--- /dev/null
+++ b/drivers/gpu/drm/adi/axi_hdmi_tx_drv.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Analog Devices AXI HDMI DRM driver.
+ *
+ * Copyright 2012-2020 Analog Devices Inc.
+ *  Author: Lars-Peter Clausen <lars@metafoo.de>
+ */
+
+#ifndef _AXI_HDMI_DRV_H_
+#define _AXI_HDMI_DRV_H_
+
+#include <drm/drm.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+
+struct xlnx_pcm_dma_params {
+	struct device_node *of_node;
+	int chan_id;
+};
+
+struct axi_hdmi_tx_encoder;
+
+struct axi_hdmi_tx_private {
+	struct drm_device *drm_dev;
+	struct drm_crtc *crtc;
+	struct axi_hdmi_tx_encoder *encoder;
+	struct i2c_client *encoder_slave;
+
+	void __iomem *base;
+
+	struct clk *hdmi_clock;
+	bool clk_enabled;
+
+	struct dma_chan *dma;
+
+	bool is_rgb;
+};
+
+struct drm_crtc *axi_hdmi_tx_crtc_create(struct drm_device *ddev,
+					 struct device *parent);
+struct drm_encoder *axi_hdmi_tx_encoder_create(struct drm_device *ddev,
+					       struct device *parent);
+
+#endif
diff --git a/drivers/gpu/drm/adi/axi_hdmi_tx_encoder.c b/drivers/gpu/drm/adi/axi_hdmi_tx_encoder.c
new file mode 100644
index 000000000000..a20a36beb8a0
--- /dev/null
+++ b/drivers/gpu/drm/adi/axi_hdmi_tx_encoder.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices AXI HDMI DRM driver.
+ *
+ * Copyright 2012-2020 Analog Devices Inc.
+ *  Author: Lars-Peter Clausen <lars@metafoo.de>
+ */
+
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/clk.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "axi_hdmi_tx_drv.h"
+
+#define AXI_HDMI_TX_STATUS_VMDA_UNDERFLOW		BIT(4)
+#define AXI_HDMI_TX_STATUS_VMDA_OVERFLOW		BIT(3)
+#define AXI_HDMI_TX_STATUS_VMDA_BE_ERROR		BIT(2)
+#define AXI_HDMI_TX_STATUS_VMDA_TPM_OOS			BIT(1)
+#define AXI_HDMI_TX_STATUS_HDMI_TPM_OOS			BIT(0)
+
+#define AXI_HDMI_TX_COLOR_PATTERN_ENABLE		BIT(24)
+
+#define AXI_HDMI_TX_REG_RESET				0x040
+#define AXI_HDMI_TX_REG_CTRL				0x044
+#define AXI_HDMI_TX_REG_SOURCE_SEL			0x048
+#define AXI_HDMI_TX_REG_COLORPATTERN			0x04c
+#define AXI_HDMI_TX_REG_STATUS				0x05c
+#define AXI_HDMI_TX_REG_VDMA_STATUS			0x060
+#define AXI_HDMI_TX_REG_TPM_STATUS			0x064
+#define AXI_HDMI_TX_REG_CLIPP_MAX			0x068
+#define AXI_HDMI_TX_REG_CLIPP_MIN			0x06c
+#define AXI_HDMI_TX_REG_HTIMING1			0x400
+#define AXI_HDMI_TX_REG_HTIMING2			0x404
+#define AXI_HDMI_TX_REG_HTIMING3			0x408
+#define AXI_HDMI_TX_REG_VTIMING1			0x440
+#define AXI_HDMI_TX_REG_VTIMING2			0x444
+#define AXI_HDMI_TX_REG_VTIMING3			0x448
+
+#define AXI_HDMI_TX_RESET_ENABLE			BIT(0)
+
+#define AXI_HDMI_TX_CTRL_SS_BYPASS			BIT(2)
+#define AXI_HDMI_TX_CTRL_FULL_RANGE			BIT(1)
+#define AXI_HDMI_TX_CTRL_CSC_BYPASS			BIT(0)
+
+#define AXI_HDMI_TX_SOURCE_SEL_COLORPATTERN		0x3
+#define AXI_HDMI_TX_SOURCE_SEL_TESTPATTERN		0x2
+#define AXI_HDMI_TX_SOURCE_SEL_NORMAL			0x1
+#define AXI_HDMI_TX_SOURCE_SEL_NONE			0x0
+
+static const struct debugfs_reg32 axi_hdmi_tx_encoder_debugfs_regs[] = {
+	{ "Reset", AXI_HDMI_TX_REG_RESET },
+	{ "Control", AXI_HDMI_TX_REG_CTRL },
+	{ "Source select", AXI_HDMI_TX_REG_SOURCE_SEL },
+	{ "Colorpattern", AXI_HDMI_TX_REG_COLORPATTERN },
+	{ "Status", AXI_HDMI_TX_REG_STATUS },
+	{ "VDMA status", AXI_HDMI_TX_REG_VDMA_STATUS },
+	{ "TPM status", AXI_HDMI_TX_REG_TPM_STATUS },
+	{ "HTiming1", AXI_HDMI_TX_REG_HTIMING1 },
+	{ "HTiming2", AXI_HDMI_TX_REG_HTIMING2 },
+	{ "HTiming3", AXI_HDMI_TX_REG_HTIMING3 },
+	{ "VTiming1", AXI_HDMI_TX_REG_VTIMING1 },
+	{ "VTiming2", AXI_HDMI_TX_REG_VTIMING2 },
+	{ "VTiming3", AXI_HDMI_TX_REG_VTIMING3 },
+};
+
+static const uint16_t adv7511_csc_ycbcr_to_rgb[] = {
+	0x0734, 0x04ad, 0x0000, 0x1c1b,
+	0x1ddc, 0x04ad, 0x1f24, 0x0135,
+	0x0000, 0x04ad, 0x087c, 0x1b77,
+};
+
+struct axi_hdmi_tx_encoder {
+	struct drm_encoder_slave encoder;
+	struct drm_connector connector;
+
+#ifdef CONFIG_DEBUG_FS
+	struct debugfs_regset32 regset;
+#endif
+};
+
+static inline struct axi_hdmi_tx_encoder *to_axi_hdmi_tx_encoder(struct drm_encoder *enc)
+{
+	return container_of(enc, struct axi_hdmi_tx_encoder, encoder.base);
+}
+
+static inline struct drm_encoder *connector_to_encoder(struct drm_connector *con)
+{
+	struct axi_hdmi_tx_encoder *enc = container_of(con, struct axi_hdmi_tx_encoder, connector);
+
+	return &enc->encoder.base;
+}
+
+static void axi_hdmi_tx_set_color_range(struct axi_hdmi_tx_private *private,
+				     unsigned int low, unsigned int high)
+{
+	writel(high, private->base + AXI_HDMI_TX_REG_CLIPP_MAX);
+	writel(low, private->base + AXI_HDMI_TX_REG_CLIPP_MIN);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static int axi_hdmi_tx_debugfs_cp_get(void *data, u64 *val)
+{
+	struct axi_hdmi_tx_private *private = data;
+	*val = readl(private->base + AXI_HDMI_TX_REG_COLORPATTERN);
+	return 0;
+}
+
+static int axi_hdmi_tx_debugfs_cp_set(void *data, u64 val)
+{
+	struct axi_hdmi_tx_private *private = data;
+
+	writel(val, private->base + AXI_HDMI_TX_REG_COLORPATTERN);
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(axi_hdmi_tx_cp_fops, axi_hdmi_tx_debugfs_cp_get,
+	axi_hdmi_tx_debugfs_cp_set, "0x%06llx\n");
+
+static const char * const axi_hdmi_tx_mode_text[] = {
+	[AXI_HDMI_TX_SOURCE_SEL_NONE] = "none",
+	[AXI_HDMI_TX_SOURCE_SEL_NORMAL] = "normal",
+	[AXI_HDMI_TX_SOURCE_SEL_TESTPATTERN] = "testpattern",
+	[AXI_HDMI_TX_SOURCE_SEL_COLORPATTERN] = "colorpattern",
+};
+
+static ssize_t axi_hdmi_tx_read_mode(struct file *file, char __user *userbuf,
+	size_t count, loff_t *ppos)
+{
+	struct axi_hdmi_tx_private *private = file->private_data;
+	uint32_t src;
+	const char *fmt;
+	size_t len = 0;
+	char buf[50];
+	int i;
+
+	src = readl(private->base + AXI_HDMI_TX_REG_SOURCE_SEL);
+
+	for (i = 0; i < ARRAY_SIZE(axi_hdmi_tx_mode_text); i++) {
+		if (src == i)
+			fmt = "[%s] ";
+		else
+			fmt = "%s ";
+		len += scnprintf(buf + len, sizeof(buf) - len, fmt,
+				axi_hdmi_tx_mode_text[i]);
+	}
+
+	buf[len - 1] = '\n';
+
+	return simple_read_from_buffer(userbuf, count, ppos, buf, len);
+}
+
+static ssize_t axi_hdmi_tx_set_mode(struct file *file, const char __user *userbuf,
+				 size_t count, loff_t *ppos)
+{
+	struct axi_hdmi_tx_private *private = file->private_data;
+	char buf[20];
+	unsigned int ctrl;
+	unsigned int i;
+
+	count = min_t(size_t, count, sizeof(buf) - 1);
+	if (copy_from_user(buf, userbuf, count))
+		return -EFAULT;
+
+	buf[count] = '\0';
+
+	for (i = 0; i < ARRAY_SIZE(axi_hdmi_tx_mode_text); i++) {
+		if (sysfs_streq(axi_hdmi_tx_mode_text[i], buf))
+			break;
+	}
+
+	if (i == ARRAY_SIZE(axi_hdmi_tx_mode_text))
+		return -EINVAL;
+
+	writel(i, private->base + AXI_HDMI_TX_REG_SOURCE_SEL);
+
+	if (i == AXI_HDMI_TX_SOURCE_SEL_TESTPATTERN) {
+		axi_hdmi_tx_set_color_range(private, 0, 0xffffff);
+		ctrl = AXI_HDMI_TX_CTRL_CSC_BYPASS | AXI_HDMI_TX_CTRL_SS_BYPASS |
+			AXI_HDMI_TX_CTRL_FULL_RANGE;
+	} else {
+		if (private->is_rgb) {
+			axi_hdmi_tx_set_color_range(private, 0, 0xffffff);
+			ctrl = AXI_HDMI_TX_CTRL_CSC_BYPASS;
+		} else {
+			axi_hdmi_tx_set_color_range(private, 0x101010, 0xf0ebf0);
+			ctrl = 0;
+		}
+	}
+
+	writel(ctrl, private->base + AXI_HDMI_TX_REG_CTRL);
+
+	return count;
+}
+
+static const struct file_operations axi_hdmi_tx_mode_fops = {
+	.open = simple_open,
+	.read = axi_hdmi_tx_read_mode,
+	.write = axi_hdmi_tx_set_mode,
+};
+
+static void axi_hdmi_tx_debugfs_init(struct axi_hdmi_tx_encoder *encoder)
+{
+	struct axi_hdmi_tx_private *priv = encoder->encoder.base.dev->dev_private;
+
+	encoder->regset.base = priv->base;
+	encoder->regset.regs = axi_hdmi_tx_encoder_debugfs_regs;
+	encoder->regset.nregs = ARRAY_SIZE(axi_hdmi_tx_encoder_debugfs_regs);
+
+	debugfs_create_regset32(dev_name(encoder->encoder.base.dev->dev), 0444,
+				NULL, &encoder->regset);
+	debugfs_create_file("color_pattern", 0600, NULL, priv, &axi_hdmi_tx_cp_fops);
+	debugfs_create_file("mode", 0600, NULL, priv, &axi_hdmi_tx_mode_fops);
+}
+
+#else
+
+static inline void axi_hdmi_tx_debugfs_init(struct axi_hdmi_tx_encoder *enc)
+{
+}
+
+#endif
+
+static void axi_hdmi_tx_encoder_enable(struct drm_encoder *encoder)
+{
+	struct axi_hdmi_tx_private *private = encoder->dev->dev_private;
+
+	if (!private->clk_enabled) {
+		clk_prepare_enable(private->hdmi_clock);
+		private->clk_enabled = true;
+	}
+
+	writel(AXI_HDMI_TX_RESET_ENABLE, private->base + AXI_HDMI_TX_REG_RESET);
+}
+
+static void axi_hdmi_tx_encoder_disable(struct drm_encoder *encoder)
+{
+	struct axi_hdmi_tx_private *private = encoder->dev->dev_private;
+
+	writel(0, private->base + AXI_HDMI_TX_REG_RESET);
+	if (private->clk_enabled) {
+		clk_disable_unprepare(private->hdmi_clock);
+		private->clk_enabled = false;
+	}
+}
+
+static void axi_hdmi_tx_encoder_mode_set(struct drm_encoder *encoder,
+	struct drm_crtc_state *crtc_state,
+	struct drm_connector_state *conn_state)
+{
+	struct axi_hdmi_tx_private *private = encoder->dev->dev_private;
+	struct drm_display_mode *mode = &crtc_state->mode;
+	unsigned int h_de_min, h_de_max;
+	unsigned int v_de_min, v_de_max;
+	unsigned int val;
+
+	h_de_min = mode->htotal - mode->hsync_start;
+	h_de_max = h_de_min + mode->hdisplay;
+	v_de_min = mode->vtotal - mode->vsync_start;
+	v_de_max = v_de_min + mode->vdisplay;
+
+	val = (mode->hdisplay << 16) | mode->htotal;
+	writel(val,  private->base + AXI_HDMI_TX_REG_HTIMING1);
+	val = mode->hsync_end - mode->hsync_start;
+	writel(val,  private->base + AXI_HDMI_TX_REG_HTIMING2);
+	val = (h_de_max << 16) | h_de_min;
+	writel(val,  private->base + AXI_HDMI_TX_REG_HTIMING3);
+
+	val = (mode->vdisplay << 16) | mode->vtotal;
+	writel(val,  private->base + AXI_HDMI_TX_REG_VTIMING1);
+	val = mode->vsync_end - mode->vsync_start;
+	writel(val,  private->base + AXI_HDMI_TX_REG_VTIMING2);
+	val = (v_de_max << 16) | v_de_min;
+	writel(val,  private->base + AXI_HDMI_TX_REG_VTIMING3);
+
+	clk_set_rate(private->hdmi_clock, mode->clock * 1000);
+}
+
+static const struct drm_encoder_helper_funcs axi_hdmi_tx_encoder_helper_funcs = {
+	.enable = axi_hdmi_tx_encoder_enable,
+	.disable = axi_hdmi_tx_encoder_disable,
+	.atomic_mode_set = axi_hdmi_tx_encoder_mode_set,
+};
+
+static const struct drm_encoder_funcs axi_hdmi_tx_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+struct drm_encoder *axi_hdmi_tx_encoder_create(struct drm_device *ddev,
+					       struct device *parent)
+{
+	struct axi_hdmi_tx_private *priv = ddev->dev_private;
+	struct axi_hdmi_tx_encoder *axi_hdmi_tx_encoder;
+	struct drm_encoder *encoder;
+	struct drm_bridge *bridge;
+	int ret;
+
+	axi_hdmi_tx_encoder = devm_kzalloc(parent, sizeof(*axi_hdmi_tx_encoder),
+					   GFP_KERNEL);
+	if (!axi_hdmi_tx_encoder)
+		return ERR_PTR(-ENOMEM);
+
+	encoder = &axi_hdmi_tx_encoder->encoder.base;
+	encoder->possible_crtcs = 1;
+
+	drm_encoder_init(ddev, encoder, &axi_hdmi_tx_encoder_funcs,
+			 DRM_MODE_ENCODER_TMDS, NULL);
+	drm_encoder_helper_add(encoder, &axi_hdmi_tx_encoder_helper_funcs);
+
+	bridge = of_drm_find_bridge(priv->encoder_slave->dev.of_node);
+	if (!bridge)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	ret = drm_bridge_attach(encoder, bridge, NULL, 0);
+	if (ret) {
+		drm_encoder_cleanup(encoder);
+		return ERR_PTR(ret);
+	}
+
+	axi_hdmi_tx_debugfs_init(axi_hdmi_tx_encoder);
+
+	writel(AXI_HDMI_TX_SOURCE_SEL_NORMAL, priv->base + AXI_HDMI_TX_REG_SOURCE_SEL);
+	if (priv->is_rgb) {
+		writel(AXI_HDMI_TX_CTRL_CSC_BYPASS, priv->base + AXI_HDMI_TX_REG_CTRL);
+		axi_hdmi_tx_set_color_range(priv, 0, 0xffffff);
+	} else {
+		axi_hdmi_tx_set_color_range(priv, 0x101010, 0xf0ebf0);
+	}
+
+	return encoder;
+}
-- 
2.28.0


             reply	other threads:[~2020-10-05 11:13 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-05 14:12 Bogdan Togorean [this message]
2020-10-05 14:12 ` [PATCH 2/2] drm: dt-bindings: adi: axi-hdmi-tx: Add DT bindings for axi-hdmi-tx Bogdan Togorean
2020-10-06 22:00   ` Rob Herring
2020-10-16 14:55 ` [PATCH 1/2] drm/adi: axi-hdmi-tx: Add support for AXI HDMI TX IP core Sam Ravnborg
2020-10-23  8:51   ` Togorean, Bogdan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201005141226.180655-1-bogdan.togorean@analog.com \
    --to=bogdan.togorean@analog.com \
    --cc=airlied@linux.ie \
    --cc=alexandru.ardelean@analog.com \
    --cc=daniel@ffwll.ch \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=lars@metafoo.de \
    --cc=linux-kernel@vger.kernel.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mike.looijmans@topic.nl \
    --cc=mripard@kernel.org \
    --cc=robh+dt@kernel.org \
    --cc=tzimmermann@suse.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).