Linux驅動:LCD驅動框架分析


一直想花時間來整理一下Linux內核LCD驅動,卻一直都忙着做其他事情去了,這些天特意抽出時間來整理之前落下的筆記,故事就這樣開始了。LCD驅動也是字符設備驅動的一種,框架上相對於字符設備驅動稍微復雜一點點,真的就是一點點,難點在對LCD硬件的配置上。

開發平台:TQ210,S5PV210處理器

內核版本:linux-3.10.46

LCD型號:AT070TN92,7英寸,TFT屏,分辨率800x480x3(RGB),24位真彩色

一、框架分析

上圖說明:①內核裝載LCD驅動模塊:設置並注冊fb_info結構,初始化LCD硬件。②APP打開LCD設備,獲取設備文件,根據設備文件進行讀寫顯存。③在內核中,根據主設備號和次設備號定位一個fb_info結構,如果應用層的系統調用是讀操作則調用fb_ops中對應的操作函數,寫操作也是一樣。

主要數據結構分析:

 1 struct fb_info {
 2     int node;                        //用作次設備號索引
 3     int flags;
 4     struct mutex lock;                //用於open/release/ioctl函數的鎖
 5     struct fb_var_screeninfo var;    //可變參數,重點
 6     struct fb_fix_screeninfo fix;    //固定參數,重點
 7     struct fb_monspecs monspecs;    //顯示器標准
 8     struct work_struct queue;        //幀緩沖區隊列
 9     struct fb_pixmap pixmap;        //圖像硬件映射
10     struct fb_pixmap sprite;        //光標硬件映射
11     struct fb_cmap cmap;            //當前顏色表
12     struct list_head modelist;      //模式鏈表
13     struct fb_videomode *mode;        //當前video模式
14 
15     char __iomem *screen_base;        //顯存基地址
16     unsigned long screen_size;        //顯存大小
17     void *pseudo_palette;            //偽16色顏色表
18 #define FBINFO_STATE_RUNNING    0
19 #define FBINFO_STATE_SUSPENDED    1
20     u32 state;                        //硬件狀態,如掛起
21     void *fbcon_par;                //用作私有數據區
22     void *par;                        //info->par指向了額外多申請內存空間的首地址
23 };
fb_info
 1 struct fb_ops {
 2     struct module *owner;
 3     int (*fb_open)(struct fb_info *info, int user);
 4     int (*fb_release)(struct fb_info *info, int user);
 5     ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos);
 6     ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos);
 7 
 8     /* 檢測可變參數,並調整到支持的值 */
 9     int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
10 
11     /* 根據 info->var 設置 video 模式 */
12     int (*fb_set_par)(struct fb_info *info);
13 
14     /* set color register */
15     int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);
16     /* set color registers in batch */
17     int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
18     /* blank display */
19     int (*fb_blank)(int blank, struct fb_info *info);
20     /* pan display */
21     int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
22 
23     /* Draws a rectangle */
24     void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
25     /* Copy data from area to another */
26     void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
27     /* Draws a image to the display */
28     void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
29 
30     ......
31 
32     /* perform fb specific ioctl (optional) */
33     int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
34             unsigned long arg);
35     /* perform fb specific mmap */
36     int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
37 
38     ......
39 };
fb_ops

