基於s5pv-210開發板 LCD驅動

lcd硬件原理:

利用液晶製成的顯示器稱爲 LCD,依據驅動方式可分爲靜態驅動、簡單矩陣驅動以及主動矩陣驅動 3 種。其中,簡單矩陣型又可再細分扭轉向列型( TN)和超扭轉式向列型( STN)兩種,而主動矩陣型則以薄膜式晶體管型( TFT)爲主流。
TFT 屏是目前嵌入式系統應用的主流,下圖給出了 TFT 屏的典型時序。時序圖中的VCLKHSYNC VSYNC 分別爲像素時鐘信號(用於鎖存圖像數據的像素時鐘)、行同步信號和幀同步信號, VDEN 爲數據有效標誌信號, VD 爲圖像的數據信號。


作爲幀同步信號的 VSYNC,每發出一個脈衝,都意味着新的一屏圖像數據開始發送。而作爲行同步信號的 HSYNC,每發出一個脈衝都表明新的一行圖像資料開始發送。在幀同步以及行同步的頭尾都必須留有回掃時間。這樣的時序安排起源於 CRT 顯示器電子槍偏轉所需要的時間,但後來成爲實際上的工業標準,因此 TFT 屏也包含了回掃時間。


怎麼寫LCD驅動程序?

1. 分配一個fb_info結構體: framebuffer_alloc

lcd_info = framebuffer_alloc(0, NULL); 

2. 設置

 /* 配置fb_info各成員*/  
    /* fix 固定參數*/  
    strcpy(lcd_info->fix.id, "s5pv210_lcd");  
    lcd_info->fix.smem_len = 800*480*4;  
    lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;  
    lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;  
    lcd_info->fix.line_length = 800*4;  
  
    /* var 可變參數*/  
    lcd_info->var.xres = 800;  
    lcd_info->var.yres = 480;  
    lcd_info->var.xres_virtual = 800;  
    lcd_info->var.yres_virtual = 480;  
    lcd_info->var.bits_per_pixel = 32;  
  
    lcd_info->var.red.offset = 16;  
    lcd_info->var.red.length = 8;  
    lcd_info->var.green.offset = 8;  
    lcd_info->var.green.length = 8;  
    lcd_info->var.blue.offset = 0;  
    lcd_info->var.blue.length = 8;  
    lcd_info->var.activate = FB_ACTIVATE_NOW;  
  
    lcd_info->screen_size = 800*480*4;  
    lcd_info->pseudo_palette = pseudo_palette;  
  
    lcd_info->fbops = &lcd_fbops;   //<span style="font-family: TimesNewRomanPSMT;">fbops <span style="font-family: SimSun;">爲指向底層操作的函數的指針<br style="orphans: 2; text-align: -webkit-auto; widows: 2;" /></span></span>

3. 硬件相關的操作

根據原理圖,映射相應寄存器:



 /* 配置硬件資源*/  
    /* 映射內存*/  
    display_control = ioremap(0xe0107008,4);  
    gpf0con      = ioremap(0xE0200120, 4);  
    gpf1con      = ioremap(0xE0200140, 4);  
    gpf2con      = ioremap(0xE0200160, 4);  
    gpf3con      = ioremap(0xE0200180, 4);  
      
    gpd0con      = ioremap(0xE02000A0, 4);  
    gpd0dat      = ioremap(0xE02000A4, 4);  
      
    vidcon0      = ioremap(0xF8000000, 4);  
    vidcon1      = ioremap(0xF8000004, 4);  
    vidtcon0     = ioremap(0xF8000010, 4);  
    vidtcon1     = ioremap(0xF8000014, 4);  
    vidtcon2     = ioremap(0xF8000018, 4);  
    wincon0      = ioremap(0xF8000020, 4);  
    vidosd0a     = ioremap(0xF8000040, 4);  
    vidosd0b     = ioremap(0xF8000044, 4);  
    vidosd0c     = ioremap(0xF8000048, 4);  
    vidw00add0b0 = ioremap(0xF80000A0, 4);  
    vidw00add1b0 = ioremap(0xF80000D0, 4);  
    shodowcon    = ioremap(0xF8000034, 4);  


