[d-kernel] [PATCH 17/39] drm: add Baikal-M SoC video display unit driver

Daniil Gnusarev gnusarevda на basealt.ru
Пн Окт 14 17:01:58 MSK 2024


Add VDU driver for Baikal BE-M1000 with firmware
from SDK version ARM64-2403-6.6

Co-developed-by: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
Bugfixes by Alexey Sheplyakov <asheplyakov на altlinux.org>
Signed-off-by: Daniil Gnusarev <gnusarevda на basealt.ru>
---
 drivers/gpu/drm/Kconfig                       |   2 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/baikal/Kconfig                |  12 +
 drivers/gpu/drm/baikal/Makefile               |  11 +
 drivers/gpu/drm/baikal/baikal-hdmi.c          | 123 +++++
 drivers/gpu/drm/baikal/baikal_vdu_backlight.c | 261 +++++++++
 drivers/gpu/drm/baikal/baikal_vdu_crtc.c      | 440 +++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c   |  95 ++++
 drivers/gpu/drm/baikal/baikal_vdu_drm.h       | 114 ++++
 drivers/gpu/drm/baikal/baikal_vdu_drv.c       | 516 ++++++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_panel.c     | 193 +++++++
 drivers/gpu/drm/baikal/baikal_vdu_plane.c     | 138 +++++
 drivers/gpu/drm/baikal/baikal_vdu_regs.h      | 137 +++++
 drivers/gpu/drm/bridge/Kconfig                |   7 +
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   9 +
 drivers/gpu/drm/drm_panel.c                   |  37 ++
 include/drm/bridge/dw_hdmi.h                  |   2 +
 include/drm/drm_panel.h                       |   3 +
 18 files changed, 2101 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_backlight.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_panel.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 ec4abf9ff47b5..946fb9fac5332 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -261,6 +261,8 @@ source "drivers/gpu/drm/i2c/Kconfig"
 
 source "drivers/gpu/drm/arm/Kconfig"
 
+source "drivers/gpu/drm/baikal/Kconfig"
+
 source "drivers/gpu/drm/radeon/Kconfig"
 
 source "drivers/gpu/drm/amd/amdgpu/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 215e78e791250..f754f002c141d 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -197,4 +197,5 @@ obj-y			+= gud/
 obj-$(CONFIG_DRM_HYPERV) += hyperv/
 obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/
 obj-$(CONFIG_DRM_LOONGSON) += loongson/
