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!\n", __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

时间: 2024-11-09 02:00:53

LCD framebuffer驱动设计文档的相关文章

DDD领域驱动设计 - 设计文档模板

设计文档模板: 系统背景和定位 需求描述 系统用例图 关键业务流程图 领域语言整理,主要是整理领域中的各种术语的定义,名词解释 领域划分(分析出子域.核心域.支撑域) 每个子域的领域模型设计(实体.值对象.聚合.领域事件,需要注意的是:领域模型是需要抽象的,要分析业务本质,而不是简单的直接对需求进行建模) 领域模型详细说明(如为什么这样设计的原因.模型内对象的关系.各种业务规则.数据一致性规则等) 领域服务.仓储.工厂设计 Saga流程设计 场景走查(讲述如何通过领域模型.领域服务.仓储.Sag

VM架构设计文档初稿v0.01

VM架构设计文档初稿v0.01 文档介绍 本文档是经过讨论,作为VM新架构设计开发中的重要依据.对该架构的整个系统的结构进行详实细致的描述.阐述框架结构,说明该架构所采取的设计策略和所有技术,并对相关内容作出统一的约定.为设计,编码,测试提供可以参考的模板和帮助.提高设计变更开发的效率,将头脑风暴的结果进行的具体的书面呈现. 架构设计思想 该架构VM以微服务思想为核心进行衍化,兼容DevOps作为主要基础,并使用DDD领域驱动设计思想作为设计过程中的指导思想及方法论. 架构体系描述 以分层体系作

《团队-科学计算器-设计文档》

设计文档: 项目:科学计算器 编辑器:python 所运用知识: 1.字符串的处理 2.正则表达式的运用 3.函数递归 基本思路: 需要优先处理内层括号运算--外层括号运算--先乘除后加减的原则: 1.正则处理用户输入的字符串,然后对其进行判断,判断计算公式是否有括号,有就先将计算公式进行正则处理,先获取最里层的每一个数据,然后一一计算 2.把有括号的计算公式计算出来的结果替换原来初始公式的位置,计算之前分别对重复运算符进行处理需要处理的重复运算 3.然后依次从里到外去除括号并进行计算,和位置替

《结对编项目作业名称-设计文档》

项目:关灯游戏,所用软件,pygame 成员:祁昊,刘孝东 关灯游戏设计文档: pygame作为一种游戏编程语言,以其简单性.可移植性等优点,得到了广泛地应用,特别是py使用比c,c++等语言简便,使其成为网络编程首选编程语言.,Pygame是跨平台Python模块,专为电子游戏设计.基于这样一个设想,所有需要的游戏功能和理念都(主要是图像方面)都完全简化为游戏逻辑本身,所有的资源结构都可以由高级语言提供,如Python.工具tile编辑器和一个关卡编辑器.得到广大程序员的接受和认可. "关灯游

Storm项目:流数据监控1《设计文档…

该文档为实实在在的原创文档,转载请注明作者及出处. 类型 详细 备注 2 该文档为原创模拟项目:流数据监控<1>文档<流数据监控设计文档>,相继会给出流数据监控<2>文档<流数据监控代码解析>及其他文档 2  该部分有源码(熬夜写出来的哦) CSDN中相应项目CODE链接:戳这里     相关描述 2  有任何其他想法,可以邮件[email protected] 2 文档及相关资料下载请到个人360云盘http://yunpan.cn/QGf2GDaRFpc

Atitit.atiagent &#160;agent分销系统 代理系统 设计文档

Atitit.atiagent  agent分销系统 代理系统 设计文档 1. 启动项目1 2. 首也2 3. 登录功能2 4. 用户中心2 5. 充值查询3 6. 授权下级代理4 7. 我的提成5 8. 查看下级玩家6 9. 查看下级代理7 10. 数据库文档 agent7 10.1. Acc 用户帐号以及上级代理id关联字段7 10.2. 充值记录表8 1. 启动项目 C:\0workspace\AtiPlatf_cms\resin run q2b_game.bat Prj::cms 数据库

《结对-自然语言进行数据库查询系统-设计文档》

二〇一七年九月十四日十点一刻少两分钟 关于结对编程的设计文档: 题目:自然语言进行数据库查询系统 编程语言:C# 数据库:MySql ,其他逐渐扩展 软件所要实现的功能: 用户打开软件之后可以连接到数据库,并且通过自然语言进行数据库的查询,例如我想知道小明的学号,如果在数据库中查询需要输入 select ID from 学生表 where name = "小明"才能实现,我们要做的是,输入查询小明的学号,软件就可以将自然语言转换成sql语句进行数据库的查询. 所要实现的功能: 1.进行

为什么要写设计文档

日趋一日,程序员能够在更少的时间内完成更多的事情.使用今日的高级编程语言,开发环境,工具和“快速应用开发”思想,程序员和经理都已经习惯于急速的开发周期.今日的程序员更倾向于直接跳入到编码之中,害怕花费在非编码工作中的每一小时,都会导致项目截止日期前的周末多加一个小时班. 编码之前做设计这一过程已经变得过时了,将设计文档化就更罕见了.很多程序员从来没有写过设计文档,面对要写设计文档这一想法都畏缩不前.即使被要求写,通常来说也只是产出了一大堆的交互图和类图,这些图表大多没有表达程序员在设计阶段的思考

设计文档

在大多数软件项目中,要末不作详细设计,要么开发完成后再补详细设计文档,质量也不容乐观,文档与系统往往不能同步,使详细设计文档完全流于形式,对工作没有起到实际的帮助. 那到底应不应该写详细设计文档呢,怎么使详细设计文档起到他应有的作用呢,下面就让我们来认识一下详细设计及写详细设计文档的好处和问题. 什么是详细设计 详细设计是相对概要设计而言的,是瀑布开发流程的一个重要环节,在概要设计的高层设计的基础上,从逻辑上实现了每一模块的功能,是编码阶段的主要参考资料,是从高层到低层.逐步精化思想的具体实现.