Chinaunix首页 | 论坛 | 博客
  • 博客访问: 77461
  • 博文数量: 63
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 14
  • 用 户 组: 普通用户
  • 注册时间: 2018-09-15 14:26
文章分类

全部博文(63)

文章存档

2018年(63)

我的朋友

分类: LINUX

2018-09-15 14:42:26

原文地址:LCD框架介绍 作者:wylhistory

LCD架构介绍



.


 

Revision History

Date Issue Description Author

<04/15/2010> <0.1> LCD架构分析 Wylhistory

目录

1. 摘要 3

2. 介绍 3

3. 思路 3

4. 初始化 4

4.1 设备和驱动注册 4

4.2 PROBE函数的执行 6

5. 用户使用流程 8

5.1 上层软件使用流程 8

5.2 驱动处理逻辑 9

6. 总结 12

7. 未讨论 12


 

1. 摘要

分析一下在pxa310平台,LCD驱动的架构;

2. 介绍

每次换LCD的屏都会经历一个痛苦的过程,所以想理一下它的架构,以便在以后的集成过程中,能有一个简单的指导;

硬件平台:pxa310

软件平台:android 2.1

内核:2.6.29

3. 思路

对于驱动的集成,我的一个原始的思路是这样的,驱动包含两方面的内容,

A) 硬件管理;

B) 提供对上接口,也就是提供给应用层接口;

从硬件管理的角度来说,任何一个模块想要工作,比如wifi,蓝牙, LCD等都必须要和我们的CPU相连,以实现通讯,所以要让这些模块工作,从上面一句话里面可以得出的结论就是这样:

(模块正常工作)==(模块本身工作正常)+(模块控制器工作正常)+(接口工作正常);

也就是说,想让一个驱动工作,就必须要考虑这三方面的内容,对应于我们要讨论的LCD的驱动,也就意味着:

A) LCD屏本身需要正确的初始化,以进入正常工作的状态(这个目前的lcd屏都不提供反馈信号);

B) LCD controller本身要正确的初始化,以进入正常的工作状态(这个可以通过寄存器的打印验证);

C) 接口需要正确配置,以实现LCD controller和LCD屏之间的通讯,接口通常都包含命令接口和数据接口,我们的命令接口是SPI的(通过gpio口模拟SPI协议),所以需要对SPI接口做相应的初始化,数据接口一般分为RGB接口和CPU接口,rgb一般是16根线,有时候需要扩展以适应屏的pin脚,而cpu通过是8根,我们这边也需要扩展,比如高位扩展比如我们这边传递5位的red,那么我们的m4——m0位接到对方的p7——p3,而对方的p0——p2就接到我们的m4——m2(这个可以通过示波器验证);

而从对上的接口来说,各个驱动通常都有统一的接口,比如:

Open,close,ioctl,write,read,mmap,当然不同的驱动有可能只选择某些来实现,对于LCD驱动来说,最重要的当然是Open,ioctl,以及mmap,我们后面可以看看它的代码逻辑;

下面会从初始化和上层使用角度来具体分析:

4. 初始化

4.1 设备和驱动注册

 

图4.1设备和驱动的注册


有的驱动设备的注册和驱动的注册是放在不同的地方的,不过LCD的驱动是放在同一个函数里面的,就在pxafb.c里面的pxafb_init里面;

按照惯例,是先注册设备,再注册驱动,我们可以看到,设备的注册最后到了函数pxa_register_device里面,看看它的代码:

void pxa_register_device(struct platform_device *dev, void *data)

{

int ret;


dev->dev.platform_data = data;


ret = platform_device_register(dev);

if (ret)

dev_err(&dev->dev, "unable to register device: %d\n", ret);

}

这里最重要的就是上面蓝色这句话,也就是保存了传入的这个info参数到platform_data里面,后面可以看到,很多操作都是需要这个数据结构的参与的,甚至可以说,这个数据结构对于LCD的驱动来说是至关重要的,那么不看看它,就不够人情了,如下:

