【Linux驱动】framebuffer设备驱动

framebuffer驱动学了好久了,今天进行一下复盘。

关于framebuffer是什么,怎么用,网上都有很多的博客介绍。这里还是聚焦于分析framebuffer的驱动框架以及设备驱动代码。

fb驱动框架初始化(fbmem.c)

framebuffer的驱动框架在kernel/drivers/video/fbmem.c文件中,该文件是以模块的形式写的,所以从后往前看。

#ifdef MODULE
module_init(fbmem_init);
static void __exit
fbmem_exit(void)
{
	remove_proc_entry("fb", NULL);
	class_destroy(fb_class);
	unregister_chrdev(FB_MAJOR, "fb");
}

module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);
#endif

这里通过MODULE这个宏来控制该文件到底是被编译成模块(module_init加载、module_exit卸载)还是静态编译进内核(启动时调用subsys_initcall载入)。
值得关注的是,当编译成模块的时有exit,而编译进内核就没有相关的卸载函数。因为只有模块才能insmod和rmmod,而真正编译进内核之后,程序就随着内核启动而启动,随着内核消亡而消亡,并不存在单独的卸载这么一说了。

static int __init
fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_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;
}

接着我们关注fbmem_init 这个fb驱动框架初始化函数,这个函数做的事情很常规,就是1、创建proc目录下名为"fb"的文件,并提供相应操作方法;2、注册字符"fb"字符设备,值得关注的是使用了老接口注册的,fb的主设备号是29;3、创建"graphics"类;
这里先留个坑,关于注册字符设备、主设备号以及class_create、device_create这一套之间的关系,准备留到下一篇misc类设备驱动中再去研究。

深入研究一下fb的proc文件系统,我们看到cat fb之后打印出来5个fb设备。
在这里插入图片描述
回归代码,fb_proc_fops中主要实现了就是这个.read方法,追进去看seq_read,其实最后主要调用的是这句代码err = m->op->show(m, p);,而这个show方法是在哪里赋值的呢?实际是在open方法中调用了seq_open,又将proc_fb_seq_ops传入,而proc_fb_seq_ops就定义了show方法,显然可以得到真正的show方法是fb_seq_show。

static const struct file_operations fb_proc_fops = {
	.owner		= THIS_MODULE,
	.open		= proc_fb_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= seq_release,
};
static const struct seq_operations proc_fb_seq_ops = {
	.start	= fb_seq_start,
	.next	= fb_seq_next,
	.stop	= fb_seq_stop,
	.show	= fb_seq_show,
};

static int proc_fb_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &proc_fb_seq_ops);
}

上面提到的fb_seq_show函数中去遍历并打印了,一个registered_fb指针数组,指针类型为struct fb_info。这个数组就是整个驱动框架用来保存注册了的fb设备的数据结构,而struct fb_info则是框架定义的用来完整描述一个fb设备的结构体。
num_registered_fb是用来记录已经注册了的fb个数的。
可想而知,fb框架的注册函数,入参必定是struct fb_info类型的指针,在调用之前需要对fb_info类型的变量进行适当的填充后传入,并且在注册函数中必定会去自加num_registered_fb以及放入registered_fb数组中去。

extern struct fb_info *registered_fb[FB_MAX];
extern int num_registered_fb;

proc先研究到这里,至于字符设备的file_operations到时候等到研究fb驱动的时候再去看,因为这些方法在框架中多会去调用注册时fb_info中的fbops,若某个方法未注册则执行框架中的通用方法。

框架注册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;

	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

	remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
					 fb_is_primary_device(fb_info));

	num_registered_fb++;
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	fb_info->node = i;
	mutex_init(&fb_info->lock);
	mutex_init(&fb_info->mm_lock);

	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), NULL, "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);

	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	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);
	registered_fb[i] = fb_info;

	event.info = fb_info;
	if (!lock_fb_info(fb_info))
		return -ENODEV;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	return 0;
}

代码贴出来了,经过1、fb设备是否注册满了;2、判断大小端是否合适;3、移除冲突的fb设备;后,可以看到:registered_fb当前可用的下标 = fb_info->node = /dev/下的设备文件节点的次设备号

int fb_init_device(struct fb_info *fb_info)
{
	int i, error = 0;

	dev_set_drvdata(fb_info->dev, fb_info);

	fb_info->class_flag |= FB_SYSFS_FLAG_ATTR;

	for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {
		error = device_create_file(fb_info->dev, &device_attrs[i]);

		if (error)
			break;
	}

	if (error) {
		while (--i >= 0)
			device_remove_file(fb_info->dev, &device_attrs[i]);
		fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;
	}

	return 0;
}

