赞
踩
学习交流加
- 个人qq:
1126137994- 个人微信:
liu1126137994- 学习交流资源分享qq群:
962535112
上一篇分析LVDS驱动程序移植过程的文章(文章链接为:移植Linux系统到iMX6开发板之LVDS显示屏驱动程序的框架分析与移植)中最后于有一点需要分析LVDS参数的匹配过程的,由于篇幅太长,所以另写一篇文章来记录。
核心函数fb_find_mode(),在分析之前先了解下几个参数。
重要参数说明:
一. ldb.c中的 ldb_modedb
在i.mx6中,关于lvds液晶屏的这个结构体参数(系统lvds接口支持的lcd时序参数都在此了)所属文件为:driver/video/mxc/ldb.c
static struct fb_videomode ldb_modedb[] = { { "LDB-WXGA", 60, 1280, 800, 14065, 40, 40, 10, 3, 80, 10, 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_DETAILED,}, { "LDB-XGA", 60, 1024, 768, 15385, 220, 40, 21, 7, 60, 10, 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_DETAILED,}, {"LDB-WSVGA", 60, 1024, 600, 19528, 140, 160, 20, 12, 20, 3, 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_DETAILED,}, {"LDB-WSVGA480", 60, 1024, 480, 23000, 140, 160, 20, 12, 20, 3, 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_DETAILED,}, { "LDB-1080P60", 60, 1920, 1080, 7692, 100, 40, 30, 3, 10, 2, 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_DETAILED,}, { "LDB-QXGA", 30, 2048, 1536, 9746, 5, 150, 9, 3, 5, 1, 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_DETAILED,}, };
这些结构体参数的意义:
/* include/linux/fb.h */ struct fb_videomode { 2. const char *name; “LDB-WSVGA” /* 名字 */ 3. u32 refresh; 60 /* 刷新频率 */ 4. u32 xres; 1024 //行像素 5. u32 yres; 768 //列像素 6. u32 pixclock; 19528(14065) //时钟频率,单位ps,14430 7. u32 left_margin; 140 // HBPD(horizontal back porch):80 8. u32 right_margin; 160 // HFPD(horizontal front porth):48 9. u32 upper_margin; 20 // VBPD(vertical back porch),15 10. u32 lower_margin; 12 // VFBD(vertical front porch),2 11. u32 hsync_len; 20 // HSPW(horizontal sync pulse width):32 12. u32 vsync_len; 3 // VSPW(vertical sync pulse width):47 13. u32 sync; 0 14. u32 vmode; 15. u32 flag; 16.};
我们项目中用的屏幕参数如下:
整屏刷新频率 60M
屏幕分辨率 1024*768
时钟频率 14065
left_margin 40(单位像素)
right_margin 40(单位像素)
upper_margin 10(单位像素)
lower_margin 3(单位像素)
行扫描脉宽 hsync_len 80(单位像素时间)
场扫描脉宽vsync_len 10(单位像素时间)
二. arch\arm\mach-mx6\Board-mx6q_sabresd.c 中的 ipuv3_fb_platform_data结构。
我们的是:
static struct ipuv3_fb_platform_data sabresd_fb_data[] = { { /*fb0*/ .disp_dev = "ldb", .interface_pix_fmt = IPU_PIX_FMT_RGB666, .mode_str = "LDB-XGA", .default_bpp = 16, .int_clk = false, .late_init = false, }, { .disp_dev = "ldb", .interface_pix_fmt = IPU_PIX_FMT_RGB666, .mode_str = "LDB-XGA", .default_bpp = 16, .int_clk = false, }, { .disp_dev = "lcd", .interface_pix_fmt = IPU_PIX_FMT_RGB565, .mode_str = "CLAA-WVGA", .default_bpp = 16, .int_clk = false, .late_init = false, }, { .disp_dev = "ldb", .interface_pix_fmt = IPU_PIX_FMT_RGB666, .mode_str = "LDB-VGA", .default_bpp = 16, .int_clk = false, .late_init = false, }, };
fb_find_mode()函数就是匹配上面结构的参数mode_str 的值,然后再去ldb_modedb结构体看看有没有LVDS需要的时序参数。
此mode_str其实就是后面会提到的mode_options, 格式如下:
<xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m] or
<name>[-<bpp>][@<refresh>]
所以有两种类型:
三 .环境变量的设置(uboot环境变量cmdline的设置:)
我们拿到的源码,是由厂家直接提供的源码,可以直接通过uboot环境变量向内核代码覆盖一些参数:
如下:
单通道模式:
setenv bootargs_mmc 'setenv bootargs ${bootargs} ip=off root=/dev/mmcblk0p1 rootwait rw
video=mxcfb0:dev=ldb,LDB-WSVGA,if=RGB24,bpp=32
video=mxcfb1:off video=mxcfb2:off ldb=sin0 fbmem=28M fb0base=0x27b00000 ’
将上面参数通过uboot启动,输入进去,保存后重新启动就可以。
它会覆盖sabresd_fb_data[]的值,覆盖的规则根据mxcfb后面的值,比如 mxcfb0 覆盖sabresd_fb_data[0]
里的值,以此类推。了解了参数的意义后,下面就好理解了:
ldb.c中的ldb_disp_init函数有如下调用:
static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
struct mxc_dispdrv_setting *setting) {
......
ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str,
ldb_modedb, ldb_modedb_sz, NULL, setting->default_bpp);
......
}
我们fb_find_mode的参数为:
setting->dft_mode_str为: “LDB-WSVGA”
setting->default_bpp为: 32
fb_find_mode执行源代码为:
int fb_find_mode(struct fb_var_screeninfo *var, struct fb_info *info, const char *mode_option, const struct fb_videomode *db, unsigned int dbsize, const struct fb_videomode *default_mode, unsigned int default_bpp) { int i; /* Set up defaults */ /*如果db参数没有给,则使用modedb*/ if (!db) { db = modedb; dbsize = ARRAY_SIZE(modedb); } /*如果没有设置则使用db[0]的值,我们本身就是使用db[0] (只不过我们是通过设置环境变量的值覆盖了它)的值*/ if (!default_mode) default_mode = &db[0]; /*没有设置bpp则默认使用8bpp,本例是32*/ if (!default_bpp) default_bpp = 8; /* Did the user specify a video mode? */ if (!mode_option) mode_option = fb_mode_option; /*本例是“LDB-WSVGA”*/ if (mode_option) { const char *name = mode_option; unsigned int namelen = strlen(name); int res_specified = 0, bpp_specified = 0, refresh_specified = 0; unsigned int xres = 0, yres = 0, bpp = default_bpp, refresh = 0; int yres_specified = 0, cvt = 0, rb = 0, interlace = 0, margins = 0; u32 best, diff, tdiff; /*数字格式规则形才会跑下面的循环*/ for (i = namelen-1; i >= 0; i--) { switch (name[i]) { /*@后面的是刷新频率*/ case '@': namelen = i; if (!refresh_specified && !bpp_specified && !yres_specified) { refresh = simple_strtol(&name[i+1], NULL, 10); refresh_specified = 1; if (cvt || rb) cvt = 0; } else goto done; break; /*后面是bpp*/ case '-': namelen = i; if (!bpp_specified && !yres_specified) { bpp = simple_strtol(&name[i+1], NULL, 10); bpp_specified = 1; if (cvt || rb) cvt = 0; } else goto done; break; /*获取yres*/ case 'x': if (!yres_specified) { yres = simple_strtol(&name[i+1], NULL, 10); yres_specified = 1; } else goto done; break; case '0' ... '9': break; case 'M': if (!yres_specified) cvt = 1; break; case 'R': if (!cvt) rb = 1; break; case 'm': if (!cvt) margins = 1; break; case 'i': if (!cvt) interlace = 1; break; default: goto done; } } /*如果yres有值,那么也获取xres.*/ if (i < 0 && yres_specified) { xres = simple_strtol(name, NULL, 10); res_specified = 1; } done: /*不会跑这里*/ if (cvt) { struct fb_videomode cvt_mode; int ret; DPRINTK("CVT mode %dx%d@%dHz%s%s%s\n", xres, yres, (refresh) ? refresh : 60, (rb) ? " reduced blanking" : "", (margins) ? " with margins" : "", (interlace) ? " interlaced" : ""); memset(&cvt_mode, 0, sizeof(cvt_mode)); cvt_mode.xres = xres; cvt_mode.yres = yres; cvt_mode.refresh = (refresh) ? refresh : 60; if (interlace) cvt_mode.vmode |= FB_VMODE_INTERLACED; else cvt_mode.vmode &= ~FB_VMODE_INTERLACED; ret = fb_find_mode_cvt(&cvt_mode, margins, rb); if (!ret && !fb_try_mode(var, info, &cvt_mode, bpp)) { DPRINTK("modedb CVT: CVT mode ok\n"); return 1; } DPRINTK("CVT mode invalid, getting mode from database\n"); } DPRINTK("Trying specified video mode%s %ix%i\n", refresh_specified ? "" : " (ignoring refresh rate)", xres, yres); /*如果刷新率没指定*/ if (!refresh_specified) { /* * If the caller has provided a custom mode database and a * valid monspecs structure, we look for the mode with the * highest refresh rate. Otherwise we play it safe it and * try to find a mode with a refresh rate closest to the * standard 60 Hz. */ if (db != modedb && info->monspecs.vfmin && info->monspecs.vfmax && info->monspecs.hfmin && info->monspecs.hfmax && info->monspecs.dclkmax) { refresh = 1000; } else { /*默认使用60HZ*/ refresh = 60; } } diff = -1; best = -1; /*根据名字或者分辨率来匹配。*/ for (i = 0; i < dbsize; i++) { if ((name_matches(db[i], name, namelen) || (res_specified && res_matches(db[i], xres, yres))) && !fb_try_mode(var, info, &db[i], bpp)) { /*刷新率也匹配的时候就认准你了!*/ if (refresh_specified && db[i].refresh == refresh) { return 1; } else { /*刷新率不一样就找差得最少的*/ if (abs(db[i].refresh - refresh) < diff) { diff = abs(db[i].refresh - refresh); best = i; } } } } /*得到刷新率差得最少的db,然后返回*/ if (best != -1) { fb_try_mode(var, info, &db[best], bpp); return (refresh_specified) ? 2 : 1; } /*跑到这里说明名字和分辨率都不匹配。*/ diff = 2 * (xres + yres); best = -1; DPRINTK("Trying best-fit modes\n"); /*找到分辨率最小的那组数据。*/ for (i = 0; i < dbsize; i++) { DPRINTK("Trying %ix%i\n", db[i].xres, db[i].yres); if (!fb_try_mode(var, info, &db[i], bpp)) { tdiff = abs(db[i].xres - xres) + abs(db[i].yres - yres); /* * Penalize modes with resolutions smaller * than requested. */ if (xres > db[i].xres || yres > db[i].yres) tdiff += xres + yres; /*差值大的会被保留,说白了,最终就是找到分辨率最小的那组参数。*/ if (diff > tdiff) { diff = tdiff; best = i; } } } /*获取best对应的var参数。*/ if (best != -1) { fb_try_mode(var, info, &db[best], bpp); return 5; } } /*运行到这里有两种情况, 1. 字母规则型(如LDB-WXVGA),那就是名字不匹配,并且参数检查失败,。 2. 数字规则型(如1920x1080), 那就是名字不匹配 && 分辨率比ldb_modedb中的小上两倍以上(比如1920x1080 和 320x240)。 */ DPRINTK("Trying default video mode\n"); if (!fb_try_mode(var, info, default_mode, default_bpp)) return 3; /*默认的还失败那只能随便找一个了。*/ DPRINTK("Trying all modes\n"); for (i = 0; i < dbsize; i++) if (!fb_try_mode(var, info, &db[i], default_bpp)) return 4; DPRINTK("No valid mode found\n"); return 0; }
本例中fb_try_mode返回的都是0,看代码,这里的作用基本上看成是得到当前对应的db值然后放再var中供后面的framebuffer driver使用。
\drivers\video\modedb.c static int fb_try_mode(struct fb_var_screeninfo *var, struct fb_info *info, const struct fb_videomode *mode, unsigned int bpp) { int err = 0; DPRINTK("Trying mode %s %dx%d-%d@%d\n", mode->name ? mode->name : "noname", mode->xres, mode->yres, bpp, mode->refresh); var->xres = mode->xres; var->yres = mode->yres; var->xres_virtual = mode->xres; var->yres_virtual = mode->yres; var->xoffset = 0; var->yoffset = 0; var->bits_per_pixel = bpp; var->activate |= FB_ACTIVATE_TEST; var->pixclock = mode->pixclock; var->left_margin = mode->left_margin; var->right_margin = mode->right_margin; var->upper_margin = mode->upper_margin; var->lower_margin = mode->lower_margin; var->hsync_len = mode->hsync_len; var->vsync_len = mode->vsync_len; var->sync = mode->sync; var->vmode = mode->vmode; if (info->fbops->fb_check_var) err = info->fbops->fb_check_var(var, info); var->activate &= ~FB_ACTIVATE_TEST; return err; }
drivers\video\mxc\mxc_ipuv3_fb.c中的 mxcfb_check_var函数
static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { u32 vtotal; u32 htotal; struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; if (var->xres == 0 || var->yres == 0) return 0; /* fg should not bigger than bg */ if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { struct fb_info *fbi_tmp; int bg_xres = 0, bg_yres = 0; int16_t pos_x, pos_y; bg_xres = var->xres; bg_yres = var->yres; fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); if (fbi_tmp) { bg_xres = fbi_tmp->var.xres; bg_yres = fbi_tmp->var.yres; } ipu_disp_get_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, &pos_x, &pos_y); if ((var->xres + pos_x) > bg_xres) var->xres = bg_xres - pos_x; if ((var->yres + pos_y) > bg_yres) var->yres = bg_yres - pos_y; } if (var->rotate > IPU_ROTATE_VERT_FLIP) var->rotate = IPU_ROTATE_NONE; if (var->xres_virtual < var->xres) var->xres_virtual = var->xres; if (var->yres_virtual < var->yres) var->yres_virtual = var->yres * 3; if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && (var->bits_per_pixel != 16) && (var->bits_per_pixel != 12) && (var->bits_per_pixel != 8)) var->bits_per_pixel = 16; if (check_var_pixfmt(var)) /* Fall back to default */ bpp_to_var(var->bits_per_pixel, var); if (var->pixclock < 1000) { htotal = var->xres + var->right_margin + var->hsync_len + var->left_margin; vtotal = var->yres + var->lower_margin + var->vsync_len + var->upper_margin; var->pixclock = (vtotal * htotal * 6UL) / 100UL; var->pixclock = KHZ2PICOS(var->pixclock); dev_dbg(info->device, "pixclock set for 60Hz refresh = %u ps\n", var->pixclock); } var->height = -1; var->width = -1; var->grayscale = 0; return 0; }
想一起探讨以及获得各种学习资源加我(有我博客中写的代码的原稿):
qq:1126137994
微信:liu1126137994
可以共同交流关于嵌入式,操作系统,C++语言,C语言,数据结构等技术问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。