1. 程式人生 > >LCD驅動(FrameBuffer)例項開發講解

LCD驅動(FrameBuffer)例項開發講解

一、開發環境

  • 主  機:VMWare--Fedora 9
  • 開發板:Mini2440--64MB Nand, Kernel:2.6.30.4
  • 編譯器:arm-linux-gcc-4.3.2

二、背景知識

1. LCD工作的硬體需求:    要使一塊LCD正常的顯示文字或影象,不僅需要LCD驅動器,而且還需要相應的LCD控制器。在通常情況下,生產廠商把LCD驅動器會以COF/COG的 形式與LCD玻璃基板製作在一起,而LCD控制器則是由外部的電路來實現,現在很多的MCU內部都集成了LCD控制器,如S3C2410/2440等。通 過LCD控制器就可以產生LCD驅動器所需要的控制訊號來控制STN/TFT屏了。 2. S3C2440內部LCD控制器結構圖:
我們根據資料手冊來描述一下這個整合在S3C2440內部的LCD控制器: a:LCD控制器由REGBANK、LCDCDMA、TIMEGEN、VIDPRCS暫存器組成; b:REGBANK由17個可程式設計的暫存器組和一塊256*16的調色盤記憶體組成,它們用來配置LCD控制器的; c:LCDCDMA是一個專用的DMA,它能自動地把在偵記憶體中的視訊資料傳送到LCD驅動器,通過使用這個DMA通道,視訊資料在不需要CPU的干預的情況下顯示在LCD屏上; d:VIDPRCS接收來自LCDCDMA的資料,將資料轉換為合適的資料格式,比如說4/8位單掃,4位雙掃顯示模式,然後通過資料埠VD[23:0]傳送視訊資料到LCD驅動器; e:TIMEGEN由可程式設計的邏輯組成,他生成LCD驅動器需要的控制訊號,比如VSYNC、HSYNC、VCLK和LEND等等,而這些控制 訊號又與REGBANK暫存器組中的LCDCON1/2/3/4/5的配置密切相關,通過不同的配置,TIMEGEN就能產生這些訊號的不同形態,從而支 持不同的LCD驅動器(即不同的STN/TFT屏)。 3. 常見TFT屏工作時序分析:
LCD提供的外部介面訊號:

VSYNC/VFRAME/STV:垂直同步訊號(TFT)/幀同步訊號(STN)/SEC TFT訊號;
HSYNC/VLINE/CPV:水平同步訊號(TFT)/行同步脈衝訊號(STN)/SEC TFT訊號;
VCLK/LCD_HCLK:象素時鐘訊號(TFT/STN)/SEC TFT訊號;
VD[23:0]:LCD畫素資料輸出埠(TFT/STN/SEC TFT);
VDEN/VM/TP:資料使能訊號(TFT)/LCD驅動交流偏置訊號(STN)/SEC TFT 訊號;
LEND/STH:行結束訊號(TFT)/SEC TFT訊號;
LCD_LPCOE:SEC TFT OE訊號;
LCD_LPCREV:

SEC TFT REV訊號;
LCD_LPCREVB:SEC TFT REVB訊號。

所有顯示器顯示影象的原理都是從上到下,從左到右的。這是什麼意思呢?這麼說吧,一副影象可以看做是一個矩形,由很多排列整齊的點一行一行組成,這些點稱之為畫素。那麼這幅圖在LCD上的顯示原理就是:

