1. 程式人生 > >linux驅動入門之LCD驅動

linux驅動入門之LCD驅動

硬體環境:tq2440 4.3 LCD

軟體環境:Fedora17 arm-linux-gcc-4.3.2

核心版本:2.6.39

以韋東山老師視訊為基礎,加入自己的實踐

1.基礎知識(轉載,原作者不詳,感謝先):

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中的定義如下:(只列出重要的一些)

struct fb_info {
    int node;
    int flags;
    struct fb_var_screeninfo var;/*LCD可變引數結構體*/
    struct fb_fix_screeninfo fix;/*LCD固定引數結構體*/
    struct fb_monspecs monspecs; /*LCD顯示器標準*/
    struct work_struct queue;    /*幀緩衝事件佇列*/
    struct fb_pixmap pixmap;     /*影象硬體mapper*/
    struct fb_pixmap sprite;     /*游標硬體mapper*/
    struct fb_cmap cmap;         /*當前的顏色表*/
    struct fb_videomode *mode;   /*當前的顯示模式*/

#ifdef CONFIG_FB_BACKLIGHT
    
struct backlight_device *bl_dev;/*對應的背光裝置*/
    struct mutex bl_curve_mutex;
    u8 bl_curve[FB_BACKLIGHT_LEVELS];/*背光調整*/
#endif
#ifdef CONFIG_FB_DEFERRED_IO
    struct delayed_work deferred_work;
    struct fb_deferred_io *fbdefio;
#endif

    struct fb_ops *fbops/*對底層硬體操作的函式指標*/
    struct device *device;
    struct device *dev;   /*fb裝置*/
    int class_flag;    
#ifdef CONFIG_FB_TILEBLITTING
    struct fb_tile_ops *tileops; /*圖塊Blitting*/
#endif
    char __iomem *screen_base;   /*虛擬基地址*/
    unsigned long screen_size;   /*LCD IO對映的虛擬記憶體大小*/ 
    void *pseudo_palette;        /*偽16色顏色表*/ 
#define FBINFO_STATE_RUNNING    0
#define FBINFO_STATE_SUSPENDED  1
    u32 state;  /*LCD的掛起或恢復狀態*/
    void *fbcon_par;
    void *par;    
};

其中,比較重要的成員有struct fb_var_screeninfo var、struct fb_fix_screeninfo fix和structfb_ops *fbops,他們也都是結構體。下面我們一個一個的來看。

fb_var_screeninfo結構體主要記錄使用者可以修改的控制器的引數,比如螢幕的解析度和每個畫素的位元數等,該結構體定義如下:

struct fb_var_screeninfo {
    __u32 xres;                /*可見螢幕一行有多少個畫素點*/
    __u32 yres;                /*可見螢幕一列有多少個畫素點*/
    __u32 xres_virtual;        /*虛擬螢幕一行有多少個畫素點*/        
    __u32 yres_virtual;        /*虛擬螢幕一列有多少個畫素點*/
    __u32 xoffset;             /*虛擬到可見螢幕之間的行偏移*/
    __u32 yoffset;             /*虛擬到可見螢幕之間的列偏移*/
    __u32 bits_per_pixel;      /*每個畫素的位數即BPP*/
    __u32 grayscale;           /*非0時,指的是灰度*/

    struct fb_bitfield red;    /*fb快取的R位域*/
    struct fb_bitfield green;  /*fb快取的G位域*/
    struct fb_bitfield blue;   /*fb快取的B位域*/
    struct fb_bitfield transp; /*透明度*/    

    __u32 nonstd;              /* != 0 非標準畫素格式*/
    __u32 activate;                
    __u32 height;              /*高度*/
    __u32 width;               /*寬度*/
    __u32 accel_flags;    

    /*定時:除了pixclock本身外,其他的都以畫素時鐘為單位*/
    __u32 pixclock;            /*畫素時鐘(皮秒)*/
    __u32 left_margin;         /*行切換,從同步到繪圖之間的延遲*/
    __u32 right_margin;        /*行切換,從繪圖到同步之間的延遲*/
    __u32 upper_margin;        /*幀切換,從同步到繪圖之間的延遲*/
    __u32 lower_margin;        /*幀切換,從繪圖到同步之間的延遲*/
    __u32 hsync_len;           /*水平同步的長度*/
    __u32 vsync_len;           /*垂直同步的長度*/
    __u32 sync;
    __u32 vmode;
    __u32 rotate;
    __u32 reserved[5];         /*保留*/
};

