Platform: IMX6Q
OS: Android 4.4
本文只讨论lvds接口的是lcd参数匹配的过程,mipi dsi以及其他接口部分会有一点差异。
核心函数fb_find_mode(),在分析之前先了解下几个参数。
重要参数说明:
1. ldb.c中的 ldb_modedb
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-1080P60", 60, 1920, 1080, 7692, 100, 40, 30, 3, 10, 2, 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_DETAILED,}, { "LDB-WSVGA", //Name 60, //refresh 1024, //xres 600, //yres 19531,//pixclock 220, 40,//left/right margin 21, 7,//upper/lower margin 60, 7,//hsync_len,vsync_len 0,//sync FB_VMODE_NONINTERLACED,//vmode FB_MODE_IS_DETAILED,}, }; static int ldb_modedb_sz = ARRAY_SIZE(ldb_modedb);
2. board-mx6-xxx.c中的ipuv3_fb_platform_data结构
本例是:
static struct ipuv3_fb_platform_data tek_fb_data[] = { { /*fb0*/ .disp_dev = "ldb", .interface_pix_fmt = IPU_PIX_FMT_RGB24, .mode_str = "LDB-WSVGA", .default_bpp = 32, .int_clk = false, .late_init = false, }, { .disp_dev = "hdmi", .interface_pix_fmt = IPU_PIX_FMT_RGB24, .mode_str = "1920x1080M@60", .default_bpp = 32, .int_clk = false, .late_init = 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, }, };
此mode_str其实就是后面会提到的mode_options, 格式如下:
<xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m] or
<name>[-<bpp>][@<refresh>]
所以有两种类型:
1. 字符规则形, 如 “LDB-WXVGA”
2. 数字规则形,如"1920*1080"
具体各个参数意义可参照fb_find_mode()函数注释。
3. kernel cmdline里设置的
本例是:
video=mxcfb0:dev=ldb,LDB-WSVGA,if=RGB24,bpp=32
它会覆盖 tek_fb_data中的值,覆盖规则根据mxcfb后面的值。
比如mxcfb0覆盖tek_fb_data[0], 以此类推。
了解了参数意义之后下面就方便理解了.
系统有如下调用:
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); ...... }
setting->dft_mode_str为: "LDB-WSVGA"
setting->default_bpp为: 32
下面看代码执行流程:
/** * fb_find_mode - finds a valid video mode * @var: frame buffer user defined part of display * @info: frame buffer info structure * @mode_option: string video mode to find * @db: video mode database * @dbsize: size of @db * @default_mode: default video mode to fall back to * @default_bpp: default color depth in bits per pixel * * Finds a suitable video mode, starting with the specified mode * in @mode_option with fallback to @default_mode. If * @default_mode fails, all modes in the video mode database will * be tried. * * Valid mode specifiers for @mode_option: * * <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m] or * <name>[-<bpp>][@<refresh>] * * with <xres>, <yres>, <bpp> and <refresh> decimal numbers and * <name> a string. * * If 'M' is present after yres (and before refresh/bpp if present), * the function will compute the timings using VESA(tm) Coordinated * Video Timings (CVT). If 'R' is present after 'M', will compute with * reduced blanking (for flatpanels). If 'i' is present, compute * interlaced mode. If 'm' is present, add margins equal to 1.8% * of xres rounded down to 8 pixels, and 1.8% of yres. The char * 'i' and 'm' must be after 'M' and 'R'. Example: * * 1024x768MR-8@60m - Reduced blank with margins at 60Hz. * * NOTE: The passed struct @var is _not_ cleared! This allows you * to supply values for e.g. the grayscale and accel_flags fields. * * Returns zero for failure, 1 if using specified @mode_option, * 2 if using specified @mode_option with an ignored refresh rate, * 3 if default mode is used, 4 if fall back to any valid 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 */ /*参数未给则使用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; printk("line:%d\n", __LINE__); 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使用。
kernel_imx\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; }
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); } if (var->height == 0 && mxc_fbi->panel_height_mm) var->height = mxc_fbi->panel_height_mm; else if (var->height == 0) var->height = -1; if (var->width == 0 && mxc_fbi->panel_width_mm) var->width = mxc_fbi->panel_width_mm; else if (var->width == 0) var->width = -1; var->grayscale = 0; return 0; }