A:顯示指標從矩形左上角的第一行第一個點開始,一個點一個點的在LCD上顯示,在上面的時序圖上用時間線表示就為VCLK,我們稱之為畫素時鐘訊號;
B:當顯示指標一直顯示到矩形的右邊就結束這一行,那麼這一行的動作在上面的時序圖中就稱之為1 Line;
C:接下來顯示指標又回到矩形的左邊從第二行開始顯示,注意,顯示指標在從第一行的右邊回到第二行的左邊是需要一定的時間的,我們稱之為行切換;
D:如此類推,顯示指標就這樣一行一行的顯示至矩形的右下角才把一副圖顯示完成。因此,這一行一行的顯示在時間線上看,就是時序圖上的HSYNC;
E:然 而,LCD的顯示並不是對一副影象快速的顯示一下,為了持續和穩定的在LCD上顯示,就需要切換到另一幅圖上(另一幅圖可以和上一副圖一樣或者不一樣,目 的只是為了將影象持續的顯示在LCD上)。那麼這一副一副的影象就稱之為幀,在時序圖上就表示為1 Frame,因此從時序圖上可以看出1 Line只是1 Frame中的一行;
F:同樣的,在幀與幀切換之間也是需要一定的時間的,我們稱之為幀切換,那麼LCD整個顯示的過程在時間線上看,就可表示為時序圖上的VSYNC。

上面時序圖上各時鐘延時引數的含義如下:(這些引數的值,LCD產生廠商會提供相應的資料手冊)

VBPD(vertical back porch):表示在一幀影象開始時,垂直同步訊號以後的無效的行數,對應驅動中的upper_margin;
VFBD(vertical front porch):表示在一幀影象結束後,垂直同步訊號以前的無效的行數,對應驅動中的lower_margin;
VSPW(vertical sync pulse width):表示垂直同步脈衝的寬度,用行數計算,對應驅動中的vsync_len;
HBPD(horizontal back porch):表示從水平同步訊號開始到一行的有效資料開始之間的VCLK的個數,對應驅動中的left_margin;
HFPD(horizontal front porth):表示一行的有效資料結束到下一個水平同步訊號開始之間的VCLK的個數,對應驅動中的right_margin;
HSPW(horizontal sync pulse width):表示水平同步訊號的寬度,用VCLK計算,對應驅動中的hsync_len;

對於以上這些引數的值將分別儲存到REGBANK暫存器組中的LCDCON1/2/3/4/5暫存器中:(對暫存器的操作請檢視S3c2440資料手冊LCD部分)

LCDCON1:17 - 8位CLKVAL 
          6 - 5位掃描模式(對於STN屏:4位單/雙掃、8位單掃) 
          4 - 1位色位模式(1BPP、8BPP、16BPP等)

LCDCON2:31 - 24位VBPD 
         23 - 14位LINEVAL 
         13 - 6位VFPD 
          5 - 0位VSPW

LCDCON3:25 - 19位HBPD 
         18 - 8位HOZVAL 
          7 - 0位HFPD

LCDCON4: 7 - 0位HSPW

LCDCON5:

4. 幀緩衝(FrameBuffer): 幀緩衝是Linux為顯示裝置提供的一個介面,它把一些顯示裝置描述成一個緩衝區,允許應用程式通過 FrameBuffer定義好的介面訪問這些圖形裝置,從而不用去關心具體的硬體細節。對於幀緩衝裝置而言,只要在顯示緩衝區與顯示點對應的區域寫入顏色 值,對應的顏色就會自動的在螢幕上顯示。下面來看一下在不同色位模式下緩衝區與顯示點的對應關係:
、幀緩衝(FrameBuffer)裝置驅動結構 幀 緩衝裝置為標準的字元型裝置,在Linux中主裝置號29,定義在/include/linux/major.h中的FB_MAJOR,次裝置號定義幀緩 衝的個數,最大允許有32個FrameBuffer,定義在/include/linux/fb.h中的FB_MAX,對應於檔案系統下/dev /fb%d裝置檔案。

1. 幀緩衝裝置驅動在Linux子系統中的結構如下:

我 們從上面這幅圖看,幀緩衝裝置在Linux中也可以看做是一個完整的子系統,大體由fbmem.c和xxxfb.c組成。向上給應用程式提供完善的裝置文 件操作介面(即對FrameBuffer裝置進行read、write、ioctl等操作),介面在Linux提供的fbmem.c檔案中實現;向下提供 了硬體操作的介面,只是這些介面Linux並沒有提供實現,因為這要根據具體的LCD控制器硬體進行設定,所以這就是我們要做的事情了(即xxxfb.c 部分的實現)。