struct pxafb_mach_info lin2008_lcdpar = {

        .modes                  = lin2008_modes,

        .num_modes              = 1,

.lccr0 = LCCR0_Act ,

.lccr3 = LCCR3_STALL | LCCR3_OutEnH,

};

这里先看看这个lccr0的值,注释如下:

#define LCCR0_Act       (LCCR0_PAS*1)   /*  Active display (TFT)           */

简单说也就是定义这个屏的类型;

Lccr3的值定义如下:

#define LCCR3_STALL (1 << 28) /* Stall pixel clock if underrun occurs */

#define LCCR3_OutEnH    (LCCR3_OEP*0)   /*  Output Enable active High      */

翻译一下就是说,如果发生没有数据传输的情况,那么就停止像素时钟;

第二行,使能输出,并且是高有效;

这里我最关心的还是lin2008_modes,我们看看它的内容:

static struct pxafb_mode_info lin2008_modes[] = {

        [0] = {

             //   .pixclock       = 68296,  // consider for 812*492*36 frame 

// .pixclock = 48344,    // consider for 812*492*60 frame 

.pixclock = 40977,    // consider for 504*807*60

// .pixclock = 68296,          // consider for 504*807*36

                .xres           = 480,

                .yres           = 800,

                .bpp            = 16,

                .hsync_len      = 7,

                .left_margin    = 8,

                .right_margin   = 16,

.vsync_len = 1,

                .upper_margin   = 4,

                .lower_margin   = 3,

                .sync           = 0,

        },

};

这里面的信息都很重要,一个个来说:

Pixclock:这里是指时钟频率的倒数,否则怎么算都不对的,而且这里没有计算行同步和帧同步,40997== (1<<12)/(480+8+16)×(800+4+3)×60;也就是说这个值等于x的分辨率加上前廊+后廊以后与y的分辨率加上上廊加下廊后的值的乘积;

OK,现在我们回到先前设备注册的地方,也就是函数pxa_register_device里面的dev成员,如下:

struct platform_device pxa_device_fb = {

.name = "pxa2xx-fb",

.id = -1,

.dev = {

.dma_mask = &fb_dma_mask,

.coherent_dma_mask = 0xffffffff,

},

.num_resources = ARRAY_SIZE(pxafb_resources),

.resource = pxafb_resources,

};

这里面唯一需要关注的就是这个资源,如下:

static struct resource pxafb_resources[] = {

[0] = {

.start = 0x44000000,

.end = 0x4400ffff,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = IRQ_LCD,

.end = IRQ_LCD,

.flags = IORESOURCE_IRQ,

},

};

也就是说与LCD controller相关的寄存器的地址范围就在0x44000000——0x4400ffff之间;

既然设备以及注册了,我们再看看驱动的注册,

platform_driver_register(&pxafb_driver),这里最重要的还是pxafb_driver,下面看看它的值:

static struct platform_driver pxafb_driver = {

.probe = pxafb_probe,

#ifdef CONFIG_PM

.suspend = pxafb_suspend,

.resume = pxafb_resume,

#endif

.driver = {

.name = "pxa2xx-fb",

},

};

4.2 Probe函数的执行

根据platform设备的配备原则,只要名字相同,就可以结婚了,正所谓“金风玉露一相逢,便调用probe函数”

于是这里的probe函数也就是pxafb_probe开始被调用了,这个函数挺长的,总结一下,至少干了以下几件事:

1, 调用lcdchip_init对LCD屏本身进行初始化;

2, 取出inf = dev->dev.platform_data,这是先前我们在设备注册的时候赋值的,它是原始数据的来源,后面的很多操作很它有关;

3, 一堆的参数检查,不讨论;

