lcd 驱动程序框架分析
在嵌入式产品中,lcd的用途可谓很大,很多产品都会用到lcd作为人机交互的接口,在linux内核中当然也有lcd的驱动,内核中都是做好了框架,用平台设备来添加和管理不同的lcd驱动程序,因为每款arm芯片的接品都有不同,每款lcd的驱动方式也有不同,方便后期开发人员增加和修改以达到适用于不同硬件设备对应的lcd驱动程序,我们想要自已编写一个基于内核的lcd驱动程序,就需要先了解内核中lcd驱动的框架,在device那部分用上自已硬件相关的代码,再用一个适合内核lcd驱动的框架的接口,即可完成,在此,我们就有必要先了解到内核关于lcd驱动的框架,按照其框架来编写即可。下面来分析
一、lcd驱动程序框架分析 在 /drivers/video/fbmem.c 这个文件入口函数开始分析
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};
static int __init
fbmem_init(void)
{
proc_create("fb", 0, NULL, &fb_proc_fops);
/* 注册字符设备驱动 主设备号=29 file_operations= fb_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;
}
入口函数里注册了一个字符设备 指定了主设备号和file_operations 结构体,我们只分析框架,不作很深层次的跟进,如果想完完全全了解其深层次的每个函数每条语句,需要大量的时间,我认为学习驱动其间,没有太多意义,我们得先会写,需要提升时或更深层次了解内核时再去分析内核的源码。在input输入子系统那节有说过,分析一个驱动,我们用假设的方法最为方便找出其流程框架,就是假设我们 open read 会如何操作,跟进去就可以了解框架了
A:这里假设我们打开这个设备,将会调用到 驱动的 open 函数 我们进去看一下。这个函数怎么操作的
static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
/* 得到打开设备的次设备号 保存到 fbidx 变量中 */
int fbidx = iminor(inode);
/* 定义一个 fb_info 结构体指针 */
struct fb_info *info;
int res = 0;
/* 初始化这个 fb_info 结构体 get_fb_info 这个函数里有 */
info = get_fb_info(fbidx);
if (!info) {
request_module("fb%d", fbidx);
info = get_fb_info(fbidx);
if (!info)
return -ENODEV;
}
if (IS_ERR(info))
return PTR_ERR(info); mutex_lock(&info->lock);
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
goto out;
}
file->private_data = info;
if (info->fbops->fb_open) {/* 判断这个函数是存在 */
/* 执行info结构体里的fbops里的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);
if (res)
put_fb_info(info);
return res;
}
在 fb_open 函数里,得到一个fo_info结构体 以打开设备的次设备号为下标在某个数组里得到这个结构体,然后执行这个结构体里的fops->open函数
如果没有这个函数 那么open 操作就到此结束了,但是这里有一个未知的东西,fb_info 结构体的内从是从数组里得到,那这个数组由谁构造?谁来填充呢?我们进入get_fb_info 这个函数里看一下,如何得到:
static struct fb_info *get_fb_info(unsigned int idx)
{
struct fb_info *fb_info; if (idx >= FB_MAX)
return ERR_PTR(-ENODEV); mutex_lock(®istration_lock);
fb_info = registered_fb[idx];
if (fb_info)
atomic_inc(&fb_info->count);
mutex_unlock(®istration_lock); return fb_info;
}
从这个函数我们知道上面说的数组是那个 就是 registered_fb【idx】 idx 是传入的参数就是上面提到的次设备号,但我们目的还没达到,我们只找到数组,但数组内容那来我们还不清楚,我们搜索一下这个数组:registered_fb[i] = fb_info; 我们搜索到这么一项,是在 do_register_framebuffer 这个函数里得到,fb_info 结构体是做为这个函数参数传进来的。那我们要再往前找找。在 register_framebuffer 里调用 do_register_framebuffer再往上找 就以搜索到一大堆lcd的驱动程序调用了 register_framebuffer 这个函数,猜测一下,是否是在lcd硬件驱动调用那边里构造并设置了这个fb_info结构体呢?我们找一个驱动程序看一下就可以知道了。以s3c2410fb.c为例看一下,当然也可以用其它的,我的板子是2440所以用这个,好理解一些。看到在 s3c2410fb.c里s3c24xxfb_probe这个函数里调用了。从名字上看 基本上可以看出,这是一个平台设备枚举函数,再往上就跟到了平台设备的东西,不是我们分析的内容,这里先不管它,下面我们看看 s3c24xxfb_probe 这里面做了什么事。
static int __devinit s3c24xxfb_probe(struct platform_device *pdev,
enum s3c_drv_type drv_type)
{
struct s3c2410fb_info *info;
struct s3c2410fb_display *display;
struct fb_info *fbinfo;
struct s3c2410fb_mach_info *mach_info;
struct resource *res;
int ret;
int irq;
int i;
int size;
u32 lcdcon1; mach_info = pdev->dev.platform_data;
if (mach_info == NULL) {
dev_err(&pdev->dev,
"no platform data for lcd, cannot attach\n");
return -EINVAL;
} if (mach_info->default_display >= mach_info->num_displays) {
dev_err(&pdev->dev, "default is %d but only %d displays\n",
mach_info->default_display, mach_info->num_displays);
return -EINVAL;
} display = mach_info->displays + mach_info->default_display;
/* 从平台设备获取资源 */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
}
/* 分配一个fbinfo结构体 */
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo)
return -ENOMEM; platform_set_drvdata(pdev, fbinfo); info = fbinfo->par;
info->dev = &pdev->dev;
info->drv_type = drv_type;
/* 从平台设备获取资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory registers\n");
ret = -ENXIO;
goto dealloc_fb;
}
/* 从平台设备获取资源 */
size = resource_size(res);
info->mem = request_mem_region(res->start, size, pdev->name);
if (info->mem == NULL) {
dev_err(&pdev->dev, "failed to get memory region\n");
ret = -ENOENT;
goto dealloc_fb;
}
/* GPIO映射 */
info->io = ioremap(res->start, size);
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
} if (drv_type == DRV_S3C2412)
info->irq_base = info->io + S3C2412_LCDINTBASE;
else
info->irq_base = info->io + S3C2410_LCDINTBASE; dprintk("devinit\n"); strcpy(fbinfo->fix.id, driver_name); /* Stop the video */
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
/* 下面都是一些对fbinfo结构体的设置 */
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
fbinfo->fix.accel = FB_ACCEL_NONE; fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED; fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal; for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
/* 注册中断 */
ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_regs;
}
/* 获得时钟 */
info->clk = clk_get(NULL, "lcd");
if (IS_ERR(info->clk)) {
printk(KERN_ERR "failed to get lcd clock source\n");
ret = PTR_ERR(info->clk);
goto release_irq;
}
/* 使能时钟 */
clk_enable(info->clk);
dprintk("got and enabled clock\n"); usleep_range(1000, 1000); info->clk_rate = clk_get_rate(info->clk); /* find maximum required memory size for display */
for (i = 0; i < mach_info->num_displays; i++) {
unsigned long smem_len = mach_info->displays[i].xres; smem_len *= mach_info->displays[i].yres;
smem_len *= mach_info->displays[i].bpp;
smem_len >>= 3;
if (fbinfo->fix.smem_len < smem_len)
fbinfo->fix.smem_len = smem_len;
} /* Initialize video memory */
ret = s3c2410fb_map_video_memory(fbinfo);
if (ret) {
printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
} dprintk("got video memory\n"); fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
/* 初始化 GPIO 寄存器 */
s3c2410fb_init_registers(fbinfo); s3c2410fb_check_var(&fbinfo->var, fbinfo); ret = s3c2410fb_cpufreq_register(info);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register cpufreq\n");
goto free_video_memory;
}
/* 注册fbinfo */
ret = register_framebuffer(fbinfo);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n",
ret);
goto free_cpufreq;
} /* create device files */
ret = device_create_file(&pdev->dev, &dev_attr_debug);
if (ret)
printk(KERN_ERR "failed to add debug attribute\n"); printk(KERN_INFO "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id); return 0; free_cpufreq:
s3c2410fb_cpufreq_deregister(info);
free_video_memory:
s3c2410fb_unmap_video_memory(fbinfo);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
release_irq:
free_irq(irq, info);
release_regs:
iounmap(info->io);
release_mem:
release_mem_region(res->start, size);
dealloc_fb:
platform_set_drvdata(pdev, NULL);
framebuffer_release(fbinfo);
return ret;
}
这里完成了,fb_info结构体的构造和填充,然后调用 register_framebuffer 把fb_info结构体做为参数传进去,再下面就是上面分析的流程了。上面我们说是打开设备 调用open函数,在fo_info 结构体里有一个fbinfo->fbops = &s3c2410fb_ops; 所以会调用到 s3c2410fb_ops->open函数 但这里没有open函数,我们回到上面看,有个判断,如果有就调用,没有话就算了。这里有个疑问,没有就算了,那调用open最后会干啥,啥也没干,我理解为,从代码上面看,注册后,即这个lcd设备就存在了,open有没有都可以,因为我们操作打开没有意义,基本上我们都是对lcd读写操作。下面我们来分析一下怎么读
B:假设app调用read函数,那流程又是怎么样的呢:会调用 fb_read 进去看看
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
/* 得到fb_info结构体 */
struct fb_info *info = file_fb_info(file);
u8 *buffer, *dst;
u8 __iomem *src;
int c, cnt = 0, err = 0;
unsigned long total_size; if (!info || ! info->screen_base)
return -ENODEV; if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
/* 判断如果有读函数就调用 info->fbops->fb_read 这个函数读 如果没有 往下执行 */
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
/* 得到lcd显示屏大小 */
total_size = info->screen_size; if (total_size == 0)
total_size = info->fix.smem_len; if (p >= total_size)
return 0; if (count >= total_size)
count = total_size; if (count + p > total_size)
count = total_size - p;
/* 分配一个buffer 应该是显存 */
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM; src = (u8 __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;
fb_memcpy_fromfb(dst, src, c);
dst += c;
src += c;
/* 把 显存的内容发送给app */
if (copy_to_user(buf, buffer, c)) {
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
} kfree(buffer); return (err) ? err : cnt;
}
读操作和写操作不同之处就是,如果有读函数就调用读函数,如果没有就从显存里读出数据发送给app。分析到这,应该算是基本了解了框 架了,
fbmem 是一个通用的接口,驱动程序通过平台接口调用register_framebuffer注册,在此前要先分配和设置fb_info结构体,这个结构体里就是硬件操作的数据参数。我们先不管平台设备,那么我们要自已写就可以总结出得到下面的步骤:
1:分配一个fb_info结构体
2:设置
3:注册
4:硬件相关的操作 这里包括很多东西,画点,画圆 等和硬件直接相关的操作。
以上就是框架,按照这个步骤就可以写出一个最简单的lcd驱动程序,至于一些显示文件,图片,这些复杂的操作可以后续添加。一步一步完成达到自已想要的结果。
我分析这些东西是参考别人的视频对着代码来看的,有很多看不懂的代码,没有分析,但对于学习驱动来说,学会分析一个驱动的方法,和去深扒一个函数的实现过程要有意义,并不是说了解实现不好,能看懂所有代码那是求之不得,但是linux内核没有我们想像中那么好分析,一个套一个,各种结构体链表,关系很复杂,我是看不明白,可能我能力不好吧。我认为首先学会写一些简单的东西,再慢慢深入,如果一开始就深扒每一个函数如何实现,会碰到很多看不明白的地方,会打击自已的信心,耐心。我深有体会。所以,随着学的东西慢慢的增加,理解慢慢的加深,会有明白的一天。社会的形势造成了这样的现像,你就是理论再强,不能做出实际有用的东西,那也是白搭,没人会用你。关于我,有兴趣可以看看,我前面写的自述。就知道我说这些话的道理在那了。
我写的东西,如果对一些人有帮助,那最好不过了,我的目的就是记录下我的学习过程,以后忘记的时候回来看一下,同时勉劢一下自已。
lcd 驱动程序框架分析的更多相关文章
- 10. LCD驱动程序 ——框架分析
引言: 由LCD的硬件原理及操作(可参看韦哥博客:第017课 LCD原理详解及裸机程序分析) 我们知道只要LCD控制器的相关寄存器正确配置好,就可以在LCD面板上显示framebuffer中的内容. ...
- 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 ...
- 2.1 摄像头V4L2驱动框架分析
学习目标:学习V4L2(V4L2:vidio for linux version 2)摄像头驱动框架,分析vivi.c(虚拟视频硬件相关)驱动源码程序,总结V4L2硬件相关的驱动的步骤: 一.V4L ...
- 18.tty驱动程序框架
tty驱动程序框架 一.TTY概念解析 在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备. 1.1串口终端(/dev/ttyS*) 串口终端是使用计算机 ...
随机推荐
- C# 开源NuGet插件
ExcelDataReader 开源免费,Excel读取插件 GitHub - ExcelDataReader/ExcelDataReader: Lightweight and fast libra ...
- ORACLE 配置ST_GEOMETRY以支持SQL方式操作SDE数据库
这里假设已经在ORACLE里边创建了SDE数据库,在此基础上进行配置: 1.以sde用户登录到oracle,运行一下语句: SELECT * FROM USER_LIBRARIES; CREATE O ...
- 【C++复习】第九章 模板与群体数据(1)
1.例:求绝对值函数的模板 函数重载方便了函数的使用者,开发者还是要写两个函数 模板是用来生成函数的东西 编译器通过推导生成函数: 2.函数模板定义语法 从例题入手,别上来就扣语法 3.例9-1 函数 ...
- Java 04-基础 数据类型转换 自动类型转换+强制类型转换
1.数据类型自动转换 规则1:如果一个操作数为double型,则整个表达式提示至double型 规则2:满足自动类型转换条件, 两种类型要兼容,数值类型(整数和浮点)相互兼容 目标类型取值大于 ...
- input点击焦点后阴影
input[type=text]:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow:inset 0 1p ...
- SHR常用f7[更新ing]
<field id="unit" name="unit" label="单位" dataType="F7" uip ...
- vue项目中如何使用markdown编辑器插件
1.安装mavon-editor $ npm install mavon-editor --save 需要使用Markdown编辑器的页面js中: import { mavonEditor } fro ...
- 如何在超星下载非资料页面的ppt
首先打开迅雷(没有就复制到网页下载) 点击f12 点击网络,筛查出输入flag,在响应模块中找到ppt,复制网址并下载
- redis数据类型常用方法
一.String set:添加String类型数据 get:获取String类型数据 del:删除数据 append:在原基础上追加数据,假如原来k1值是v1,执行append k1 ddd,那么值就 ...
- MVC+EF API 跨域
MVC+EF API --2 一. MVC+EF 不管是MvcHAIS Ef 都有文件夹Controller 二.Link查询 多表联查 匿名类型 三.Postman使用 四.mvc访问使用API 跨 ...