查看芯片手冊,配置gpio,例如:


 /* 配置GPIO*/  
    *gpf0con = 0x22222222;  
    *gpf1con = 0x22222222;  
    *gpf2con = 0x22222222;  
    *gpf3con = 0x22222222;  
    *gpd0con &= ~0xf;  
    *gpd0con |= 0x1;  
    *gpd0dat |= 1<<0;  
    *display_control = 2<<0;  


 /* 使能時鐘*/  
    lcd_clk = clk_get(NULL, "lcd");  
    if (!lcd_clk || IS_ERR(lcd_clk)) {  
        printk(KERN_INFO "failed to get lcd clock source\n");  
    }  


根據芯片手冊及原理圖,配置相應寄存器:




時序圖,如下:


 /* 配置LCD控制器*/   //VCLK = HCLK / (CLKVAL+1), where CLKVAL >= 1   HCLK=200MHz    VCLK=Clock Frequency=40MHz    CLKVAL=4
    *vidcon0 &= ~(0xff << 6 | 1 << 4);

    //data |= VIDCON0_CLKVAL_F(clkdiv-1) | VIDCON0_CLKDIR;
    *vidcon0 |= (3 << 6) | (1 << 4);   ////#define VIDCON0_CLKVAL_F(_x)			((_x) << 6)   CLKVAL=3;
   
    *vidcon0 |= (1 << 1) | (1 << 0);

    *vidcon1 = 0x04; 
     // writel(pd->vidcon1, vidcon1);
	//writel(0x04, vidcon1);

   /*data = VIDTCON0_VBPD(var->upper_margin - 1) |VIDTCON0_VFPD(var->lower_margin - 1) | VIDTCON0_VSPW(var->vsync_len - 1);

   writel(data, regs + sfb->variant.vidtcon);*/
  /*data = VIDTCON1_HBPD(var->left_margin - 1) |VIDTCON1_HFPD(var->right_margin - 1) |VIDTCON1_HSPW(var->hsync_len - 1);
		
   writel(data, regs + sfb->variant.vidtcon + 4);*/

  /*data = VIDTCON2_LINEVAL(var->yres - 1) | VIDTCON2_HOZVAL(var->xres - 1);
   writel(data, regs + sfb->variant.vidtcon + 8);*/


   	/*VBPD [23:16] Vertical back porch specifies the number of inactive lines at the
	start of a frame after vertical synchronization period. 0x00
	VFPD [15:8] Vertical front porch specifies the number of inactive lines at the end
	of a frame before vertical synchronization period. 0x00*/
    //tVBP = 22;
    /*tVFP = tVP - tVW - tVBP - tw
	   = 635 - 1 - 22 - 480 
	   =125*/
    *vidtcon0 = (22 << 16)|(125 << 8);  //*vidtcon0 = (31 << 16)|(31 << 8);  

    
	/*HBPD [23:16] Horizontal back porch specifies the number of VCLK periods
	between the falling edge of HSYNC and start of active data. 0x00
	HFPD [15:8] Horizontal front porch specifies the number of VCLK periods
	between the end of active data and rising edge of HSYNC. */
	
	//tHBP = 45;
	/*tHFP = tHP - tHW - tHBP - tHV
	       = 1056 - 1 - 45 - 800
	       = 210*/
    *vidtcon1 = (45 << 16)|(210 << 8); 	//*vidtcon1 = (63 << 16)|(63 << 8); 
    *vidtcon2 = (479 << 11)|(799 << 0);  //HOZVAL = (Horizontal display size) -1 ; LINEVAL = (Vertical display size) -1
  
    *wincon0 &= ~(0xf<<2);  
    *wincon0 |= (0xb<<2);  //select bpp
  
    *vidosd0a = (0<<11)|(0<<0);  //左上角座標 
    *vidosd0b = (799<<11)|(479<<0);  //右下角座標
    *vidosd0c = 480*800;   //指定的窗口大小
    //物理地址  
    lcd_info->screen_base = dma_alloc_writecombine(NULL,lcd_info->fix.smem_len, (dma_addr_t *)&(lcd_info->fix.smem_start), GFP_KERNEL);  
      
    *vidw00add0b0 = lcd_info->fix.smem_start;  
    *vidw00add1b0 = lcd_info->fix.smem_start + lcd_info->fix.smem_len;  
  
    *shodowcon = 0x1;  
  

