lcd硬件原理:
利用液晶制成的显示器称为 LCD,依据驱动方式可分为静态驱动、简单矩阵驱动以及主动矩阵驱动 3 种。其中,简单矩阵型又可再细分扭转向列型( TN)和超扭转式向列型( STN)两种,而主动矩阵型则以薄膜式晶体管型( TFT)为主流。
TFT 屏是目前嵌入式系统应用的主流,下图给出了 TFT 屏的典型时序。时序图中的VCLK、 HSYNC 和 VSYNC 分别为像素时钟信号(用于锁存图像数据的像素时钟)、行同步信号和帧同步信号, VDEN 为数据有效标志信号, VD 为图像的数据信号。
怎么写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. 硬件相关的操作
根据原理图,映射相应寄存器:
/* 配置硬件资源*/
/* 映射内存*/
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
#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 // 花屏