[PATCH 5/6] video: add generic panel-lvds driver

Johannes Schneider johannes.schneider at leica-geosystems.com
Mon Jun 1 21:09:51 PDT 2026


From: Thomas Haemmerle <thomas.haemmerle at leica-geosystems.com>

Barebox has no driver for "panel-lvds" compatible DT nodes, which are used by
all i.MX8M board display pipelines to describe LVDS panels with a panel-timing
subnode. Without this driver the VPL chain in fsl-ldb terminates before it can
query video modes, and the LCDIF2 framebuffer driver cannot determine the active
display resolution, so no framebuffer is allocated and no splash is shown.

The driver reads one video mode from the panel-timing subnode, enables
an optional power-supply regulator and enable GPIO on VPL_ENABLE, and
controls an optional backlight device.  Closely follows the Linux
drivers/gpu/drm/panel/panel-lvds.c behavior.

Assisted-by: Claude:claude-sonnet-4-6
Signed-of-by: Thomas Haemmerle <thomas.haemmerle at leica-geosystems.com>
---
 drivers/video/Kconfig      |   9 ++
 drivers/video/Makefile     |   1 +
 drivers/video/panel-lvds.c | 175 +++++++++++++++++++++++++++++++++++++
 3 files changed, 185 insertions(+)
 create mode 100644 drivers/video/panel-lvds.c

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index fbcec6fd67..964c2c8a29 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -204,6 +204,15 @@ config DRIVER_VIDEO_IMX_LCDIF
 	  Supports the "fsl,imx8mp-lcdif" compatible. Creates a simple-framebuffer
 	  device tree node for kernel handoff via DRM_SIMPLEDRM.
 
+config DRIVER_VIDEO_PANEL_LVDS
+	bool "Generic LVDS panel support"
+	select VIDEO_VPL
+	depends on OFTREE && OFDEVICE
+	help
+	  Driver for "panel-lvds" compatible display panels. Reads the video
+	  mode from a "panel-timing" subnode and controls the panel power
+	  supply regulator.
+
 config DRIVER_VIDEO_SIMPLE_PANEL
 	bool "Simple panel support"
 	select VIDEO_VPL
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 9957ff5ad2..9d073fdf75 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_VIDEO_VPL) += vpl.o
 obj-$(CONFIG_DRIVER_VIDEO_MTL017) += mtl017.o
 obj-$(CONFIG_DRIVER_VIDEO_TC358767) += tc358767.o
 obj-$(CONFIG_DRIVER_VIDEO_IMX_LCDIF) += imx-lcdif.o
+obj-$(CONFIG_DRIVER_VIDEO_PANEL_LVDS) += panel-lvds.o
 obj-$(CONFIG_DRIVER_VIDEO_SIMPLE_PANEL) += simple-panel.o
 obj-$(CONFIG_DRIVER_VIDEO_MIPI_DBI) += mipi_dbi.o
 obj-$(CONFIG_DRIVER_VIDEO_MIPI_DSI) += mipi_dsi.o
