• 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设备驱动程序

  • 相关阅读:
    数据类型装换
    变量及数据类型
    27 网络通信协议 udp tcp
    26 socket简单操作
    26 socket简单操作
    14 内置函数 递归 二分法查找
    15 装饰器 开闭原则 代参装饰器 多个装饰器同一函数应用
    12 生成器和生成器函数以及各种推导式
    13 内置函数 匿名函数 eval,exec,compile
    10 函数进阶 动态传参 作用域和名称空间 函数的嵌套 全局变量
  • 原文地址:https://www.cnblogs.com/y4247464/p/10159859.html
Copyright © 2020-2023  润新知