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\n", FB_MAJOR);

         fb_class = class_create(THIS_MODULE, "graphics");       //创建类

         if (IS_ERR(fb_class)) {
                   printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", 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\n");
              return -EINVAL;
       }
       mregs = &mach_info->regs;


       irq = platform_get_irq(pdev, 0);
       if (irq < 0) {
              dev_err(&pdev->dev, "no irq for device\n");
              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\n", 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)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏lgp20151222

rabbit的简单搭建,java使用rabbitmq queue的简单例子和一些坑

由于本人的码云太多太乱了,于是决定一个一个的整合到一个springboot项目里面。

1931
来自专栏坚毅的PHP

jersey处理支付宝异步回调通知的问题:java.lang.IllegalArgumentException: Error parsing media type 'application/x-www

tcpflow以流为单位分析请求内容,非常适合服务器端接口类服务查问题 这次遇到的问题跟支付宝支付后的回调post结果有关 淘宝的代码例子: publi...

5195
来自专栏Java3y

WebService就是这么简单

WebService介绍 首先我们来谈一下为什么需要学习webService这样的一个技术吧…. 问题一 如果我们的网站需要提供一个天气预报这样一个需求的话,那...

2.6K14
来自专栏一枝花算不算浪漫

[数据库连接池] Java数据库连接池--DBCP浅析.

35814
来自专栏岑玉海

Hadoop源码系列(一)FairScheduler申请和分配container的过程

1、如何申请资源 1.1 如何启动AM并申请资源 1.1.1 如何启动AM val yarnClient = YarnClient.createYarnClie...

4093
来自专栏实战docker

修改,编译,GDB调试openjdk8源码(docker环境下)

在上一章《在docker上编译openjdk8》里,我们在docker容器内成功编译了openjdk8的源码,有没有读者朋友产生过这个念头:“能不能修改open...

4389
来自专栏向治洪

Android 使用android-support-multidex解决Dex超出方法数的限制问题

随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的...

3098
来自专栏Linux驱动

第1阶段——uboot分析之硬件初始化start.S(4)

分析uboot第一个执行函数_start(cpu/arm920t/start.S)  打开cpu/arm920t/start.S 1 .globl _start...

1978
来自专栏散尽浮华

Linux系统下的用户密码设定梳理

随着linux使用的普遍,对于linux用户以及系统的安全要求越来越高,而用户密码复杂程度是系统安全性高低的首要体现。因此如何对linux下用户的密码进行规则限...

2369
来自专栏java达人

ThreadLocal与Spring 事务管理

编写线程安全代码的关键是管理程序中的共享可变状态,除了通过synchronized加锁机制防止多个线程同时访问同一段数据外,还有一种方法就是通过ThreadLo...

20910

扫码关注云+社区