diff --git a/drivers/video/panel-lvds.c b/drivers/video/panel-lvds.c
new file mode 100644
index 0000000000..80d0b105ba
--- /dev/null
+++ b/drivers/video/panel-lvds.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LVDS panel driver for barebox
+ *
+ * Supports "panel-lvds" compatible panels with a "panel-timing" subnode.
+ *
+ * Based on Linux drivers/gpu/drm/panel/panel-lvds.c
+ * Copyright (C) 2017 Laurent Pinchart <laurent.pinchart at ideasonboard.com>
+ *
+ * Copyright Leica Geosystems AG
+ */
+
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <linux/err.h>
+#include <of.h>
+#include <fb.h>
+#include <regulator.h>
+#include <gpio.h>
+#include <of_gpio.h>
+#include <video/vpl.h>
+#include <video/backlight.h>
+
+struct panel_lvds {
+	struct device *dev;
+	struct vpl vpl;
+	struct regulator *power;
+	int enable_gpio;
+	enum of_gpio_flags enable_gpio_flags;
+	struct device_node *backlight_node;
+	struct backlight_device *backlight;
+	struct fb_videomode mode;
+	bool mode_valid;
+};
+
+static int panel_lvds_enable(struct panel_lvds *panel)
+{
+	int ret;
+
+	if (panel->power) {
+		ret = regulator_enable(panel->power);
+		if (ret) {
+			dev_err(panel->dev, "power-supply enable failed: %pe\n",
+				ERR_PTR(ret));
+			return ret;
+		}
+	}
+
+	if (gpio_is_valid(panel->enable_gpio)) {
+		int val = !(panel->enable_gpio_flags & OF_GPIO_ACTIVE_LOW);
+
+		gpio_direction_output(panel->enable_gpio, val);
+	}
+
+	/* Lazily resolve backlight on first enable (may probe after panel) */
+	if (panel->backlight_node && !panel->backlight)
+		panel->backlight = of_backlight_find(panel->backlight_node);
+
+	if (panel->backlight)
+		backlight_set_brightness_default(panel->backlight);
+
+	return 0;
+}
+
+static void panel_lvds_disable(struct panel_lvds *panel)
+{
+	if (panel->backlight)
+		backlight_set_brightness(panel->backlight, 0);
+
+	if (gpio_is_valid(panel->enable_gpio))
+		gpio_direction_output(panel->enable_gpio,
+				      !!(panel->enable_gpio_flags & OF_GPIO_ACTIVE_LOW));
+
+	if (panel->power)
+		regulator_disable(panel->power);
+}
+
+static int panel_lvds_ioctl(struct vpl *vpl, unsigned int port,
+			    unsigned int cmd, void *ptr)
+{
+	struct panel_lvds *panel = container_of(vpl, struct panel_lvds, vpl);
+	struct display_timings *timings;
+
+	switch (cmd) {
+	case VPL_ENABLE:
+		return panel_lvds_enable(panel);
+
+	case VPL_DISABLE:
+		panel_lvds_disable(panel);
+		return 0;
+
+	case VPL_PREPARE:
+	case VPL_UNPREPARE:
+		return 0;
+
+	case VPL_GET_VIDEOMODES:
+		timings = ptr;
+		if (!panel->mode_valid)
+			return -ENOENT;
+		timings->modes = &panel->mode;
+		timings->num_modes = 1;
+		return 0;
+
+	default:
+		return 0;
+	}
+}
+
+static int panel_lvds_probe(struct device *dev)
+{
+	struct panel_lvds *panel;
+	int ret;
+
+	panel = xzalloc(sizeof(*panel));
+	panel->dev = dev;
+
+	/* Parse panel-timing subnode */
+	ret = of_get_display_timing(dev->of_node, "panel-timing", &panel->mode);
+	if (ret) {
+		dev_err(dev, "failed to parse panel-timing: %d\n", ret);
+		return ret;
+	}
+	panel->mode_valid = true;
+
+	panel->power = regulator_get(dev, "power");
+	if (IS_ERR(panel->power)) {
+		/*
+		 * power-supply is optional; if not present continue without it.
+		 * This matches the Linux driver behavior.
+		 */
+		if (PTR_ERR(panel->power) == -ENODEV)
+			panel->power = NULL;
+		else
+			return dev_errp_probe(dev, panel->power, "power supply\n");
+	}
+
+	panel->enable_gpio = of_get_named_gpio_flags(dev->of_node, "enable-gpios",
+						     0, &panel->enable_gpio_flags);
+	if (gpio_is_valid(panel->enable_gpio)) {
+		int initial = (panel->enable_gpio_flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0;
+
+		ret = gpio_request(panel->enable_gpio, "panel-enable");
+		if (ret)
+			dev_warn(dev, "failed to request enable-gpio: %d\n", ret);
+		else
+			gpio_direction_output(panel->enable_gpio, initial);
+	}
+
+	panel->backlight_node = of_parse_phandle(dev->of_node, "backlight", 0);
+
+	panel->vpl.node = dev->of_node;
+	panel->vpl.ioctl = panel_lvds_ioctl;
+
+	ret = vpl_register(&panel->vpl);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "LVDS panel %dx%d registered\n",
+		panel->mode.xres, panel->mode.yres);
+	return 0;
+}
+
+static const struct of_device_id panel_lvds_dt_ids[] = {
+	{ .compatible = "panel-lvds" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, panel_lvds_dt_ids);
+
+static struct driver panel_lvds_driver = {
+	.name		= "panel-lvds",
+	.probe		= panel_lvds_probe,
+	.of_compatible	= DRV_OF_COMPAT(panel_lvds_dt_ids),
+};
+device_platform_driver(panel_lvds_driver);
-- 
2.43.0




More information about the barebox mailing list