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 <mapmem.h> 10 #include <splash.h> 11 #include <video.h> 12 #include <watchdog.h> 13 #include <asm/unaligned.h> 14 15 #ifdef CONFIG_VIDEO_BMP_RLE8 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 static void draw_unencoded_bitmap(ushort **fbp, uchar *bmap, ushort *cmap, 22 int cnt) 23 { 24 while (cnt > 0) { 25 *(*fbp)++ = cmap[*bmap++]; 26 cnt--; 27 } 28 } 29 30 static void draw_encoded_bitmap(ushort **fbp, ushort col, int cnt) 31 { 32 ushort *fb = *fbp; 33 34 while (cnt > 0) { 35 *fb++ = col; 36 cnt--; 37 } 38 *fbp = fb; 39 } 40 41 static void video_display_rle8_bitmap(struct udevice *dev, 42 struct bmp_image *bmp, ushort *cmap, 43 uchar *fb, int x_off, int y_off) 44 { 45 struct video_priv *priv = dev_get_uclass_priv(dev); 46 uchar *bmap; 47 ulong width, height; 48 ulong cnt, runlen; 49 int x, y; 50 int decode = 1; 51 52 debug("%s\n", __func__); 53 width = get_unaligned_le32(&bmp->header.width); 54 height = get_unaligned_le32(&bmp->header.height); 55 bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset); 56 57 x = 0; 58 y = height - 1; 59 60 while (decode) { 61 if (bmap[0] == BMP_RLE8_ESCAPE) { 62 switch (bmap[1]) { 63 case BMP_RLE8_EOL: 64 /* end of line */ 65 bmap += 2; 66 x = 0; 67 y--; 68 /* 16bpix, 2-byte per pixel, width should *2 */ 69 fb -= (width * 2 + priv->line_length); 70 break; 71 case BMP_RLE8_EOBMP: 72 /* end of bitmap */ 73 decode = 0; 74 break; 75 case BMP_RLE8_DELTA: 76 /* delta run */ 77 x += bmap[2]; 78 y -= bmap[3]; 79 /* 16bpix, 2-byte per pixel, x should *2 */ 80 fb = (uchar *)(priv->fb + (y + y_off - 1) 81 * priv->line_length + (x + x_off) * 2); 82 bmap += 4; 83 break; 84 default: 85 /* unencoded run */ 86 runlen = bmap[1]; 87 bmap += 2; 88 if (y < height) { 89 if (x < width) { 90 if (x + runlen > width) 91 cnt = width - x; 92 else 93 cnt = runlen; 94 draw_unencoded_bitmap( 95 (ushort **)&fb, 96 bmap, cmap, cnt); 97 } 98 x += runlen; 99 } 100 bmap += runlen; 101 if (runlen & 1) 102 bmap++; 103 } 104 } else { 105 /* encoded run */ 106 if (y < height) { 107 runlen = bmap[0]; 108 if (x < width) { 109 /* aggregate the same code */ 110 while (bmap[0] == 0xff && 111 bmap[2] != BMP_RLE8_ESCAPE && 112 bmap[1] == bmap[3]) { 113 runlen += bmap[2]; 114 bmap += 2; 115 } 116 if (x + runlen > width) 117 cnt = width - x; 118 else 119 cnt = runlen; 120 draw_encoded_bitmap((ushort **)&fb, 121 cmap[bmap[1]], cnt); 122 } 123 x += runlen; 124 } 125 bmap += 2; 126 } 127 } 128 } 129 #endif 130 131 __weak void fb_put_byte(uchar **fb, uchar **from) 132 { 133 *(*fb)++ = *(*from)++; 134 } 135 136 #if defined(CONFIG_BMP_16BPP) 137 __weak void fb_put_word(uchar **fb, uchar **from) 138 { 139 *(*fb)++ = *(*from)++; 140 *(*fb)++ = *(*from)++; 141 } 142 #endif /* CONFIG_BMP_16BPP */ 143 144 /** 145 * video_splash_align_axis() - Align a single coordinate 146 * 147 *- if a coordinate is 0x7fff then the image will be centred in 148 * that direction 149 *- if a coordinate is -ve then it will be offset to the 150 * left/top of the centre by that many pixels 151 *- if a coordinate is positive it will be used unchnaged. 152 * 153 * @axis: Input and output coordinate 154 * @panel_size: Size of panel in pixels for that axis 155 * @picture_size: Size of bitmap in pixels for that axis 156 */ 157 static void video_splash_align_axis(int *axis, unsigned long panel_size, 158 unsigned long picture_size) 159 { 160 unsigned long panel_picture_delta = panel_size - picture_size; 161 unsigned long axis_alignment; 162 163 if (*axis == BMP_ALIGN_CENTER) 164 axis_alignment = panel_picture_delta / 2; 165 else if (*axis < 0) 166 axis_alignment = panel_picture_delta + *axis + 1; 167 else 168 return; 169 170 *axis = max(0, (int)axis_alignment); 171 } 172 173 static void video_set_cmap(struct udevice *dev, 174 struct bmp_color_table_entry *cte, unsigned colours) 175 { 176 struct video_priv *priv = dev_get_uclass_priv(dev); 177 int i; 178 ushort *cmap = priv->cmap; 179 180 debug("%s: colours=%d\n", __func__, colours); 181 for (i = 0; i < colours; ++i) { 182 *cmap = ((cte->red << 8) & 0xf800) | 183 ((cte->green << 3) & 0x07e0) | 184 ((cte->blue >> 3) & 0x001f); 185 cmap++; 186 cte++; 187 } 188 } 189 190 int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y, 191 bool align) 192 { 193 struct video_priv *priv = dev_get_uclass_priv(dev); 194 ushort *cmap_base = NULL; 195 int i, j; 196 uchar *fb; 197 struct bmp_image *bmp = map_sysmem(bmp_image, 0); 198 uchar *bmap; 199 ushort padded_width; 200 unsigned long width, height, byte_width; 201 unsigned long pwidth = priv->xsize; 202 unsigned colours, bpix, bmp_bpix; 203 struct bmp_color_table_entry *palette; 204 int hdr_size; 205 206 if (!bmp || !(bmp->header.signature[0] == 'B' && 207 bmp->header.signature[1] == 'M')) { 208 printf("Error: no valid bmp image at %lx\n", bmp_image); 209 210 return -EINVAL; 211 } 212 213 width = get_unaligned_le32(&bmp->header.width); 214 height = get_unaligned_le32(&bmp->header.height); 215 bmp_bpix = get_unaligned_le16(&bmp->header.bit_count); 216 hdr_size = get_unaligned_le16(&bmp->header.size); 217 debug("hdr_size=%d, bmp_bpix=%d\n", hdr_size, bmp_bpix); 218 palette = (void *)bmp + 14 + hdr_size; 219 220 colours = 1 << bmp_bpix; 221 222 bpix = VNBITS(priv->bpix); 223 224 if (bpix != 1 && bpix != 8 && bpix != 16 && bpix != 32) { 225 printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n", 226 bpix, bmp_bpix); 227 228 return -EINVAL; 229 } 230 231 /* 232 * We support displaying 8bpp BMPs on 16bpp LCDs 233 * and displaying 24bpp BMPs on 32bpp LCDs 234 * */ 235 if (bpix != bmp_bpix && 236 !(bmp_bpix == 8 && bpix == 16) && 237 !(bmp_bpix == 24 && bpix == 32)) { 238 printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n", 239 bpix, get_unaligned_le16(&bmp->header.bit_count)); 240 return -EPERM; 241 } 242 243 debug("Display-bmp: %d x %d with %d colours, display %d\n", 244 (int)width, (int)height, (int)colours, 1 << bpix); 245 246 if (bmp_bpix == 8) 247 video_set_cmap(dev, palette, colours); 248 249 padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width); 250 251 if (align) { 252 video_splash_align_axis(&x, priv->xsize, width); 253 video_splash_align_axis(&y, priv->ysize, height); 254 } 255 256 if ((x + width) > pwidth) 257 width = pwidth - x; 258 if ((y + height) > priv->ysize) 259 height = priv->ysize - y; 260 261 bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset); 262 fb = (uchar *)(priv->fb + 263 (y + height - 1) * priv->line_length + x * bpix / 8); 264 265 switch (bmp_bpix) { 266 case 1: 267 case 8: { 268 cmap_base = priv->cmap; 269 #ifdef CONFIG_VIDEO_BMP_RLE8 270 u32 compression = get_unaligned_le32(&bmp->header.compression); 271 debug("compressed %d %d\n", compression, BMP_BI_RLE8); 272 if (compression == BMP_BI_RLE8) { 273 if (bpix != 16) { 274 /* TODO implement render code for bpix != 16 */ 275 printf("Error: only support 16 bpix"); 276 return -EPROTONOSUPPORT; 277 } 278 video_display_rle8_bitmap(dev, bmp, cmap_base, fb, x, 279 y); 280 break; 281 } 282 #endif 283 284 if (bpix != 16) 285 byte_width = width; 286 else 287 byte_width = width * 2; 288 289 for (i = 0; i < height; ++i) { 290 WATCHDOG_RESET(); 291 for (j = 0; j < width; j++) { 292 if (bpix != 16) { 293 fb_put_byte(&fb, &bmap); 294 } else { 295 *(uint16_t *)fb = cmap_base[*bmap]; 296 bmap++; 297 fb += sizeof(uint16_t) / sizeof(*fb); 298 } 299 } 300 bmap += (padded_width - width); 301 fb -= byte_width + priv->line_length; 302 } 303 break; 304 } 305 #if defined(CONFIG_BMP_16BPP) 306 case 16: 307 for (i = 0; i < height; ++i) { 308 WATCHDOG_RESET(); 309 for (j = 0; j < width; j++) 310 fb_put_word(&fb, &bmap); 311 312 bmap += (padded_width - width) * 2; 313 fb -= width * 2 + priv->line_length; 314 } 315 break; 316 #endif /* CONFIG_BMP_16BPP */ 317 #if defined(CONFIG_BMP_24BPP) 318 case 24: 319 for (i = 0; i < height; ++i) { 320 for (j = 0; j < width; j++) { 321 *(fb++) = *(bmap++); 322 *(fb++) = *(bmap++); 323 *(fb++) = *(bmap++); 324 *(fb++) = 0; 325 } 326 fb -= priv->line_length + width * (bpix / 8); 327 } 328 break; 329 #endif /* CONFIG_BMP_24BPP */ 330 #if defined(CONFIG_BMP_32BPP) 331 case 32: 332 for (i = 0; i < height; ++i) { 333 for (j = 0; j < width; j++) { 334 *(fb++) = *(bmap++); 335 *(fb++) = *(bmap++); 336 *(fb++) = *(bmap++); 337 *(fb++) = *(bmap++); 338 } 339 fb -= priv->line_length + width * (bpix / 8); 340 } 341 break; 342 #endif /* CONFIG_BMP_32BPP */ 343 default: 344 break; 345 }; 346 347 video_sync(dev, false); 348 349 return 0; 350 } 351 352