《驱动学习 - LCD驱动》
https://www.cnblogs.com/sky-heaven/p/8670477.html
https://www.cnblogs.com/lifexy/p/7603327.html
https://www.cnblogs.com/silencehuan/p/11202496.html
https://www.cnblogs.com/lubiao/p/4850258.html
1.LCD驱动概念
LCD是Liquid Crystal Display的简称,也就是经常所说的液晶显示器。LCD能够支持彩色图像的显示和视频的播放,是一种非常重要的输出设备。如果我们的系统要用GUI(图形界面接口),比如minigui,MicroWindows。这时LCD设备驱动程序就应该编写成frambuffer接口,而不是编写成仅仅操作底层的LCD控制器接口。
framebuffer是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行操作。framebuffer又叫帧缓冲,是Linux为操作显示设备提供的一个用户接口。用户应用程序可以通过framebuffer透明地访问不同类型的显示设备。从这个方面来说,framebuffer是硬件设备显示缓冲区的抽象。Linux抽象出framebuffer这个帧缓冲区可以供用户应用程序直接读写,通过更改framebuffer中的内容,就可以立刻显示在LCD显示屏上。
framebuffer是一个标准的字符设备,主设备号是29,次设备号根据缓冲区的数目而定。framebuffer对应/dev/fb%d设备文件。根据显卡的多少,设备文件可能是/dev/fb0、/dev/fb1等。缓冲区设备也是一种普通的内存设备,可以直接对其进行读写。对用户程序而言,它和/dev下面的其他设备没有什么区别,用户可以把frameBuffer看成一块内存,既可以写,又可以读。显示器将根据内存数据显示对应的图像界面。这一切都由LCD控制器和响应的驱动程序来完成。
2、fb与应用程序的交互
对于用户程序而言,它和其他的设备并没有什么区别,用户可以把fb看成是一块内存,既可以向内存中写数据,也可以读数据。fb的显示缓冲区位于内核空间,应用程序可以把此空间映射到自己的用户空间,在进行操作。
在应用程序中,操作/dev/fbn的一般步骤如下:
(1)打开/dev/fbn设备文件。
(2)用ioctl()操作取得当前显示屏幕的参数,如屏幕分辨率、每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
(3)用mmap()函数,将屏幕缓冲区映射到用户空间。
(4)映射后就可以直接读/写屏幕缓冲区,进行绘图和图片显示了。
3、LCD驱动框架
在linux中,fb设备驱动的源码主要在Fb.h (\include\linux)和Fbmem.c (\drivers\video)两个文件中,它们是fb设备驱动的中间层,为上层提供系统调用,为底层驱动提供接口。
其中结构体成员因为太多就不列出来。具体可查看源码。
4.LCD驱动核心层(fbmem.c)源码分析
一般分析一个驱动,先从他得入口函数开始分析fbmem_init()
static int __init fbmem_init(void) { proc_create("fb", 0, NULL, &fb_proc_fops); if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) 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; }
#define FB_MAJOR 29 /* /dev/fb* framebuffers */
register_chrdev(FB_MAJOR,"fb",&fb_fops)向内核注册了一个主设备号为29,名字叫“fb”,fb_fops的file_operations的结构体变量。
通过cat /proc/devices 也能找到这个字符设备:
和我们之前的驱动程序一样,但是没有使用创建设备节点,为什么?
因为需要注册了LCD驱动后,才会有设备节点,所以这里的代码没有 ,后面会分析哪里有。
应用程序要使用某个设备,就要先open这个设备。所以接下来分析一下fb_fops中的fb_open
extern struct fb_info *registered_fb[FB_MAX];
static int fb_open(struct inode *inode, struct file *file) { int fbidx = iminor(inode); struct fb_info *info; int res = 0; if (fbidx >= FB_MAX) return -ENODEV; info = registered_fb[fbidx]; if (!info) request_module("fb%d", fbidx); info = registered_fb[fbidx]; if (!info) return -ENODEV; mutex_lock(&info->lock); if (!try_module_get(info->fbops->owner)) { res = -ENODEV; goto out; } file->private_data = info; if (info->fbops->fb_open) { res = info->fbops->fb_open(info,1); if (res) module_put(info->fbops->owner); } #ifdef CONFIG_FB_DEFERRED_IO if (info->fbdefio) fb_deferred_io_open(info, inode, file); #endif out: mutex_unlock(&info->lock); return res; }
从上面的三行红色代码大概可以看出,把次设备号作为下标,在registered_fb数组中找到保存着的LCD驱动信息,再赋值给info,然后去调用具体LCD的open函数。
所以接下来就要了解一下,怎么把LCD驱动信息放到registered_fb数组中(查看在哪里对registered_fb进行赋值)。
通过查找,只有一条registered_fb[i] = fb_info(register_framebuffer()函数中)
所以驱动设备是通过register_framebuffer()函数进行一个注册的,而且还在这里创建了设备节点device_create()。
现在就可以通过查看哪些函数调用了register_framebuffer()函数,然后分析一下LCD设备是怎么向驱动核心层注册的。
可以看到有很多设备都已经注册了。就拿我们熟悉的s3c24xx来分析一下吧。
static int __init s3c24xxfb_probe(struct platform_device *pdev, enum s3c_drv_type drv_type) { /*略*/ ret = register_framebuffer(fbinfo); /*略*/ }
probe函数,很熟悉。就是驱动设备总线模型了。和我们上节分析的platform机制一样,当与设备匹配成功,就进入probe函数,初始化驱动设备。
5.总结
1.当我们加载某个lcd硬件设备模块,就会通过platform机制一样,当与设备匹配成功,就进入probe函数,初始化驱动设备。
2.probe函数中调用了registered_fb[i] = fb_info和device_create(),这两个分别注册了LCD硬件信息和创建了设备节点。
3.应用程序通过open函数打开某个LCD设备的时候,会调用fb_open。
4.fb_open中通过获取次设备号,在registered_fb数组中找到相对应LCD设备信息。最后调用LCD设备信息中open函数。