4. 註冊: register_framebuffer

ret = register_framebuffer(lcd_info);  



完整lcd驅動程序如下:

 #include <linux/module.h>  
#include <linux/fb.h>  
#include <linux/dma-mapping.h>  
#include <linux/clk.h>  
  
  
static struct fb_info *lcd_info;  
unsigned long pseudo_palette[16];  
  
unsigned long *display_control;  
  
volatile unsigned long* gpf0con;  
volatile unsigned long* gpf1con;  
volatile unsigned long* gpf2con;  
volatile unsigned long* gpf3con;  
volatile unsigned long* gpd0con;  
volatile unsigned long* gpd0dat;  
volatile unsigned long* vidcon0;  
volatile unsigned long* vidcon1;  
volatile unsigned long* vidtcon0;  
volatile unsigned long* vidtcon1;  
volatile unsigned long* vidtcon2;  
volatile unsigned long* wincon0;  
volatile unsigned long* vidosd0a;  
volatile unsigned long* vidosd0b;  
volatile unsigned long* vidosd0c;  
volatile unsigned long* vidw00add0b0;  
volatile unsigned long* vidw00add1b0;  
volatile unsigned long* shodowcon;  
  
struct clk *lcd_clk;  
  
  
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)  
{  
    chan &= 0xffff;  
    chan >>= 16 - bf->length;  
    return chan << bf->offset;  
}  
  
static int lcdfb_setcolreg(unsigned int regno, unsigned int red,  
                 unsigned int green, unsigned int blue,  
                 unsigned int transp, struct fb_info *info)  
{  
    unsigned int val;  
      
    if (regno > 16)  
        return 1;  
  
    /* 用red,green,blue三原色構造出val */  
    val  = chan_to_field(red,   &info->var.red);  
    val |= chan_to_field(green, &info->var.green);  
    val |= chan_to_field(blue,  &info->var.blue);  
      
    //((u32 *)(info->pseudo_palette))[regno] = val;  
    pseudo_palette[regno] = val;  
    return 0;  
}  
  
static struct fb_ops lcd_fbops = {  
    .owner      = THIS_MODULE,  
    .fb_setcolreg   = lcdfb_setcolreg,  
    .fb_fillrect    = cfb_fillrect,  
    .fb_copyarea    = cfb_copyarea,  
    .fb_imageblit   = cfb_imageblit,  
};  
  