主要操作代碼分析:

 1 fb_open
 2 {
 3     int fbidx = iminor(inode);         //獲取次設備號
 4     struct fb_info *info;
 5     info = get_fb_info(fbidx);
 6         struct fb_info *fb_info;
 7         fb_info = registered_fb[fbidx];//根據次設備號從已注冊的fb_info數組中獲取響應的結構
 8         return fb_info;
 9     ......
10     /* 
11      * 從registered_fb[]數組項里找到fb_info結構體后,將其保存到 
12      * struct file結構中的私有信息成員,難道這是為了以后在某些情況方便找到並調用??先放着...
13      * 回過來發現:這樣做是為了驗證在read、write、ioctl等系統調用中獲得的fb_info結構和open獲得的是否一樣
14      */  
15     file->private_data = info;
16     //info->fbops->fb_open無定義,這是值得思考的問題!
17     if (info->fbops->fb_open) {
18         res = info->fbops->fb_open(info,1);
19         if (res)
20             module_put(info->fbops->owner);
21     }
22     ......
23 }
fb_open
 1 fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
 2 {
 3     struct fb_info *info = file_fb_info(file);
 4         struct inode *inode = file_inode(file);
 5         int fbidx = iminor(inode);
 6         //也是根據次設備號來獲取fb_info結構
 7         struct fb_info *info = registered_fb[fbidx];
 8     
 9         if (info != file->private_data)
10             info = NULL;
11         return info;
12     //無定義
13     if (info->fbops->fb_read)
14         return info->fbops->fb_read(info, buf, count, ppos);
15     //獲得顯存的大小
16     total_size = info->screen_size;
17     //如果應用層要讀的數據count比實際最大的顯存還要大,修改count值為最大顯存值
18     if (count >= total_size)
19         count = total_size;
20     //分配顯存,最大只能是一頁PAGE_SIZE=4KB
21     buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);
22     //要讀的源地址:顯存虛擬基地址+偏移
23     src = (u8 __iomem *) (info->screen_base + p);
24     while (count) {
25         c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
26         //讀的目的地址
27         dst = buffer;
28         //讀操作:拷貝數據
29         fb_memcpy_fromfb(dst, src, c);
30         dst += c;
31         src += c;
32 
33         if (copy_to_user(buf, buffer, c)) {
34             err = -EFAULT;
35             break;
36         }
37         *ppos += c;
38         buf += c;
39         cnt += c;
40         count -= c;
41     }
42     kfree(buffer); //釋放buffer,只起到臨時中轉站的作用
43 }
fb_read
static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    struct fb_info *info = file_fb_info(file); //獲取fb_info結構
/************************************************************
    函數跟進分析:
    static struct fb_info *file_fb_info(struct file *file)
    {
        struct inode *inode = file_inode(file); 
        int fbidx = iminor(inode);                   //獲取次設備號
        struct fb_info *info = registered_fb[fbidx]; //根據次設備號獲取相應的fb_info結構
    
        if (info != file->private_data)
            info = NULL;
        return info;  //返回fb_info結構
    }
************************************************************/
    u8 *buffer, *src;
    u8 __iomem *dst;
    int c, cnt = 0, err = 0;
    unsigned long total_size;
    //獲取fb_info失敗或者fb_info結構中沒有設置顯存基址,返回
    if (!info || !info->screen_base)
        return -ENODEV;

    if (info->state != FBINFO_STATE_RUNNING)
        return -EPERM;
    //如果幀緩沖操作函數結構中有重定義fb_write函數,優先使用!實際上沒有。
    if (info->fbops->fb_write)
        return info->fbops->fb_write(info, buf, count, ppos);
    //獲取顯存大小
    total_size = info->screen_size;

    if (total_size == 0)
        total_size = info->fix.smem_len;
    //如果寫偏移位置p比整個顯存還要大,出錯返回。
    if (p > total_size)
        return -EFBIG;

    if (count > total_size) {
        err = -EFBIG;
        count = total_size;
    }

    if (count + p > total_size) {
        if (!err)
            err = -ENOSPC;

        count = total_size - p;
    }
    //內核空間分配臨時幀緩沖區
    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;
    //計算寫目的地址(虛擬地址:內核空間中能夠操作的也就是虛擬地址)
    dst = (u8 __iomem *) (info->screen_base + p);

    if (info->fbops->fb_sync)
        info->fbops->fb_sync(info);

    while (count) {
        c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
        //源地址
        src = buffer;

        if (copy_from_user(src, buf, c)) {
            err = -EFAULT;
            break;
        }
        // 從內存buffer拷貝數據到幀緩沖區
        fb_memcpy_tofb(dst, src, c);
        dst += c;
        src += c;
        *ppos += c;
        buf += c;
        cnt += c;
        count -= c;
    }

    kfree(buffer);
    return (cnt) ? cnt : err;
}
fb_write
 1 /* 
 2  * 函數功能:將內核空間分配的物理顯存空間映射到用戶空間中 
 3  * 用戶空間就能訪問這段內存空間了
 4  */  
 5 static int fb_mmap(struct file *file, struct vm_area_struct * vma)
 6 {
 7     struct fb_info *info = file_fb_info(file);
 8     struct fb_ops *fb;
 9     unsigned long mmio_pgoff;
10     unsigned long start;
11     u32 len;
12 
13     if (!info)
14         return -ENODEV;
15     fb = info->fbops;
16     if (!fb)
17         return -ENODEV;
18     mutex_lock(&info->mm_lock);
19     //如果fb_info->fbops->fb_mmap存在就調用該函數,實際中沒有!
20     if (fb->fb_mmap) {
21         int res;
22         res = fb->fb_mmap(info, vma);
23         mutex_unlock(&info->mm_lock);
24         return res;
25     }
26     /*
27     * fb緩沖內存的開始位置(物理地址)
28     * info->fix.smem_start這個地址是在哪里被設置的?
29     * 在驅動程序xxx_lcd_init()函數中:
30     * clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);
31     * dma_alloc_writecombine函數返回的是內核虛擬起始地址,同時第3個參數fix.smem_start會被設置成對應的物理起始地址。
32     * 內核中操作這個分配的空間只能操作虛擬的地址空間!!!
33     * dma_alloc_writecombine函數的調用只是把物理顯存映射到內核空間,並沒有映射到用戶空間,因此用戶在操作物理顯存之前要先把
34     * 物理顯存空間映射到用戶可見的用戶空間中來,這就是該函數的意義所在。
35     */
36     start = info->fix.smem_start; 
37     //幀緩沖長度
38     len = info->fix.smem_len;
39     mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
40     if (vma->vm_pgoff >= mmio_pgoff) {
41         if (info->var.accel_flags) {
42             mutex_unlock(&info->mm_lock);
43             return -EINVAL;
44         }
45 
46         vma->vm_pgoff -= mmio_pgoff;
47         start = info->fix.mmio_start;
48         len = info->fix.mmio_len;
49     }
50     mutex_unlock(&info->mm_lock);
51 
52     vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
53     fb_pgprotect(file, vma, start);
54     //映射物理內存到用戶空間虛擬地址
55     return vm_iomap_memory(vma, start, len);
56 }
fb_mmap

