10. LCD驅動程序 ——框架分析

引言:html

由LCD的硬件原理及操做(可參看韋哥博客:第017課 LCD原理詳解及裸機程序分析node

咱們知道只要LCD控制器的相關寄存器正確配置好,就能夠在LCD面板上顯示framebuffer中的內容。linux

若應用程序須要在LCD屏幕上顯示文字或圖像時,只須要把相應的顯示內容以正確的格式寫到Framebuffer中便可。c++

(Framebuffer,中文名字是幀緩衝,這個幀也就是一副圖像所須要的數據。所以,幀緩衝其實就是LCD設備的驅動程序)算法

 

一.LCD驅動程序框架數組

根據上述思路,Linux LCD 驅動程分爲兩個層次,以下圖所示數據結構

相似於Platform 平臺驅動框架,也將驅動程序分爲相對穩定的算法驅動,即fb總線驅動,與易變的設備驅動,即fb設備驅動框架

1.底層爲LCD硬件驅動層ide

  負責對LCD硬件相關寄存器進行初始化;函數

2.上層爲幀緩衝區層

  主要用來爲應用程序提供操做LCD屏的接口,應用程序要在LCD上顯示時,只需把內容寫到幀緩衝區中便可。

  在幀緩衝區層,主要把內核空間的一塊內存虛擬爲一個字符設備,並實現文件接口操做函數(open/read/write)

     而後把幀緩衝註冊爲一個字符設備,這樣在應用層就能夠像訪問普通字符設備同樣來訪問幀緩衝,從而實現顯示。

 

2、驅動源碼分析

以s3c2440 CPU爲例:

FrameBuffer設備驅動基於以下兩個文件:

  1) linux/include/linux/fb.h

  2) linux/drivers/video/fbmem.c

下面分析這兩個文件。

一)fb.h

   幀緩衝主要的數據結構幾乎都是在這個中文件定義的。這些結構包括:

1)fb_var_screeninfo

2) fb_fix_screeninfon

3) fb_cmap

4) fb_info

5) struct fb_ops

6) structure map

詳見:【Linux開發】全面的framebuffer詳解

 

二).fbmem.c

  fbmem.c 處於Framebuffer設備驅動技術的中心位置.它爲上層應用程序提供系統調用,

       也爲下一層的特定硬件驅動提供接口;那些底層硬件驅動須要用到這兒的接口來向系統內核註冊它們本身.

  fbmem.c 爲全部支持FrameBuffer的設備驅動提供了通用的接口,避免重複工做.

  內核中的frambuffer在drivers/video/fbmem.c

1.  進入fbmem.c找到它的入口函數:

 1 fbmem_init(void)  2 {  3     create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);  4 
 5     if (register_chrdev(FB_MAJOR,"fb",&fb_fops))  //建立字符設備  6         printk("unable to get major %d for fb devs\n", FB_MAJOR);  7 
 8     fb_class = class_create(THIS_MODULE, "graphics"); //建立類  9     if (IS_ERR(fb_class)) { 10         printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); 11         fb_class = NULL; 12  } 13     return 0; 14 }
(1)create_proc_read_entry在/proc下也會有fb文件

         

 

  (2)建立字符設備"fb", FB_MAJOR=29,主設備號爲29,咱們cat /proc/devices 也能找到這個字符設備:

   與以前的驅動程序同樣,可是沒有使用建立設備節點,爲何?

   由於須要註冊了LCD驅動後,纔會有設備節點

        

 (3)class_create 註冊了一個類 graphics, 具體的設備文件不在此處建立

2.進入結構體fb_fops

  此處註冊的是字符設備驅動,結構爲默認的file_operations = fb_fops, 從 open = fb_open 開始分析

  分析一下應用層是如何打開驅動、讀取驅動數據

  2.1 fb_open函數以下:

 1 static int fb_open(struct inode *inode, struct file *file)  2 {  3        int fbidx = iminor(inode);      //獲取設備節點的次設備號
 4        struct fb_info *info;           //定義fb_info結構體,其中包含幀緩衝相關信息
 5        int res = 0;  6  ... ...  7 
 8 if (!(info = registered_fb[fbidx]))   //(1) info= registered_fb[fbidx],獲取此設備號的lcd驅動信息
 9  try_to_load(fbidx); 10  ... ... 11 
12        if (info->fbops->fb_open) { ////判斷此結構體中是否有fb_open 13               res = info->fbops->fb_open(info,1);  //調用registered_fb[fbidx]->fbops->fb_open
14               if (res) 15                      module_put(info->fbops->owner); 16  } 17 
18        return res; 19 }

   1).registered_fb[fbidx] 這個數組也是fb_info結構體,其中fbidx等於次設備號id,顯然這個數組就是保存咱們各個lcd驅動的信息。

   2).根據次設備號在 registered_fb 中尋找對應的 fb_info中的 fb_ops中的open,有就調用,無則返回

