欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第二期驱动篇——1.1 LCD驱动编写——Linux内核中LCD驱动框架分析

程序员文章站 2022-07-14 09:38:38
...

Linux内核中LCD驱动框架分析

/*
 *硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
 *软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
 *参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》


一、前言

  在016 LCD(2)-编写程序这篇博客中,编写了裸机LCD驱动程序,在这一节中,将在Linux系统下编写LCD驱动程序。

二、框架

1、回顾裸板LCD驱动

第二期驱动篇——1.1 LCD驱动编写——Linux内核中LCD驱动框架分析

  • 第一层:测试菜单层lcd_test.c
  • 第二层:根据目的划分三个C文件:framebuffer.cgeometry.cfont.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总结

第二期驱动篇——1.1 LCD驱动编写——Linux内核中LCD驱动框架分析

  1. fbmem.c是一个通用的文件
  2. 供上层应用来调用:根据上层open等函数来打开对应的设备节点
  3. 在当层中:通过找到该设备的次设备号,通过设备号在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驱动的大致框架如下图:
第二期驱动篇——1.1 LCD驱动编写——Linux内核中LCD驱动框架分析
可以总结出来,platform总线机制下的的LCD驱动的编写:不像编写裸板LCD时是抽出那么多函数来形成一个拓展性很好的框架(因为Linux内核已经帮我们做了),我们只需要编写我们具体驱动文件。

驱动文件编写:

  1. 设置相关函数与结构体init、platform_driver、probe.....
  2. 分配一个 fb_info 结构体:framebuffer_alloc
  3. 注册:register_framebuffer
  4. 硬件相关的操作