| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Common LCD routines |
| * |
| * (C) Copyright 2001-2002 |
| * Wolfgang Denk, DENX Software Engineering -- wd@denx.de |
| */ |
| |
| /* #define DEBUG */ |
| #include <config.h> |
| #include <common.h> |
| #include <command.h> |
| #include <cpu_func.h> |
| #include <env_callback.h> |
| #include <log.h> |
| #include <asm/cache.h> |
| #include <init.h> |
| #include <asm/global_data.h> |
| #include <linux/types.h> |
| #include <stdio_dev.h> |
| #include <lcd.h> |
| #include <mapmem.h> |
| #include <watchdog.h> |
| #include <asm/unaligned.h> |
| #include <splash.h> |
| #include <asm/io.h> |
| #include <asm/unaligned.h> |
| #include <video_font.h> |
| |
| #ifdef CONFIG_LCD_LOGO |
| #include <bmp_logo.h> |
| #include <bmp_logo_data.h> |
| #if (CONSOLE_COLOR_WHITE >= BMP_LOGO_OFFSET) && (LCD_BPP != LCD_COLOR16) |
| #error Default Color Map overlaps with Logo Color Map |
| #endif |
| #endif |
| |
| #if (LCD_BPP != LCD_COLOR8) && (LCD_BPP != LCD_COLOR16) && \ |
| (LCD_BPP != LCD_COLOR32) |
| #error Unsupported LCD BPP. |
| #endif |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| static int lcd_init(void *lcdbase); |
| static void lcd_logo(void); |
| static void lcd_setfgcolor(int color); |
| static void lcd_setbgcolor(int color); |
| |
| static int lcd_color_fg; |
| static int lcd_color_bg; |
| int lcd_line_length; |
| char lcd_is_enabled = 0; |
| static void *lcd_base; /* Start of framebuffer memory */ |
| static char lcd_flush_dcache; /* 1 to flush dcache after each lcd update */ |
| |
| /* Flush LCD activity to the caches */ |
| void lcd_sync(void) |
| { |
| /* |
| * flush_dcache_range() is declared in common.h but it seems that some |
| * architectures do not actually implement it. Is there a way to find |
| * out whether it exists? For now, ARM is safe. |
| */ |
| #if defined(CONFIG_ARM) && !CONFIG_IS_ENABLED(SYS_DCACHE_OFF) |
| int line_length; |
| |
| if (lcd_flush_dcache) |
| flush_dcache_range((ulong)lcd_base, |
| (ulong)(lcd_base + lcd_get_size(&line_length))); |
| #endif |
| } |
| |
| void lcd_set_flush_dcache(int flush) |
| { |
| lcd_flush_dcache = (flush != 0); |
| } |
| |
| static void lcd_stub_putc(struct stdio_dev *dev, const char c) |
| { |
| lcd_putc(c); |
| } |
| |
| static void lcd_stub_puts(struct stdio_dev *dev, const char *s) |
| { |
| lcd_puts(s); |
| } |
| |
| /* |
| * With most lcd drivers the line length is set up |
| * by calculating it from panel_info parameters. Some |
| * drivers need to calculate the line length differently, |
| * so make the function weak to allow overriding it. |
| */ |
| __weak int lcd_get_size(int *line_length) |
| { |
| *line_length = (panel_info.vl_col * NBITS(panel_info.vl_bpix)) / 8; |
| return *line_length * panel_info.vl_row; |
| } |
| |
| int drv_lcd_init(void) |
| { |
| struct stdio_dev lcddev; |
| int rc; |
| |
| lcd_base = map_sysmem(gd->fb_base, 0); |
| |
| lcd_init(lcd_base); |
| |
| /* Device initialization */ |
| memset(&lcddev, 0, sizeof(lcddev)); |
| |
| strcpy(lcddev.name, "lcd"); |
| lcddev.ext = 0; /* No extensions */ |
| lcddev.flags = DEV_FLAGS_OUTPUT; /* Output only */ |
| lcddev.putc = lcd_stub_putc; /* 'putc' function */ |
| lcddev.puts = lcd_stub_puts; /* 'puts' function */ |
| |
| rc = stdio_register(&lcddev); |
| |
| return (rc == 0) ? 1 : rc; |
| } |
| |
| void lcd_clear(void) |
| { |
| int bg_color; |
| __maybe_unused ulong addr; |
| static int do_splash = 1; |
| #if LCD_BPP == LCD_COLOR8 |
| /* Setting the palette */ |
| lcd_setcolreg(CONSOLE_COLOR_BLACK, 0, 0, 0); |
| lcd_setcolreg(CONSOLE_COLOR_RED, 0xFF, 0, 0); |
| lcd_setcolreg(CONSOLE_COLOR_GREEN, 0, 0xFF, 0); |
| lcd_setcolreg(CONSOLE_COLOR_YELLOW, 0xFF, 0xFF, 0); |
| lcd_setcolreg(CONSOLE_COLOR_BLUE, 0, 0, 0xFF); |
| lcd_setcolreg(CONSOLE_COLOR_MAGENTA, 0xFF, 0, 0xFF); |
| lcd_setcolreg(CONSOLE_COLOR_CYAN, 0, 0xFF, 0xFF); |
| lcd_setcolreg(CONSOLE_COLOR_GREY, 0xAA, 0xAA, 0xAA); |
| lcd_setcolreg(CONSOLE_COLOR_WHITE, 0xFF, 0xFF, 0xFF); |
| #endif |
| |
| #ifndef CONFIG_SYS_WHITE_ON_BLACK |
| lcd_setfgcolor(CONSOLE_COLOR_BLACK); |
| lcd_setbgcolor(CONSOLE_COLOR_WHITE); |
| bg_color = CONSOLE_COLOR_WHITE; |
| #else |
| lcd_setfgcolor(CONSOLE_COLOR_WHITE); |
| lcd_setbgcolor(CONSOLE_COLOR_BLACK); |
| bg_color = CONSOLE_COLOR_BLACK; |
| #endif /* CONFIG_SYS_WHITE_ON_BLACK */ |
| |
| /* set framebuffer to background color */ |
| #if (LCD_BPP != LCD_COLOR32) |
| memset((char *)lcd_base, bg_color, lcd_line_length * panel_info.vl_row); |
| #else |
| u32 *ppix = lcd_base; |
| u32 i; |
| for (i = 0; |
| i < (lcd_line_length * panel_info.vl_row)/NBYTES(panel_info.vl_bpix); |
| i++) { |
| *ppix++ = bg_color; |
| } |
| #endif |
| /* setup text-console */ |
| debug("[LCD] setting up console...\n"); |
| lcd_init_console(lcd_base, |
| panel_info.vl_col, |
| panel_info.vl_row, |
| panel_info.vl_rot); |
| /* Paint the logo and retrieve LCD base address */ |
| debug("[LCD] Drawing the logo...\n"); |
| if (do_splash) { |
| if (splash_display() == 0) { |
| do_splash = 0; |
| lcd_sync(); |
| return; |
| } |
| } |
| |
| lcd_logo(); |
| #if defined(CONFIG_LCD_LOGO) && !defined(CONFIG_LCD_INFO_BELOW_LOGO) |
| addr = (ulong)lcd_base + BMP_LOGO_HEIGHT * lcd_line_length; |
| lcd_init_console((void *)addr, panel_info.vl_col, |
| panel_info.vl_row, panel_info.vl_rot); |
| #endif |
| lcd_sync(); |
| } |
| |
| static int lcd_init(void *lcdbase) |
| { |
| debug("[LCD] Initializing LCD frambuffer at %p\n", lcdbase); |
| lcd_ctrl_init(lcdbase); |
| |
| /* |
| * lcd_ctrl_init() of some drivers (i.e. bcm2835 on rpi) ignores |
| * the 'lcdbase' argument and uses custom lcd base address |
| * by setting up gd->fb_base. Check for this condition and fixup |
| * 'lcd_base' address. |
| */ |
| if (map_to_sysmem(lcdbase) != gd->fb_base) |
| lcd_base = map_sysmem(gd->fb_base, 0); |
| |
| debug("[LCD] Using LCD frambuffer at %p\n", lcd_base); |
| |
| lcd_get_size(&lcd_line_length); |
| lcd_is_enabled = 1; |
| lcd_clear(); |
| lcd_enable(); |
| |
| /* Initialize the console */ |
| lcd_set_col(0); |
| #ifdef CONFIG_LCD_INFO_BELOW_LOGO |
| lcd_set_row(7 + BMP_LOGO_HEIGHT / VIDEO_FONT_HEIGHT); |
| #else |
| lcd_set_row(1); /* leave 1 blank line below logo */ |
| #endif |
| |
| return 0; |
| } |
| |
| /* |
| * This is called early in the system initialization to grab memory |
| * for the LCD controller. |
| * Returns new address for monitor, after reserving LCD buffer memory |
| * |
| * Note that this is running from ROM, so no write access to global data. |
| */ |
| ulong lcd_setmem(ulong addr) |
| { |
| ulong size; |
| int line_length; |
| |
| debug("LCD panel info: %d x %d, %d bit/pix\n", panel_info.vl_col, |
| panel_info.vl_row, NBITS(panel_info.vl_bpix)); |
| |
| size = lcd_get_size(&line_length); |
| |
| /* Allocate pages for the frame buffer. */ |
| addr -= size; |
| |
| debug("Reserving %ldk for LCD Framebuffer at: %08lx\n", |
| size >> 10, addr); |
| |
| return addr; |
| } |
| |
| static void lcd_setfgcolor(int color) |
| { |
| lcd_color_fg = color; |
| } |
| |
| int lcd_getfgcolor(void) |
| { |
| return lcd_color_fg; |
| } |
| |
| static void lcd_setbgcolor(int color) |
| { |
| lcd_color_bg = color; |
| } |
| |
| int lcd_getbgcolor(void) |
| { |
| return lcd_color_bg; |
| } |
| |
| #ifdef CONFIG_LCD_LOGO |
| __weak void lcd_logo_set_cmap(void) |
| { |
| int i; |
| ushort *cmap = configuration_get_cmap(); |
| |
| for (i = 0; i < ARRAY_SIZE(bmp_logo_palette); ++i) |
| *cmap++ = bmp_logo_palette[i]; |
| } |
| |
| void lcd_logo_plot(int x, int y) |
| { |
| ushort i, j; |
| uchar *bmap = &bmp_logo_bitmap[0]; |
| unsigned bpix = NBITS(panel_info.vl_bpix); |
| uchar *fb = (uchar *)(lcd_base + y * lcd_line_length + x * bpix / 8); |
| ushort *fb16; |
| |
| debug("Logo: width %d height %d colors %d\n", |
| BMP_LOGO_WIDTH, BMP_LOGO_HEIGHT, BMP_LOGO_COLORS); |
| |
| if (bpix < 12) { |
| schedule(); |
| lcd_logo_set_cmap(); |
| schedule(); |
| |
| for (i = 0; i < BMP_LOGO_HEIGHT; ++i) { |
| memcpy(fb, bmap, BMP_LOGO_WIDTH); |
| bmap += BMP_LOGO_WIDTH; |
| fb += panel_info.vl_col; |
| } |
| } |
| else { /* true color mode */ |
| u16 col16; |
| fb16 = (ushort *)fb; |
| for (i = 0; i < BMP_LOGO_HEIGHT; ++i) { |
| for (j = 0; j < BMP_LOGO_WIDTH; j++) { |
| col16 = bmp_logo_palette[(bmap[j]-16)]; |
| fb16[j] = |
| ((col16 & 0x000F) << 1) | |
| ((col16 & 0x00F0) << 3) | |
| ((col16 & 0x0F00) << 4); |
| } |
| bmap += BMP_LOGO_WIDTH; |
| fb16 += panel_info.vl_col; |
| } |
| } |
| |
| schedule(); |
| lcd_sync(); |
| } |
| #else |
| static inline void lcd_logo_plot(int x, int y) {} |
| #endif /* CONFIG_LCD_LOGO */ |
| |
| #if defined(CONFIG_CMD_BMP) || defined(CONFIG_SPLASH_SCREEN) |
| #ifdef CONFIG_SPLASH_SCREEN_ALIGN |
| |
| static void 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); |
| } |
| #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 */ |
| |
| __weak void lcd_set_cmap(struct bmp_image *bmp, unsigned colors) |
| { |
| int i; |
| struct bmp_color_table_entry cte; |
| ushort *cmap = configuration_get_cmap(); |
| |
| for (i = 0; i < colors; ++i) { |
| cte = bmp->color_table[i]; |
| *cmap = (((cte.red) << 8) & 0xf800) | |
| (((cte.green) << 3) & 0x07e0) | |
| (((cte.blue) >> 3) & 0x001f); |
| cmap++; |
| } |
| } |
| |
| int lcd_display_bitmap(ulong bmp_image, int x, int y) |
| { |
| ushort *cmap_base = NULL; |
| ushort i, j; |
| uchar *fb; |
| struct bmp_image *bmp = (struct bmp_image *)map_sysmem(bmp_image, 0); |
| uchar *bmap; |
| ushort padded_width; |
| unsigned long width, height, byte_width; |
| unsigned long pwidth = panel_info.vl_col; |
| unsigned colors, bpix, bmp_bpix; |
| int hdr_size; |
| struct bmp_color_table_entry *palette; |
| |
| if (!bmp || !(bmp->header.signature[0] == 'B' && |
| bmp->header.signature[1] == 'M')) { |
| printf("Error: no valid bmp image at %lx\n", bmp_image); |
| |
| return 1; |
| } |
| |
| palette = bmp->color_table; |
| 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); |
| |
| colors = 1 << bmp_bpix; |
| |
| bpix = NBITS(panel_info.vl_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 1; |
| } |
| |
| /* |
| * 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 1; |
| } |
| |
| debug("Display-bmp: %d x %d with %d colors, display %d\n", |
| (int)width, (int)height, (int)colors, 1 << bpix); |
| |
| if (bmp_bpix == 8) |
| lcd_set_cmap(bmp, colors); |
| |
| padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width); |
| |
| #ifdef CONFIG_SPLASH_SCREEN_ALIGN |
| splash_align_axis(&x, pwidth, width); |
| splash_align_axis(&y, panel_info.vl_row, height); |
| #endif /* CONFIG_SPLASH_SCREEN_ALIGN */ |
| |
| if ((x + width) > pwidth) |
| width = pwidth - x; |
| if ((y + height) > panel_info.vl_row) |
| height = panel_info.vl_row - y; |
| |
| bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset); |
| fb = (uchar *)(lcd_base + |
| (y + height - 1) * lcd_line_length + x * bpix / 8); |
| |
| switch (bmp_bpix) { |
| case 1: |
| case 8: { |
| cmap_base = configuration_get_cmap(); |
| |
| if (bpix != 16) |
| byte_width = width; |
| else |
| byte_width = width * 2; |
| |
| for (i = 0; i < height; ++i) { |
| schedule(); |
| for (j = 0; j < width; j++) { |
| if (bpix != 16) { |
| fb_put_byte(&fb, &bmap); |
| } else { |
| struct bmp_color_table_entry *entry; |
| uint val; |
| |
| if (cmap_base) { |
| val = cmap_base[*bmap]; |
| } else { |
| entry = &palette[*bmap]; |
| val = entry->blue >> 3 | |
| entry->green >> 2 << 5 | |
| entry->red >> 3 << 11; |
| } |
| *(uint16_t *)fb = val; |
| bmap++; |
| fb += sizeof(uint16_t) / sizeof(*fb); |
| } |
| } |
| bmap += (padded_width - width); |
| fb -= byte_width + lcd_line_length; |
| } |
| break; |
| } |
| #if defined(CONFIG_BMP_16BPP) |
| case 16: |
| for (i = 0; i < height; ++i) { |
| schedule(); |
| 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_24BPP) |
| 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_24BPP */ |
| #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; |
| }; |
| |
| lcd_sync(); |
| return 0; |
| } |
| #endif |
| |
| static void lcd_logo(void) |
| { |
| lcd_logo_plot(0, 0); |
| |
| #ifdef CONFIG_LCD_INFO |
| lcd_set_col(LCD_INFO_X / VIDEO_FONT_WIDTH); |
| lcd_set_row(LCD_INFO_Y / VIDEO_FONT_HEIGHT); |
| lcd_show_board_info(); |
| #endif /* CONFIG_LCD_INFO */ |
| } |
| |
| #ifdef CONFIG_SPLASHIMAGE_GUARD |
| static int on_splashimage(const char *name, const char *value, enum env_op op, |
| int flags) |
| { |
| ulong addr; |
| int aligned; |
| |
| if (op == env_op_delete) |
| return 0; |
| |
| addr = hextoul(value, NULL); |
| /* See README.displaying-bmps */ |
| aligned = (addr % 4 == 2); |
| if (!aligned) { |
| printf("Invalid splashimage value. Value must be 16 bit aligned, but not 32 bit aligned\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| U_BOOT_ENV_CALLBACK(splashimage, on_splashimage); |
| #endif |
| |
| int lcd_get_pixel_width(void) |
| { |
| return panel_info.vl_col; |
| } |
| |
| int lcd_get_pixel_height(void) |
| { |
| return panel_info.vl_row; |
| } |