static int lcd_init(void){  
    int ret;  
  
    /*分配fb_info */  
    lcd_info = framebuffer_alloc(0, NULL);  
    if(lcd_info == NULL){  
        printk(KERN_ERR "alloc framebuffer failed!\n");  
        return -ENOMEM;  
    }  
  
    /* 配置fb_info各成員*/  
    /* fix */  
    strcpy(lcd_info->fix.id, "s5pv210_lcd");  
    lcd_info->fix.smem_len = 800*480*4;  
    lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;  
    lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;  
    lcd_info->fix.line_length = 800*4;  
  
    /* var */  
    lcd_info->var.xres = 800;  
    lcd_info->var.yres = 480;  
    lcd_info->var.xres_virtual = 800;  
    lcd_info->var.yres_virtual = 480;  
    lcd_info->var.bits_per_pixel = 32;  
  
    lcd_info->var.red.offset = 16;  
    lcd_info->var.red.length = 8;  
    lcd_info->var.green.offset = 8;  
    lcd_info->var.green.length = 8;  
    lcd_info->var.blue.offset = 0;  
    lcd_info->var.blue.length = 8;  
    lcd_info->var.activate = FB_ACTIVATE_NOW;  
  
    lcd_info->screen_size = 800*480*4;  
    lcd_info->pseudo_palette = pseudo_palette;  
  
    lcd_info->fbops = &lcd_fbops;  
    /* 配置硬件資源*/  
    /* 映射內存*/  
    display_control = ioremap(0xe0107008,4);  
    gpf0con      = ioremap(0xE0200120, 4);  
    gpf1con      = ioremap(0xE0200140, 4);  
    gpf2con      = ioremap(0xE0200160, 4);  
    gpf3con      = ioremap(0xE0200180, 4);  
      
    gpd0con      = ioremap(0xE02000A0, 4);  
    gpd0dat      = ioremap(0xE02000A4, 4);  
      
    vidcon0      = ioremap(0xF8000000, 4);  
    vidcon1      = ioremap(0xF8000004, 4);  
    vidtcon0     = ioremap(0xF8000010, 4);  
    vidtcon1     = ioremap(0xF8000014, 4);  
    vidtcon2     = ioremap(0xF8000018, 4);  
    wincon0      = ioremap(0xF8000020, 4);  
    vidosd0a     = ioremap(0xF8000040, 4);  
    vidosd0b     = ioremap(0xF8000044, 4);  
    vidosd0c     = ioremap(0xF8000048, 4);  
    vidw00add0b0 = ioremap(0xF80000A0, 4);  
    vidw00add1b0 = ioremap(0xF80000D0, 4);  
    shodowcon    = ioremap(0xF8000034, 4);  
      
    /* 配置GPIO*/  
    *gpf0con = 0x22222222;  
    *gpf1con = 0x22222222;  
    *gpf2con = 0x22222222;  
    *gpf3con = 0x22222222;  
    *gpd0con &= ~0xf;  
    *gpd0con |= 0x1;  
    *gpd0dat |= 1<<0;  
    *display_control = 2<<0;  
    /* 使能時鐘*/  
    lcd_clk = clk_get(NULL, "lcd");  
    if (!lcd_clk || IS_ERR(lcd_clk)) {  
        printk(KERN_INFO "failed to get lcd clock source\n");  
    }  
    clk_enable(lcd_clk);  
     /* 配置LCD控制器*/   //VCLK = HCLK / (CLKVAL+1), where CLKVAL >= 1   HCLK=200MHz    VCLK=Clock Frequency=40MHz    CLKVAL=4
    *vidcon0 &= ~(0xff << 6 | 1 << 4);

    //data |= VIDCON0_CLKVAL_F(clkdiv-1) | VIDCON0_CLKDIR;
    *vidcon0 |= (3 << 6) | (1 << 4);   ////#define VIDCON0_CLKVAL_F(_x)			((_x) << 6)   CLKVAL=3;
   
    *vidcon0 |= (1 << 1) | (1 << 0);

    *vidcon1 = 0x04; 
     // writel(pd->vidcon1, vidcon1);
	//writel(0x04, vidcon1);

   /*data = VIDTCON0_VBPD(var->upper_margin - 1) |VIDTCON0_VFPD(var->lower_margin - 1) | VIDTCON0_VSPW(var->vsync_len - 1);

   writel(data, regs + sfb->variant.vidtcon);*/
  /*data = VIDTCON1_HBPD(var->left_margin - 1) |VIDTCON1_HFPD(var->right_margin - 1) |VIDTCON1_HSPW(var->hsync_len - 1);
		
   writel(data, regs + sfb->variant.vidtcon + 4);*/

  /*data = VIDTCON2_LINEVAL(var->yres - 1) | VIDTCON2_HOZVAL(var->xres - 1);
   writel(data, regs + sfb->variant.vidtcon + 8);*/


   	/*VBPD [23:16] Vertical back porch specifies the number of inactive lines at the
	start of a frame after vertical synchronization period. 0x00
	VFPD [15:8] Vertical front porch specifies the number of inactive lines at the end
	of a frame before vertical synchronization period. 0x00*/
    //tVBP = 22;
    /*tVFP = tVP - tVW - tVBP - tw
	   = 635 - 1 - 22 - 480 
	   =125*/
    *vidtcon0 = (22 << 16)|(125 << 8);  //*vidtcon0 = (31 << 16)|(31 << 8);  

    
	/*HBPD [23:16] Horizontal back porch specifies the number of VCLK periods
	between the falling edge of HSYNC and start of active data. 0x00
	HFPD [15:8] Horizontal front porch specifies the number of VCLK periods
	between the end of active data and rising edge of HSYNC. */
	
	//tHBP = 45;
	/*tHFP = tHP - tHW - tHBP - tHV
	       = 1056 - 1 - 45 - 800
	       = 210*/
    *vidtcon1 = (45 << 16)|(210 << 8); 	//*vidtcon1 = (63 << 16)|(63 << 8); 
    *vidtcon2 = (479 << 11)|(799 << 0);  //HOZVAL = (Horizontal display size) -1 ; LINEVAL = (Vertical display size) -1
  
    *wincon0 &= ~(0xf<<2);  
    *wincon0 |= (0xb<<2);  //select bpp
  
    *vidosd0a = (0<<11)|(0<<0);  //左上角座標 
    *vidosd0b = (799<<11)|(479<<0);  //右下角座標
    *vidosd0c = 480*800;   //指定的窗口大小
    //物理地址  
    lcd_info->screen_base = dma_alloc_writecombine(NULL,lcd_info->fix.smem_len, (dma_addr_t *)&(lcd_info->fix.smem_start), GFP_KERNEL);  
      
    *vidw00add0b0 = lcd_info->fix.smem_start;  
    *vidw00add1b0 = lcd_info->fix.smem_start + lcd_info->fix.smem_len;  
  
    *shodowcon = 0x1;  
  
    //開啓狀態  
    *wincon0 |= 1;  
    *vidcon0 |= 3;  
    /* 註冊fb_info */  
    ret = register_framebuffer(lcd_info);  
    return ret;  
}  
  