2. 幀緩衝相關的重要資料結構:
   從 幀緩衝裝置驅動程式結構看,該驅動主要跟fb_info結構體有關,該結構體記錄了幀緩衝裝置的全部資訊,包括裝置的設定引數、狀態以及對底層硬體操作的 函式指標。在Linux中,每一個幀緩衝裝置都必須對應一個fb_info,fb_info在/linux/fb.h中的定義如下:(只列出重要的一些)

fb_ops結構體是對底層硬體操作的函式指標,該結構體中定義了對硬體的操作有:(這裡只列出了常用的操作)

struct fb_ops {

    struct module *owner;

    
//檢查可變引數並進行設定
    int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

    
//根據設定的值進行更新,使之有效
    int (*fb_set_par)(struct fb_info *info);

    
//設定顏色暫存器
    int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
             unsigned blue, unsigned transp, struct fb_info *info);

    
//顯示空白
    int (*fb_blank)(int blank, struct fb_info *info);

    
//矩形填充
    void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);

    
//複製資料
    void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);

    
//圖形填充
    void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
};

3. 幀緩衝裝置作為平臺裝置:
   在S3C2440中,LCD控制器被整合在晶片的內部作為一個相對獨立的單元,所以Linux把它看做是一個平臺裝置,故在核心程式碼/arch/arm/plat-s3c24xx/devs.c中定義有LCD相關的平臺裝置及資源,程式碼如下: 

   除此之外,Linux還在/arch/arm/mach-s3c2410/include/mach/fb.h中為LCD平臺裝置定義了一個 s3c2410fb_mach_info結構體,該結構體主要是記錄LCD的硬體引數資訊(比如該結構體的s3c2410fb_display成員結構中 就用於記錄LCD的螢幕尺寸、螢幕資訊、可變的螢幕引數、LCD配置暫存器等),這樣在寫驅動的時候就直接使用這個結構體。下面,我們來看一下核心是如果 使用這個結構體的。在/arch/arm/mach-s3c2440/mach-smdk2440.c中定義有:

/* LCD driver info */

//LCD硬體的配置資訊,注意這裡我使用的LCD是NEC 3.5寸TFT屏,這些引數要根據具體的LCD屏進行設定
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {

    //這個地方的設定是配置LCD暫存器5,這些巨集定義在regs-lcd.h中,計算後二進位制為:111111111111,然後對照資料手冊上LCDCON5的各位來看,注意是從右邊開始
    .lcdcon5 = S3C2410_LCDCON5_FRM565 |
               S3C2410_LCDCON5_INVVLINE |
               S3C2410_LCDCON5_INVVFRAME |
               S3C2410_LCDCON5_PWREN |
               S3C2410_LCDCON5_HWSWP,

    .type    = S3C2410_LCDCON1_TFT
,//TFT型別

    /* NEC 3.5'' */
    .width        = 240
,//螢幕寬度
    .height       = 320,//螢幕高度

//以下一些引數在上面的時序圖分析中講到過,各引數的值請跟據具體的LCD屏資料手冊結合上面時序分析來設定
    .pixclock     = 100000,//畫素時鐘
    .xres         = 240,//水平可見的有效畫素
    .yres         = 320,//垂直可見的有效畫素
    .bpp          = 16,//色位模式
    .left_margin  = 19,//行切換,從同步到繪圖之間的延遲
    .right_margin = 36,//行切換,從繪圖到同步之間的延遲
    .hsync_len    = 5,//水平同步的長度
    .upper_margin = 1,//幀切換,從同步到繪圖之間的延遲
    .lower_margin = 5,//幀切換,從繪圖到同步之間的延遲
    .vsync_len    = 1,//垂直同步的長度
};

static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
    .displays        = &smdk2440_lcd_cfg
