u-boot与Linux内核视频显示接口参数配置及传递方案
一、一般视频显示接口初始化所需要的参数
众所周知,显示器显示的是二维的,处理器将视频数据通过显示接口行、地发送到显示器,每行中的每bit数据通过pclk(像素时钟)同步,每一行通过hsync(行同步时钟)来告诉显示器发完一行。当发完了一帧数据,通过vsync(场同步时钟)告诉显示器已经发完一帧。这些波形时序可以通过以前我写过的一篇《VGA视频信号详解》中的示波器的截图来体会。这些也是写视频显示和采集驱动的基础知识,你必须了解CPU与视频接口间的是数据格式。
/* * (C) Copyright 2004 * Pierre Aubert, Staubli Faverges , <p.aubert@staubli.com> * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ /************************************************************************ 为视频模式获取参数: 默认视频模式可以通过 CONFIG_SYS_DEFAULT_VIDEO_MODE 定义。 如果没有定义,默认视频模式为 0x301。 参数可以通过环境变量"videomode"设置, 有两种不同的方法: "videomode=301" - 301 是一个16进制数,他定义了 VESA 模式。 以下模式是已经实现的: 颜色 640x480 800x600 1024x768 1152x864 1280x1024 --------+--------------------------------------------- 8 bits | 0x301 0x303 0x305 0x161 0x307 15 bits | 0x310 0x313 0x316 0x162 0x319 16 bits | 0x311 0x314 0x317 0x163 0x31A 24 bits | 0x312 0x315 0x318 ? 0x31B --------+--------------------------------------------- "videomode=bootargs" - 从bootargs环境变量中解析参数。 格式为"NAME:VALUE,NAME:VALUE" 等等。 例如: "bootargs=video=ctfb:x:800,y:600,depth:16,pclk:25000" 以上列表中不包含的参数从默认模式中获取, 也就是以下模式之一: mode:0 640x480x24 mode:1 800x600x16 mode:2 1024x768x8 mode:3 960x720x24 mode:4 1152x864x16 mode:5 1280x1024x8 如果 "mode" 没有在参数列表中提供, 则假定为 mode:0 。 此方法支持以下参数: x xres = 可见(有效)水平解析度 y yres = 可见(有效)垂直解析度 pclk 每微秒的像素时钟个数 le 从行同步到图像左边沿的像素时钟数 ri 从行同步到图像右边沿的像素时钟数 up 从场同步到图像上边沿的行数 lo 从场同步到图像下边沿的行数 hs 行同步时间长度(像素时钟数) vs 场同步时间长度(行数) sync see FB_SYNC_* vmode see FB_VMODE_* depth 每个像素的色深(单位:位) 存在于bootargs中的所有其他的参数都将被忽略。 也可以直接在变量"videomode"中直接设置参数, 或者在其他的参数中(例如"myvideo")设置并设置变量为 "videomode=myvideo"。 ****************************************************************************/ #include <common.h> #include "videomodes.h" const struct ctfb_vesa_modes vesa_modes[VESA_MODES_COUNT] = { {0x301, RES_MODE_640x480, 8}, {0x310, RES_MODE_640x480, 15}, {0x311, RES_MODE_640x480, 16}, {0x312, RES_MODE_640x480, 24}, {0x303, RES_MODE_800x600, 8}, {0x313, RES_MODE_800x600, 15}, {0x314, RES_MODE_800x600, 16}, {0x315, RES_MODE_800x600, 24}, {0x305, RES_MODE_1024x768, 8}, {0x316, RES_MODE_1024x768, 15}, {0x317, RES_MODE_1024x768, 16}, {0x318, RES_MODE_1024x768, 24}, {0x161, RES_MODE_1152x864, 8}, {0x162, RES_MODE_1152x864, 15}, {0x163, RES_MODE_1152x864, 16}, {0x307, RES_MODE_1280x1024, 8}, {0x319, RES_MODE_1280x1024, 15}, {0x31A, RES_MODE_1280x1024, 16}, {0x31B, RES_MODE_1280x1024, 24}, }; const struct ctfb_res_modes res_mode_init[RES_MODES_COUNT] = { /* x y pixclk le ri up lo hs vs s vmode */ {640, 480, 39721, 40, 24, 32, 11, 96, 2, 0, FB_VMODE_NONINTERLACED}, {800, 600, 27778, 64, 24, 22, 1, 72, 2, 0, FB_VMODE_NONINTERLACED}, {1024, 768, 15384, 168, 8, 29, 3, 144, 4, 0, FB_VMODE_NONINTERLACED}, {960, 720, 13100, 160, 40, 32, 8, 80, 4, 0, FB_VMODE_NONINTERLACED}, {1152, 864, 12004, 200, 64, 32, 16, 80, 4, 0, FB_VMODE_NONINTERLACED}, {1280, 1024, 9090, 200, 48, 26, 1, 184, 3, 0, FB_VMODE_NONINTERLACED}, }; /************************************************************************ * 为视频模式获取参数: */ /********************************************************************* * (*start:参数起始指针,sep:分割符)返回下一个参数的长度 */ static int video_get_param_len (char *start, char sep) { int i = 0; while ((*start != 0) && (*start != sep)) { start++; i++; } return i; } /* 视频参数名的字符串对比 */ static int video_search_param (char *start, char *param) { int len, totallen, i; char *p = start; len = strlen (param); totallen = len + strlen (start); for (i = 0; i < totallen; i++) { if (strncmp (p++, param, len) == 0) return (i); } return -1; } /*************************************************************** * 通过环境变量获取参数,Linux内核已经实现 * 例如: * video=ctfb:x:800,xv:1280,y:600,yv:1024,depth:16,mode:0,pclk:25000, * le:56,ri:48,up:26,lo:5,hs:152,vs:2,sync:0,vmode:0,accel:0 * * penv是一个指向环境变量的指针,包含字符串或其他环境变量名。 * 它甚至可以是"bootargs"。 */ #define GET_OPTION(name,var) \ if(strncmp(p,name,strlen(name))==0) { \ val_s=p+strlen(name); \ var=simple_strtoul(val_s, NULL, 10); \ } int video_get_params (struct ctfb_res_modes *pPar, char *penv) { char *p, *s, *val_s; int i = 0, t; int bpp; int mode; /* 优先搜索包含在环境变量中的字符串参数 */ s = penv; if ((p = getenv (s)) != NULL) { s = p; } /* bootargs参数的情况,我们必须从 * "video=ctfb:"开始。 */ i = video_search_param (s, "video=ctfb:"); if (i >= 0) { s += i; s += strlen ("video=ctfb:"); } /* 首先搜索“模式”信息,作为默认值 */ p = s; t = 0; mode = 0; /* 默认值为mode0 */ while ((i = video_get_param_len (p, ',')) != 0) { GET_OPTION ("mode:", mode) p += i; if (*p != 0) p++; /* 跳过 ',' */ } if (mode >= RES_MODES_COUNT) mode = 0; *pPar = res_mode_init[mode]; /* 拷贝默认值 */ bpp = 24 - ((mode % 3) * 8); p = s; /* 从新开始 */ while ((i = video_get_param_len (p, ',')) != 0) { GET_OPTION ("x:", pPar->xres) GET_OPTION ("y:", pPar->yres) GET_OPTION ("le:", pPar->left_margin) GET_OPTION ("ri:", pPar->right_margin) GET_OPTION ("up:", pPar->upper_margin) GET_OPTION ("lo:", pPar->lower_margin) GET_OPTION ("hs:", pPar->hsync_len) GET_OPTION ("vs:", pPar->vsync_len) GET_OPTION ("sync:", pPar->sync) GET_OPTION ("vmode:", pPar->vmode) GET_OPTION ("pclk:", pPar->pixclock) GET_OPTION ("depth:", bpp) p += i; if (*p != 0) p++; /* 跳过 ',' */ } return bpp; }
重要数据结构:
/****************************************************************** * 解析结构体 ******************************************************************/ 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_* */ }; /****************************************************************** * Vesa 模式结构体 ******************************************************************/ struct ctfb_vesa_modes { int vesanr; /* Vesa 号(在LILO中定义) (VESA Nr + 0x200} */ int resindex; /* 解析结构体的索引 */ int bits_per_pixel; /* bpp */ };
从这些代码中我们可以看出,我们不仅可以通过预定义在videomodes.h中的res_mode_init获取参数,也可以通过环境变量中的bootargs获取视频参数。当然,通过bootargs获取参数比较灵活,这样只需改写uboot的环境变量就可以让uboot重新适应新的显示器,并且可以将这个参数通过bootargs传递给内核cmdline,内核也可以通过解析cmdline获取这些参数。
int video_get_params (struct ctfb_res_modes *pPar, char *penv) pPar:是接收解析好的视频参数的结构体 penv:是需要解析的字符串指针,也可以是包含这个字符串的uboot环境变量名。
如果uboot需要实现LCD等的显示驱动,就可以通过这个API函数解析出需要的参数,并保存到一个struct ctfb_res_modes结构体中,在LCD初始化的时候使用。实现视频显示参数的串口配置。例如我在S3C6410处理器的U-boot中就实现了参数的获取。
dvo2_config=x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000
在LCD初始化代码中可以通过以下代码中获取参数,保存到一个struct ctfb_res_modes结构体中。
struct ctfb_res_modes temp; int bpp; bpp = video_get_params (&temp, "dvo2_config");
这些保存到struct ctfb_res_modes temp中的数据可以用于初始化LCD控制器等显示设备。
三、与内核视频参数传递方式
要将数据传给Linux内核,一个标准的方法就是使用cmdline。将 $dvo2_config 放入uboot的bootargs环境变量中,这样就可以将参数传给内核解析,格式应该为:
video=dvo2:x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000
如果你需要配置多个显示器,那么必须在多添加一个,例如:
video=hdmi:pclk:148500,x:1920,le:40,ri:152,hs:44,y:1080,up:4,lo:36,vs:5
不可以放到同一个“video=”中,目的是方便内核解析,后面可以看出。
所以当你配置uboot的bootargs的时候,可以这样:
setenv bootargs '...... video=dvo2:$dvo2_config video=hdmi:$hdmi_config'
四、linux通过cmdline获取参数
static char *video_options[FB_MAX] __read_mostly;
如果你有多个“video=”参数,每个可以包含不同显器的配置参数。之后在显示控制器初始化代码中,你可以利用fbmem.c中导出的fb_get_options函数获取你要处理的那个显示器的参数video_options[i]。如使你在cmdline中存在以下字符串:
video=dvo2:x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000
你就可以使用以下代码获取参数字符串的指针。
int ret = 0; char *dvo2_option; if ((ret =fb_get_options("dvo2", &dvo2_option)) != 0) { pr_debug("fb_get_options failed:%d\n", ret); } //处理结果为dvo2_option = "x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000"
接下来就是解析这些参数的问题了。由于在现有的内核中我没有找到相关的解析函数(虽然uboot说内核已经实现),所以我就借用了U-boot的video_get_params函数,修改出一个解析函数:
video_param.h /* * (C) Copyright 2012 * Tekkaman Ninja , <tekkamanninja@gmail.com> * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ #include <linux/fb.h> void video_get_params (struct fb_videomode *pPar, char *penv); /* mainly for debugging */ void print_video_options(char *, struct fb_videomode *);
video_param.c /* * (C) Copyright 2012 * Tekkaman Ninja , <tekkamanninja@gmail.com> * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ /************************************************************************ Get Parameters for the boot cmdline: Ex.: "bootargs=video=dvo2:x:800,y:600,depth:16,pclk:25000" Following parameters are supported: x xres = visible resolution horizontal y yres = visible resolution vertical pclk pixelclocks in pico sec le left_marging time from sync to picture in pixelclocks ri right_marging time from picture to sync in pixelclocks up upper_margin time from sync to picture lo lower_margin hs hsync_len length of horizontal sync vs vsync_len length of vertical sync sync see FB_SYNC_* vmode see FB_VMODE_* depth Color depth in bits per pixel ****************************************************************************/ #include "video_param.h" #include <linux/string.h> #include <linux/kernel.h> /************************************************************************ * Get Parameters for the video mode: */ /********************************************************************* * returns the length to the next seperator */ static int video_get_param_len (char *start, char sep) { int i = 0; while ((*start != 0) && (*start != sep)) { start++; i++; } return i; } /*************************************************************** * Get parameter via the environment as it is done for the * linux kernel i.e: * video=ctfb:x:800,xv:1280,y:600,yv:1024,depth:16,mode:0,pclk:25000, * le:56,ri:48,up:26,lo:5,hs:152,vs:2,sync:0,vmode:0,accel:0 * * penv is a pointer to the environment, containing the string, or the name of * another environment variable. It could even be the term "bootargs" */ #define GET_OPTION(name,var) \ if(strncmp(p,name,strlen(name))==0) { \ val_s=p+strlen(name); \ var=simple_strtoul(val_s, NULL, 10); \ } #define PRINT_OPTION(name,var) \ do {pr_info (name " : %d", var);} while(0) void video_get_params (struct fb_videomode *pPar, char *penv) { char *p, *val_s; int i = 0; // int bpp = 16; // int mode; /* first search for the environment containing the real param string */ /* search for mode as a default value */ p = penv; while ((i = video_get_param_len (p, ',')) != 0) { GET_OPTION ("x:", pPar->xres) GET_OPTION ("y:", pPar->yres) GET_OPTION ("le:", pPar->left_margin) GET_OPTION ("ri:", pPar->right_margin) GET_OPTION ("up:", pPar->upper_margin) GET_OPTION ("lo:", pPar->lower_margin) GET_OPTION ("hs:", pPar->hsync_len) GET_OPTION ("vs:", pPar->vsync_len) GET_OPTION ("sync:", pPar->sync) GET_OPTION ("vmode:", pPar->vmode) GET_OPTION ("pclk:", pPar->pixclock) // GET_OPTION ("depth:", bpp) p += i; if (*p != 0) p++; /* skip ',' */ } return ; } void print_video_options(char *name, struct fb_videomode *option) { pr_info("got %s option:\n",name); PRINT_OPTION ("x:", option->xres); PRINT_OPTION ("y:", option->yres); PRINT_OPTION ("le:", option->left_margin); PRINT_OPTION ("ri:", option->right_margin); PRINT_OPTION ("up:", option->upper_margin); PRINT_OPTION ("lo:", option->lower_margin); PRINT_OPTION ("hs:", option->hsync_len); PRINT_OPTION ("vs:", option->vsync_len); PRINT_OPTION ("sync:", option->sync); PRINT_OPTION ("vmode:", option->vmode); PRINT_OPTION ("pclk:", option->pixclock); // PRINT_OPTION ("depth:", bpp) return; }
void video_get_params (struct fb_videomode *pPar, char *penv)
struct fb_videomode video_port_option = {NULL, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; video_get_params (&video_port_option, dvo2_option);
在获取了参数之后,有效数据(非-1)就可以用于初始化LCD控制器等显示设备。
# fw_setenv dvo2_config "x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000" # fw_printenv -n dvo2_config