blob: ad512d99a1b9133b4166fa63cf3b14751125e8f5 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Simon Glass68b26272016-01-18 19:52:22 -07002/*
3 * Copyright (c) 2015 Google, Inc
Simon Glass68b26272016-01-18 19:52:22 -07004 */
5
Simon Glass68b26272016-01-18 19:52:22 -07006#include <bmp_layout.h>
7#include <dm.h>
Simon Glass0f2af882020-05-10 11:40:05 -06008#include <log.h>
Simon Glass68b26272016-01-18 19:52:22 -07009#include <mapmem.h>
Anatolij Gustschin410f0962018-12-01 15:30:08 +010010#include <splash.h>
Simon Glass68b26272016-01-18 19:52:22 -070011#include <video.h>
12#include <watchdog.h>
13#include <asm/unaligned.h>
14
Simon Glass68b26272016-01-18 19:52:22 -070015#define BMP_RLE8_ESCAPE 0
16#define BMP_RLE8_EOL 0
17#define BMP_RLE8_EOBMP 1
18#define BMP_RLE8_DELTA 2
19
Simon Glass998e16a2021-11-19 13:23:54 -070020/**
21 * get_bmp_col_16bpp() - Convert a colour-table entry into a 16bpp pixel value
22 *
Heinrich Schuchardt47b4c022022-01-19 18:05:50 +010023 * Return: value to write to the 16bpp frame buffer for this palette entry
Simon Glass998e16a2021-11-19 13:23:54 -070024 */
25static uint get_bmp_col_16bpp(struct bmp_color_table_entry cte)
26{
27 return ((cte.red << 8) & 0xf800) |
28 ((cte.green << 3) & 0x07e0) |
29 ((cte.blue >> 3) & 0x001f);
30}
31
32/**
Janne Grunau8296d142022-02-09 22:16:22 +010033 * get_bmp_col_x2r10g10b10() - Convert a colour-table entry into a x2r10g10b10 pixel value
34 *
35 * Return: value to write to the x2r10g10b10 frame buffer for this palette entry
36 */
37static u32 get_bmp_col_x2r10g10b10(struct bmp_color_table_entry *cte)
38{
39 return ((cte->red << 22U) |
40 (cte->green << 12U) |
41 (cte->blue << 2U));
42}
43
44/**
Michal Simek1774cc92023-05-17 10:42:08 +020045 * get_bmp_col_rgba8888() - Convert a colour-table entry into a rgba8888 pixel value
46 *
47 * Return: value to write to the rgba8888 frame buffer for this palette entry
48 */
49static u32 get_bmp_col_rgba8888(struct bmp_color_table_entry *cte)
50{
51 return ((cte->red) |
52 (cte->green << 8U) |
53 (cte->blue << 16U) | 0xff << 24U);
54}
55
56/**
Simon Glass998e16a2021-11-19 13:23:54 -070057 * write_pix8() - Write a pixel from a BMP image into the framebuffer
58 *
59 * This handles frame buffers with 8, 16, 24 or 32 bits per pixel
60 *
61 * @fb: Place in frame buffer to update
62 * @bpix: Frame buffer bits-per-pixel, which controls how many bytes are written
63 * @palette: BMP palette table
64 * @bmap: Pointer to BMP bitmap position to write. This contains a single byte
65 * which is either written directly (bpix == 8) or used to look up the
66 * palette to get a colour to write
67 */
Janne Grunau8296d142022-02-09 22:16:22 +010068static void write_pix8(u8 *fb, uint bpix, enum video_format eformat,
69 struct bmp_color_table_entry *palette, u8 *bmap)
Simon Glass998e16a2021-11-19 13:23:54 -070070{
71 if (bpix == 8) {
72 *fb++ = *bmap;
73 } else if (bpix == 16) {
74 *(u16 *)fb = get_bmp_col_16bpp(palette[*bmap]);
75 } else {
76 /* Only support big endian */
77 struct bmp_color_table_entry *cte = &palette[*bmap];
78
79 if (bpix == 24) {
80 *fb++ = cte->red;
81 *fb++ = cte->green;
82 *fb++ = cte->blue;
Janne Grunau8296d142022-02-09 22:16:22 +010083 } else if (eformat == VIDEO_X2R10G10B10) {
84 *(u32 *)fb = get_bmp_col_x2r10g10b10(cte);
Michal Simek1774cc92023-05-17 10:42:08 +020085 } else if (eformat == VIDEO_RGBA8888) {
86 *(u32 *)fb = get_bmp_col_rgba8888(cte);
Simon Glass998e16a2021-11-19 13:23:54 -070087 } else {
88 *fb++ = cte->blue;
89 *fb++ = cte->green;
90 *fb++ = cte->red;
91 *fb++ = 0;
92 }
93 }
94}
95
Janne Grunau8296d142022-02-09 22:16:22 +010096static void draw_unencoded_bitmap(u8 **fbp, uint bpix,
97 enum video_format eformat, uchar *bmap,
Simon Glass490bb992021-11-19 13:23:55 -070098 struct bmp_color_table_entry *palette,
Simon Glass68b26272016-01-18 19:52:22 -070099 int cnt)
100{
Simon Glass490bb992021-11-19 13:23:55 -0700101 u8 *fb = *fbp;
102
Simon Glass68b26272016-01-18 19:52:22 -0700103 while (cnt > 0) {
Janne Grunau8296d142022-02-09 22:16:22 +0100104 write_pix8(fb, bpix, eformat, palette, bmap++);
Simon Glass490bb992021-11-19 13:23:55 -0700105 fb += bpix / 8;
Simon Glass68b26272016-01-18 19:52:22 -0700106 cnt--;
107 }
Simon Glass490bb992021-11-19 13:23:55 -0700108 *fbp = fb;
Simon Glass68b26272016-01-18 19:52:22 -0700109}
110
Janne Grunau8296d142022-02-09 22:16:22 +0100111static void draw_encoded_bitmap(u8 **fbp, uint bpix, enum video_format eformat,
Simon Glass490bb992021-11-19 13:23:55 -0700112 struct bmp_color_table_entry *palette, u8 *bmap,
113 int cnt)
Simon Glass68b26272016-01-18 19:52:22 -0700114{
Simon Glass490bb992021-11-19 13:23:55 -0700115 u8 *fb = *fbp;
Simon Glass68b26272016-01-18 19:52:22 -0700116
117 while (cnt > 0) {
Janne Grunau8296d142022-02-09 22:16:22 +0100118 write_pix8(fb, bpix, eformat, palette, bmap);
Simon Glass490bb992021-11-19 13:23:55 -0700119 fb += bpix / 8;
Simon Glass68b26272016-01-18 19:52:22 -0700120 cnt--;
121 }
122 *fbp = fb;
123}
124
125static void video_display_rle8_bitmap(struct udevice *dev,
Simon Glass490bb992021-11-19 13:23:55 -0700126 struct bmp_image *bmp, uint bpix,
127 struct bmp_color_table_entry *palette,
Patrice Chotardcb30f7d2019-11-20 14:11:16 +0100128 uchar *fb, int x_off, int y_off,
129 ulong width, ulong height)
Simon Glass68b26272016-01-18 19:52:22 -0700130{
131 struct video_priv *priv = dev_get_uclass_priv(dev);
132 uchar *bmap;
Simon Glass68b26272016-01-18 19:52:22 -0700133 ulong cnt, runlen;
134 int x, y;
135 int decode = 1;
Simon Glass490bb992021-11-19 13:23:55 -0700136 uint bytes_per_pixel = bpix / 8;
Janne Grunau8296d142022-02-09 22:16:22 +0100137 enum video_format eformat = priv->format;
Simon Glass68b26272016-01-18 19:52:22 -0700138
139 debug("%s\n", __func__);
Simon Glass68b26272016-01-18 19:52:22 -0700140 bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
141
142 x = 0;
143 y = height - 1;
144
145 while (decode) {
146 if (bmap[0] == BMP_RLE8_ESCAPE) {
147 switch (bmap[1]) {
148 case BMP_RLE8_EOL:
149 /* end of line */
150 bmap += 2;
151 x = 0;
152 y--;
Simon Glass490bb992021-11-19 13:23:55 -0700153 fb -= width * bytes_per_pixel +
154 priv->line_length;
Simon Glass68b26272016-01-18 19:52:22 -0700155 break;
156 case BMP_RLE8_EOBMP:
157 /* end of bitmap */
158 decode = 0;
159 break;
160 case BMP_RLE8_DELTA:
161 /* delta run */
162 x += bmap[2];
163 y -= bmap[3];
Simon Glass490bb992021-11-19 13:23:55 -0700164 fb = (uchar *)(priv->fb +
165 (y + y_off - 1) * priv->line_length +
166 (x + x_off) * bytes_per_pixel);
Simon Glass68b26272016-01-18 19:52:22 -0700167 bmap += 4;
168 break;
169 default:
170 /* unencoded run */
171 runlen = bmap[1];
172 bmap += 2;
173 if (y < height) {
174 if (x < width) {
175 if (x + runlen > width)
176 cnt = width - x;
177 else
178 cnt = runlen;
179 draw_unencoded_bitmap(
Janne Grunau8296d142022-02-09 22:16:22 +0100180 &fb, bpix, eformat,
Simon Glass490bb992021-11-19 13:23:55 -0700181 bmap, palette, cnt);
Simon Glass68b26272016-01-18 19:52:22 -0700182 }
183 x += runlen;
184 }
185 bmap += runlen;
186 if (runlen & 1)
187 bmap++;
188 }
189 } else {
190 /* encoded run */
191 if (y < height) {
192 runlen = bmap[0];
193 if (x < width) {
194 /* aggregate the same code */
195 while (bmap[0] == 0xff &&
196 bmap[2] != BMP_RLE8_ESCAPE &&
197 bmap[1] == bmap[3]) {
198 runlen += bmap[2];
199 bmap += 2;
200 }
201 if (x + runlen > width)
202 cnt = width - x;
203 else
204 cnt = runlen;
Janne Grunau8296d142022-02-09 22:16:22 +0100205 draw_encoded_bitmap(&fb, bpix, eformat,
206 palette, &bmap[1],
207 cnt);
Simon Glass68b26272016-01-18 19:52:22 -0700208 }
209 x += runlen;
210 }
211 bmap += 2;
212 }
213 }
214}
Simon Glass68b26272016-01-18 19:52:22 -0700215
Simon Glass68b26272016-01-18 19:52:22 -0700216/**
217 * video_splash_align_axis() - Align a single coordinate
218 *
219 *- if a coordinate is 0x7fff then the image will be centred in
220 * that direction
221 *- if a coordinate is -ve then it will be offset to the
222 * left/top of the centre by that many pixels
223 *- if a coordinate is positive it will be used unchnaged.
224 *
225 * @axis: Input and output coordinate
226 * @panel_size: Size of panel in pixels for that axis
227 * @picture_size: Size of bitmap in pixels for that axis
228 */
229static void video_splash_align_axis(int *axis, unsigned long panel_size,
230 unsigned long picture_size)
231{
Patrice Chotard066eace2019-11-20 14:11:15 +0100232 long panel_picture_delta = panel_size - picture_size;
233 long axis_alignment;
Simon Glass68b26272016-01-18 19:52:22 -0700234
235 if (*axis == BMP_ALIGN_CENTER)
236 axis_alignment = panel_picture_delta / 2;
237 else if (*axis < 0)
238 axis_alignment = panel_picture_delta + *axis + 1;
239 else
240 return;
241
242 *axis = max(0, (int)axis_alignment);
243}
244
Simon Glass7e148042022-10-06 08:36:17 -0600245void video_bmp_get_info(void *bmp_image, ulong *widthp, ulong *heightp,
246 uint *bpixp)
247{
248 struct bmp_image *bmp = bmp_image;
249
250 *widthp = get_unaligned_le32(&bmp->header.width);
251 *heightp = get_unaligned_le32(&bmp->header.height);
252 *bpixp = get_unaligned_le16(&bmp->header.bit_count);
253}
254
Simon Glass68b26272016-01-18 19:52:22 -0700255int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y,
256 bool align)
257{
258 struct video_priv *priv = dev_get_uclass_priv(dev);
Simon Glass1f475732016-01-30 15:45:16 -0700259 int i, j;
Simon Glass4761c8c2020-07-02 21:12:27 -0600260 uchar *start, *fb;
Simon Glass68b26272016-01-18 19:52:22 -0700261 struct bmp_image *bmp = map_sysmem(bmp_image, 0);
262 uchar *bmap;
263 ushort padded_width;
264 unsigned long width, height, byte_width;
265 unsigned long pwidth = priv->xsize;
266 unsigned colours, bpix, bmp_bpix;
Janne Grunau8296d142022-02-09 22:16:22 +0100267 enum video_format eformat;
Simon Glass68b26272016-01-18 19:52:22 -0700268 struct bmp_color_table_entry *palette;
269 int hdr_size;
Simon Glass4761c8c2020-07-02 21:12:27 -0600270 int ret;
Simon Glass68b26272016-01-18 19:52:22 -0700271
272 if (!bmp || !(bmp->header.signature[0] == 'B' &&
273 bmp->header.signature[1] == 'M')) {
274 printf("Error: no valid bmp image at %lx\n", bmp_image);
275
276 return -EINVAL;
277 }
278
Simon Glass7e148042022-10-06 08:36:17 -0600279 video_bmp_get_info(bmp, &width, &height, &bmp_bpix);
Simon Glass68b26272016-01-18 19:52:22 -0700280 hdr_size = get_unaligned_le16(&bmp->header.size);
281 debug("hdr_size=%d, bmp_bpix=%d\n", hdr_size, bmp_bpix);
282 palette = (void *)bmp + 14 + hdr_size;
283
284 colours = 1 << bmp_bpix;
285
286 bpix = VNBITS(priv->bpix);
Janne Grunau8296d142022-02-09 22:16:22 +0100287 eformat = priv->format;
Simon Glass68b26272016-01-18 19:52:22 -0700288
289 if (bpix != 1 && bpix != 8 && bpix != 16 && bpix != 32) {
290 printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n",
291 bpix, bmp_bpix);
292
293 return -EINVAL;
294 }
295
296 /*
Stefan Roese7ee599e2019-01-30 08:54:12 +0100297 * We support displaying 8bpp and 24bpp BMPs on 16bpp LCDs
Simon Glass68b26272016-01-18 19:52:22 -0700298 * and displaying 24bpp BMPs on 32bpp LCDs
Stefan Roese7ee599e2019-01-30 08:54:12 +0100299 */
Simon Glass68b26272016-01-18 19:52:22 -0700300 if (bpix != bmp_bpix &&
301 !(bmp_bpix == 8 && bpix == 16) &&
Ye Lib5c864d2020-06-10 02:52:23 -0700302 !(bmp_bpix == 8 && bpix == 24) &&
303 !(bmp_bpix == 8 && bpix == 32) &&
Stefan Roese7ee599e2019-01-30 08:54:12 +0100304 !(bmp_bpix == 24 && bpix == 16) &&
Simon Glass68b26272016-01-18 19:52:22 -0700305 !(bmp_bpix == 24 && bpix == 32)) {
306 printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n",
Simon Glass7e148042022-10-06 08:36:17 -0600307 bpix, colours);
Simon Glass68b26272016-01-18 19:52:22 -0700308 return -EPERM;
309 }
310
311 debug("Display-bmp: %d x %d with %d colours, display %d\n",
312 (int)width, (int)height, (int)colours, 1 << bpix);
313
Simon Glass68b26272016-01-18 19:52:22 -0700314 padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width);
315
316 if (align) {
317 video_splash_align_axis(&x, priv->xsize, width);
318 video_splash_align_axis(&y, priv->ysize, height);
319 }
320
321 if ((x + width) > pwidth)
322 width = pwidth - x;
323 if ((y + height) > priv->ysize)
324 height = priv->ysize - y;
325
326 bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
Simon Glass4761c8c2020-07-02 21:12:27 -0600327 start = (uchar *)(priv->fb +
328 (y + height) * priv->line_length + x * bpix / 8);
329
330 /* Move back to the final line to be drawn */
331 fb = start - priv->line_length;
Simon Glass68b26272016-01-18 19:52:22 -0700332
333 switch (bmp_bpix) {
334 case 1:
Simon Glass61494702021-11-19 13:24:00 -0700335 case 8:
Nikhil M Jain9e3301d2023-04-20 17:41:08 +0530336 if (CONFIG_IS_ENABLED(VIDEO_BMP_RLE8)) {
Simon Glass61494702021-11-19 13:24:00 -0700337 u32 compression = get_unaligned_le32(
338 &bmp->header.compression);
339 debug("compressed %d %d\n", compression, BMP_BI_RLE8);
340 if (compression == BMP_BI_RLE8) {
341 video_display_rle8_bitmap(dev, bmp, bpix, palette, fb,
342 x, y, width, height);
343 break;
344 }
Simon Glass68b26272016-01-18 19:52:22 -0700345 }
Simon Glass61494702021-11-19 13:24:00 -0700346
347 /* Not compressed */
Ye Lib5c864d2020-06-10 02:52:23 -0700348 byte_width = width * (bpix / 8);
349 if (!byte_width)
Simon Glass68b26272016-01-18 19:52:22 -0700350 byte_width = width;
Simon Glass68b26272016-01-18 19:52:22 -0700351
352 for (i = 0; i < height; ++i) {
Stefan Roese80877fa2022-09-02 14:10:46 +0200353 schedule();
Simon Glass68b26272016-01-18 19:52:22 -0700354 for (j = 0; j < width; j++) {
Janne Grunau8296d142022-02-09 22:16:22 +0100355 write_pix8(fb, bpix, eformat, palette, bmap);
Simon Glass998e16a2021-11-19 13:23:54 -0700356 bmap++;
357 fb += bpix / 8;
Simon Glass68b26272016-01-18 19:52:22 -0700358 }
359 bmap += (padded_width - width);
360 fb -= byte_width + priv->line_length;
361 }
362 break;
Simon Glass68b26272016-01-18 19:52:22 -0700363 case 16:
Nikhil M Jain9e3301d2023-04-20 17:41:08 +0530364 if (CONFIG_IS_ENABLED(BMP_16BPP)) {
Simon Glass61494702021-11-19 13:24:00 -0700365 for (i = 0; i < height; ++i) {
Stefan Roese80877fa2022-09-02 14:10:46 +0200366 schedule();
Simon Glass61494702021-11-19 13:24:00 -0700367 for (j = 0; j < width; j++) {
368 *fb++ = *bmap++;
369 *fb++ = *bmap++;
370 }
371 bmap += (padded_width - width);
372 fb -= width * 2 + priv->line_length;
Simon Glassaa662a62021-11-19 13:23:53 -0700373 }
Simon Glass68b26272016-01-18 19:52:22 -0700374 }
375 break;
Simon Glass68b26272016-01-18 19:52:22 -0700376 case 24:
Nikhil M Jain9e3301d2023-04-20 17:41:08 +0530377 if (CONFIG_IS_ENABLED(BMP_24BPP)) {
Simon Glass61494702021-11-19 13:24:00 -0700378 for (i = 0; i < height; ++i) {
379 for (j = 0; j < width; j++) {
380 if (bpix == 16) {
381 /* 16bit 565RGB format */
382 *(u16 *)fb = ((bmap[2] >> 3)
383 << 11) |
384 ((bmap[1] >> 2) << 5) |
385 (bmap[0] >> 3);
386 bmap += 3;
387 fb += 2;
Janne Grunau8296d142022-02-09 22:16:22 +0100388 } else if (eformat == VIDEO_X2R10G10B10) {
389 u32 pix;
390
391 pix = *bmap++ << 2U;
392 pix |= *bmap++ << 12U;
393 pix |= *bmap++ << 22U;
394 *fb++ = pix & 0xff;
395 *fb++ = (pix >> 8) & 0xff;
396 *fb++ = (pix >> 16) & 0xff;
397 *fb++ = pix >> 24;
Michal Simek1774cc92023-05-17 10:42:08 +0200398 } else if (eformat == VIDEO_RGBA8888) {
399 u32 pix;
400
401 pix = *bmap++ << 8U; /* blue */
402 pix |= *bmap++ << 16U; /* green */
403 pix |= *bmap++ << 24U; /* red */
404
405 *fb++ = (pix >> 24) & 0xff;
406 *fb++ = (pix >> 16) & 0xff;
407 *fb++ = (pix >> 8) & 0xff;
408 *fb++ = 0xff;
Simon Glass61494702021-11-19 13:24:00 -0700409 } else {
410 *fb++ = *bmap++;
411 *fb++ = *bmap++;
412 *fb++ = *bmap++;
413 *fb++ = 0;
414 }
Stefan Roese7ee599e2019-01-30 08:54:12 +0100415 }
Simon Glass61494702021-11-19 13:24:00 -0700416 fb -= priv->line_length + width * (bpix / 8);
417 bmap += (padded_width - width);
Simon Glass68b26272016-01-18 19:52:22 -0700418 }
Simon Glass68b26272016-01-18 19:52:22 -0700419 }
420 break;
Simon Glass68b26272016-01-18 19:52:22 -0700421 case 32:
Nikhil M Jain9e3301d2023-04-20 17:41:08 +0530422 if (CONFIG_IS_ENABLED(BMP_32BPP)) {
Simon Glass61494702021-11-19 13:24:00 -0700423 for (i = 0; i < height; ++i) {
424 for (j = 0; j < width; j++) {
Janne Grunau8296d142022-02-09 22:16:22 +0100425 if (eformat == VIDEO_X2R10G10B10) {
426 u32 pix;
427
428 pix = *bmap++ << 2U;
429 pix |= *bmap++ << 12U;
430 pix |= *bmap++ << 22U;
431 pix |= (*bmap++ >> 6) << 30U;
432 *fb++ = pix & 0xff;
433 *fb++ = (pix >> 8) & 0xff;
434 *fb++ = (pix >> 16) & 0xff;
435 *fb++ = pix >> 24;
Michal Simek1774cc92023-05-17 10:42:08 +0200436 } else if (eformat == VIDEO_RGBA8888) {
437 u32 pix;
438
439 pix = *bmap++ << 8U; /* blue */
440 pix |= *bmap++ << 16U; /* green */
441 pix |= *bmap++ << 24U; /* red */
442 bmap++;
443 *fb++ = (pix >> 24) & 0xff;
444 *fb++ = (pix >> 16) & 0xff;
445 *fb++ = (pix >> 8) & 0xff;
446 *fb++ = 0xff; /* opacity */
Janne Grunau8296d142022-02-09 22:16:22 +0100447 } else {
448 *fb++ = *bmap++;
449 *fb++ = *bmap++;
450 *fb++ = *bmap++;
451 *fb++ = *bmap++;
452 }
Simon Glass61494702021-11-19 13:24:00 -0700453 }
454 fb -= priv->line_length + width * (bpix / 8);
Simon Glass68b26272016-01-18 19:52:22 -0700455 }
Simon Glass68b26272016-01-18 19:52:22 -0700456 }
457 break;
Simon Glass68b26272016-01-18 19:52:22 -0700458 default:
459 break;
460 };
461
Simon Glass4761c8c2020-07-02 21:12:27 -0600462 /* Find the position of the top left of the image in the framebuffer */
463 fb = (uchar *)(priv->fb + y * priv->line_length + x * bpix / 8);
464 ret = video_sync_copy(dev, start, fb);
465 if (ret)
466 return log_ret(ret);
467
Michal Simek632e3d42020-12-14 08:47:52 +0100468 return video_sync(dev, false);
Simon Glass68b26272016-01-18 19:52:22 -0700469}