关于fb_init_device函数,其实就是创建/sys/class/graphics/fb0下的属性文件,主要是调用了device_create_file,把device_attrs传了进去。

static struct device_attribute device_attrs[] = {
	__ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),
	__ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),
	__ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console),
	__ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor),
	__ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode),
	__ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes),
	__ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan),
	__ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual),
	__ATTR(name, S_IRUGO, show_name, NULL),
	__ATTR(stride, S_IRUGO, show_stride, NULL),
	__ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate),
	__ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),
#ifdef CONFIG_FB_BACKLIGHT
	__ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),
#endif
};

这些方法中,基本上都是通过使用fb_info结构体里面的数据进行操作,但这些方法的入参都只有struct device。因此为了得到fb_info,就可以看到每个方法开始的地方都是调用dev_get_drvdata来获取fb_info,而调用dev_get_drvdata的前提条件就是上述调用了dev_set_drvdata。

void dev_set_drvdata(struct device *dev, void *data)
{
	int error;

	if (!dev)
		return;
	if (!dev->p) {
		error = device_private_init(dev);
		if (error)
			return;
	}
	dev->p->driver_data = data;
}

核心为最后一句,struct device 中的 p->driver_data暂存上fb_info,然后要用的时候通过dev_get_drvdata取即可。

接下来,
fb_info->pixmap的一些初始化;
fb_info->var转成fb_videomode类型的mode,然后与fb_info->modelist链表中已有的比较,不存在相同的mode才加入该链表;(modedb.c中有好多标准的mode定义)
registered_fb[i] = fb_info;
event.info = fb_info;

在最后,执行fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);去通知所有已注册在fb_notifier_list这个链表中的所有成员,当前有一个FB_EVENT_FB_REGISTERED事件。
到此,register_framebuffer分析完毕。

FB驱动框架-总结

FB框架无非就两个核心内容:
1、fbmem_init (fb的proc系统下文件创建、字符设备注册(file_operations)、创建class);
2、register_framebuffer,主要操作与fb_info相关;

设备驱动文件(s3cfb.c)

static struct platform_driver s3cfb_driver = {
	.probe = s3cfb_probe,
	.remove = __devexit_p(s3cfb_remove),
	.driver = {
		   .name = S3CFB_NAME,
		   .owner = THIS_MODULE,
	},
};

static int __init s3cfb_register(void)
{
	platform_driver_register(&s3cfb_driver);

	return 0;
}
static void __exit s3cfb_unregister(void)
{
	platform_driver_unregister(&s3cfb_driver);
}

module_init(s3cfb_register);
module_exit(s3cfb_unregister);

从上述可以看出,这个驱动将fb实现为一个platform平台总线,这符合FB在SOC中有个LCD控制器(一般我们将SOC内部集成的内部外设实现成平台总线驱动)。同时也看到了platform_driver,其中比较重要的无非为probe函数和.driver中的name,通过这个name我们先去找对应的platform_device。(根据经验platform_device一般在mach-xxx.c中)

#ifdef CONFIG_FB_S3C
	&s3c_device_fb,
#endif
#if defined(CONFIG_S5P_DEV_FB)
static struct resource s3cfb_resource[] = {
	[0] = {
		.start = S5P_PA_LCD,
		.end   = S5P_PA_LCD + S5P_SZ_LCD - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_LCD1,
		.end   = IRQ_LCD1,
		.flags = IORESOURCE_IRQ,
	},
	[2] = {
		.start = IRQ_LCD0,
		.end   = IRQ_LCD0,
		.flags = IORESOURCE_IRQ,
	},
};

static u64 fb_dma_mask = 0xffffffffUL;

struct platform_device s3c_device_fb = {
	.name		  = "s3cfb",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3cfb_resource),
	.resource	  = s3cfb_resource,
	.dev		  = {
		.dma_mask		= &fb_dma_mask,
		.coherent_dma_mask	= 0xffffffffUL
	}
};
#define S5PV210_PA_LCD	   	(0xF8000000)
#define S5P_PA_LCD		S5PV210_PA_LCD
#define S5PV210_SZ_LCD		SZ_1M
#define S5P_SZ_LCD		S5PV210_SZ_LCD

进过一番寻找,s3c_device_fb 就是对应的platform_device(name一致)。特别说明,这里涉及两个宏CONFIG_FB_S3C、CONFIG_S5P_DEV_FB,均需要在make menuconfig中进行配置。若没有配置,驱动就无法正常工作,而且问题还比较难查,这种情况经常出现
值得关注的id为-1,表示希望驱动去自动分配次设备号;s3cfb_resource这个变量描述了该设备资源列表。其中包括IORESOURCE_MEM以及两个IRQ。IORESOURCE_MEM指的就是FB相关的寄存器(IO和内存统一编址),查数据手册可得知,s5pv210的FB寄存器首地址从0xF8000000开始到0xF8004110,而代码中分配的IO地址范围(PA物理地址)为1M,分配多了是没有问题的(需要确保多出来的地址不会被别的设备使用到)。这些物理地址后续会被通过动态映射映射到相应的虚拟地址。

