9. 移植u-boot-2016.03修改代码支持LCD、显示logo
前我们修改uboot的代码支持了NOR Flash、NAND Flash、DM9000网卡等,在uboot启动总是可以看到开发板的LCD由于uboot不支持出现花屏的现象,总感觉不爽。因此,这一节我们来修改uboot的代码支持LCD、显示logo。
9.1 u-boot-2016.03 LCD初始化过程分析
从前面uboot的启动过程分析,我们知道LCD的初始化在uboot启动的第二阶段,在init_sequence_r函数指针数组里,我们可以找到函数stdio_add_devices,该函数的部分代码如下:
int stdio_add_devices(void)
{
......
#ifdef CONFIG_DM_VIDEO
struct udevice *vdev;
# ifndef CONFIG_DM_KEYBOARD
int ret;
# endif
for (ret = uclass_first_device(UCLASS_VIDEO, &vdev);
vdev;
ret = uclass_next_device(&vdev))
;
if (ret)
printf("%s: Video device failed (ret=%d)\n", __func__, ret);
#else
# if defined(CONFIG_LCD)
drv_lcd_init (); /*lcd 初始化函数*/
# endif
# if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)
drv_video_init ();
# endif
#endif /* CONFIG_DM_VIDEO */
......
return 0;
}
从上面的代码可以看出,只有定义了CONFIG_LCD才会执行drv_lcd_init初始化函数。 那么我们继续跳转到drv_lcd_init函数,它的代码如下:
int drv_lcd_init(void)
{
struct stdio_dev lcddev;
int rc;
lcd_base = map_sysmem(gd->fb_base, 0); /*获取LCD framebuffer的基地址*/
lcd_init(lcd_base); /*出入LCD framebuffer的基地址,执行LCD初始化函数*/
/* 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;
}
通过阅读上面的代码可知,drv_lcd_init函数会进一步调用LCD初始化函数lcd_init(lcd_base)
;对于获取LCD framebuffer的基地址 lcd_base = map_sysmem(gd->fb_base, 0);
,gd->fb_base
在uboot初始化的第一阶段的init_sequence_f函数指针数组里的reserve_lcd函数里有设置,该函数的代码如下:
static int reserve_lcd(void)
{
# ifdef CONFIG_FB_ADDR /*CONFIG_FB_ADDR 未定义*/
gd->fb_base = CONFIG_FB_ADDR;
# else
/* reserve memory for LCD display (always full pages) */
gd->relocaddr = lcd_setmem(gd->relocaddr); /*计算LCD framebuffer的大小,然后用gd->relocaddr减去LCD framebuffer的大小得到gd->fb_base*/
gd->fb_base = gd->relocaddr;
# endif /* CONFIG_FB_ADDR */
return 0;
}
此时定义CONFIG_LCD后,uboot的内存分布图如下图所示:
lcd_init函数的代码如下:
static int lcd_init(void *lcdbase)
{
debug("[LCD] Initializing LCD frambuffer at %p\n", lcdbase);
lcd_ctrl_init(lcdbase); /*初始化LCD控制器,底层硬件相关*/
/*
* 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(); /*清屏、显示logo*/
lcd_enable();/*lcd使能*/
/* 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;
}
总结uboot LCD初始化及logo函数调用过程:
board_init_r(): common/board_r.c
... stdio_add_devices(): common/stdio.c
...... drv_lcd_init() : common/lcd.c
......... lcd_init() : common/lcd.c
............ lcd_ctrl_init() : drivers/video/<硬件相关代码文件>
............... lcd_clear():
.................. lcd_logo(): 图片显示
..................... lcd_logo_plot(0, 0): 从屏幕坐标(0,0)开始绘制图片
............... lcd_enable():与lcd_ctrl_init()同在一个文件,lcd 底层硬件使能
绘制图片函数lcd_logo_plot的代码如下:
void lcd_logo_plot(int x, int y)
{
ushort i, j;
uchar *bmap = &bmp_logo_bitmap[0]; /*将图片数组的首地址赋给bmap*/
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) {
WATCHDOG_RESET();
lcd_logo_set_cmap();
WATCHDOG_RESET();
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 */ /*因为jz2440的LCD使用RGB565的颜色显示,因此执行else分支*/
u16 col16;
fb16 = (ushort *)fb; /*把framebuffer的基地赋给fb16*/
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] = /*将图片数组数据写入framebuffer*/
((col16 & 0x000F) << 1) |
((col16 & 0x00F0) << 3) |
((col16 & 0x0F00) << 4);
}
bmap += BMP_LOGO_WIDTH;
fb16 += panel_info.vl_col;
}
}
WATCHDOG_RESET();
lcd_sync(); /*把图片数据同步到lcd(通过LCD控制器的LCDDMA自动把framebuffer的数据从内存发送到LCD,此过程无需CPU参与)*/
}
注:① 图片转化函数:tools/bmp_logo.c 将bmp文件转化为二维数组bmp_logo_bitmap[](在include/bmp_logo_data.h文件定义);
② 改变图片显示的首地址:可以修改 lcd_logo_plot(int x, int y) 函数的x、y坐标;
③ include/bmp_logo.h 定义图片大小也是tools/bmp_logo.c生成的,include/bmp_logo.h代码如下所示:
#ifndef __BMP_LOGO_H__
#define __BMP_LOGO_H__
#define BMP_LOGO_WIDTH 160 /*logo图片的宽*/
#define BMP_LOGO_HEIGHT 96 /*logo图片的高*/
#define BMP_LOGO_COLORS 31
#define BMP_LOGO_OFFSET 16
extern unsigned short bmp_logo_palette[];
extern unsigned char bmp_logo_bitmap[];
#endif /* __BMP_LOGO_H__ */
9.2 修改代码支持LCD显示
从最前面的分析可知,uboot支持LCD需用定义CONFIG_LCD,那么我们在include/configs/jz2440.h 中定义CONFIG_LCD,然后重新编译uboot,打印的错误如下:
从上面的第一个错误可知,我们没有定义panel_info,那么我们就参考其他lcd在drivers/video/s3c-fb.c中定义它,代码如下:
#ifdef CONFIG_JZ2440
vidinfo_t panel_info = {
.vl_col = LCD_XSIZE_TFT, /* 水平分辨率 */
.vl_row = LCD_YSIZE_TFT, /* 垂直分辨率 */
.vl_bpix = LCD_BPP, /* 每个像素用几位表示*/
};
#endif
同时在drivers/video/s3c-fb.c包含头文件:#include <lcd.h>
;
并且在include/configs/jz2440.h 增加如下定义:
#define CONFIG_JZ2440
#define CONFIG_VIDEO_S3C /*从drivers/video/Makefile可知,编译s3c-fb.c需要定义CONFIG_VIDEO_S3C*/
#define LCD_BPP LCD_COLOR16
#define LCD_XSIZE_TFT 480 /*开发板lcd分辨率*/
#define LCD_YSIZE_TFT 272
然后重新编译uboot,错误如下:
上图显示lcd_ctrl_init、lcd_enable函数未定义,这两个函数是与底层硬件先关的函数,需要我们在drivers/video/s3c-fb.c自己定义,我们可以参考韦东山老师的博客第017课 LCD原理详解及裸机程序分析编写lcd_ctrl_init和lcd_enable函数。
(1) 在drivers/video/s3c-fb.c定义LCD 相关信息的结构体:
enum {
NORMAL = 0, /*NORMAL : 正常极性*/
INVERT = 1, /*INVERT : 反转极性*/
};
/*定义LCD 管脚极性结构体*/
struct pins_polarity {
int de; /* normal: 高电平时可以传输数据 */
int pwren; /* normal: 高电平有效 */
int vclk; /* normal: 在下降沿获取数据 */
int rgb; /* normal: 高电平表示1 */
int hsync; /* normal: 高脉冲 */
int vsync; /* normal: 高脉冲 */
};
/*定义LCD 信息的结构体*/
struct display_info_t {
unsigned int fb_base; /*framebuffer 的基地址*/
unsigned int pixfmt; /*像素格式,也就是一个像素点是多少位*/
struct ctfb_res_modes mode;
struct pins_polarity pins;
};
static struct display_info_t display = {
.pixfmt = 16,
.mode = {
.xres = 480,
.yres = 272,
.pixclock = 100000,
.left_margin = 2,
.right_margin = 2,
.upper_margin = 2,
.lower_margin = 2,
.hsync_len = 41,
.vsync_len = 10,
},
.pins = {
.de = NORMAL,
.pwren = NORMAL,
.vclk = NORMAL,
.rgb = NORMAL,
.hsync = INVERT,
.vsync = INVERT,
},
};
(2) 修改drivers/video/s3c-fb.c文件中的s3c_lcd_init函数,通过阅读代码我们可以发现该函数没有初始化LCD相关管脚的信号极性,我们需要添加这部分的代码,首先我们添加相关的宏定义,代码如下:
#define S3CFB_LCDCON5_BSWP(x) ((x)<<1)
#define S3CFB_LCDCON5_INVPWREN(x) ((x)<<5)
#define S3CFB_LCDCON5_INVVDEN(x) ((x)<<6)
#define S3CFB_LCDCON5_INVVD(x) ((x)<<7)
#define S3CFB_LCDCON5_VSYNC(x) ((x)<<8)
#define S3CFB_LCDCON5_HSYNC(x) ((x)<<9)
#define S3CFB_LCDCON5_INVVCLK(x) ((x)<<10)
然后修改s3c_lcd_init函数,修改后的代码如下:
static void s3c_lcd_init(GraphicDevice *panel,
struct ctfb_res_modes *mode, int bpp)
{
uint32_t clk_divider;
struct s3c24x0_lcd *regs = s3c24x0_get_base_lcd();
/* Stop the controller. */
clrbits_le32(®s->lcdcon1, 1);
/* Calculate clock divider. */
clk_divider = (get_HCLK() / PS2KHZ(mode->pixclock)) / 1000;
clk_divider = DIV_ROUND_UP(clk_divider, 2);
if (clk_divider)
clk_divider -= 1;
/* Program LCD configuration. */
switch (bpp) {
case 16:
writel(S3CFB_LCDCON1_BPPMODE_TFT_16BPP |
S3CFB_LCDCON1_PNRMODE_TFT |
S3CFB_LCDCON1_CLKVAL(clk_divider),
®s->lcdcon1);
#ifdef CONFIG_JZ2440 /*以下是添加*/
writel(S3CFB_LCDCON5_HWSWP | S3CFB_LCDCON5_FRM565 |
S3CFB_LCDCON5_BSWP(0) |
S3CFB_LCDCON5_INVPWREN(display.pins.pwren) |
S3CFB_LCDCON5_INVVDEN(display.pins.de) |
S3CFB_LCDCON5_INVVD(display.pins.rgb) |
S3CFB_LCDCON5_VSYNC(display.pins.vsync) |
S3CFB_LCDCON5_HSYNC(display.pins.hsync) |
S3CFB_LCDCON5_INVVCLK(display.pins.vclk),
®s->lcdcon5);
#else /*以上是添加*/
writel(S3CFB_LCDCON5_HWSWP | S3CFB_LCDCON5_FRM565,
®s->lcdcon5);
#endif
break;
case 24:
writel(S3CFB_LCDCON1_BPPMODE_TFT_24BPP |
S3CFB_LCDCON1_PNRMODE_TFT |
S3CFB_LCDCON1_CLKVAL(clk_divider),
®s->lcdcon1);
writel(S3CFB_LCDCON5_BPP24BL, ®s->lcdcon5);
break;
}
writel(S3CFB_LCDCON2_LINEVAL(mode->yres - 1) |
S3CFB_LCDCON2_VBPD(mode->upper_margin - 1) |
S3CFB_LCDCON2_VFPD(mode->lower_margin - 1) |
S3CFB_LCDCON2_VSPW(mode->vsync_len - 1),
®s->lcdcon2);
writel(S3CFB_LCDCON3_HBPD(mode->right_margin - 1) |
S3CFB_LCDCON3_HFPD(mode->left_margin - 1) |
S3CFB_LCDCON3_HOZVAL(mode->xres - 1),
®s->lcdcon3);
writel(S3CFB_LCDCON4_HSPW(mode->hsync_len - 1),
®s->lcdcon4);
/* Write FB address. */
writel(panel->frameAdrs >> 1, ®s->lcdsaddr1);
writel((panel->frameAdrs +
(mode->xres * mode->yres * panel->gdfBytesPP)) >> 1,
®s->lcdsaddr2);
writel(mode->xres * bpp / 16, ®s->lcdsaddr3);
/* Start the controller. */
setbits_le32(®s->lcdcon1, 1);
}
(3) 编写lcd_ctrl_init与lcd_enable函数,代码如下:
#ifdef CONFIG_JZ2440
static void lcd_pin_init(void)
{
u32 val;
struct s3c24x0_gpio * const gpio = s3c24x0_get_base_gpio();
val = readl(&gpio->gpbcon);
val &= ~0x3;
val |= 0x01;
writel(val,&gpio->gpbcon);
val = 0xaaaaaaaa;
writel(val,&gpio->gpccon);
writel(val,&gpio->gpdcon);
val = readl(&gpio->gpgcon);
val |= (3<<8);
writel(val,&gpio->gpgcon);
}
void lcd_ctrl_init(void *lcdbase)
{
void *fb;
lcd_pin_init();
panel.winSizeX = display.mode.xres;
panel.winSizeY = display.mode.yres;
panel.plnSizeX = display.mode.xres;
panel.plnSizeY = display.mode.yres;
panel.gdfBytesPP = 2;
panel.gdfIndex = GDF_16BIT_565RGB;
panel.memSize = display.mode.xres * display.mode.yres * panel.gdfBytesPP;
panel.frameAdrs = (u32)lcdbase;
/* Wipe framebuffer */
memset((u32 *)panel.frameAdrs, 0, panel.memSize);
/* Start framebuffer */
s3c_lcd_init(&panel, &display.mode, display.pixfmt);
}
void lcd_enable(void)
{
u32 val;
struct s3c24x0_lcd *regs = s3c24x0_get_base_lcd();
struct s3c24x0_gpio * const gpio = s3c24x0_get_base_gpio();
val = readl(&gpio->gpbdat);
val |= (1<<0);
writel(val,&gpio->gpbdat);
val = readl(®s->lcdcon5);
val |= (1<<3);
writel(val,®s->lcdcon5);
val = readl(®s->lcdcon1);
val |= (1<<0);
writel(val,®s->lcdcon1);
}
void lcd_show_board_info(void)
{
/*在LCD显示uboot的版本信息、编译日期、编译时间*/
lcd_printf ("%s (%s - %s)\n",U_BOOT_VERSION,U_BOOT_DATE,U_BOOT_TIME);
}
#endif
重新编译,编译通过。
9.3 测试
(1) 把编译好的u-boot.bin文件烧写到开发板,开发板LCD显示如下:
串口的输出到Err: lcd
,接下来的信息就从LCD 屏幕输出了。
(2) 在键盘输入命令:setenv stdout s3ser0
,可以把标准输出更改为串口;输入命令:setenv stdout lcd
又可以去把标准输出更改LCD;
(3) 在include/configs/jz2440.h添加定义#define CONFIG_CONSOLE_MUX
,#define CONFIG_SYS_CONSOLE_IS_IN_ENV
可以支持将u-boot的打印输出同时定向到串口和LCD屏,否则只支持其中一个。(启动开发板后执行命令:setenv stdout 'lcd,serial'
即刻打印输出同时定向到串口和LCD);
(4) 如果需要显示logo, 需要在include/configs/jz2440.h添加定义#define CONFIG_LCD_LOGO
。
(5) 总的LCD相关的配置如下:
/*
*support lcd
*/
#define CONFIG_LCD
#define CONFIG_JZ2440
#define CONFIG_VIDEO_S3C
#define LCD_BPP LCD_COLOR16
#define LCD_XSIZE_TFT 480 /*开发板lcd分辨率*/
#define LCD_YSIZE_TFT 272
#define CONFIG_CONSOLE_MUX
#define CONFIG_LCD_LOGO
#define CONFIG_LCD_INFO /* 显示用户定义的信息 */
#define CONFIG_LCD_INFO_BELOW_LOGO
#define CONFIG_SYS_CONSOLE_IS_IN_ENV
启动后的效果如下图:
(6) u-boot提供了一个命令:coninfo
,执行后显示的信息如下:
从上图可以看出,uboot一共支持5个终端,lcd表示将lcd屏作为输出终端,最后的O表示这个终端只能输出,不能输入;serial表示的是串口终端,实际上跟s3ser0的效果是一样的,因为u-boot默认将串口0作为串口终端,s3ser1和s3ser2分别表示串口1和串口2,此时我们使用的是串口0;
9.3 自定义uboot显示的logo
9.3.1 制作logo
(1) uboot的logo图片必须是bmp格式图片,我们可以看到tools/logos目录下的图片都是bmp格式的图片,如下图所示:
(2) 通过 photoshop 或者其他图片制作工具把常用的jpg格式图片转为bmp格式图片:
首先修改修改uboot的像素为160*69:(也可以修改为其他像素大小的图片,这里为了减少编译后u-boot.bin文件的大小,就把图片做小为160*69像素)
然后把图片另存为bmp格式的图片:(注:在另存为bmp格式图片的过程中,需要我们选择颜色深度,一定要选这8位颜色深度,否则在LCD显示时会显示异常)
(3) 把图片拷贝到uboot的 tools/logos/ 目录,并把 denx.bmp 图片替换为我们制作好的图片;
(4) 重新编译uboot,把uboot烧写到开发板,重新启动,LCD的显示效果如下: