第二期驱动篇——1.1 LCD驱动编写——Linux内核中LCD驱动框架分析
Linux内核中LCD驱动框架分析
/*
*硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
*软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
*参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》
目录
一、前言
在016 LCD(2)-编写程序这篇博客中,编写了裸机LCD驱动程序,在这一节中,将在Linux系统下编写LCD驱动程序。
二、框架
1、回顾裸板LCD驱动
-
第一层:测试菜单层:
lcd_test.c
-
第二层:根据目的划分三个C文件:
framebuffer.c
、geometry.c
、font.c
-
第三层:LCD参数设置:
lcd.c
,考虑到拓展性,如果需要用到不同尺寸的lcd时,需要修改太多东西,所以在这一层下分了一个小层,用来放各种尺寸lcd的参数设置文件如lcd_4_3.c
,通过在lcd.c
调用对应的文件。 -
第四层:LCD控制器设置:
lcd_controller.c
,考虑到拓展性,如果用到不同芯片驱动的lcd时,需要修改太多东西,所以在这一层下分了一个小层,用来放各种芯片的lcd控制器设置文件如s3c2440_lcd_controller.c
,通过在lcd_controller.c
调用对应的文件。
2、Linux下LCD驱动框架
由于在Linux系统下,系统中的/drivers/video/fbmen.c
文件已经做好了相当于裸板驱动框架的第二层的工作,我们只需要设置相关的硬件操作。下面先对fbmen.c
中的函数进行分析,了解其大致的框架。
fbmen.c文件解析:
2.1 fbmem_init函数解析
- 函数原型:
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))
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;
}
- 实现功能:
①、register_chrdev(FB_MAJOR,"fb",&fb_fops)
:对构建的file_operation
结构体fb_fops
进行注册。
通过继续代码追踪可以发现结构体fb_fops
的原型中的read、write、open
等函数可以发现:没有直接操作硬件设备的寄存器或IO口,而是根据struct fb_info *info
这个结构体变量来进行函数的操作。
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
/*..............*/
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
if (!info || ! info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
/*..............*/
}
②、class_create(THIS_MODULE, "graphics");
:创建一个类。
- 提问:根据之前的经验,在创建类之后会创建类的设备节点,为什么这里没有呢?
- 回答:因为
fbmem.c
是通用的文件,相当于在裸板LCD驱动中lcd.c、lcdcontroller.c
,根据下层的lcd_4.3.c、s3c2440_controlller.c
来注册设备节点,给上层的应用来使用。所以fbmem.c
还需要根据一个文件的参数来进行注册设备节点、调用下层的具体函数等。
2.2 fb_open函数解析
- 函数原型:
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;
#ifdef CONFIG_KMOD
if (!(info = registered_fb[fbidx]))
try_to_load(fbidx);
#endif /* CONFIG_KMOD */
if (!(info = registered_fb[fbidx]))
return -ENODEV;
if (!try_module_get(info->fbops->owner))
return -ENODEV;
file->private_data = info;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
- 功能:
①、int fbidx = iminor(inode);
:找到该设备的次设备号。
②、struct fb_info *info = registered_fb[fbidx];
:根据次设备号作为索引,在数组中找到对应项赋值给info
。
继续追踪registered_fb
的原型:发现硬件设置中的参数保存在fb_info
结构体中
struct fb_info {
int node;
int flags;
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue */
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode */
/*.........*/
};
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
/*...........................*/
};
2.3 fbmem.c总结
- fbmem.c是一个通用的文件
-
供上层应用来调用:根据上层
open
等函数来打开对应的设备节点 - 在当层中:通过找到该设备的次设备号,通过设备号在register_fb数组中找到该设备的硬件设置函数来进行硬件初始化、调用下层具体函数。
下面我们在看看Linux系统中已经写好的硬件设置文件s3c2410fb.c
,进一步分析。
s3c2410fb.c文件解析:
2.4 s3c2410fb_init函数解析
- 函数原型:
int __devinit s3c2410fb_init(void)
{
return platform_driver_register(&s3c2410fb_driver);
}
- 功能:
①、调用platform_driver_register()
平台设备注册函数。这个时候就可以指导内核在管理LCD驱动时,采用的是platform总线机制,注册平台驱动程序。
2.5 struct platform_driver s3c2410fb_driver结构体解析
- 函数原型:
static struct platform_driver s3c2410fb_driver = {
.probe = s3c2410fb_probe,
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
};
- 功能:
①、存储各类操作函数的地址
②、特别之处:与我们之前编写的结构体不同,这里没有open、read
函数。
因为此时采用的是platform总线机制,优点:在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中用使用这些资源时,通过platform device提供的标准接口进行申请并使用。
对于LCD驱动,这个标准接口就是刚才所介绍的fbmem.c
文件。
2.6 s3c2410fb_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;
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);
if (!fbinfo) {
return -ENOMEM;
}
info = fbinfo->par;
info->fb = fbinfo;
info->dev = &pdev->dev;
platform_set_drvdata(pdev, fbinfo);
dprintk("devinit\n");
strcpy(fbinfo->fix.id, driver_name);
memcpy(&info->regs, &mach_info->regs, sizeof(info->regs));
/* Stop the video and unset ENVID if set */
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
lcdcon1 = readl(S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
info->mach_info = pdev->dev.platform_data;
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.height = mach_info->height;
fbinfo->var.width = mach_info->width;
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;
fbinfo->var.xres = mach_info->xres.defval;
fbinfo->var.xres_virtual = mach_info->xres.defval;
fbinfo->var.yres = mach_info->yres.defval;
fbinfo->var.yres_virtual = mach_info->yres.defval;
fbinfo->var.bits_per_pixel = mach_info->bpp.defval;
fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
fbinfo->var.transp.length = 0;
fbinfo->fix.smem_len = mach_info->xres.max *
mach_info->yres.max *
mach_info->bpp.max / 8;
/*.......*/
ret = s3c2410fb_init_registers(info);
ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);
ret = register_framebuffer(fbinfo);
/*.......*/
}
- 功能:
①、fbinfo = framebuffer_alloc()
:根据s3c2410fb_info
结构体的大小分配内存空间给fbinfo
。
②、此时的具体硬件设置存放在fbinfo
结构体指针所指向的内存空间。
③、ret = register_framebuffer(fbinfo);
:把具体的硬件设置注册到数组中去。
2.7 register_framebuffer函数解析
register_framebuffer()
函数位于fbmen.c
文件中
- 函数原型:
int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
if (num_registered_fb == FB_MAX)
return -ENXIO;
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), "fb%d", i);
/*...............*/
}
- 功能:
①、在数组中寻找空的项,把该项的索引号保存到fbinfo
中。
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
②、注册类的设备节点,这个时候有了具体的设备,才会注册设备节点。
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), "fb%d", i);
三、总结
对于platform总线机制下的的LCD驱动的大致框架如下图:
可以总结出来,platform总线机制下的的LCD驱动的编写:不像编写裸板LCD时是抽出那么多函数来形成一个拓展性很好的框架(因为Linux内核已经帮我们做了),我们只需要编写我们具体驱动文件。
驱动文件编写:{
-
设置相关函数与结构体:
init、platform_driver、probe.....
- 分配一个 fb_info 结构体:framebuffer_alloc
- 注册:register_framebuffer
-
硬件相关的操作
}
上一篇: 浅析Lcd驱动基于framebuffer框架(一)
下一篇: LCD 设备驱动框架分析及核心结构