基于s5pv-210开发板 LCD驱动

lcd硬件原理:

利用液晶制成的显示器称为 LCD,依据驱动方式可分为静态驱动、简单矩阵驱动以及主动矩阵驱动 3 种。其中,简单矩阵型又可再细分扭转向列型( TN)和超扭转式向列型( STN)两种,而主动矩阵型则以薄膜式晶体管型( TFT)为主流。
TFT 屏是目前嵌入式系统应用的主流,下图给出了 TFT 屏的典型时序。时序图中的VCLKHSYNC VSYNC 分别为像素时钟信号(用于锁存图像数据的像素时钟)、行同步信号和帧同步信号, VDEN 为数据有效标志信号, VD 为图像的数据信号。

基于s5pv-210开发板 LCD驱动_第1张图片
作为帧同步信号的 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;   //fbops 为指向底层操作的函数的指针

3. 硬件相关的操作

根据原理图,映射相应寄存器:

基于s5pv-210开发板 LCD驱动_第2张图片


 /* 配置硬件资源*/  
    /* 映射内存*/  
    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,例如:

基于s5pv-210开发板 LCD驱动_第3张图片

 /* 配置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");  
    }  


根据芯片手册及原理图,配置相应寄存器:

基于s5pv-210开发板 LCD驱动_第4张图片

基于s5pv-210开发板 LCD驱动_第5张图片


时序图,如下:

基于s5pv-210开发板 LCD驱动_第6张图片

 /* 配置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   
#include   
#include   
#include   
  
  
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   // 花屏




你可能感兴趣的:(linux驱动开发)