4, 构建framebuffer信息,对于pxa310的架构,有三个层,所以不同的层拥有不同的framebuffer信息,但是这些信息的初始都来自于先前的platform_data,可以把这一步的动作理解为我前面总结的是为了提供对上接口,而做的准备,这里会对我们在应用层看到的var 信息和fix信息做填充;需要单独说明的是这两句代码:fbi->fb.fbops = &pxafb_ops;……; INIT_WORK(&fbi->task, pxafb_task);前一个设置函数操作集,后面一句话初始化一个work pxafb_task后面会用到;

5, 通过pxafb_map_video_memory函数分配framebuffer空间,空间的长度为xres*yres*bpp/2(如果是android系统还会再乘以2,因为是双缓冲)+PAGESIZE,这个pagesize,通常是用来存放DMA描述符的,如果要调整上层看到的framebuffer的大小,这里需要修改,当然返回给上层的smem_len也必须要修改;

6, 设置中断处理函数,通常也就用在调试的时候;

7, 通过pxafb_set_par设置DMA描述符的地址,以及描述符里面的内容,如果有某个相关的寄存器有改变,那么就通过pxafb_schedule_work(fbi, C_REENABLE)来启动在第4步里面设置的work也就是pxafb_task来运行,注意不是立即运行;

8, 通过register_framebuffer(&fbi->fb)来注册这个framebuffer信息,本质上就是让registered_fb【index】指向先前我们分配的这个framebuffer信息,以便在后面使用的时候可以取出来;

9, 调用set_ctrlr_state(fbi, C_ENABLE)再通过pxafb_enable_controller 来enable controller,这里会把先前在第7步里面赋好的值放入具体的寄存器;

好现在先看看第4步里面的pxafb_ops:

static struct fb_ops pxafb_ops = {

.owner = THIS_MODULE,

.fb_check_var = pxafb_check_var,

.fb_set_par = pxafb_set_par,

.fb_setcolreg = pxafb_setcolreg,

.fb_fillrect = cfb_fillrect,

.fb_copyarea = cfb_copyarea,

.fb_imageblit = cfb_imageblit,

.fb_blank = pxafb_blank,

.fb_mmap = pxafb_mmap,

.fb_pan_display = pxafb_pan_display,

#ifdef CONFIG_FB_PXA_MINILCD

.fb_ioctl       = pxafb_minilcd_ioctl,

#else

.fb_ioctl = pxafb_ioctl,

#endif

};

再看看这个pxafb_task:

static void pxafb_task(struct work_struct *work)

{

struct pxafb_info *fbi =

container_of(work, struct pxafb_info, task);

u_int state = xchg(&fbi->task_state, -1);


set_ctrlr_state(fbi, state);

}

这里面先取得fbi->task_state放到state里面,估计是为了实现互斥的访问,所以搞了一个xchg操作;然后通过set_ctrlr_state来实现真正的LCD controller的寄存器的操作;

总结下来就是说,参数的原始设置在static struct pxafb_mode_info lin2008_modes里面,必须要正确填写,估计不同的屏这个地方都需要修改,然后就是controller的enable,这个是需要到set_ctrlr_state的时候,才会被传递到寄存器的,那么有就是说,在这之前,必须要保证相关的值已经被正确的赋给了framebuffer info,需要注意的就是那个描述符的内容的填写,结构如下:

struct pxafb_dma_descriptor {

unsigned int fdadr;

unsigned int fsadr;

unsigned int fidr;

unsigned int ldcmd;

};

fdadr:下一个描述符的地址,注意这个是DMA的地址,在我们的平台上就是物理地址,通常可以让它指向这个结构本身的地址,这样就可以形成一个环,从而实现一直刷新;

fsadr:这个是数据的地址,也就是说应用层在这个地方开始写的数据,对于android平台,这个地址有可能是framebuffer的起始地址,也有可能是起始地址+xres*yres*bpp/2;

fidr,这个值没怎么使用到,我们都填的是0;

ldcm,也就是这个数据的长度,如果要刷一屏的话,对于800X480的分辨率16位色,那么就是800*480*2;


5. 用户使用流程

5.1 上层软件使用流程

