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
二).fbmem.c
fbmem.c 处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用,
也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己.
fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作.
内核中的frambuffer在drivers/video/fbmem.c
1. 进入fbmem.c找到它的入口函数:
fbmem_init(void)
{
create_proc_read_entry("fb", , NULL, fbmem_read_proc, NULL); 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 ;
}
(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函数如下:
static int fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode); //获取设备节点的次设备号
struct fb_info *info; //定义fb_info结构体,其中包含帧缓冲相关信息
int res = ;
... ... if (!(info = registered_fb[fbidx])) //(1) info= registered_fb[fbidx],获取此设备号的lcd驱动信息
try_to_load(fbidx);
... ... if (info->fbops->fb_open) { ////判断此结构体中是否有fb_open
res = info->fbops->fb_open(info,); //调用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).根据次设备号在 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函数如下:
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 = , err = ;
unsigned long total_size;
... ...
if (info->fbops->fb_read) //如果自定义了驱动层的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 >> ; i--; )
*dst++ = fb_readl(src++); //读取显存每个像素点数据,放到dst地址上 if (c & ) {
u8 *dst8 = (u8 *) dst;
u8 __iomem *src8 = (u8 __iomem *) src;
for (i = c & ; 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.registered_fb[fbidx]数组在哪里被注册,位于register_framebuffer():
int register_framebuffer(struct fb_info *fb_info)
{
... ...
for (i = ; 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,还创建了设备节点,底层驱动程序即设备驱动,通过调用register_framebuffer来设置硬件
所以要注册驱动时就调用这个,如下图所示:
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);
}
入口函数中,注册LCD平台设备驱动的数据结构体到平台总线上。出口函数则卸载。
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,
},
};
当有对应的设备注册到平台总线上时,就会根据设备名(s3c2410-lcd)或ID,找到相应的设备驱动,调用probe函数来探测设备。
4.2 再进入probe函数看看它的处理
先看看函数传入的参数 s3c2410fb_probe (struct platform_device *pdev)
//arch\arm\plat-s3c24xx\devs.c
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd", //设备名称
.id = -, //设备ID
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
static int __init s3c2410fb_probe(struct platform_device *pdev)
{
struct s3c2410fb_info *info; //定义指向s3c2410fb_info的结构体指针
struct fb_info *fbinfo; //定义指向fb_info的结构体指针
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, );
if (irq < ) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
} /* 1. 分配一个s3c2410fb_info结构体给fbinfo*/
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo) {
return -ENOMEM;
} /*2.设置fb_info*/
info = fbinfo->par; //par成员用来存放帧缓冲的私有数据,此处为LCD控制器
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); //设置寄存器,配置引脚
... ...
/* 4.注册一个fb_info结构体,里面包含帧缓冲的相关信息 */
ret = register_framebuffer(fbinfo);
if (ret < ) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
goto free_video_memory;
}
... ...
return ret;
}
完成了帧缓冲变量struct s3c2410fb_info初始化之后,调用fbmem.c的接口,即第49行,通过register_framebuffer注册fb_info结构体后,
会根据次设备号将fb_info存入registered_fb[fbidx]数组中,
这样操作函数就可以通过次设备号找到数组中对应的设备信息,进行操作。
参考一下框图
总结
参照drivers\video\s3c2410fb.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设备驱动程序
10. LCD驱动程序 ——框架分析的更多相关文章
- Linux驱动:LCD驱动框架分析
一直想花时间来整理一下Linux内核LCD驱动,却一直都忙着做其他事情去了,这些天特意抽出时间来整理之前落下的笔记,故事就这样开始了.LCD驱动也是字符设备驱动的一种,框架上相对于字符设备驱动稍微复杂 ...
- 9、LCD驱动程序框架
linux-3.4.2\drivers\video\S3C2410fb.c(内核自带驱动程序) fbmem.c是LCD驱动程序顶层框架文件,是一个通用的文件,在初始化init函数中会注册一个字符设备, ...
- 【Linux高级驱动】LCD驱动框架分析
1.framebuffer接口层(fbmem.c) 功能:给用户提供接口 fbmem_init ),"fb",&fb_fops) /*2.创建一个设备类*/ fb_cl ...
- LCD底层驱动分析
根据分析的框架,自己写一个LCD驱动程序 1分析LCD硬件原理图 Von和Voff接的是一个电源电路,通过LCD_POWER接的是GPG4来控制LCD电源,高电平表示开启LCD电源 VM接的是CPU的 ...
- [国嵌攻略][143][LCD驱动程序分析]
LCD驱动程序分析 LCD驱动程序代码在/drivers/video/s3c2410fb.c文件中,在该驱动的s3c2410fb_init中注册了平台驱动,该驱动的初始化代码在s3c24xxfc_pr ...
- LCD驱动程序架构和分析
一.LCD驱动程序架构 1.裸机驱动代码分析 ①LCD初始化:控制器初始化,端口初始化,指明帧缓冲 ②LCD图形显示:将图形数据写入帧缓冲 void lcd_init() { lcd_port_ini ...
- lcd驱动框架
目录 lcd驱动框架 框图 程序分析 入口 打开open 读read 初始化registered_fb 注册 小结 程序设计 测试 方式一操作fb0 方式二操作tty 方式三操作终端 完整程序 tit ...
- tty驱动程序框架
tty驱动程序框架 一.TTY概念解析 在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备. 1.1串口终端(/dev/ttyS*) 串口终端是使用计算机 ...
- Linux USB驱动框架分析(2)【转】
转自:http://blog.chinaunix.net/uid-23046336-id-3243543.html 看了http://blog.chinaunix.net/uid-11848011 ...
随机推荐
- Android——bootchart
bootchart:android原生自带的开机性能查看机制.通过收集android开机过程中的各种log数据,终于能够图表的形式展现各个进程在开机过程中的性能.(博客不能断-) 撰写不易,转载需注明 ...
- BitSet的使用
有些程序须要处理二进制有序集,标准库提供了bitset 类型,其实,bitset 是一个二进制容器.容器中每个元素都是一位二进制码,或为 0,或为 1. bitset除了能够訪问指定下标的bit位以外 ...
- 124.C++输出小结
#include <iostream> #include <iomanip> using namespace std; void main() { ////调用cout的成员函 ...
- Linux常用音乐播放器
1.Rhythmbox是一个音乐播放和管理应用,GNOME桌面环境自带,它可以播放各种音频格式的音乐管理收藏的音乐.同时还具有音乐回放.音乐导入.刻录音频CD.显示专辑封面.显示歌词.DAAP共享等功 ...
- javaScript 原型与原型链学习笔记
javaScript中,原型是常用到一种方式,它能降低储存占用,写出更高效的代码 原型常用到的则是prototype属性 JavaScript prototype 属性 定义和用法 prototype ...
- 处理某客户p570硬盘故障所思
p570,硬盘故障. 机器有两个vg,rootvg和datavg,rootvg未做镜像,datavg已做镜像.系统errpt和HMC报硬盘有问题,查看错误代码可能是硬盘有坏道(坏块),在尽量保全用户数 ...
- iterator的使用和封个问题
这篇文章的内容还是不错的: http://www.cnblogs.com/zhuyf87/archive/2012/12/08/2808290.html for (vector<int>: ...
- QTP校验数据库中数据后台项目
数据校验功能后台主要包含两个类:QTPCommonServlet.java和QTPCommonDao.java 当中QTPCommonServlet.java为: package com.runqia ...
- 自己写unicode转换ascii码,wchar*到char*
对于ascii码的char事实上就是unicode码wchar的首个字节码, 如wchar[20] = "qqqq"; 在内存中排码事实上是char的'q' '\0'这类.因此我们 ...
- Boolean operations between triangle meshes
Boolean operations between triangle meshes eryar@163.com Abstract. Boolean operations is one of basi ...