关键词:
引言:
由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
二).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驱动程序
参考:
lcd驱动框架
Linux的帧缓冲设备
【Linux开发】全面的framebuffer详解
深入理解嵌入式Linux设备驱动程序
tty初探—uart驱动框架分析(代码片段)
本文参考了大量牛人的博客,对大神的分享表示由衷的感谢。主要参考: tty驱动分析:http://www.wowotech.net/linux_kenrel/183.html LinuxTTY驱动--Uart_driver底层:http://blog.csdn.net/sharecode/article/details/9196591 查看详情
linuxpci驱动框架分析:(peripheralcomponentinterconnect,外部设备互联)(代码片段)
...Linux指令:lspci显示PCI总线设备信息》目录LinuxPCI驱动框架分析(一)1.概述2.PCILocalBus2.1PCI总线组成2.2PCI总 查看详情
二.mfc框架程序分析--windows编程课程学习笔记(代码片段)
...辅助生成代码的向导工具,可以帮助自动生成基于MFC框架的源代码。创建一个MFC的单文档界面(SDI)应用程序,命名为Windows编程。2.2基于MFC的程序框架剖析查看一下类视图(ClassView)的标签页,可以看... 查看详情
hisi3516dv300芯片基于hwmon驱动框架的温度获取驱动源码分析(代码片段)
1、内核hwmon驱动框架参考博客:《内核hwmon驱动框架详解以及海思芯片温度驱动分析》;2、驱动实现的效果/sys/devices/virtual/hwmon/hwmon0#pwd/sys/class/hwmon/hwmon0/sys/devices/virtual/hwmon/hwmon0#lspowersubsystemtemp1_inputuevent/sys 查看详情
输入子系统框架(代码片段)
目录回顾引入简介框架小结次设备号框架结构图数据管理结构关键函数框架分析input_initinput_open_fileinput_register_handlerinput_register_deviceinput_attach_handlerconnectread程序设计无框架驱动框架架构测试hexdump分析tty读取分析按键连发title:输... 查看详情
nrf52832学习笔记sdk框架分析(代码片段)
nrf52832学习笔记(二)SDK框架分析个人对SDK框架的一些理解,如有错误欢迎斧正。flash分区在不包含DFU的情况下,nrf52832flash划分为:MBR0x00000000-0x00001000为主引导程序(MBR),包括中断向量表和主引导... 查看详情
fridahook框架学习(代码片段)
FridaHook框架学习(2)前言学习过程参考https://bbs.pediy.com/thread-227233.htm。逆向分析安装并运行例子程序,可以看到这个例子是一个验证注册码的程序。使用jadx解析这个APK。通过类名以及AndroidManifest可以猜测以及知道LauncherActivity是... 查看详情
waylandqtwayland框架分析(代码片段)
QtWayland框架分析QtWayland概念介绍QtWayland是Qt官方基于Wayland开发的一款Toolbox,根据其官网介绍TheQtWaylandmoduleconsistsoftwoparts:Waylandplatformplugin:EnablesQtapplicationstoberunasWaylandclients.QtWaylandCompositorA 查看详情
waylandqtwayland框架分析(代码片段)
QtWayland框架分析QtWayland概念介绍QtWayland是Qt官方基于Wayland开发的一款Toolbox,根据其官网介绍TheQtWaylandmoduleconsistsoftwoparts:Waylandplatformplugin:EnablesQtapplicationstoberunasWaylandclients.QtWaylandCompositorA 查看详情
微信小程序学习:基本框架(代码片段)
微信小程序学习(1):基本框架文章目录微信小程序学习(1):基本框架一.了解认识微信小程序二.认识开发者工具和微信小程序基本框架1.HelloWorld!2.架构分析来新建一个带基础模板的页面看看①目录结构②主体文件③页面文件④... 查看详情
开源web框架django知识总结(代码片段)
开源web框架django知识总结(七)一、项目介绍1、项目需求分析需求分析原因:可以整体的了解项目的业务流程和主要的业务需求。项目中,需求驱动开发。即开发人员需要以需求为目标来实现业务逻辑。需求分析... 查看详情
开源web框架django知识总结(代码片段)
开源web框架django知识总结(七)一、项目介绍1、项目需求分析需求分析原因:可以整体的了解项目的业务流程和主要的业务需求。项目中,需求驱动开发。即开发人员需要以需求为目标来实现业务逻辑。需求分析... 查看详情
uvc驱动调用过程与驱动框架的简单分析(代码片段)
UVC驱动整体调用流程:/*打开设备描述符*/1.open:uvc_v4l2_open/*查询设备属性*/2.VIDIOC_QUERYCAPif(video->streaming->type==V4L2_BUF_TYPE_VIDEO_CAPTURE)cap->capabilities=V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_STREAMING;elsecap->capabilities=V4L2_CAP_VIDEO_OUTPUT|V4L2_CAP_STREAM... 查看详情
usbgadget驱动框架分析(代码片段)
以renesasBSP为例:1.composite_driver层注册流程usb_composite_probeusb_gadget_probe_driver/*driver->udc_name没有指定为e659000.usb那么就默认是使用第一个UDC,找里面的第一个还没有被driver匹配的udc*/udc_bind_to_driverusb_gadget_udc_set_speed/*##调用usbhsg_gadge... 查看详情
uvc框架分析(代码片段)
title:UVC框架分析date:2019/4/2319:50:00toc:true---UVC框架分析源码的位置在drivers\media\video\uvc,查看下Makefile,我们可以从入口uvc_driver.c中分析uvcvideo-objs:=uvc_driver.ouvc_queue.ouvc_v4l2.ouvc_video.ouvc_ctrl.ouvc_status.ouv 查看详情
驱动程序实例:mpu6050(iic+cdev)(代码片段)
在我们实际开发中,I2C总线驱动一般芯片原厂会提供,我们开发一般是设计设备驱动。本文结合之前对Linux内核的IIC子系统的分析,以及对字符设备的cdev接口的分析,本文将编写基于IIC总线与cdev接口的MPU6050设备的实例代码并对... 查看详情
应用behave行为驱动框架实现qq邮箱的登录(代码片段)
为了更好地定位元素,首先要仔细分析某个系统登录时,他的登录页面到底有几个iframe元素,当有多个时,切登录框的iframe是独立的话,需要先把frame切换到登录的iframe上,这样才能对其上面的元素进行一系列操作!!如图所示... 查看详情
二.mfc框架程序分析--windows编程课程学习笔记(代码片段)
...辅助生成代码的向导工具,可以帮助自动生成基于MFC框架的源代码。创建一个MFC的单文档界面(SDI)应用程序,命名为Windows编程。2.2基于MFC的程序框架剖析查看一下类视图(ClassView)的标签页,可以看... 查看详情