ALT Linux Team development discussions
 help / color / mirror / Atom feed
From: asheplyakov@yandex.ru
To: devel@lists.altlinux.org
Cc: Alexey Sheplyakov <asheplyakov@basealt.ru>,
	Alexey Sheplyakov <asheplyakov@altlinux.org>,
	Evgeny Sinelnikov <sin@basealt.ru>,
	Vitaly Chikunov <vt@altlinux.org>, Igor Chudov <nir@basealt.ru>,
	Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
Subject: [devel] [PATCH 14/35] drm: added Baikal-M SoC video display unit driver
Date: Fri, 20 May 2022 20:28:28 +0400
Message-ID: <20220520162849.1554351-15-asheplyakov@yandex.ru> (raw)
In-Reply-To: <20220520162849.1554351-1-asheplyakov@yandex.ru>

From: Alexey Sheplyakov <asheplyakov@altlinux.org>

Due to hardware peculiarities using both HDMI and DP outputs is
possible only with some constraints:

- Resolution of both displays should be exactly the same
- HDMI viewport must be above or below the display port one

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
Signed-off-by: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
X-feature-Baikal-M
---
 drivers/gpu/drm/Kconfig                       |   1 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/baikal/Kconfig                |  15 +
 drivers/gpu/drm/baikal/Makefile               |  10 +
 drivers/gpu/drm/baikal/baikal-hdmi.c          | 119 ++++++
 drivers/gpu/drm/baikal/baikal_vdu_connector.c | 118 ++++++
 drivers/gpu/drm/baikal/baikal_vdu_crtc.c      | 345 +++++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c   |  87 +++++
 drivers/gpu/drm/baikal/baikal_vdu_drm.h       |  65 ++++
 drivers/gpu/drm/baikal/baikal_vdu_drv.c       | 364 ++++++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_plane.c     | 209 ++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_regs.h      | 139 +++++++
 drivers/gpu/drm/bridge/Kconfig                |   7 +
 13 files changed, 1480 insertions(+)
 create mode 100644 drivers/gpu/drm/baikal/Kconfig
 create mode 100644 drivers/gpu/drm/baikal/Makefile
 create mode 100644 drivers/gpu/drm/baikal/baikal-hdmi.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_connector.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_crtc.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drm.h
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drv.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_plane.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_regs.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index b1f22e457fd0..1d6cccc6f1fe 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -230,6 +230,7 @@ config DRM_SCHED
 source "drivers/gpu/drm/i2c/Kconfig"
 
 source "drivers/gpu/drm/arm/Kconfig"