static void lcd_exit(void){  
    unregister_framebuffer(lcd_info);  
    dma_free_writecombine(NULL, lcd_info->fix.smem_len,   
        (void*)lcd_info->screen_base, (dma_addr_t)lcd_info->fix.smem_start);  
  
    iounmap(shodowcon);  
    iounmap(vidw00add1b0);  
    iounmap(vidw00add0b0);  
    iounmap(vidosd0c);  
    iounmap(vidosd0b);  
    iounmap(vidosd0a);  
    iounmap(wincon0);  
    iounmap(vidtcon2);  
    iounmap(vidtcon1);  
    iounmap(vidtcon0);  
    iounmap(vidcon1);  
    iounmap(vidcon0);  
    iounmap(gpd0dat);  
    iounmap(gpd0con);  
    iounmap(gpf3con);  
    iounmap(gpf2con);  
    iounmap(gpf1con);  
    iounmap(gpf0con);  
    framebuffer_release(lcd_info);  
}  
  
module_init(lcd_init);  
module_exit(lcd_exit);  
MODULE_LICENSE("GPL"); 

測試:

去掉原來的驅動,加載自己編譯的驅動。

echo hello > /dev/tty1  // 可以在LCD上看見hello
cat lcd.ko > /dev/fb0   // 花屏




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章