10. LCD驱动程序 ——框架分析

引言:html

由LCD的硬件原理及操做(可参看韦哥博客:第017课 LCD原理详解及裸机程序分析node

咱们知道只要LCD控制器的相关寄存器正确配置好,就能够在LCD面板上显示framebuffer中的内容。linux

若应用程序须要在LCD屏幕上显示文字或图像时,只须要把相应的显示内容以正确的格式写到Framebuffer中便可。c++

(Framebuffer,中文名字是帧缓冲,这个帧也就是一副图像所须要的数据。所以,帧缓冲其实就是LCD设备的驱动程序)算法

 

一.LCD驱动程序框架数组

根据上述思路,Linux LCD 驱动程分为两个层次,以下图所示数据结构

相似于Platform 平台驱动框架,也将驱动程序分为相对稳定的算法驱动,即fb总线驱动,与易变的设备驱动,即fb设备驱动框架

1.底层为LCD硬件驱动层ide

  负责对LCD硬件相关寄存器进行初始化;函数

2.上层为帧缓冲区层

  主要用来为应用程序提供操做LCD屏的接口,应用程序要在LCD上显示时,只需把内容写到帧缓冲区中便可。

  在帧缓冲区层,主要把内核空间的一块内存虚拟为一个字符设备,并实现文件接口操做函数(open/read/write)

     而后把帧缓冲注册为一个字符设备,这样在应用层就能够像访问普通字符设备同样来访问帧缓冲,从而实现显示。

 

2、驱动源码分析

以s3c2440 CPU为例:

FrameBuffer设备驱动基于以下两个文件:

  1) linux/include/linux/fb.h

  2) linux/drivers/video/fbmem.c

下面分析这两个文件。

一)fb.h

   帧缓冲主要的数据结构几乎都是在这个中文件定义的。这些结构包括:

1)fb_var_screeninfo

2) fb_fix_screeninfon

3) fb_cmap

4) fb_info

5) struct fb_ops

6) structure map

详见:【Linux开发】全面的framebuffer详解

 

二).fbmem.c

  fbmem.c 处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用,

       也为下一层的特定硬件驱动提供接口;那些底层硬件驱动须要用到这儿的接口来向系统内核注册它们本身.

  fbmem.c 为全部支持FrameBuffer的设备驱动提供了通用的接口,避免重复工做.

  内核中的frambuffer在drivers/video/fbmem.c

1.  进入fbmem.c找到它的入口函数:

 1 fbmem_init(void)  2 {  3     create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);  4 
 5     if (register_chrdev(FB_MAJOR,"fb",&fb_fops))  //建立字符设备  6         printk("unable to get major %d for fb devs\n", FB_MAJOR);  7 
 8     fb_class = class_create(THIS_MODULE, "graphics"); //建立类  9     if (IS_ERR(fb_class)) { 10         printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); 11         fb_class = NULL; 12  } 13     return 0; 14 }
(1)create_proc_read_entry在/proc下也会有fb文件

         

 

  (2)建立字符设备"fb", FB_MAJOR=29,主设备号为29,咱们cat /proc/devices 也能找到这个字符设备:

   与以前的驱动程序同样,可是没有使用建立设备节点,为何?

   由于须要注册了LCD驱动后,才会有设备节点

        

 (3)class_create 注册了一个类 graphics, 具体的设备文件不在此处建立

2.进入结构体fb_fops

  此处注册的是字符设备驱动,结构为默认的file_operations = fb_fops, 从 open = fb_open 开始分析

  分析一下应用层是如何打开驱动、读取驱动数据

  2.1 fb_open函数以下:

 1 static int fb_open(struct inode *inode, struct file *file)  2 {  3        int fbidx = iminor(inode);      //获取设备节点的次设备号
 4        struct fb_info *info;           //定义fb_info结构体,其中包含帧缓冲相关信息
 5        int res = 0;  6  ... ...  7 
 8 if (!(info = registered_fb[fbidx]))   //(1) info= registered_fb[fbidx],获取此设备号的lcd驱动信息
 9  try_to_load(fbidx); 10  ... ... 11 
12        if (info->fbops->fb_open) { ////判断此结构体中是否有fb_open 13               res = info->fbops->fb_open(info,1);  //调用registered_fb[fbidx]->fbops->fb_open
14               if (res) 15                      module_put(info->fbops->owner); 16  } 17 
18        return res; 19 }

   1).registered_fb[fbidx] 这个数组也是fb_info结构体,其中fbidx等于次设备号id,显然这个数组就是保存咱们各个lcd驱动的信息。

   2).根据次设备号在 registered_fb 中寻找对应的 fb_info中的 fb_ops中的open,有就调用,无则返回