额外说一下,基本上所有驱动都是采用动态映射的方法,原因是:采用静态映射不灵活,难以移植到其他不同的芯片平台——静态映射时会绑定(写死)与硬件相关的寄存器地址,而不同芯片的寄存器地址又是不一样的,因此用静态映射写出来的驱动不具备可移植性。

接下来,我们回到probe函数。先关注一下struct s3c_platform_fb *pdata;这个局部变量,类型为struct s3c_platform_fb,这个类型其实就是用来表示struct device下的platform_data的,platform_data在platform框架中很重要,是设备的一些硬件数据,驱动是根据这些数据操作硬件的。从如下代码可知。

	pdata = to_fb_plat(&pdev->dev);
	if (!pdata) {
		dev_err(fbdev->dev, "failed to get platform data\n");
		ret = -EINVAL;
		goto err_pdata;
	}

这里是在取pdev->dev.platform_data,那么这个数据到底是在哪里被赋值的呢?这个pdev是入参,也就是struct platform_device *pdev,因此我们去找回他定义的地方(上面有讲到过)。但是发现s3c_device_fb 中的.dev中并没有对platform_data的赋值。既然没有直接赋值,那应该是在某个地方又对这个变量的重新赋值。因此在这个文件中找一下关于该变量的操作。
那很幸运的是,在紧挨着的下面就看到了下面就有一个设置platdata的函数void __init s3cfb_set_platdata(struct s3c_platform_fb *pd),我们把需要的struct s3c_platform_fb类型的pd传入,这个函数就会设置下去。然后我们查找到这个函数是在mach-xxx.c文件中的smdkc110_machine_init中被调用的。

#ifdef CONFIG_FB_S3C_LTE480WV
	s3cfb_set_platdata(&lte480wv_fb_data);
#endif  

#ifdef CONFIG_FB_S3C_EK070TN93
	smdkv210_backlight_off();
	s3cfb_set_platdata(&ek070tn93_fb_data);
#endif

且查看.config,是ek070tn93_fb_data这个platform_data被设置进去了。

static struct s3c_platform_fb ek070tn93_fb_data __initdata = {
	.hw_ver	= 0x62,
	.nr_wins = 5,
	.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
	.swap = FB_SWAP_WORD | FB_SWAP_HWORD,

	.lcd = &ek070tn93,
	.cfg_gpio	= ek070tn93_cfg_gpio,
	.backlight_on	= ek070tn93_backlight_on,
	.backlight_onoff    = ek070tn93_backlight_off,
	.reset_lcd	= ek070tn93_reset_lcd,
};
static struct s3cfb_lcd ek070tn93 = {
	.width = S5PV210_LCD_WIDTH,
	.height = S5PV210_LCD_HEIGHT,
	.bpp = 32,
	.freq = 60,

	.timing = {
		.h_fp	= 160,
		.h_bp	= 140,
		.h_sw	= 20,
		.v_fp	= 12,
		.v_fpe	= 1,
		.v_bp	= 20,
		.v_bpe	= 1,
		.v_sw	= 3,
	},
	.polarity = {
		.rise_vclk = 0,
		.inv_hsync = 1,
		.inv_vsync = 1,
		.inv_vden = 0,
	},
};

.lcd = &ek070tn93,这里的lcd,即ek070tn93 已经完整定义了LCD的具体参数,如果要重新移植一块LCD的话,就该这个变量中的值就可以了。

回到probe函数,接下来这几句代码:
1)将pdata中的lcd赋值给fbdev的lcd;
2)执行pdata->cfg_gpio,gpio初始化;
3)执行pdata->clk_on,clk初始化;

	fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd;

	if (pdata->cfg_gpio)
		pdata->cfg_gpio(pdev);

	if (pdata->clk_on)
		pdata->clk_on(pdev, &fbdev->clock);

接下来这段代码:
1)通过resource的.flag为IORESOURCE_MEM获取对应的resource,即FB寄存器相关的寄存器物理地址;
2)request_mem_region、ioremap动态申请分配虚拟地址,放入fbdev->regs中;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(fbdev->dev, "failed to get io memory region\n");
		ret = -EINVAL;
		goto err_io;
	}

	res = request_mem_region(res->start,
				 res->end - res->start + 1, pdev->name);
	if (!res) {
		dev_err(fbdev->dev, "failed to request io memory region\n");
		ret = -EINVAL;
		goto err_io;
	}

	fbdev->regs = ioremap(res->start, res->end - res->start + 1);
	if (!fbdev->regs) {
		dev_err(fbdev->dev, "failed to remap io region\n");
		ret = -EINVAL;
		goto err_mem;
	}

