1. 程式人生 > >10. LCD驅動程式 ——框架分析 第017課 LCD原理詳解及裸機程式分析 15.linux-LCD層次分析(詳解)

10. LCD驅動程式 ——框架分析 第017課 LCD原理詳解及裸機程式分析 15.linux-LCD層次分析(詳解)

引言:

由LCD的硬體原理及操作(可參看韋哥部落格:第017課 LCD原理詳解及裸機程式分析

我們知道只要LCD控制器的相關暫存器正確配置好,就可以在LCD面板上顯示framebuffer中的內容。

若應用程式需要在LCD螢幕上顯示文字或影象時,只需要把相應的顯示內容以正確的格式寫到Framebuffer中即可。

(Framebuffer,中文名字是幀緩衝,這個幀也就是一副影象所需要的資料。因此,幀緩衝其實就是LCD裝置的驅動程式)

 

一.LCD驅動程式框架

根據上述思路,Linux LCD 驅動程分為兩個層次,如下圖所示

類似於Platform 平臺驅動框架,也將驅動程式分為相對穩定的演算法驅動,即fb匯流排驅動,與易變的裝置驅動,即fb裝置驅動

1.底層為LCD硬體驅動層

  負責對LCD硬體相關暫存器進行初始化;

2.上層為幀緩衝區層

  主要用來為應用程式提供操作LCD屏的介面,應用程式要在LCD上顯示時,只需把內容寫到幀緩衝區中即可。

  在幀緩衝區層,主要把核心空間的一塊記憶體虛擬為一個字元裝置,並實現檔案介面操作函式(open/read/write)

     然後把幀緩衝註冊為一個字元裝置,這樣在應用層就可以像訪問普通字元裝置一樣來訪問幀緩衝,從而實現顯示。

 

二、驅動原始碼分析

以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,最多支援32個fb裝置
extern
struct fb_info *registered_fb[FB_MAX];

 

  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.c來設計這個fb匯流排下的platform平臺驅動,我們這裡不使用platform設計,
而是直接寫驅動.參考s3c2410fb_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裝置驅動程式