#define FB_MAX 32  //次设备号最大为,最多支持32个设备
extern
struct fb_info *registered_fb[FB_MAX];32fb

 

  2.1 fb_read函数以下:

 1 static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)  2 {  3        unsigned long p = *ppos;  4        struct inode *inode = file->f_path.dentry->d_inode;  5        int fbidx = iminor(inode);                       //获取次设备号
 6        struct fb_info *info = registered_fb[fbidx];     //获取次设备号的lcd驱动的信息
 7        u32 *buffer, *dst;  8        u32 __iomem *src;  9        int c, i, cnt = 0, err = 0; 10        unsigned long total_size; 11  ... ... 12        if (info->fbops->fb_read)  //若是自定义了驱动层的read,则调用自定义的,不然调用默认的 13               return info->fbops->fb_read(info, buf, count, ppos); 14      
15        total_size = info->screen_size;     //获取屏幕长度
16     
17  ... ... 18     
19        buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL); //分配缓冲区 
20        if (!buffer) 21               return -ENOMEM;
24 
25        src = (u32 __iomem *) (info->screen_base + p);         //获取显存物理基地址
26        if (info->fbops->fb_sync) 27               info->fbops->fb_sync(info); 28 
29        while (count) { 30               c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;   //获取页地址
31               dst = buffer; 32 
33          /*由于src是32位,一个src等于4个字节,因此页地址c >> 2*/
34               for (i = c >> 2; i--; ) 35                      *dst++ = fb_readl(src++);    //读取显存每一个像素点数据,放到dst地址上
36 
37               if (c & 3) { 38                      u8 *dst8 = (u8 *) dst; 39                      u8 __iomem *src8 = (u8 __iomem *) src; 40                      for (i = c & 3; i--;) 41                             *dst8++ = fb_readb(src8++); 42                      src = (u32 __iomem *) src8; 43  } 44               if (copy_to_user(buf, buffer, c)) {  //上传数据,长度等于页地址大小
45                      err = -EFAULT; 46                      break; 47  } 48               *ppos += c; 49               buf += c; 50               cnt += c; 51               count -= c; 52  } 53  kfree(buffer); 54        return (err) ? err : cnt; 55 }

  从.open和.write函数中能够发现,都依赖于fb_info帧缓冲信息结构体,它从registered_fb[fbidx]数组中获得,这个数组保存咱们各个lcd驱动的信息

 

.3.registered_fb[fbidx]数组在哪里被注册,位于register_framebuffer():

 1 int register_framebuffer(struct fb_info *fb_info)  2 {  3  ... ...  4 for (i = 0 ; i < FB_MAX; i++)    //查找空的数组
 5         if (!registered_fb[i])  6          break;  7 
 8 fb_info->node = i;  9  ... ... 10 
11 /*建立设备节点,名称为fdi,主设备号为29,次设备号为i */
12 fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i); 13  ... ... 14 
15 registered_fb[i] = fb_info; 16  ... ... 17 }  

  register_framebuffer()除了注册fb_info,还建立了设备节点,底层驱动程序即设备驱动,经过调用register_framebuffer来设置硬件

  因此要注册驱动时就调用这个,以下图所示:

 

4.再来看看/drivers/video/s3c2410fb.c 中又是怎么实现驱动的

  4.1先找到入口出口函数:

