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