,//應用上面定義的配置資訊
    .num_displays    = 1,
    .default_display = 0,

    .gpccon          = 0xaaaa555a,//將GPC0、GPC1配置成LEND和VCLK,將GPC8-15配置成VD0-7,其他配置成普通輸出IO口
    .gpccon_mask     = 0xffffffff,
    .gpcup           = 0x0000ffff,//禁止GPIOC的上拉功能
    .gpcup_mask      = 0xffffffff,
    .gpdcon          = 0xaaaaaaaa,//將GPD0-15配置成VD8-23
    .gpdcon_mask     = 0xffffffff,
    .gpdup           = 0x0000ffff,//禁止GPIOD的上拉功能
    .gpdup_mask      = 0xffffffff,

    .lpcsel          = 0x0,//這個是三星TFT屏的引數,這裡不用
};

注意:可能有很多朋友不知道上面紅色部分的引數是做什麼的,其值又是怎麼設定的?其實它是跟你的開發板LCD控制器密切相關的,看了下面兩幅圖相信就大概知道他們是幹什麼用的:

上面第一幅圖是開發板原理圖的LCD控制器部分,第二幅圖是S3c2440資料手冊中IO埠C和IO埠D控制器部分。原理圖中使用了 GPC8-15和GPD0-15來用做LCD控制器VD0-VD23的資料埠,又分別使用GPC0、GPC1埠用做LCD控制器的LEND和VCLK 訊號,對於GPC2-7則是用做STN屏或者三星專業TFT屏的相關訊號。然而,S3C2440的各個IO口並不是單一的功能,都是複用埠,要使用他們 首先要對他們進行配置。所以上面紅色部分的引數就是把GPC和GPD的部分埠配置成LCD控制功能模式。

   從以上講述的內容來看,要使LCD控制器支援其他的LCD屏,重要的是根據LCD的資料手冊修改以上這些引數的值。下面,我們再看一下在驅動中是如果引用 到s3c2410fb_mach_info結構體的(注意上面講的是在核心中如何使用的)。在mach-smdk2440.c中有:

s3c24xx_fb_set_platdata定義在plat-s3c24xx/devs.c中:

   這裡再講一個小知識:不知大家有沒有留意,在平臺裝置驅動中,platform_data可以儲存各自平臺裝置例項的資料,但這些資料的型別都是不同的, 為什麼都可以儲存?這就要看看platform_data的定義,定義在/linux/device.h中,void *platform_data是一個void型別的指標,在Linux中void可儲存任何資料型別。

四、幀緩衝(FrameBuffer)裝置驅動例項程式碼:

①、建立驅動檔案:my2440_lcd.c,依就是驅動程式的最基本結構:FrameBuffer驅動的初始化和解除安裝部分及其他,如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/fb.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/mm.h>

#include <linux/slab.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/div64.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>
#include <linux/pm.h>


/*FrameBuffer裝置名稱*/
static char driver_name[] = "my2440_lcd";

/*定義一個結構體用來維護驅動程式中各函式中用到的變數
  先別看結構體要定義這些成員,到各函式使用的地方就明白了*/

struct my2440fb_var
{
    int lcd_irq_no;           /*儲存LCD中斷號*/
    struct clk *lcd_clock;    /*儲存從平臺時鐘佇列中獲取的LCD時鐘*/
    struct resource *lcd_mem; /*LCD的IO空間*/
    void __iomem *lcd_base;   /*LCD的IO空間對映到虛擬地址*/
    struct device *dev;

    struct s3c2410fb_hw regs; /*表示5個LCD配置暫存器,s3c2410fb_hw定義在mach-s3c2410/include/mach/fb.h中*/

    
/*定義一個數組來充當調色盤。
    據資料手冊描述,TFT屏色位模式為8BPP時,調色盤(顏色表)的長度為256,調色盤起始地址為0x4D000400*/

    u32    palette_buffer[256]; 

    u32 pseudo_pal[16];   
    unsigned int palette_ready; /*標識調色盤是否準備好了*/
};

/*用做清空調色盤(顏色表)*/
#define PALETTE_BUFF_CLEAR (0x80000000)    

/*LCD平臺驅動結構體,平臺驅動結構體定義在platform_device.h中,該結構體成員介面函式在第②步中實現*/
static struct platform_driver lcd_fb_driver = 
{
    .probe     =