diff options
-rw-r--r-- | common/cmd_bmp.c | 22 | ||||
-rw-r--r-- | drivers/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/video_bmp.c | 353 |
3 files changed, 375 insertions, 1 deletions
diff --git a/common/cmd_bmp.c b/common/cmd_bmp.c index f04b7d4866a..fd5b7db2885 100644 --- a/common/cmd_bmp.c +++ b/common/cmd_bmp.c @@ -10,7 +10,9 @@ */ #include <common.h> +#include <dm.h> #include <lcd.h> +#include <mapmem.h> #include <bmp_layout.h> #include <command.h> #include <asm/byteorder.h> @@ -225,6 +227,9 @@ static int bmp_info(ulong addr) */ int bmp_display(ulong addr, int x, int y) { +#ifdef CONFIG_DM_VIDEO + struct udevice *dev; +#endif int ret; struct bmp_image *bmp = map_sysmem(addr, 0); void *bmp_alloc_addr = NULL; @@ -240,7 +245,22 @@ int bmp_display(ulong addr, int x, int y) } addr = map_to_sysmem(bmp); -#if defined(CONFIG_LCD) +#ifdef CONFIG_DM_VIDEO + ret = uclass_first_device(UCLASS_VIDEO, &dev); + if (!ret) { + if (!dev) + ret = -ENODEV; + if (!ret) { + bool align = false; + +# ifdef CONFIG_SPLASH_SCREEN_ALIGN + align = true; +# endif /* CONFIG_SPLASH_SCREEN_ALIGN */ + ret = video_bmp_display(dev, addr, x, y, align); + } + } + return ret ? CMD_RET_FAILURE : 0; +#elif defined(CONFIG_LCD) ret = lcd_display_bitmap(addr, x, y); #elif defined(CONFIG_VIDEO) ret = video_display_bitmap(addr, x, y); diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 8f26d1d94ec..ee046296e61 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -8,6 +8,7 @@ ifdef CONFIG_DM obj-$(CONFIG_DISPLAY_PORT) += dp-uclass.o obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o console_normal.o +obj-$(CONFIG_DM_VIDEO) += video_bmp.o obj-$(CONFIG_VIDEO_ROTATION) += console_rotate.o endif diff --git a/drivers/video/video_bmp.c b/drivers/video/video_bmp.c new file mode 100644 index 00000000000..c9075d62dd7 --- /dev/null +++ b/drivers/video/video_bmp.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2015 Google, Inc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <bmp_layout.h> +#include <dm.h> +#include <mapmem.h> +#include <video.h> +#include <watchdog.h> +#include <asm/unaligned.h> + +#ifdef CONFIG_VIDEO_BMP_RLE8 +#define BMP_RLE8_ESCAPE 0 +#define BMP_RLE8_EOL 0 +#define BMP_RLE8_EOBMP 1 +#define BMP_RLE8_DELTA 2 + +static void draw_unencoded_bitmap(ushort **fbp, uchar *bmap, ushort *cmap, + int cnt) +{ + while (cnt > 0) { + *(*fbp)++ = cmap[*bmap++]; + cnt--; + } +} + +static void draw_encoded_bitmap(ushort **fbp, ushort col, int cnt) +{ + ushort *fb = *fbp; + + while (cnt > 0) { + *fb++ = col; + cnt--; + } + *fbp = fb; +} + +static void video_display_rle8_bitmap(struct udevice *dev, + struct bmp_image *bmp, ushort *cmap, + uchar *fb, int x_off, int y_off) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + uchar *bmap; + ulong width, height; + ulong cnt, runlen; + int x, y; + int decode = 1; + + debug("%s\n", __func__); + width = get_unaligned_le32(&bmp->header.width); + height = get_unaligned_le32(&bmp->header.height); + bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset); + + x = 0; + y = height - 1; + + while (decode) { + if (bmap[0] == BMP_RLE8_ESCAPE) { + switch (bmap[1]) { + case BMP_RLE8_EOL: + /* end of line */ + bmap += 2; + x = 0; + y--; + /* 16bpix, 2-byte per pixel, width should *2 */ + fb -= (width * 2 + priv->line_length); + break; + case BMP_RLE8_EOBMP: + /* end of bitmap */ + decode = 0; + break; + case BMP_RLE8_DELTA: + /* delta run */ + x += bmap[2]; + y -= bmap[3]; + /* 16bpix, 2-byte per pixel, x should *2 */ + fb = (uchar *)(priv->fb + (y + y_off - 1) + * priv->line_length + (x + x_off) * 2); + bmap += 4; + break; + default: + /* unencoded run */ + runlen = bmap[1]; + bmap += 2; + if (y < height) { + if (x < width) { + if (x + runlen > width) + cnt = width - x; + else + cnt = runlen; + draw_unencoded_bitmap( + (ushort **)&fb, + bmap, cmap, cnt); + } + x += runlen; + } + bmap += runlen; + if (runlen & 1) + bmap++; + } + } else { + /* encoded run */ + if (y < height) { + runlen = bmap[0]; + if (x < width) { + /* aggregate the same code */ + while (bmap[0] == 0xff && + bmap[2] != BMP_RLE8_ESCAPE && + bmap[1] == bmap[3]) { + runlen += bmap[2]; + bmap += 2; + } + if (x + runlen > width) + cnt = width - x; + else + cnt = runlen; + draw_encoded_bitmap((ushort **)&fb, + cmap[bmap[1]], cnt); + } + x += runlen; + } + bmap += 2; + } + } +} +#endif + +__weak void fb_put_byte(uchar **fb, uchar **from) +{ + *(*fb)++ = *(*from)++; +} + +#if defined(CONFIG_BMP_16BPP) +__weak void fb_put_word(uchar **fb, uchar **from) +{ + *(*fb)++ = *(*from)++; + *(*fb)++ = *(*from)++; +} +#endif /* CONFIG_BMP_16BPP */ + +#define BMP_ALIGN_CENTER 0x7fff + +/** + * video_splash_align_axis() - Align a single coordinate + * + *- if a coordinate is 0x7fff then the image will be centred in + * that direction + *- if a coordinate is -ve then it will be offset to the + * left/top of the centre by that many pixels + *- if a coordinate is positive it will be used unchnaged. + * + * @axis: Input and output coordinate + * @panel_size: Size of panel in pixels for that axis + * @picture_size: Size of bitmap in pixels for that axis + */ +static void video_splash_align_axis(int *axis, unsigned long panel_size, + unsigned long picture_size) +{ + unsigned long panel_picture_delta = panel_size - picture_size; + unsigned long axis_alignment; + + if (*axis == BMP_ALIGN_CENTER) + axis_alignment = panel_picture_delta / 2; + else if (*axis < 0) + axis_alignment = panel_picture_delta + *axis + 1; + else + return; + + *axis = max(0, (int)axis_alignment); +} + +static void video_set_cmap(struct udevice *dev, + struct bmp_color_table_entry *cte, unsigned colours) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + int i; + ushort *cmap = priv->cmap; + + debug("%s: colours=%d\n", __func__, colours); + for (i = 0; i < colours; ++i) { + *cmap = ((cte->red << 8) & 0xf800) | + ((cte->green << 3) & 0x07e0) | + ((cte->blue >> 3) & 0x001f); + cmap++; + cte++; + } +} + +int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y, + bool align) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + ushort *cmap_base = NULL; + ushort i, j; + uchar *fb; + struct bmp_image *bmp = map_sysmem(bmp_image, 0); + uchar *bmap; + ushort padded_width; + unsigned long width, height, byte_width; + unsigned long pwidth = priv->xsize; + unsigned colours, bpix, bmp_bpix; + struct bmp_color_table_entry *palette; + int hdr_size; + + if (!bmp || !(bmp->header.signature[0] == 'B' && + bmp->header.signature[1] == 'M')) { + printf("Error: no valid bmp image at %lx\n", bmp_image); + + return -EINVAL; + } + + width = get_unaligned_le32(&bmp->header.width); + height = get_unaligned_le32(&bmp->header.height); + bmp_bpix = get_unaligned_le16(&bmp->header.bit_count); + hdr_size = get_unaligned_le16(&bmp->header.size); + debug("hdr_size=%d, bmp_bpix=%d\n", hdr_size, bmp_bpix); + palette = (void *)bmp + 14 + hdr_size; + + colours = 1 << bmp_bpix; + + bpix = VNBITS(priv->bpix); + + if (bpix != 1 && bpix != 8 && bpix != 16 && bpix != 32) { + printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n", + bpix, bmp_bpix); + + return -EINVAL; + } + + /* + * We support displaying 8bpp BMPs on 16bpp LCDs + * and displaying 24bpp BMPs on 32bpp LCDs + * */ + if (bpix != bmp_bpix && + !(bmp_bpix == 8 && bpix == 16) && + !(bmp_bpix == 24 && bpix == 32)) { + printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n", + bpix, get_unaligned_le16(&bmp->header.bit_count)); + return -EPERM; + } + + debug("Display-bmp: %d x %d with %d colours, display %d\n", + (int)width, (int)height, (int)colours, 1 << bpix); + + if (bmp_bpix == 8) + video_set_cmap(dev, palette, colours); + + padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width); + + if (align) { + video_splash_align_axis(&x, priv->xsize, width); + video_splash_align_axis(&y, priv->ysize, height); + } + + if ((x + width) > pwidth) + width = pwidth - x; + if ((y + height) > priv->ysize) + height = priv->ysize - y; + + bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset); + fb = (uchar *)(priv->fb + + (y + height - 1) * priv->line_length + x * bpix / 8); + + switch (bmp_bpix) { + case 1: + case 8: { + cmap_base = priv->cmap; +#ifdef CONFIG_VIDEO_BMP_RLE8 + u32 compression = get_unaligned_le32(&bmp->header.compression); + debug("compressed %d %d\n", compression, BMP_BI_RLE8); + if (compression == BMP_BI_RLE8) { + if (bpix != 16) { + /* TODO implement render code for bpix != 16 */ + printf("Error: only support 16 bpix"); + return -EPROTONOSUPPORT; + } + video_display_rle8_bitmap(dev, bmp, cmap_base, fb, x, + y); + break; + } +#endif + + if (bpix != 16) + byte_width = width; + else + byte_width = width * 2; + + for (i = 0; i < height; ++i) { + WATCHDOG_RESET(); + for (j = 0; j < width; j++) { + if (bpix != 16) { + fb_put_byte(&fb, &bmap); + } else { + *(uint16_t *)fb = cmap_base[*bmap]; + bmap++; + fb += sizeof(uint16_t) / sizeof(*fb); + } + } + bmap += (padded_width - width); + fb -= byte_width + priv->line_length; + } + break; + } +#if defined(CONFIG_BMP_16BPP) + case 16: + for (i = 0; i < height; ++i) { + WATCHDOG_RESET(); + for (j = 0; j < width; j++) + fb_put_word(&fb, &bmap); + + bmap += (padded_width - width) * 2; + fb -= width * 2 + lcd_line_length; + } + break; +#endif /* CONFIG_BMP_16BPP */ +#if defined(CONFIG_BMP_24BMP) + case 24: + for (i = 0; i < height; ++i) { + for (j = 0; j < width; j++) { + *(fb++) = *(bmap++); + *(fb++) = *(bmap++); + *(fb++) = *(bmap++); + *(fb++) = 0; + } + fb -= lcd_line_length + width * (bpix / 8); + } + break; +#endif /* CONFIG_BMP_24BMP */ +#if defined(CONFIG_BMP_32BPP) + case 32: + for (i = 0; i < height; ++i) { + for (j = 0; j < width; j++) { + *(fb++) = *(bmap++); + *(fb++) = *(bmap++); + *(fb++) = *(bmap++); + *(fb++) = *(bmap++); + } + fb -= lcd_line_length + width * (bpix / 8); + } + break; +#endif /* CONFIG_BMP_32BPP */ + default: + break; + }; + + video_sync(dev); + + return 0; +} + |