#define FB_MAX 32  //次設備號最大爲,最多支持32個設備
extern
struct fb_info *registered_fb[FB_MAX];32fb

 

  2.1 fb_read函數以下:

 1 static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)  2 {  3        unsigned long p = *ppos;  4        struct inode *inode = file->f_path.dentry->d_inode;  5        int fbidx = iminor(inode);                       //獲取次設備號
 6        struct fb_info *info = registered_fb[fbidx];     //獲取次設備號的lcd驅動的信息
 7        u32 *buffer, *dst;  8        u32 __iomem *src;  9        int c, i, cnt = 0, err = 0; 10        unsigned long total_size; 11  ... ... 12        if (info->fbops->fb_read)  //若是自定義了驅動層的read,則調用自定義的,不然調用默認的 13               return info->fbops->fb_read(info, buf, count, ppos); 14      
15        total_size = info->screen_size;     //獲取屏幕長度
16     
17  ... ... 18     
19        buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL); //分配緩衝區 
20        if (!buffer) 21               return -ENOMEM;
24 
25        src = (u32 __iomem *) (info->screen_base + p);         //獲取顯存物理基地址
26        if (info->fbops->fb_sync) 27               info->fbops->fb_sync(info); 28 
29        while (count) { 30               c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;   //獲取頁地址
31               dst = buffer; 32 
33          /*由於src是32位,一個src等於4個字節,因此頁地址c >> 2*/
34               for (i = c >> 2; i--; ) 35                      *dst++ = fb_readl(src++);    //讀取顯存每一個像素點數據,放到dst地址上
36 
37               if (c & 3) { 38                      u8 *dst8 = (u8 *) dst; 39                      u8 __iomem *src8 = (u8 __iomem *) src; 40                      for (i = c & 3; i--;) 41                             *dst8++ = fb_readb(src8++); 42                      src = (u32 __iomem *) src8; 43  } 44               if (copy_to_user(buf, buffer, c)) {  //上傳數據,長度等於頁地址大小
45                      err = -EFAULT; 46                      break; 47  } 48               *ppos += c; 49               buf += c; 50               cnt += c; 51               count -= c; 52  } 53  kfree(buffer); 54        return (err) ? err : cnt; 55 }

  從.open和.write函數中能夠發現,都依賴於fb_info幀緩衝信息結構體,它從registered_fb[fbidx]數組中獲得,這個數組保存咱們各個lcd驅動的信息

 

.3.registered_fb[fbidx]數組在哪裏被註冊,位於register_framebuffer():

 1 int register_framebuffer(struct fb_info *fb_info)  2 {  3  ... ...  4 for (i = 0 ; i < FB_MAX; i++)    //查找空的數組
 5         if (!registered_fb[i])  6          break;  7 
 8 fb_info->node = i;  9  ... ... 10 
11 /*建立設備節點,名稱爲fdi,主設備號爲29,次設備號爲i */
12 fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i); 13  ... ... 14 
15 registered_fb[i] = fb_info; 16  ... ... 17 }  

  register_framebuffer()除了註冊fb_info,還建立了設備節點,底層驅動程序即設備驅動,經過調用register_framebuffer來設置硬件

  因此要註冊驅動時就調用這個,以下圖所示:

 

4.再來看看/drivers/video/s3c2410fb.c 中又是怎麼實現驅動的

  4.1先找到入口出口函數:

1 int __devinit s3c2410fb_init(void) 2 { 3      return platform_driver_register(&s3c2410fb_driver); 4 } 5 
6 static void __exit s3c2410fb_cleanup(void) 7 { 8      platform_driver_unregister(&s3c2410fb_driver); 9 }

 入口函數中,註冊LCD平臺設備驅動的數據結構體到平臺總線上。出口函數則卸載。

  

  4.2 來看看平臺設備驅動 s3c2410fb_driver 如何定義的

 1 static struct platform_driver s3c2410fb_driver = {  2        .probe           = s3c2410fb_probe,    //檢測函數,註冊設備
 3        .remove         = s3c2410fb_remove,    //刪除設備
 4        .suspend = s3c2410fb_suspend,          //休眠
 5        .resume          = s3c2410fb_resume,   //喚醒
 6        .driver            = {  7               .name     = "s3c2410-lcd",       //drv名字
 8               .owner    = THIS_MODULE,  9  }, 10 };

  當有對應的設備註冊到平臺總線上時,就會根據設備名(s3c2410-lcd)或ID,找到相應的設備驅動,調用probe函數來探測設備。

  4.2 再進入probe函數看看它的處理

  先看看函數傳入的參數  s3c2410fb_probe (struct platform_device *pdev)

 1 //arch\arm\plat-s3c24xx\devs.c
 2 struct platform_device s3c_device_lcd = {  3     .name          = "s3c2410-lcd", //設備名稱  4     .id            = -1, //設備ID  5     .num_resources = ARRAY_SIZE(s3c_lcd_resource),  6     .resource      = s3c_lcd_resource,  7     .dev           = {  8        .dma_mask      = &s3c_device_lcd_dmamask,  9        .coherent_dma_mask    = 0xffffffffUL
10  } 11 };

 

 1 static int __init s3c2410fb_probe(struct platform_device *pdev)  2 {  3        struct s3c2410fb_info *info; //定義指向s3c2410fb_info的結構體指針  4        struct fb_info     *fbinfo; //定義指向fb_info的結構體指針  5        struct s3c2410fb_hw *mregs;  6        int ret;  7        int irq;  8        int i;  9  u32 lcdcon1; 10  
11        mach_info = pdev->dev.platform_data;     //獲取LCD設備信息(長寬、類型等)
12 
13        if (mach_info == NULL) { 14               dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n"); 15               return -EINVAL; 16  } 17        mregs = &mach_info->regs; 18 
19 
20        irq = platform_get_irq(pdev, 0); 21        if (irq < 0) { 22               dev_err(&pdev->dev, "no irq for device\n"); 23               return -ENOENT; 24  } 25 
27     /* 1. 分配一個s3c2410fb_info結構體給fbinfo*/
28        fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
29        if (!fbinfo) { 30               return -ENOMEM; 31  }
34 
35      /*2.設置fb_info*/
36        info = fbinfo->par; //par成員用來存放幀緩衝的私有數據,此處爲LCD控制器 37        info->fb = fbinfo; 38        info->dev = &pdev->dev; 39  ... ... 40 
41     /*3.硬件相關的操做,設置中斷,LCD時鐘頻率,顯存地址, 配置引腳... ...*/
42        ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); //設置中斷
43        info->clk = clk_get(NULL, "lcd");             //獲取時鐘
44        clk_enable(info->clk);                        //使能時鐘
45        ret = s3c2410fb_map_video_memory(info);       //顯存地址 
46        ret = s3c2410fb_init_registers(info);         //設置寄存器,配置引腳
47  ... ... 48    
      /* 4.註冊一個fb_info結構體,裏面包含幀緩衝的相關信息 */ 49 ret = register_framebuffer(fbinfo); 50 if (ret < 0) { 51 printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); 52 goto free_video_memory; 53 } 54 ... ... 55 return ret; 56 }
完成了幀緩衝變量struct s3c2410fb_info初始化以後,調用fbmem.c的接口,即第49行,經過register_framebuffer註冊fb_info結構體後,
會根據次設備號將fb_info存入registered_fb[fbidx]數組中,
這樣操做函數就能夠經過次設備號找到數組中對應的設備信息,進行操做。
參考一下框圖

 

 

總結

參照來設計這個總線下的平臺驅動,咱們這裏不使用設計,
而是直接寫驅動.參考來進行初始化設置drivers\video\s3c2410fb.cfbplatformplatforms3c2410fb_probe

由上可知要寫個LCD驅動程序,須要如下4步:

1) 分配一個fb_info結構體: framebuffer_alloc();

2) 設置fb_info

3) 硬件相關的操做(設置中斷,LCD時鐘頻率,顯存地址, 配置引腳... ...)

4 註冊fb_info: register_framebuffer()

 

下一節寫LCD驅動程序

參考:

15.linux-LCD層次分析(詳解)

lcd驅動框架

Linux的幀緩衝設備

【Linux開發】全面的framebuffer詳解

深刻理解嵌入式Linux設備驅動程序
相關文章
相關標籤/搜索