[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