1 int __devinit s3c2410fb_init(void) 2 { 3      return platform_driver_register(&s3c2410fb_driver); 4 } 5 
6 static void __exit s3c2410fb_cleanup(void) 7 { 8      platform_driver_unregister(&s3c2410fb_driver); 9 }

 入口函数中,注册LCD平台设备驱动的数据结构体到平台总线上。出口函数则卸载。

  

  4.2 来看看平台设备驱动 s3c2410fb_driver 如何定义的

 1 static struct platform_driver s3c2410fb_driver = {  2        .probe           = s3c2410fb_probe,    //检测函数,注册设备
 3        .remove         = s3c2410fb_remove,    //删除设备
 4        .suspend = s3c2410fb_suspend,          //休眠
 5        .resume          = s3c2410fb_resume,   //唤醒
 6        .driver            = {  7               .name     = "s3c2410-lcd",       //drv名字
 8               .owner    = THIS_MODULE,  9  }, 10 };

  当有对应的设备注册到平台总线上时,就会根据设备名(s3c2410-lcd)或ID,找到相应的设备驱动,调用probe函数来探测设备。

  4.2 再进入probe函数看看它的处理

  先看看函数传入的参数  s3c2410fb_probe (struct platform_device *pdev)

 1 //arch\arm\plat-s3c24xx\devs.c
 2 struct platform_device s3c_device_lcd = {  3     .name          = "s3c2410-lcd", //设备名称  4     .id            = -1, //设备ID  5     .num_resources = ARRAY_SIZE(s3c_lcd_resource),  6     .resource      = s3c_lcd_resource,  7     .dev           = {  8        .dma_mask      = &s3c_device_lcd_dmamask,  9        .coherent_dma_mask    = 0xffffffffUL
10  } 11 };

 

 1 static int __init s3c2410fb_probe(struct platform_device *pdev)  2 {  3        struct s3c2410fb_info *info; //定义指向s3c2410fb_info的结构体指针  4        struct fb_info     *fbinfo; //定义指向fb_info的结构体指针  5        struct s3c2410fb_hw *mregs;  6        int ret;  7        int irq;  8        int i;  9  u32 lcdcon1; 10  
11        mach_info = pdev->dev.platform_data;     //获取LCD设备信息(长宽、类型等)
12 
13        if (mach_info == NULL) { 14               dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n"); 15               return -EINVAL; 16  } 17        mregs = &mach_info->regs; 18 
19 
20        irq = platform_get_irq(pdev, 0); 21        if (irq < 0) { 22               dev_err(&pdev->dev, "no irq for device\n"); 23               return -ENOENT; 24  } 25 
27     /* 1. 分配一个s3c2410fb_info结构体给fbinfo*/
28        fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
29        if (!fbinfo) { 30               return -ENOMEM; 31  }
34 
35      /*2.设置fb_info*/
36        info = fbinfo->par; //par成员用来存放帧缓冲的私有数据,此处为LCD控制器 37        info->fb = fbinfo; 38        info->dev = &pdev->dev; 39  ... ... 40 
41     /*3.硬件相关的操做,设置中断,LCD时钟频率,显存地址, 配置引脚... ...*/
42        ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); //设置中断
43        info->clk = clk_get(NULL, "lcd");             //获取时钟
44        clk_enable(info->clk);                        //使能时钟
45        ret = s3c2410fb_map_video_memory(info);       //显存地址 
46        ret = s3c2410fb_init_registers(info);         //设置寄存器,配置引脚
47  ... ... 48    
      /* 4.注册一个fb_info结构体,里面包含帧缓冲的相关信息 */ 49 ret = register_framebuffer(fbinfo); 50 if (ret < 0) { 51 printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); 52 goto free_video_memory; 53 } 54 ... ... 55 return ret; 56 }
完成了帧缓冲变量struct s3c2410fb_info初始化以后,调用fbmem.c的接口,即第49行,经过register_framebuffer注册fb_info结构体后,
会根据次设备号将fb_info存入registered_fb[fbidx]数组中,
这样操做函数就能够经过次设备号找到数组中对应的设备信息,进行操做。
参考一下框图

 

 

总结

参照来设计这个总线下的平台驱动,咱们这里不使用设计,
而是直接写驱动.参考来进行初始化设置drivers\video\s3c2410fb.cfbplatformplatforms3c2410fb_probe

由上可知要写个LCD驱动程序,须要如下4步:

1) 分配一个fb_info结构体: framebuffer_alloc();

2) 设置fb_info

3) 硬件相关的操做(设置中断,LCD时钟频率,显存地址, 配置引脚... ...)

4 注册fb_info: register_framebuffer()

 

下一节写LCD驱动程序

参考:

15.linux-LCD层次分析(详解)

lcd驱动框架

Linux的帧缓冲设备

【Linux开发】全面的framebuffer详解

深刻理解嵌入式Linux设备驱动程序
相关文章
相关标签/搜索