fb_fix_screeninfo結構體又主要記錄使用者不可以修改的控制器的引數,比如螢幕緩衝區的實體地址和長度等,該結構體的定義如下:

struct fb_fix_screeninfo {
    char id[16];                /*字串形式的標示符 */
    unsigned long smem_start;   /*fb快取的開始位置 */
    __u32 smem_len;             /*fb快取的長度 */
    __u32 type;                 /*看FB_TYPE_* */
    __u32 type_aux;             /*分界*/
    __u32 visual;               /*看FB_VISUAL_* */ 
    __u16 xpanstep;             /*如果沒有硬體panning就賦值為0 */
    __u16 ypanstep;             /*如果沒有硬體panning就賦值為0 */
    __u16 ywrapstep;            /*如果沒有硬體ywrap就賦值為0 */
    __u32 line_length;          /*一行的位元組數 */
    unsigned long mmio_start;   /*記憶體對映IO的開始位置*/
    __u32 mmio_len;             /*記憶體對映IO的長度*/
    __u32 accel;
    __u16 reserved[3];          /*保留*/
};

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相關的平臺裝置及資源,程式碼如下:

/* LCD Controller */

//LCD控制器的資源資訊
static struct resource s3c_lcd_resource[] = {
    [0] = {
        .start = S3C24XX_PA_LCD
//控制器IO埠開始地址
        .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,//控制器IO埠結束地址
        .flags = IORESOURCE_MEM,//標識為LCD控制器IO埠,在驅動中引用這個就表示引用IO埠
    },
    [1] = {
        .start = IRQ_LCD
,//LCD中斷
        .end = IRQ_LCD,
        .flags = IORESOURCE_IRQ
,//標識為LCD中斷
    }
};

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;

struct platform_device s3c_device_lcd = {
    .name         = "s3c2410-lcd"
,//作為平臺裝置的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
    }
};

EXPORT_SYMBOL(s3c_device_lcd)
;//匯出定義的LCD平臺裝置,好在mach-smdk2440.c的smdk2440_devices[]中新增到平臺裝置列表中


   除此之外,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屏的引數,這裡不用
};

現在知道當初移植linux LCD驅動時,這些引數是怎麼設定的了

注意:可能有很多朋友不知道上面紅色部分的引數是做什麼的,其值又是怎麼設定的?其實它是跟你的開發板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中有:

//S3C2440初始化函式
static void __init smdk2440_machine_init(void)
{

    //呼叫該函式將上面定義的LCD硬體資訊儲存到平臺數據中
    s3c24xx_fb_set_platdata(&smdk2440_fb_info);
    
    s3c_i2c0_set_platdata(NULL);

    platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
    smdk_machine_init();
}

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

void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
    struct s3c2410fb_mach_info *npd;

    npd = kmalloc(sizeof(*npd), GFP_KERNEL);
    if (npd) {
        memcpy(npd, pd, sizeof(*npd));

        //這裡就是將核心中定義的s3c2410fb_mach_info結構體資料儲存到LCD平臺數據中,所以在寫驅動的時候就可以直接在平臺數據中獲取s3c2410fb_mach_info結構體的資料(即LCD各種引數資訊)進行操作
        s3c_device_lcd.dev.platform_data = npd;
    } else {
        printk(KERN_ERR "no memory for LCD platform data/n");
    }
}

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

2.程式碼:

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

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>

#include <asm/mach/map.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>

static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info);


struct lcd_regs {
	unsigned long	lcdcon1;
	unsigned long	lcdcon2;
	unsigned long	lcdcon3;
	unsigned long	lcdcon4;
	unsigned long	lcdcon5;
    	unsigned long	lcdsaddr1;
    	unsigned long	lcdsaddr2;
    	unsigned long	lcdsaddr3;
    	unsigned long	redlut;
    	unsigned long	greenlut;
    	unsigned long	bluelut;
    	unsigned long	reserved[9];
    	unsigned long	dithmode;
    	unsigned long	tpal;
    	unsigned long	lcdintpnd;
    	unsigned long	lcdsrcpnd;
    	unsigned long	lcdintmsk;
    	unsigned long	lpcsel;
};

static struct fb_ops s3c_lcdfb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= s3c_lcdfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,/* linux核心自帶的驅動,需要載入,後面使用的時候會講到 */
};


static struct fb_info *s3c_lcd;

static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static