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

  • 相关阅读:
    @pytest.mark.parametrize使用笔录
    Python常用模块/函数
    Python代码驱动facebook-Wda
    pytest简述--pytest.ini、allure、logging、assert的使用
    终端命令启动webDriverAgent
    Wda自动化环境搭建[IOS]
    windows10常用终端命令+adb shell
    Python-unittest框架+HtmlTestRunner
    appium+python Android UI自动化环境搭建(windows10)
    调用百度AI实现人脸检索
  • 原文地址:https://www.cnblogs.com/biglucky/p/4421997.html
Copyright © 2011-2022 走看看