問題思考:

問1.什么叫幀緩沖區,他有哪些特性指標?

答1.對於應用層來說,顯示圖像到LCD設備就相當於往“一塊內存”中寫入數據,獲取LCD設備上的圖像就相當於拷貝“這塊內存”中的數據。因此,LCD就和“一塊內存”一樣,專業一點術語叫幀緩沖區,它和普通的內存不太一樣,除了可以“讀寫”操作之外還可以進行其他操作和功能設置,特性指標就是LCD的特性指標。在內核中,一個LCD顯示器就相當於一個幀緩沖設備,對應一個fb_info結構。

問2.為什么要通過 registered_fb[] 數組來找到對應的 fb_info 結構體?

答2.通過對上邊這幾個函數的剖析發現,不管是fb_read、fb_write、fb_ioctl、fb_mmap系統調用,都是通過次設備號在已注冊的fb_info結構數組中找到匹配的那一個結構之后,判斷其中的fbops結構中的操作函數是否有定義,有的話就優先調用該函數,沒有就使用往下的方案策略。這樣的好處就是多個相同的LCD設備可以使用同一套代碼,減少代碼的重復性,同時對於需要特殊定義的函數又可以方便實現重定義。

問3.這個數組在哪里被注冊?

答3.在register_framebuffer()函數中被注冊 register_framebuffer(struct fb_info *fb_info) ret = do_register_framebuffer(fb_info); ...... registered_fb[i] = fb_info; ......

問4.fb_mmap()函數在什么場合使用?

答4.在用戶空間中通過mmap()函數來進行系統調用,該函數執行成功返回的是指向被映射的幀緩沖區的指針,這樣用戶直接可以通過該指針來讀寫緩沖區。

問5.在用戶程序中調用write函數和直接使用mmap函數返回的fbp指針有什么不一樣?

答5.用戶空間使用fbp指針操作的地址是用戶空間和物理顯存空間直接映射的關系,而使用write是將用戶中的數據拷貝到內核空間,然后再將這些數據寫到內核中已映射的虛擬地址空間中;write是操作整個fb,而fbp只操作一個像素點。

二、驅動代碼編寫

  1 #include <linux/module.h>
  2 #include <linux/kernel.h>
  3 #include <linux/errno.h>
  4 #include <linux/string.h>
  5 #include <linux/mm.h>
  6 #include <linux/slab.h>
  7 #include <linux/delay.h>
  8 #include <linux/fb.h>
  9 #include <linux/init.h>
 10 #include <linux/dma-mapping.h>
 11 #include <linux/interrupt.h>
 12 #include <linux/platform_device.h>
 13 #include <linux/clk.h>
 14 #include <linux/workqueue.h>
 15 
 16 #include <asm/io.h>
 17 #include <asm/div64.h>
 18 #include <asm/uaccess.h>
 19 
 20 #include <asm/mach/map.h>
 21 #include <mach/regs-gpio.h>
 22 #include <linux/fb.h>
 23 
 24 #define VSPW        9   //4
 25 #define VBPD        13  //17
 26 #define LINEVAL     479  
 27 #define VFPD        21  //26
 28 
 29 #define HSPW        19    //4
 30 #define HBPD        25   //40
 31 #define HOZVAL      799   
 32 #define HFPD        209   //214
 33 
 34 #define LeftTopX    0
 35 #define LeftTopY    0
 36 #define RightBotX   799
 37 #define RightBotY   479
 38 
 39 static struct fb_info *clb_fbinfo;
 40 
 41 /* LCD GPIO Pins */
 42 static long unsigned long *gpf0con;
 43 static long unsigned long *gpf1con;
 44 static long unsigned long *gpf2con;
 45 static long unsigned long *gpf3con;
 46 static long unsigned long *gpd0con;
 47 static long unsigned long *gpd0dat;
 48 static long unsigned long *display_control;
 49 
 50 /* LCD Controler Pins */
 51 struct s5pv210_lcd_regs{
 52     volatile unsigned long vidcon0;
 53     volatile unsigned long vidcon1;
 54     volatile unsigned long vidcon2;
 55     volatile unsigned long vidcon3;
 56     
 57     volatile unsigned long vidtcon0;
 58     volatile unsigned long vidtcon1;
 59     volatile unsigned long vidtcon2;
 60     volatile unsigned long vidtcon3;
 61     
 62     volatile unsigned long wincon0;
 63     volatile unsigned long wincon1;
 64     volatile unsigned long wincon2;
 65     volatile unsigned long wincon3;
 66     volatile unsigned long wincon4;
 67     
 68     volatile unsigned long shadowcon;
 69     volatile unsigned long reserve1[2];
 70     
 71     volatile unsigned long vidosd0a;
 72     volatile unsigned long vidosd0b;
 73     volatile unsigned long vidosd0c;
 74 };
 75 
 76 struct clk      *lcd_clk;
 77 static struct s5pv210_lcd_regs *lcd_regs;
 78 
 79 static long unsigned long *vidw00add0b0;
 80 static long unsigned long *vidw00add1b0;
 81 
 82 static u32  pseudo_palette[16];
 83 
 84 /* from pxafb.c */
 85 static  unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
 86 {
 87     chan &= 0xffff;
 88     chan >>= 16 - bf->length;
 89     return chan << bf->offset;
 90 }
 91 
 92 static int  clb210_lcdfb_setcolreg(unsigned regno,
 93                    unsigned red, unsigned green, unsigned blue,
 94                    unsigned transp, struct fb_info *info)
 95 {
 96     unsigned int val;
 97     
 98     if (regno > 16) 
 99         return 1;
100     
101     /* 用red,green,blue三原色構造出val */
102     val  = chan_to_field(red,   &info->var.red);
103     val |= chan_to_field(green, &info->var.green);
104     val |= chan_to_field(blue,  &info->var.blue);
105 
106     pseudo_palette[regno] = val;
107         
108     return 0;    
109 }
110 
111 //幀緩沖操作函數
112 static struct fb_ops clb210_lcdfb_ops = 
113 {
114     .owner            = THIS_MODULE,
115     .fb_setcolreg    = clb210_lcdfb_setcolreg, //設置color寄存器和調色板
116     //下面這3個函數是通用的
117     .fb_fillrect    = cfb_fillrect,  //畫一個矩形
118     .fb_copyarea    = cfb_copyarea,  //數據拷貝
119     .fb_imageblit    = cfb_imageblit, //圖像填充
120 };
121 
122 static int  __init clb210_lcd_init(void)
123 {
124     /* 1.分配一個fb_info */
125     clb_fbinfo = framebuffer_alloc(0 , NULL);
126 
127     /* 2. 設置 */
128     /* 2.1 設置固定的參數 */
129     strcpy(clb_fbinfo->fix.id, "clb210_lcd");
130     clb_fbinfo->fix.smem_len = 800 * 480 * 32/8;
131     clb_fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
132     clb_fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
133     clb_fbinfo->fix.line_length = 800 * 32/8;
134 
135     /* 2.2 設置可變的參數 */
136     clb_fbinfo->var.xres = 800;
137     clb_fbinfo->var.yres = 480;
138     clb_fbinfo->var.xres_virtual   = 800;
139     clb_fbinfo->var.yres_virtual   = 480;
140     clb_fbinfo->var.bits_per_pixel = 32;
141     
142     /*RGB:888*/
143     clb_fbinfo->var.red.offset = 16;
144     clb_fbinfo->var.red.length = 8;
145     
146     clb_fbinfo->var.green.offset = 8;
147     clb_fbinfo->var.green.length = 8;
148     
149     clb_fbinfo->var.blue.offset = 0;
150     clb_fbinfo->var.blue.length = 8;
151     
152     clb_fbinfo->var.activate = FB_ACTIVATE_NOW    ;
153 
154     /* 2.3 設置操作函數 */
155     clb_fbinfo->fbops = &clb210_lcdfb_ops;
156     
157     /* 2.4 其他的設置 */
158     /* 2.4.1 設置顯存的大小 */
159     clb_fbinfo->screen_size =  800 * 480 * 32/8;    
160 
161     /* 2.4.2 設置調色板 */
162     clb_fbinfo->pseudo_palette = pseudo_palette;
163 
164     /* 2.4.3 設置顯存的虛擬起始地址 */
165     clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,
166             clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);
167     
168 
169     /* 3. 硬件相關的操作 */
170     /* 3.1 獲取lcd時鍾,使能時鍾 */
171     lcd_clk = clk_get(NULL, "lcd");
172     if (!lcd_clk || IS_ERR(lcd_clk)) {
173         printk(KERN_INFO "failed to get lcd clock source\n");
174     }
175     clk_enable(lcd_clk);
176 
177     /* 3.2 配置GPIO用於LCD */
178     gpf0con = ioremap(0xE0200120, 4);
179     gpf1con = ioremap(0xE0200140, 4);
180     gpf2con = ioremap(0xE0200160, 4);
181     gpf3con = ioremap(0xE0200180, 4);
182     gpd0con = ioremap(0xE02000A0, 4);
183     gpd0dat = ioremap(0xE02000A4, 4);
184     
185     gpd0con      = ioremap(0xE02000A0, 4);  
186     gpd0dat      = ioremap(0xE02000A4, 4);  
187       
188     vidcon0      = ioremap(0xF8000000, 4);  
189     vidcon1      = ioremap(0xF8000004, 4);  
190     vidtcon0     = ioremap(0xF8000010, 4);  
191     vidtcon1     = ioremap(0xF8000014, 4);  
192     vidtcon2     = ioremap(0xF8000018, 4);  
193     wincon0      = ioremap(0xF8000020, 4);  
194     vidosd0a     = ioremap(0xF8000040, 4);  
195     vidosd0b     = ioremap(0xF8000044, 4);  
196     vidosd0c     = ioremap(0xF8000048, 4);  
197     vidw00add0b0 = ioremap(0xF80000A0, 4);  
198     vidw00add1b0 = ioremap(0xF80000D0, 4);  
199     shodowcon    = ioremap(0xF8000034, 4); 
200     
201     display_control = ioremap(0xe0107008, 4);
202 
203     /* 設置相關GPIO引腳用於LCD */
204     *gpf0con = 0x22222222;
205     *gpf1con = 0x22222222;
206     *gpf2con = 0x22222222;
207     *gpf3con = 0x22222222;
208 
209     /* 使能LCD本身 */
210     *gpd0con |= 1<<4;
211     *gpd0dat |= 1<<1;
212 
213     /* 顯示路徑的選擇, 0b10: RGB=FIMD I80=FIMD ITU=FIMD */
214     *display_control  = 2<<0;
215 
216     /* 3.3 映射LCD控制器對應寄存器 */    
217     lcd_regs = ioremap(0xF8000000, sizeof(struct s5pv210_lcd_regs));    
218     vidw00add0b0 = ioremap(0xF80000A0, 4);
219     vidw00add1b0 = ioremap(0xF80000D0, 4);
220     
221     lcd_regs->vidcon0 &= ~((3<<26) | (1<<18) | (0xff<<6)  | (1<<2));   
222     lcd_regs->vidcon0 |= ((5<<6) | (1<<4) );
223 
224     lcd_regs->vidcon1 &= ~(1<<7);            /* 在vclk的下降沿獲取數據 */
225     lcd_regs->vidcon1 |= ((1<<6) | (1<<5));  /* HSYNC極性反轉, VSYNC極性反轉 */
226 
227     lcd_regs->vidtcon0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0);
228     lcd_regs->vidtcon1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0);
229     lcd_regs->vidtcon2 = (LINEVAL << 11) | (HOZVAL << 0);
230     lcd_regs->wincon0 &= ~(0xf << 2);
231     lcd_regs->wincon0 |= (0xB<<2)|(1<<15);
232     lcd_regs->vidosd0a = (LeftTopX<<11) | (LeftTopY << 0);
233     lcd_regs->vidosd0b = (RightBotX<<11) | (RightBotY << 0);
234     lcd_regs->vidosd0c = (LINEVAL + 1) * (HOZVAL + 1);
235 
236     *vidw00add0b0 = clb_fbinfo->fix.smem_start;  
237     *vidw00add1b0 = clb_fbinfo->fix.smem_start + clb_fbinfo->fix.smem_len;  
238 
239     lcd_regs->shadowcon = 0x1;  /* 使能通道0 */    
240     lcd_regs->vidcon0  |= 0x3;  /* 開啟總控制器 */    
241     lcd_regs->wincon0 |= 1;     /* 開啟窗口0 */
242 
243     
244     /*4.注冊*/
245     register_framebuffer(clb_fbinfo);
246 
247     return 0;
248 }
249 static void __exit clb210_lcd_exit(void)
250 {
251     unregister_framebuffer(clb_fbinfo);
252     dma_free_writecombine(NULL,  clb_fbinfo->fix.smem_len, clb_fbinfo->screen_base, clb_fbinfo->fix.smem_start);
253     iounmap(gpf0con);
254     iounmap(gpf1con);
255     iounmap(gpf2con);
256     iounmap(gpf3con);
257     iounmap(gpd0con);
258     iounmap(gpd0dat);

259     iounmap(display_control);
260     iounmap(lcd_regs);
261     iounmap(vidw00add0b0);
262     iounmap(vidw00add1b0);
263     framebuffer_release(clb_fbinfo);
264 }
265 
266 module_init(clb210_lcd_init);
267 module_exit(clb210_lcd_exit);
268 
269 MODULE_LICENSE("GPL");
270 MODULE_AUTHOR("clb");
271 MODULE_DESCRIPTION("Lcd driver for clb210 board");
lcd_drv.c

這份代碼沒有基於platform設備驅動來編寫,在內核源碼中的demo就是基於platform驅動模型來搭建的,主要內容其實一樣。

 


免責聲明!

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



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