 |
 |
培訓信息 |
|
|
|
 |
 |
贊助商 |
|
|
|
|
 |
 |
Linux-2.6.20的LCD驅動分析(二) (轉載) |
|
|
| Linux-2.6.20的LCD驅動分析(二) (轉載) |
| 作者:佚名 來源:單片機 錄入:jdzj868 更新時間:2009-8-12 16:51:21 點擊數:0 |
【字體:
】 |
|
http://blog.chinaunix.net/u1/49924/showart_499156.html 二、s3c2410fb_probe函數分析2.1 驅動的入口點擺在面前的第一個問題相信應該是,這個函數是從那里開始運行的。這里就應該從long long ago 開始了,打開drivers/video/s3c2410fb.c文件,然后找到s3c2410fb_init函數,先不管它里面是怎么回事,再把目光下移就會看到這樣一串字符串module_init(s3c2410fb_init),郁悶,這和S3C2410fb_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.20的cs8900驅動分析(一)這篇文章中有對它的揭秘。 上面啰嗦了這么多,最終是要說明只要用module_init申明了一個函數,該函數就會被Linux內核在適當的時機運行,這些時機包括在linux啟動的do_initcalls()時調用(設備被編譯進內核),或者在動態插入時調用。 回到上面的module_init(s3c2410fb_init)處,也就是說內核與buffer驅動發生關系的第一次地點是在s3c2410fb_init函數,該函數就只有一條語句platform_driver_register (&s3c2410fb_driver);??????…… 2.2 platform是何許人也 platform可以理解成一種設備類型,就像字符設備、塊設備和網絡設備一樣,而LCD就屬于這種設備。對于platform設備Linux為應用添加了相關的接口,在這里只是簡單的說說這些接口的用法,而不去深入探討這些接口的實現(我現在還沒有那個能力呢!)。說到這里,馬上就有個問題涌上心頭了,那就是Linux提供了那些接口呢?如果我們需要添加這些設備應該怎么樣做呢? platform中的相關數據結構是應用的關鍵,為了向內核添加一個platform設備,程序員應該填寫兩個數據結構platform_device 和platform_driver,這兩個數據結構的定義都可以在include/linux/platform_device.h文件中找到。看看LCD驅動是怎么做的,第一步是填寫platform_device,在arch/arm/mach-s3c2410/devs.c可以找到填寫platform_device的代碼,如下: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 }}; 這里面的各個數據成員的意思,在platform_device數據結構中有詳細的說明,這里不贅述。上面的代碼中的ARRAY_SIZE宏還是比較有意思的,其實是個c的編程技巧,這個技巧很有用哦!可以在include/linux/kernel.h中找到它的定義:#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))該宏可以方便的求出一個數組中有多少數據成員,這在很多情況下是很有用的,比如對于 int a[]={1,5,65,23,12,20,3}數組,可以使用該宏求出a[]有7個元素。 另外,platform_device的另外一項重要成員是resource,在上面的代碼中此域被賦予了s3c_lcd_resource,s3c_lcd_resource也可以在arch/arm/mach-s3c2410/devs.c找到。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, }};struct resource結構實際上描述了該設備占用的硬件資源(如地址空間,中斷號等s),s3c_lcd_resource描述了內存空間和中斷分配情況。 最后在smdk2410_devices指針數組中添加上s3c_device_lcd的大名,Linux在初始化platform的時候就知道系統中有個s3c_device_lcd設備了。注意了這里只是向Linux描述了設備需要的資源情況,不代表內核會給這些資源的。如果設備要得到這些設備還需要在自己的初始化函數中去申請。 static struct platform_device *smdk2410_devices[] __initdata = { &s3c_device_usb, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c, &s3c_device_iis, &s3c_device_ts,};說到這里,應該說向Linux添加一個platform設備應該很容易。 2.2 回到s3c2410fb_init終于把platform的相關知識啰嗦了一番,下面回到s3c2410fb_init函數所調用platform_driver_register(&s3c2410fb_driver)。簡單地說platform_driver_register要將向內核注冊一個platform設備的驅動,這里是要注冊LCD設備。上面說過platform有兩個重要的數據結構platform_device和platform_driver,現在是應該提到后者的時候了。platform_driver也在include/linux/platform_device.h中,它的各個成員應該再明白不過來吧!在LCD驅動程序(drivers/video/s3c2410fb.c)中定義了填充了platform_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, },};可以看到該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 s3c2410fb_probe(struct platform_device *pdev){ struct s3c2410 fb_info *info; //s3c2410fb_info結構在driver/video/s3c2410fb.h中定義,//可以說該結構記錄了s3c2410fb驅動的所有信息。 struct fb_info *fbinfo; /* fb_info為內核提供的buffer驅動的接口數據結構, 每個幀緩沖驅動都對應一個這樣的結構。s3c2410fb_probe的最終目的填充該結構,并向內核注冊。*/ struct s3c2410fb_hw *mregs; // s3c2410fb_hw為描述LCD的硬件控制寄存器的結構體,//在include/asm-arm/arch-s3c2410/fb.h可以找到它的原型。…… mach_info = pdev->dev.platform_data; /*這一步看來要多費些口舌了。mach_info是一個s3c2410fb_mach_info類型的指針,注意區分s3c2410fb_mach_info和s3c2410fb_info結構,簡單地說前者只是用于描述LCD初始化時所用的值,而后者是描述整個LCD驅動的結構體。s3c2410fb_mach_info在include/asm-arm/arch-s3c2410/fb.h中定義,從他的位置可以看出它和平臺相關,也即它不是內核認知的數據結構,這只是驅動程序設計者設計的結構。這里的主要疑問是什么呢?從下面的if語句可以看出如果mach_info等于NULL的話,整個驅動程序就退出了,這就引出了問題――pdev->dev.platform_data是在什么時候被初始化的呢?看來要回答這個問題,歷史應該回到孫悟空大鬧天宮的時候了。按住倒帶鍵不放一直到本篇文章的第一部分,看看那個時候做了些什么。放在這里來解釋第一部分的內容希望沒有為時已晚。其實在內核啟動init進程之前就會執行smdk2410_map_io( )函數(內核的啟動分析就免了吧@_@),而在smdk2410_map_io( )中我們加入了s3c24xx_fb_set_platdata (&smdk2410_lcd_platdata);這條語句,s3c24xx_fb_set_platdata()的實現為:void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd){ s3c_device_lcd.dev.platform_data = pd;}根據這些代碼,可以清楚的看到s3c_device_lcd.dev.platform_data指向了smdk2410_lcd_platdata,而這個smdk2410_lcd_platdata就是一個s3c2410fb_mach_info的變量,它里面就存放了LCD驅動初始化需要的初始數據。當s3c2410fb_probe被回調時,所傳給它的參數實際就是s3c_device_lcd的首地址,說到這里一切應該都明了了吧!好了,又撤了一通,現在假設這步成功,繼續往下面走。*/ if (mach_info == NULL) { dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n"); return -EINVAL; } mregs = &mach_info->regs; //mregs指向硬件各控制寄存器的初始值,可參見第一部//分的smdk2410_lcd_platdata變量。 irq = platform_get_irq(pdev, 0); /*該函數獲得中斷號,該函數的實現是通過比較struct resource的flags域,得到irq中斷號,在上2.1的時候提到s3c_lcd_resource[],platform_get_irq函數檢測到flags==IORESOURCE_IRQ時就返回中斷號IRQ_LCD。詳細的內容請讀它的源代碼吧!*/ if (irq < 0) { //沒有找到可用的中斷號,返回-ENOENT dev_err(&pdev->dev, "no irq for device\n"); return -ENOENT; } fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); /* 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域指向該私有空間。*/ if (!fbinfo) { return -ENOMEM; } //以下開始做正經事了,填充fbinfo了。 info = fbinfo->par; //你中有我,我中有你! info->fb = fbinfo; platform_set_drvdata(pdev, fbinfo); /*該函數的實現非常簡單,實際的操作為:pdev->dev.driver_data = fbinfo,device結構的driver_data域指向驅動程序的私有數據空間。*/ 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);//停止硬件 /*以下的對fbinfo的填寫就免了吧!對于fb_info結構的各個成員,在include/linux/fb文件中都有詳細的說明,如果不知道說明的意思,就應該找些基本的知識讀讀了。在眾多的初始化中,fbinfo->fbops = &s3c2410fb_ops;是值得一提的,變量s3c2410fb_ops 就在s3c2410fb.c中定義,它記錄了該幀緩沖區驅動所支持的操作 */ …… 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")) {/* 向內核申請內存空間,如果request_mem_region返回0表示申請失敗,此時程序跳到dealloc_fb處開始執行,該處會調用framebuffer_release釋放剛才由framebuffer_alloc申請的fb_info空間 */ ret = -EBUSY; goto dealloc_fb; }…… ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);/* 向內核注冊中斷,如果注冊失敗,程序跳轉到release_mem處運行,此處釋放fb_info和剛才由request_mem_region申請的內存空間 */ if (ret) { dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret); ret = -EBUSY; goto release_mem; } info->clk = clk_get(NULL, "lcd"); //該函數得到時鐘源,并與硬件緊密相連,對于我的//板子,可以在arch/arm/mach-s3c2410/clock.c看到它的原型和實現。 if (!info->clk || IS_ERR(info->clk)) { printk(KERN_ERR "failed to get lcd clock source\n"); ret = -ENOENT; goto release_irq; //該處釋放上面申請的fb_info,內存,和irq資源 } clk_enable(info->clk); //打開時鐘 dprintk("got and enabled clock\n"); msleep(1); //運行得太久有點累了,去打個盹再說 /* Initialize video memory */ ret = s3c2410fb_map_video_memory(info);/*此函數就在s3c2410fb.c文件中被定義,它的作用是申請幀緩沖器內存空間*/ if (ret) { printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret); ret = -ENOMEM; goto release_clock; //釋放所有已得到的資源 } dprintk("got video memory\n"); ret = s3c2410fb_init_registers(info); //此函數也在s3c2410fb.c文件中定義,后面會分析 ret = s3c2410fb_check_var(&fbinfo->var, fbinfo); //此函數也在s3c2410fb.c文件中定義 ret = register_framebuffer(fbinfo); //神圣的時刻終于到來,向內核正式注冊。 if (ret < 0) { printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); goto free_video_memory; //不讓注冊真郁悶,那就釋放所有的資源,出家算了! } /* create device files */ device_create_file(&pdev->dev, &dev_attr_debug); //為該設備創建一個在sysfs中的屬性 printk(KERN_INFO "fb%d: %s frame buffer device\n", fbinfo->node, fbinfo->fix.id); return 0; //大功告成! free_video_memory: s3c2410fb_unmap_video_memory(info);release_clock: clk_disable(info->clk); clk_put(info->clk);release_irq: free_irq(irq,info);release_mem: release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD);dealloc_fb: framebuffer_release(fbinfo); return ret;} To be continued…… ------ anmnmnly ------ 2008.03.18 |
|
|
發表評論 告訴好友 打印此文 收藏此頁 關閉窗口 返回頂部 |
|
|
 |
 |
網友評論:(只顯示最新5條。) |
|
|
|
|
|