blob: 466c0f54363d5929e77815ef9be541ea963da8fc [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
6#include <common.h>
7#include <bmp_layout.h>
8#include <dm.h>
Simon Glass0f2af882020-05-10 11:40:05 -06009#include <log.h>
Simon Glass68b26272016-01-18 19:52:22 -070010#include <mapmem.h>
Anatolij Gustschin410f0962018-12-01 15:30:08 +010011#include <splash.h>
Simon Glass68b26272016-01-18 19:52:22 -070012#include <video.h>
13#include <watchdog.h>
14#include <asm/unaligned.h>
15
Simon Glass68b26272016-01-18 19:52:22 -070016#define BMP_RLE8_ESCAPE 0
17#define BMP_RLE8_EOL 0
18#define BMP_RLE8_EOBMP 1
19#define BMP_RLE8_DELTA 2
20
Simon Glass998e16a2021-11-19 13:23:54 -070021/**
22 * get_bmp_col_16bpp() - Convert a colour-table entry into a 16bpp pixel value
23 *
24 * @return value to write to the 16bpp frame buffer for this palette entry
25 */
26static uint get_bmp_col_16bpp(struct bmp_color_table_entry cte)
27{
28 return ((cte.red << 8) & 0xf800) |
29 ((cte.green << 3) & 0x07e0) |
30 ((cte.blue >> 3) & 0x001f);
31}
32
33/**
34 * write_pix8() - Write a pixel from a BMP image into the framebuffer
35 *
36 * This handles frame buffers with 8, 16, 24 or 32 bits per pixel
37 *
38 * @fb: Place in frame buffer to update
39 * @bpix: Frame buffer bits-per-pixel, which controls how many bytes are written
40 * @palette: BMP palette table
41 * @bmap: Pointer to BMP bitmap position to write. This contains a single byte
42 * which is either written directly (bpix == 8) or used to look up the
43 * palette to get a colour to write
44 */
45static void write_pix8(u8 *fb, uint bpix, struct bmp_color_table_entry *palette,
46 u8 *bmap)
47{
48 if (bpix == 8) {
49 *fb++ = *bmap;
50 } else if (bpix == 16) {
51 *(u16 *)fb = get_bmp_col_16bpp(palette[*bmap]);
52 } else {
53 /* Only support big endian */
54 struct bmp_color_table_entry *cte = &palette[*bmap];
55
56 if (bpix == 24) {
57 *fb++ = cte->red;
58 *fb++ = cte->green;
59 *fb++ = cte->blue;
60 } else {
61 *fb++ = cte->blue;
62 *fb++ = cte->green;
63 *fb++ = cte->red;
64 *fb++ = 0;
65 }
66 }
67}
68
69#ifdef CONFIG_VIDEO_BMP_RLE8
Simon Glass490bb992021-11-19 13:23:55 -070070static void draw_unencoded_bitmap(u8 **fbp, uint bpix, uchar *bmap,
71 struct bmp_color_table_entry *palette,
Simon Glass68b26272016-01-18 19:52:22 -070072 int cnt)
73{
Simon Glass490bb992021-11-19 13:23:55 -070074 u8 *fb = *fbp;
75
Simon Glass68b26272016-01-18 19:52:22 -070076 while (cnt > 0) {
Simon Glass490bb992021-11-19 13:23:55 -070077 write_pix8(fb, bpix, palette, bmap++);
78 fb += bpix / 8;
Simon Glass68b26272016-01-18 19:52:22 -070079 cnt--;
80 }
Simon Glass490bb992021-11-19 13:23:55 -070081 *fbp = fb;
Simon Glass68b26272016-01-18 19:52:22 -070082}
83
Simon Glass490bb992021-11-19 13:23:55 -070084static void draw_encoded_bitmap(u8 **fbp, uint bpix,
85 struct bmp_color_table_entry *palette, u8 *bmap,
86 int cnt)
Simon Glass68b26272016-01-18 19:52:22 -070087{
Simon Glass490bb992021-11-19 13:23:55 -070088 u8 *fb = *fbp;
Simon Glass68b26272016-01-18 19:52:22 -070089
90 while (cnt > 0) {
Simon Glass490bb992021-11-19 13:23:55 -070091 write_pix8(fb, bpix, palette, bmap);
92 fb += bpix / 8;
Simon Glass68b26272016-01-18 19:52:22 -070093 cnt--;
94 }
95 *fbp = fb;
96}
97
98static void video_display_rle8_bitmap(struct udevice *dev,
Simon Glass490bb992021-11-19 13:23:55 -070099 struct bmp_image *bmp, uint bpix,
100 struct bmp_color_table_entry *palette,
Patrice Chotardcb30f7d2019-11-20 14:11:16 +0100101 uchar *fb, int x_off, int y_off,
102 ulong width, ulong height)
Simon Glass68b26272016-01-18 19:52:22 -0700103{
104 struct video_priv *priv = dev_get_uclass_priv(dev);
105 uchar *bmap;
Simon Glass68b26272016-01-18 19:52:22 -0700106 ulong cnt, runlen;
107 int x, y;
108 int decode = 1;
Simon Glass490bb992021-11-19 13:23:55 -0700109 uint bytes_per_pixel = bpix / 8;
Simon Glass68b26272016-01-18 19:52:22 -0700110
111 debug("%s\n", __func__);
Simon Glass68b26272016-01-18 19:52:22 -0700112 bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
113
114 x = 0;
115 y = height - 1;
116
117 while (decode) {
118 if (bmap[0] == BMP_RLE8_ESCAPE) {
119 switch (bmap[1]) {
120 case BMP_RLE8_EOL:
121 /* end of line */
122 bmap += 2;
123 x = 0;
124 y--;
Simon Glass490bb992021-11-19 13:23:55 -0700125 fb -= width * bytes_per_pixel +
126 priv->line_length;
Simon Glass68b26272016-01-18 19:52:22 -0700127 break;
128 case BMP_RLE8_EOBMP:
129 /* end of bitmap */
130 decode = 0;
131 break;
132 case BMP_RLE8_DELTA:
133 /* delta run */
134 x += bmap[2];
135 y -= bmap[3];
Simon Glass490bb992021-11-19 13:23:55 -0700136 fb = (uchar *)(priv->fb +
137 (y + y_off - 1) * priv->line_length +
138 (x + x_off) * bytes_per_pixel);
Simon Glass68b26272016-01-18 19:52:22 -0700139 bmap += 4;
140 break;
141 default:
142 /* unencoded run */
143 runlen = bmap[1];
144 bmap += 2;
145 if (y < height) {
146 if (x < width) {
147 if (x + runlen > width)
148 cnt = width - x;
149 else
150 cnt = runlen;
151 draw_unencoded_bitmap(
Simon Glass490bb992021-11-19 13:23:55 -0700152 &fb, bpix,
153 bmap, palette, cnt);
Simon Glass68b26272016-01-18 19:52:22 -0700154 }
155 x += runlen;
156 }
157 bmap += runlen;
158 if (runlen & 1)
159 bmap++;
160 }
161 } else {
162 /* encoded run */
163 if (y < height) {
164 runlen = bmap[0];
165 if (x < width) {
166 /* aggregate the same code */
167 while (bmap[0] == 0xff &&
168 bmap[2] != BMP_RLE8_ESCAPE &&
169 bmap[1] == bmap[3]) {
170 runlen += bmap[2];
171 bmap += 2;
172 }
173 if (x + runlen > width)
174 cnt = width - x;
175 else
176 cnt = runlen;
Simon Glass490bb992021-11-19 13:23:55 -0700177 draw_encoded_bitmap(&fb, bpix, palette,
178 &bmap[1], cnt);
Simon Glass68b26272016-01-18 19:52:22 -0700179 }
180 x += runlen;
181 }
182 bmap += 2;
183 }
184 }
185}
186#endif
187
Simon Glass68b26272016-01-18 19:52:22 -0700188/**
189 * video_splash_align_axis() - Align a single coordinate
190 *
191 *- if a coordinate is 0x7fff then the image will be centred in
192 * that direction
193 *- if a coordinate is -ve then it will be offset to the
194 * left/top of the centre by that many pixels
195 *- if a coordinate is positive it will be used unchnaged.
196 *
197 * @axis: Input and output coordinate
198 * @panel_size: Size of panel in pixels for that axis
199 * @picture_size: Size of bitmap in pixels for that axis
200 */
201static void video_splash_align_axis(int *axis, unsigned long panel_size,
202 unsigned long picture_size)
203{
Patrice Chotard066eace2019-11-20 14:11:15 +0100204 long panel_picture_delta = panel_size - picture_size;
205 long axis_alignment;
Simon Glass68b26272016-01-18 19:52:22 -0700206
207 if (*axis == BMP_ALIGN_CENTER)
208 axis_alignment = panel_picture_delta / 2;
209 else if (*axis < 0)
210 axis_alignment = panel_picture_delta + *axis + 1;
211 else
212 return;
213
214 *axis = max(0, (int)axis_alignment);
215}
216
Simon Glass68b26272016-01-18 19:52:22 -0700217int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y,
218 bool align)
219{
220 struct video_priv *priv = dev_get_uclass_priv(dev);
Simon Glass1f475732016-01-30 15:45:16 -0700221 int i, j;
Simon Glass4761c8c2020-07-02 21:12:27 -0600222 uchar *start, *fb;
Simon Glass68b26272016-01-18 19:52:22 -0700223 struct bmp_image *bmp = map_sysmem(bmp_image, 0);
224 uchar *bmap;
225 ushort padded_width;
226 unsigned long width, height, byte_width;
227 unsigned long pwidth = priv->xsize;
228 unsigned colours, bpix, bmp_bpix;
229 struct bmp_color_table_entry *palette;
230 int hdr_size;
Simon Glass4761c8c2020-07-02 21:12:27 -0600231 int ret;
Simon Glass68b26272016-01-18 19:52:22 -0700232
233 if (!bmp || !(bmp->header.signature[0] == 'B' &&
234 bmp->header.signature[1] == 'M')) {
235 printf("Error: no valid bmp image at %lx\n", bmp_image);
236
237 return -EINVAL;
238 }
239
240 width = get_unaligned_le32(&bmp->header.width);
241 height = get_unaligned_le32(&bmp->header.height);
242 bmp_bpix = get_unaligned_le16(&bmp->header.bit_count);
243 hdr_size = get_unaligned_le16(&bmp->header.size);
244 debug("hdr_size=%d, bmp_bpix=%d\n", hdr_size, bmp_bpix);
245 palette = (void *)bmp + 14 + hdr_size;
246
247 colours = 1 << bmp_bpix;
248
249 bpix = VNBITS(priv->bpix);
250
251 if (bpix != 1 && bpix != 8 && bpix != 16 && bpix != 32) {
252 printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n",
253 bpix, bmp_bpix);
254
255 return -EINVAL;
256 }
257
258 /*
Stefan Roese7ee599e2019-01-30 08:54:12 +0100259 * We support displaying 8bpp and 24bpp BMPs on 16bpp LCDs
Simon Glass68b26272016-01-18 19:52:22 -0700260 * and displaying 24bpp BMPs on 32bpp LCDs
Stefan Roese7ee599e2019-01-30 08:54:12 +0100261 */
Simon Glass68b26272016-01-18 19:52:22 -0700262 if (bpix != bmp_bpix &&
263 !(bmp_bpix == 8 && bpix == 16) &&
Ye Lib5c864d2020-06-10 02:52:23 -0700264 !(bmp_bpix == 8 && bpix == 24) &&
265 !(bmp_bpix == 8 && bpix == 32) &&
Stefan Roese7ee599e2019-01-30 08:54:12 +0100266 !(bmp_bpix == 24 && bpix == 16) &&
Simon Glass68b26272016-01-18 19:52:22 -0700267 !(bmp_bpix == 24 && bpix == 32)) {
268 printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n",
269 bpix, get_unaligned_le16(&bmp->header.bit_count));
270 return -EPERM;
271 }
272
273 debug("Display-bmp: %d x %d with %d colours, display %d\n",
274 (int)width, (int)height, (int)colours, 1 << bpix);
275
Simon Glass68b26272016-01-18 19:52:22 -0700276 padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width);
277
278 if (align) {
279 video_splash_align_axis(&x, priv->xsize, width);
280 video_splash_align_axis(&y, priv->ysize, height);
281 }
282
283 if ((x + width) > pwidth)
284 width = pwidth - x;
285 if ((y + height) > priv->ysize)
286 height = priv->ysize - y;
287
288 bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
Simon Glass4761c8c2020-07-02 21:12:27 -0600289 start = (uchar *)(priv->fb +
290 (y + height) * priv->line_length + x * bpix / 8);
291
292 /* Move back to the final line to be drawn */
293 fb = start - priv->line_length;
Simon Glass68b26272016-01-18 19:52:22 -0700294
295 switch (bmp_bpix) {
296 case 1:
297 case 8: {
Simon Glass68b26272016-01-18 19:52:22 -0700298#ifdef CONFIG_VIDEO_BMP_RLE8
299 u32 compression = get_unaligned_le32(&bmp->header.compression);
300 debug("compressed %d %d\n", compression, BMP_BI_RLE8);
301 if (compression == BMP_BI_RLE8) {
Simon Glass490bb992021-11-19 13:23:55 -0700302 video_display_rle8_bitmap(dev, bmp, bpix, palette, fb,
303 x, y, width, height);
Simon Glass68b26272016-01-18 19:52:22 -0700304 break;
305 }
306#endif
Ye Lib5c864d2020-06-10 02:52:23 -0700307 byte_width = width * (bpix / 8);
308 if (!byte_width)
Simon Glass68b26272016-01-18 19:52:22 -0700309 byte_width = width;
Simon Glass68b26272016-01-18 19:52:22 -0700310
311 for (i = 0; i < height; ++i) {
312 WATCHDOG_RESET();
313 for (j = 0; j < width; j++) {
Simon Glass998e16a2021-11-19 13:23:54 -0700314 write_pix8(fb, bpix, palette, bmap);
315 bmap++;
316 fb += bpix / 8;
Simon Glass68b26272016-01-18 19:52:22 -0700317 }
318 bmap += (padded_width - width);
319 fb -= byte_width + priv->line_length;
320 }
321 break;
322 }
323#if defined(CONFIG_BMP_16BPP)
324 case 16:
325 for (i = 0; i < height; ++i) {
326 WATCHDOG_RESET();
Simon Glassaa662a62021-11-19 13:23:53 -0700327 for (j = 0; j < width; j++) {
328 *fb++ = *bmap++;
329 *fb++ = *bmap++;
330 }
Sylwester Nawrocki17e65b32020-12-01 12:30:50 +0100331 bmap += (padded_width - width);
Stefan Roese956e3392016-11-12 10:32:38 +0100332 fb -= width * 2 + priv->line_length;
Simon Glass68b26272016-01-18 19:52:22 -0700333 }
334 break;
335#endif /* CONFIG_BMP_16BPP */
Philipp Tomsich5cfa3f42017-05-05 21:48:30 +0200336#if defined(CONFIG_BMP_24BPP)
Simon Glass68b26272016-01-18 19:52:22 -0700337 case 24:
338 for (i = 0; i < height; ++i) {
339 for (j = 0; j < width; j++) {
Stefan Roese7ee599e2019-01-30 08:54:12 +0100340 if (bpix == 16) {
341 /* 16bit 555RGB format */
342 *(u16 *)fb = ((bmap[2] >> 3) << 10) |
343 ((bmap[1] >> 3) << 5) |
344 (bmap[0] >> 3);
345 bmap += 3;
346 fb += 2;
347 } else {
348 *(fb++) = *(bmap++);
349 *(fb++) = *(bmap++);
350 *(fb++) = *(bmap++);
351 *(fb++) = 0;
352 }
Simon Glass68b26272016-01-18 19:52:22 -0700353 }
Stefan Roese956e3392016-11-12 10:32:38 +0100354 fb -= priv->line_length + width * (bpix / 8);
Sylwester Nawrocki17e65b32020-12-01 12:30:50 +0100355 bmap += (padded_width - width);
Simon Glass68b26272016-01-18 19:52:22 -0700356 }
357 break;
Philipp Tomsich5cfa3f42017-05-05 21:48:30 +0200358#endif /* CONFIG_BMP_24BPP */
Simon Glass68b26272016-01-18 19:52:22 -0700359#if defined(CONFIG_BMP_32BPP)
360 case 32:
361 for (i = 0; i < height; ++i) {
362 for (j = 0; j < width; j++) {
363 *(fb++) = *(bmap++);
364 *(fb++) = *(bmap++);
365 *(fb++) = *(bmap++);
366 *(fb++) = *(bmap++);
367 }
Stefan Roese956e3392016-11-12 10:32:38 +0100368 fb -= priv->line_length + width * (bpix / 8);
Simon Glass68b26272016-01-18 19:52:22 -0700369 }
370 break;
371#endif /* CONFIG_BMP_32BPP */
372 default:
373 break;
374 };
375
Simon Glass4761c8c2020-07-02 21:12:27 -0600376 /* Find the position of the top left of the image in the framebuffer */
377 fb = (uchar *)(priv->fb + y * priv->line_length + x * bpix / 8);
378 ret = video_sync_copy(dev, start, fb);
379 if (ret)
380 return log_ret(ret);
381
Michal Simek632e3d42020-12-14 08:47:52 +0100382 return video_sync(dev, false);
Simon Glass68b26272016-01-18 19:52:22 -0700383}