zoukankan      html  css  js  c++  java
  • LCD framebuffer驱动设计文档

    内容提要:
    1. android display相关的名词
    2. 调试LCD驱动需要注意的步骤
    3. 关于帧缓冲区及I/O内存
    ------------------------------------------------------------------------------------------
    1.名词解释

    GPU:Graphic Processing Unit (图形处理器)

    OpenGL:Open Graphic Library 定义了一个跨编程语言、跨平台的编程接口的规格,不同厂商会有不同的实现方法,它主要用于三维图象(二维的亦可)绘制。

    SurfaceFlinger:Android中负责Surface之间叠加、混合操作的动态库

    Skia:Android中的2D图形库

    libagl:Android中通过软件方法实现的一套OpenGL动态库

    libhgl:为区别libagl,自定义的一种叫法。特指GPU厂商提供的硬件实现的OpenGL

    composition:特指SurfaceFlinger对各个Surface之间的叠加、混合操作

    render:特指使用OpenGL动态库进行3D渲染

    copybit:Android使用2D引擎来加速图形操作(主要是Surface之间的composition操作)的一种技术,对应着一个或几个动态库。

    pmem:Android特有驱动,从linux内核中reserve物理连续内存,可以为2d、3d引擎、vpu等设备分配物理连续内存。



    Android在启动后,会在运行时根据配置文件加载OpenGL(libagl & libhgl)的实现,如果有libhgl实现,默认使用libhgl实现,否则使用libagl实现。

    OpenGL在Android中两个作用:

    1. 用于Surface的composition操作。

             SurfaceFlinger会调用到OpenGL中,通过libagl或者libhgl做Surface的组合、叠加操作。

    2. 用于图形图像的渲染

             Android framework会对OpenGL实现进行java层次的简单封装,在java应用程序中对OpenGL的调用最终会调用到libagl或者libhgl中去。

             很多第三方游戏、3D图库、某些launcher会使用OpenGL实现比较炫丽UI的特效。

    Copybit在Android中的作用

    Copybit在Android中主要用于Surface的composition操作。


    Skia在Android中的作用

    Skia是Android的2D图形库,用于绘制文字、几何图形、图像等。

    Skia的设备后端:Raster、OpenGL、PDF

    双缓冲:
    在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。如果所需要绘制的图形很简单,那么这样也没什么问题。但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。
    让我们把计算机想象成一个画图比较快的人,假如他直接在屏幕上画图,而图形比较复杂,则有可能在他只画了某幅图的一半的时候就被观众看到。而后面虽然他把画补全了,但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。
    如何解决这一问题呢?我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域,在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,这一方法效率非常高,所以被广泛的采用。

    (以上这段是网上摘的)
    ------------------------------------------------------------------------------------------
    LCD调光方式有:
     一线脉冲计数调光
     PWM调光


    调试LCD需要注意的:
    SCLK
    SDA
    CS
    RESET
    上面这几路线是发命令和参数用的,所以首先要调通,这几路OK后屏应该亮或花屏


    PCLK
    HSYNC
    VSYNC
    DE
    这几路是刷数据用的,屏亮后显示不正常就调这几路的参数


    320x480的,参数为:
    PCLK    10MHz
    HSYNC    30KHz(行刷新)
    VSYNC    60Hz(列刷新,即一屏)
    SCLK    70KHz
    DE    30KHz(Data Enable RGB写数据时用到)
    CS    SPI写命令或参数时用,往屏上刷数据时用不到


    常见的问题:
    ---
    lk亮屏了,但是白屏,查下极性,看是不是反了
    极性: lcdc侧的Pclk,Hsync,Vsync,DE是高有效,那么panel driver的
    这些信号也应该是高有效,在往寄存器写参数时要核对下
    ---
    要注意这些东西,HFP,HBP,VFP,VBP 即通常所讲的前沿,后沿
    屏幕图像左右抖动,不用说,肯定是HFP/HBP不对
    前沿后沿就是给hsync,vsync往屏幕刷数据提供时间,可以看你的LCD IC SPEC了解


    调试步骤(这段是网上摘的):
    1)调试lcd背光,背光主要分为PMIC自带的和单独的DCDC,如果为PMIC自带的背光,一般平台厂商已经做好,
    直接调用接口即可,如果为单独的DCDC驱动,则需要用GPIO控制DCDC的EN端
     
    2)确认lcd的模拟电,io电是否正常
     
    3)根据lcd的分辨率,RGB/CPU/MIPI等不同的接口,配置控制寄存器接口
     
    4)根据lcd spec配置PCLK的频率,配置PCLK,VSYNC,HSYNC,DE等控制线的极性
     
    5)使用示波器测试所有clk的波形,确认频率,极性是否符合要求
     
    6)使用示波器测试data线,看是否有数据输出,bpp的设置是否正确
     
    7)如果lcd需要初始化,配置spi的接口,一般分为cpu自带的spi控制器,和gpio模拟的spi。
     
    8)根据lcd spec中的初始化代码进行lcd的初始化
     
    9)用示波器测量lcd的spi clk及数据线,确认是否正常输出
     
    10)正常情况下,此时lcd应该可以点亮。如果没有点亮,按照上述步骤1到9,逐项进行检查测试,重点检查第
    5项,clk的极性
     
    11)如果lcd点亮,但是花屏。则需要先确认数据格式是否正确,然后确认fb里的数据是否正常,有以下几种方
    法确认fb里的数据
    i)cat /dev/graphics/fb0 > /sdcard/fb0,然后将/sdcard/fb0 >到另一台相同分辨率及相同格式的手机上,看图片显示是否正常
    ii)使用irfanview软件显示cat /dev/graphics/fb0出来的raw数据,注意要正确设置分辨率及格式,否则显示花屏
    iii)如果adb连接正常,可以使用豌豆莢等软件,查看fb中的数据是否正常
     
    通过以上三种途径,如果确认fb中的数据正常显示,则很可能为lcd初始化代码的问题,或者clk极性的问题,
    如果fb数据不正常,则可能为lcd控制寄存器配置不正常导致。
     
     
    LCD屏的调试注意事项
     
    1. Pix clock是否在规定的范围内。
     
    2. Pclk是否极性正确。上升沿还是下降沿。
     
    3. 变频引起的闪屏问题。可以通过锁定频率来试验是否是变频引起。

    要确保每一步都正确,那肯定可以显示出图像来
    ------------------------------------------------------------------------------------------
    framebuffer

    我们现在用的是Adreno系列,Adreno是什么?就相当于PC机上的显卡.显卡,也叫GPU,
    它的好坏会对手机的多媒体播放和3D游戏性能有着直接的影响.
    framebuffer即帧缓冲区,这个帧缓冲区就位于显卡的内部,帧缓冲区是显卡上固化的存储器,其中存放的是当前屏幕的内容.你要做高通平台,那么还会遇到MDP,MDP(Mobile Display Processor), MDP是什么? 比方说你用的data format是RGB666,那么这里的data format输出是多少位是由MDP决定的,MDP1.1/MDP1.2只能输出16或18bpp,而MDP1.3可以输出24bpp.还有图形的旋转等效果,上面说的Copybit进行composition的动作我想底层就是由MDP支持的.我们也可以选择是GPU来composition还是MDP来composition. 那么GPU和MDP是什么关系?GPU是显卡,MDP是图形处理器,普通的合成MDP就可完成,但如果是复杂的比如3D的应用等就必须使用GPU,最终合成的好数据会被送到framebuffer中. 后面分析代码会看到,我们把数据写到MDP就不管了,它内部应该是把数据再调整,然后再写到各个接口(MIPI/LCDC)连接的panel上.这些东西都是SoC,所以你在板子上是看不到的.

    显卡对CPU来说是外设,外设会通过I/O接口连接到I/O总线上,I/O总线再连接到CPU. I/O接口包含多个I/O端口.每个连接到I/O总线上的设备都有自己的I/O地址集,通常称为I/O端口(LKD part13 原话).为I/O编程提供统一的方法,但又不牺牲性能,所以设计者们把一组I/O端口(即一地址集)组织成一组专用的寄存器. 这也就是我们通常所说的I/O端口即寄存器的由来. 所以每个外设都是通过读写其寄存器来控制的.

    I/O接口是处于一组I/O端口和对应的设备控制器之间的一种硬件电路.它起翻译器的作用,即把I/O端口中的值转换成设备所需要的命令和数据.还可以通过一条IRQ线把这种电路连接到可编程中断控制器上,以使它代表相应的设备发出中断请求.

    硬件组织上了解后我们看软件上怎么处理.我们要做的工作是,检测哪些I/O端口已经分配给I/O设备. 通常来讲,I/O设备驱动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据,但是,如果其他硬件设备已经使用了这个端口,那么系统就会崩溃.所以,linux的设计者们便想出用"资源"来记录分配给每个硬件设备的I/O端口.

    资源(resource)被互斥地分配给设备驱动程序,一个资源表示I/O端口地址的一个范围.
    我为什么会说I/O呢,因为帧缓冲区就是I/O内存,和I/O有关,抛砖引玉. 关于I/O端口和I/O内存的区别可是有学问的,你知道吗?不知道是哪位大侠创作或整理的文章,粉好,你要是知道这两者的区别,那就不用看了,你要是不知道,那不妨看一看吧.
    http://blog.csdn.net/insoonior/article/details/8011192#t0

    说到这里我们需要看代码了:
    static struct resource msm_fb_resources[] = {
            {
                    .flags  = IORESOURCE_DMA,
            }
    };

    static struct platform_device msm_fb_device = {
            .name   = "msm_fb",
            .id     = 0,
            .num_resources  = ARRAY_SIZE(msm_fb_resources),
            .resource       = msm_fb_resources,
            .dev    = {
                    .platform_data = &msm_fb_pdata,
            }
    };

    void __init msm_msm7627a_allocate_memory_regions(void)
    {
            addr = alloc_bootmem_align(fb_size, 0x1000);    //这就是framebuffer的物理地址,在framebuffer的驱动中会用到
            msm_fb_resources[0].start = __pa(addr);
            msm_fb_resources[0].end = msm_fb_resources[0].start + fb_size - 1;
    }

    系统启动并初始化时会通过以下调用,把device 的 resource 加入到resource树中:
      # TO tag         FROM line  in file/text
      1  1 platform_add_devices  1455  arch/arm/mach-msm/board-qrd7627a.c
      2  1 platform_device_register   114  drivers/base/platform.c
      3  1 platform_device_add   337  drivers/base/platform.c
      4  1 insert_resource   275  drivers/base/platform.c
      5  1 insert_resource_conflict   667  conflict = insert_resource_conflict(parent, new);
      6  1 __insert_resource   651  conflict = __insert_resource(parent, new);

    有了以上的资源后就清楚了:
    static int msm_fb_probe(struct platform_device *pdev)
    {
                    fbram_size =
                            pdev->resource[0].end - pdev->resource[0].start + 1;
                    fbram_phys = (char *)pdev->resource[0].start;
                    fbram = __va(fbram_phys);
    }

    static int msm_fb_register(struct msm_fb_data_type *mfd)
    {
            struct fb_fix_screeninfo *fix;
            struct fb_var_screeninfo *var;    //主要是根据panel的信息初始化这两个数据结构

            fbram_offset = PAGE_ALIGN((int)fbram)-(int)fbram;
            fbram += fbram_offset;
            fbram_phys += fbram_offset;
            fbram_size -= fbram_offset;
            fbi->screen_base = fbram;
        fbi->fix.smem_start = (unsigned long)fbram_phys;
    }

    smem_start在后面分析mdp_lcdc_update时会用到.
    ------------------------------------------------------------------------------------------
    我们看下数据到framebuffer后是怎么显示到屏幕上的.先从HAL层分析数据的走向,再往上的逻辑待定.

    android/hardware/msm7k/libgralloc-qsd8k/ 这是display subsys的HAL层

    ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info)
    上层调用这句ioctl把数据推给driver,放到framebuffer里

    ioctl(fd, FBIOGET_VSCREENINFO, &info)
    上层通过这句可以获知屏幕的大小

    ioctl一路调用下来会到
    drivers/video/fbmem.c
    static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
                            unsigned long arg)
            case FBIOPUT_VSCREENINFO:
                    if (copy_from_user(&var, argp, sizeof(var)))
                            return -EFAULT;
                    if (!lock_fb_info(info))
                            return -ENODEV;
                    console_lock();
                    info->flags |= FBINFO_MISC_USEREVENT;
                    ret = fb_set_var(info, &var);    //struct fb_info info, struct fb_var_screeninfo var
                    info->flags &= ~FBINFO_MISC_USEREVENT;
                    console_unlock();
                    unlock_fb_info(info);
                    if (!ret && copy_to_user(argp, &var, sizeof(var)))
                            ret = -EFAULT;
                    break;


    int fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
        fb_pan_display(info, &info->var);


    int fb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var)
            if ((err = info->fbops->fb_pan_display(var, info)))
                    return err;


    drivers/video/msm/msm_fb.c
    static int msm_fb_pan_display(struct fb_var_screeninfo *var,
                                  struct fb_info *info)
            if (info->node == 0 && !(mfd->cont_splash_done)) { /* primary */    /* 如果未上电,给panel上电 */
                    mdp_set_dma_pan_info(info, NULL, TRUE);
                    if (msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable)) {
                            pr_err("%s: can't turn on display! ", __func__);
                            return -EINVAL;
                    }
            }
        mdp_dma_pan_update(info);    /* 这句是把数据通过MDP更新到panel */



    drivers/video/msm/mdp_dma.c
    void mdp_dma_pan_update(struct fb_info *info)
        struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
        mfd->dma_fnc(mfd);


    drivers/video/msm/mdp.c
    static int mdp_probe(struct platform_device *pdev)
        case LCDC_PANEL:
            mfd->dma_fnc = mdp_lcdc_update;



    drivers/video/msm/mdp_dma_lcdc.c
    void mdp_lcdc_update(struct msm_fb_data_type *mfd)
            struct fb_info *fbi = mfd->fbi;
            uint8 *buf;
            int bpp;

            bpp = fbi->var.bits_per_pixel / 8;
            buf = (uint8 *) fbi->fix.smem_start;

            buf += calc_fb_offset(mfd, fbi, bpp);

        MDP_OUTP(MDP_BASE + dma_base + 0x8, (uint32) buf);    /* 最终会调到这句,上层抛过来的一堆数据存在buf指针指向的区域,然后
                                       把这个地址写到MDP BASE,然后MDP再写到panel上 */


    drivers/video/msm/mdp.h
    #define MDP_OUTP(addr, data) outpdw((addr), (data))


    drivers/video/msm/msm_fb_def.h
    #define outpdw(port, val)      writel(val, port)
    ------------------------------------------------------------------------------------------
    framebuffer还有好多方方面面没提到的,网上一搜一大把,就不提了.我只写些自己认为需要整理的东西,算是做个整理吧.

    遗留问题:
    addr = alloc_bootmem_align(fb_size, 0x1000)分配的内存怎么与帧缓冲区这个IO内存对应上的?

    第一部分:驱动的硬件配置部分:

    Sep0718 处理器的 lcdc 控制器是带有普通的显示功能和 overlay 功能的,因此硬件配置上可以分成基本配置和额外的配置,基本的配置可以保证 lcdc 的正常运作,实现简单功能(单层的显示),额外配置主要包括对 overlay 的配置,从而实现多层叠加的效果:

    基本配置:

    基本配置包括:

    1.     lcdc 的基本属性配置(极性, dma burst 长度, 屏幕 size ),在 bcr 和 scr 寄存器中;

    2.     Lcdc 的时序配置(行信号,帧信号,对比度控制),在 plcr , pfcr , pccr 寄存器中;

    3.     Base 层的配置( buffer 起始地址,终止地址; base 层的显示位置, base 层的图像格式,RAW_IMAGE_WIDTH_0 DIS_IMAGE_WIDTH_0 ) ,在 bbsar , bbear , btpcr , bbpcr 和 bcr ,RAW_IMAGE_WIDTH_0 DIS_IMAGE_WIDTH_0 寄存器中。

    注意:在对非 buffer 数据地址的 lcdc 参数重新配置时,一定要将 lcdc 使能关闭后才能重新配置。因此,简化为下面的初始化代码为:

        /* 关闭 lcdc 使能寄存器 */

    write_reg(LCDC_ECR,0);

        write_reg(LCDC_BECR,0);

        write_reg(LCDC_W1ECR,0);

        write_reg(LCDC_W2ECR,0);

        write_reg(LCDC_CECR,0);

        /* 此函数中将配置时序及屏幕 size*/

        lcdc_set(800, 480, 30);

    {

        unsigned int parameter = 0;

        unsigned int LCD_PCD,H_value,V_value;

        H_value = X + (H_WIDTH >> 26) +(H_WAIT1 >> 8) + H_WAIT2 + 7;

        V_value = Y + (V_WIDTH >> 26) +(V_WAIT1 >> 8) + V_WAIT2 + 2;

        LCD_PCD = (sysclk / Freq) / (H_value * V_value);

        if (LCD_PCD & 0x1 != 0)

           LCD_PCD = LCD_PCD - 1;

        else

           LCD_PCD = LCD_PCD - 2;

        if (LCD_PCD <= 2)

           LCD_PCD = 2;

        parameter = XMAX(X) | YMAX(Y);

        write_reg(LCDC_SCR,parameter);

       

        parameter = 0;

        parameter = parameter | INT_LEVEL | HB | PIXPOL | FLMPOL | LPPOL | CLKPOL | OEPOL | PCD;

        write_reg(LCDC_BCR,parameter);

       

        parameter = 0;

        parameter = H_WIDTH | H_WAIT1 | H_WAIT2;

        write_reg(LCDC_PLCR,parameter);

     

        parameter = 0;

        parameter = V_WIDTH | V_WAIT1 | V_WAIT2;

        write_reg(LCDC_PFCR,parameter);

       

        parameter = 0;

        parameter = SCR | CC_EN | PW;

        write_reg(LCDC_PCCR,parameter);

        //the reset config of the YUV display

    }

        /*base 层的配置,包括起始地址, size 等 */

    BASE_CONFIGURE(0X40100000,800,800,0,0,799,479);

    {

        U32 X1,X2,parameter;

       

        write_reg(LCDC_BBSAR,BBS_ADDR);

        write_reg(RAW_IMAGE_WIDTH_0,RAW_IMAGE_WIDTH);

        write_reg(DIS_IMAGE_WIDTH_0,DIS_IMAGE_WIDTH);

        X1 = (x1 << 16);

        X2 = (x2 << 16);

        parameter = X1 | y1;

        write_reg(LCDC_BTPCR,parameter);

        parameter = 0;

        parameter = X2 | y2;

        write_reg(LCDC_BBPCR,parameter);

    }

    lcdc_rgb_set(bpp16);

    {

        U32 parameter;

       

        RGB_MODE = rgb_mode;

        parameter = read_reg(LCDC_BCR);

        parameter = parameter | BPIX_LAYER(RGB_MODE);

        write_reg(LCDC_BCR,parameter);

    }

        /* 使能 lcdc*/

    write_reg(LCDC_CECR,1);

    write_reg(LCDC_W2ECR,1);

    write_reg(LCDC_W1ECR,1);

    write_reg(LCDC_BECR,1);

    write_reg(LCDC_ECR,1);  

     

    由于 lcd 控制器的硬件决定, lcdc 没有接受和发送的函数,需要更换显示内容时,只需要更换 lcdc 的 buffer 地址即可, 改变 buffer 起始地址时,先将 LCDC_ACSR 寄存器置 0 ,配置完成后再次置 1 。

     

    第二部分:驱动的软件设计部分 :

    1.   驱动的框架介绍:

    Framebuffer 驱动在本质上是一个字符型驱动,通过文件 /drivers/video/fbmem.c 对每个驱动进行抽象,而 fbmem.c 就是一个典型的字符型驱动,有 open , close , ioctl ;用户态应用程序将通过系统调用访问到 fbmem 的相应接口,fbmem.c 通过 ioctl 操作具体的 fb 驱动。

           对于一个 framebuffer 驱动而言,最重要的是下面几个参数:

    1)   fb_var_screeninfo

    这个结构描述了显示卡的特性:(这个结构体中很多参数和具体的液晶屏有关,因此有大量都是在液晶屏的配置文件中,用蓝色标示) 
    struct fb_var_screeninfo
    {
    __u32 xres; /* visible resolution */   // 可视区域 
    __u32 yres;
    __u32 xres_virtual; /* virtual resolution */  // 虚拟区域,很多场合会用到,简单的意思就是我内存中定义的区间是比较大的,但是可视的仅仅是我的一部分,比如滚屏操作…… 
    __u32 yres_virtual; 
    __u32 xoffset; /* offset from virtual to visible resolution */ // 可视区域的偏移 
    __u32 yoffset;

    __u32 bits_per_pixel; /* guess what */  // 每一象素的 bit 数,这个参数不需要自己配置,而是通过上层在调用 checkvar 函数传递 bpp 的时候赋值的。 
    __u32 grayscale; /* != 0 Gray levels instead of colors */// 等于零就成黑白

    // 通过 pixel per bpp 来设定 red green 和 blue 的位置; pixel per bpp 可以通过 ioctl 设定

    struct fb_bitfield red; /* bitfield in fb mem if true color, */ 真彩的 bit 机构 
    struct fb_bitfield green; /* else only length is significant */
    struct fb_bitfield blue;   
    struct fb_bitfield transp; /* transparency */  透明

    __u32 nonstd; /* != 0 Non standard pixel format */ 不是标准格式

    __u32 activate; /* see FB_ACTIVATE_* */

    __u32 height; /* height of picture in mm */ 内存中的图像高度 
    __u32 width; /* width of picture in mm */ 内存中的图像宽度

    __u32 accel_flags; /* acceleration flags (hints) */ 加速标志

    /* Timing: All values in pixclocks, except pixclock (of course) */

    时序 -_- 这些部分就是显示器的显示方法了,和具体的液晶显示屏有关,在驱动中一般放在 具体液晶屏的配置文件 
    __u32 pixclock; /* pixel clock in ps (pico seconds) */
    __u32 left_margin; /* time from sync to picture */
    __u32 right_margin; /* time from picture to sync */
    __u32 upper_margin; /* time from sync to picture */
    __u32 lower_margin;
    __u32 hsync_len; /* length of horizontal sync */  水平可视区域 
    __u32 vsync_len; /* length of vertical sync */   垂直可视区域 
    __u32 sync; /* see FB_SYNC_* */
    __u32 vmode; /* see FB_VMODE_* */
    __u32 reserved[6]; /* Reserved for future compatibility */ 备用-以后开发 
    };

    2) fb_fix_screeninfon
    这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改 ;比如 FrameBuffer 内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。

    struct fb_fix_screeninfo {
    char id[16]; /* identification string eg "TT Builtin" */ ID
    unsigned long smem_start; /* Start of frame buffer mem */ 内存起始物理地址,也就是 dma** 
    __u32 smem_len; /* Length of frame buffer mem */ 内存大小,这个会根据是不是双 buffer ,是不是 virtual ,有变化。 
    __u32 type; /* see FB_TYPE_* */ 
    __u32 type_aux; /* Interleave for interleaved Planes */ 插入区域? 
    __u32 visual; /* see FB_VISUAL_* */
    __u16 xpanstep; /* zero if no hardware panning */ 没有硬件设备就为零 
    __u16 ypanstep; /* zero if no hardware panning */ 设置成 1 就是标示我们可以在相应的方向移动显示 ,非常重要, android 中使用了 
    __u16 ywrapstep; /* zero if no hardware ywrap */
    __u32 line_length; /* length of a line in bytes */ 一行的字节表示 
    unsigned long mmio_start; /* Start of Memory Mapped I/O */ 内存映射的 I/O 起始 
    /* (physical address) */ 
    __u32 mmio_len; /* Length of Memory Mapped I/O */ I/O 的大小 
    __u32 accel; /* Type of acceleration available */  可用的加速类型 
    __u16 reserved[3]; /* Reserved for future compatibility */
    };

    3 ) struct fb_ops s3cfb_ops = {

           .owner           = THIS_MODULE,

           .fb_check_var = s3cfb_check_var, // 在上面的小节中提到对于一个 LCD 屏来说内核提供了两组数据结构来描述它,一组是可变属性( fb_var_screeninfo 描述),另一组是不变属性( fb_fix_screeninfo 描述)。对于可变属性,应该防止在操作的过程中出现超出法定范围的情况,因此内核应该可以调用相关函数来检测、并将这些属性固定在法定的范围内

           .fb_set_par     = s3cfb_set_par, // 用户应用程序可通过 ioctl 对 FIX 参数进行重新配置,因此在进行这个之前首先会调用 check_var 保证参数在支持范围内。参数可改变的列表见 var 结构体,实际应用中主要是改变屏幕 bpp 和行的长度。

           .fb_blank = s3cfb_blank// 理论是关闭屏幕的操作(分别包括横向,纵向,整个屏) , 在实际中一般分为屏和背光都开,屏开背光关闭,屏和背光都关闭。

           .fb_pan_display      = s3cfb_pan_display,// FBIOPAN_DISPLAY 在 linux 的注释里是 “ 平移显示 ” 的意思。怎么理解呢?就是按照 y 坐标平移显示缓存中的内容。调用 FBIOPAN_DISPLAY 时,会传一个 y 坐标偏移量 yoffset 给驱动,然后驱动会把当前显存的指针偏移 “yoffset X 屏幕宽度 X 位色字节数 ” 个字节,这样就好像实现了图像的 y 坐标平移,也就是 “ 平移显示” 。当这个 yoffset 等于屏幕高度的时候,就实现了显存的切换。

           .fb_setcolreg   = s3cfb_setcolreg,

           .fb_fillrect      = cfb_fillrect,

           .fb_copyarea   = cfb_copyarea,

           .fb_imageblit  = cfb_imageblit,

           .fb_cursor       = soft_cursor,

           .fb_ioctl  = s3cfb_ioctl,

    };

    因此 sep0718fb.c 驱动所做的主要工作就是完成这三个大结构的配置填充,并且使之和第一部分的硬件配置挂钩在一起。

     

    2.   代码接口:

    a)          重要结构体说明:

    综上, framebuffer 驱动主要涉及两方面,一方面涉及到 framebuffer 的参数配置,一方面涉及到 lcdc 硬件的配置,因此我设计了两个主要的结构体。

    typedef struct {

    struct fb_info         fb;

    struct device          *dev;

    struct clk        *clk;

    struct resource        *mem;

    void __iomem        *io;

    unsigned int           win_id;

    unsigned int           max_bpp;

    unsigned int           max_xres;

    unsigned int           max_yres;

    /* raw memory addresses */

    dma_addr_t           map_dma_f1;  /* physical */

    u_char *         map_cpu_f1;   /* virtual */

    unsigned int           map_size_f1;

     

    /* addresses of pieces placed in raw buffer */

    dma_addr_t           screen_dma_f1;      /* physical address of frame buffer */

    u_char *         screen_cpu_f1;       /* virtual address of frame buffer */

     

    unsigned int           lcd_offset_x;

    unsigned int           lcd_offset_y;

    unsigned int   pseudo_pal[16];

    } sep0718fb_info_t ;

    sep0718fb_info_t 是关于 framebuffer 参数配置的结构体,里面主要包含了 fb_info 结构体(这个结构体中包含了上面提到的三个重要的参数 var , fix , fb_ops )。

     

    typedef struct

    {

    /* Screen size */

    int width;

    int height;

     

    /* Screen info */

    int xres;

    int yres;

     

    /* Virtual Screen info */

    int xres_virtual;

    int yres_virtual;

    int xoffset;

    int yoffset;

     

    /* OSD Screen size (overlay 1)*/

    int osd_width;

    int osd_height;

     

    /* OSD Screen info */

    int osd_xres;

    int osd_yres;

     

    /* OSD Screen info */

    int osd_xres_virtual;

    int osd_yres_virtual;

     

    int bpp;

    int bytes_per_pixel;

    unsigned long pixclock;

     

     

    /* lcd configuration registers */

    struct sep0718fb_hw regs;

     

    int hsync_len;

    int left_margin;

    int right_margin;

    int vsync_len;

    int upper_margin;

    int lower_margin;

    int sync;

     

    int cmap_grayscale:1;

    int cmap_inverse:1;

    int cmap_static:1;

    int unused:29;

     

    /* backlight info */

    int backlight_min;

    int backlight_max;

    int backlight_default;

     

    int vs_offset;

    int brightness;

    int palette_win;

    int backlight_level;

    int backlight_power;

    int lcd_power;

    }sep0718_lcdc_info_t ;

    sep0718_lcdc_info_t 是关于 0718 lcdc 硬件的结构体。里面包含了 0718 的寄存器映射列表,用于和sep0718fb_info_t 值交互的一些参数。

     

    3.   驱动详细说明:

    Sep0718fb 驱动所采用的架构是基于 platform 的形式。 Probe 函数是驱动的初始化函数, remove 是驱动的卸载函数, suspend 和 resume 是驱动的挂起和恢复函数,在电源管理的时候会用到。

     

    Probe 函数是整个系统的核心 ,由于之前提到由于 lcdc 的使用流程更多的是对寄存器的配置,因此 probe 对于整个驱动非常重要。 Probe 主要实现了两件事情,完成了对硬件的初始化以及对 framebuffer 结构的申请初始化注册。

    硬件的初始化分布在 probe 中的两个地方:

    1. if (index == 0)

                   sep0718fb_init_hw(&sepfb_info[index]);

    这里完成了 lcdc 的最基本的配置,比如时序,极性,分辨率等,因此这部分的内容是跟具体的屏幕有关的,因此这个函数是需要针对不同的屏幕实现的,具体的代码在 fb800_480.c.

    2. ret = sep0718fb_init_registers(&sepfb_info[index]);

    由于我们的 lcdc 是 overlay 多层架构的,因此将针对不同的层分别进行配置。

           对 framebuffer 结构体的初始化主要分布在 probe 中的两个函数:

    sep0718fb_init_fbinfo(&sepfb_info[index], driver_name, index);// 完成了对大部分 fb 参数的配置

           /* Initialize video memory */

           ret = sep0718fb_map_video_memory(&sepfb_info[index]);// 主要完成了对缓冲区的配置。

     

    struct fb_ops sep0718fb_ops 是整个驱动在初始化结束后会涉及的内容, 由于在 probe 中会将 sep0718fb_ops结构体注册到内核中。因此在驱动完成初始化后,其实就是这个结构体在起作用,因此下面讲一下这个结构体所做的工作。在这个结构体中具体是驱动设计到的函数有:

    fb_check_var: 在上面的小节中提到对于一个 LCD 屏来说内核提供了两组数据结构来描述它,一组是可变属性(fb_var_screeninfo 描述),另一组是不变属性( fb_fix_screeninfo 描述)。对于可变属性,应该防止在操作的过程中出现超出法定范围的情况,因此内核应该可以调用相关函数来检测、并将这些属性固定在法定的范围内。我们在这里的实现主要是针对 bits_per_pixel 的。

     

    fb_set_var : 用户应用程序可通过 ioctl 对 FIX 参数进行重新配置,因此在进行这个之前首先会调用 check_var 保证参数在支持范围内。参数可改变的列表见 var 结构体,实际应用中主要是改变屏幕 bpp 和行的长度。这里需要注意的一个地方时在 set_var 函数的内部我们是通过 active 函数来使寄存器重新配置的,对寄存器参数进行重新配置时一定要对 lcdc 先关闭,然后配完了再使能。

     

    fb_pan_display : FBIOPAN_DISPLAY 在 linux 的注释里是 “ 平移显示 ” 的意思。怎么理解呢?就是按照 y 坐标平移显示缓存中的内容。调用 FBIOPAN_DISPLAY 时,会传一个 y 坐标偏移量 yoffset 给驱动,然后驱动会把当前显存的指针偏移 “yoffset X 屏幕宽度 X 位色字节数 ” 个字节,这样就好像实现了图像的 y 坐标平移,也就是 “ 平移显示” 。当这个 yoffset 等于屏幕高度的时候,就实现了显存的切换。由于 android 中的要求是对 y 方向平移显示,所以我们在这里的实现也是平移显示的。注意是 y 平移还是 x 平移是在 sep0718fb_init_fbinfo 初始化函数中的       finfo->fb.fix.ypanstep = 1; 所决定的。

     

    fb_ioctl : ioctl 函数主要是为驱动实现一些 framebuffer 架构没有包含的一些特殊的特性,用户应用程序可以通过ioctl 来操作这些特殊的操作。 Sep0718 的 overlay , alpha blending , color key 等功能就是通过此处实现的。

    第三部分:驱动的内核配置及代码分布:

    1: 文件位置说明(具体文件, kconfig , makefile )

    整个驱动主要分为 3 个文件,均在 /drivers/video/sep0718 目录: sep0718_fb.c 是整个 framebuffer 的最主要的组成,包含了 fb 的所有操作,硬件配置; fb800_480.c 是具体液晶屏的配置文件; sep0718_fb.h 是 fb 的头文件。

     

    由于驱动是 platform 结构,因此还有一部分关于 device 设备描述的代码在 /arch/arm/mach_sep0718/board.c 中。

     

    相应 kconfig 代码位置: /drivers/video/kconfig line245-295

    comment "SEP0718 Frame buffer hardware drivers config"

    depends on FB

     

    config FB_SEP0718

    tristate "SEP0718 frame buffer support "

    depends on FB && ARCH_SEP0718

    select FB_CFB_FILLRECT

    select FB_CFB_COPYAREA

    select FB_CFB_IMAGEBLIT

    help

              Frame buffer driver for SEP0718 based boards.

     

    choice

    prompt "SEP0718 LCDC TYPE"

    depends on FB_SEP0718

    default FB_SEP0718_800_480

     

    config FB_SEP0718_800_480

    bool "800*480 lcd support "

    depends on FB_SEP0718

     

    endchoice

     

     

    choice

    prompt "SEP0718 LCDC COLOR TYPE"

    depends on FB_SEP0718

    default FB_SEP0718_BPP_16

     

    config FB_SEP0718_BPP_16

    bool "16 bpp"

    depends on FB_SEP0718

     

    config FB_SEP0718_BPP_18

    bool "18 bpp unpacked"

    depends on FB_SEP0718

     

    config FB_SEP0718_BPP_24

    bool "24 bpp unpacked"

    depends on FB_SEP0718

     

    endchoice

     

    config FB_SEP0718_NUM

        int "Number of Framebuffers"

        depends on FB_SEP0718

        default "1"

     

    config FB_SEP0718_VIRTUAL_SCREEN

    bool "sep0718 virtual screen support"

    depends on FB_SEP0718

     

    comment "********************"

    depends on FB

     

    相应的 makefile 的位置, drivers/video/sep0718/makefile :

    obj-$(CONFIG_FB_SEP0718) += sep0718_fb.o

    obj-$(CONFIG_FB_SEP0718_800_480) += fb800_480.o

     

    通过这种架构的实现,以后增加不同型号液晶屏,只需直接增加一个液晶屏的配置文件,可以完全拷贝 fb800_480.c的代码,只需对文件开始处的宏定义按照所用的屏进行修改即可(当然也需要按照 fb800_480.c, 对 kconfig 和makefile 部分进行修改,即按照红色部分进行增加)。

     

    2:  make menuconfig 选择:

    简单驱动选择路径:

    一次选上framebuffer驱动,控制台输出,bootuplogo

  • 相关阅读:
    ActiveSync合作关系对话框的配置
    WINCE对象存储区(object store)
    Wince 隐藏TASKBAR的方法
    Wince输入法换肤换语言机制
    poj 3080 Blue Jeans 解题报告
    codeforces A. Vasily the Bear and Triangle 解题报告
    hdu 1050 Moving Tables 解题报告
    hdu 1113 Word Amalgamation 解题报告
    codeforces A. IQ Test 解题报告
    poj 1007 DNA Sorting 解题报告
  • 原文地址:https://www.cnblogs.com/biglucky/p/4421997.html
Copyright © 2011-2022 走看看