diff --git a/drivers/gpu/drm/baikal/Kconfig b/drivers/gpu/drm/baikal/Kconfig
new file mode 100644
index 0000000000000..4a18055cd22fa
--- /dev/null
+++ b/drivers/gpu/drm/baikal/Kconfig
@@ -0,0 +1,12 @@
+config DRM_BAIKAL_VDU
+	tristate "DRM Support for Baikal-M VDU"
+	depends on DRM
+	depends on ARM || ARM64 || COMPILE_TEST
+	depends on COMMON_CLK
+	select DRM_KMS_HELPER
+	select DRM_GEM_DMA_HELPER
+	select DRM_PANEL
+	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 0000000000000..321d3be96b814
--- /dev/null
+++ b/drivers/gpu/drm/baikal/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+baikal_vdu_drm-y +=	baikal_vdu_backlight.o \
+		baikal_vdu_crtc.o \
+		baikal_vdu_drv.o \
+		baikal_vdu_panel.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 0000000000000..7d3d0f0c7358e
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal-hdmi.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal Electronics BE-M1000 DesignWare HDMI 2.0 Tx PHY support driver
+ *
+ * Copyright (C) 2019-2022 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <drm/drm_modes.h>
+
+#include <drm/bridge/dw_hdmi.h>
+
+#include "baikal_vdu_drm.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 baikal_hdmi_bridge *priv;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, priv);
+
+	priv->hdmi = dw_hdmi_probe(pdev, &baikal_dw_hdmi_plat_data);
+	if (IS_ERR(priv->hdmi))
+		return PTR_ERR(priv->hdmi);
+
+	priv->bridge = dw_hdmi_get_bridge(priv->hdmi);
+	return 0;
+}
+
+static int baikal_dw_hdmi_remove(struct platform_device *pdev)
+{
+	struct baikal_hdmi_bridge *priv = platform_get_drvdata(pdev);
+
+	dw_hdmi_remove(priv->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_backlight.c b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c
new file mode 100644
index 0000000000000..f58568a755c9c
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ *
+ */
+
+/**
+ * baikal_vdu_backlight.c
+ * Implementation of backlight functions for
+ * Baikal Electronics BE-M1000 SoC's VDU
+ */
+
+#include <linux/clk.h>
+#include <linux/input.h>
+#include <linux/module.h>
+
+#include <drm/drm_atomic_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define BAIKAL_VDU_MIN_BRIGHTNESS	0
+#define BAIKAL_VDU_DEFAULT_BRIGHTNESS	50
+#define BAIKAL_VDU_BRIGHTNESS_STEP	5
+#define BAIKAL_VDU_DEFAULT_PWM_FREQ	10000
+
+static int baikal_vdu_backlight_update_status(struct backlight_device *bl_dev)
+{
+	struct baikal_vdu_private *priv = bl_get_data(bl_dev);
+	int brightness_on = 1;
+	int brightness = bl_dev->props.brightness;
+	u8 pwmdc;
+
+	if (bl_dev->props.power != FB_BLANK_UNBLANK ||
+	    bl_dev->props.fb_blank != FB_BLANK_UNBLANK ||
+	    bl_dev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) {
+		brightness_on = 0;
+		brightness = priv->min_brightness;
+	}
+
+	if (priv->enable_gpio)
+		gpiod_set_value_cansleep(priv->enable_gpio, brightness_on);
+
+	pwmdc = brightness ? ((brightness << 6) / 25 - 1) : 0;
+
+	writel(pwmdc, priv->regs + PWMDCR);
+
+	return 0;
+}
+
+static const struct backlight_ops baikal_vdu_backlight_ops = {
+	.options        = BL_CORE_SUSPENDRESUME,
+	.update_status	= baikal_vdu_backlight_update_status,
+};
+
+static void baikal_vdu_input_event(struct input_handle *handle,
+				   unsigned int type, unsigned int code,
+				   int value)
+{
+	struct baikal_vdu_private *priv = handle->private;
+	int brightness;
+
+	if (type != EV_KEY || value == 0)
+		return;
+
+	switch (code) {
+	case KEY_BRIGHTNESSDOWN:
+		brightness = priv->bl_dev->props.brightness -
+			     priv->brightness_step;
+		if (brightness >= priv->min_brightness)
+			backlight_device_set_brightness(priv->bl_dev,
+							brightness);
+		break;
+
+	case KEY_BRIGHTNESSUP:
+		brightness = priv->bl_dev->props.brightness +
+			     priv->brightness_step;
+		backlight_device_set_brightness(priv->bl_dev, brightness);
+		break;
+
+	case KEY_BRIGHTNESS_TOGGLE:
+		priv->brightness_on = !priv->brightness_on;
+		if (priv->brightness_on)
+			backlight_enable(priv->bl_dev);
+		else
+			backlight_disable(priv->bl_dev);
+		break;
+
+	default:
+		return;
+	}
+
+	backlight_force_update(priv->bl_dev, BACKLIGHT_UPDATE_HOTKEY);
+}
+
+static int baikal_vdu_input_connect(struct input_handler *handler,
+				    struct input_dev *dev,
+				    const struct input_device_id *id)
+{
+	struct input_handle *handle;
+	int ret;
+
+	handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+	if (!handle)
+		return -ENOMEM;
+
+	handle->private = handler->private;
+	handle->name = KBUILD_MODNAME;
+	handle->dev = dev;
+	handle->handler = handler;
+
+	ret = input_register_handle(handle);
+	if (ret)
+		goto err_free_handle;
+
+	ret = input_open_device(handle);
+	if (ret)
+		goto err_unregister_handle;
+
+	return 0;
+
+err_unregister_handle:
+	input_unregister_handle(handle);
+err_free_handle:
+	kfree(handle);
+	return ret;
+}
+
+static void baikal_vdu_input_disconnect(struct input_handle *handle)
+{
+	input_close_device(handle);
+	input_unregister_handle(handle);
+	kfree(handle);
+}
+
+static const struct input_device_id baikal_vdu_input_ids[] = {
+	{
+		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+		.evbit = { BIT_MASK(EV_KEY) },
+	},
+
+	{ },    /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(input, baikal_vdu_input_ids);
+
+int baikal_vdu_backlight_create(struct drm_device *drm)
+{
+	struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
+	struct baikal_vdu_private *priv = &crossbar->lvds;
+	struct device *dev = drm->dev;
+	struct backlight_properties props;
+	struct input_handler *handler;
+	struct fwnode_handle *node;
+	u32 min_brightness = BAIKAL_VDU_MIN_BRIGHTNESS;
+	u32 dfl_brightness = BAIKAL_VDU_DEFAULT_BRIGHTNESS;
+	u32 brightness_step = BAIKAL_VDU_BRIGHTNESS_STEP;
+	u32 pwm_frequency = 0;
+	int ret = 0;
+	unsigned long rate;
+	unsigned int pwmfr = 0;
+
+	priv->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS);
+	if (IS_ERR(priv->enable_gpio)) {
+		dev_warn(dev, "failed to get ENABLE GPIO\n");
+		priv->enable_gpio = NULL;
+	}
+
+	if (priv->enable_gpio && gpiod_get_direction(priv->enable_gpio) != 0)
+		gpiod_direction_output(priv->enable_gpio, 1);
+
+	node = fwnode_get_named_child_node(dev->fwnode, is_of_node(dev->fwnode) ?
+					   "backlight" : "BCKL");
+	if (!node)
+		return 0;
+
+	fwnode_property_read_u32(node, "min-brightness-level", &min_brightness);
+	fwnode_property_read_u32(node, "default-brightness-level", &dfl_brightness);
+	fwnode_property_read_u32(node, "brightness-level-step", &brightness_step);
+	fwnode_property_read_u32(node, "pwm-frequency", &pwm_frequency);
+
+	if (pwm_frequency == 0) {
+		dev_warn(dev, "using default PWM frequency %u\n",
+			 BAIKAL_VDU_DEFAULT_PWM_FREQ);
+		pwm_frequency = BAIKAL_VDU_DEFAULT_PWM_FREQ;
+	}
+
+	memset(&props, 0, sizeof(props));
+	props.max_brightness = 100;
+	props.type = BACKLIGHT_RAW;
+	props.scale = BACKLIGHT_SCALE_LINEAR;
+
+	if (min_brightness > props.max_brightness) {
+		dev_warn(dev, "invalid min brightness level: %u, using %u\n",
+			 min_brightness, props.max_brightness);
+		min_brightness = props.max_brightness;
+	}
+
+	if (dfl_brightness > props.max_brightness ||
+	    dfl_brightness < min_brightness) {
+		dev_warn(dev,
+			 "invalid default brightness level: %u, using %u\n",
+			 dfl_brightness, props.max_brightness);
+		dfl_brightness = props.max_brightness;
+	}
+
+	priv->min_brightness = min_brightness;
+	priv->brightness_step = brightness_step;
+	priv->brightness_on = true;
+
+	props.brightness = dfl_brightness;
+	props.power = FB_BLANK_UNBLANK;
+
+	priv->bl_dev =
+		devm_backlight_device_register(dev, dev_name(dev), dev, priv,
+					       &baikal_vdu_backlight_ops,
+					       &props);
+	if (IS_ERR(priv->bl_dev)) {
+		dev_err(dev, "failed to register backlight device\n");
+		ret = PTR_ERR(priv->bl_dev);
+		priv->bl_dev = NULL;
+		goto out;
+	}
+
+	handler = devm_kzalloc(dev, sizeof(*handler), GFP_KERNEL);
+	if (!handler) {
+		dev_err(dev, "failed to allocate input handler\n");
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	handler->private = priv;
+	handler->event = baikal_vdu_input_event;
+	handler->connect = baikal_vdu_input_connect;
+	handler->disconnect = baikal_vdu_input_disconnect;
+	handler->name = KBUILD_MODNAME;
+	handler->id_table = baikal_vdu_input_ids;
+
+	ret = input_register_handler(handler);
+	if (ret) {
+		dev_err(dev, "failed to register input handler\n");
+		goto out;
+	}
+
+	/* Hold PWM Clock Domain Reset, disable clocking */
+	writel(0, priv->regs + PWMFR);
+
+	rate = baikal_vdu_crtc_get_rate(priv);
+	pwmfr |= PWMFR_PWMFCD(rate / pwm_frequency - 1) | PWMFR_PWMFCI;
+	writel(pwmfr, priv->regs + PWMFR);
+
+	/* Release PWM Clock Domain Reset, enable clocking */
+	writel(pwmfr | PWMFR_PWMPCR | PWMFR_PWMFCE, priv->regs + PWMFR);
+
+	backlight_update_status(priv->bl_dev);
+out:
+	fwnode_handle_put(node);
+	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 0000000000000..50327813c04b3
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ *
+ */
+
+/**
+ * baikal_vdu_crtc.c
+ * Implementation of the CRTC functions for Baikal Electronics BE-M1000 VDU driver
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/dma-buf.h>
+#include <linux/shmem_fs.h>
+#include <linux/version.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"
+
+irqreturn_t baikal_vdu_irq(int irq, void *data)
+{
+	struct baikal_vdu_private *priv = data;
+	irqreturn_t status = IRQ_NONE;
+	u32 raw_stat;
+	u32 irq_stat;
+
+	priv->counters[0]++;
+	irq_stat = readl(priv->regs + IVR);
+	raw_stat = readl(priv->regs + ISR);
+
+	if (raw_stat & INTR_UFU) {
+		priv->counters[4]++;
+		status = IRQ_HANDLED;
+	}
+
+	if (raw_stat & INTR_IFO) {
+		priv->counters[5]++;
+		status = IRQ_HANDLED;
+	}
+
+	if (raw_stat & INTR_OFU) {
+		priv->counters[6]++;
+		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(raw_stat, priv->regs + ISR);
+
+	return status;
+}
+
+static bool baikal_vdu_crtc_is_clk_enabled(struct baikal_vdu_private *priv)
+{
+	if (acpi_disabled) {
+		return __clk_is_enabled(priv->clk);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj = {
+			.type = ACPI_TYPE_INTEGER,
+			.integer.type = ACPI_TYPE_INTEGER,
+			.integer.value = priv->index
+	        };
+	        struct acpi_object_list args = {
+	                .count = 1,
+	                .pointer = &obj
+	        };
+	        union acpi_object *obj2;
+		struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+		acpi_status status;
+		u64 val;
+
+		status = acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+					      "CISE", &args, &buffer);
+		if (ACPI_FAILURE(status))
+			return 0;
+
+		obj2 = buffer.pointer;
+		if (!obj2 || obj2->type != ACPI_TYPE_INTEGER) {
+			kfree(obj2);
+			return 0;
+		}
+
+		val = obj2->integer.value;
+		kfree(obj2);
+
+		return val;
+#endif
+	}
+}
+
+static void baikal_vdu_crtc_clk_enable(struct baikal_vdu_private *priv)
+{
+	if (acpi_disabled) {
+		clk_prepare_enable(priv->clk);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj = {
+			.type = ACPI_TYPE_INTEGER,
+			.integer.type = ACPI_TYPE_INTEGER,
+			.integer.value = priv->index
+	        };
+	        struct acpi_object_list args = {
+	                .count = 1,
+	                .pointer = &obj
+	        };
+
+		acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+				     "CEN", &args, NULL);
+#endif
+	}
+}
+
+static void baikal_vdu_crtc_clk_disable(struct baikal_vdu_private *priv)
+{
+	if (acpi_disabled) {
+		clk_disable_unprepare(priv->clk);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj = {
+			.type = ACPI_TYPE_INTEGER,
+			.integer.type = ACPI_TYPE_INTEGER,
+			.integer.value = priv->index
+	        };
+	        struct acpi_object_list args = {
+	                .count = 1,
+	                .pointer = &obj
+	        };
+
+		acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+				     "CDIS", &args, NULL);
+#endif
+	}
+}
+
+static int baikal_vdu_crtc_set_rate(struct baikal_vdu_private *priv, u32 rate)
+{
+	if (acpi_disabled) {
+		return clk_set_rate(priv->clk, rate);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj[2] = {
+			{
+				.type = ACPI_TYPE_INTEGER,
+				.integer.type = ACPI_TYPE_INTEGER,
+				.integer.value = priv->index
+			},
+			{
+				.type = ACPI_TYPE_INTEGER,
+				.integer.type = ACPI_TYPE_INTEGER,
+				.integer.value = rate
+			}
+	        };
+	        struct acpi_object_list args = {
+	                .count = 2,
+	                .pointer = obj
+	        };
+
+		acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+				     "CSET", &args, NULL);
+		return 0;
+#endif
+	}
+}
+
+u64 baikal_vdu_crtc_get_rate(struct baikal_vdu_private *priv)
+{
+	if (acpi_disabled) {
+		return clk_get_rate(priv->clk);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj = {
+			.type = ACPI_TYPE_INTEGER,
+			.integer.type = ACPI_TYPE_INTEGER,
+			.integer.value = priv->index
+	        };
+	        struct acpi_object_list args = {
+	                .count = 1,
+	                .pointer = &obj
+	        };
+	        union acpi_object *obj2;
+		struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+		acpi_status status;
+		u64 val;
+
+		status = acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+					      "CGET", &args, &buffer);
+		if (ACPI_FAILURE(status))
+			return 0;
+
+		obj2 = buffer.pointer;
+		if (!obj2 || obj2->type != ACPI_TYPE_INTEGER) {
+			kfree(obj2);
+			return 0;
+		}
+
+		val = obj2->integer.value;
+		kfree(obj2);
+
+		return val;
+#endif
+	}
+}
+
+static void baikal_vdu_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+	const struct drm_display_mode *mode = &crtc->state->mode;
+	unsigned long rate;
+	unsigned int ppl, hsw, hfp, hbp;
+	unsigned int lpp, vsw, vfp, vbp;
+	unsigned int reg;
+	int ret = 0;
+
+	drm_mode_debug_printmodeline(mode);
+
+	rate = mode->clock * 1000;
+
+	if (rate != baikal_vdu_crtc_get_rate(priv)) {
+		DRM_DEV_DEBUG_DRIVER(dev->dev, "Requested pixel clock is %lu Hz\n", rate);
+
+		/* hold clock domain reset; disable clocking */
+		writel(0, priv->regs + PCTR);
+
+		if (baikal_vdu_crtc_is_clk_enabled(priv))
+			baikal_vdu_crtc_clk_disable(priv);
+		ret = baikal_vdu_crtc_set_rate(priv, rate);
+
+		if (ret >= 0) {
+			baikal_vdu_crtc_clk_enable(priv);
+			if (!baikal_vdu_crtc_is_clk_enabled(priv))
+				ret = -1;
+		}
+
+		/* release clock domain reset; enable clocking */
+		reg = readl(priv->regs + PCTR);
+		reg |= PCTR_PCR + PCTR_PCI;
+		writel(reg, priv->regs + PCTR);
+	}
+
+	if (ret < 0)
+		DRM_ERROR("Cannot set desired pixel clock (%lu Hz)\n", rate);
+
+	ppl = mode->hdisplay / 16;
+	if (priv->index == CRTC_LVDS && priv-> num_lanes == 2) {
+		hsw = mode->hsync_end - mode->hsync_start;
+		hfp = mode->hsync_start - mode->hdisplay - 1;
+	} else {
+		hsw = mode->hsync_end - mode->hsync_start - 1;
+		hfp = mode->hsync_start - mode->hdisplay;
+	}
+	hbp = mode->htotal - mode->hsync_end;
+
+	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_HSP;
+	else
+		reg &= ~CR1_HSP;
+	reg &= ~CR1_VSP; // always set VSP to active high
+	reg |= CR1_DEP; // set DE to active high;
+	writel(reg, priv->regs + CR1);
+}
+
+static enum drm_mode_status baikal_vdu_mode_valid(struct drm_crtc *crtc,
+	                const struct drm_display_mode *mode)
+{
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+	if (!priv->mode_override && (mode->hdisplay > 2560 ||
+			mode->vdisplay > 1440))
+		return MODE_BAD;
+	else
+		return MODE_OK;
+}
+
+static void baikal_vdu_crtc_helper_enable(struct drm_crtc *crtc,
+					  struct drm_atomic_state *old_state)
+{
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+	const char *data_mapping = NULL;
+	u32 cntl, gpio;
+
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+	baikal_vdu_crtc_clk_enable(priv);
+
+	/* Set 16-word input FIFO watermark */
+	/* Enable and Power Up */
+	cntl = readl(priv->regs + CR1);
+	cntl &= ~(CR1_FDW_MASK | CR1_OPS_MASK);
+	cntl |= CR1_LCE | CR1_FDW_16_WORDS;
+
+	if (priv->index == CRTC_LVDS) {
+		if (priv->bridge->of_node) {
+			of_property_read_string(priv->bridge->of_node,
+						"data-mapping", &data_mapping);
+		} else {
+			struct fwnode_handle *fwnode;
+
+			fwnode = fwnode_find_reference(priv->bridge->dev->dev->fwnode,
+						       "baikal,lvds-panel", 0);
+			if (!IS_ERR_OR_NULL(fwnode))
+				fwnode_property_read_string(fwnode, "data-mapping",
+							    &data_mapping);
+		}
+
+		if (!data_mapping) {
+			cntl |= CR1_OPS_LCD18;
+		} else if (!strncmp(data_mapping, "vesa-24", 7))
+			cntl |= CR1_OPS_LCD24;
+		else if (!strncmp(data_mapping, "jeida-18", 8))
+			cntl |= CR1_OPS_LCD18;
+		else {
+			dev_warn(crtc->dev->dev, "%s data mapping is not supported, vesa-24 is set\n", data_mapping);
+			cntl |= CR1_OPS_LCD24;
+		}
+		gpio = GPIOR_UHD_ENB;
+		if (priv->num_lanes == 4)
+			gpio |= GPIOR_UHD_QUAD_PORT;
+		else if (priv->num_lanes == 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);
+
+	writel(0x3ffff, priv->regs + ISR);
+	writel(INTR_FER, priv->regs + IMR);
+}
+
+void baikal_vdu_crtc_helper_disable(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+
+	writel(0x3ffff, priv->regs + ISR);
+	writel(0, priv->regs + IMR);
+
+	/* Disable clock */
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "disabling pixel clock\n");
+	baikal_vdu_crtc_clk_disable(priv);
+}
+
+static void baikal_vdu_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+					   struct drm_atomic_state *old_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);
+	}
+}
+
+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,
+};
+
+const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+	.mode_set_nofb = baikal_vdu_crtc_helper_mode_set_nofb,
+	.mode_valid = baikal_vdu_mode_valid,
+	.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 baikal_vdu_private *priv)
+{
+	struct drm_device *dev = priv->drm;
+	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);
+
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+	baikal_vdu_crtc_clk_enable(priv);
+
+	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 0000000000000..1fda8c2173117
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ *
+ */
+
+#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(PWMFR),
+	REGDEF(PWMDCR),
+	REGDEF(HVTER),
+	REGDEF(HPPLOR),
+	REGDEF(GPIOR),
+	REGDEF(MRR),
+};
+
+#define REGS_HDMI	"regs_hdmi"
+#define REGS_LVDS	"regs_lvds"
+
+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_crossbar *crossbar = drm_to_baikal_vdu_crossbar(dev);
+	char *filename = m->file->f_path.dentry->d_iname;
+	struct baikal_vdu_private *priv = NULL;
+	int i;
+
+	if (!strcmp(REGS_HDMI, filename))
+		priv = &crossbar->hdmi;
+	if (!strcmp(REGS_LVDS, filename))
+		priv = &crossbar->lvds;
+
+	if (!priv || !priv->regs)
+		return 0;
+
+	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_hdmi_debugfs_list[] = {
+	{REGS_HDMI, baikal_vdu_debugfs_regs, 0},
+};
+
+static const struct drm_info_list baikal_vdu_lvds_debugfs_list[] = {
+	{REGS_LVDS, baikal_vdu_debugfs_regs, 0},
+};
+
+void baikal_vdu_hdmi_debugfs_init(struct drm_minor *minor)
+{
+	drm_debugfs_create_files(baikal_vdu_hdmi_debugfs_list,
+				ARRAY_SIZE(baikal_vdu_hdmi_debugfs_list),
+				minor->debugfs_root, minor);
+}
+
+void baikal_vdu_lvds_debugfs_init(struct drm_minor *minor)
+{
+	drm_debugfs_create_files(baikal_vdu_lvds_debugfs_list,
+				ARRAY_SIZE(baikal_vdu_lvds_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 0000000000000..432270bebed69
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ *
+ */
+
+#ifndef __BAIKAL_VDU_DRM_H__
+#define __BAIKAL_VDU_DRM_H__
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include <drm/bridge/dw_hdmi.h>
+
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/backlight.h>
+
+#define CRTC_HDMI	0
+#define CRTC_LVDS	1
+
+#define crtc_to_baikal_vdu(x) \
+	container_of(x, struct baikal_vdu_private, crtc)
+#define bridge_to_baikal_lvds_bridge(x) \
+	container_of(x, struct baikal_lvds_bridge, bridge)
+#define connector_to_baikal_lvds_bridge(x) \
+	container_of(x, struct baikal_lvds_bridge, connector)
+#define drm_to_baikal_vdu_crossbar(x) \
+	container_of(x, struct baikal_vdu_crossbar, drm)
+
+#define VDU_NAME_LEN 5
+
+struct baikal_vdu_private {
+	struct drm_device *drm;
+	struct drm_crtc crtc;
+	struct drm_encoder encoder;
+	struct drm_bridge *bridge;
+	struct drm_plane primary;
+	void *regs;
+	int irq;
+	struct clk *clk;
+	spinlock_t lock;
+	u32 counters[20];
+	int mode_override;
+	int index;
+	char name[VDU_NAME_LEN];
+	char irq_name[VDU_NAME_LEN + 4];
+	char pclk_name[VDU_NAME_LEN + 5];
+	char regs_name[VDU_NAME_LEN + 5];
+	int num_lanes;
+	int data_mapping;
+	int off;
+	int ready;
+
+	/* backlight */
+	struct gpio_desc *enable_gpio;
+	struct backlight_device *bl_dev;
+	int min_brightness;
+	int brightness_step;
+	bool brightness_on;
+};
+
+struct baikal_vdu_crossbar {
+	struct drm_device drm;
+	struct baikal_vdu_private hdmi;
+	struct baikal_vdu_private lvds;
+};
+
+struct baikal_lvds_bridge {
+	struct baikal_vdu_private *vdu;
+	struct drm_bridge bridge;
+	struct drm_connector connector;
+	struct drm_panel *panel;
+	u32 connector_type;
+};
+
+struct baikal_hdmi_bridge {
+	struct dw_hdmi *hdmi;
+	struct drm_bridge *bridge;
+};
+
+/* Generic functions */
+inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv);
+
+inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv);
+
+/* Bridge functions */
+bool bridge_is_baikal_lvds_bridge(const struct drm_bridge *bridge);
+
+struct drm_bridge *devm_baikal_lvds_bridge_add(struct device *dev,
+					     struct drm_panel *panel,
+					     u32 connector_type);
+
+/* CRTC Functions */
+u64 baikal_vdu_crtc_get_rate(struct baikal_vdu_private *priv);
+
+int baikal_vdu_crtc_create(struct baikal_vdu_private *priv);
+
+irqreturn_t baikal_vdu_irq(int irq, void *data);
+
+int baikal_vdu_primary_plane_init(struct baikal_vdu_private *priv);
+
+/* Backlight Functions */
+int baikal_vdu_backlight_create(struct drm_device *drm);
+
+/* Debugfs functions */
+void baikal_vdu_hdmi_debugfs_init(struct drm_minor *minor);
+
+void baikal_vdu_lvds_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 0000000000000..c9d5cea635628
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -0,0 +1,516 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ * Bugfixes by Alexey Sheplyakov <asheplyakov на altlinux.org>
+ *
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_aperture.h>
+#include <drm/drm_atomic_helper.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_dma_helper.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_ioctl.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 DRIVER_NAME		"baikal-vdu"
+#define DRIVER_DESC		"Baikal VDU DRM driver"
+#define DRIVER_DATE		"20230221"
+
+#define BAIKAL_SMC_LOG_DISABLE	0xC2000200
+#define IFIFO_SIZE	16384
+#define UHD_FIFO_SIZE	16384
+
+int mode_override = 0;
+int hdmi_off = 0;
+int lvds_off = 0;
+
+static struct drm_driver vdu_drm_driver;
+
+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,
+};
+
+const struct drm_encoder_funcs baikal_vdu_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static struct drm_bridge *devm_baikal_get_bridge(struct device *dev,
+						 u32 port, u32 endpoint)
+{
+	struct baikal_hdmi_bridge *priv_hdmi;
+	struct device *tmp;
+	struct drm_bridge *bridge;
+	struct drm_panel *panel;
+	struct fwnode_handle *fwnode;
+	int ret = 0;
+
+	if (is_of_node(dev->fwnode)) {
+		ret = drm_of_find_panel_or_bridge(to_of_node(dev->fwnode), port,
+						  endpoint, &panel, &bridge);
+	} else {
+		if (port == CRTC_HDMI) {
+			fwnode = fwnode_find_reference(dev->fwnode,
+						       "baikal,hdmi-bridge", 0);
+			if (IS_ERR_OR_NULL(fwnode))
+				return ERR_PTR(-ENODEV);
+
+			tmp = bus_find_device_by_fwnode(&platform_bus_type, fwnode);
+			if (IS_ERR_OR_NULL(tmp))
+				return ERR_PTR(-ENODEV);
+
+			priv_hdmi = dev_get_drvdata(tmp);
+			if (!priv_hdmi)
+				return ERR_PTR(-EPROBE_DEFER);
+
+			bridge = priv_hdmi->bridge;
+			panel = NULL;
+		} else if (port == CRTC_LVDS) {
+			fwnode = fwnode_find_reference(dev->fwnode,
+						       "baikal,lvds-panel", 0);
+			if (IS_ERR_OR_NULL(fwnode))
+				return ERR_PTR(-ENODEV);
+
+			panel = fwnode_drm_find_panel(fwnode);
+			if (IS_ERR(panel))
+				return ERR_CAST(panel);
+		} else {
+			return ERR_PTR(-ENODEV);
+		}
+	}
+
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (panel) {
+		bridge = devm_baikal_lvds_bridge_add(dev, panel, DRM_MODE_CONNECTOR_LVDS);
+	}
+
+	return bridge;
+}
+
+static int baikal_vdu_remove_efifb(struct drm_device *dev)
+{
+	int err;
+	err = drm_aperture_remove_framebuffers(&vdu_drm_driver);
+	if (err)
+		dev_warn(dev->dev, "failed to remove firmware framebuffer\n");
+	return err;
+}
+
+inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv)
+{
+	u32 cntl = readl(priv->regs + CR1);
+	cntl &= ~CR1_LCE;
+	writel(cntl, priv->regs + CR1);
+}
+
+inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv)
+{
+	u32 cntl = readl(priv->regs + CR1);
+	cntl |= CR1_LCE;
+	writel(cntl, priv->regs + CR1);
+}
+
+static int baikal_vdu_modeset_init(struct baikal_vdu_private *priv)
+{
+	struct drm_device *dev = priv->drm;
+	struct drm_encoder *encoder;
+	int ret = 0;
+	int index;
+
+	if (priv == NULL)
+		return -EINVAL;
+	index = priv->index;
+
+	ret = baikal_vdu_primary_plane_init(priv);
+	if (ret != 0) {
+		dev_err(dev->dev, "%s: failed to init primary plane\n", priv->name);
+		return ret;
+	}
+
+	ret = baikal_vdu_crtc_create(priv);
+	if (ret) {
+		dev_err(dev->dev, "%s: failed to create CRTC\n", priv->name);
+		return ret;
+	}
+
+	if (priv->bridge) {
+		encoder = &priv->encoder;
+		ret = drm_encoder_init(dev, encoder, &baikal_vdu_encoder_funcs,
+				       DRM_MODE_ENCODER_NONE, NULL);
+		if (ret) {
+			dev_err(dev->dev, "%s: failed to create DRM encoder\n", priv->name);
+			return ret;
+		}
+		encoder->crtc = &priv->crtc;
+		encoder->possible_crtcs = BIT(drm_crtc_index(encoder->crtc));
+		priv->bridge->encoder = encoder;
+		ret = drm_bridge_attach(encoder, priv->bridge, NULL, 0);
+		if (ret) {
+			dev_err(dev->dev, "%s: failed to attach DRM bridge (%d)\n", priv->name, ret);
+			return ret;
+		}
+	} else {
+		dev_err(dev->dev, "%s: no bridge or panel attached\n", priv->name);
+		return -ENODEV;
+	}
+
+	priv->mode_override = mode_override;
+
+	return ret;
+}
+
+static int baikal_dumb_create(struct drm_file *file, struct drm_device *dev,
+			     struct drm_mode_create_dumb *args)
+{
+	args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+	args->size = args->pitch * args->height + IFIFO_SIZE + UHD_FIFO_SIZE;
+
+	return drm_gem_dma_dumb_create_internal(file, dev, args);
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(baikal_drm_fops);
+
+static struct drm_driver vdu_drm_driver = {
+	.driver_features = DRIVER_GEM |	DRIVER_MODESET | DRIVER_ATOMIC,
+	DRM_GEM_DMA_DRIVER_OPS,
+	.ioctls = NULL,
+	.fops = &baikal_drm_fops,
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = 2,
+	.minor = 0,
+	.patchlevel = 0,
+	.dumb_create = baikal_dumb_create,
+	.dumb_map_offset = drm_gem_dumb_map_offset,
+	.gem_prime_import = drm_gem_prime_import,
+	.gem_prime_import_sg_table = drm_gem_dma_prime_import_sg_table,
+};
+
+static int baikal_vdu_allocate_resources(struct platform_device *pdev,
+		struct baikal_vdu_private *priv)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *mem;
+	int ret;
+
+	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, priv->regs_name);
+	if (!mem)
+		mem = platform_get_resource(pdev, IORESOURCE_MEM, priv->index);
+
+	if (!mem) {
+		dev_err(dev, "%s %s: no MMIO resource specified\n", __func__, priv->name);
+		return -EINVAL;
+	}
+
+	priv->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(priv->regs)) {
+		dev_err(dev, "%s %s: MMIO allocation failed\n", __func__, priv->name);
+		return PTR_ERR(priv->regs);
+	}
+
+	if (priv->off) {
+		baikal_vdu_switch_off(priv);
+		return -EPERM;
+	} else {
+		ret = baikal_vdu_modeset_init(priv);
+		if (ret) {
+			dev_err(dev, "%s %s: failed to init modeset\n", __func__, priv->name);
+			if (ret == -ENODEV) {
+				baikal_vdu_switch_off(priv);
+			}
+			return ret;
+		} else {
+			writel(MRR_MAX_VALUE, priv->regs + MRR);
+			spin_lock_init(&priv->lock);
+			return 0;
+		}
+	}
+}
+
+static int baikal_vdu_allocate_irq(struct platform_device *pdev,
+	       struct baikal_vdu_private *priv)
+{
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	priv->irq = fwnode_irq_get_byname(dev->fwnode, priv->irq_name);
+	if (priv->irq < 0) {
+		dev_err(dev, "%s %s: no IRQ resource specified\n", __func__, priv->name);
+		return -EINVAL;
+	}
+
+	/* turn off interrupts before requesting the irq */
+	writel(0, priv->regs + IMR);
+	ret = request_irq(priv->irq, baikal_vdu_irq, IRQF_SHARED, dev->driver->name, priv);
+	if (ret != 0)
+		dev_err(dev, "%s %s: IRQ %d allocation failed\n", __func__, priv->name, priv->irq);
+	return ret;
+}
+
+static void baikal_vdu_free_irq(struct baikal_vdu_private *priv)
+{
+	writel(0, priv->regs + IMR);
+	writel(0x3ffff, priv->regs + ISR);
+	free_irq(priv->irq, priv->drm->dev);
+}
+
+static int baikal_vdu_allocate_clk(struct baikal_vdu_private *priv)
+{
+	if (is_of_node(priv->drm->dev->fwnode)) {
+		priv->clk = clk_get(priv->drm->dev, priv->pclk_name);
+		if (IS_ERR(priv->clk)) {
+			dev_err(priv->drm->dev, "%s: unable to get %s, err %ld\n", priv->name, priv->pclk_name, PTR_ERR(priv->clk));
+			return PTR_ERR(priv->clk);
+		}
+	}
+
+	return 0;
+}
+
+static void baikal_vdu_set_name(struct baikal_vdu_private *priv, int index, const char *name)
+{
+	char *c;
+	int len = sizeof(priv->name) / sizeof(priv->name[0]) - 1;
+	strncpy(priv->name, name, len);
+	for (c = priv->name; c < priv->name + len && *c; c++) {
+		*c = toupper(*c);
+	}
+	sprintf(priv->irq_name, "%s_irq", name);
+	sprintf(priv->pclk_name, "%s_pclk", name);
+	sprintf(priv->regs_name, "%s_regs", name);
+	priv->index = index;
+}
+
+static int baikal_vdu_bridge_init(struct baikal_vdu_private *priv, struct drm_device *drm) {
+	int ret = 0;
+	struct device *dev;
+	struct drm_bridge *bridge;
+	if (!priv || !drm)
+		return -ENODEV;
+	priv->drm = drm;
+	dev = drm->dev;
+	bridge = devm_baikal_get_bridge(dev, priv->index, 0);
+	if (IS_ERR(bridge)) {
+		ret = PTR_ERR(bridge);
+		if (ret == -EPROBE_DEFER) {
+			dev_info(dev, "%s: bridge probe deferred\n", priv->name);
+		}
+		priv->bridge = NULL;
+	} else {
+		priv->bridge = bridge;
+	}
+	return ret;
+}
+
+static int baikal_vdu_resources_init(struct platform_device *pdev, struct baikal_vdu_private *priv)
+{
+	int ret = baikal_vdu_allocate_resources(pdev, priv);
+	if (ret)
+		return 0;
+	ret = baikal_vdu_allocate_irq(pdev, priv);
+	if (ret)
+		return 0;
+	ret = baikal_vdu_allocate_clk(priv);
+	if (ret) {
+		baikal_vdu_free_irq(priv);
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+static int baikal_vdu_drm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct baikal_lvds_bridge *panel_bridge;
+	struct baikal_vdu_crossbar *crossbar;
+	struct baikal_vdu_private *hdmi;
+	struct baikal_vdu_private *lvds;
+	struct drm_device *drm;
+	struct drm_mode_config *mode_config;
+	struct arm_smccc_res res;
+	int ret;
+
+	crossbar = devm_drm_dev_alloc(dev, &vdu_drm_driver,
+                  struct baikal_vdu_crossbar, drm);
+	if (IS_ERR(crossbar))
+		return PTR_ERR(crossbar);
+
+	drm = &crossbar->drm;
+	platform_set_drvdata(pdev, drm);
+	hdmi = &crossbar->hdmi;
+	baikal_vdu_set_name(hdmi, CRTC_HDMI, "hdmi");
+	lvds = &crossbar->lvds;
+	baikal_vdu_set_name(lvds, CRTC_LVDS, "lvds");
+
+	ret = baikal_vdu_bridge_init(hdmi, drm);
+	if (ret == -EPROBE_DEFER) {
+		goto out_drm;
+	}
+
+	ret = device_property_read_u32(&pdev->dev, "lvds-lanes",
+				       &lvds->num_lanes);
+	if (ret)
+		lvds->num_lanes = 0;
+	if (lvds->num_lanes) {
+		ret = baikal_vdu_bridge_init(lvds, drm);
+		if (ret == -EPROBE_DEFER) {
+			goto out_drm;
+		}
+	}
+
+	drm_mode_config_init(drm);
+	mode_config = &drm->mode_config;
+	mode_config->funcs = &mode_config_funcs;
+	mode_config->min_width = 1;
+	mode_config->max_width = 8192;
+	mode_config->min_height = 1;
+	mode_config->max_height = 8192;
+
+	hdmi->off = hdmi_off;
+	hdmi->ready = baikal_vdu_resources_init(pdev, hdmi);
+	if (lvds->num_lanes) {
+		lvds->off = lvds_off;
+		lvds->ready = baikal_vdu_resources_init(pdev, lvds);
+	} else {
+		lvds->ready = 0;
+		lvds->bridge = NULL;
+		dev_info(dev, "No 'lvds-lanes' property found\n");
+	}
+	if (lvds->ready) {
+		ret = baikal_vdu_backlight_create(drm);
+		if (ret) {
+			dev_err(dev, "LVDS: failed to create backlight\n");
+		}
+		if (bridge_is_baikal_lvds_bridge(lvds->bridge)) {
+			panel_bridge = bridge_to_baikal_lvds_bridge(lvds->bridge);
+			panel_bridge->vdu = lvds;
+		} else {
+		// TODO implement handling of third-party bridges
+		}
+	}
+	if (hdmi->bridge) {
+		// TODO implement functions specific to HDMI bridge
+	}
+
+	hdmi->ready = hdmi->ready & !hdmi->off;
+	lvds->ready = lvds->ready & !lvds->off;
+	dev_info(dev, "%s output %s\n", hdmi->name, hdmi->ready ? "enabled" : "disabled");
+	dev_info(dev, "%s output %s\n", lvds->name, lvds->ready ? "enabled" : "disabled");
+	baikal_vdu_remove_efifb(drm);
+
+	if (hdmi->ready || lvds->ready) {
+		/* Disable SCP debug output as it may affect VDU performance */
+		arm_smccc_smc(BAIKAL_SMC_LOG_DISABLE, 0, 0, 0, 0, 0, 0, 0, &res);
+
+		drm_mode_config_reset(drm);
+		drm_kms_helper_poll_init(drm);
+		ret = drm_dev_register(drm, 0);
+		if (ret) {
+			dev_err(dev, "failed to register DRM device\n");
+			goto out_config;
+		}
+		drm_fbdev_dma_setup(drm, 32);
+#if defined(CONFIG_DEBUG_FS)
+		if (hdmi->ready)
+			baikal_vdu_hdmi_debugfs_init(drm->primary);
+		if (lvds->ready)
+			baikal_vdu_lvds_debugfs_init(drm->primary);
+#endif
+		return 0;
+	} else {
+		dev_err(dev, "no active outputs configured\n");
+		ret = -ENODEV;
+	}
+out_config:
+	drm_mode_config_cleanup(drm);
+out_drm:
+	dev_err(dev, "failed to probe: %d\n", ret);
+	return ret;
+}
+
+static void baikal_vdu_drm_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+	struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
+
+	drm_dev_unregister(drm);
+	drm_mode_config_cleanup(drm);
+	if (crossbar->hdmi.irq)
+		free_irq(crossbar->hdmi.irq, drm->dev);
+	if (crossbar->lvds.irq)
+		free_irq(crossbar->lvds.irq, drm->dev);
+}
+
+static const struct of_device_id baikal_vdu_of_match[] = {
+	{ .compatible = "baikal,vdu" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, baikal_vdu_of_match);
+
+static int baikal_vdu_pm_suspend(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	return drm_mode_config_helper_suspend(drm);
+}
+
+static int baikal_vdu_pm_resume(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	return drm_mode_config_helper_resume(drm);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(baikal_vdu_pm_ops, baikal_vdu_pm_suspend, baikal_vdu_pm_resume);
+
+static struct platform_driver baikal_vdu_platform_driver = {
+	.probe      = baikal_vdu_drm_probe,
+	.remove_new = baikal_vdu_drm_remove,
+	.driver     = {
+		.name   = DRIVER_NAME,
+		.of_match_table = baikal_vdu_of_match,
+		.pm = pm_sleep_ptr(&baikal_vdu_pm_ops)
+	},
+};
+
+module_param(mode_override, int, 0644);
+module_param(hdmi_off, int, 0644);
+module_param(lvds_off, 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_panel.c b/drivers/gpu/drm/baikal/baikal_vdu_panel.c
new file mode 100644
index 0000000000000..9983f36b94f1e
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_panel.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ *
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+static void baikal_lvds_connector_force(struct drm_connector *connector)
+{
+	struct baikal_lvds_bridge *bridge = connector_to_baikal_lvds_bridge(connector);
+	struct baikal_vdu_private *priv = bridge->vdu;
+	u32 cntl = readl(priv->regs + CR1);
+	if (connector->force == DRM_FORCE_OFF)
+		cntl &= ~CR1_LCE;
+	else
+		cntl |= CR1_LCE;
+	writel(cntl, priv->regs + CR1);
+}
+
+static int baikal_lvds_connector_get_modes(struct drm_connector *connector)
+{
+	struct baikal_lvds_bridge *panel_bridge =
+		connector_to_baikal_lvds_bridge(connector);
+
+	return drm_panel_get_modes(panel_bridge->panel, connector);
+}
+
+static const struct drm_connector_helper_funcs
+baikal_lvds_bridge_connector_helper_funcs = {
+	.get_modes = baikal_lvds_connector_get_modes,
+};
+
+static const struct drm_connector_funcs baikal_lvds_bridge_connector_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+	.force = baikal_lvds_connector_force,
+};
+
+static int baikal_lvds_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+	struct drm_connector *connector = &panel_bridge->connector;
+	int ret;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Missing encoder\n");
+		return -ENODEV;
+	}
+
+	drm_connector_helper_add(connector,
+				 &baikal_lvds_bridge_connector_helper_funcs);
+
+	ret = drm_connector_init(bridge->dev, connector,
+				 &baikal_lvds_bridge_connector_funcs,
+				 panel_bridge->connector_type);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector\n");
+		return ret;
+	}
+
+	ret = drm_connector_attach_encoder(&panel_bridge->connector,
+					  bridge->encoder);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void baikal_lvds_bridge_pre_enable(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	baikal_vdu_switch_on(panel_bridge->vdu);
+	drm_panel_prepare(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_enable(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	drm_panel_enable(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_disable(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	drm_panel_disable(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_post_disable(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	drm_panel_unprepare(panel_bridge->panel);
+	baikal_vdu_switch_off(panel_bridge->vdu);
+}
+
+static const struct drm_bridge_funcs baikal_lvds_bridge_funcs = {
+	.attach = baikal_lvds_bridge_attach,
+	.pre_enable = baikal_lvds_bridge_pre_enable,
+	.enable = baikal_lvds_bridge_enable,
+	.disable = baikal_lvds_bridge_disable,
+	.post_disable = baikal_lvds_bridge_post_disable,
+};
+
+static struct drm_bridge *baikal_lvds_bridge_add(struct drm_panel *panel,
+					u32 connector_type)
+{
+	struct baikal_lvds_bridge *panel_bridge;
+
+	if (!panel)
+		return ERR_PTR(-EINVAL);
+
+	panel_bridge = devm_kzalloc(panel->dev, sizeof(*panel_bridge),
+				    GFP_KERNEL);
+	if (!panel_bridge)
+		return ERR_PTR(-ENOMEM);
+
+	panel_bridge->connector_type = connector_type;
+	panel_bridge->panel = panel;
+
+	panel_bridge->bridge.funcs = &baikal_lvds_bridge_funcs;
+#ifdef CONFIG_OF
+	panel_bridge->bridge.of_node = panel->dev->of_node;
+#endif
+
+	drm_bridge_add(&panel_bridge->bridge);
+
+	return &panel_bridge->bridge;
+}
+
+static void baikal_lvds_bridge_remove(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge;
+
+	if (!bridge)
+		return;
+
+	if (bridge->funcs != &baikal_lvds_bridge_funcs)
+		return;
+
+	panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	drm_bridge_remove(bridge);
+	devm_kfree(panel_bridge->panel->dev, bridge);
+}
+
+static void devm_baikal_lvds_bridge_release(struct device *dev, void *res)
+{
+	struct drm_bridge **bridge = res;
+
+	baikal_lvds_bridge_remove(*bridge);
+}
+
+struct drm_bridge *devm_baikal_lvds_bridge_add(struct device *dev,
+					     struct drm_panel *panel,
+					     u32 connector_type)
+{
+	struct drm_bridge **ptr, *bridge;
+
+	ptr = devres_alloc(devm_baikal_lvds_bridge_release, sizeof(*ptr),
+			   GFP_KERNEL);
+	if (!ptr)
+		return ERR_PTR(-ENOMEM);
+
+	bridge = baikal_lvds_bridge_add(panel, connector_type);
+	if (!IS_ERR(bridge)) {
+		*ptr = bridge;
+		devres_add(dev, ptr);
+	} else {
+		devres_free(ptr);
+	}
+
+	return bridge;
+}
+
+bool bridge_is_baikal_lvds_bridge(const struct drm_bridge *bridge)
+{
+	return bridge && (bridge->funcs == &baikal_lvds_bridge_funcs);
+}
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 0000000000000..d9bdb95a8d962
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ *
+ */
+
+#include <linux/clk.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_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+static void baikal_vdu_primary_plane_atomic_update(struct drm_plane *plane,
+					      struct drm_atomic_state *old_state)
+{
+	struct baikal_vdu_private *priv;
+	struct drm_plane_state *state = plane->state;
+	struct drm_crtc *crtc = state->crtc;
+	struct drm_framebuffer *fb = state->fb;
+	uint32_t cntl;
+	uint32_t addr;
+	unsigned long flags;
+
+	if (!fb)
+		return;
+
+	priv = crtc_to_baikal_vdu(crtc);
+	addr = drm_fb_dma_get_gem_addr(fb, state, 0);
+
+	spin_lock_irqsave(&priv->lock, flags);
+	writel(addr, priv->regs + DBAR);
+	spin_unlock_irqrestore(&priv->lock, flags);
+	priv->counters[16]++;
+
+	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(cntl, priv->regs + CR1);
+}
+
+static const struct drm_plane_helper_funcs baikal_vdu_primary_plane_helper_funcs = {
+	.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 baikal_vdu_private *priv)
+{
+	struct drm_device *drm = priv->drm;
+	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 0000000000000..d48ea8d12051f
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko на baikalelectronics.ru>
+ *
+ */
+
+#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 PWMFR       0x034
+#define PWMDCR      0x038
+#define HVTER       0x044
+#define HPPLOR      0x048
+#define GPIOR       0x1F8
+#define MRR         0xFFC
+
+#define INTR_UFU    BIT(16)
+#define INTR_BAU    BIT(7)
+#define INTR_VCT    BIT(6)
+#define INTR_MBE    BIT(5)
+#define INTR_FER    BIT(4)
+#define INTR_IFO    BIT(3)
+#define INTR_OFU    BIT(0)
+
+#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_MASK        GENMASK(14, 12)
+#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 PWMFR_PWMPCR        BIT(24)
+#define PWMFR_PWMFCI        BIT(23)
+#define PWMFR_PWMFCE        BIT(22)
+#define PWMFR_PWMFCD_MASK   GENMASK(21, 0)
+#define PWMFR_PWMFCD(x)     ((x) << 0)
+
+#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)
+#define MRR_MAX_VALUE       ((0xffffffff & MRR_DEAR_MRR_MASK) | MRR_OUTSTND_RQ(4))
+
+#endif /* __BAIKAL_VDU_REGS_H__ */
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 3e6a4e2044c0e..6b51a41ee2c6f 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -162,6 +162,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
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 6c1d794745054..0a7d15e3ba80e 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3691,6 +3691,15 @@ void dw_hdmi_resume(struct dw_hdmi *hdmi)
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_resume);
 
+struct drm_bridge *dw_hdmi_get_bridge(struct dw_hdmi *hdmi)
+{
+	if (IS_ERR_OR_NULL(hdmi))
+		return NULL;
+
+	return &hdmi->bridge;
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_get_bridge);
+
 MODULE_AUTHOR("Sascha Hauer <s.hauer на pengutronix.de>");
 MODULE_AUTHOR("Andy Yan <andy.yan на rock-chips.com>");
 MODULE_AUTHOR("Yakir Yang <ykk на rock-chips.com>");
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index cfbe020de54e0..4ae9218d5da41 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -295,6 +295,43 @@ int drm_panel_get_modes(struct drm_panel *panel,
 }
 EXPORT_SYMBOL(drm_panel_get_modes);
 
+/**
+ * fwnode_drm_find_panel - look up a panel using a fwnode
+ * @fwnode: fwnode of the panel
+ *
+ * Searches the set of registered panels for one that matches the given fwnode.
+ * If a matching panel is found, return a pointer to it.
+ *
+ * Return: A pointer to the panel registered for the specified fwnode or
+ * an ERR_PTR() if no panel matching the fwnode can be found.
+ *
+ * Possible error codes returned by this function:
+ *
+ * - EPROBE_DEFER: the panel device has not been probed yet, and the caller
+ *   should retry later
+ * - ENODEV: the device is not available
+ */
+struct drm_panel *fwnode_drm_find_panel(const struct fwnode_handle *fwnode)
+{
+	struct drm_panel *panel;
+
+	if (!fwnode_device_is_available(fwnode))
+		return ERR_PTR(-ENODEV);
+
+	mutex_lock(&panel_lock);
+
+	list_for_each_entry(panel, &panel_list, list) {
+		if (panel->dev->fwnode == fwnode) {
+			mutex_unlock(&panel_lock);
+			return panel;
+		}
+	}
+
+	mutex_unlock(&panel_lock);
+	return ERR_PTR(-EPROBE_DEFER);
+}
+EXPORT_SYMBOL(fwnode_drm_find_panel);
+
 #ifdef CONFIG_OF
 /**
  * of_drm_find_panel - look up a panel using a device tree node
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index 6a46baa0737cd..08aafb5171aae 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -174,6 +174,8 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
 
 void dw_hdmi_resume(struct dw_hdmi *hdmi);
 
+struct drm_bridge *dw_hdmi_get_bridge(struct dw_hdmi *hdmi);
+
 void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense);
 
 int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index 10015891b056f..196cd8176ac44 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -28,6 +28,7 @@
 #include <linux/errno.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
+#include <linux/fwnode.h>
 
 struct backlight_device;
 struct dentry;
@@ -284,6 +285,8 @@ int drm_panel_disable(struct drm_panel *panel);
 
 int drm_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector);
 
+struct drm_panel *fwnode_drm_find_panel(const struct fwnode_handle *fwnode);
+
 #if defined(CONFIG_OF) && defined(CONFIG_DRM_PANEL)
 struct drm_panel *of_drm_find_panel(const struct device_node *np);
 int of_drm_get_panel_orientation(const struct device_node *np,
-- 
2.42.2



Подробная информация о списке рассылки devel-kernel