Linux-2.6.20的LCD驅動分析


一、讓LCD顯示可愛的小企鵝
還是先說說環境吧,處理器為S3C2410linux的版本當然是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.csmdk2410_map_io函數中調用s3c24xx_fb_set_platdata( ),具體為:
s3c24xx_fb_set_platdata(&smdk2410_lcd_platdata);
注:此處未采用內核中提供的源函數,因為系統會崩潰,估計是它調用kmalloc函數引起的。

3. make menuconfig的時候配置Linuxlogo選項,然后的時候在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_moduleinitfn的別名,或者init_moduleinitfn的一個連接,再簡單一點說這個時候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.20cs8900驅動分析()這篇文章中有對它的揭秘。
上面啰嗦了這么多,最終是要說明只要用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_resources3c_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_deviceplatform_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_probes3c2410fb_remove等等。通過platform_driver_register函數注冊該設備的過程中,它會回調.probe函數,說到這里也就明白s3c2410fb_probe是在platform_driver_registe中回調的。到目前為止,經過二萬五千里長征終於到達s3c2410fb_probeLCD的驅動程序)了。

2.3 s3c2410fb_probe揭秘
對於該函數,我想最好的辦法就是跟着程序一步一步的解釋。OKlet’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_infos3c2410fb_info結構,簡單地說前者只是用於描述LCD初始化時所用的值,而后者是描述整個LCD驅動的結構體。s3c2410fb_mach_infoinclude/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 resourceflags域,得到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_infopar域指向該私有空間。*/
       if (!fbinfo) {
              return -ENOMEM;
       }

//以下開始做正經事了,填充fbinfo了。
       info = fbinfo->par;   //你中有我,我中有你!
       info->fb = fbinfo;
       platform_set_drvdata(pdev, fbinfo);           /*該函數的實現非常簡單,實際的操作為:pdev->dev.driver_data fbinfodevice結構的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_suspends3c2410fb_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。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM