一、讓LCD顯示可愛的小企鵝
還是先說說環境吧,處理器為S3C2410,linux的版本當然是2.6.20的。下面先說說怎樣讓LCD上顯示出可愛的小企鵝。最直接的步驟如下(記住不要問為什么哈~_~,一步一步跟着走就行了):
1. 添加s3c2410處理器的LCD控制寄存器的初始值,具體做法為在文件arch/arm/mach-s3c2410/mach-smdk2410.c中添加struct s3c2410fb_mach_info類型的寄存器描述訊息,如下所示:
static struct s3c2410fb_mach_info smdk2410_lcd_platdata = {
.fixed_syncs=0,
.type = S3C2410_LCDCON1_TFT,
.width= 240,
.height= 320,
.xres = {
.defval= 240,
.min= 240,
.max= 240,
},
.yres = {
.defval= 320,
.min= 320,
.max= 320,
},
.bpp = {
.defval= 16,
.min= 16,
.max= 16,
},
.regs = {
.lcdcon1= S3C2410_LCDCON1_TFT16BPP | /
S3C2410_LCDCON1_TFT | /
S3C2410_LCDCON1_CLKVAL(5) | /
(0<<7),
.lcdcon2= S3C2410_LCDCON2_VBPD(2) | /
S3C2410_LCDCON2_LINEVAL(320-1) | /
S3C2410_LCDCON2_VFPD(2) | /
S3C2410_LCDCON2_VSPW(4),
.lcdcon3= S3C2410_LCDCON3_HBPD(8) | /
S3C2410_LCDCON3_HOZVAL(240-1) | /
S3C2410_LCDCON3_HFPD(8),
.lcdcon4= S3C2410_LCDCON4_HSPW(6) | /
S3C2410_LCDCON4_MVAL(13),
.lcdcon5= S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_HWSWP,
},
.gpcup= 0x0,
.gpcup_mask= 0xFFFFFFFF,
.gpccon= 0xaaaa56a9,
.gpccon_mask= 0xFFFFFFFF,
.gpdup= 0x0,
.gpdup_mask= 0xFFFFFFFF,
.gpdcon= 0xaaaaaaaa,
.gpdcon_mask= 0xFFFFFFFF,
.lpcsel= 0x00
};
2. 通過s3c24xx_fb_set_platdata函數向內核注冊上面的信息。具體做法為:修改s3c24xx_fb_set_platdata函數(當然也可以重新起名字),修改如下:(此函數在arch/arm/mach-s3c2410/devs.c中)
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
s3c_device_lcd.dev.platform_data = pd;
}
然后在arch/arm/mach-s3c2410/mach-smdk2410.c的smdk2410_map_io函數中調用s3c24xx_fb_set_platdata( ),具體為:
s3c24xx_fb_set_platdata(&smdk2410_lcd_platdata);
注:此處未采用內核中提供的源函數,因為系統會崩潰,估計是它調用kmalloc函數引起的。
3. 在make menuconfig的時候配置Linux的logo選項,然后的時候在console選項中選上buffer console surpport,要不然看不到小企鵝。
上面這些步驟均來源於網上,感謝您們的無私貢獻!嘿嘿,到目前為止差不多也可以交差了,但我還想深入了解一下真正的驅動程序。呵呵,欲知后事如何且聽下回分解。
二、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函數,該函數就只有一條語句
??????……
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中找到它的定義:
該宏可以方便的求出一個數組中有多少數據成員,這在很多情況下是很有用的,比如對於 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()的實現為:
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 = buffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); /* buffer_alloc可以在include/linux/fb.h文件中找到其原型:struct fb_info *buffer_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 = PALETTE_BUFF_CLEAR;
if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
/* 向內核申請內存空間,如果request_mem_region返回0表示申請失敗,此時程序跳到dealloc_fb處開始執行,該處會調用buffer_release釋放剛才由buffer_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_buffer(fbinfo); //神聖的時刻終於到來,向內核正式注冊。
if (ret < 0) {
printk(KERN_ERR "Failed to register buffer 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 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:
buffer_release(fbinfo);
return ret;
}
三、解剖s3c2410fb_driver變量
s3c2410fb_driver變量有什么作用呢?在前面的2.2節提到了它的定義,從它的原型可以看出s3c2410fb_driver是個platform_driver類型的變量,前面的幾個小節提到了從platform_driver的名字可以看出它應該是platform_device的驅動類型。為了方便閱讀,這里再貼一次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,
},
};
從定義可以看出,該platform_device的驅動函數有s3c2410fb_probe,s3c2410fb_remove,s3c2410fb_suspend和s3c2410fb_suspend。.resource成員前面的章節有說明,.driver成員的值相信不用再說明了吧,再明白不過了。前面的章節,s3c2410fb_probe被比較詳細的介紹,這節中的主要任務就是解釋其他的幾個函數。在解釋他們之前,s3c2410fb_probe里面在該函數結尾的時候調用了幾個函數沒有說到,所以在這里補上。
3.1 s3c2410fb_probe余黨
在s3c2410fb_probe中最好調用了s3c2410fb_init_registers和s3c2410fb_check_var函數,這里應該將他們交代清楚。很顯然,s3c2410fb_init_registers是初始化相關寄存器。那么后者呢?這里先把s3c2410fb_init_registers搞定再說。s3c2410fb_init_registers的定義與實現如下,先根據它的指向流程,一步一步解釋:
static int s3c2410fb_init_registers(struct s3c2410fb_info *fbi)
{
unsigned long flags;
/* Initialise LCD with values from haret */
local_irq_save(flags); /* 關閉中斷,在關閉中斷前,中斷的當前狀態被保存在flags中,對於關閉中斷的函數,linux內核有很多種,可以查閱相關的資料。*/
/* modify the gpio(s) with interrupts set (bjd) */
/*下面的modify_gpio函數是修改處理器GPIO的工作模式,它的實現很簡單,將第二個參數的值與第三個參數的反碼按位與操作后,在寫到第一個參數。這里的第一個參數實際就是硬件的GPIO控制器。*/
modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);
modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask);
modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);
local_irq_restore(flags); //使能中斷,並恢復以前的狀態
/*下面的幾個writel函數開始初始化LCD控制寄存器,它的值就是我們在smdk2410_lcd_platdata(arch/arm/mach-s3c2410/mach-smdk2410.c)中regs域的值。*/
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
writel(fbi->regs.lcdcon3, S3C2410_LCDCON3);
writel(fbi->regs.lcdcon4, S3C2410_LCDCON4);
writel(fbi->regs.lcdcon5, S3C2410_LCDCON5);
s3c2410fb_set_lcdaddr(fbi); /*該函數的主要作用是讓處理器的LCD控制器的三個地址寄存器指向正確的位置,這個位置就是LCD的緩沖區,詳細的情況可以參見s3c2410的用戶手冊。*/
……
/* Enable video by setting the ENVID bit to 1 這里打開video,在s3c2410fb_probe中被關閉了,這里打開*/
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
return 0;
}
OK,s3c2410fb_init_registers就簡單介紹到這里。下面看看s3c2410fb_check_var函數要干些什么事,要說到這個函數,還得提到fb_var_screeninfo結構類型,與它對應的是fb_fix_screeninfo結構類型。這兩個類型分別代表了顯示屏的屬性信息,這些信息可以分為可變屬性信息(如:顏色深度,分辨率等)和不可變的信息(如幀緩沖的其實地址)。既然fb_var_screeninfo表示了可變的屬下信息,那么這些可變信息就應該有一定范圍,否則顯示就會出問題,所以s3c2410fb_check_var函數的功能就是要在LCD的幀緩沖驅動開始運行之前將這些值初始到合法的范圍內。知道了s3c2410fb_check_var要做什么,再去閱讀s3c2410fb_check_var函數的代碼就沒什么問題了。
3.2 s3c2410fb_remove
從這里開始將解釋s3c2410fb_driver中的其他幾個函數。那么就從s3c2410fb_remove開刀吧!顧名思義該函數就該知道,它要將這個platform設備從系統中移除,可以推測它應該釋放掉所有的資源,包括內存空間,中斷線等等。還是按照慣例,在它的實現代碼中一步步的解釋。
static int s3c2410fb_remove(struct platform_device *pdev)
{
struct fb_info *fbinfo = platform_get_drvdata(pdev); /*該函數從platform_device中,到fb_info信息*/
struct s3c2410fb_info *info = fbinfo->par; //得到私有數據
int irq;
s3c2410fb_stop_lcd(info); //該函數停止LCD控制器,實現可以在s3c2410fb.c中找到
msleep(1); //休息以下,等待LCD停止
s3c2410fb_unmap_video_memory(info); //該函數釋放緩沖區
if (info->clk) { //停止時鍾
clk_disable(info->clk);
clk_put(info->clk);
info->clk = NULL;
}
irq = platform_get_irq(pdev, 0); //得到中斷線,以便釋放
free_irq(irq,info); //釋放該中斷
release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD); /* 釋放內存空間 */
unregister_buffer(fbinfo); //向內核注銷該幀緩沖
return 0;
}
3.3 s3c2410fb_suspend與s3c2410fb_resume
在實際的設備,常常可以看到LCD在不需要的時候進入休眠狀態,當需要使用的時候又開始工作,比如手機,在不需要的時候LCD就熄滅,當需要使用的時候LCD又被點亮。從實際中可以看出這對函數非常重要。雖然他們很重要,但不一定很復雜,下面看看它們是怎么樣實現的。
static int s3c2410fb_suspend(struct platform_device *dev, pm_message_t state)
{
struct fb_info *fbinfo = platform_get_drvdata(dev); //這兩條語句好面熟^_^
struct s3c2410fb_info *info = fbinfo->par;
s3c2410fb_stop_lcd(info); //停止LCD
/* sleep before disabling the clock, we need to ensure
* the LCD DMA engine is not going to get back on the bus
* before the clock goes off again (bjd) */
msleep(1); //等待一下,因為LCD停止需要一點時間
clk_disable(info->clk); //關閉LCD的時鍾
return 0;
}
^_^,下面的代碼就不用解釋了吧!
static int s3c2410fb_resume(struct platform_device *dev)
{
struct fb_info *fbinfo = platform_get_drvdata(dev);
struct s3c2410fb_info *info = fbinfo->par;
clk_enable(info->clk);
msleep(1);
s3c2410fb_init_registers(info);
return 0;
}
四、s3c2410fb_ops變量詳解
在上面的文字中,較為詳細的解釋了platform device相關的代碼,通過上面的代碼的執行,一個platform設備(buffer被當作了platform設備)就加載到內核中去了。就像一個PCI的網卡被加入到內核一樣,不同的是PCI的網卡占用的是PCI總線,內核會直接支持它。而對於platform設備需要用上面軟件的方法加載到內核,同PCI網卡一樣,設備需要驅動程序,剛才只是將platform設備注冊到內核中,現在它還需要驅動程序,本節中就來看看這些驅動。
4.1 static struct fb_ops s3c2410fb_ops
對於s3c2410的buffer驅動支持的操作主要有s3c2410fb_ops變量中定義,該變量類型為struct fb_ops,該類型的定義在include/linux/fb.h文件中。它的相關解釋可以在http://www.91linux.com/html/article/kernel/20071204/8805.html頁面中找到,當然在fb.h中也有很詳細的說明。下面看看對於s3c2410的驅動為該buffer提供了哪些操作。
static struct fb_ops s3c2410fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = s3c2410fb_check_var,
.fb_set_par = s3c2410fb_set_par,
.fb_blank = s3c2410fb_blank,
.fb_setcolreg = s3c2410fb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
上面的代碼描述了支持的相關操作,下面主要會解釋s3c2410****的函數,從.fb_fillrect開始的三個函數將不會被提及,當然也可以去看看它們的行為是什么。這里還有一個問題要說明一下,就是s3c2410fb_ops是在什么時候被注冊的,這個問題的答案可以在s3c2410fb_probe函數中找到,請查看s3c2410fb_probe分析的那一小節。
4.2.1 s3c2410fb_check_var
在上面的小節中提到對於一個LCD屏來說內核提供了兩組數據結構來描述它,一組是可變屬性(fb_var_screeninfo描述),另一組是不變屬性(fb_fix_screeninfo描述)。對於可變屬性,應該防止在操作的過程中出現超出法定范圍的情況,因此內核應該可以調用相關函數來檢測、並將這些屬性固定在法定的范圍內,完成這個操作的函數就是s3c2410_check_var。
下面簡單說明一下該函數要做的事情,在這里最好看着fb_var_screeninfo和fb_info的定義。
static int s3c2410fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct s3c2410fb_info *fbi = info->par; //得到驅動的私有數據信息,注意info-par的值
……
/* 下面檢查fb_var_screeninfo的xres和yres的值是否超出法定范圍,如果查出將其設定為正確的值。*/
if (var->yres > fbi->mach_info->yres.max)
var->yres = fbi->mach_info->yres.max;
else if (var->yres < fbi->mach_info->yres.min)
var->yres = fbi->mach_info->yres.min;
if (var->xres > fbi->mach_info->xres.max)
var->yres = fbi->mach_info->xres.max;
else if (var->xres < fbi->mach_info->xres.min)
var->xres = fbi->mach_info->xres.min;
……
/* 羡慕開始檢查bpp(表示用多少位表示一個像素),如果不合法,將其設置正確*/
if (var->bits_per_pixel > fbi->mach_info->bpp.max)
var->bits_per_pixel = fbi->mach_info->bpp.max;
else if (var->bits_per_pixel < fbi->mach_info->bpp.min)
var->bits_per_pixel = fbi->mach_info->bpp.min;
/* 下面的代碼根據bpp設置正確的顏色信息,代碼略 */
……
}
return 0;
}
4.2.2 s3c2410fb_set_par
該函數的主要工作是重新設置驅動的私有數據信息,主要改變的屬性有bpp和行的長度(以字節為單位)。這些屬性值其實是存放在fb_fix_screeninfo結構中的,前面說過這些值在運行基本是不會改變的,這些不可改變的值又可分為絕對不能改變和允許改變的兩種類型,前一種的例子就是幀緩沖區的起始地址,后一種的例子就是在s3c2410fb_set_par函數中提到的屬性。假如應用程序需要修改硬件的顯示狀態之類的操作,這個函數就顯得十分重要。
static int s3c2410fb_set_par(struct fb_info *info)
{
struct s3c2410fb_info *fbi = info->par; //得到私有數據信息
struct fb_var_screeninfo *var = &info->var; //可變的數據屬性
switch (var->bits_per_pixel) //根據bpp設置不變屬性信息的顏色模式
{
case 16:
fbi->fb->fix.visual = FB_VISUAL_TRUECOLOR; //真彩色
break;
case 1:
fbi->fb->fix.visual = FB_VISUAL_MONO01; // 單色
break;
default:
fbi->fb->fix.visual = FB_VISUAL_PSEUDOCOLOR; //偽彩色
break;
}
fbi->fb->fix.line_length = (var->width*var->bits_per_pixel)/8; //修改行長度信息(以字節為單位),計算方法是一行中的(像素總數 * 表達每個像素的位數)/8。
……
s3c2410fb_activate_var(fbi, var); //該函數實際是設置硬件寄存器,解釋略。
return 0;
}
4.2.3 s3c2410fb_blank和s3c2410fb_setcolreg
對於s3c2410fb_blank函數實現的功能非常簡單,而且也有較詳細的說明,因此對它的說明就省略了。s3c2410fb_setcolreg函數的功能是設置顏色寄存器。它需要6個參數,分別代表寄存器編號,紅色,綠色,藍色,透明和fb_info結構。
static int s3c2410fb_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
struct s3c2410fb_info *fbi = info->par; //得到私有數據信息
unsigned int val;
……
switch (fbi->fb->fix.visual) {
case FB_VISUAL_TRUECOLOR: //真彩色,使用了調色板
/* true-colour, use pseuo-palette */
if (regno < 16) {
u32 *pal = fbi->fb->pseudo_palette;
val = chan_to_field(red, &fbi->fb->var.red); //根據顏色值生成需要的數據
val |= chan_to_field(green, &fbi->fb->var.green);
val |= chan_to_field(blue, &fbi->fb->var.blue);
pal[regno] = val;
}
break;
case FB_VISUAL_PSEUDOCOLOR: //偽彩色
if (regno < 256) {
/* 當前假設為 RGB 5-6-5 模式 */
val = ((red >> 0) & 0xf800);
val |= ((green >> 5) & 0x07e0);
val |= ((blue >> 11) & 0x001f);
writel(val, S3C2410_TFTPAL(regno)); //將此值直接寫入寄存器
schedule_palette_update(fbi, regno, val); //相關寄存器
}
break;
default:
return 1; /* unknown type */
}
return 0;
}
到目前為止,整個驅動的主要部分已經解釋完畢了。最后還是得提一下中斷處理函數s3c2410fb_irq,這個函數實現也比較短,它的主要調用了s3c2410fb_write_palette函數將它的功能是將調色板中的數據顯示到LCD上。兩個函數的實現也不難,這里就不再贅述。
OK!good bye everyone!see you next time。