zoukankan      html  css  js  c++  java
  • 15.linux-LCD层次分析(详解)

    如果我们的系统要用GUI(图形界面接口),这时LCD设备驱动程序就应该编写成frambuffer接口,而不是像之前那样只编写操作底层的LCD控制器接口。

    什么是frambuffer设备?

       frambuffer设备层是对图像设备的一种抽象,它代表了视频硬件的帧缓存,使得应用程序通过定义好的接口就可以访问硬件。所以应用程序不需要考虑底层的(寄存器级)的操作。应用程序对设备文件的访问一般在/dev目录,如 /dev/fb*。


    内核中的frambuffer在drivers/video/fbmem.c(fb: frame buffer)

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

    static  int __init  fbmem_init(void)
    {
    
             create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
    
             if (register_chrdev(FB_MAJOR,"fb",&fb_fops))           //(1)创建字符设备
                      printk("unable to get major %d for fb devs
    ", FB_MAJOR);
    
             fb_class = class_create(THIS_MODULE, "graphics");       //创建类
    
             if (IS_ERR(fb_class)) {
                       printk(KERN_WARNING "Unable to create fb class; errno = %ld
    ", PTR_ERR(fb_class));
                      fb_class = NULL;
            }
             return 0;
    }

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

     

    和我们之前的驱动程序一样,但是没有使用创建设备节点,为什么?

    因为需要注册了LCD驱动后,才会有设备节点,所以这里的代码没有 ,后面会分析哪里有。

    2.我们来看看注册的file_operations结构体fb_fops的.open函数和.read函数,应用层是如何打开驱动、读取驱动数据

    2.1 fb_open函数如下:

    static int fb_open(struct inode *inode, struct file *file)
    {
           int fbidx = iminor(inode);      //获取设备节点的次设备号
           struct fb_info *info;                //定义fb_info结构体
           int res = 0;
           ... ...
    
    if (!(info = registered_fb[fbidx]))   //(1) info= registered_fb[fbidx],获取此设备号的lcd驱动信息
                  try_to_load(fbidx);
           ... ... 
    
           if (info->fbops->fb_open) {          
                  res = info->fbops->fb_open(info,1);  //调用registered_fb[fbidx]->fbops->fb_open
                  if (res)
                         module_put(info->fbops->owner);
           }
    
           return res;
    }

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

    2.2 fb_read函数如下:

    static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
    {
           unsigned long p = *ppos;
           struct inode *inode = file->f_path.dentry->d_inode; 
           int fbidx = iminor(inode);                       //获取次设备号
           struct fb_info *info = registered_fb[fbidx];     //获取次设备号的lcd驱动的信息
           u32 *buffer, *dst;
           u32 __iomem *src;
           int c, i, cnt = 0, err = 0;
           unsigned long total_size;
           ... ...
           if (info->fbops->fb_read)
                  return info->fbops->fb_read(info, buf, count, ppos);
         
           total_size = info->screen_size;     //获取屏幕长度
        
    ... ... buffer
    = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL); //分配缓冲区 if (!buffer) return -ENOMEM; src = (u32 __iomem *) (info->screen_base + p); //获取显存物理基地址 if (info->fbops->fb_sync) info->fbops->fb_sync(info); while (count) { c = (count > PAGE_SIZE) ? PAGE_SIZE : count; //获取页地址 dst = buffer; /*因为src是32位,一个src等于4个字节,所以页地址c >> 2*/ for (i = c >> 2; i--; ) *dst++ = fb_readl(src++); //读取显存每个像素点数据,放到dst地址上 if (c & 3) { u8 *dst8 = (u8 *) dst; u8 __iomem *src8 = (u8 __iomem *) src; for (i = c & 3; i--;) *dst8++ = fb_readb(src8++); src = (u32 __iomem *) src8; } if (copy_to_user(buf, buffer, c)) { //上传数据,长度等于页地址大小 err = -EFAULT; break; } *ppos += c; buf += c; cnt += c; count -= c; } kfree(buffer); return (err) ? err : cnt; }

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

    3.我们来找找这个数组在哪里被注册,位于register_framebuffer():

    int register_framebuffer(struct fb_info *fb_info)
    {
     ... ...
    for (i = 0 ; i < FB_MAX; i++)    //查找空的数组
            if (!registered_fb[i])
             break;
    
    fb_info->node = i;           
     ... ...
    
    /*创建设备节点,名称为fdi,主设备号为29,次设备号为i   */
    fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i);
     ... ...
    
    registered_fb[i] = fb_info;
     ... ...
    }

    得出这个register_framebuffer()除了注册fb_info,还创建了设备节点

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

     

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

    4.1先找到入口出口函数:

    int __devinit s3c2410fb_init(void)
    {
         return platform_driver_register(&s3c2410fb_driver);
    }
    
    static void __exit s3c2410fb_cleanup(void)
    {
         platform_driver_unregister(&s3c2410fb_driver);
    }

    发现是注册、注销设备平台drv

    4.2 来看看s3c2410fb_driver 如何定义的

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

    和我们上节分析的platform机制一样,当与设备匹配成功,就进入probe函数,初始化驱动设备

    4.3 来看看.probe函数,如何实现驱动的

    static int __init s3c2410fb_probe(struct platform_device *pdev)
    {
           struct s3c2410fb_info *info;
           struct fb_info     *fbinfo;
           struct s3c2410fb_hw *mregs;
           int ret;
           int irq;
           int i;
           u32 lcdcon1;
     
           mach_info = pdev->dev.platform_data;     //获取LCD设备信息(长宽、类型等)
    
           if (mach_info == NULL) {
                  dev_err(&pdev->dev,"no platform data for lcd, cannot attach
    ");
                  return -EINVAL;
           }
           mregs = &mach_info->regs;
    
    
           irq = platform_get_irq(pdev, 0);
           if (irq < 0) {
                  dev_err(&pdev->dev, "no irq for device
    ");
                  return -ENOENT;
           }
    
     
    
           fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); //1.分配一个fb_info结构体
           if (!fbinfo) {
                  return -ENOMEM;
           }
    
     
    
         /*2.设置fb_info*/
           info = fbinfo->par;
           info->fb = fbinfo;
           info->dev = &pdev->dev;
           ... ...
    
        /*3.硬件相关的操作,设置中断,LCD时钟频率,显存地址, 配置引脚... ...*/
           ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); //设置中断
           info->clk = clk_get(NULL, "lcd");                    //获取时钟
           clk_enable(info->clk);                                  //使能时钟
           ret = s3c2410fb_map_video_memory(info);               //显存地址  
           ret = s3c2410fb_init_registers(info);                //设置寄存器,配置引脚
           ... ...
    ret
    = register_framebuffer(fbinfo); //4.注册一个fb_info结构体 if (ret < 0) { printk(KERN_ERR "Failed to register framebuffer device: %d ", ret); goto free_video_memory; } ... ... return ret; }

    4.4 显然要写个LCD驱动程序,需要以下4步:

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

    2) 设置fb_info

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

    4 注册fb_info: register_framebuffer()

     

    下节就开始如何来写LCD驱动

     (LCD驱动详解入口地址: http://www.cnblogs.com/lifexy/p/7604011.html)

  • 相关阅读:
    四、持久层框架(Hibernate)
    三、持久层框架(Hibernate)
    二、持久层框架(Hibernate)
    一、持久层框架(Hibernate)
    一、Redis的数据类型
    数组元素必须具有相同的数据类型?--不一定哦
    equals()源代码及释义
    古罗马皇帝凯撒加密军事情报
    String中重要方法与字段
    《大道至简》第四章
  • 原文地址:https://www.cnblogs.com/lifexy/p/7603327.html
Copyright © 2011-2022 走看看