iMX6开发板移植Linux系统之LVDS显示屏驱动程序分析之LVDS参数的匹配过程分析

上一篇分析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>]

所以有两种类型:
1. 字符规则形, 如 “LDB-WXVGA”
2. 数字规则形,如”1920*1080”
具体各个参数意义可参照fb_find_mode()函数注释。

三 .环境变量的设置(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语言,数据结构等技术问题。

猜你喜欢

转载自blog.csdn.net/qq_37375427/article/details/78989415
今日推荐