[PATCH v6 11/15] drm/client: splash: turn the device tree image as it asks
From: Màxim Pedraza Padilla
Date: Tue Oct 27 2026 - 10:03:27 EST
Let the device tree node ask for the image to be turned, for a logo
drawn in one orientation and meant to be seen in another, 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.
It describes the image only: a panel mounted turned is described on the
panel, and a later patch in this series makes the splash follow it.
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 turn happens in the first of the two drawing steps, which reads
the BMP into an XRGB8888 buffer: it walks the source through the
rotation, so the buffer holds the image as it is to be seen. The second
step, into the framebuffer's format, does not change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@xxxxxxxxx>
---
drivers/gpu/drm/clients/drm_splash.c | 109 +++++++++++++++++++++++----
1 file changed, 93 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 81070547390f..a50036155763 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -28,6 +28,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>
@@ -114,6 +115,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)
@@ -273,10 +276,34 @@ static void drm_splash_dt_pair(struct drm_splash *splash,
static void drm_splash_dt_placement(struct drm_splash *splash,
struct device_node *np)
{
+ u32 val;
+
drm_splash_dt_pair(splash, np, "logo-position", -1, 65535,
&splash->pos_x, &splash->pos_y);
drm_splash_dt_pair(splash, np, "logo-offset", -65535, 65535,
&splash->off_x, &splash->off_y);
+
+ /* Degrees counter clockwise, as for panels, and as DRM counts them */
+ if (!of_property_read_u32(np, "rotation", &val)) {
+ switch (val) {
+ 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\n",
+ np, val);
+ }
+ }
}
/*
@@ -494,19 +521,59 @@ static unsigned int drm_splash_place_axis(s32 pos, s32 off, unsigned int span,
return clamp_t(s64, coord + off, 0, last);
}
-/* The rows of a 24 bpp BMP, bottom up or top down, upright in XRGB8888 */
-static void drm_splash_bmp_decode(u32 *dst, const u8 *src, unsigned int spitch,
+/*
+ * The pixels of a 24 bpp BMP, whose rows go bottom up or top down, into
+ * XRGB8888, turned by @rotation, one of DRM_MODE_ROTATE_*, which count
+ * counter clockwise. A quarter turn swaps width and height in @dst.
+ *
+ * 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_bmp_decode(u32 *dst, const u8 *sbuf8, unsigned int spitch,
unsigned int width, unsigned int height,
- bool invert_y)
+ bool invert_y, unsigned int rotation)
{
+ bool quarter = rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270);
+ unsigned int dw = quarter ? height : width;
+ unsigned int dh = quarter ? width : height;
+ /* A positive BMP height stores the rows bottom up */
+ ptrdiff_t down = invert_y ? -(ptrdiff_t)spitch : spitch;
+ const u8 *first = sbuf8 + (invert_y ? (height - 1) * spitch : 0);
+ const u8 *last = first + (height - 1) * down;
+ ptrdiff_t step_x, step_y;
+ const u8 *src, *p;
unsigned int x, y;
- for (y = 0; y < height; y++) {
- const u8 *p = src + (invert_y ? height - 1 - y : y) * spitch;
+ /*
+ * Where the source pixel for the top left of the turned image is, and
+ * how far one step right and one step down in it move in the source
+ */
+ switch (rotation) {
+ case DRM_MODE_ROTATE_90:
+ src = first + 3 * (width - 1);
+ step_x = down;
+ step_y = -3;
+ break;
+ case DRM_MODE_ROTATE_180:
+ src = last + 3 * (width - 1);
+ step_x = -3;
+ step_y = -down;
+ break;
+ case DRM_MODE_ROTATE_270:
+ src = last;
+ step_x = -down;
+ step_y = 3;
+ break;
+ default:
+ src = first;
+ step_x = 3;
+ step_y = down;
+ break;
+ }
- for (x = 0; x < width; x++, p += 3)
+ for (y = 0; y < dh; y++, src += step_y)
+ for (x = 0, p = src; x < dw; x++, p += step_x)
*dst++ = get_unaligned_le24(p);
- }
}
/* An XRGB8888 image into a buffer of another format, at x0, y0 */
@@ -564,7 +631,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;
@@ -629,7 +696,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\n");
return -EINVAL;
}
@@ -662,19 +738,19 @@ 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;
- /* First the image upright in XRGB8888, then into the buffer's format */
+ /* First the image turned in XRGB8888, then into the buffer's format */
img = kvmalloc_array(bmp_width, bmp_height * sizeof(*img), GFP_KERNEL);
if (!img)
return -ENOMEM;
drm_splash_bmp_decode(img, image_data, bmp_pitch, bmp_width, bmp_height,
- bmp_invert_y);
+ bmp_invert_y, splash->rotation);
ret = drm_client_buffer_vmap_local(buffer, &map);
if (ret) {
@@ -683,9 +759,9 @@ 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);
drm_splash_blit(&map, fb->pitches[0], fb->format, x_pad, y_pad, img,
- bmp_width, bmp_height);
+ img_w, img_h);
kvfree(img);
drm_client_buffer_vunmap_local(buffer);
@@ -1184,9 +1260,10 @@ void drm_splash_register(struct drm_device *dev,
mutex_init(&splash->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