LCD驱动开发——相关内核源码的分析

本篇博客LCD驱动开发环境,内核基于Linux2.6.22,soc基于s3c2440
我们先来看下内核中相关代码,其实我们会发现LCD驱动和前面输入子系统在理念上非常相似(有趣的是,LCD驱动可以基于输入子系统来开发),我个人认为,内核在驱动中最重要的一个思想就是机制和策略分离,将代表着硬件设备(机制)的代码和代表着驱动(策略)的代码尽量剥离开来,然后从大体相同而在细节上不同的驱动之间找出它们的相同代码,将相同的代码构建成一个类似于平台一样的东西,这个平台尽量具有包容性,这样就可以让两端的驱动和设备的代码量减少,有效了减少了内核驱动的冗余代码,也给我们这样的普通开发者一个更友好的开发环境。
类型与输入子系统,LCD驱动也有一个相当于核心层
/driver/video/fbmem.c

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;
}

这个函数向内核申请了主设备号,注册了一个fb_fops,同时还向创建了一个class,但是没有在这个class基础上创建一个设备节点,这是由原因的,稍后我们就会知道
看下这个fb_fops,当我们调用注册在这个平台下的fb(frambuffer)read、write等函数时,对应的系统调用就是fb_fops中的函数

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.ioctl =	fb_ioctl,
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
};

我们首先看下open函数

static int
fb_open(struct inode *inode, struct file *file)
{
	int fbidx = iminor(inode);
	struct fb_info *info;
	int res = 0;
	//判断次设备号是否大于FB_MAX,这个是由限制的
	if (fbidx >= FB_MAX)
		return -ENODEV;
	//这个是最为核心的部分,从registered_fb这个全局数组中根据次设备号来获取一个info
	if (!(info = registered_fb[fbidx]))
		return -ENODEV;
	if (!try_module_get(info->fbops->owner))
		return -ENODEV;
	//把info存放在file->private_data中
	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;
}

这个open函数和输入子系统中的open函数其实没有太大区别,都是根据次设备号从一个全局数组中获取一个成员(info)。然后把info保存在file->private_data中。所以我们可能想知道到底是哪个函数对这个registered_fb全局数组进行初始化,这个稍后分析,继续看fb_fops

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];
	u32 *buffer, *dst;
	u32 __iomem *src;
	int c, i, cnt = 0, err = 0;
	unsigned long total_size;
	...
	//判断info->fbops里有没有fb_read函数,如果有,就直接执行info->fbops函数
	if (info->fbops->fb_read)
		return info->fbops->fb_read(info, buf, count, ppos);
	
	total_size = info->screen_size;
	
	...
	//申请一个buffer,这个buffer等下是作为向用户空间传递信息的载体
	//有趣的是这里对buffer的空间大小有要求,所以可以猜测一下,等下信息传递肯定会有一个循序
	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;
	//读操作的src地址是显存的基地址加上传进来的文件指针偏移
	src = (u32 __iomem *) (info->screen_base + p);
	//如果fbops中有定义fb_sync就执行
	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);
	
	while (count) {
		c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		dst = buffer;
		//因为src和dst都是32位数,所以fb_readl每次读的是四个字节,所以需要对c除以4,
		for (i = c >> 2; i--; )
			*dst++ = fb_readl(src++);
			//那如果除以4有余数呢,显然就不可以直接32位一起读了,所以只能以一个字节为单位来读了。
		if (c & 3) {
			u8 *dst8 = (u8 *) dst;
			u8 __iomem *src8 = (u8 __iomem *) src;

			for (i = c & 3; 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;
}

这里对缓冲区的读操作写得非常地巧妙,值得我们好好学习一下。读懂了read函数,write函数就很好懂了,我们来看下之前提到的那个问题,到底是哪个函数初始化了registered_fb这个全局数组

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++全局变量加一计数,
		//如果最终没有申请成功,也没有看到对这个全局变量进行恢复,所以是不是有问题?
	num_registered_fb++;
	//找出第一个没有被使用的次设备号
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	fb_info->node = i;
	//在fb_class基础上创建一个设备节点(device),在入口函数中我们发现只有register_chrdev和class_create,但是没有device_create。
	//原来是把device_create放在了register_framebuffer中。首先这可以说明,一个主设备号可以对应多个次设备号(废话),一个fb_class仅仅只是对应
	//着一个主设备号,可以依据不同的次设备号在同一个class基础上创建不同的device
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), "fb%d", i);
	if (IS_ERR(fb_info->dev)) {
		/* Not fatal */
		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info);
	//对fb_info进行基本的初始化,代码比较多我给删掉了
	...
	fb_info->pixmap.offset = 0;

	if (!fb_info->pixmap.blit_x)
		fb_info->pixmap.blit_x = ~(u32)0;

	if (!fb_info->pixmap.blit_y)
		fb_info->pixmap.blit_y = ~(u32)0;

	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);

	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);
	//将初始化好的fb_info注册到全局数组register_fb中,用次设备号来管理
	registered_fb[i] = fb_info;

	event.info = fb_info;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	return 0;
}

那是谁调用了register_framebuffer?这里我们以s3c2410fb.c为例
可以看到是s3c2410fb_probe调用了register_framebuffer,这个文件是建立在平台总线之上

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;
	//从pdev中获取irq
	irq = platform_get_irq(pdev, 0);
	//申请一个fb_info,为什么要在这里用sizeof(struct s3c2410fb_info)?
	//可以进入framebuffer_alloc看一下,size_t size参数表示的是额外申请空间,就是说如果我们把这个参数设置为0
	//那么将会申请到sizeof(struct fb_info)大小的空间。那为什么要申请额外空间呢?这个额外空间是给private_data使用的
	//在核心层fbmem.c的fb_open函数中,将会把fb_info保存在其中	
	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;
	/*fix对应的是fb_fix_screeninfo,代表着固定参数*/
	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;
	/*var 代表着可变参数的设置*/
	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;   //三种颜色的offset和length是和他的bpp mode有关,我们这里选择的是565
	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;


	for (i = 0; i < 256; i++)
		info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

	if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
		ret = -EBUSY;
		goto dealloc_fb;

	dprintk("got LCD region\n");

	ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);

	info->clk = clk_get(NULL, "lcd");

	clk_enable(info->clk);
	dprintk("got and enabled clock\n");

	msleep(1);

	/* Initialize video memory */
	ret = s3c2410fb_map_video_memory(info);

	dprintk("got video memory\n");

	ret = s3c2410fb_init_registers(info);

	ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);
	//初始化fbinfo完毕,将其注册到全局数组中
	ret = register_framebuffer(fbinfo);


	/* create device files */
	device_create_file(&pdev->dev, &dev_attr_debug);

	printk(KERN_INFO "fb%d: %s frame buffer device\n",
		fbinfo->node, fbinfo->fix.id);

	return 0;

}

如下是s3c2410-lcd对应的resource

/* LCD Controller */

static struct resource s3c_lcd_resource[] = {
	[0] = {
		.start = S3C24XX_PA_LCD,
		.end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_LCD,
		.end   = IRQ_LCD,
		.flags = IORESOURCE_IRQ,
	}

};

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;

struct platform_device s3c_device_lcd = {
	.name		  = "s3c2410-lcd",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3c_lcd_resource),
	.resource	  = s3c_lcd_resource,
	.dev              = {
		.dma_mask		= &s3c_device_lcd_dmamask,
		.coherent_dma_mask	= 0xffffffffUL
	}
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值