代码如下:

static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer)

{

    if (private_handle_t::validate(buffer) < 0)

        return -EINVAL;


    fb_context_t* ctx = (fb_context_t*)dev;


    private_handle_t const* hnd = reinterpret_cast(buffer);

    private_module_t* m = reinterpret_cast(

            dev->common.module);

    

    if (m->currentBuffer) {

        m->base.unlock(&m->base, m->currentBuffer);

        m->currentBuffer = 0;

    }


    if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {


        m->base.lock(&m->base, buffer, 

                private_module_t::PRIV_USAGE_LOCKED_FOR_POST, 

                0, 0, m->info.xres, m->info.yres, NULL);


        const size_t offset = hnd->base - m->framebuffer->base;

        m->info.activate = FB_ACTIVATE_VBL;

        m->info.yoffset = offset / m->finfo.line_length;

        if (ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info) == -1) {

            LOGE("FBIOPUT_VSCREENINFO failed");

            m->base.unlock(&m->base, buffer); 

            return -errno;

        }

        m->currentBuffer = buffer;

        

    } else {

        // If we can't do the page_flip, just copy the buffer to the front 

        // FIXME: use copybit HAL instead of memcpy

        

        void* fb_vaddr;

        void* buffer_vaddr;

        

        m->base.lock(&m->base, m->framebuffer, 

                GRALLOC_USAGE_SW_WRITE_RARELY, 

                0, 0, m->info.xres, m->info.yres,

                &fb_vaddr);


        m->base.lock(&m->base, buffer, 

                GRALLOC_USAGE_SW_READ_RARELY, 

                0, 0, m->info.xres, m->info.yres,

                &buffer_vaddr);


        memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres);

        

        m->base.unlock(&m->base, buffer); 

        m->base.unlock(&m->base, m->framebuffer); 

    }

    

    return 0;

}

这个函数会在surfaceflinger层的postframebuffer到opengl层到nativewindow层的queueBuffer里面会调用fb->post(fb, handle)触发这个函数的执行;

从驱动的角度来说,它最关心的还是FBIOPUT_VSCREENINFO这个ioctl,下面看看驱动对应的处理逻辑;

5.2 驱动处理逻辑

如下图所示:

 

上层的ioctl会触发这个fb_ioctl的执行,在里面首先从registerd_fb[fbidx]取出先前我们注册的framebuffer信息(对于fb0来说就是在probe函数里面通过register_framebuffer来实现注册的);然后通过fb_set_var把这个信息传递下去,var代表了上层传下来的数据信息,这个函数也是相当的长,不过我还是贴出来了,去掉了一写错误处理:

int

fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)

{

int flags = info->flags;

int ret = 0;

……………………….

…………………….....


if ((var->activate & FB_ACTIVATE_FORCE) ||

   memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) {

u32 activate = var->activate;

if (!info->fbops->fb_check_var) {

*var = info->var;

goto done;

}


ret = info->fbops->fb_check_var(var, info);

if (ret)

goto done;


if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {

struct fb_videomode mode;

if (info->fbops->fb_get_caps) {

ret = fb_check_caps(info, var, activate);


if (ret)

goto done;

}


info->var = *var;


if (info->fbops->fb_set_par)

info->fbops->fb_set_par(info);


fb_pan_display(info, &info->var);

fb_set_cmap(&info->cmap, info);


fb_var_to_videomode(&mode, &info->var);


if (info->modelist.prev && info->modelist.next &&

   !list_empty(&info->modelist))

ret = fb_add_videomode(&mode, &info->modelist);



if (!ret && (flags & FBINFO_MISC_USEREVENT)) {

struct fb_event event;

int evnt = (activate & FB_ACTIVATE_ALL) ?

FB_EVENT_MODE_CHANGE_ALL :

FB_EVENT_MODE_CHANGE;


info->flags &= ~FBINFO_MISC_USEREVENT;

event.info = info;

event.data = &mode;

fb_notifier_call_chain(evnt, &event);

}

}

}


 done:

  return ret;

}

A) 先通过pxafb_check_var检查传入的参数的有效性;

B) 调用info->fbops->fb_set_par(info)也就是pxafb_set_par计算新的DMA传输相关的参数,通过调用pxafb_activate_var来把新的包括行同步,帧同步,前廊后廊,上廊下廊等也包括DMA描述符内容的更新保存在fbi->reg*成员里面,然后触发pxafb_task的调度,注意这时候还没有被执行;

C) fb_pan_display-> info->fbops->fb_pan_display(var, info),最后就调用到了pxafb_pan_display,这个函数最主要的作用就是pxafb_schedule_work(fbi, C_CHANGE_DMA_BASE);注意这个状态因为是后设置的,也就是说前面在fb_set_par的时候设置的状态将会被这个状态覆盖,那么最后在执行pxafb_task的时候取得的状态将会是这个C_CHANGE_DMA_BASE的状态;


等这个pxafb_task被调度执行的时候,将会走到C_CHANGE_DMA_BASE的分支,如下:

static void pxafb_task(struct work_struct *work)

{

struct pxafb_info *fbi =

container_of(work, struct pxafb_info, task);

u_int state = xchg(&fbi->task_state, -1);


set_ctrlr_state(fbi, state);

}

void set_ctrlr_state(struct pxafb_info *fbi, u_int state)

{

……

case C_CHANGE_DMA_BASE:

fbi->dmadesc_fbhigh_cpu->fsadr = fbi->screen_dma +

(fbi->fb.var.xres*fbi->fb.var.yoffset*\

fbi->fb.var.bits_per_pixel/8);

break;

……

}

可以看到基本上就是修改fsadr的地址,它的值,我们前面有看到就是实际的数据的起始地址,通过这样的方式,那么下一次DMA数据传递的时候,数据将会从这个地址取得,从而实现了android平台下的双缓冲机制;

6. 总结

A)对于分辨率的修改,也就是说,如果屏本身支持这个新的分辨率,那么只需要对 static struct pxafb_mode_info lin2008_modes这个结构体修改就可以了,它这里面的xres,yres是后面许多数据结构里面的值的根源,如果需要在只支持800X480的屏上模拟320X480的话,还需要修改前廊后廊以扩展到相应的pixelclock就应该可以了;

B)真正对于controller的设置都是在set_ctrlr_state里面做的,而这些值的设置都是在pxafb_activate_var里面做的,也就是说,如果要欺骗controller也就是说为了实现实际的值和上层看到的值的差异性,我想可以在这里做hack;

C)对于屏不亮的问题,我想首先要考虑的是电压问题,如果电压正常,那么就考虑另外四根线,CLOCK,hsync,vsync,以及de线,如果这个都正常,再看看数据线是否正常,这些都可以通过示波器看出来,当然正确的对屏本身的初始化是前提,而屏无法提供反馈信号,这也是调试LCD驱动最痛苦的地方,因为你无法判断初始化是否已经正确;

D)可以通过打开中断来判断,每帧数据的发送情况,当然,里面的打印信息不是完全有效,但是可以提供一定的参考,而且如果它在一直打印,至少可以说明,数据在不断的刷新;

E)显示结果和应该的结果如果有差异,可以通过抓出framebuffer的内容转化层图片来比较;当然也可以通过cat一幅图片到framebuffer来判断驱动是否已经工作,如果开始的时候cat工作,过了一定时间cat又不工作,说明在这个过程中某些硬件的配置已经被改变了,这个需要看看是不是别的模块和你的设置有冲突;


7. 未讨论

1, controller寄存器的详细设置没有分析,这个我觉得需要对照spec详细看;

2, lcd屏本身的初始化没有仔细看,这个我觉得和屏本身有关;

3, 其它;

备注:

作者:wylhistory

联系方式: wylhistory@gmail.com


阅读(618) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~