+source "drivers/gpu/drm/baikal/Kconfig"
 
 config DRM_RADEON
 	tristate "ATI Radeon"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 301a44dc18e3..2d6eda7c79d2 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -135,3 +135,4 @@ obj-y			+= xlnx/
 obj-y			+= gud/
 obj-$(CONFIG_DRM_HYPERV) += hyperv/
 obj-$(CONFIG_DRM_SPRD) += sprd/
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/
diff --git a/drivers/gpu/drm/baikal/Kconfig b/drivers/gpu/drm/baikal/Kconfig
new file mode 100644
index 000000000000..7f3661ae5578
--- /dev/null
+++ b/drivers/gpu/drm/baikal/Kconfig
@@ -0,0 +1,15 @@
+config DRM_BAIKAL_VDU
+	tristate "DRM Support for Baikal-M VDU"
+	depends on DRM
+	depends on ARM || ARM64 || COMPILE_TEST
+	depends on COMMON_CLK
+	default y if ARCH_BAIKAL
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	select DRM_PANEL
+	select DRM_BAIKAL_HDMI
+	select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+	help
+	  Choose this option for DRM support for the Baikal-M Video Display Unit (VDU).
+	  If M is selected the module will be called baikal_vdu_drm.
diff --git a/drivers/gpu/drm/baikal/Makefile b/drivers/gpu/drm/baikal/Makefile
new file mode 100644
index 000000000000..eb029494e823
--- /dev/null
+++ b/drivers/gpu/drm/baikal/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+baikal_vdu_drm-y +=	baikal_vdu_connector.o \
+		baikal_vdu_crtc.o \
+		baikal_vdu_drv.o \
+		baikal_vdu_plane.o
+
+baikal_vdu_drm-$(CONFIG_DEBUG_FS) += baikal_vdu_debugfs.o
+
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal_vdu_drm.o
+obj-$(CONFIG_DRM_BAIKAL_HDMI) += baikal-hdmi.o
diff --git a/drivers/gpu/drm/baikal/baikal-hdmi.c b/drivers/gpu/drm/baikal/baikal-hdmi.c
new file mode 100644
index 000000000000..6a55d03d93f8
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal-hdmi.c
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Baikal Electronics BE-M1000 DesignWare HDMI 2.0 Tx PHY support driver
+ *
+ * Copyright (C) 2019-2021 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <drm/drm_modes.h>
+
+#include <drm/bridge/dw_hdmi.h>
+
+int fixed_clock = 0;
+int max_clock = 0;
+
+static const struct dw_hdmi_mpll_config baikal_hdmi_mpll_cfg[] = {
+	/* pixelclk      opmode  gmp         */
+	{  44900000, { { 0x00b3, 0x0000 }, }, },
+	{  90000000, { { 0x0072, 0x0001 }, }, },
+	{ 182750000, { { 0x0051, 0x0002 }, }, },
+	{ 340000000, { { 0x0040, 0x0003 }, }, },
+	{ 594000000, { { 0x1a40, 0x0003 }, }, },
+	{ ~0UL,      { { 0x0000, 0x0000 }, }, }
+};
+
+static const struct dw_hdmi_curr_ctrl baikal_hdmi_cur_ctr[] = {
+	/* pixelclk    current   */
+	{  44900000, { 0x0000, }, },
+	{  90000000, { 0x0008, }, },
+	{ 182750000, { 0x001b, }, },
+	{ 340000000, { 0x0036, }, },
+	{ 594000000, { 0x003f, }, },
+	{ ~0UL,      { 0x0000, }, }
+};
+
+static const struct dw_hdmi_phy_config baikal_hdmi_phy_cfg[] = {
+	/* pixelclk  symbol  term    vlev */
+	{ 148250000, 0x8009, 0x0004, 0x0232},
+	{ 218250000, 0x8009, 0x0004, 0x0230},
+	{ 288000000, 0x8009, 0x0004, 0x0273},
+	{ 340000000, 0x8029, 0x0004, 0x0273},
+	{ 594000000, 0x8039, 0x0004, 0x014a},
+	{ ~0UL,      0x0000, 0x0000, 0x0000}
+};
+
+static enum drm_mode_status baikal_hdmi_mode_valid(struct dw_hdmi *hdmi,
+						   void *data,
+						   const struct drm_display_info *info,
+						   const struct drm_display_mode *mode)
+{
+	if (mode->clock < 13500)
+		return MODE_CLOCK_LOW;
+	if (mode->clock >= 340000)
+		return MODE_CLOCK_HIGH;
+	if (fixed_clock && mode->clock != fixed_clock)
+		return MODE_BAD;
+	if (max_clock && mode->clock > max_clock)
+		return MODE_BAD;
+
+	return MODE_OK;
+}
+
+static struct dw_hdmi_plat_data baikal_dw_hdmi_plat_data = {
+	.mpll_cfg   = baikal_hdmi_mpll_cfg,
+	.cur_ctr    = baikal_hdmi_cur_ctr,
+	.phy_config = baikal_hdmi_phy_cfg,
+	.mode_valid = baikal_hdmi_mode_valid,
+};
+
+static int baikal_dw_hdmi_probe(struct platform_device *pdev)
+{
+	struct dw_hdmi *hdmi;
+	hdmi = dw_hdmi_probe(pdev, &baikal_dw_hdmi_plat_data);
+	if (IS_ERR(hdmi)) {
+		return PTR_ERR(hdmi);
+	} else {
+		return 0;
+	}
+}
+
+static int baikal_dw_hdmi_remove(struct platform_device *pdev)
+{
+	struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
+	dw_hdmi_remove(hdmi);
+	return 0;
+}
+
+static const struct of_device_id baikal_dw_hdmi_of_table[] = {
+	{ .compatible = "baikal,hdmi" },
+	{ /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, baikal_dw_hdmi_of_table);
+
+static struct platform_driver baikal_dw_hdmi_platform_driver = {
+	.probe		= baikal_dw_hdmi_probe,
+	.remove		= baikal_dw_hdmi_remove,
+	.driver		= {
+		.name	= "baikal-dw-hdmi",
+		.of_match_table = baikal_dw_hdmi_of_table,
+	},
+};
+
+module_param(fixed_clock, int, 0644);
+module_param(max_clock, int, 0644);
+
+module_platform_driver(baikal_dw_hdmi_platform_driver);
+
+MODULE_AUTHOR("Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal BE-M1000 SoC DesignWare HDMI 2.0 Tx + Gen2 PHY Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_connector.c b/drivers/gpu/drm/baikal/baikal_vdu_connector.c
new file mode 100644
index 000000000000..2f20cf3da627
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_connector.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+/**
+ * baikal_vdu_connector.c
+ * Implementation of the connector functions for Baikal Electronics BE-M1000 SoC's VDU
+ */
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define to_baikal_vdu_private(x) \
+	container_of(x, struct baikal_vdu_private, connector)
+
+static void baikal_vdu_drm_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static enum drm_connector_status baikal_vdu_drm_connector_detect(
+		struct drm_connector *connector, bool force)
+{
+	struct baikal_vdu_private *priv = to_baikal_vdu_private(connector);
+
+	return (priv->panel ?
+		connector_status_connected :
+		connector_status_disconnected);
+}
+
+static int baikal_vdu_drm_connector_helper_get_modes(
+		struct drm_connector *connector)
+{
+	struct baikal_vdu_private *priv = to_baikal_vdu_private(connector);
+
+	if (!priv->panel)
+		return 0;
+
+	return drm_panel_get_modes(priv->panel, connector);
+}
+
+const struct drm_connector_funcs connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = baikal_vdu_drm_connector_destroy,
+	.detect = baikal_vdu_drm_connector_detect,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+const struct drm_connector_helper_funcs connector_helper_funcs = {
+	.get_modes = baikal_vdu_drm_connector_helper_get_modes,
+};
+
+static const struct drm_encoder_funcs encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+int baikal_vdu_lvds_connector_create(struct drm_device *dev)
+{
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct drm_connector *connector = &priv->connector;
+	struct drm_encoder *encoder = &priv->encoder;
+	int ret = 0;
+
+	ret = drm_connector_init(dev, connector, &connector_funcs,
+			DRM_MODE_CONNECTOR_LVDS);
+	if (ret) {
+		dev_err(dev->dev, "drm_connector_init failed: %d\n", ret);
+		goto out;
+	}
+	drm_connector_helper_add(connector, &connector_helper_funcs);
+	ret = drm_encoder_init(dev, encoder, &encoder_funcs,
+			       DRM_MODE_ENCODER_LVDS, NULL);
+	if (ret) {
+		dev_err(dev->dev, "drm_encoder_init failed: %d\n", ret);
+		goto out;
+	}
+	encoder->crtc = &priv->crtc;
+	encoder->possible_crtcs = drm_crtc_mask(encoder->crtc);
+	ret = drm_connector_attach_encoder(connector, encoder);
+	if (ret) {
+		dev_err(dev->dev, "drm_connector_attach_encoder failed: %d\n", ret);
+		goto out;
+	}
+	ret = drm_connector_register(connector);
+	if (ret) {
+		dev_err(dev->dev, "drm_connector_register failed: %d\n", ret);
+		goto out;
+	}
+out:
+	return ret;
+}
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_crtc.c b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
new file mode 100644
index 000000000000..e338ff8b3080
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+/**
+ * baikal_vdu_crtc.c
+ * Implementation of the CRTC functions for Baikal Electronics BE-M1000 VDU driver
+ */
+#include <linux/clk.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_vblank.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+struct baikal_vdu_crtc_mode_fixup {
+	int vdisplay;
+	int vfp_add;
+};
+
+static const struct baikal_vdu_crtc_mode_fixup mode_fixups[] = {
+	{ 480, 38 },
+	{ 600, 8 },
+	{ 720, 43 },
+	{ 768, 43 },
+	{ 800, 71 },
+	{ 864, 71 },
+	{ 900, 71 },
+	{ 960, 71 },
+	{ 1024, 25 },
+	{ 1050, 25 },
+	{ 1080, 8 },
+	{ 1200, 32 },
+	{ 1440, 27 },
+	{ ~0U },
+};
+
+irqreturn_t baikal_vdu_irq(int irq, void *data)
+{
+	struct drm_device *drm = data;
+	struct baikal_vdu_private *priv = drm->dev_private;
+	irqreturn_t status = IRQ_NONE;
+	u32 raw_stat;
+	u32 irq_stat;
+
+	irq_stat = readl(priv->regs + IVR);
+	raw_stat = readl(priv->regs + ISR);
+
+	if (irq_stat & INTR_VCT) {
+		priv->counters[10]++;
+		drm_crtc_handle_vblank(&priv->crtc);
+		status = IRQ_HANDLED;
+	}
+
+	if (irq_stat & INTR_FER) {
+		priv->counters[11]++;
+		priv->counters[12] = readl(priv->regs + DBAR);
+		priv->counters[13] = readl(priv->regs + DCAR);
+		priv->counters[14] = readl(priv->regs + MRR);
+		status = IRQ_HANDLED;
+	}
+
+	priv->counters[3] |= raw_stat;
+
+	/* Clear all interrupts */
+	writel(irq_stat, priv->regs + ISR);
+
+	return status;
+}
+
+bool baikal_vdu_crtc_mode_fixup(struct drm_crtc *crtc,
+					const struct drm_display_mode *mode,
+					struct drm_display_mode *adjusted_mode)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+
+	memcpy(adjusted_mode, mode, sizeof(*mode));
+
+	if (!priv->mode_fixup)
+		return true;
+
+	if (priv->mode_fixup == -1) {
+		const struct baikal_vdu_crtc_mode_fixup *fixups = mode_fixups;
+		for (; fixups && fixups->vdisplay != ~0U; ++fixups) {
+			if (mode->vdisplay <= fixups->vdisplay)
+				break;
+		}
+		if (fixups->vdisplay == ~0U)
+			return true;
+		else
+			priv->mode_fixup = fixups->vfp_add;
+	}
+
+	adjusted_mode->vtotal += priv->mode_fixup;
+	adjusted_mode->vsync_start += priv->mode_fixup;
+	adjusted_mode->vsync_end += priv->mode_fixup;
+	adjusted_mode->clock = mode->clock * adjusted_mode->vtotal / mode->vtotal;
+
+	return true;
+}
+
+static void baikal_vdu_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	const struct drm_display_mode *orig_mode = &crtc->state->mode;
+	const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	unsigned int ppl, hsw, hfp, hbp;
+	unsigned int lpp, vsw, vfp, vbp;
+	unsigned int reg;
+
+	drm_mode_debug_printmodeline(orig_mode);
+	drm_mode_debug_printmodeline(mode);
+
+	ppl = mode->hdisplay / 16;
+	if (priv->type == VDU_TYPE_LVDS) {
+		hsw = mode->hsync_end - mode->hsync_start;
+		hfp = mode->hsync_start - mode->hdisplay - 1;
+		hbp = mode->htotal - mode->hsync_end;
+	} else {
+		hsw = mode->hsync_end - mode->hsync_start - 1;
+		hfp = mode->hsync_start - mode->hdisplay;
+		hbp = mode->htotal - mode->hsync_end - 1;
+	}
+
+	lpp = mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	vfp = mode->vsync_start - mode->vdisplay;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	writel((HTR_HFP(hfp) & HTR_HFP_MASK) |
+			(HTR_PPL(ppl) & HTR_PPL_MASK) |
+			(HTR_HBP(hbp) & HTR_HBP_MASK) |
+			(HTR_HSW(hsw) & HTR_HSW_MASK),
+			priv->regs + HTR);
+
+	if (mode->hdisplay > 4080 || ppl * 16 != mode->hdisplay)
+		writel((HPPLOR_HPPLO(mode->hdisplay) & HPPLOR_HPPLO_MASK) | HPPLOR_HPOE,
+				priv->regs + HPPLOR);
+
+	writel((VTR1_VSW(vsw) & VTR1_VSW_MASK) |
+			(VTR1_VFP(vfp) & VTR1_VFP_MASK) |
+			(VTR1_VBP(vbp) & VTR1_VBP_MASK),
+			priv->regs + VTR1);
+
+	writel(lpp & VTR2_LPP_MASK, priv->regs + VTR2);
+
+	writel((HVTER_VSWE(vsw >> VTR1_VSW_LSB_WIDTH) & HVTER_VSWE_MASK) |
+			(HVTER_HSWE(hsw >> HTR_HSW_LSB_WIDTH) & HVTER_HSWE_MASK) |
+			(HVTER_VBPE(vbp >> VTR1_VBP_LSB_WIDTH) & HVTER_VBPE_MASK) |
+			(HVTER_VFPE(vfp >> VTR1_VFP_LSB_WIDTH) & HVTER_VFPE_MASK) |
+			(HVTER_HBPE(hbp >> HTR_HBP_LSB_WIDTH) & HVTER_HBPE_MASK) |
+			(HVTER_HFPE(hfp >> HTR_HFP_LSB_WIDTH) & HVTER_HFPE_MASK),
+			priv->regs + HVTER);
+
+	/* Set polarities */
+	reg = readl(priv->regs + CR1);
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		reg |= CR1_VSP;
+	else
+		reg &= ~CR1_VSP;
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		reg |= CR1_HSP;
+	else
+		reg &= ~CR1_HSP;
+	reg |= CR1_DEP; // set DE to active high;
+	writel(reg, priv->regs + CR1);
+
+	crtc->hwmode = crtc->state->adjusted_mode;
+}
+
+static void baikal_vdu_crtc_helper_enable(struct drm_crtc *crtc,
+                      struct drm_atomic_state *state)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+	struct drm_panel *panel = priv->panel;
+	const char *data_mapping = NULL;
+	u32 cntl, gpio;
+
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "priv = %px\n", priv);
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+	clk_prepare_enable(priv->clk);
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "panel = %px\n", panel);
+	drm_panel_prepare(panel);
+
+	writel(ISCR_VSC_VFP, priv->regs + ISCR);
+
+	/* release clock reset; enable clocking */
+	cntl = readl(priv->regs + PCTR);
+	cntl |= PCTR_PCR + PCTR_PCI;
+	writel(cntl, priv->regs + PCTR);
+
+	/* Set 16-word input FIFO watermark */
+	/* Enable and Power Up */
+	cntl = readl(priv->regs + CR1);
+	cntl &= ~CR1_FDW_MASK;
+	cntl |= CR1_LCE + CR1_FDW_16_WORDS;
+
+	if (priv->type == VDU_TYPE_LVDS) {
+		if (panel) {
+			of_property_read_string(panel->dev->of_node,
+						"data-mapping", &data_mapping);
+		}
+		if (!data_mapping) {
+			cntl |= CR1_OPS_LCD18;
+			dev_dbg(crtc->dev->dev, "data mapping not specified, using jeida-18");
+		} else if (!strncmp(data_mapping, "vesa-24", 7)) {
+			cntl |= CR1_OPS_LCD24;
+			dev_dbg(crtc->dev->dev, "using vesa-24 mapping\n");
+		} else if (!strncmp(data_mapping, "jeida-18", 8)) {
+				cntl |= CR1_OPS_LCD18;
+				dev_dbg(crtc->dev->dev, "using jeida-18 mapping\n");
+		} else {
+			dev_warn(crtc->dev->dev, "unsupported data mapping '%s', using vesa-24\n", data_mapping);
+			cntl |= CR1_OPS_LCD24;
+		}
+
+		gpio = GPIOR_UHD_ENB;
+		if (priv->ep_count == 4)
+			gpio |= GPIOR_UHD_QUAD_PORT;
+		else if (priv->ep_count == 2)
+			gpio |= GPIOR_UHD_DUAL_PORT;
+		else
+			gpio |= GPIOR_UHD_SNGL_PORT;
+		writel(gpio, priv->regs + GPIOR);
+	} else
+		cntl |= CR1_OPS_LCD24;
+	writel(cntl, priv->regs + CR1);
+
+	drm_panel_enable(priv->panel);
+	drm_crtc_vblank_on(crtc);
+}
+
+void baikal_vdu_crtc_helper_disable(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+
+	drm_crtc_vblank_off(crtc);
+	drm_panel_disable(priv->panel);
+
+	drm_panel_unprepare(priv->panel);
+
+	/* Disable clock */
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "disabling pixel clock\n");
+	clk_disable_unprepare(priv->clk);
+}
+
+static void baikal_vdu_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+                       struct drm_atomic_state *state)
+{
+	struct drm_pending_vblank_event *event = crtc->state->event;
+
+	if (event) {
+		crtc->state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+		if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0)
+			drm_crtc_arm_vblank_event(crtc, event);
+		else
+			drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+static int baikal_vdu_enable_vblank(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+
+	/* clear interrupt status */
+	writel(0x3ffff, priv->regs + ISR);
+
+	writel(INTR_VCT + INTR_FER, priv->regs + IMR);
+
+	return 0;
+}
+
+static void baikal_vdu_disable_vblank(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+
+	/* clear interrupt status */
+	writel(0x3ffff, priv->regs + ISR);
+
+	writel(INTR_FER, priv->regs + IMR);
+}
+
+const struct drm_crtc_funcs crtc_funcs = {
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.reset = drm_atomic_helper_crtc_reset,
+	.destroy = drm_crtc_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = baikal_vdu_enable_vblank,
+	.disable_vblank = baikal_vdu_disable_vblank,
+};
+
+const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+	.mode_fixup = baikal_vdu_crtc_mode_fixup,
+	.mode_set_nofb = baikal_vdu_crtc_helper_mode_set_nofb,
+	.atomic_flush = baikal_vdu_crtc_helper_atomic_flush,
+	.disable = baikal_vdu_crtc_helper_disable,
+	.atomic_enable = baikal_vdu_crtc_helper_enable,
+};
+
+int baikal_vdu_crtc_create(struct drm_device *dev)
+{
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct drm_crtc *crtc = &priv->crtc;
+
+	drm_crtc_init_with_planes(dev, crtc,
+				  &priv->primary, NULL,
+				  &crtc_funcs, "primary");
+	drm_crtc_helper_add(crtc, &crtc_helper_funcs);
+
+	/* XXX: The runtime clock disabling still results in
+	 * occasional system hangs, and needs debugging.
+	 */
+
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+	clk_prepare_enable(priv->clk);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
new file mode 100644
index 000000000000..77be6aa588dc
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ *  Copyright © 2017 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/seq_file.h>
+#include <linux/device.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define REGDEF(reg) { reg, #reg }
+static const struct {
+	u32 reg;
+	const char *name;
+} baikal_vdu_reg_defs[] = {
+	REGDEF(CR1),
+	REGDEF(HTR),
+	REGDEF(VTR1),
+	REGDEF(VTR2),
+	REGDEF(PCTR),
+	REGDEF(ISR),
+	REGDEF(IMR),
+	REGDEF(IVR),
+	REGDEF(ISCR),
+	REGDEF(DBAR),
+	REGDEF(DCAR),
+	REGDEF(DEAR),
+	REGDEF(HVTER),
+	REGDEF(HPPLOR),
+	REGDEF(GPIOR),
+	REGDEF(OWER),
+	REGDEF(OWXSER0),
+	REGDEF(OWYSER0),
+	REGDEF(OWDBAR0),
+	REGDEF(OWDCAR0),
+	REGDEF(OWDEAR0),
+	REGDEF(OWXSER1),
+	REGDEF(OWYSER1),
+	REGDEF(OWDBAR1),
+	REGDEF(OWDCAR1),
+	REGDEF(OWDEAR1),
+	REGDEF(MRR),
+};
+
+int baikal_vdu_debugfs_regs(struct seq_file *m, void *unused)
+{
+	struct drm_info_node *node = (struct drm_info_node *)m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(baikal_vdu_reg_defs); i++) {
+		seq_printf(m, "%s (0x%04x): 0x%08x\n",
+			   baikal_vdu_reg_defs[i].name, baikal_vdu_reg_defs[i].reg,
+			   readl(priv->regs + baikal_vdu_reg_defs[i].reg));
+	}
+
+	for (i = 0; i < ARRAY_SIZE(priv->counters); i++) {
+		seq_printf(m, "COUNTER[%d]: 0x%08x\n", i, priv->counters[i]);
+	}
+
+	return 0;
+}
+
+static const struct drm_info_list baikal_vdu_debugfs_list[] = {
+	{"regs", baikal_vdu_debugfs_regs, 0},
+};
+
+void baikal_vdu_debugfs_init(struct drm_minor *minor)
+{
+	drm_debugfs_create_files(baikal_vdu_debugfs_list,
+				 ARRAY_SIZE(baikal_vdu_debugfs_list),
+				 minor->debugfs_root, minor);
+}
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drm.h b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
new file mode 100644
index 000000000000..755d4abeedf7
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+#ifndef __BAIKAL_VDU_DRM_H__
+#define __BAIKAL_VDU_DRM_H__
+
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#define VDU_TYPE_HDMI	0
+#define VDU_TYPE_LVDS	1
+
+struct baikal_vdu_private {
+	struct drm_device *drm;
+
+	unsigned int irq;
+	bool irq_enabled;
+
+	struct drm_connector connector;
+	struct drm_crtc crtc;
+	struct drm_encoder encoder;
+	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+	struct drm_plane primary;
+
+	void *regs;
+	struct clk *clk;
+	u32 counters[20];
+	int mode_fixup;
+	int type;
+	u32 ep_count;
+	u32 fb_addr;
+	u32 fb_end;
+
+	struct gpio_desc *enable_gpio;
+};
+
+/* CRTC Functions */
+int baikal_vdu_crtc_create(struct drm_device *dev);
+irqreturn_t baikal_vdu_irq(int irq, void *data);
+
+int baikal_vdu_primary_plane_init(struct drm_device *dev);
+
+/* Connector Functions */
+int baikal_vdu_lvds_connector_create(struct drm_device *dev);
+
+void baikal_vdu_debugfs_init(struct drm_minor *minor);
+
+#endif /* __BAIKAL_VDU_DRM_H__ */
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drv.c b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
new file mode 100644
index 000000000000..cedf7962daa6
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ * All bugs by Alexey Sheplyakov <asheplyakov@altlinux.org>
+ *
+ * This driver is based on ARM PL111 DRM driver
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_aperture.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define DRIVER_NAME                 "baikal-vdu"
+#define DRIVER_DESC                 "DRM module for Baikal VDU"
+#define DRIVER_DATE                 "20200131"
+
+#define BAIKAL_SMC_SCP_LOG_DISABLE  0x82000200
+
+int mode_fixup = 0;
+
+static struct drm_mode_config_funcs mode_config_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static const struct drm_encoder_funcs baikal_vdu_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+DEFINE_DRM_GEM_CMA_FOPS(drm_fops);
+
+static struct drm_driver vdu_drm_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.ioctls = NULL,
+	.fops = &drm_fops,
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = 1,
+	.minor = 0,
+	.patchlevel = 0,
+	DRM_GEM_CMA_DRIVER_OPS,
+#if defined(CONFIG_DEBUG_FS)
+	.debugfs_init = baikal_vdu_debugfs_init,
+#endif
+};
+
+static int vdu_modeset_init(struct drm_device *dev)
+{
+	struct drm_mode_config *mode_config;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct arm_smccc_res res;
+	int ret = 0, ep_count = 0;
+
+	if (priv == NULL)
+		return -EINVAL;
+
+	drm_mode_config_init(dev);
+	mode_config = &dev->mode_config;
+	mode_config->funcs = &mode_config_funcs;
+	mode_config->min_width = 1;
+	mode_config->max_width = 4095;
+	mode_config->min_height = 1;
+	mode_config->max_height = 4095;
+
+	ret = baikal_vdu_primary_plane_init(dev);
+	if (ret != 0) {
+		dev_err(dev->dev, "Failed to init primary plane\n");
+		goto out_config;
+	}
+
+	ret = baikal_vdu_crtc_create(dev);
+	if (ret) {
+		dev_err(dev->dev, "Failed to create crtc\n");
+		goto out_config;
+	}
+
+	ret = drm_of_find_panel_or_bridge(dev->dev->of_node, -1, -1,
+					  &priv->panel,
+					  &priv->bridge);
+	if (ret == -EPROBE_DEFER) {
+		dev_info(dev->dev, "Bridge probe deferred\n");
+		goto out_config;
+	}
+	ep_count = of_graph_get_endpoint_count(dev->dev->of_node);
+	if (ep_count <= 0) {
+		dev_err(dev->dev, "no endpoints connected to panel/bridge\n");
+		goto out_config;
+	}
+	priv->ep_count = ep_count;
+	dev_dbg(dev->dev, "panel/bridge has %d endpoints\n", priv->ep_count);
+
+	if (priv->bridge) {
+		struct drm_encoder *encoder = &priv->encoder;
+		ret = drm_encoder_init(dev, encoder, &baikal_vdu_encoder_funcs,
+				       DRM_MODE_ENCODER_NONE, NULL);
+		if (ret) {
+			dev_err(dev->dev, "failed to create DRM encoder\n");
+			goto out_config;
+		}
+		encoder->crtc = &priv->crtc;
+		encoder->possible_crtcs = drm_crtc_mask(encoder->crtc);
+		priv->bridge->encoder = &priv->encoder;
+		ret = drm_bridge_attach(&priv->encoder, priv->bridge, NULL, 0);
+		if (ret) {
+			dev_err(dev->dev, "Failed to attach DRM bridge %d\n", ret);
+			goto out_config;
+		}
+	} else if (priv->panel) {
+		dev_dbg(dev->dev, "panel has %d endpoints\n", priv->ep_count);
+		ret = baikal_vdu_lvds_connector_create(dev);
+		if (ret) {
+			dev_err(dev->dev, "Failed to create DRM connector\n");
+			goto out_config;
+		}
+	} else
+		ret = -EINVAL;
+
+	if (ret) {
+		dev_err(dev->dev, "No bridge or panel attached!\n");
+		goto out_config;
+	}
+
+	priv->clk = clk_get(dev->dev, "pclk");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev->dev, "fatal: unable to get pclk, err %ld\n", PTR_ERR(priv->clk));
+		ret = PTR_ERR(priv->clk);
+		goto out_config;
+	}
+
+	priv->mode_fixup = mode_fixup;
+
+	drm_aperture_remove_framebuffers(false, &vdu_drm_driver);
+
+	ret = drm_vblank_init(dev, 1);
+	if (ret != 0) {
+		dev_err(dev->dev, "Failed to init vblank\n");
+		goto out_clk;
+	}
+
+	arm_smccc_smc(BAIKAL_SMC_SCP_LOG_DISABLE, 0, 0, 0, 0, 0, 0, 0, &res);
+
+	drm_mode_config_reset(dev);
+
+	drm_kms_helper_poll_init(dev);
+
+	ret = drm_dev_register(dev, 0);
+	if (ret)
+		goto out_clk;
+
+	drm_fbdev_generic_setup(dev, 32);
+	goto finish;
+
+out_clk:
+	clk_put(priv->clk);
+out_config:
+	drm_mode_config_cleanup(dev);
+finish:
+	return ret;
+}
+
+
+static int baikal_vdu_irq_install(struct baikal_vdu_private *priv, int irq)
+{
+	int ret;
+	ret= request_irq(irq, baikal_vdu_irq, 0, DRIVER_NAME, priv->drm);
+	if (ret < 0)
+		return ret;
+	priv->irq_enabled = true;
+	return 0;
+}
+
+static void baikal_vdu_irq_uninstall(struct baikal_vdu_private *priv)
+{
+	if (priv->irq_enabled) {
+		priv->irq_enabled = false;
+		disable_irq(priv->irq);
+		free_irq(priv->irq, priv->drm);
+	}
+}
+
+static int vdu_maybe_enable_lvds(struct baikal_vdu_private *vdu)
+{
+	int err = 0;
+	struct device *dev;
+	if (!vdu->drm) {
+		pr_err("%s: vdu->drm is NULL\n", __func__);
+		return -EINVAL;
+	}
+	dev = vdu->drm->dev;
+
+	vdu->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(vdu->enable_gpio)) {
+		err = (int)PTR_ERR(vdu->enable_gpio);
+		dev_err(dev, "failed to get enable-gpios, error %d\n", err);
+		vdu->enable_gpio = NULL;
+		return err;
+	}
+	if (vdu->enable_gpio) {
+		dev_dbg(dev, "%s: setting enable-gpio\n", __func__);
+		gpiod_set_value_cansleep(vdu->enable_gpio, 1);
+	} else {
+		dev_dbg(dev, "%s: no enable-gpios, assuming it's handled by panel-lvds\n", __func__);
+	}
+	return 0;
+}
+
+static int baikal_vdu_drm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct baikal_vdu_private *priv;
+	struct drm_device *drm;
+	struct resource *mem;
+	int irq;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	drm = drm_dev_alloc(&vdu_drm_driver, dev);
+	if (IS_ERR(drm))
+		return PTR_ERR(drm);
+	platform_set_drvdata(pdev, drm);
+	priv->drm = drm;
+	drm->dev_private = priv;
+
+	if (!(mem = platform_get_resource(pdev, IORESOURCE_MEM, 0))) {
+		dev_err(dev, "%s no MMIO resource specified\n", __func__);
+		return -EINVAL;
+	}
+
+	priv->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(priv->regs)) {
+		dev_err(dev, "%s MMIO allocation failed\n", __func__);
+		return PTR_ERR(priv->regs);
+	}
+
+	/* turn off interrupts before requesting the irq */
+	writel(0, priv->regs + IMR);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "%s no IRQ resource specified\n", __func__);
+		return -EINVAL;
+	}
+	priv->irq = irq;
+
+	ret = baikal_vdu_irq_install(priv, irq);
+	if (ret != 0) {
+		dev_err(dev, "%s IRQ %d allocation failed\n", __func__, irq);
+		return ret;
+	}
+
+	if (pdev->dev.of_node && of_property_read_bool(pdev->dev.of_node, "lvds-out")) {
+		priv->type = VDU_TYPE_LVDS;
+		if (of_property_read_u32(pdev->dev.of_node, "num-lanes", &priv->ep_count))
+			priv->ep_count = 1;
+	}
+	else
+		priv->type = VDU_TYPE_HDMI;
+
+	ret = vdu_modeset_init(drm);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init modeset\n");
+		goto dev_unref;
+	}
+
+	ret = vdu_maybe_enable_lvds(priv);
+	if (ret != 0) {
+		dev_err(dev, "failed to enable LVDS\n");
+	}
+
+	return 0;
+
+dev_unref:
+	writel(0, priv->regs + IMR);
+	writel(0x3ffff, priv->regs + ISR);
+	baikal_vdu_irq_uninstall(priv);
+	drm->dev_private = NULL;
+	drm_dev_put(drm);
+	return ret;
+}
+
+static int baikal_vdu_drm_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm;
+	struct baikal_vdu_private *priv;
+
+	drm = platform_get_drvdata(pdev);
+	if (!drm) {
+		return -1;
+	}
+	priv = drm->dev_private;
+
+	drm_dev_unregister(drm);
+	drm_mode_config_cleanup(drm);
+	baikal_vdu_irq_uninstall(priv);
+	drm->dev_private = NULL;
+	drm_dev_put(drm);
+
+	return 0;
+}
+
+static const struct of_device_id baikal_vdu_of_match[] = {
+    { .compatible = "baikal,vdu" },
+    { },
+};
+MODULE_DEVICE_TABLE(of, baikal_vdu_of_match);
+
+static struct platform_driver baikal_vdu_platform_driver = {
+    .probe  = baikal_vdu_drm_probe,
+    .remove = baikal_vdu_drm_remove,
+    .driver = {
+        .name   = DRIVER_NAME,
+        .of_match_table = baikal_vdu_of_match,
+    },
+};
+
+module_param(mode_fixup, int, 0644);
+
+module_platform_driver(baikal_vdu_platform_driver);
+
+MODULE_AUTHOR("Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal Electronics BE-M1000 Video Display Unit (VDU) DRM Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_SOFTDEP("pre: baikal_hdmi");
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_plane.c b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
new file mode 100644
index 000000000000..4a3e971793d2
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/of_graph.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+static int baikal_vdu_primary_plane_atomic_check(struct drm_plane *plane,
+						 struct drm_atomic_state *atomic_state)
+{
+	struct drm_device *dev = plane->dev;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct drm_crtc_state *crtc_state;
+	struct drm_plane_state *state;
+	struct drm_display_mode *mode;
+	int rate, ret;
+	u32 cntl;
+
+	state = drm_atomic_get_new_plane_state(atomic_state, plane);
+	if (!state || !state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
+	if (IS_ERR(crtc_state)) {
+		ret = PTR_ERR(crtc_state);
+		dev_warn(dev->dev, "failed to get crtc_state: %d\n", ret);
+		return ret;
+	}
+	mode = &crtc_state->adjusted_mode;
+	rate = mode->clock * 1000;
+	if (rate == clk_get_rate(priv->clk))
+		return 0;
+
+	/* hold clock domain reset; disable clocking */
+	writel(0, priv->regs + PCTR);
+
+	if (__clk_is_enabled(priv->clk))
+		clk_disable_unprepare(priv->clk);
+	ret = clk_set_rate(priv->clk, rate);
+	DRM_DEV_DEBUG_DRIVER(dev->dev, "Requested pixel clock is %d Hz\n", rate);
+
+	if (ret < 0) {
+		DRM_ERROR("Cannot set desired pixel clock (%d Hz)\n",
+			  rate);
+		ret = -EINVAL;
+	} else {
+		clk_prepare_enable(priv->clk);
+		if (__clk_is_enabled(priv->clk))
+			ret = 0;
+		else {
+			DRM_ERROR("PLL could not lock at desired frequency (%d Hz)\n",
+			  rate);
+			ret = -EINVAL;
+		}
+	}
+
+	/* release clock domain reset; enable clocking */
+	cntl = readl(priv->regs + PCTR);
+	cntl |= PCTR_PCR + PCTR_PCI;
+	writel(cntl, priv->regs + PCTR);
+
+	return ret;
+}
+
+static void baikal_vdu_primary_plane_atomic_update(struct drm_plane *plane,
+						   struct drm_atomic_state *old_state)
+{
+	struct drm_device *dev = plane->dev;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct drm_plane_state *state = plane->state;
+	struct drm_framebuffer *fb = state->fb;
+	u32 cntl, addr, end;
+
+	if (!fb)
+		return;
+
+	addr = drm_fb_cma_get_gem_addr(fb, state, 0);
+	priv->fb_addr = addr & 0xfffffff8;
+
+	cntl = readl(priv->regs + CR1);
+	cntl &= ~CR1_BPP_MASK;
+
+	/* Note that the the hardware's format reader takes 'r' from
+	 * the low bit, while DRM formats list channels from high bit
+	 * to low bit as you read left to right.
+	 */
+	switch (fb->format->format) {
+	case DRM_FORMAT_BGR888:
+		cntl |= CR1_BPP24 | CR1_FBP | CR1_BGR;
+		break;
+	case DRM_FORMAT_RGB888:
+		cntl |= CR1_BPP24 | CR1_FBP;
+		break;
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_XBGR8888:
+		cntl |= CR1_BPP24 | CR1_BGR;
+		break;
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XRGB8888:
+		cntl |= CR1_BPP24;
+		break;
+	case DRM_FORMAT_BGR565:
+		cntl |= CR1_BPP16_565 | CR1_BGR;
+		break;
+	case DRM_FORMAT_RGB565:
+		cntl |= CR1_BPP16_565;
+		break;
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_XBGR1555:
+		cntl |= CR1_BPP16_555 | CR1_BGR;
+		break;
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_XRGB1555:
+		cntl |= CR1_BPP16_555;
+		break;
+	default:
+		WARN_ONCE(true, "Unknown FB format 0x%08x, set XRGB8888 instead\n",
+				fb->format->format);
+		cntl |= CR1_BPP24;
+		break;
+	}
+
+	writel(priv->fb_addr, priv->regs + DBAR);
+	end = ((priv->fb_addr + fb->height * fb->pitches[0] - 1) & MRR_DEAR_MRR_MASK) | \
+		MRR_OUTSTND_RQ(4);
+
+	if (priv->fb_end < end) {
+		writel(end, priv->regs + MRR);
+		priv->fb_end = end;
+	}
+	writel(cntl, priv->regs + CR1);
+}
+
+static const struct drm_plane_helper_funcs baikal_vdu_primary_plane_helper_funcs = {
+	.atomic_check = baikal_vdu_primary_plane_atomic_check,
+	.atomic_update = baikal_vdu_primary_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs baikal_vdu_primary_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.reset = drm_atomic_helper_plane_reset,
+	.destroy = drm_plane_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+int baikal_vdu_primary_plane_init(struct drm_device *drm)
+{
+	struct baikal_vdu_private *priv = drm->dev_private;
+	struct drm_plane *plane = &priv->primary;
+	static const u32 formats[] = {
+		DRM_FORMAT_BGR888,
+		DRM_FORMAT_RGB888,
+		DRM_FORMAT_ABGR8888,
+		DRM_FORMAT_XBGR8888,
+		DRM_FORMAT_ARGB8888,
+		DRM_FORMAT_XRGB8888,
+		DRM_FORMAT_BGR565,
+		DRM_FORMAT_RGB565,
+		DRM_FORMAT_ABGR1555,
+		DRM_FORMAT_XBGR1555,
+		DRM_FORMAT_ARGB1555,
+		DRM_FORMAT_XRGB1555,
+	};
+	int ret;
+
+	ret = drm_universal_plane_init(drm, plane, 0,
+				       &baikal_vdu_primary_plane_funcs,
+				       formats,
+				       ARRAY_SIZE(formats),
+				       NULL,
+				       DRM_PLANE_TYPE_PRIMARY,
+				       NULL);
+	if (ret)
+		return ret;
+
+	drm_plane_helper_add(plane, &baikal_vdu_primary_plane_helper_funcs);
+
+	return 0;
+}
+
+
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_regs.h b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
new file mode 100644
index 000000000000..5553fcac5fec
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019-2021 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ *   David A Rusling
+ *   Copyright (C) 2001 ARM Limited
+ */
+
+#ifndef __BAIKAL_VDU_REGS_H__
+#define __BAIKAL_VDU_REGS_H__
+
+#define CR1         0x000
+#define HTR         0x008
+#define VTR1        0x00C
+#define VTR2        0x010
+#define PCTR        0x014
+#define ISR         0x018
+#define IMR         0x01C
+#define IVR         0x020
+#define ISCR        0x024
+#define DBAR        0x028
+#define DCAR        0x02C
+#define DEAR        0x030
+#define HVTER       0x044
+#define HPPLOR      0x048
+#define GPIOR       0x1F8
+#define OWER        0x600
+#define OWXSER0     0x604
+#define OWYSER0     0x608
+#define OWDBAR0     0x60C
+#define OWDCAR0     0x610
+#define OWDEAR0     0x614
+#define OWXSER1     0x618
+#define OWYSER1     0x61C
+#define OWDBAR1     0x620
+#define OWDCAR1     0x624
+#define OWDEAR1     0x628
+#define MRR         0xFFC
+
+#define INTR_BAU    BIT(7)
+#define INTR_VCT    BIT(6)
+#define INTR_MBE    BIT(5)
+#define INTR_FER    BIT(4)
+
+#define CR1_FBP             BIT(19)
+#define CR1_FDW_MASK        GENMASK(17, 16)
+#define CR1_FDW_4_WORDS     (0 << 16)
+#define CR1_FDW_8_WORDS     (1 << 16)
+#define CR1_FDW_16_WORDS    (2 << 16)
+#define CR1_OPS_LCD18       (0 << 13)
+#define CR1_OPS_LCD24       (1 << 13)
+#define CR1_OPS_565         (0 << 12)
+#define CR1_OPS_555         (1 << 12)
+#define CR1_VSP             BIT(11)
+#define CR1_HSP             BIT(10)
+#define CR1_DEP             BIT(8)
+#define CR1_BGR             BIT(5)
+#define CR1_BPP_MASK        GENMASK(4, 2)
+#define CR1_BPP1            (0 << 2)
+#define CR1_BPP2            (1 << 2)
+#define CR1_BPP4            (2 << 2)
+#define CR1_BPP8            (3 << 2)
+#define CR1_BPP16           (4 << 2)
+#define CR1_BPP18           (5 << 2)
+#define CR1_BPP24           (6 << 2)
+#define CR1_LCE             BIT(0)
+
+#define CR1_BPP16_555 ((CR1_BPP16) | (CR1_OPS_555))
+#define CR1_BPP16_565 ((CR1_BPP16) | (CR1_OPS_565))
+
+#define VTR1_VBP_MASK       GENMASK(23, 16)
+#define VTR1_VBP(x)         ((x) << 16)
+#define VTR1_VBP_LSB_WIDTH  8
+#define VTR1_VFP_MASK       GENMASK(15, 8)
+#define VTR1_VFP(x)         ((x) << 8)
+#define VTR1_VFP_LSB_WIDTH  8
+#define VTR1_VSW_MASK       GENMASK(7, 0)
+#define VTR1_VSW(x)         ((x) << 0)
+#define VTR1_VSW_LSB_WIDTH  8
+
+#define VTR2_LPP_MASK       GENMASK(11, 0)
+
+#define HTR_HSW_MASK        GENMASK(31, 24)
+#define HTR_HSW(x)          ((x) << 24)
+#define HTR_HSW_LSB_WIDTH   8
+#define HTR_HBP_MASK        GENMASK(23, 16)
+#define HTR_HBP(x)          ((x) << 16)
+#define HTR_HBP_LSB_WIDTH   8
+#define HTR_PPL_MASK        GENMASK(15, 8)
+#define HTR_PPL(x)          ((x) << 8)
+#define HTR_HFP_MASK        GENMASK(7, 0)
+#define HTR_HFP(x)          ((x) << 0)
+#define HTR_HFP_LSB_WIDTH   8
+
+#define PCTR_PCI2           BIT(11)
+#define PCTR_PCR            BIT(10)
+#define PCTR_PCI            BIT(9)
+#define PCTR_PCB            BIT(8)
+#define PCTR_PCD_MASK       GENMASK(7, 0)
+#define PCTR_MAX_PCD        128
+
+#define ISCR_VSC_OFF        0x0
+#define ISCR_VSC_VSW        0x4
+#define ISCR_VSC_VBP        0x5
+#define ISCR_VSC_VACTIVE    0x6
+#define ISCR_VSC_VFP        0x7
+
+#define HVTER_VSWE_MASK     GENMASK(25, 24)
+#define HVTER_VSWE(x)       ((x) << 24)
+#define HVTER_HSWE_MASK     GENMASK(17, 16)
+#define HVTER_HSWE(x)       ((x) << 16)
+#define HVTER_VBPE_MASK     GENMASK(13, 12)
+#define HVTER_VBPE(x)       ((x) << 12)
+#define HVTER_VFPE_MASK     GENMASK(9, 8)
+#define HVTER_VFPE(x)       ((x) << 8)
+#define HVTER_HBPE_MASK     GENMASK(5, 4)
+#define HVTER_HBPE(x)       ((x) << 4)
+#define HVTER_HFPE_MASK     GENMASK(1, 0)
+#define HVTER_HFPE(x)       ((x) << 0)
+
+#define HPPLOR_HPOE         BIT(31)
+#define HPPLOR_HPPLO_MASK   GENMASK(11, 0)
+#define HPPLOR_HPPLO(x)     ((x) << 0)
+
+#define GPIOR_UHD_MASK      GENMASK(23, 16)
+#define GPIOR_UHD_SNGL_PORT (0 << 18)
+#define GPIOR_UHD_DUAL_PORT (1 << 18)
+#define GPIOR_UHD_QUAD_PORT (2 << 18)
+#define GPIOR_UHD_ENB       BIT(17)
+
+#define MRR_DEAR_MRR_MASK   GENMASK(31, 3)
+#define MRR_OUTSTND_RQ_MASK GENMASK(2, 0)
+#define MRR_OUTSTND_RQ(x)   ((x >> 1) << 0)
+
+#endif /* __BAIKAL_VDU_REGS_H__ */
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 3795e7020aec..000174421afc 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -135,6 +135,13 @@ config DRM_LVDS_CODEC
 	  Support for transparent LVDS encoders and decoders that don't
 	  require any configuration.
 
+config DRM_BAIKAL_HDMI
+	tristate "Baikal-M HDMI transmitter"
+	default y if ARCH_BAIKAL
+	select DRM_DW_HDMI
+	help
+	  Choose this if you want to use HDMI on Baikal-M.
+
 config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW
 	tristate "MegaChips stdp4028-ge-b850v3-fw and stdp2690-ge-b850v3-fw"
 	depends on OF
-- 
2.32.0



  parent reply	other threads:[~2022-05-20 16:28 UTC|newest]

Thread overview: 45+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-20 16:28 [devel] kernel-image-un-def: Baikal-M asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 01/35] net: stmmac: inital support of Baikal-T1/M SoCs GMAC asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 02/35] dt-bindings: dwmac: Add bindings for Baikal-T1/M SoCs asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 03/35] net: stmmac: custom mdio reset for some Baikal-M boards asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 04/35] net: dwmac-baikal: added compatible strings asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 05/35] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 06/35] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 07/35] hwmon: bt1-pvt: adjusted probing " asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 08/35] hwmon: bt1-pvt: added compatible baikal,pvt asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 09/35] clk: added Baikal-M clock management unit driver asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 10/35] cpufreq-dt: don't load on Baikal-M SoC asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 11/35] usb: dwc3: of-simple: added compatible string for " asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 12/35] arm64: Enable armv8 based Baikal-M SoC support asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 13/35] drm: new bridge driver - stdp4028 asheplyakov
2022-05-20 16:28 ` asheplyakov [this message]
2022-05-20 16:28 ` [devel] [PATCH 15/35] dw-hdmi-ahb-audio: support Baikal-M SoC asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 16/35] Added TF307/TF306 board management controller driver asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 17/35] ALSA: hda: Baikal-M support asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 18/35] [rejected] serial: 8250_dw: verify clock rate in dw8250_set_termios asheplyakov
2022-05-20 16:53   ` Andy Shevchenko
2022-05-20 16:28 ` [devel] [PATCH 19/35] drm/panfrost: forcibly set dma-coherent on Baikal-M asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 20/35] drm/panfrost: disable devfreq " asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 21/35] pm: disable all sleep states on Baikal-M based boards asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 22/35] arm64-stub: fixed secondary cores boot on Baikal-M SoC asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 23/35] efi-rtc: avoid calling efi.get_time " asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 24/35] net: fwnode_get_phy_id: consider all compatible strings asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 25/35] (BROKEN) dwc-i2s: support Baikal-M SoC asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 26/35] input: added TF307 serio PS/2 emulator driver asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 27/35] arm64: added Baikal-M SoC and TF307 board device tree asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 28/35] arm64: device tree: baikal: mark GPU as dma-coherent asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 29/35] arm64: device tree: Baikal-M: fixed PHY binding description asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 30/35] arm64: device tree: Baikal-M: fixed gpio alias asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 31/35] arm64: device tree: Baikal-M: fixed GPU opp_table asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 32/35] arm64: device tree: Baikal-M: fixed CPUs opp_table asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 33/35] arm64: defconfig for Baikal-M support testing asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 34/35] config-aarch64: enable more Baikal-M related drivers asheplyakov
2022-05-20 16:28 ` [devel] [PATCH 35/35] 1:5.17.9-alt2 asheplyakov
2022-05-21  4:04   ` P X
2022-05-21 16:50     ` Alexey Sheplyakov
2022-05-21 16:55       ` [devel] devel-kernel@ Антон Мидюков
2022-05-22  5:51       ` [devel] [PATCH 35/35] 1:5.17.9-alt2 Dmitry V. Levin
2022-05-23  8:51         ` Alexey Sheplyakov
2022-05-23  9:58           ` Dmitry V. Levin
2022-05-23 11:21           ` Alexey Gladkov
2022-05-23  9:47   ` [devel] Baikal-M patches for et101, aqbm1000, tf307 Evgeny Sinelnikov

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=20220520162849.1554351-15-asheplyakov@yandex.ru \
    --to=asheplyakov@yandex.ru \
    --cc=Pavel.Parkhomenko@baikalelectronics.ru \
    --cc=asheplyakov@altlinux.org \
    --cc=asheplyakov@basealt.ru \
    --cc=devel@lists.altlinux.org \
    --cc=nir@basealt.ru \
    --cc=sin@basealt.ru \
    --cc=vt@altlinux.org \
    /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

ALT Linux Team development discussions

This inbox may be cloned and mirrored by anyone:

	git clone --mirror http://lore.altlinux.org/devel/0 devel/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 devel devel/ http://lore.altlinux.org/devel \
		devel@altlinux.org devel@altlinux.ru devel@lists.altlinux.org devel@lists.altlinux.ru devel@linux.iplabs.ru mandrake-russian@linuxteam.iplabs.ru sisyphus@linuxteam.iplabs.ru
	public-inbox-index devel

Example config snippet for mirrors.
Newsgroup available over NNTP:
	nntp://lore.altlinux.org/org.altlinux.lists.devel


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git