U-boot中通过ENV设置显示设备(如LCD)参数的方法与格式
于一个移植比较完善的U-boot来说,显示设备一般也是可以使用的。在嵌入式中的LCD液晶屏一般由芯片的内置的LCD控制器或者VPSS(视频处理子系统)来控制。在U-boot中已经实现了类似framebuffer的机制,只要在移植的时候完成对LCD控制等显示设备的初始化,并将framebuffer的对应的内存地址空间通过结构体传递给u-boot的中控制台实现部分,就可以在u-boot的启动时从LCD上看到u-boot的控制台终端信息,并且可以在LCD的左上角看到uboot的logo。对于显示部分的初始化代码根据CPU芯片的不同而异,但是大体结构都是一样的。
红色的字体是必须的,这个看了video_get_params 函数你就明白了。
在大部分液晶控制器的初始化中都需要以下参数:
-
/******************************************************************
- * 解析结构体
- ******************************************************************/
- struct ctfb_res_modes {
- int xres;
/* 可见分辨率
*/
- int yres;
- /* 时序: 所有值都以像素时钟为单位(当然除了像素时钟本身)
*/
- int pixclock;
/* 像素时钟(单位:微秒)
*/
- int left_margin;
/* 从行同步到图像左边沿的像素时钟数
*/
- int right_margin;
/* 从行同步到图像右边沿的像素时钟数
*/
- int upper_margin;
/* 从场同步到图像上边沿的行数
*/
- int lower_margin;
/* 从场同步到图像下边沿的行数
*/
- int hsync_len;
/* 行同步时间长度(像素时钟数)
*/
- int vsync_len;
/* 场同步时间长度(行数)
*/
- int sync;
/* see FB_SYNC_*
*/
- int vmode;
/* see FB_VMODE_*
*/
- };
而这些参数将随着所使用的LCD液晶屏不同而不同。如果将这些参数写死在代码中,这样换一个不同参数的液晶屏就要重新给参数,重新编译一次uboot,这么做当然比较不合适。动态配置这些参数对于U-boot来说,当然是从自身ENV(环境变量)中获取。两年前我在移植MINI6410的时候,就对液晶屏的显示做了支持,并参考uboot中别的液晶驱动做了动态参数配置的支持,但是当时写完代码之后没有仔细去测试和研究其配置的方法和格式。这些研究显示设备参数的传递的时候才重新回来验证一下原来的代码,发现原来我在MINI6410上的代码是完全OK的,并支持3种不同的配置方法。关键的参数获取代码如下:
- got_mode = 0;
- videomode = CONFIG_SYS_DEFAULT_VIDEO_MODE;
- /*
get video mode via environment
*/
- if (((penv
= getenv ("videomode"))
!=
NULL) &&
- (penv[0]
<=
'9')) {
- videomode =
(int) simple_strtoul
(penv,
NULL, 16);
- /* parameter are vesa modes
*/
- /* search params
*/
- for (i
= 0; i
< VESA_MODES_COUNT; i++)
{
- if (vesa_modes[i].vesanr
== videomode)
- break;
- }
- if (i
== VESA_MODES_COUNT)
{
- printf ("no VESA Mode found, switching to mode 0x%x ",
- CONFIG_SYS_DEFAULT_VIDEO_MODE);
- i = VESA_MODES_COUNT
- 2;
- }
- res_mode =
(struct ctfb_res_modes *)
&res_mode_init[vesa_modes[i].resindex];
- bits_per_pixel = vesa_modes[i].bits_per_pixel;
- } else
{
- /*init
to default params*/
- res_mode =
(struct ctfb_res_modes *)
&res_mode_init[vesa_modes[VESA_MODES_COUNT
- 2].resindex];
- bits_per_pixel = vesa_modes[VESA_MODES_COUNT
- 2].bits_per_pixel;
- if
((penv = getenv
("video-mode"))
!=
NULL) {
- ret = video_get_video_mode(&var_mode.xres,
&var_mode.yres,
- &bits_per_pixel,
&dumpfreq,
&p);
- while
((i = video_get_param_len
(p,
','))
!= 0)
{
- GET_OPTION ("le:", var_mode.left_margin)
- GET_OPTION ("ri:", var_mode.right_margin)
- GET_OPTION ("up:", var_mode.upper_margin)
- GET_OPTION ("lo:", var_mode.lower_margin)
- GET_OPTION ("hs:", var_mode.hsync_len)
- GET_OPTION ("vs:", var_mode.vsync_len)
- GET_OPTION ("pclk:", var_mode.pixclock)
- p += i;
- if
(*p != 0)
- p++; /* skip
','
*/
- }
- res_mode =
(struct ctfb_res_modes *)
&var_mode;
- } else {
- printf ("no Video params found, try bootargs~~ ");
- res_mode =
(struct ctfb_res_modes *)
&var_mode;
- bits_per_pixel = video_get_params
(res_mode,
"bootargs");
- if((bits_per_pixel
!= 8)
&&
- (bits_per_pixel
!= 15)
&&
- (bits_per_pixel
!= 16)
&&
- (bits_per_pixel
!= 24)) {
- printf ("Get params error, set to default!");
- res_mode =
(struct ctfb_res_modes *)
&res_mode_init[vesa_modes[VESA_MODES_COUNT
- 2].resindex];
- bits_per_pixel = vesa_modes[VESA_MODES_COUNT
- 2].bits_per_pixel;
- }
- }
- }
上面的代码根据先后顺序会从3个不同的环境变量中获取所连接的液晶屏参数:
- 一、选择预先编译进uboot的某个显示器参数(videomode=)
这种形式的参数配置适合预先知道你要用的几个液晶屏的参数,并将其写在driver/video/videomodes.c中。比如我在MINI6410的移植中添加了3.5寸、 4.3寸和7寸屏的参数:
-
const struct ctfb_vesa_modes vesa_modes[VESA_MODES_COUNT]
= {
-
......
- {0x211, RES_MODE_240x320, 16},
- {0x212, RES_MODE_480x272, 16},
- {0x213, RES_MODE_800x480, 16},
-
};
-
const struct ctfb_res_modes res_mode_init[RES_MODES_COUNT]
= {
- /* x y pixclk le ri up lo hs vs s vmode
*/
-
......
- {240, 320, 90000, 1, 4, 1,
1, 30, 4, 0, FB_VMODE_NONINTERLACED},
- {480, 272, 75000, 2, 3, 1,
1, 40, 1, 0, FB_VMODE_NONINTERLACED},
- {800, 480, 50000, 2, 2, 2,
2, 41, 4, 0, FB_VMODE_NONINTERLACED},
- };
而对于这三种屏,只需要在环境变量中配置:
- 3.5寸 :videomode=0x211
- 4.3寸 :videomode=0x212
- 7 寸 :videomode=0x213
即,
- 3.5寸 : setenv videomode 0x211 ;saveenv
- 4.3寸 : setenv videomode 0x212 ;saveenv
- 7 寸 : setenv videomode 0x213 ;saveenv
- 二、通过环境变量(video-mode=)获取参数
如果在环境变量中没有videomode,则uboot会继续查找是否有video-mode变量。如果有,代码会从这个环境变量中直接解析出所需要的参数,这个环境变量的格式应为:
- video-mode=ctfb:800x480-16@60,le:2,ri:2,up:2,lo:2,hs:41,vs:4,pclk:50000
其中ctfb 有没有都无所谓,但是后面的 :一定要有
蓝色的符合只要不是数字就好,写成这个形式比较标准罢了。这个看了代码就明白了。
这种情况比较适用于仅uboot需要这些显示参数,并需要灵活配置的情况。这些参数没有主动传递给内核。
- 三、从将要传递给内核的cmdline(bootargs=)获取参数
如果通过上面两步,在环境变量中没有正确地解析出参数,代码会到bootargs(也就是传递给内核的cmdline)中找相关参数,在bootargs中的格式必须是:
- video=ctfb:x:800,y:480,depth:16,le:2,ri:2,up:2,lo:2,hs:41,vs:4,pclk:50000,vmode:0,sync:0
这种情况可以灵活配置,并且这些参数会通过tagged-list--->cmdline主动传递给内核,适用于uboot和内核共享ENV区参数的情况。
而对于在上面我在mini6410中实现的代码,其实可以移植到别的芯片启动中去,只要将参数解析到struct ctfb_res_modes 中去,其中的数据就可以用于初始化LCD控制器等初始化代码中了