1 /* 2 * Copyright (c) 2015 Google, Inc 3 * 4 * SPDX-License-Identifier: GPL-2.0+ 5 */ 6 7 #include <common.h> 8 #include <bmp_layout.h> 9 #include <dm.h> 10 #include <mapmem.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 #define BMP_ALIGN_CENTER 0x7fff 145 146 /** 147 * video_splash_align_axis() - Align a single coordinate 148 * 149 *- if a coordinate is 0x7fff then the image will be centred in 150 * that direction 151 *- if a coordinate is -ve then it will be offset to the 152 * left/top of the centre by that many pixels 153 *- if a coordinate is positive it will be used unchnaged. 154 * 155 * @axis: Input and output coordinate 156 * @panel_size: Size of panel in pixels for that axis 157 * @picture_size: Size of bitmap in pixels for that axis 158 */ 159 static void video_splash_align_axis(int *axis, unsigned long panel_size, 160 unsigned long picture_size) 161 { 162 unsigned long panel_picture_delta = panel_size - picture_size; 163 unsigned long axis_alignment; 164 165 if (*axis == BMP_ALIGN_CENTER) 166 axis_alignment = panel_picture_delta / 2; 167 else if (*axis < 0) 168 axis_alignment = panel_picture_delta + *axis + 1; 169 else 170 return; 171 172 *axis = max(0, (int)axis_alignment); 173 } 174 175 static void video_set_cmap(struct udevice *dev, 176 struct bmp_color_table_entry *cte, unsigned colours) 177 { 178 struct video_priv *priv = dev_get_uclass_priv(dev); 179 int i; 180 ushort *cmap = priv->cmap; 181 182 debug("%s: colours=%d\n", __func__, colours); 183 for (i = 0; i < colours; ++i) { 184 *cmap = ((cte->red << 8) & 0xf800) | 185 ((cte->green << 3) & 0x07e0) | 186 ((cte->blue >> 3) & 0x001f); 187 cmap++; 188 cte++; 189 } 190 } 191 192 int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y, 193 bool align) 194 { 195 struct video_priv *priv = dev_get_uclass_priv(dev); 196 ushort *cmap_base = NULL; 197 int i, j; 198 uchar *fb; 199 struct bmp_image *bmp = map_sysmem(bmp_image, 0); 200 uchar *bmap; 201 ushort padded_width; 202 unsigned long width, height, byte_width; 203 unsigned long pwidth = priv->xsize; 204 unsigned colours, bpix, bmp_bpix; 205 struct bmp_color_table_entry *palette; 206 int hdr_size; 207 208 if (!bmp || !(bmp->header.signature[0] == 'B' && 209 bmp->header.signature[1] == 'M')) { 210 printf("Error: no valid bmp image at %lx\n", bmp_image); 211 212 return -EINVAL; 213 } 214 215 width = get_unaligned_le32(&bmp->header.width); 216 height = get_unaligned_le32(&bmp->header.height); 217 bmp_bpix = get_unaligned_le16(&bmp->header.bit_count); 218 hdr_size = get_unaligned_le16(&bmp->header.size); 219 debug("hdr_size=%d, bmp_bpix=%d\n", hdr_size, bmp_bpix); 220 palette = (void *)bmp + 14 + hdr_size; 221 222 colours = 1 << bmp_bpix; 223 224 bpix = VNBITS(priv->bpix); 225 226 if (bpix != 1 && bpix != 8 && bpix != 16 && bpix != 32) { 227 printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n", 228 bpix, bmp_bpix); 229 230 return -EINVAL; 231 } 232 233 /* 234 * We support displaying 8bpp BMPs on 16bpp LCDs 235 * and displaying 24bpp BMPs on 32bpp LCDs 236 * */ 237 if (bpix != bmp_bpix && 238 !(bmp_bpix == 8 && bpix == 16) && 239 !(bmp_bpix == 24 && bpix == 32)) { 240 printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n", 241 bpix, get_unaligned_le16(&bmp->header.bit_count)); 242 return -EPERM; 243 } 244 245 debug("Display-bmp: %d x %d with %d colours, display %d\n", 246 (int)width, (int)height, (int)colours, 1 << bpix); 247 248 if (bmp_bpix == 8) 249 video_set_cmap(dev, palette, colours); 250 251 padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width); 252 253 if (align) { 254 video_splash_align_axis(&x, priv->xsize, width); 255 video_splash_align_axis(&y, priv->ysize, height); 256 } 257 258 if ((x + width) > pwidth) 259 width = pwidth - x; 260 if ((y + height) > priv->ysize) 261 height = priv->ysize - y; 262 263 bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset); 264 fb = (uchar *)(priv->fb + 265 (y + height - 1) * priv->line_length + x * bpix / 8); 266 267 switch (bmp_bpix) { 268 case 1: 269 case 8: { 270 cmap_base = priv->cmap; 271 #ifdef CONFIG_VIDEO_BMP_RLE8 272 u32 compression = get_unaligned_le32(&bmp->header.compression); 273 debug("compressed %d %d\n", compression, BMP_BI_RLE8); 274 if (compression == BMP_BI_RLE8) { 275 if (bpix != 16) { 276 /* TODO implement render code for bpix != 16 */ 277 printf("Error: only support 16 bpix"); 278 return -EPROTONOSUPPORT; 279 } 280 video_display_rle8_bitmap(dev, bmp, cmap_base, fb, x, 281 y); 282 break; 283 } 284 #endif 285 286 if (bpix != 16) 287 byte_width = width; 288 else 289 byte_width = width * 2; 290 291 for (i = 0; i < height; ++i) { 292 WATCHDOG_RESET(); 293 for (j = 0; j < width; j++) { 294 if (bpix != 16) { 295 fb_put_byte(&fb, &bmap); 296 } else { 297 *(uint16_t *)fb = cmap_base[*bmap]; 298 bmap++; 299 fb += sizeof(uint16_t) / sizeof(*fb); 300 } 301 } 302 bmap += (padded_width - width); 303 fb -= byte_width + priv->line_length; 304 } 305 break; 306 } 307 #if defined(CONFIG_BMP_16BPP) 308 case 16: 309 for (i = 0; i < height; ++i) { 310 WATCHDOG_RESET(); 311 for (j = 0; j < width; j++) 312 fb_put_word(&fb, &bmap); 313 314 bmap += (padded_width - width) * 2; 315 fb -= width * 2 + priv->line_length; 316 } 317 break; 318 #endif /* CONFIG_BMP_16BPP */ 319 #if defined(CONFIG_BMP_24BMP) 320 case 24: 321 for (i = 0; i < height; ++i) { 322 for (j = 0; j < width; j++) { 323 *(fb++) = *(bmap++); 324 *(fb++) = *(bmap++); 325 *(fb++) = *(bmap++); 326 *(fb++) = 0; 327 } 328 fb -= priv->line_length + width * (bpix / 8); 329 } 330 break; 331 #endif /* CONFIG_BMP_24BMP */ 332 #if defined(CONFIG_BMP_32BPP) 333 case 32: 334 for (i = 0; i < height; ++i) { 335 for (j = 0; j < width; j++) { 336 *(fb++) = *(bmap++); 337 *(fb++) = *(bmap++); 338 *(fb++) = *(bmap++); 339 *(fb++) = *(bmap++); 340 } 341 fb -= priv->line_length + width * (bpix / 8); 342 } 343 break; 344 #endif /* CONFIG_BMP_32BPP */ 345 default: 346 break; 347 }; 348 349 video_sync(dev); 350 351 return 0; 352 } 353 354