Linux-2.6.38的LCD驱动分析(二)
参考自:http://blog.chinaunix.net/uid-11114210-id-2907026.html
二、s3cfb_probe函数分析
2.1 驱动的入口点
摆在面前的第一个问题相信应该是,这个函数是从那里开始运行的。这里就应该从long long ago 开始了,打开linux-2.6.38\drivers\video\samsung\s3cfb.c文件,然后找到s3cfb_init函数,先不管它里面是怎么回事,再把目光下移就会看到这样一串字符串module_init(s3cfb_init),郁闷,这和s3cfb_probe有啥关系嘛?这个问题问的好!不要着急慢慢往下面走。先摸摸module_init是何方神圣再说,于是乎我就登陆了http://lxr.linux.no/linux+v2.6.20/网站,在上面一搜,原来module_init老家在include/linux/init.h,原来它居然还有两重身份,其原型如下:
#ifndef MODULE
……
#define module_init(x) __initcall(x); ①
……
#else
……
#define module_init(initfn) \ ②
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#c)));
……
#endif
从上面可以看出,module_init到底用哪个,就取决于MODULE了,那么MODULE的作用是什么呢?我们知道Linux可以将设备当作模块动态加进内核,也可以直接编译进内核,说到这里大概有点明白MODULE的作用了,不错!它就是要控制一个驱动加入内核的方式。定义了MODULE就表示将设备当作模块动态加入。所以上面的①表示将设备加进内核。在②中的__attribute__((alias(#initfn)))很有意思,这代表什么呢?主要alias就是属性的意思,它的英文意思是别名,可以在http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=/com.ibm.xlcpp8l.doc/language/ref/fn_attrib_alias.htm找到它的详细说明,这里简单的说int init_module(void) __attribute__((alias(#initfn)));的意思为init_module是initfn的别名,或者init_module是initfn的一个连接,再简单一点说这个时候module_init宏基因突变成了init_module()了。对于第一种情况,__initcall(fn) 又被宏定义成了device_initcall(fn),也就是说module_init(x)等于device_initcall(fn)。对于device_initcall(fn)又是一个宏定义,它被定义成了__define_initcall("6",fn,6),至于这个宏表示什么意思,在这里就不啰嗦重复了,在Linux-2.6.38的cs8900驱动分析(一)这篇文章中有对它的揭秘。
上面啰嗦了这么多,最终是要说明只要用module_init申明了一个函数,该函数就会被Linux内核在适当的时机运行,这些时机包括在linux启动的do_initcalls()时调用(设备被编译进内核),或者在动态插入时调用。
回到上面的module_init(s3cfb_init)处,也就是说内核与buffer驱动发生关系的第一次地点是在s3cfb_init函数,该函数就只有一条语句
platform_driver_register (&s3cfb_driver);
??????……
2.2 platform是何许人也
platform可以理解成一种设备类型,就像字符设备、块设备和网络设备一样,而LCD就属于这种设备。对于platform设备Linux为应用添加了相关的接口,在这里只是简单的说说这些接口的用法,而不去深入探讨这些接口的实现(我现在还没有那个能力呢!)。说到这里,马上就有个问题涌上心头了,那就是Linux提供了那些接口呢?如果我们需要添加这些设备应该怎么样做呢?
platform中的相关数据结构是应用的关键,为了向内核添加一个platform设备,程序员应该填写两个数据结构platform_device 和platform_driver,这两个数据结构的定义都可以在include/linux/platform_device.h文件中找到。看看LCD驱动是怎么做的,第一步是填写platform_device,在J:\Tiny6410\最新arch\arm\mach-s3c64xx\mach-mini6410.c可以找到填写platform_device的代码,如下:
/* framebuffer and LCD setup. */
/* GPF15 = LCD backlight control
* GPF13 => Panel power
* GPN5 = LCD nRESET signal
* PWM_TOUT1 => backlight brightness
*/
static void mini6410_lcd_power_set(struct plat_lcd_data *pd,
unsigned int power)
{
if (power) {
gpio_direction_output(S3C64XX_GPF(13), 1);
gpio_direction_output(S3C64XX_GPF(15), 1);
/* fire nRESET on power up */
gpio_direction_output(S3C64XX_GPN(5), 0);
msleep(10);
gpio_direction_output(S3C64XX_GPN(5), 1);
msleep(1);
} else {
gpio_direction_output(S3C64XX_GPF(15), 0);
gpio_direction_output(S3C64XX_GPF(13), 0);
}
}
static struct plat_lcd_data mini6410_lcd_power_data = {
.set_power = mini6410_lcd_power_set,
};
static struct platform_device mini6410_lcd_powerdev = {
.name = "platform-lcd",
.dev.parent = &s3c_device_fb.dev,
.dev.platform_data= &mini6410_lcd_power_data,
};
第二步在struct platform_device *mini6410_devices[]指针数组中添加上mini6410_lcd_powerdev的大名,Linux在初始化platform的时候就知道系统中有个mini6410_lcd_powerdev设备了。注意了这里只是向Linux描述了设备需要的资源情况,不代表内核会给这些资源的。如果设备要得到这些设备还需要在自己的初始化函数中去申请。
static struct platform_device *mini6410_devices[] __initdata = {
&s3c_device_i2c0,
&s3c_device_nand,
&s3c_device_fb,
&s3c_device_ohci,
&s3c_device_usb_hsotg,
&samsung_asoc_dma,
&mini6410_lcd_powerdev,//向内核注册lcd device
......
/* Multimedia support */
#ifdef CONFIG_VIDEO_SAMSUNG
&s3c_device_vpp,
&s3c_device_mfc,
&s3c_device_tvenc,
&s3c_device_tvscaler,
&s3c_device_rotator,
&s3c_device_jpeg,
&s3c_device_fimc0,
&s3c_device_fimc1,
&s3c_device_g2d,
&s3c_device_g3d,
#endif
};
说到这里,应该说向Linux添加一个platform设备应该很容易。
2.2 回到s3cfb_init
终于把platform的相关知识啰嗦了一番,下面回到s3cfb_init函数所调用platform_driver_register(&s3cfb_driver)。简单地说platform_driver_register要将向内核注册一个platform设备的驱动,这里是要注册LCD设备。上面说过platform有两个重要的数据结构platform_device和platform_driver,现在是应该提到后者的时候了。platform_driver也在include/linux/platform_device.h中,它的各个成员应该再明白不过来吧!在LCD驱动程序(drivers/video/samsung/s3cfb.c)中定义了填充了platform_driver这个结构,如下:
static struct platform_driver s3cfb_driver = {
.probe = s3cfb_probe,
.remove = s3cfb_remove,
.suspend = s3cfb_suspend,
.resume = s3cfb_resume,
.driver = {
.name = "s3c-fb",
.owner = THIS_MODULE,
},
};
可以看到该platform设备的驱动函数有s3c2410fb_probe、s3c2410fb_remove等等。通过platform_driver_register函数注册该设备的过程中,它会回调.probe函数,说到这里也就明白s3c2410fb_probe是在platform_driver_registe中回调的。到目前为止,经过二万五千里长征终于到达s3c2410fb_probe(LCD的驱动程序)了。
2.3 s3c2410fb_probe揭秘
对于该函数,我想最好的办法就是跟着程序一步一步的解释。OK,let’s go to ……
static int __init s3cfb_probe(struct platform_device *pdev)
{
struct resource *res; //描述LCD的硬件控制寄存器的结构体.
struct fb_info *fbinfo; //fb_info为内核提供的buffer驱动的接口数据结构, 每个帧缓冲驱动都对应一个这样的结构。s3cfb_probe的最终目的填充该结构,并向内核注册.
s3cfb_info_t *info; //该结构记录了s3cfb驱动的所有信息.
char driver_name[] = "s3cfb"; //驱动名称
int index = 0, ret, size;
//framebuffer_alloc可以在include/linux/fb.h文件中找到其原型:struct fb_info *framebuffer_alloc(size_t size, struct device *dev); 它的功能是向内核申请一段大小为sizeof(struct fb_info) + size的空间,其中size的大小代表设备的私有数据空间,并用fb_info的par域指向该私有空间。
fbinfo = framebuffer_alloc(sizeof(s3cfb_info_t), &pdev->dev);
if (!fbinfo)
return -ENOMEM;
//以下开始做正经事了,填充fbinfo了。
platform_set_drvdata(pdev, fbinfo);
//该函数的实现非常简单,实际的操作为:pdev->dev.driver_data = fbinfo,device结构的driver_data域指向驱动程序的私有数据空间。
info = fbinfo->par;
info->dev = &pdev->dev;
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 = (res->end - res->start) + 1;
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;
}
//映射内存
info->io = ioremap(res->start, size);
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}
s3cfb_pre_init();
s3cfb_set_backlight_power(1);
s3cfb_set_lcd_power(1);
s3cfb_set_backlight_level(S3CFB_DEFAULT_BACKLIGHT_LEVEL);
//该函数得到时钟源,并与硬件紧密相连
info->clk = clk_get(NULL, "lcd");
if (!info->clk || IS_ERR(info->clk)) {
printk(KERN_INFO "failed to get lcd clock source\n");
ret = -ENOENT;
goto release_io;
}
//打开时钟
clk_enable(info->clk);
printk("S3C_LCD clock got enabled :: %ld.%03ld Mhz\n", PRINT_MHZ(clk_get_rate(info->clk)));
s3cfb_fimd.vsync_info.count = 0;
init_waitqueue_head(&s3cfb_fimd.vsync_info.wait_queue);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get irq\n");
ret = -ENXIO;
goto release_clock;
}
//向内核注册中断
ret = request_irq(res->start, s3cfb_irq, 0, "s3c-lcd", pdev);
if (ret != 0) {
printk("Failed to install irq (%d)\n", ret);
goto release_clock;
}
msleep(5);
for (index = 0; index < S3CFB_NUM; index++) {
s3cfb_info[index].mem = info->mem;
s3cfb_info[index].io = info->io;
s3cfb_info[index].clk = info->clk;
s3cfb_init_fbinfo(&s3cfb_info[index], driver_name, index);
/* Initialize video memory */
ret = s3cfb_map_video_memory(&s3cfb_info[index]);
if (ret) {
printk("Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_irq;
}
ret = s3cfb_init_registers(&s3cfb_info[index]);
ret = s3cfb_check_var(&s3cfb_info[index].fb.var, &s3cfb_info[index].fb);
if (index < 2){
if (fb_alloc_cmap(&s3cfb_info[index].fb.cmap, 256, 0) < 0)
goto dealloc_fb;
} else {
if (fb_alloc_cmap(&s3cfb_info[index].fb.cmap, 16, 0) < 0)
goto dealloc_fb;
}
//神圣的时刻终于到来,向内核正式注册。
ret = register_framebuffer(&s3cfb_info[index].fb);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
goto free_video_memory;
}
printk(KERN_INFO "fb%d: %s frame buffer device\n", s3cfb_info[index].fb.node, s3cfb_info[index].fb.fix.id);
}
/* create device files */
//为该设备创建一个在sysfs中的属性
ret = device_create_file(&(pdev->dev), &dev_attr_backlight_power);
if (ret < 0)
printk(KERN_WARNING "s3cfb: failed to add entries\n");
ret = device_create_file(&(pdev->dev), &dev_attr_backlight_level);
if (ret < 0)
printk(KERN_WARNING "s3cfb: failed to add entries\n");
ret = device_create_file(&(pdev->dev), &dev_attr_lcd_power);
if (ret < 0)
printk(KERN_WARNING "s3cfb: failed to add entries\n");
return 0;
//错误处理返回
free_video_memory:
s3cfb_unmap_video_memory(&s3cfb_info[index]);
release_irq:
free_irq(res->start, &info);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
release_io:
iounmap(info->io);
release_mem:
release_resource(info->mem);
kfree(info->mem);
dealloc_fb:
framebuffer_release(fbinfo);
return ret;
}
。。。To be continued !