zoukankan      html  css  js  c++  java
  • 10. LCD驱动程序 ——框架分析

    引言:

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

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

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

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

     

    一.LCD驱动程序框架

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

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

    1.底层为LCD硬件驱动层

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

    2.上层为帧缓冲区层

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

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

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

     

    二、驱动源码分析

    以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
    ", 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
    ", 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,最多支持32个fb设备
    extern
    struct fb_info *registered_fb[FB_MAX];

      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 //archarmplat-s3c24xxdevs.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
    ");
    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
    ");
    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 ", 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]数组中,
    这样操作函数就可以通过次设备号找到数组中对应的设备信息,进行操作。
    参考一下框图

    总结

    参照driversvideos3c2410fb.c来设计这个fb总线下的platform平台驱动,我们这里不使用platform设计,
    而是直接写驱动.参考s3c2410fb_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设备驱动程序

  • 相关阅读:
    Leetcode 18. 4Sum
    Leetcode 15. 3Sum
    Leetcode 16. 3Sum Closest
    String类型的理解
    求字符串中某个字符出现的次数
    用StringBuilder来实现经典的反转问题
    String/StringBuilder 类 用对象数组实现登录注册功能
    String/StringBuilder 类 统计字符串中字符出现的次数
    String/StringBuilder 类 判断QQ号码
    C++ 面向对象: I/O对象的应用
  • 原文地址:https://www.cnblogs.com/y4247464/p/10159859.html
Copyright © 2011-2022 走看看