1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (c) 2015 Google, Inc
4  */
5 
6 #include <common.h>
7 #include <bmp_layout.h>
8 #include <dm.h>
9 #include <log.h>
10 #include <mapmem.h>
11 #include <splash.h>
12 #include <video.h>
13 #include <watchdog.h>
14 #include <asm/unaligned.h>
15 
16 #define BMP_RLE8_ESCAPE		0
17 #define BMP_RLE8_EOL		0
18 #define BMP_RLE8_EOBMP		1
19 #define BMP_RLE8_DELTA		2
20 
21 /**
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  */
get_bmp_col_16bpp(struct bmp_color_table_entry cte)26 static 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  * get_bmp_col_x2r10g10b10() - Convert a colour-table entry into a x2r10g10b10  pixel value
35  *
36  * Return: value to write to the x2r10g10b10 frame buffer for this palette entry
37  */
get_bmp_col_x2r10g10b10(struct bmp_color_table_entry * cte)38 static u32 get_bmp_col_x2r10g10b10(struct bmp_color_table_entry *cte)
39 {
40 	return ((cte->red << 22U) |
41 		(cte->green << 12U) |
42 		(cte->blue << 2U));
43 }
44 
45 /**
46  * write_pix8() - Write a pixel from a BMP image into the framebuffer
47  *
48  * This handles frame buffers with 8, 16, 24 or 32 bits per pixel
49  *
50  * @fb: Place in frame buffer to update
51  * @bpix: Frame buffer bits-per-pixel, which controls how many bytes are written
52  * @palette: BMP palette table
53  * @bmap: Pointer to BMP bitmap position to write. This contains a single byte
54  *	which is either written directly (bpix == 8) or used to look up the
55  *	palette to get a colour to write
56  */
write_pix8(u8 * fb,uint bpix,enum video_format eformat,struct bmp_color_table_entry * palette,u8 * bmap)57 static void write_pix8(u8 *fb, uint bpix, enum video_format eformat,
58 		       struct bmp_color_table_entry *palette, u8 *bmap)
59 {
60 	if (bpix == 8) {
61 		*fb++ = *bmap;
62 	} else if (bpix == 16) {
63 		*(u16 *)fb = get_bmp_col_16bpp(palette[*bmap]);
64 	} else {
65 		/* Only support big endian */
66 		struct bmp_color_table_entry *cte = &palette[*bmap];
67 
68 		if (bpix == 24) {
69 			*fb++ = cte->red;
70 			*fb++ = cte->green;
71 			*fb++ = cte->blue;
72 		} else if (eformat == VIDEO_X2R10G10B10) {
73 			*(u32 *)fb = get_bmp_col_x2r10g10b10(cte);
74 		} else {
75 			*fb++ = cte->blue;
76 			*fb++ = cte->green;
77 			*fb++ = cte->red;
78 			*fb++ = 0;
79 		}
80 	}
81 }
82 
draw_unencoded_bitmap(u8 ** fbp,uint bpix,enum video_format eformat,uchar * bmap,struct bmp_color_table_entry * palette,int cnt)83 static void draw_unencoded_bitmap(u8 **fbp, uint bpix,
84 				  enum video_format eformat, uchar *bmap,
85 				  struct bmp_color_table_entry *palette,
86 				  int cnt)
87 {
88 	u8 *fb = *fbp;
89 
90 	while (cnt > 0) {
91 		write_pix8(fb, bpix, eformat, palette, bmap++);
92 		fb += bpix / 8;
93 		cnt--;
94 	}
95 	*fbp = fb;
96 }
97 
draw_encoded_bitmap(u8 ** fbp,uint bpix,enum video_format eformat,struct bmp_color_table_entry * palette,u8 * bmap,int cnt)98 static void draw_encoded_bitmap(u8 **fbp, uint bpix, enum video_format eformat,
99 				struct bmp_color_table_entry *palette, u8 *bmap,
100 				int cnt)
101 {
102 	u8 *fb = *fbp;
103 
104 	while (cnt > 0) {
105 		write_pix8(fb, bpix, eformat, palette, bmap);
106 		fb += bpix / 8;
107 		cnt--;
108 	}
109 	*fbp = fb;
110 }
111 
video_display_rle8_bitmap(struct udevice * dev,struct bmp_image * bmp,uint bpix,struct bmp_color_table_entry * palette,uchar * fb,int x_off,int y_off,ulong width,ulong height)112 static void video_display_rle8_bitmap(struct udevice *dev,
113 				      struct bmp_image *bmp, uint bpix,
114 				      struct bmp_color_table_entry *palette,
115 				      uchar *fb, int x_off, int y_off,
116 				      ulong width, ulong height)
117 {
118 	struct video_priv *priv = dev_get_uclass_priv(dev);
119 	uchar *bmap;
120 	ulong cnt, runlen;
121 	int x, y;
122 	int decode = 1;
123 	uint bytes_per_pixel = bpix / 8;
124 	enum video_format eformat = priv->format;
125 
126 	debug("%s\n", __func__);
127 	bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
128 
129 	x = 0;
130 	y = height - 1;
131 
132 	while (decode) {
133 		if (bmap[0] == BMP_RLE8_ESCAPE) {
134 			switch (bmap[1]) {
135 			case BMP_RLE8_EOL:
136 				/* end of line */
137 				bmap += 2;
138 				x = 0;
139 				y--;
140 				fb -= width * bytes_per_pixel +
141 					priv->line_length;
142 				break;
143 			case BMP_RLE8_EOBMP:
144 				/* end of bitmap */
145 				decode = 0;
146 				break;
147 			case BMP_RLE8_DELTA:
148 				/* delta run */
149 				x += bmap[2];
150 				y -= bmap[3];
151 				fb = (uchar *)(priv->fb +
152 					(y + y_off - 1) * priv->line_length +
153 					(x + x_off) * bytes_per_pixel);
154 				bmap += 4;
155 				break;
156 			default:
157 				/* unencoded run */
158 				runlen = bmap[1];
159 				bmap += 2;
160 				if (y < height) {
161 					if (x < width) {
162 						if (x + runlen > width)
163 							cnt = width - x;
164 						else
165 							cnt = runlen;
166 						draw_unencoded_bitmap(
167 							&fb, bpix, eformat,
168 							bmap, palette, cnt);
169 					}
170 					x += runlen;
171 				}
172 				bmap += runlen;
173 				if (runlen & 1)
174 					bmap++;
175 			}
176 		} else {
177 			/* encoded run */
178 			if (y < height) {
179 				runlen = bmap[0];
180 				if (x < width) {
181 					/* aggregate the same code */
182 					while (bmap[0] == 0xff &&
183 					       bmap[2] != BMP_RLE8_ESCAPE &&
184 					       bmap[1] == bmap[3]) {
185 						runlen += bmap[2];
186 						bmap += 2;
187 					}
188 					if (x + runlen > width)
189 						cnt = width - x;
190 					else
191 						cnt = runlen;
192 					draw_encoded_bitmap(&fb, bpix, eformat,
193 							    palette, &bmap[1],
194 							    cnt);
195 				}
196 				x += runlen;
197 			}
198 			bmap += 2;
199 		}
200 	}
201 }
202 
203 /**
204  * video_splash_align_axis() - Align a single coordinate
205  *
206  *- if a coordinate is 0x7fff then the image will be centred in
207  *  that direction
208  *- if a coordinate is -ve then it will be offset to the
209  *  left/top of the centre by that many pixels
210  *- if a coordinate is positive it will be used unchnaged.
211  *
212  * @axis:	Input and output coordinate
213  * @panel_size:	Size of panel in pixels for that axis
214  * @picture_size:	Size of bitmap in pixels for that axis
215  */
video_splash_align_axis(int * axis,unsigned long panel_size,unsigned long picture_size)216 static void video_splash_align_axis(int *axis, unsigned long panel_size,
217 				    unsigned long picture_size)
218 {
219 	long panel_picture_delta = panel_size - picture_size;
220 	long axis_alignment;
221 
222 	if (*axis == BMP_ALIGN_CENTER)
223 		axis_alignment = panel_picture_delta / 2;
224 	else if (*axis < 0)
225 		axis_alignment = panel_picture_delta + *axis + 1;
226 	else
227 		return;
228 
229 	*axis = max(0, (int)axis_alignment);
230 }
231 
video_bmp_get_info(void * bmp_image,ulong * widthp,ulong * heightp,uint * bpixp)232 void video_bmp_get_info(void *bmp_image, ulong *widthp, ulong *heightp,
233 			uint *bpixp)
234 {
235 	struct bmp_image *bmp = bmp_image;
236 
237 	*widthp = get_unaligned_le32(&bmp->header.width);
238 	*heightp = get_unaligned_le32(&bmp->header.height);
239 	*bpixp = get_unaligned_le16(&bmp->header.bit_count);
240 }
241 
video_bmp_display(struct udevice * dev,ulong bmp_image,int x,int y,bool align)242 int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y,
243 		      bool align)
244 {
245 	struct video_priv *priv = dev_get_uclass_priv(dev);
246 	int i, j;
247 	uchar *start, *fb;
248 	struct bmp_image *bmp = map_sysmem(bmp_image, 0);
249 	uchar *bmap;
250 	ushort padded_width;
251 	unsigned long width, height, byte_width;
252 	unsigned long pwidth = priv->xsize;
253 	unsigned colours, bpix, bmp_bpix;
254 	enum video_format eformat;
255 	struct bmp_color_table_entry *palette;
256 	int hdr_size;
257 	int ret;
258 
259 	if (!bmp || !(bmp->header.signature[0] == 'B' &&
260 	    bmp->header.signature[1] == 'M')) {
261 		printf("Error: no valid bmp image at %lx\n", bmp_image);
262 
263 		return -EINVAL;
264 	}
265 
266 	video_bmp_get_info(bmp, &width, &height, &bmp_bpix);
267 	hdr_size = get_unaligned_le16(&bmp->header.size);
268 	debug("hdr_size=%d, bmp_bpix=%d\n", hdr_size, bmp_bpix);
269 	palette = (void *)bmp + 14 + hdr_size;
270 
271 	colours = 1 << bmp_bpix;
272 
273 	bpix = VNBITS(priv->bpix);
274 	eformat = priv->format;
275 
276 	if (bpix != 1 && bpix != 8 && bpix != 16 && bpix != 32) {
277 		printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n",
278 		       bpix, bmp_bpix);
279 
280 		return -EINVAL;
281 	}
282 
283 	/*
284 	 * We support displaying 8bpp and 24bpp BMPs on 16bpp LCDs
285 	 * and displaying 24bpp BMPs on 32bpp LCDs
286 	 */
287 	if (bpix != bmp_bpix &&
288 	    !(bmp_bpix == 8 && bpix == 16) &&
289 	    !(bmp_bpix == 8 && bpix == 24) &&
290 	    !(bmp_bpix == 8 && bpix == 32) &&
291 	    !(bmp_bpix == 24 && bpix == 16) &&
292 	    !(bmp_bpix == 24 && bpix == 32)) {
293 		printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n",
294 		       bpix, colours);
295 		return -EPERM;
296 	}
297 
298 	debug("Display-bmp: %d x %d  with %d colours, display %d\n",
299 	      (int)width, (int)height, (int)colours, 1 << bpix);
300 
301 	padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width);
302 
303 	if (align) {
304 		video_splash_align_axis(&x, priv->xsize, width);
305 		video_splash_align_axis(&y, priv->ysize, height);
306 	}
307 
308 	if ((x + width) > pwidth)
309 		width = pwidth - x;
310 	if ((y + height) > priv->ysize)
311 		height = priv->ysize - y;
312 
313 	bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
314 	start = (uchar *)(priv->fb +
315 		(y + height) * priv->line_length + x * bpix / 8);
316 
317 	/* Move back to the final line to be drawn */
318 	fb = start - priv->line_length;
319 
320 	switch (bmp_bpix) {
321 	case 1:
322 	case 8:
323 		if (CONFIG_IS_ENABLED(VIDEO_BMP_RLE8)) {
324 			u32 compression = get_unaligned_le32(
325 				&bmp->header.compression);
326 			debug("compressed %d %d\n", compression, BMP_BI_RLE8);
327 			if (compression == BMP_BI_RLE8) {
328 				video_display_rle8_bitmap(dev, bmp, bpix, palette, fb,
329 							  x, y, width, height);
330 				break;
331 			}
332 		}
333 
334 		/* Not compressed */
335 		byte_width = width * (bpix / 8);
336 		if (!byte_width)
337 			byte_width = width;
338 
339 		for (i = 0; i < height; ++i) {
340 			schedule();
341 			for (j = 0; j < width; j++) {
342 				write_pix8(fb, bpix, eformat, palette, bmap);
343 				bmap++;
344 				fb += bpix / 8;
345 			}
346 			bmap += (padded_width - width);
347 			fb -= byte_width + priv->line_length;
348 		}
349 		break;
350 	case 16:
351 		if (CONFIG_IS_ENABLED(BMP_16BPP)) {
352 			for (i = 0; i < height; ++i) {
353 				schedule();
354 				for (j = 0; j < width; j++) {
355 					*fb++ = *bmap++;
356 					*fb++ = *bmap++;
357 				}
358 				bmap += (padded_width - width);
359 				fb -= width * 2 + priv->line_length;
360 			}
361 		}
362 		break;
363 	case 24:
364 		if (CONFIG_IS_ENABLED(BMP_24BPP)) {
365 			for (i = 0; i < height; ++i) {
366 				for (j = 0; j < width; j++) {
367 					if (bpix == 16) {
368 						/* 16bit 565RGB format */
369 						*(u16 *)fb = ((bmap[2] >> 3)
370 							<< 11) |
371 							((bmap[1] >> 2) << 5) |
372 							(bmap[0] >> 3);
373 						bmap += 3;
374 						fb += 2;
375 					} else if (eformat == VIDEO_X2R10G10B10) {
376 						u32 pix;
377 
378 						pix = *bmap++ << 2U;
379 						pix |= *bmap++ << 12U;
380 						pix |= *bmap++ << 22U;
381 						*fb++ = pix & 0xff;
382 						*fb++ = (pix >> 8) & 0xff;
383 						*fb++ = (pix >> 16) & 0xff;
384 						*fb++ = pix >> 24;
385 					} else {
386 						*fb++ = *bmap++;
387 						*fb++ = *bmap++;
388 						*fb++ = *bmap++;
389 						*fb++ = 0;
390 					}
391 				}
392 				fb -= priv->line_length + width * (bpix / 8);
393 				bmap += (padded_width - width);
394 			}
395 		}
396 		break;
397 	case 32:
398 		if (CONFIG_IS_ENABLED(BMP_32BPP)) {
399 			for (i = 0; i < height; ++i) {
400 				for (j = 0; j < width; j++) {
401 					if (eformat == VIDEO_X2R10G10B10) {
402 						u32 pix;
403 
404 						pix = *bmap++ << 2U;
405 						pix |= *bmap++ << 12U;
406 						pix |= *bmap++ << 22U;
407 						pix |= (*bmap++ >> 6) << 30U;
408 						*fb++ = pix & 0xff;
409 						*fb++ = (pix >> 8) & 0xff;
410 						*fb++ = (pix >> 16) & 0xff;
411 						*fb++ = pix >> 24;
412 					} else {
413 						*fb++ = *bmap++;
414 						*fb++ = *bmap++;
415 						*fb++ = *bmap++;
416 						*fb++ = *bmap++;
417 					}
418 				}
419 				fb -= priv->line_length + width * (bpix / 8);
420 			}
421 		}
422 		break;
423 	default:
424 		break;
425 	};
426 
427 	/* Find the position of the top left of the image in the framebuffer */
428 	fb = (uchar *)(priv->fb + y * priv->line_length + x * bpix / 8);
429 	ret = video_sync_copy(dev, start, fb);
430 	if (ret)
431 		return log_ret(ret);
432 
433 	return video_sync(dev, false);
434 }
435