From: Daniil Gnusarev <gnusarevda@basealt.ru>
To: gnusarevda@basealt.ru, devel-kernel@lists.altlinux.org
Subject: [d-kernel] [PATCH 14/35] drm: add Baikal-M SoC video display unit driver
Date: Fri, 27 Feb 2026 14:32:15 +0400
Message-ID: <20260227103236.785736-15-gnusarevda@basealt.ru> (raw)
In-Reply-To: <20260227103236.785736-1-gnusarevda@basealt.ru>
Add VDU driver for Baikal BE-M1000
Taken from SDK ARM64-2509-6.12. Updated for version 6.18
Co-developed-by: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
Bugfixes by Alexey Sheplyakov <asheplyakov@altlinux.org>
Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
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 | 122 +++++
drivers/gpu/drm/baikal/baikal_vdu_backlight.c | 259 +++++++++
drivers/gpu/drm/baikal/baikal_vdu_crtc.c | 443 +++++++++++++++
drivers/gpu/drm/baikal/baikal_vdu_debugfs.c | 94 ++++
drivers/gpu/drm/baikal/baikal_vdu_drm.h | 114 ++++
drivers/gpu/drm/baikal/baikal_vdu_drv.c | 517 ++++++++++++++++++
drivers/gpu/drm/baikal/baikal_vdu_panel.c | 195 +++++++
drivers/gpu/drm/baikal/baikal_vdu_plane.c | 118 ++++
drivers/gpu/drm/baikal/baikal_vdu_regs.h | 136 +++++
drivers/gpu/drm/bridge/synopsys/Kconfig | 6 +
drivers/gpu/drm/drm_panel.c | 3 +-
include/drm/drm_panel.h | 6 +
16 files changed, 2038 insertions(+), 1 deletion(-)
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 ed85d0ceee3ba5..d375f33e329a89 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -269,6 +269,8 @@ source "drivers/gpu/drm/sysfb/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 742f0d590c5afa..e8722bba5a6bd3 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -232,6 +232,7 @@ obj-$(CONFIG_DRM_HYPERV) += hyperv/
obj-y += sitronix/
obj-y += solomon/
obj-$(CONFIG_DRM_SPRD) += sprd/
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/
obj-$(CONFIG_DRM_LOONGSON) += loongson/
obj-$(CONFIG_DRM_POWERVR) += imagination/
diff --git a/drivers/gpu/drm/baikal/Kconfig b/drivers/gpu/drm/baikal/Kconfig
new file mode 100644
index 00000000000000..4a18055cd22fae
--- /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 00000000000000..321d3be96b8147
--- /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 00000000000000..40030cb42833b4
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal-hdmi.c
@@ -0,0 +1,122 @@
+// 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 = of_drm_find_bridge(pdev->dev.of_node);
+ return 0;
+}
+
+static void baikal_dw_hdmi_remove(struct platform_device *pdev)
+{
+ struct baikal_hdmi_bridge *priv = platform_get_drvdata(pdev);
+
+ dw_hdmi_remove(priv->hdmi);
+}
+
+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 00000000000000..84136001f2b187
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c
@@ -0,0 +1,259 @@
+// 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 (backlight_is_blank(bl_dev)) {
+ 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 00000000000000..87684a3e35d4d7
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
@@ -0,0 +1,443 @@
+// 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);
+}
+
+static 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 00000000000000..f3f4bfd1d4ac10
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
@@ -0,0 +1,94 @@
+// 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"
+
+static 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 00000000000000..432270bebed69b
--- /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 00000000000000..e2a5e6ff03e37d
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2025 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ * Bugfixes by Alexey Sheplyakov <asheplyakov@altlinux.org>
+ *
+ */
+
+#include <linux/firmware/baikal/baikal-smc.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 <linux/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 <drm/clients/drm_client_setup.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 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 = find_panel_by_fwnode(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 = aperture_remove_all_conflicting_devices(vdu_drm_driver.name);
+ 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;
+
+ if (priv == NULL)
+ return -EINVAL;
+
+ 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,
+ DRM_FBDEV_DMA_DRIVER_OPS,
+ .ioctls = NULL,
+ .fops = &baikal_drm_fops,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .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_SCP_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_client_setup(drm, NULL);
+#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 = 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 00000000000000..e511bd453dcb1e
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_panel.c
@@ -0,0 +1,195 @@
+// 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,
+ struct drm_encoder *encoder,
+ 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_drm_bridge_alloc(panel->dev, struct baikal_lvds_bridge, bridge,
+ &baikal_lvds_bridge_funcs);
+ if (IS_ERR(panel_bridge))
+ return ERR_PTR(-ENOMEM);
+
+ panel_bridge->connector_type = connector_type;
+ panel_bridge->panel = panel;
+
+#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 00000000000000..daa8d152e0f487
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
@@ -0,0 +1,118 @@
+// 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 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_RGB888:
+ cntl |= CR1_BPP24 | CR1_FBP;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ cntl |= CR1_BPP24;
+ break;
+ case DRM_FORMAT_RGB565:
+ cntl |= CR1_BPP16_565;
+ 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_RGB888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_RGB565,
+ 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 00000000000000..fa174397dc7dd9
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
@@ -0,0 +1,136 @@
+/* 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_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/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
index 2c5e532410de9e..8ffa13e4c7da06 100644
--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
@@ -72,3 +72,9 @@ config DRM_DW_MIPI_DSI2
select DRM_KMS_HELPER
select DRM_MIPI_DSI
select DRM_PANEL_BRIDGE
+
+config DRM_BAIKAL_HDMI
+ tristate "Baikal-M HDMI transmitter"
+ select DRM_DW_HDMI
+ help
+ Choose this if you want to use HDMI on Baikal-M.
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index d1e6598ea3bc02..73512e170f1c7f 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -513,7 +513,7 @@ EXPORT_SYMBOL(of_drm_get_panel_orientation);
#endif
/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */
-static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
+struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
{
struct drm_panel *panel;
@@ -533,6 +533,7 @@ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode
return ERR_PTR(-EPROBE_DEFER);
}
+EXPORT_SYMBOL(find_panel_by_fwnode);
/* Find panel by follower device */
static struct drm_panel *find_panel_by_dev(struct device *follower_dev)
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index 2407bfa60236f8..4866dc3f0cd887 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -362,6 +362,7 @@ int drm_panel_add_follower(struct device *follower_dev,
void drm_panel_remove_follower(struct drm_panel_follower *follower);
int devm_drm_panel_add_follower(struct device *follower_dev,
struct drm_panel_follower *follower);
+struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode);
#else
static inline bool drm_is_panel_follower(struct device *dev)
{
@@ -380,6 +381,11 @@ static inline int devm_drm_panel_add_follower(struct device *follower_dev,
{
return -ENODEV;
}
+
+static inline struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
+{
+ return -ENODEV;
+}
#endif
#if IS_ENABLED(CONFIG_DRM_PANEL) && (IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \
--
2.42.2
next prev parent reply other threads:[~2026-02-27 10:32 UTC|newest]
Thread overview: 36+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 01/35] Baikal Electronics SoC family Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 02/35] clk: Add clock drivers for Baikal BE-M1000 Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 03/35] clk: baikal-m: old firmware: use "cmu-id" if there is no "reg" in devicetree Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 04/35] usb: add support for Baikal USB PHY Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 05/35] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 06/35] uart: add support for UART Baikal BE-M1000 Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 07/35] serial: 8250_dw: verify clock rate in dw8250_set_termios Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 08/35] cpufreq-dt: don't load on Baikal-M SoC Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 09/35] net: stmmac: support of Baikal-BE1000 SoCs GMAC Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 10/35] net: fwnode_get_phy_id: consider all compatible strings Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 11/35] drm/panfrost: forcibly set dma-coherent on Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 12/35] drm/panfrost: disable devfreq " Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 13/35] ata: ahci: add support for Baikal BE-M1000 Daniil Gnusarev
2026-02-27 10:32 ` Daniil Gnusarev [this message]
2026-02-27 10:32 ` [d-kernel] [PATCH 15/35] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9 Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 16/35] drm: baikal-vdu: disable backlight driver loading Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 17/35] sound: add support for Baikal BE-M1000 I2S Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 18/35] sound: dwc-i2s: request all IRQs specified in device tree Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 19/35] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 20/35] sound: baikal-i2s: " Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 21/35] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 22/35] dt-bindings: dw-hdmi: added ahb-audio-regshift Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 23/35] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 24/35] dw-hdmi: add flag SNDRV_PCM_INFO_BATCH for audio via hdmi on Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 25/35] bmc: add board management controller driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 26/35] pm: disable all sleep states on Baikal-M based boards Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 27/35] sound: hda: add driver for HDA controller on Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 28/35] sound: hda: enable jack detection in polling mode " Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 29/35] input: new driver - serdev-serio Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 30/35] input: serio: add an alias to the sersev-serio driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 31/35] input: added TF307 serio PS/2 emulator driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 32/35] hwmon: add Baikal-M monitoring driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 33/35] hwmon: baikal-pvt: support work on machines with old firmware Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 34/35] dw-pcie: refuse to load on Baikal-M with recent firmware Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 35/35] config-aarch64: enable more configs for Baikal-M support Daniil Gnusarev
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=20260227103236.785736-15-gnusarevda@basealt.ru \
--to=gnusarevda@basealt.ru \
--cc=devel-kernel@lists.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 kernel packages development
This inbox may be cloned and mirrored by anyone:
git clone --mirror http://lore.altlinux.org/devel-kernel/0 devel-kernel/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-kernel devel-kernel/ http://lore.altlinux.org/devel-kernel \
devel-kernel@altlinux.org devel-kernel@altlinux.ru devel-kernel@altlinux.com
public-inbox-index devel-kernel
Example config snippet for mirrors.
Newsgroup available over NNTP:
nntp://lore.altlinux.org/org.altlinux.lists.devel-kernel
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git