趁着上面把寄存器的地址映射好了,下面就进行对这些寄存器的操作,一些硬件初始化。

	s3cfb_set_vsync_interrupt(fbdev, 1);
	s3cfb_set_global_interrupt(fbdev, 1);
	s3cfb_init_global(fbdev);

s3cfb_set_vsync_interrupt、s3cfb_set_global_interrupt这两个函数是完全对硬件寄存器的操作,遵循“读、改、写”三部曲操作,这些硬件操作的函数都放在s3cfb_fimd6x.c这个文件中。s3cfb_fimd6x.c这个文件存放都是FB相关的硬件操作,且每个函数的入参基本上都是struct s3cfb_global的fbdev。而s3cfb_init_global这个函数的定义还在s3cfb.c中,那就说明这个函数没有实际的硬件操作,而这个函数内部却调用了s3cfb_fimd6x.c的几个硬件寄存器初始化的函数。

接下来重点介绍一下如下两个函数:

	if (s3cfb_alloc_framebuffer(fbdev)) {
		ret = -ENOMEM;
		goto err_alloc;
	}

	if (s3cfb_register_framebuffer(fbdev)) {
		ret = -EINVAL;
		goto err_register;
	}

s3cfb_alloc_framebuffer函数主要的步骤:
1)为struct s3cfb_global的fbdev中的fb_info 申请内存,以及fb_info内部的一些地址申请内存。为后续拿fb_info去驱动框架注册做准备;
注意,一个fbdev一般会包含多个fb_info,正如该驱动中就有5个fb_info(paltdata中.nr_wins = 5,)。这样做有个好处:多个fb可以叠加使用,对于画面静止的fb可以不进行刷新,仅刷新画面改变的fb的一小部分,提高刷新显示效率。
2)for循环中调用s3cfb_init_fbinfo(ctrl, i);,这个函数还在s3cfb.c中,其实就是填充每个fb_info中的内容,主要是fix和var,数据源主要来自struct s3cfb_global的fbdev中的lcd和timing,有些值也是写死的;
注意,该函数中调用了platform_set_drvdata(to_platform_device(ctrl->dev), ctrl);,这个对应后续很多地方调用platform_get_drvdata是对应的。主要是将platform_device中的dev->p->driver_data放上struct s3cfb_global的fbdev地址,让后续可以通过platform_device找到fbdev。其实在之前就已经将执行过fbdev->dev = &pdev->dev;,两者就已经做过挂接。说实话不知道为什么这段代码会放在这里。

而s3cfb_register_framebuffer中就是循环调用register_framebuffer向框架注册fb设备。

接下来比较重要的就是logo显示。

	if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
		printk("Start display and show logo\n");
		/* Start display and show logo on boot */
		fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
		fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);
	}

调用关系如下:

fb_prepare_logo
	fb_find_logo			//真正查找logo文件
fb_show_logo
	fb_show_logo_line	//真正显示logo
		fb_do_show_logo
			info->fbops->fb_imageblit	//实际操作硬件fb进行显示工作的函数

最后,调用pdata->backlight_on(pdev)点亮背光。

FB驱动代码-总结

到此,probe函数分析结束。
probe函数主要做了2件事:
1、硬件初始化;
2、数据结构的填充,特别是struct s3cfb_global的fbdev。

关于数据结构,再进行总结一下:
1、probe函数接收一个platform_device的入参,即s3c_device_fb ;

struct platform_device s3c_device_fb = {
	.name		  = "s3cfb",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3cfb_resource),
	.resource	  = s3cfb_resource,
	.dev		  = {
		.dma_mask		= &fb_dma_mask,
		.coherent_dma_mask	= 0xffffffffUL
	}
};

2、该入参中的.dev.platform_data会被设置成&ek070tn93_fb_data,该变量的类型是struct s3c_platform_fb。

static struct s3c_platform_fb ek070tn93_fb_data __initdata = {
	.hw_ver	= 0x62,
	.nr_wins = 5,
	.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
	.swap = FB_SWAP_WORD | FB_SWAP_HWORD,

	.lcd = &ek070tn93,
	.cfg_gpio	= ek070tn93_cfg_gpio,
	.backlight_on	= ek070tn93_backlight_on,
	.backlight_onoff    = ek070tn93_backlight_off,
	.reset_lcd	= ek070tn93_reset_lcd,
};

总的来说,设备信息主要来自platform_device的resource 和 platform_data(s3c_platform_fb)(特别是.lcd)中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值