九、移植u-boot-2016.03到Jz2440之修改源码支持LCD、显示logo

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.cbmp文件转化为二维数组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_initlcd_enable函数未定义,这两个函数是与底层硬件先关的函数,需要我们在drivers/video/s3c-fb.c自己定义,我们可以参考韦东山老师的博客第017课 LCD原理详解及裸机程序分析编写lcd_ctrl_initlcd_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(&regs->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),
		       &regs->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),
	           &regs->lcdcon5);
#else /*以上是添加*/
		writel(S3CFB_LCDCON5_HWSWP | S3CFB_LCDCON5_FRM565,
		       &regs->lcdcon5);
#endif
		break;
	case 24:
		writel(S3CFB_LCDCON1_BPPMODE_TFT_24BPP |
		       S3CFB_LCDCON1_PNRMODE_TFT |
		       S3CFB_LCDCON1_CLKVAL(clk_divider),
		       &regs->lcdcon1);
		writel(S3CFB_LCDCON5_BPP24BL, &regs->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),
	       &regs->lcdcon2);

	writel(S3CFB_LCDCON3_HBPD(mode->right_margin - 1) |
	       S3CFB_LCDCON3_HFPD(mode->left_margin - 1) |
	       S3CFB_LCDCON3_HOZVAL(mode->xres - 1),
	       &regs->lcdcon3);

	writel(S3CFB_LCDCON4_HSPW(mode->hsync_len - 1),
	       &regs->lcdcon4);

	/* Write FB address. */
	writel(panel->frameAdrs >> 1, &regs->lcdsaddr1);
	writel((panel->frameAdrs +
	       (mode->xres * mode->yres * panel->gdfBytesPP)) >> 1,
	       &regs->lcdsaddr2);
	writel(mode->xres * bpp / 16, &regs->lcdsaddr3);

	/* Start the controller. */
	setbits_le32(&regs->lcdcon1, 1);
}

(3) 编写lcd_ctrl_initlcd_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(&regs->lcdcon5);
    val |= (1<<3);
    writel(val,&regs->lcdcon5);
	
	val = readl(&regs->lcdcon1);
	val |= (1<<0);
    writel(val,&regs->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的显示效果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_35031421/article/details/104567531
今日推荐