* [PATCH RFC v4 01/13] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 02/13] efi: bgrt: export the BGRT table and image size Màxim Pedraza Padilla
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
The body of DRM_CLIENT_DEFAULT is indented with spaces, unlike every
other entry in the file. Use tabs, so that the next client added to the
list does not have to pick one or the other.
No functional change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index 6096c623d9d5..8d8bc37ec9f2 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -114,10 +114,10 @@ config DRM_CLIENT_DEFAULT_LOG
endchoice
config DRM_CLIENT_DEFAULT
- string
- depends on DRM_CLIENT
- default "fbdev" if DRM_CLIENT_DEFAULT_FBDEV
- default "log" if DRM_CLIENT_DEFAULT_LOG
- default ""
+ string
+ depends on DRM_CLIENT
+ default "fbdev" if DRM_CLIENT_DEFAULT_FBDEV
+ default "log" if DRM_CLIENT_DEFAULT_LOG
+ default ""
endmenu
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 02/13] efi: bgrt: export the BGRT table and image size
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 01/13] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 03/13] drm: client: add splash client Màxim Pedraza Padilla
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
The DRM splash client reads the boot graphics the firmware left behind
through bgrt_tab and bgrt_image_size. Built as part of drm_client_lib,
which can be a module, it then fails to link:
ERROR: modpost: drivers/gpu/drm/clients/drm_client_lib.ko: symbol 'bgrt_tab' undefined!
ERROR: modpost: drivers/gpu/drm/clients/drm_client_lib.ko: symbol 'bgrt_image_size' undefined!
Export both. They are filled in once, early in boot, and only read
after that.
Reported-by: Mario Limonciello <mario.limonciello@amd.com>
Closes: https://lore.kernel.org/dri-devel/5d7067de-97b7-4232-9cf6-e4b978696482@amd.com/
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/firmware/efi/efi-bgrt.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/firmware/efi/efi-bgrt.c b/drivers/firmware/efi/efi-bgrt.c
index 1da451582812..746a8fb3a337 100644
--- a/drivers/firmware/efi/efi-bgrt.c
+++ b/drivers/firmware/efi/efi-bgrt.c
@@ -17,7 +17,10 @@
#include <linux/efi-bgrt.h>
struct acpi_table_bgrt bgrt_tab;
+EXPORT_SYMBOL_GPL(bgrt_tab);
+
size_t bgrt_image_size;
+EXPORT_SYMBOL_GPL(bgrt_image_size);
struct bmp_header {
u16 id;
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 03/13] drm: client: add splash client
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 01/13] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 02/13] efi: bgrt: export the BGRT table and image size Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 04/13] MAINTAINERS: add entry for DRM " Màxim Pedraza Padilla
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
From: Francesco Valla <francesco@valla.it>
Add a DRM client that draws a simple splash, with possibility to show:
- the logo provided by EFI BGRT;
- a static BMP image (loaded as firmware);
- a colored background.
The client is not meant to replace a full-featured bootsplash, but
rather to remove some complexity (and hopefully boot time) on small
embedded platforms or on systems with a limited scope (e.g: recovery
or manufacturing images).
The background color can be set either at build time from a dedicated
config option or at runtime through the drm_client_lib.splash_color
command line parameter. Any color in RGB888 format can be used.
If enabled, the static BMP image is loaded using the kernel firmware
infrastructure; a valid BMP image with 24bpp color and no compression
is expected. The name of the image can be set through the
drm_client_lib.splash_bmp kernel command line parameter, with the
default being 'drm_splash.bmp'.
Just like the existing DRM clients, the splash can be enabled from the
kernel command line using drm_client_lib.active=splash.
Signed-off-by: Francesco Valla <francesco@valla.it>
[Màxim: fix the render thread never exiting when the BMP firmware is
missing, a lost wake-up, and stopping a thread that had exited, never
existed or failed to start; fix buffers going to the wrong modesets
and tiled groups in drm_splash_init_client(), and take modeset_mutex
there; don't keep an ERR_PTR as a buffer; unmap the image only once;
fix use-after-frees on unbind and in the firmware callback; check the
BMP size without overflowing; indent the Kconfig entry with tabs]
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 63 +-
drivers/gpu/drm/clients/Makefile | 1 +
drivers/gpu/drm/clients/drm_client_internal.h | 9 +
drivers/gpu/drm/clients/drm_client_setup.c | 8 +
drivers/gpu/drm/clients/drm_splash.c | 1028 +++++++++++++++++
5 files changed, 1108 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/clients/drm_splash.c
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index 8d8bc37ec9f2..cbe271c3a23f 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -12,6 +12,7 @@ config DRM_CLIENT_LIB
config DRM_CLIENT_SELECTION
tristate
depends on DRM
+ select DRM_CLIENT_LIB if DRM_CLIENT_SPLASH
select DRM_CLIENT_LIB if DRM_CLIENT_LOG
select DRM_CLIENT_LIB if DRM_FBDEV_EMULATION
help
@@ -85,10 +86,63 @@ config DRM_CLIENT_LOG
If you only need logs, but no terminal, or if you prefer userspace
terminal, say "Y".
+config DRM_CLIENT_SPLASH
+ bool "Display graphic splash"
+ depends on DRM_CLIENT_SELECTION
+ select DRM_CLIENT
+ select DRM_CLIENT_SETUP
+ select DRM_DRAW
+ help
+ This enables a splash drm client, able to display either a plain
+ color or a static image until the userspace is ready to take over.
+ The splash will be displayed on all screens available at boot, if
+ any, or on the ones part of the first hotplug event.
+
+config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
+ hex "Splash background color"
+ depends on DRM_CLIENT_SPLASH
+ default 0x000000
+ help
+ The default splash background color, in RGB888 format.
+
+ The color can be overridden through the drm_client_lib.splash_color
+ kernel command line parameter.
+
+config DRM_CLIENT_SPLASH_SRC_BGRT
+ bool "EFI BGRT as splash source"
+ depends on DRM_CLIENT_SPLASH
+ depends on ACPI_BGRT
+ select DRM_CLIENT_SPLASH_BMP_SUPPORT
+ help
+ Use the BGRT image provided by the EFI bootloader. If the image is
+ smaller than the display(s), it will be centered and the color
+ specified through the DRM_CLIENT_SPLASH_BACKGROUND_COLOR config
+ option will be used as background.
+
+config DRM_CLIENT_SPLASH_SRC_BMP
+ bool "BMP image as splash source"
+ depends on DRM_CLIENT_SPLASH
+ select DRM_CLIENT_SPLASH_BMP_SUPPORT
+ select FW_LOADER
+ help
+ Use a BMP (bitmap) image as splash. If the image is smaller than the
+ display(s), it will be centered and the color specified through the
+ DRM_CLIENT_SPLASH_BACKGROUND_COLOR config option will be used as
+ background.
+
+ The image will be loaded using the firmware loading facility the
+ kernel provides; it shall use 24 bits per pixel and shall not be
+ compressed. The name of the file can be set through the
+ drm_client_lib.splash_bmp command line parameter, with the default
+ being 'drm_splash.bmp'.
+
+config DRM_CLIENT_SPLASH_BMP_SUPPORT
+ bool
+
choice
prompt "Default DRM Client"
depends on DRM_CLIENT_SELECTION
- depends on DRM_FBDEV_EMULATION || DRM_CLIENT_LOG
+ depends on DRM_FBDEV_EMULATION || DRM_CLIENT_LOG || DRM_CLIENT_SPLASH
default DRM_CLIENT_DEFAULT_FBDEV
help
Selects the default drm client.
@@ -111,6 +165,12 @@ config DRM_CLIENT_DEFAULT_LOG
screen, but doesn't implement a full terminal. For that you will need
a userspace terminal using drm/kms.
+config DRM_CLIENT_DEFAULT_SPLASH
+ bool "splash"
+ depends on DRM_CLIENT_SPLASH
+ help
+ Use splash as default drm client.
+
endchoice
config DRM_CLIENT_DEFAULT
@@ -118,6 +178,7 @@ config DRM_CLIENT_DEFAULT
depends on DRM_CLIENT
default "fbdev" if DRM_CLIENT_DEFAULT_FBDEV
default "log" if DRM_CLIENT_DEFAULT_LOG
+ default "splash" if DRM_CLIENT_DEFAULT_SPLASH
default ""
endmenu
diff --git a/drivers/gpu/drm/clients/Makefile b/drivers/gpu/drm/clients/Makefile
index 1304ff358509..b43b1e86fe8e 100644
--- a/drivers/gpu/drm/clients/Makefile
+++ b/drivers/gpu/drm/clients/Makefile
@@ -6,4 +6,5 @@ drm_client_lib-y := drm_client_setup.o
drm_client_lib-$(CONFIG_DRM_CLIENT_LOG) += drm_log.o
drm_client_lib-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_client.o \
drm_fbdev_helper.o
+drm_client_lib-$(CONFIG_DRM_CLIENT_SPLASH) += drm_splash.o
obj-$(CONFIG_DRM_CLIENT_LIB) += drm_client_lib.o
diff --git a/drivers/gpu/drm/clients/drm_client_internal.h b/drivers/gpu/drm/clients/drm_client_internal.h
index 6dc078bf6503..48ee0c1c2529 100644
--- a/drivers/gpu/drm/clients/drm_client_internal.h
+++ b/drivers/gpu/drm/clients/drm_client_internal.h
@@ -22,4 +22,13 @@ void drm_log_register(struct drm_device *dev);
static inline void drm_log_register(struct drm_device *dev) {}
#endif
+#ifdef CONFIG_DRM_CLIENT_SPLASH
+void drm_splash_register(struct drm_device *dev,
+ const struct drm_format_info *format);
+#else
+static inline void drm_splash_register(struct drm_device *dev,
+ const struct drm_format_info *format)
+{}
+#endif
+
#endif
diff --git a/drivers/gpu/drm/clients/drm_client_setup.c b/drivers/gpu/drm/clients/drm_client_setup.c
index 515aceac22b1..c19498938ee3 100644
--- a/drivers/gpu/drm/clients/drm_client_setup.c
+++ b/drivers/gpu/drm/clients/drm_client_setup.c
@@ -56,6 +56,14 @@ void drm_client_setup(struct drm_device *dev, const struct drm_format_info *form
return;
}
#endif
+
+#ifdef CONFIG_DRM_CLIENT_SPLASH
+ if (!strcmp(drm_client_default, "splash")) {
+ drm_splash_register(dev, format);
+ return;
+ }
+#endif
+
if (strcmp(drm_client_default, ""))
drm_warn(dev, "Unknown DRM client %s\n", drm_client_default);
}
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
new file mode 100644
index 000000000000..66c192139d9c
--- /dev/null
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -0,0 +1,1028 @@
+// SPDX-License-Identifier: GPL-2.0 or MIT
+/*
+ * Copyright (c) 2025-2026 Francesco Valla <francesco@valla.it>
+ */
+
+#include <linux/atomic.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/efi-bgrt.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/iosys-map.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include <acpi/actbl1.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "drm_client_internal.h"
+#include "drm_draw_internal.h"
+#include "drm_internal.h"
+
+/**
+ * DOC: overview
+ *
+ * This is a simple graphic bootsplash, able to display either a plain color or
+ * a static image.
+ */
+
+static unsigned int splash_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+module_param(splash_color, uint, 0400);
+MODULE_PARM_DESC(splash_color, "Splash background color (RGB888)");
+
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP)
+#define DEFAULT_SPLASH_BMP "drm_splash.bmp"
+static char *splash_bmp = DEFAULT_SPLASH_BMP;
+module_param(splash_bmp, charp, 0400);
+MODULE_PARM_DESC(splash_bmp, "Name of splash image (default: \"" DEFAULT_SPLASH_BMP "\")");
+#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BMP
+
+#define BMP_FILE_MAGIC_ID 0x4d42
+
+/* BMP header structures copied from drivers/video/fbdev/efifb.c */
+struct bmp_file_header {
+ __le16 id;
+ __le32 file_size;
+ __le32 reserved;
+ __le32 bitmap_offset;
+} __packed;
+
+struct bmp_dib_header {
+ __le32 dib_header_size;
+ __le32 width;
+ __le32 height;
+ __le16 planes;
+ __le16 bpp;
+ __le32 compression;
+ __le32 bitmap_size;
+ __le32 horz_resolution;
+ __le32 vert_resolution;
+ __le32 colors_used;
+ __le32 colors_important;
+} __packed;
+
+struct drm_splash_scanout {
+ int id;
+ /* Modesets scanning this buffer out, by position in the list */
+ u32 modesets;
+ u32 format;
+ unsigned int width;
+ unsigned int height;
+ struct drm_client_buffer *buffer;
+ bool bg_drawn;
+ bool img_drawn;
+};
+
+struct drm_splash {
+ struct drm_client_dev client;
+ u32 preferred_format;
+ struct device dev;
+
+ struct task_struct *thread;
+ atomic_t pending;
+ /* A firmware request is in flight, so an image may still arrive */
+ bool fw_pending;
+ /* A request was made, and fw_done tells when its callback is over */
+ bool fw_requested;
+ struct completion fw_done;
+
+ /* Serialises hotplug events: the first one sets the modesets up */
+ struct mutex hotplug_lock;
+ bool initialized;
+
+ u32 n_scanout;
+ struct drm_splash_scanout *scanout;
+
+ /* Hands fw over from the firmware callback to the render thread */
+ spinlock_t fw_lock;
+ const struct firmware *fw;
+ void *map_data;
+
+ bool use_bgrt;
+};
+
+static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
+{
+ return container_of_const(client, struct drm_splash, client);
+}
+
+static struct drm_splash_scanout *
+get_scanout_from_tile_group(struct drm_splash *splash, int id)
+{
+ int j;
+
+ for (j = 0; j < splash->n_scanout; j++)
+ if (splash->scanout[j].id == id)
+ return &splash->scanout[j];
+
+ return NULL;
+}
+
+static inline void drm_splash_wake_render_thread(struct drm_splash *splash)
+{
+ wake_up_process(splash->thread);
+}
+
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP)
+static int drm_splash_fw_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ const struct firmware *fw;
+
+ scoped_guard(spinlock, &splash->fw_lock)
+ fw = splash->fw;
+
+ if (!fw)
+ return -ENOENT;
+
+ *data = fw->data;
+ *size = fw->size;
+
+ return 0;
+}
+
+static void drm_splash_fw_callback(const struct firmware *fw, void *context)
+{
+ struct drm_splash *splash = context;
+ struct drm_client_dev *client = &splash->client;
+
+ if (!fw || !fw->data) {
+ drm_err(client->dev, "splash: no firmware");
+ release_firmware(fw);
+ fw = NULL;
+ }
+
+ scoped_guard(spinlock, &splash->fw_lock)
+ splash->fw = fw;
+
+ /*
+ * Whether it came or not, nothing else is coming: the thread must be
+ * told either way, or it waits for good. Release, so that a thread
+ * that sees the flag clear also sees the firmware stored above.
+ */
+ smp_store_release(&splash->fw_pending, false);
+
+ /* Wake the render thread */
+ drm_dbg(client->dev, "splash: firmware request done, wake up drawing thread");
+ atomic_set(&splash->pending, 1);
+ drm_splash_wake_render_thread(splash);
+
+ /* Last: once this is seen, unregister may free the splash */
+ complete(&splash->fw_done);
+}
+
+static int drm_splash_fw_request_bmp(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+
+ drm_info(client->dev, "splash: request %s as firmware", splash_bmp);
+
+ return request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+ splash_bmp, client->dev->dev, GFP_KERNEL,
+ splash, drm_splash_fw_callback);
+}
+#else
+static inline int drm_splash_fw_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int drm_splash_fw_request_bmp(struct drm_splash *splash)
+{
+ return -EOPNOTSUPP;
+}
+#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BMP
+
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BGRT)
+static bool drm_splash_bgrt_available(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+
+ if (!bgrt_tab.image_address) {
+ drm_info(client->dev, "splash: no BGRT found");
+ return false;
+ }
+
+ if (bgrt_tab.status & ACPI_BGRT_ORIENTATION_OFFSET) {
+ drm_info(client->dev, "splash: BGRT rotation bits set, skipping");
+ return false;
+ }
+
+ return true;
+}
+
+static inline unsigned int drm_splash_bgrt_get_xoffset(void)
+{
+ return bgrt_tab.image_offset_x;
+}
+
+static inline unsigned int drm_splash_bgrt_get_yoffset(void)
+{
+ return bgrt_tab.image_offset_y;
+}
+
+static int drm_splash_bgrt_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ struct drm_client_dev *client = &splash->client;
+
+ if (!drm_splash_bgrt_available(splash))
+ return -ENOENT;
+
+ drm_dbg(client->dev, "splash: BGRT image is at 0x%016llx, size=%zX",
+ bgrt_tab.image_address, bgrt_image_size);
+
+ splash->map_data = memremap(bgrt_tab.image_address, bgrt_image_size,
+ MEMREMAP_WB);
+ if (!splash->map_data) {
+ drm_warn(client->dev, "splash: failed to map BGRT image memory");
+ return -ENOMEM;
+ }
+
+ *data = splash->map_data;
+ *size = bgrt_image_size;
+
+ return 0;
+}
+#else
+static inline bool drm_splash_bgrt_available(struct drm_splash *splash)
+{
+ return false;
+}
+
+static inline unsigned int drm_splash_bgrt_get_xoffset(void)
+{
+ return 0;
+}
+
+static inline unsigned int drm_splash_bgrt_get_yoffset(void)
+{
+ return 0;
+}
+
+static inline int drm_splash_bgrt_load(struct drm_splash *splash,
+ const u8 **data, size_t *size)
+{
+ return -EOPNOTSUPP;
+}
+#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BGRT
+
+static u32 drm_splash_find_usable_format(struct drm_plane *plane,
+ u32 preferred_format)
+{
+ int i;
+
+ /* Check if the preferred format can be used */
+ for (i = 0; i < plane->format_count; i++)
+ if (plane->format_types[i] == preferred_format)
+ return preferred_format;
+
+ /* Otherwise, find the first format that can be converted from XRGB8888 */
+ for (i = 0; i < plane->format_count; i++)
+ if (drm_draw_can_convert_from_xrgb8888(plane->format_types[i]))
+ return plane->format_types[i];
+
+ return DRM_FORMAT_INVALID;
+}
+
+static void drm_splash_fill(struct iosys_map *map, unsigned int dst_pitch,
+ unsigned int height, unsigned int width,
+ u32 px_width, u32 color)
+{
+ switch (px_width) {
+ case 2:
+ drm_draw_fill16(map, dst_pitch, height, width, color);
+ break;
+ case 3:
+ drm_draw_fill24(map, dst_pitch, height, width, color);
+ break;
+ case 4:
+ drm_draw_fill32(map, dst_pitch, height, width, color);
+ break;
+ default:
+ WARN_ONCE(1, "Can't fill with pixel width %d", px_width);
+ }
+}
+
+static int drm_splash_fill_solid_color(struct drm_client_buffer *buffer,
+ u32 color)
+{
+ struct drm_client_dev *client = buffer->client;
+ struct drm_framebuffer *fb = buffer->fb;
+ struct drm_rect r = DRM_RECT_INIT(0, 0, fb->width, fb->height);
+ u32 px_width = fb->format->cpp[0];
+ struct iosys_map map;
+ int ret;
+
+ ret = drm_client_buffer_vmap_local(buffer, &map);
+ if (ret) {
+ drm_err(client->dev, "splash: cannot vmap buffer: %d", ret);
+ return ret;
+ }
+
+ drm_splash_fill(&map, fb->pitches[0], drm_rect_height(&r),
+ drm_rect_width(&r), px_width, color);
+
+ drm_client_buffer_vunmap_local(buffer);
+
+ return drm_client_buffer_flush(buffer, &r);
+}
+
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_BMP_SUPPORT)
+static void drm_splash_blit_pix16(struct iosys_map *map, unsigned int dpitch,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y, u32 format)
+{
+ unsigned int x, y, src_offset, dst_offset;
+ u32 scolor, dcolor, wr_off;
+
+ for (y = 0; y < height; y++) {
+ src_offset = (invert_y ? (height - y - 1) : y) * spitch;
+ dst_offset = (y_pad + y) * dpitch;
+
+ for (x = 0; x < width; x++) {
+ scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
+ dcolor = drm_draw_color_from_xrgb8888(scolor, format);
+ wr_off = dst_offset + (x_pad + x) * sizeof(u16);
+
+ iosys_map_wr(map, wr_off, u16, dcolor);
+ }
+ }
+}
+
+static void drm_splash_blit_pix24(struct iosys_map *map, unsigned int dpitch,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y, u32 format)
+{
+ unsigned int x, y, src_offset, dst_offset;
+ u32 scolor, dcolor, wr_off;
+
+ for (y = 0; y < height; y++) {
+ src_offset = (invert_y ? (height - y - 1) : y) * spitch;
+ dst_offset = (y_pad + y) * dpitch;
+
+ for (x = 0; x < width; x++) {
+ scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
+ dcolor = drm_draw_color_from_xrgb8888(scolor, format);
+ wr_off = dst_offset + (x_pad + x) * 3;
+
+ iosys_map_wr(map, wr_off, u8, (dcolor & 0x000000FF) >> 0);
+ iosys_map_wr(map, wr_off + 1, u8, (dcolor & 0x0000FF00) >> 8);
+ iosys_map_wr(map, wr_off + 2, u8, (dcolor & 0x00FF0000) >> 16);
+ }
+ }
+}
+
+static void drm_splash_blit_pix32(struct iosys_map *map, unsigned int dpitch,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y, u32 format)
+{
+ unsigned int x, y, src_offset, dst_offset;
+ u32 scolor, dcolor, wr_off;
+
+ for (y = 0; y < height; y++) {
+ src_offset = (invert_y ? (height - y - 1) : y) * spitch;
+ dst_offset = (y_pad + y) * dpitch;
+
+ for (x = 0; x < width; x++) {
+ scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
+ dcolor = drm_draw_color_from_xrgb8888(scolor, format);
+ wr_off = dst_offset + (x_pad + x) * sizeof(u32);
+
+ iosys_map_wr(map, wr_off, u32, dcolor);
+ }
+ }
+}
+
+static void drm_splash_blit_rgb888(struct iosys_map *map, unsigned int dpitch,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y)
+{
+ unsigned int y, src_offset, dst_offset;
+
+ for (y = 0; y < height; y++) {
+ src_offset = (invert_y ? (height - y - 1) : y) * spitch;
+ dst_offset = (y_pad + y) * dpitch + x_pad * 3;
+
+ iosys_map_memcpy_to(map, dst_offset, &sbuf8[src_offset], width * 3);
+ }
+}
+
+static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
+ struct drm_splash_scanout *scanout,
+ const u8 *data, size_t data_len)
+
+{
+ struct drm_client_buffer *buffer = scanout->buffer;
+ struct drm_client_dev *client = buffer->client;
+ struct drm_framebuffer *fb = buffer->fb;
+ u32 px_width = fb->format->cpp[0];
+ const struct bmp_file_header *file_header;
+ const struct bmp_dib_header *dib_header;
+ u32 dib_header_size;
+ u16 bmp_id, bmp_bpp, bmp_planes;
+ u32 bmp_compression, bmp_pitch;
+ s32 bmp_width, bmp_height;
+ bool bmp_invert_y;
+ u32 bitmap_offset;
+ size_t row_len, pixels_len, end;
+ unsigned int x_pad, y_pad;
+ const u8 *image_data;
+ struct iosys_map map;
+ struct drm_rect r;
+ int ret;
+
+ if (data_len < (sizeof(*file_header) + sizeof(*dib_header))) {
+ drm_err(client->dev, "splash: BMP file too short");
+ return -EINVAL;
+ }
+
+ file_header = (const struct bmp_file_header *)data;
+
+ bmp_id = get_unaligned_le16(&file_header->id);
+ if (bmp_id != BMP_FILE_MAGIC_ID) {
+ drm_err(client->dev, "splash: invalid BMP magic 0x%04X", bmp_id);
+ return -EINVAL;
+ }
+
+ bitmap_offset = get_unaligned_le32(&file_header->bitmap_offset);
+
+ dib_header = (const struct bmp_dib_header *)(data + sizeof(*file_header));
+
+ dib_header_size = get_unaligned_le32(&dib_header->dib_header_size);
+
+ bmp_width = (s32)get_unaligned_le32(&dib_header->width);
+ bmp_height = (s32)get_unaligned_le32(&dib_header->height);
+ bmp_planes = get_unaligned_le16(&dib_header->planes);
+ bmp_bpp = get_unaligned_le16(&dib_header->bpp);
+ bmp_compression = get_unaligned_le32(&dib_header->compression);
+
+ /* Restrict supported format to uncompressed, 24bit RGB888 */
+ if (dib_header_size != 40 || bmp_width <= 0 || bmp_height == 0 ||
+ bmp_height == S32_MIN || bmp_planes != 1 ||
+ bmp_compression != 0 || bmp_bpp != 24) {
+ drm_err(client->dev, "splash: invalid BMP format");
+ return -EINVAL;
+ }
+
+ /* A positive height means bottom-to-top scan direction */
+ bmp_invert_y = (bmp_height > 0);
+ bmp_height = abs(bmp_height);
+
+ /*
+ * Every field comes from the image, which the firmware or the device
+ * tree supplied, so check the arithmetic before trusting the result:
+ * rows are padded to four bytes, and the pixels must end within the
+ * data.
+ */
+ if (check_mul_overflow((size_t)bmp_width, (size_t)3, &row_len) ||
+ check_add_overflow(row_len, (size_t)3, &row_len) ||
+ check_mul_overflow(row_len & ~(size_t)3, (size_t)bmp_height,
+ &pixels_len) ||
+ check_add_overflow((size_t)bitmap_offset, pixels_len, &end) ||
+ end > data_len) {
+ drm_err(client->dev, "splash: invalid BMP size");
+ return -EINVAL;
+ }
+ bmp_pitch = row_len & ~(size_t)3;
+
+ if (bmp_width > scanout->width || bmp_height > scanout->height) {
+ drm_err(client->dev, "splash: BMP image is too big for the screen");
+ return -EINVAL;
+ }
+
+ if (splash->use_bgrt) {
+ x_pad = drm_splash_bgrt_get_xoffset();
+ y_pad = drm_splash_bgrt_get_yoffset();
+
+ if ((x_pad + bmp_width) > scanout->width ||
+ (y_pad + bmp_height) > scanout->height) {
+ drm_err(client->dev, "splash: BGRT image would overflow");
+ return -EINVAL;
+ }
+
+#ifdef CONFIG_X86
+ /*
+ * BGRT sanity check, taken from efifb.c:
+ *
+ * On x86 some firmwares use a low non native resolution for
+ * the display when they have shown some text messages. While
+ * keeping the bgrt filled with info for the native resolution.
+ * If the bgrt image intended for the native resolution still
+ * fits, it will be displayed very close to the right edge of
+ * the display looking quite bad.
+ */
+
+ if (x_pad != (scanout->width - bmp_width) / 2) {
+ drm_err(client->dev, "splash: BGRT sanity check failed");
+ return -EINVAL;
+ }
+#endif
+ } else {
+ /* Center X and Y */
+ x_pad = (scanout->width - bmp_width) / 2;
+ y_pad = (scanout->height - bmp_height) / 2;
+ }
+
+ image_data = data + bitmap_offset;
+
+ ret = drm_client_buffer_vmap_local(buffer, &map);
+ if (ret) {
+ drm_err(client->dev, "splash: cannot vmap buffer: %d", ret);
+ return ret;
+ }
+
+ r = DRM_RECT_INIT(x_pad, y_pad, bmp_width, bmp_height);
+
+ /* In case the target format is RGB888, source data can be copied to
+ * the video buffer line by line, avoiding some overhead.
+ */
+ if (scanout->format == DRM_FORMAT_RGB888) {
+ drm_splash_blit_rgb888(&map, fb->pitches[0], x_pad, y_pad,
+ image_data, bmp_pitch, bmp_width,
+ bmp_height, bmp_invert_y);
+ } else {
+ switch (px_width) {
+ case 2:
+ drm_splash_blit_pix16(&map, fb->pitches[0], x_pad,
+ y_pad, image_data, bmp_pitch,
+ bmp_width, bmp_height,
+ bmp_invert_y, scanout->format);
+ break;
+ case 3:
+ drm_splash_blit_pix24(&map, fb->pitches[0], x_pad,
+ y_pad, image_data, bmp_pitch,
+ bmp_width, bmp_height,
+ bmp_invert_y, scanout->format);
+ break;
+ case 4:
+ drm_splash_blit_pix32(&map, fb->pitches[0], x_pad,
+ y_pad, image_data, bmp_pitch,
+ bmp_width, bmp_height,
+ bmp_invert_y, scanout->format);
+ break;
+ default:
+ drm_warn_once(client->dev,
+ "splash: can't blit with pixel width %d",
+ px_width);
+ }
+ }
+
+ drm_client_buffer_vunmap_local(buffer);
+
+ return drm_client_buffer_flush(buffer, &r);
+}
+#else
+static inline int drm_splash_bmp_to_scanout(struct drm_splash *splash,
+ struct drm_splash_scanout *scanout,
+ const u8 *data, size_t data_len)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
+ size_t *img_data_len)
+{
+ int ret = 0;
+
+ if (splash->use_bgrt) {
+ ret = drm_splash_bgrt_load(splash, img_data, img_data_len);
+ if (ret)
+ splash->use_bgrt = false;
+ }
+
+ /* BGRT failed to load */
+ if (!splash->use_bgrt)
+ ret = drm_splash_fw_load(splash, img_data, img_data_len);
+
+ return ret;
+}
+
+static void drm_splash_image_cleanup(struct drm_splash *splash)
+{
+ const struct firmware *fw = NULL;
+
+ /* Called after drawing and again at unregister: unmap only once */
+ memunmap(splash->map_data);
+ splash->map_data = NULL;
+
+ scoped_guard(spinlock, &splash->fw_lock) {
+ fw = splash->fw;
+ splash->fw = NULL;
+ }
+
+ release_firmware(fw);
+}
+
+static int drm_splash_render_thread(void *data)
+{
+ struct drm_splash *splash = data;
+ struct drm_client_dev *client = &splash->client;
+
+ while (!kthread_should_stop()) {
+ unsigned int draw_count = 0;
+ const u8 *img_data = NULL;
+ size_t img_data_len = 0;
+ bool img_loaded, last;
+ int i, ret;
+
+ drm_dbg(client->dev, "splash: run render thread...");
+
+ /*
+ * Whether this is the last pass has to be known before looking
+ * for the image: if the firmware lands while this pass runs,
+ * another pass has to pick it up.
+ */
+ last = !smp_load_acquire(&splash->fw_pending);
+
+ ret = drm_splash_image_load(splash, &img_data, &img_data_len);
+ img_loaded = (ret == 0);
+
+ for (i = 0; i < splash->n_scanout; i++) {
+ struct drm_splash_scanout *scanout = &splash->scanout[i];
+
+ if (!scanout->buffer) {
+ drm_err(client->dev,
+ "splash: no buffer for scanout %d", i);
+ continue;
+ }
+
+ if (!scanout->bg_drawn) {
+ drm_dbg(client->dev, "draw background for scanout %d", i);
+ u32 color = drm_draw_color_from_xrgb8888(splash_color,
+ scanout->format);
+ drm_splash_fill_solid_color(scanout->buffer, color);
+ scanout->bg_drawn = true;
+ }
+
+ if (img_loaded && !scanout->img_drawn) {
+ drm_dbg(client->dev, "draw image for scanout %d", i);
+ /* Ignore the return value, since the solid
+ * color has already been drawn to screen.
+ */
+ ret = drm_splash_bmp_to_scanout(splash, scanout,
+ img_data,
+ img_data_len);
+ scanout->img_drawn = (ret == 0);
+ }
+
+ draw_count++;
+ }
+
+ if (img_loaded)
+ drm_splash_image_cleanup(splash);
+
+ if (draw_count > 0) {
+ ret = drm_client_modeset_commit(client);
+ /* If commit returns EBUSY, another master showed up.
+ * This means that the splash is no more required.
+ */
+ if (ret == -EBUSY) {
+ drm_info(client->dev, "splash: not master anymore, exiting");
+ break;
+ }
+ }
+
+ /* Done, or nothing could have arrived since the image was sought */
+ if (img_loaded || last)
+ break;
+
+ /*
+ * Wait for the firmware callback. The state is set before
+ * looking at the flag, so that a wake up in between is not
+ * lost, and the wait is idle rather than uninterruptible, as
+ * nothing guarantees how long it lasts.
+ */
+ set_current_state(TASK_IDLE);
+ if (atomic_xchg(&splash->pending, 0) || kthread_should_stop() ||
+ !READ_ONCE(splash->fw_pending)) {
+ __set_current_state(TASK_RUNNING);
+ continue;
+ }
+
+ schedule();
+ }
+
+ return 0;
+}
+
+static int drm_splash_init_client(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+ struct drm_mode_set *modeset;
+ unsigned int fb_count = 0;
+ unsigned int i, k;
+
+ if (drm_client_modeset_probe(client, 0, 0))
+ return -EIO;
+
+ mutex_lock(&client->modeset_mutex);
+
+ /*
+ * Collect one scanout per usable modeset, or per tile group. Each
+ * scanout records the modesets it feeds by their position in the
+ * list, so that the second pass below can find them again whatever
+ * was skipped in between.
+ */
+ i = 0;
+ drm_client_for_each_modeset(modeset, client) {
+ struct drm_splash_scanout *tmp;
+ struct drm_splash_scanout *scanout;
+ unsigned int idx = i++;
+ u32 format;
+ int id = -1;
+
+ if (WARN_ON_ONCE(idx >= 32))
+ break;
+
+ /* Skip modesets without a mode */
+ if (!modeset->mode)
+ continue;
+
+ if (modeset->connectors[0]->has_tile) {
+ struct drm_splash_scanout *tiled;
+ int new_id = modeset->connectors[0]->tile_group->id;
+
+ /*
+ * Tiled modesets contribute to a single framebuffer:
+ * the first column adds rows, the first row adds
+ * columns.
+ */
+ tiled = get_scanout_from_tile_group(splash, new_id);
+ if (tiled) {
+ if (!modeset->x)
+ tiled->height += modeset->mode->vdisplay;
+ if (!modeset->y)
+ tiled->width += modeset->mode->hdisplay;
+ tiled->modesets |= BIT(idx);
+ continue;
+ }
+
+ /* New tile group, save its ID for later */
+ id = new_id;
+ }
+
+ format = drm_splash_find_usable_format(modeset->crtc->primary,
+ splash->preferred_format);
+ if (format == DRM_FORMAT_INVALID) {
+ drm_warn(client->dev,
+ "splash: can't find a usable format for modeset");
+ continue;
+ }
+
+ tmp = krealloc(splash->scanout,
+ (splash->n_scanout + 1) * sizeof(*splash->scanout),
+ GFP_KERNEL);
+ if (!tmp) {
+ drm_warn(client->dev,
+ "splash: can't reallocate the scanout array");
+ break;
+ }
+
+ splash->scanout = tmp;
+ scanout = &splash->scanout[splash->n_scanout];
+ splash->n_scanout++;
+
+ memset(scanout, 0, sizeof(*scanout));
+ scanout->id = id;
+ scanout->modesets = BIT(idx);
+ scanout->format = format;
+ scanout->width = modeset->mode->hdisplay;
+ scanout->height = modeset->mode->vdisplay;
+ }
+
+ /* Now that the sizes are known, allocate one buffer per scanout */
+ for (k = 0; k < splash->n_scanout; k++) {
+ struct drm_splash_scanout *scanout = &splash->scanout[k];
+ struct drm_client_buffer *buffer;
+
+ buffer = drm_client_buffer_create_dumb(client, scanout->width,
+ scanout->height,
+ scanout->format);
+ if (IS_ERR(buffer)) {
+ drm_warn(client->dev,
+ "splash: can't create dumb buffer %d %d %p4cc",
+ scanout->width, scanout->height, &scanout->format);
+ continue;
+ }
+
+ drm_info(client->dev, "splash: created dumb buffer %d %d %p4cc",
+ scanout->width, scanout->height, &scanout->format);
+
+ scanout->buffer = buffer;
+ fb_count++;
+ }
+
+ /* And point every modeset at the buffer of its scanout */
+ i = 0;
+ drm_client_for_each_modeset(modeset, client) {
+ unsigned int idx = i++;
+
+ if (idx >= 32)
+ break;
+
+ for (k = 0; k < splash->n_scanout; k++) {
+ struct drm_splash_scanout *scanout = &splash->scanout[k];
+
+ if (scanout->buffer && (scanout->modesets & BIT(idx))) {
+ modeset->fb = scanout->buffer->fb;
+ break;
+ }
+ }
+ }
+
+ mutex_unlock(&client->modeset_mutex);
+
+ return (fb_count == 0) ? -ENODEV : 0;
+}
+
+static void drm_splash_free_scanout(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ int i;
+
+ if (splash->n_scanout) {
+ for (i = 0; i < splash->n_scanout; i++)
+ drm_client_buffer_delete(splash->scanout[i].buffer);
+
+ splash->n_scanout = 0;
+ kfree(splash->scanout);
+ splash->scanout = NULL;
+ }
+}
+
+static int drm_splash_client_hotplug(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ struct task_struct *thread;
+ int ret = 0;
+
+ guard(mutex)(&splash->hotplug_lock);
+
+ /* The modesets that get a splash are defined at first hotplug event */
+ if (splash->initialized)
+ return 0;
+
+ ret = drm_splash_init_client(splash);
+ if (ret == -ENODEV) {
+ drm_info(client->dev, "splash: no modeset found");
+ return 0;
+ } else if (ret) {
+ drm_err(client->dev,
+ "splash: failed to init client: %d", ret);
+ return ret;
+ }
+
+ /* Create the render thread, waken later */
+ thread = kthread_create(drm_splash_render_thread, splash,
+ "drm_splash_%s", client->dev->unique);
+ if (IS_ERR(thread)) {
+ ret = PTR_ERR(thread);
+ drm_err(client->dev,
+ "splash: failed to create render thread: %d", ret);
+ drm_splash_free_scanout(client);
+ return ret;
+ }
+
+ /*
+ * The thread exits on its own once the splash is up, so hold on to
+ * it: kthread_stop() at unregister must not find it gone.
+ */
+ get_task_struct(thread);
+ splash->thread = thread;
+
+ splash->use_bgrt = drm_splash_bgrt_available(splash);
+
+ /* If no other image has been loaded, try to load a BMP as firmware */
+ if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP) && !splash->use_bgrt) {
+ WRITE_ONCE(splash->fw_pending, true);
+ ret = drm_splash_fw_request_bmp(splash);
+ splash->fw_requested = !ret;
+ if (ret) {
+ drm_err(client->dev,
+ "splash: failed to kick image load: %d", ret);
+ kthread_stop_put(splash->thread);
+ splash->thread = NULL;
+ drm_splash_free_scanout(client);
+ return ret;
+ }
+ }
+
+ /* Wake the render thread to show initial contents */
+ drm_splash_wake_render_thread(splash);
+
+ splash->initialized = true;
+
+ return 0;
+}
+
+static int drm_splash_client_restore(struct drm_client_dev *client, bool force)
+{
+ int ret;
+
+ if (force)
+ ret = drm_client_modeset_commit_locked(client);
+ else
+ ret = drm_client_modeset_commit(client);
+
+ return ret;
+}
+
+static void drm_splash_client_unregister(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+
+ /*
+ * A firmware request cannot be cancelled, and its callback uses the
+ * splash and wakes the thread: let it finish first. It is usually over
+ * already; at worst this waits for the firmware fallback timeout.
+ */
+ if (splash->fw_requested)
+ wait_for_completion(&splash->fw_done);
+
+ /* No thread if no output was usable at the first hotplug */
+ if (splash->thread)
+ kthread_stop_put(splash->thread);
+ drm_splash_free_scanout(client);
+ drm_splash_image_cleanup(splash);
+
+ /* Last: it ends up in drm_splash_client_free(), which frees splash */
+ drm_client_release(client);
+}
+
+static void drm_splash_client_free(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ struct drm_device *dev = client->dev;
+
+ mutex_destroy(&splash->hotplug_lock);
+ kfree(splash);
+
+ drm_dbg(dev, "Unregistered with drm splash");
+}
+
+static const struct drm_client_funcs drm_splash_client_funcs = {
+ .owner = THIS_MODULE,
+ .hotplug = drm_splash_client_hotplug,
+ .restore = drm_splash_client_restore,
+ .unregister = drm_splash_client_unregister,
+ .free = drm_splash_client_free,
+};
+
+/**
+ * drm_splash_register() - Register a drm device to drm_splash
+ * @dev: the drm device to register.
+ * @format: drm device preferred format.
+ */
+void drm_splash_register(struct drm_device *dev,
+ const struct drm_format_info *format)
+{
+ struct drm_splash *splash;
+
+ splash = kzalloc_obj(*splash);
+ if (!splash)
+ goto err_warn;
+
+ mutex_init(&splash->hotplug_lock);
+ spin_lock_init(&splash->fw_lock);
+ init_completion(&splash->fw_done);
+
+ if (format && format->num_planes == 1)
+ splash->preferred_format = format->format;
+ else
+ splash->preferred_format = DRM_FORMAT_RGB888;
+
+ if (drm_client_init(dev, &splash->client, "drm_splash",
+ &drm_splash_client_funcs))
+ goto err_free;
+
+ drm_client_register(&splash->client);
+ drm_dbg(dev, "Registered with drm splash");
+
+ return;
+
+err_free:
+ kfree(splash);
+err_warn:
+ drm_warn(dev, "Failed to register with drm splash");
+}
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 04/13] MAINTAINERS: add entry for DRM splash client
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (2 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 03/13] drm: client: add splash client Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 05/13] drm: docs: remove bootsplash from TODO Màxim Pedraza Padilla
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
From: Francesco Valla <francesco@valla.it>
Add myself as maintainer for the DRM splash client.
Signed-off-by: Francesco Valla <francesco@valla.it>
[Màxim: add myself as co-maintainer]
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 4d108339f44e..44730414e5df 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9155,6 +9155,14 @@ F: Documentation/gpu/drm-ras.rst
F: drivers/gpu/drm/drm_ras*
F: include/drm/drm_ras*
+DRM SPLASH
+M: Francesco Valla <francesco@valla.it>
+M: Màxim Pedraza Padilla <maximpedraza@gmail.com>
+L: dri-devel@lists.freedesktop.org
+S: Maintained
+T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F: drivers/gpu/drm/clients/drm_splash.c
+
DRM TTM SUBSYSTEM
M: Natalie Vock <nat@pixelcluster.dev>
M: Arun Pravin <arunpravin.paneerselvam@amd.com>
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 05/13] drm: docs: remove bootsplash from TODO
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (3 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 04/13] MAINTAINERS: add entry for DRM " Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 06/13] dt-bindings: display: add a boot logo node under /chosen Màxim Pedraza Padilla
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
From: Francesco Valla <francesco@valla.it>
Now that a splash client exists, remove the bootsplash task from the
TODO list for the DRM subsystem.
Signed-off-by: Francesco Valla <francesco@valla.it>
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
Documentation/gpu/todo.rst | 17 -----------------
1 file changed, 17 deletions(-)
diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst
index a437ae5c71f4..e77de5e2a24f 100644
--- a/Documentation/gpu/todo.rst
+++ b/Documentation/gpu/todo.rst
@@ -773,23 +773,6 @@ See drivers/gpu/drm/amd/display/TODO for tasks.
Contact: Harry Wentland, Alex Deucher
-Bootsplash
-==========
-
-There is support in place now for writing internal DRM clients making it
-possible to pick up the bootsplash work that was rejected because it was written
-for fbdev.
-
-- [v6,8/8] drm/client: Hack: Add bootsplash example
- https://patchwork.freedesktop.org/patch/306579/
-
-- [RFC PATCH v2 00/13] Kernel based bootsplash
- https://lore.kernel.org/r/20171213194755.3409-1-mstaudt@suse.de
-
-Contact: Sam Ravnborg
-
-Level: Advanced
-
Brightness handling on devices with multiple internal panels
============================================================
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 06/13] dt-bindings: display: add a boot logo node under /chosen
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (4 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 05/13] drm: docs: remove bootsplash from TODO Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 07/13] drm/client: splash: add a device tree image source Màxim Pedraza Padilla
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
Products built on the same board often differ only in branding. On UEFI
systems the firmware hands the operating system its boot logo through
the ACPI BGRT; a device tree system has no such table, so today the logo
is built into the kernel, and each product needs its own kernel image.
With U-Boot's Falcon mode the device tree is the only thing that reaches
the kernel, so it is also the only place the logo can come from.
Add a "boot-logo" node under /chosen. The node lives there because a
logo is configuration handed over by firmware rather than a description
of the hardware, as simple-framebuffer nodes already are.
The image is a BMP with 24 bits per pixel and no compression, the
format of the BGRT image, so that one file serves the firmware splash,
a BMP loaded as firmware and this node alike. It is carried either in
an "image" property, which dtc fills in from the file with /incbin/, or
in a reserved memory region the bootloader loaded it into, named by
"memory-region". Exactly one of the two must be present.
"logo-position" takes -1 on an axis to mean centre on that axis, which
is the only way to say it when the device tree does not know the image
size, and "logo-offset" is added afterwards. "rotation" counts degrees
counter clockwise, as for panels, and turns the image rather than the
screen, so position and offset stay in screen pixels. The optional
"background-color" goes with the image, since a logo designed for one
background looks wrong on another.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
.../bindings/display/boot-logo.yaml | 151 ++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 152 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/boot-logo.yaml
diff --git a/Documentation/devicetree/bindings/display/boot-logo.yaml b/Documentation/devicetree/bindings/display/boot-logo.yaml
new file mode 100644
index 000000000000..b540a04deb78
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/boot-logo.yaml
@@ -0,0 +1,151 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/boot-logo.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Boot logo supplied by the device tree
+
+maintainers:
+ - Francesco Valla <francesco@valla.it>
+ - Màxim Pedraza Padilla <maximpedraza@gmail.com>
+
+description: |
+ An image for the operating system to show on the display while it boots,
+ handed over by the firmware together with where and how to show it.
+
+ Products built on the same board often differ only in branding. Carrying
+ the logo in the device tree lets a single kernel image serve all of them,
+ each booting with its own device tree, and it works where the device tree
+ is the only thing that reaches the kernel, as with U-Boot's Falcon mode.
+ It plays the part the ACPI BGRT plays on UEFI systems.
+
+ Since a logo is configuration rather than a description of the hardware,
+ the node lives under /chosen, next to the other things firmware hands to
+ the operating system.
+
+ The image is a BMP file with a 40 byte BITMAPINFOHEADER, 24 bits per pixel
+ and no compression, the format of the BGRT image. It is carried either in
+ the node itself or in a reserved memory region the bootloader loaded it
+ into, never both.
+
+properties:
+ $nodename:
+ const: logo
+
+ compatible:
+ const: boot-logo
+
+ image:
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+ description:
+ The BMP file itself, byte for byte, which dtc can fill in from the file
+ with /incbin/. The device tree stays in memory for as long as the
+ system runs, so this is meant for small images; larger ones belong in
+ a reserved memory region.
+
+ memory-region:
+ maxItems: 1
+ description: |
+ Reserved memory region the bootloader loaded the BMP file into, starting
+ at the beginning of the region. The BMP header says how much of the
+ region is image.
+
+ This keeps the image out of the device tree, so that it can be changed
+ without rebuilding the device tree, for instance by loading it from a
+ partition of its own that userspace can update.
+
+ Memory keeps its contents across a reset, and may even across a short
+ power cycle, so a bootloader that loads no image into the region has to
+ clear it: the operating system cannot tell a stale image from a fresh
+ one.
+
+ logo-position:
+ $ref: /schemas/types.yaml#/definitions/int32-array
+ description:
+ X and Y coordinates, in screen pixels, of the top left corner of the
+ image once it has been rotated. A value of -1 on an axis centres the
+ image on that axis instead, which is the only way to say it when the
+ device tree does not know the image size, as with a memory region.
+ Defaults to centring on both axes.
+ items:
+ - description: X coordinate, or -1 to centre horizontally
+ minimum: -1
+ maximum: 65535
+ - description: Y coordinate, or -1 to centre vertically
+ minimum: -1
+ maximum: 65535
+
+ logo-offset:
+ $ref: /schemas/types.yaml#/definitions/int32-array
+ description:
+ X and Y displacement, in screen pixels, applied after the image has been
+ placed. Mostly useful together with a centred axis, to land the image
+ somewhere other than the middle of a panel whose visible area is not
+ the middle of the mode.
+ items:
+ - description: X displacement
+ minimum: -65535
+ maximum: 65535
+ - description: Y displacement
+ minimum: -65535
+ maximum: 65535
+
+ rotation:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Rotation applied to the image before it is shown, in degrees counter
+ clockwise, as for the rotation property of panels. It turns the image
+ and not the screen, so a quarter turn swaps how much room the image
+ takes up, but logo-position and logo-offset stay in screen pixels
+ either way.
+ enum: [0, 90, 180, 270]
+ default: 0
+
+ background-color:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Colour of the rest of the screen, as 0xRRGGBB. When absent, the
+ operating system uses its own default.
+ maximum: 0xffffff
+
+required:
+ - compatible
+
+# The image lives either in the node or in a reserved memory region,
+# never both and never neither.
+oneOf:
+ - required:
+ - image
+ - required:
+ - memory-region
+
+additionalProperties: false
+
+examples:
+ - |
+ // A 2x1 image, red and blue, centred and 120 pixels below the middle
+ / {
+ compatible = "foo";
+ model = "foo";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ chosen {
+ logo {
+ compatible = "boot-logo";
+ /* Normally: image = /incbin/("logo.bmp"); */
+ image = /bits/ 8 <0x42 0x4d 0x3e 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x36 0x00 0x00 0x00 0x28 0x00
+ 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00
+ 0x00 0x00 0x01 0x00 0x18 0x00 0x00 0x00
+ 0x00 0x00 0x08 0x00 0x00 0x00 0x13 0x0b
+ 0x00 0x00 0x13 0x0b 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0xff 0xff 0x00 0x00 0x00 0x00>;
+ logo-position = <(-1) (-1)>;
+ logo-offset = <0 120>;
+ background-color = <0x1e1e28>;
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 44730414e5df..9f057fdb3bdb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9161,6 +9161,7 @@ M: Màxim Pedraza Padilla <maximpedraza@gmail.com>
L: dri-devel@lists.freedesktop.org
S: Maintained
T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F: Documentation/devicetree/bindings/display/boot-logo.yaml
F: drivers/gpu/drm/clients/drm_splash.c
DRM TTM SUBSYSTEM
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 07/13] drm/client: splash: add a device tree image source
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (5 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 06/13] dt-bindings: display: add a boot logo node under /chosen Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 08/13] drm/client: splash: place the device tree image where it asks Màxim Pedraza Padilla
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
Embedded products often share one kernel image and one board support
package across several models that differ in branding. The splash
client can show the firmware's BGRT image or a BMP loaded as firmware,
but a device tree system has no BGRT, and a BMP loaded as firmware is
either built into the kernel, which means one kernel per logo, or read
from a filesystem that is not there yet when the display comes up. With
U-Boot's Falcon mode the SPL starts the kernel directly, and the device
tree is the only thing that reaches it.
Add a source that takes the image from a "boot-logo" node under /chosen.
The BMP either sits in the node itself, in an "image" property that dtc
fills in with /incbin/, or in a reserved memory region the bootloader
loaded it into, named by "memory-region". Exactly one of the two must be
present; a node with both or neither is ignored with a warning.
The image in the node is used where it is: the device tree stays in
memory, so there is nothing to copy or release. The region is mapped
like the BGRT image, and the BMP header is checked against its size.
A node is only there if someone put it there for that board, so the
device tree goes first, then the BGRT, then the BMP loaded as firmware.
The boolean that chose between the latter two becomes an enum naming
the one source in use.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 15 +++
drivers/gpu/drm/clients/drm_splash.c | 166 ++++++++++++++++++++++++---
2 files changed, 165 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index cbe271c3a23f..aef3781cc110 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -108,6 +108,21 @@ config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
The color can be overridden through the drm_client_lib.splash_color
kernel command line parameter.
+config DRM_CLIENT_SPLASH_SRC_DT
+ bool "Device tree as splash source"
+ depends on DRM_CLIENT_SPLASH
+ depends on OF
+ select DRM_CLIENT_SPLASH_BMP_SUPPORT
+ help
+ Use a BMP image described by a "boot-logo" node under /chosen in
+ the device tree, either carried in the node itself or placed by the
+ bootloader in a reserved memory region. This lets one kernel image
+ show a different logo on each board or product, chosen by the
+ device tree it boots with.
+
+ When present, the device tree image is preferred over the other
+ sources.
+
config DRM_CLIENT_SPLASH_SRC_BGRT
bool "EFI BGRT as splash source"
depends on DRM_CLIENT_SPLASH
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 66c192139d9c..444f612c48ac 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -9,9 +9,12 @@
#include <linux/efi-bgrt.h>
#include <linux/firmware.h>
#include <linux/init.h>
+#include <linux/io.h>
#include <linux/iosys-map.h>
#include <linux/kthread.h>
#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
#include <linux/overflow.h>
#include <linux/types.h>
#include <linux/unaligned.h>
@@ -71,6 +74,14 @@ struct bmp_dib_header {
__le32 colors_important;
} __packed;
+/* Where the image comes from, in order of preference */
+enum drm_splash_source {
+ DRM_SPLASH_SRC_NONE,
+ DRM_SPLASH_SRC_DT,
+ DRM_SPLASH_SRC_BGRT,
+ DRM_SPLASH_SRC_BMP,
+};
+
struct drm_splash_scanout {
int id;
/* Modesets scanning this buffer out, by position in the list */
@@ -108,7 +119,7 @@ struct drm_splash {
const struct firmware *fw;
void *map_data;
- bool use_bgrt;
+ enum drm_splash_source src;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -204,6 +215,114 @@ static inline int drm_splash_fw_request_bmp(struct drm_splash *splash)
}
#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BMP
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_DT)
+#define DRM_SPLASH_DT_COMPATIBLE "boot-logo"
+
+/*
+ * The boot logo node, if the device tree has a usable one. Only a child of
+ * /chosen is considered, not anything compatible anywhere in the tree: the
+ * image is configuration handed over by the firmware, not a device.
+ */
+static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+ struct device_node *np;
+ bool image, region;
+
+ np = of_get_compatible_child(of_chosen, DRM_SPLASH_DT_COMPATIBLE);
+ if (!np)
+ return NULL;
+
+ if (!of_device_is_available(np))
+ goto out_put;
+
+ image = of_property_present(np, "image");
+ region = of_property_present(np, "memory-region");
+ if (image == region) {
+ drm_warn(client->dev,
+ "splash: %pOF needs exactly one of image or memory-region",
+ np);
+ goto out_put;
+ }
+
+ return np;
+
+out_put:
+ of_node_put(np);
+ return NULL;
+}
+
+static bool drm_splash_dt_available(struct drm_splash *splash)
+{
+ struct device_node *np = drm_splash_dt_node(splash);
+
+ of_node_put(np);
+
+ return np;
+}
+
+static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ struct drm_client_dev *client = &splash->client;
+ struct device_node *np;
+ struct resource res;
+ const void *prop;
+ int len, ret = 0;
+
+ np = drm_splash_dt_node(splash);
+ if (!np)
+ return -ENOENT;
+
+ /*
+ * An image in the node is used in place: the device tree stays in
+ * memory for as long as the system runs, so there is nothing to copy
+ * and nothing to release.
+ */
+ prop = of_get_property(np, "image", &len);
+ if (prop) {
+ *data = prop;
+ *size = len;
+ goto out_put;
+ }
+
+ /* Otherwise the bootloader loaded it into a reserved region */
+ ret = of_reserved_mem_region_to_resource(np, 0, &res);
+ if (ret) {
+ drm_warn(client->dev, "splash: %pOF: no usable memory-region: %d",
+ np, ret);
+ goto out_put;
+ }
+
+ splash->map_data = memremap(res.start, resource_size(&res),
+ MEMREMAP_WB);
+ if (!splash->map_data) {
+ drm_warn(client->dev, "splash: failed to map %pR", &res);
+ ret = -ENOMEM;
+ goto out_put;
+ }
+
+ /* The BMP header, checked against this size, says how much is image */
+ *data = splash->map_data;
+ *size = resource_size(&res);
+
+out_put:
+ of_node_put(np);
+ return ret;
+}
+#else
+static inline bool drm_splash_dt_available(struct drm_splash *splash)
+{
+ return false;
+}
+
+static inline int drm_splash_dt_load(struct drm_splash *splash,
+ const u8 **data, size_t *size)
+{
+ return -EOPNOTSUPP;
+}
+#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_DT
+
#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BGRT)
static bool drm_splash_bgrt_available(struct drm_splash *splash)
{
@@ -510,7 +629,7 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
return -EINVAL;
}
- if (splash->use_bgrt) {
+ if (splash->src == DRM_SPLASH_SRC_BGRT) {
x_pad = drm_splash_bgrt_get_xoffset();
y_pad = drm_splash_bgrt_get_yoffset();
@@ -603,19 +722,34 @@ static inline int drm_splash_bmp_to_scanout(struct drm_splash *splash,
static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
size_t *img_data_len)
{
- int ret = 0;
-
- if (splash->use_bgrt) {
- ret = drm_splash_bgrt_load(splash, img_data, img_data_len);
- if (ret)
- splash->use_bgrt = false;
+ switch (splash->src) {
+ case DRM_SPLASH_SRC_DT:
+ return drm_splash_dt_load(splash, img_data, img_data_len);
+ case DRM_SPLASH_SRC_BGRT:
+ return drm_splash_bgrt_load(splash, img_data, img_data_len);
+ case DRM_SPLASH_SRC_BMP:
+ return drm_splash_fw_load(splash, img_data, img_data_len);
+ default:
+ return -ENOENT;
}
+}
- /* BGRT failed to load */
- if (!splash->use_bgrt)
- ret = drm_splash_fw_load(splash, img_data, img_data_len);
-
- return ret;
+/*
+ * Pick the one source the image will come from. A device tree node is only
+ * there if someone put it there for this board, so it goes first; the BGRT
+ * is whatever the firmware shows, and the BMP loaded as firmware is the
+ * fallback. With none of them, only the background colour is drawn.
+ */
+static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
+{
+ if (drm_splash_dt_available(splash))
+ return DRM_SPLASH_SRC_DT;
+ if (drm_splash_bgrt_available(splash))
+ return DRM_SPLASH_SRC_BGRT;
+ if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
+ return DRM_SPLASH_SRC_BMP;
+
+ return DRM_SPLASH_SRC_NONE;
}
static void drm_splash_image_cleanup(struct drm_splash *splash)
@@ -911,10 +1045,10 @@ static int drm_splash_client_hotplug(struct drm_client_dev *client)
get_task_struct(thread);
splash->thread = thread;
- splash->use_bgrt = drm_splash_bgrt_available(splash);
+ splash->src = drm_splash_pick_source(splash);
- /* If no other image has been loaded, try to load a BMP as firmware */
- if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP) && !splash->use_bgrt) {
+ /* The BMP loaded as firmware is the only source that arrives later */
+ if (splash->src == DRM_SPLASH_SRC_BMP) {
WRITE_ONCE(splash->fw_pending, true);
ret = drm_splash_fw_request_bmp(splash);
splash->fw_requested = !ret;
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 08/13] drm/client: splash: place the device tree image where it asks
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (6 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 07/13] drm/client: splash: add a device tree image source Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 09/13] drm/client: splash: turn the device tree image as " Màxim Pedraza Padilla
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
A logo supplied by the device tree describes a particular product, and
where it lands on the panel matters: some panels only show part of the
mode, so the middle of the mode is not the middle of what the user
sees.
Read the optional "logo-position" and "logo-offset" properties of the
node. The position is the top left corner, with -1 on an axis meaning
centre on that axis, and the offset is added afterwards, which is the
only way to say "below the centre" when the device tree does not know
the image size, as with an image in a reserved region.
Both come straight from the device tree, so the sum is done in 64 bits
and clamped, and a silly pair of values lands the image against an
edge rather than off the screen: the blitters do not clip. Every other
source keeps being centred, except the BGRT, which keeps its own
offsets.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/drm_splash.c | 73 ++++++++++++++++++++++++----
1 file changed, 63 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 444f612c48ac..349f68d392e5 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -120,6 +120,14 @@ struct drm_splash {
void *map_data;
enum drm_splash_source src;
+
+ /*
+ * Where the image goes, in screen pixels: a coordinate of -1 centres
+ * it on that axis, and the offset is added afterwards. Only the device
+ * tree asks for anything but the centre.
+ */
+ s32 pos_x, pos_y;
+ s32 off_x, off_y;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -252,13 +260,21 @@ static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
return NULL;
}
-static bool drm_splash_dt_available(struct drm_splash *splash)
+/* The optional placement properties of the node, read once */
+static void drm_splash_dt_placement(struct drm_splash *splash,
+ struct device_node *np)
{
- struct device_node *np = drm_splash_dt_node(splash);
+ u32 val[2];
- of_node_put(np);
+ if (!of_property_read_u32_array(np, "logo-position", val, 2)) {
+ splash->pos_x = (s32)val[0];
+ splash->pos_y = (s32)val[1];
+ }
- return np;
+ if (!of_property_read_u32_array(np, "logo-offset", val, 2)) {
+ splash->off_x = (s32)val[0];
+ splash->off_y = (s32)val[1];
+ }
}
static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
@@ -311,9 +327,14 @@ static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
return ret;
}
#else
-static inline bool drm_splash_dt_available(struct drm_splash *splash)
+static inline struct device_node *drm_splash_dt_node(struct drm_splash *splash)
+{
+ return NULL;
+}
+
+static inline void drm_splash_dt_placement(struct drm_splash *splash,
+ struct device_node *np)
{
- return false;
}
static inline int drm_splash_dt_load(struct drm_splash *splash,
@@ -459,6 +480,26 @@ static int drm_splash_fill_solid_color(struct drm_client_buffer *buffer,
}
#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_BMP_SUPPORT)
+/*
+ * Place the image along one axis of the screen. @pos is the coordinate asked
+ * for, or -1 to centre on that axis, and @off is added afterwards. Both may
+ * come from the device tree, so the sum is done wide enough not to wrap, and
+ * the result keeps the whole image on the screen: the blitters do not clip.
+ */
+static unsigned int drm_splash_place_axis(s32 pos, s32 off, unsigned int span,
+ unsigned int size)
+{
+ s64 last = (s64)span - size;
+ s64 coord;
+
+ if (last <= 0)
+ return 0;
+
+ coord = pos == -1 ? last / 2 : pos;
+
+ return clamp_t(s64, coord + off, 0, last);
+}
+
static void drm_splash_blit_pix16(struct iosys_map *map, unsigned int dpitch,
unsigned int x_pad, unsigned int y_pad,
const u8 *sbuf8, unsigned int spitch,
@@ -657,9 +698,10 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
}
#endif
} else {
- /* Center X and Y */
- x_pad = (scanout->width - bmp_width) / 2;
- y_pad = (scanout->height - bmp_height) / 2;
+ x_pad = drm_splash_place_axis(splash->pos_x, splash->off_x,
+ scanout->width, bmp_width);
+ y_pad = drm_splash_place_axis(splash->pos_y, splash->off_y,
+ scanout->height, bmp_height);
}
image_data = data + bitmap_offset;
@@ -742,8 +784,15 @@ static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
*/
static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
{
- if (drm_splash_dt_available(splash))
+ struct device_node *np;
+
+ np = drm_splash_dt_node(splash);
+ if (np) {
+ drm_splash_dt_placement(splash, np);
+ of_node_put(np);
return DRM_SPLASH_SRC_DT;
+ }
+
if (drm_splash_bgrt_available(splash))
return DRM_SPLASH_SRC_BGRT;
if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
@@ -1141,6 +1190,10 @@ void drm_splash_register(struct drm_device *dev,
spin_lock_init(&splash->fw_lock);
init_completion(&splash->fw_done);
+ /* Centred unless the image source asks otherwise */
+ splash->pos_x = -1;
+ splash->pos_y = -1;
+
if (format && format->num_planes == 1)
splash->preferred_format = format->format;
else
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 09/13] drm/client: splash: turn the device tree image as it asks
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (7 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 08/13] drm/client: splash: place the device tree image where it asks Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 10/13] drm/client: splash: take the background colour from the image source Màxim Pedraza Padilla
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
The same board is often fitted with the same panel mounted in more than
one orientation. Let the device tree node ask for the image to be turned,
with a "rotation" property counting degrees counter clockwise, as panels
already do and as DRM_MODE_ROTATE_* count, which is what it is kept as.
The rotation turns the image and not the screen: "logo-position" and
"logo-offset" stay in screen pixels, and a quarter turn only swaps how
much room the image takes up. Placing it in a frame that turned with it
would make the same pair of coordinates mean different places on the
same panel, decided by a property that describes the image.
The three blitters, one per destination pixel size, become one that
reads the source pixel through the rotation and writes it according to
the pixel size. Each source pixel is read as three bytes rather than as
an unaligned u32: a BMP row is only padded as a whole, so with 3 * width
already a multiple of four the last pixel of the image was read one byte
past its end. An upright image going to an RGB888 buffer is still copied
a row at a time.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/drm_splash.c | 206 ++++++++++++++-------------
1 file changed, 110 insertions(+), 96 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 349f68d392e5..924bc0032d1d 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -25,6 +25,7 @@
#include <drm/drm_drv.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
+#include <drm/drm_mode.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.h>
@@ -128,6 +129,8 @@ struct drm_splash {
*/
s32 pos_x, pos_y;
s32 off_x, off_y;
+ /* One of DRM_MODE_ROTATE_*: turns the image, not the screen */
+ unsigned int rotation;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -275,6 +278,28 @@ static void drm_splash_dt_placement(struct drm_splash *splash,
splash->off_x = (s32)val[0];
splash->off_y = (s32)val[1];
}
+
+ /* Degrees counter clockwise, as for panels, and as DRM counts them */
+ if (!of_property_read_u32(np, "rotation", &val[0])) {
+ switch (val[0]) {
+ case 0:
+ splash->rotation = DRM_MODE_ROTATE_0;
+ break;
+ case 90:
+ splash->rotation = DRM_MODE_ROTATE_90;
+ break;
+ case 180:
+ splash->rotation = DRM_MODE_ROTATE_180;
+ break;
+ case 270:
+ splash->rotation = DRM_MODE_ROTATE_270;
+ break;
+ default:
+ drm_warn(splash->client.dev,
+ "splash: %pOF: unsupported rotation %u",
+ np, val[0]);
+ }
+ }
}
static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
@@ -500,73 +525,68 @@ static unsigned int drm_splash_place_axis(s32 pos, s32 off, unsigned int span,
return clamp_t(s64, coord + off, 0, last);
}
-static void drm_splash_blit_pix16(struct iosys_map *map, unsigned int dpitch,
- unsigned int x_pad, unsigned int y_pad,
- const u8 *sbuf8, unsigned int spitch,
- unsigned int width, unsigned int height,
- bool invert_y, u32 format)
-{
- unsigned int x, y, src_offset, dst_offset;
- u32 scolor, dcolor, wr_off;
-
- for (y = 0; y < height; y++) {
- src_offset = (invert_y ? (height - y - 1) : y) * spitch;
- dst_offset = (y_pad + y) * dpitch;
-
- for (x = 0; x < width; x++) {
- scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
- dcolor = drm_draw_color_from_xrgb8888(scolor, format);
- wr_off = dst_offset + (x_pad + x) * sizeof(u16);
-
- iosys_map_wr(map, wr_off, u16, dcolor);
- }
- }
-}
-
-static void drm_splash_blit_pix24(struct iosys_map *map, unsigned int dpitch,
- unsigned int x_pad, unsigned int y_pad,
- const u8 *sbuf8, unsigned int spitch,
- unsigned int width, unsigned int height,
- bool invert_y, u32 format)
-{
- unsigned int x, y, src_offset, dst_offset;
- u32 scolor, dcolor, wr_off;
-
- for (y = 0; y < height; y++) {
- src_offset = (invert_y ? (height - y - 1) : y) * spitch;
- dst_offset = (y_pad + y) * dpitch;
-
- for (x = 0; x < width; x++) {
- scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
- dcolor = drm_draw_color_from_xrgb8888(scolor, format);
- wr_off = dst_offset + (x_pad + x) * 3;
-
- iosys_map_wr(map, wr_off, u8, (dcolor & 0x000000FF) >> 0);
- iosys_map_wr(map, wr_off + 1, u8, (dcolor & 0x0000FF00) >> 8);
- iosys_map_wr(map, wr_off + 2, u8, (dcolor & 0x00FF0000) >> 16);
- }
- }
-}
-
-static void drm_splash_blit_pix32(struct iosys_map *map, unsigned int dpitch,
- unsigned int x_pad, unsigned int y_pad,
- const u8 *sbuf8, unsigned int spitch,
- unsigned int width, unsigned int height,
- bool invert_y, u32 format)
+/*
+ * Copy the BMP pixels into the buffer, converted to its format and turned by
+ * @rotation, one of DRM_MODE_ROTATE_*, which count counter clockwise. The
+ * rotation turns the image, not the screen: a quarter turn swaps how much
+ * room the image takes up, and @x_pad and @y_pad place that room on screen.
+ *
+ * Each source pixel is three bytes read on their own, since a BMP row is
+ * only padded to four bytes as a whole and the image need not be aligned.
+ */
+static void drm_splash_blit(struct iosys_map *map, unsigned int dpitch,
+ u32 format, unsigned int cpp,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y, unsigned int rotation)
{
- unsigned int x, y, src_offset, dst_offset;
- u32 scolor, dcolor, wr_off;
-
- for (y = 0; y < height; y++) {
- src_offset = (invert_y ? (height - y - 1) : y) * spitch;
- dst_offset = (y_pad + y) * dpitch;
+ bool quarter = rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270);
+ unsigned int dw = quarter ? height : width;
+ unsigned int dh = quarter ? width : height;
+ unsigned int x, y, sx, sy, row, off;
+ u32 scolor, dcolor;
+
+ for (y = 0; y < dh; y++) {
+ for (x = 0; x < dw; x++) {
+ switch (rotation) {
+ case DRM_MODE_ROTATE_90:
+ sx = width - 1 - y;
+ sy = x;
+ break;
+ case DRM_MODE_ROTATE_180:
+ sx = width - 1 - x;
+ sy = height - 1 - y;
+ break;
+ case DRM_MODE_ROTATE_270:
+ sx = y;
+ sy = height - 1 - x;
+ break;
+ default:
+ sx = x;
+ sy = y;
+ break;
+ }
- for (x = 0; x < width; x++) {
- scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
+ /* A positive BMP height stores the rows bottom up */
+ row = invert_y ? height - 1 - sy : sy;
+ scolor = get_unaligned_le24(&sbuf8[row * spitch + 3 * sx]);
dcolor = drm_draw_color_from_xrgb8888(scolor, format);
- wr_off = dst_offset + (x_pad + x) * sizeof(u32);
+ off = (y_pad + y) * dpitch + (x_pad + x) * cpp;
- iosys_map_wr(map, wr_off, u32, dcolor);
+ switch (cpp) {
+ case 2:
+ iosys_map_wr(map, off, u16, dcolor);
+ break;
+ case 3:
+ iosys_map_wr(map, off, u8, dcolor & 0xff);
+ iosys_map_wr(map, off + 1, u8, (dcolor >> 8) & 0xff);
+ iosys_map_wr(map, off + 2, u8, (dcolor >> 16) & 0xff);
+ break;
+ case 4:
+ iosys_map_wr(map, off, u32, dcolor);
+ break;
+ }
}
}
}
@@ -605,7 +625,7 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
bool bmp_invert_y;
u32 bitmap_offset;
size_t row_len, pixels_len, end;
- unsigned int x_pad, y_pad;
+ unsigned int x_pad, y_pad, img_w, img_h;
const u8 *image_data;
struct iosys_map map;
struct drm_rect r;
@@ -665,7 +685,16 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
}
bmp_pitch = row_len & ~(size_t)3;
- if (bmp_width > scanout->width || bmp_height > scanout->height) {
+ /* The room the image takes up on screen, once turned */
+ if (splash->rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270)) {
+ img_w = bmp_height;
+ img_h = bmp_width;
+ } else {
+ img_w = bmp_width;
+ img_h = bmp_height;
+ }
+
+ if (img_w > scanout->width || img_h > scanout->height) {
drm_err(client->dev, "splash: BMP image is too big for the screen");
return -EINVAL;
}
@@ -699,9 +728,9 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
#endif
} else {
x_pad = drm_splash_place_axis(splash->pos_x, splash->off_x,
- scanout->width, bmp_width);
+ scanout->width, img_w);
y_pad = drm_splash_place_axis(splash->pos_y, splash->off_y,
- scanout->height, bmp_height);
+ scanout->height, img_h);
}
image_data = data + bitmap_offset;
@@ -712,40 +741,24 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
return ret;
}
- r = DRM_RECT_INIT(x_pad, y_pad, bmp_width, bmp_height);
+ r = DRM_RECT_INIT(x_pad, y_pad, img_w, img_h);
- /* In case the target format is RGB888, source data can be copied to
- * the video buffer line by line, avoiding some overhead.
+ /*
+ * An unturned image going to an RGB888 buffer has the same byte order
+ * as the BMP, so it is copied a row at a time.
*/
- if (scanout->format == DRM_FORMAT_RGB888) {
+ if (scanout->format == DRM_FORMAT_RGB888 &&
+ splash->rotation == DRM_MODE_ROTATE_0) {
drm_splash_blit_rgb888(&map, fb->pitches[0], x_pad, y_pad,
image_data, bmp_pitch, bmp_width,
bmp_height, bmp_invert_y);
+ } else if (px_width >= 2 && px_width <= 4) {
+ drm_splash_blit(&map, fb->pitches[0], scanout->format, px_width,
+ x_pad, y_pad, image_data, bmp_pitch, bmp_width,
+ bmp_height, bmp_invert_y, splash->rotation);
} else {
- switch (px_width) {
- case 2:
- drm_splash_blit_pix16(&map, fb->pitches[0], x_pad,
- y_pad, image_data, bmp_pitch,
- bmp_width, bmp_height,
- bmp_invert_y, scanout->format);
- break;
- case 3:
- drm_splash_blit_pix24(&map, fb->pitches[0], x_pad,
- y_pad, image_data, bmp_pitch,
- bmp_width, bmp_height,
- bmp_invert_y, scanout->format);
- break;
- case 4:
- drm_splash_blit_pix32(&map, fb->pitches[0], x_pad,
- y_pad, image_data, bmp_pitch,
- bmp_width, bmp_height,
- bmp_invert_y, scanout->format);
- break;
- default:
- drm_warn_once(client->dev,
- "splash: can't blit with pixel width %d",
- px_width);
- }
+ drm_warn_once(client->dev, "splash: can't blit with pixel width %d",
+ px_width);
}
drm_client_buffer_vunmap_local(buffer);
@@ -1190,9 +1203,10 @@ void drm_splash_register(struct drm_device *dev,
spin_lock_init(&splash->fw_lock);
init_completion(&splash->fw_done);
- /* Centred unless the image source asks otherwise */
+ /* Centred and upright unless the image source asks otherwise */
splash->pos_x = -1;
splash->pos_y = -1;
+ splash->rotation = DRM_MODE_ROTATE_0;
if (format && format->num_planes == 1)
splash->preferred_format = format->format;
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 10/13] drm/client: splash: take the background colour from the image source
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (8 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 09/13] drm/client: splash: turn the device tree image as " Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 11/13] drm/client: splash: turn the BGRT image on panels mounted turned Màxim Pedraza Padilla
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
A logo designed for a light background looks wrong framed in black, and
products that share a board support package differ in their colours as
much as in their logos. The background colour belongs with the image,
so take it from the same place:
- a device tree image uses the optional "background-color" of its node;
- the BGRT carries no colour, and firmware splashes sit on black, so it
uses the kernel's default;
- the BMP loaded as firmware is named on the command line, and so is its
colour, through drm_client_lib.splash_color, which also covers the
screen when there is no image at all.
CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR remains the default wherever
the source says nothing. The command line no longer overrides a device
tree image: a bootloader that wants another colour for a given boot,
say to flag a failed test on a production line, changes the node.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 7 ++++--
drivers/gpu/drm/clients/drm_splash.c | 36 +++++++++++++++++++++-------
2 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index aef3781cc110..f471019bab87 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -105,8 +105,11 @@ config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
help
The default splash background color, in RGB888 format.
- The color can be overridden through the drm_client_lib.splash_color
- kernel command line parameter.
+ The background color comes from the same place as the image. A
+ device tree image can carry its own, and the drm_client_lib.splash_color
+ kernel command line parameter sets it for a BMP loaded as firmware,
+ or for the whole screen when there is no image. This color is used
+ otherwise, and always with the BGRT image, which carries none.
config DRM_CLIENT_SPLASH_SRC_DT
bool "Device tree as splash source"
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 924bc0032d1d..30ba6573424e 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -42,7 +42,8 @@
static unsigned int splash_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
module_param(splash_color, uint, 0400);
-MODULE_PARM_DESC(splash_color, "Splash background color (RGB888)");
+MODULE_PARM_DESC(splash_color,
+ "Background color (RGB888) around the BMP loaded as firmware, or of the whole screen with no image");
#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP)
#define DEFAULT_SPLASH_BMP "drm_splash.bmp"
@@ -131,6 +132,8 @@ struct drm_splash {
s32 off_x, off_y;
/* One of DRM_MODE_ROTATE_*: turns the image, not the screen */
unsigned int rotation;
+ /* RGB888, from the same place as the image */
+ u32 bg_color;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -263,12 +266,18 @@ static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
return NULL;
}
-/* The optional placement properties of the node, read once */
-static void drm_splash_dt_placement(struct drm_splash *splash,
- struct device_node *np)
+/* The optional properties of the node, read once */
+static void drm_splash_dt_props(struct drm_splash *splash,
+ struct device_node *np)
{
u32 val[2];
+ /* The colour travels with the image, or it is the kernel's default */
+ if (!of_property_read_u32(np, "background-color", &val[0]))
+ splash->bg_color = val[0] & 0xffffff;
+ else
+ splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+
if (!of_property_read_u32_array(np, "logo-position", val, 2)) {
splash->pos_x = (s32)val[0];
splash->pos_y = (s32)val[1];
@@ -357,8 +366,8 @@ static inline struct device_node *drm_splash_dt_node(struct drm_splash *splash)
return NULL;
}
-static inline void drm_splash_dt_placement(struct drm_splash *splash,
- struct device_node *np)
+static inline void drm_splash_dt_props(struct drm_splash *splash,
+ struct device_node *np)
{
}
@@ -801,13 +810,22 @@ static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
np = drm_splash_dt_node(splash);
if (np) {
- drm_splash_dt_placement(splash, np);
+ drm_splash_dt_props(splash, np);
of_node_put(np);
return DRM_SPLASH_SRC_DT;
}
- if (drm_splash_bgrt_available(splash))
+ /* The BGRT carries no colour: firmware splashes sit on black */
+ if (drm_splash_bgrt_available(splash)) {
+ splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
return DRM_SPLASH_SRC_BGRT;
+ }
+
+ /*
+ * The BMP loaded as firmware is named on the command line, and so is
+ * its colour, which also covers the screen when there is no image.
+ */
+ splash->bg_color = splash_color & 0xffffff;
if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
return DRM_SPLASH_SRC_BMP;
@@ -865,7 +883,7 @@ static int drm_splash_render_thread(void *data)
if (!scanout->bg_drawn) {
drm_dbg(client->dev, "draw background for scanout %d", i);
- u32 color = drm_draw_color_from_xrgb8888(splash_color,
+ u32 color = drm_draw_color_from_xrgb8888(splash->bg_color,
scanout->format);
drm_splash_fill_solid_color(scanout->buffer, color);
scanout->bg_drawn = true;
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 11/13] drm/client: splash: turn the BGRT image on panels mounted turned
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (9 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 10/13] drm/client: splash: take the background colour from the image source Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 12/13] drm/client: splash: prefer what the command line asks for Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 13/13] drm/client: splash: document the image sources and parameters Màxim Pedraza Padilla
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
Since ACPI 6.2, bits 1-2 of the BGRT status tell that the firmware
image is not pre-rotated and how far it has to be turned to show
upright on a panel mounted turned. The client skipped such images
altogether, so tablets and 2-in-1s with a portrait panel got no splash.
Turn the image as the bits say. The values count 90 degree steps
counter clockwise, as Plymouth, the main user of the BGRT, applies
them, and as DRM_MODE_ROTATE_* count, which is what they become.
The offsets need more care. For such an image the firmware gives them
on the upright screen, while the client draws into the buffer as it is
scanned out, which on a panel mounted turned is the upright screen
turned the other way. Check them and apply the efifb sanity check in
the upright frame, then move the image's rectangle into the buffer's
frame. An image without the bits is drawn at its offsets as before:
the buffer is the frame the firmware drew it in, so a pre-rotated image
needs none of the guessing userspace has to do for it.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/drm_splash.c | 77 ++++++++++++++++++++++++----
1 file changed, 66 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 30ba6573424e..34f3c3910be1 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -388,14 +388,14 @@ static bool drm_splash_bgrt_available(struct drm_splash *splash)
return false;
}
- if (bgrt_tab.status & ACPI_BGRT_ORIENTATION_OFFSET) {
- drm_info(client->dev, "splash: BGRT rotation bits set, skipping");
- return false;
- }
-
return true;
}
+static inline u8 drm_splash_bgrt_get_status(void)
+{
+ return bgrt_tab.status;
+}
+
static inline unsigned int drm_splash_bgrt_get_xoffset(void)
{
return bgrt_tab.image_offset_x;
@@ -445,6 +445,11 @@ static inline unsigned int drm_splash_bgrt_get_yoffset(void)
return 0;
}
+static inline u8 drm_splash_bgrt_get_status(void)
+{
+ return 0;
+}
+
static inline int drm_splash_bgrt_load(struct drm_splash *splash,
const u8 **data, size_t *size)
{
@@ -452,6 +457,26 @@ static inline int drm_splash_bgrt_load(struct drm_splash *splash,
}
#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BGRT
+/*
+ * Since ACPI 6.2, bits 1-2 of the BGRT status say that the image is not
+ * pre-rotated for a panel mounted turned, and by how much it has to be
+ * turned to show upright on it: 0, 90, 180 or 270 degrees, which firmware
+ * and userspace (Plymouth) apply counter clockwise, as DRM counts them.
+ */
+static unsigned int drm_splash_bgrt_rotation(void)
+{
+ switch ((drm_splash_bgrt_get_status() & ACPI_BGRT_ORIENTATION_OFFSET) >> 1) {
+ case 1:
+ return DRM_MODE_ROTATE_90;
+ case 2:
+ return DRM_MODE_ROTATE_180;
+ case 3:
+ return DRM_MODE_ROTATE_270;
+ default:
+ return DRM_MODE_ROTATE_0;
+ }
+}
+
static u32 drm_splash_find_usable_format(struct drm_plane *plane,
u32 preferred_format)
{
@@ -709,11 +734,21 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
}
if (splash->src == DRM_SPLASH_SRC_BGRT) {
- x_pad = drm_splash_bgrt_get_xoffset();
- y_pad = drm_splash_bgrt_get_yoffset();
-
- if ((x_pad + bmp_width) > scanout->width ||
- (y_pad + bmp_height) > scanout->height) {
+ /*
+ * The BGRT offsets place the upright image on the upright
+ * screen, which for a panel mounted turned is not the frame
+ * the buffer is scanned out in. Work in that frame, then move
+ * the image's rectangle into the buffer's.
+ */
+ bool quarter = splash->rotation &
+ (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270);
+ unsigned int lw = quarter ? scanout->height : scanout->width;
+ unsigned int lh = quarter ? scanout->width : scanout->height;
+ unsigned int xo = drm_splash_bgrt_get_xoffset();
+ unsigned int yo = drm_splash_bgrt_get_yoffset();
+
+ if (xo > lw || bmp_width > lw - xo ||
+ yo > lh || bmp_height > lh - yo) {
drm_err(client->dev, "splash: BGRT image would overflow");
return -EINVAL;
}
@@ -730,11 +765,30 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
* the display looking quite bad.
*/
- if (x_pad != (scanout->width - bmp_width) / 2) {
+ if (xo != (lw - bmp_width) / 2) {
drm_err(client->dev, "splash: BGRT sanity check failed");
return -EINVAL;
}
#endif
+
+ switch (splash->rotation) {
+ case DRM_MODE_ROTATE_90:
+ x_pad = yo;
+ y_pad = lw - xo - bmp_width;
+ break;
+ case DRM_MODE_ROTATE_180:
+ x_pad = lw - xo - bmp_width;
+ y_pad = lh - yo - bmp_height;
+ break;
+ case DRM_MODE_ROTATE_270:
+ x_pad = lh - yo - bmp_height;
+ y_pad = xo;
+ break;
+ default:
+ x_pad = xo;
+ y_pad = yo;
+ break;
+ }
} else {
x_pad = drm_splash_place_axis(splash->pos_x, splash->off_x,
scanout->width, img_w);
@@ -818,6 +872,7 @@ static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
/* The BGRT carries no colour: firmware splashes sit on black */
if (drm_splash_bgrt_available(splash)) {
splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+ splash->rotation = drm_splash_bgrt_rotation();
return DRM_SPLASH_SRC_BGRT;
}
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 12/13] drm/client: splash: prefer what the command line asks for
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (10 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 11/13] drm/client: splash: turn the BGRT image on panels mounted turned Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
2026-10-01 19:58 ` [PATCH RFC v4 13/13] drm/client: splash: document the image sources and parameters Màxim Pedraza Padilla
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
A logo or a colour given on the kernel command line was asked for this
very boot: a bootloader that picks the product variant from an EEPROM
and passes it along, a developer, or a production line that turns the
screen red when a test fails. The device tree, the BGRT and the kernel's
defaults describe what the board or the build normally wants. Let the
command line win, as console= wins over stdout-path.
Each parameter wins for what it says, and only when it is given, so
none has a default of its own:
- drm_client_lib.splash_bmp names a BMP to load as firmware, preferred
over the device tree and the BGRT. Without it the order is unchanged:
device tree, BGRT, then "drm_splash.bmp" loaded as firmware. An empty
value counts as not given. A file that is named but missing gives no
image rather than falling back, since it is what was asked for.
- drm_client_lib.splash_bmp=none asks for no image at all: only the
background is drawn, whatever the other sources have.
- drm_client_lib.splash_pos=X,Y, splash_offset=DX,DY and
splash_rotation=0|90|180|270 place and turn the image with the same
meaning as the device tree properties, whatever the image's source.
A BMP loaded as firmware has no other way to be placed or turned, and
for the others they let a placement be tried without rebuilding a
device tree. Positions take both values or are ignored with a
warning. When they place a BGRT image, it is placed in screen pixels
like any other, and neither the table offsets nor the efifb sanity
check apply.
- drm_client_lib.splash_color sets the background whatever the image.
Without it the colour comes from the source that supplied the image,
a device tree node's "background-color", and otherwise from
CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR. A device tree colour is not
used around an image named on the command line: it belongs to another
image.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 20 ++--
drivers/gpu/drm/clients/drm_splash.c | 171 +++++++++++++++++++++------
2 files changed, 142 insertions(+), 49 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index f471019bab87..2a374bea370b 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -105,11 +105,9 @@ config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
help
The default splash background color, in RGB888 format.
- The background color comes from the same place as the image. A
- device tree image can carry its own, and the drm_client_lib.splash_color
- kernel command line parameter sets it for a BMP loaded as firmware,
- or for the whole screen when there is no image. This color is used
- otherwise, and always with the BGRT image, which carries none.
+ The drm_client_lib.splash_color kernel command line parameter, when
+ given, is used whatever the image. Otherwise a device tree image can
+ carry its own color, and this one is used for everything else.
config DRM_CLIENT_SPLASH_SRC_DT
bool "Device tree as splash source"
@@ -123,8 +121,9 @@ config DRM_CLIENT_SPLASH_SRC_DT
show a different logo on each board or product, chosen by the
device tree it boots with.
- When present, the device tree image is preferred over the other
- sources.
+ When present, the device tree image is preferred over the BGRT and
+ over the default BMP loaded as firmware. A BMP named on the kernel
+ command line is preferred over it.
config DRM_CLIENT_SPLASH_SRC_BGRT
bool "EFI BGRT as splash source"
@@ -150,9 +149,10 @@ config DRM_CLIENT_SPLASH_SRC_BMP
The image will be loaded using the firmware loading facility the
kernel provides; it shall use 24 bits per pixel and shall not be
- compressed. The name of the file can be set through the
- drm_client_lib.splash_bmp command line parameter, with the default
- being 'drm_splash.bmp'.
+ compressed. A file named through the drm_client_lib.splash_bmp
+ command line parameter is preferred over any other image source;
+ otherwise 'drm_splash.bmp' is used when neither the device tree nor
+ the BGRT provides an image.
config DRM_CLIENT_SPLASH_BMP_SUPPORT
bool
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 34f3c3910be1..8d056edfee56 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -40,18 +40,90 @@
* a static image.
*/
-static unsigned int splash_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
-module_param(splash_color, uint, 0400);
+/*
+ * What is given on the command line wins over the device tree and the
+ * firmware tables, and each parameter only for what it says. Neither has a
+ * value of its own until it is given: -1 and NULL mean "not given".
+ */
+static int splash_color = -1;
+module_param(splash_color, int, 0400);
MODULE_PARM_DESC(splash_color,
- "Background color (RGB888) around the BMP loaded as firmware, or of the whole screen with no image");
+ "Background color as 0xRRGGBB, preferred over any other");
+
+static int splash_pos[2];
+static unsigned int splash_pos_n;
+module_param_array(splash_pos, int, &splash_pos_n, 0400);
+MODULE_PARM_DESC(splash_pos,
+ "Image position as X,Y in screen pixels, -1 centres that axis");
+
+static int splash_offset[2];
+static unsigned int splash_offset_n;
+module_param_array(splash_offset, int, &splash_offset_n, 0400);
+MODULE_PARM_DESC(splash_offset,
+ "Displacement as DX,DY in screen pixels, added after placing");
+
+static int splash_rotation = -1;
+module_param(splash_rotation, int, 0400);
+MODULE_PARM_DESC(splash_rotation,
+ "Image rotation in degrees counter clockwise: 0, 90, 180 or 270");
#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP)
#define DEFAULT_SPLASH_BMP "drm_splash.bmp"
-static char *splash_bmp = DEFAULT_SPLASH_BMP;
+static char *splash_bmp;
module_param(splash_bmp, charp, 0400);
-MODULE_PARM_DESC(splash_bmp, "Name of splash image (default: \"" DEFAULT_SPLASH_BMP "\")");
+MODULE_PARM_DESC(splash_bmp,
+ "BMP to load as firmware, preferred over any other image");
+
+/* Given on the command line, and not empty */
+static bool drm_splash_bmp_given(void)
+{
+ return splash_bmp && *splash_bmp;
+}
+
+/* "none" asks for no image at all, whatever the other sources have */
+static bool drm_splash_bmp_none(void)
+{
+ return drm_splash_bmp_given() && !strcmp(splash_bmp, "none");
+}
+
+static const char *drm_splash_bmp_name(void)
+{
+ return drm_splash_bmp_given() ? splash_bmp : DEFAULT_SPLASH_BMP;
+}
+#else
+static inline bool drm_splash_bmp_given(void)
+{
+ return false;
+}
+
+static inline bool drm_splash_bmp_none(void)
+{
+ return false;
+}
#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BMP
+/* Degrees counter clockwise, as for panels, and as DRM counts them */
+static bool drm_splash_rotation_from_degrees(u32 degrees,
+ unsigned int *rotation)
+{
+ switch (degrees) {
+ case 0:
+ *rotation = DRM_MODE_ROTATE_0;
+ return true;
+ case 90:
+ *rotation = DRM_MODE_ROTATE_90;
+ return true;
+ case 180:
+ *rotation = DRM_MODE_ROTATE_180;
+ return true;
+ case 270:
+ *rotation = DRM_MODE_ROTATE_270;
+ return true;
+ }
+
+ return false;
+}
+
#define BMP_FILE_MAGIC_ID 0x4d42
/* BMP header structures copied from drivers/video/fbdev/efifb.c */
@@ -132,6 +204,8 @@ struct drm_splash {
s32 off_x, off_y;
/* One of DRM_MODE_ROTATE_*: turns the image, not the screen */
unsigned int rotation;
+ /* Placed from the command line, in screen pixels, whatever the source */
+ bool place_cmdline;
/* RGB888, from the same place as the image */
u32 bg_color;
};
@@ -210,10 +284,11 @@ static int drm_splash_fw_request_bmp(struct drm_splash *splash)
{
struct drm_client_dev *client = &splash->client;
- drm_info(client->dev, "splash: request %s as firmware", splash_bmp);
+ drm_info(client->dev, "splash: request %s as firmware",
+ drm_splash_bmp_name());
return request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
- splash_bmp, client->dev->dev, GFP_KERNEL,
+ drm_splash_bmp_name(), client->dev->dev, GFP_KERNEL,
splash, drm_splash_fw_callback);
}
#else
@@ -272,11 +347,9 @@ static void drm_splash_dt_props(struct drm_splash *splash,
{
u32 val[2];
- /* The colour travels with the image, or it is the kernel's default */
+ /* The colour travels with the image */
if (!of_property_read_u32(np, "background-color", &val[0]))
splash->bg_color = val[0] & 0xffffff;
- else
- splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
if (!of_property_read_u32_array(np, "logo-position", val, 2)) {
splash->pos_x = (s32)val[0];
@@ -288,27 +361,10 @@ static void drm_splash_dt_props(struct drm_splash *splash,
splash->off_y = (s32)val[1];
}
- /* Degrees counter clockwise, as for panels, and as DRM counts them */
- if (!of_property_read_u32(np, "rotation", &val[0])) {
- switch (val[0]) {
- case 0:
- splash->rotation = DRM_MODE_ROTATE_0;
- break;
- case 90:
- splash->rotation = DRM_MODE_ROTATE_90;
- break;
- case 180:
- splash->rotation = DRM_MODE_ROTATE_180;
- break;
- case 270:
- splash->rotation = DRM_MODE_ROTATE_270;
- break;
- default:
- drm_warn(splash->client.dev,
- "splash: %pOF: unsupported rotation %u",
- np, val[0]);
- }
- }
+ if (!of_property_read_u32(np, "rotation", &val[0]) &&
+ !drm_splash_rotation_from_degrees(val[0], &splash->rotation))
+ drm_warn(splash->client.dev,
+ "splash: %pOF: unsupported rotation %u", np, val[0]);
}
static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
@@ -733,7 +789,7 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
return -EINVAL;
}
- if (splash->src == DRM_SPLASH_SRC_BGRT) {
+ if (splash->src == DRM_SPLASH_SRC_BGRT && !splash->place_cmdline) {
/*
* The BGRT offsets place the upright image on the upright
* screen, which for a panel mounted turned is not the frame
@@ -858,10 +914,53 @@ static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
* is whatever the firmware shows, and the BMP loaded as firmware is the
* fallback. With none of them, only the background colour is drawn.
*/
+/*
+ * What the command line gives wins whatever the image and its source, each
+ * parameter for what it says. Positions come in pairs: half of one is
+ * ignored rather than guessed.
+ */
+static void drm_splash_cmdline_overrides(struct drm_splash *splash)
+{
+ struct drm_device *dev = splash->client.dev;
+
+ if (splash_color >= 0)
+ splash->bg_color = splash_color & 0xffffff;
+
+ if (splash_pos_n == 2) {
+ splash->pos_x = splash_pos[0];
+ splash->pos_y = splash_pos[1];
+ splash->place_cmdline = true;
+ } else if (splash_pos_n) {
+ drm_warn(dev, "splash: splash_pos needs X,Y, ignored");
+ }
+
+ if (splash_offset_n == 2) {
+ splash->off_x = splash_offset[0];
+ splash->off_y = splash_offset[1];
+ splash->place_cmdline = true;
+ } else if (splash_offset_n) {
+ drm_warn(dev, "splash: splash_offset needs DX,DY, ignored");
+ }
+
+ if (splash_rotation >= 0 &&
+ !drm_splash_rotation_from_degrees(splash_rotation, &splash->rotation))
+ drm_warn(dev, "splash: unsupported splash_rotation %d, ignored",
+ splash_rotation);
+}
+
static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
{
struct device_node *np;
+ /* Unless the source says otherwise, the kernel's default colour */
+ splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+
+ /* A BMP named on the command line was asked for this very boot */
+ if (drm_splash_bmp_none())
+ return DRM_SPLASH_SRC_NONE;
+ if (drm_splash_bmp_given())
+ return DRM_SPLASH_SRC_BMP;
+
np = drm_splash_dt_node(splash);
if (np) {
drm_splash_dt_props(splash, np);
@@ -869,18 +968,11 @@ static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
return DRM_SPLASH_SRC_DT;
}
- /* The BGRT carries no colour: firmware splashes sit on black */
if (drm_splash_bgrt_available(splash)) {
- splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
splash->rotation = drm_splash_bgrt_rotation();
return DRM_SPLASH_SRC_BGRT;
}
- /*
- * The BMP loaded as firmware is named on the command line, and so is
- * its colour, which also covers the screen when there is no image.
- */
- splash->bg_color = splash_color & 0xffffff;
if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
return DRM_SPLASH_SRC_BMP;
@@ -1181,6 +1273,7 @@ static int drm_splash_client_hotplug(struct drm_client_dev *client)
splash->thread = thread;
splash->src = drm_splash_pick_source(splash);
+ drm_splash_cmdline_overrides(splash);
/* The BMP loaded as firmware is the only source that arrives later */
if (splash->src == DRM_SPLASH_SRC_BMP) {
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH RFC v4 13/13] drm/client: splash: document the image sources and parameters
2026-10-01 19:58 [PATCH RFC v4 00/13] Add splash DRM client Màxim Pedraza Padilla
` (11 preceding siblings ...)
2026-10-01 19:58 ` [PATCH RFC v4 12/13] drm/client: splash: prefer what the command line asks for Màxim Pedraza Padilla
@ 2026-10-01 19:58 ` Màxim Pedraza Padilla
12 siblings, 0 replies; 14+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-01 19:58 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Mario Limonciello, Javier Martinez Canillas,
Jocelyn Falempe, Sam Ravnborg, Ilias Apalodimas, Shuah Khan,
Randy Dunlap, dri-devel, devicetree, linux-efi, linux-doc,
linux-embedded, linux-kernel
The overview only said that the client draws a colour or an image. By
now the image can come from four places in a set order, be placed and
turned, and every part of that can be overridden from the command line,
each parameter for its own part only. Write it down: the sources and
their order, the image format, placement and rotation, the background,
every parameter with its format, and what a parameter given alone
keeps from the image source.
Also note that the splash rules out fbdev emulation on the same device,
and that a bootloader that loads no image into a reserved region has to
clear it, since the region can keep the previous image across a reset
and even a short power cycle.
Pull the overview into Documentation/gpu/drm-client.rst, which covers
the in-kernel clients, so that it is published.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
Documentation/gpu/drm-client.rst | 6 +++
drivers/gpu/drm/clients/drm_splash.c | 78 +++++++++++++++++++++++++++-
2 files changed, 83 insertions(+), 1 deletion(-)
diff --git a/Documentation/gpu/drm-client.rst b/Documentation/gpu/drm-client.rst
index cbcfe30de777..a58d6440d9f2 100644
--- a/Documentation/gpu/drm-client.rst
+++ b/Documentation/gpu/drm-client.rst
@@ -16,3 +16,9 @@ Kernel clients
.. kernel-doc:: drivers/gpu/drm/drm_client_event.c
:export:
+
+Splash client
+=============
+
+.. kernel-doc:: drivers/gpu/drm/clients/drm_splash.c
+ :doc: overview
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 8d056edfee56..85083337870c 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -37,7 +37,83 @@
* DOC: overview
*
* This is a simple graphic bootsplash, able to display either a plain color or
- * a static image.
+ * a static image. It draws once the display driver has registered, and stays
+ * until userspace takes the display over.
+ *
+ * It is selected with ``drm_client_lib.active=splash`` or
+ * CONFIG_DRM_CLIENT_DEFAULT_SPLASH. A device has a single in-kernel client, so
+ * with the splash there is no fbdev emulation: no ``/dev/fb0`` and no
+ * framebuffer console.
+ *
+ * Image sources
+ * -------------
+ *
+ * The image is a BMP with a 40 byte BITMAPINFOHEADER, 24 bits per pixel and no
+ * compression. It comes from the first of these that provides one:
+ *
+ * 1. a BMP loaded as firmware and named on the command line with
+ * ``drm_client_lib.splash_bmp=``; ``splash_bmp=none`` asks for no image at
+ * all, and a named file that is missing gives no image rather than falling
+ * back to the sources below;
+ * 2. a "boot-logo" node under ``/chosen`` in the device tree, carrying the BMP
+ * itself or pointing at a reserved memory region the bootloader loaded it
+ * into (see Documentation/devicetree/bindings/display/boot-logo.yaml);
+ * 3. the EFI BGRT, the image the firmware showed;
+ * 4. ``drm_splash.bmp`` loaded as firmware, for instance built into the kernel
+ * with CONFIG_EXTRA_FIRMWARE.
+ *
+ * With none of them, only the background is drawn.
+ *
+ * A reserved memory region keeps its contents across a reset, and may even
+ * across a short power cycle, so a bootloader that loads no image there has
+ * to clear it: the kernel cannot tell a stale image from a fresh one.
+ *
+ * Placement
+ * ---------
+ *
+ * The image is placed at a position in screen pixels, where -1 centres it on
+ * that axis, and an offset is added afterwards; the result is clamped so that
+ * the whole image stays on screen. A rotation of 90, 180 or 270 degrees
+ * counter clockwise turns the image and not the screen: position and offset
+ * stay in screen pixels, and a quarter turn only swaps how much room the image
+ * takes up.
+ *
+ * A device tree image is placed and turned as its node says. A BGRT image is
+ * placed at the table offsets and turned as its orientation bits say, the
+ * offsets then being given on the upright screen. Any other image is centred
+ * and upright.
+ *
+ * Background
+ * ----------
+ *
+ * The rest of the screen is filled with the node's "background-color" for a
+ * device tree image, and with CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR
+ * otherwise.
+ *
+ * Command line
+ * ------------
+ *
+ * What the command line gives wins over every source, each parameter only for
+ * what it says, and only when it is given:
+ *
+ * ``drm_client_lib.splash_bmp=NAME``
+ * BMP to load as firmware, or ``none`` for no image.
+ * ``drm_client_lib.splash_color=0xRRGGBB``
+ * background color, whatever the image.
+ * ``drm_client_lib.splash_pos=X,Y``
+ * position; ``-1,-1`` centres, ``-1,183`` centres horizontally only.
+ * ``drm_client_lib.splash_offset=DX,DY``
+ * offset added to the position, for instance ``0,80``.
+ * ``drm_client_lib.splash_rotation=DEGREES``
+ * 0, 90, 180 or 270, counter clockwise.
+ *
+ * Positions take both values, or are ignored with a warning. Since each
+ * parameter only replaces its own part, the rest keeps coming from the image
+ * source: ``splash_offset=`` alone moves the image from where the source puts
+ * it, centred for a BMP loaded as firmware, and ``splash_pos=`` alone keeps a
+ * device tree node's "logo-offset". Give both to place the image exactly. A
+ * BGRT image placed from the command line is placed in screen pixels like any
+ * other: the table offsets are then not used.
*/
/*
--
2.39.5
^ permalink raw reply [flat|nested] 14+ messages in thread