一、S3C6410 LCD驱动裸机代码
LCD控制器初始化:
1 unsigned long VideoBuffer[LCD_LOW][LCD_COL] = {0}; 2 void lcd_init(void) 3 { 4 /* 1.初始化IO端口为LCD端口 */ 5 /* GPIO configure */ 6 GPICON = 0xAAAAAAAA; 7 GPJCON = 0x00AAAAAA; 8 9 /* 2.使能LCD时钟 */ 10 //HCLK_GATE |= (1 << 3); //默认打开 11 12 MIFPCON &=~(1 << 3); 13 14 /* 3.设置I/F类型 */ 15 SPCON &=~(3 << 0); 16 SPCON |= (1 << 0); 17 18 VIDCON0 = (CLK_DIV << 6) | (1 << 4) | (1 << 1) | (1 << 0); 19 VIDCON1 |= (1 << 6) | (1 << 5); 20 21 /* 4.LCD控制器相关寄存器配置 */ 22 VIDTCON0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0); 23 VIDTCON1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0); 24 VIDTCON2 = (LINEVAL << 11) | (HOZVAL << 0); 25 26 WINCON0 |= (0xB << 2) | (1 << 0); 27 VIDOSD0A = (0 << 11) | (0 << 0); 28 VIDOSD0B = (HOZVAL << 11) | (LINEVAL << 0); 29 VIDOSD0C = LCD_LOW * LCD_COL; 30 31 VIDW00ADD0B0 = ((unsigned long)VideoBuffer); 32 VIDW00ADD1B0 = ((unsigned long)VideoBuffer + LCD_LOW * LCD_COL * 4) & 0xFFFFFF; 33 }
LCD控制器驱动程序有几个重要步骤:
1、初始化IO端口为LCD端口,通过GPICON和GPJCON将相应的为设置成LCD VD[n]模式。
2、使能LCD时钟,HCLK_GATE寄存器默认是打开的,所以不需要设置。
3、设置I/F类型,根据芯片手册上的描述,需要设置SPCON寄存器把I/F模式设置成RGB I/F style。
4、LCD控制器相关寄存器配置,这一步是根据LCD规格书设置时序相关的时间参数,包括水平时间参数和垂直时间参数,下面是我所使用的LCD时间参数:
根据这张图可以确定VSPW的值即为VS pulse width的值,这里取个中间值10。
VFPD的值即为VS Front Porch的值,取典型值22。
VBPD的值没有明确给出,但是通过下图可以知道VBPD = tvb - tvpw,从上图中可以知道tvb就是VS Blanking等于23,tvpw为VSPW = 10,所以可以确定VBPD = 23 - 10。
水平方向这三个方向的分析和垂直方向是一样的,最终得出的数据如下:
1 #define VBPD (23 - 10 - 1) //tvb - tvpw = tvb - VSPW 2 #define VFPD (22 - 1) 3 #define VSPW (10 - 1) 4 #define HBPD (46 - 20 - 1) //thb - thpw = thb - HSPW 5 #define HFPD (210 - 1) 6 #define HSPW (20 - 1) 7 #define LINEVAL (480 - 1) 8 #define HOZVAL (800 - 1)
LINEVAL为屏幕垂直方向的像素,HOZVAL为屏幕水平方向的像素。在芯片手册上可以看到下面的描述,所以上面的参数都要进行减一操作。
最后VIDW00ADD0B0和VIDW00ADD1B0寄存器指定了LCD缓冲区。
二、Linux内核LCD设备驱动
Linux为LCD显示设备提供了一个帧缓冲接口,所谓的帧缓冲其实就是想裸机代码一样在内存中分配一段空间,这段空间就描述了整个屏幕像素点的信息,往缓冲区中与现实点对应的位置写入颜色值,对应的颜色就会在LCD上显示。帧缓冲设备实质上是一个字符设备,主设备号29,对应于/dev/fbn设备文件。
上面简单概括了LCD控制器裸机程序相关寄存器的操作,下面看看Linux内核里面是怎样实现LCD设备驱动的。
Linux内核LCD设备驱动程序在s3cfb.c文件里面,从s3cfb.c里面的module_init()函数入手,找到平台驱动结构体里面的s3cfb_probe()函数,在之前的博客中已经提到,分析平台设备驱动首先要分析的函数就是probe函数。进入s3cfb_probe()函数中,找到了一个s3cfb_init_fbinfo()函数,这个函数又调用了s3cfb_init_hw()函数,在s3cfb_init_hw()函数中调用了s3cfb_set_fimd_info()函数和s3cfb_set_gpio()函数,s3cfb_set_fimd_info()函数中初始化了一个s3cfb_fimd_info_t结构体,里面存储的数据就包括上面裸机程序里面时序相关的时间参数,这个结构体里面包含着所有的LCD控制器相关的参数。s3cfb_set_gpio()函数部分代码如下:
1 int s3cfb_set_gpio(void) 2 { 3 ... 4 5 /* 2.使能LCD时钟 */ 6 /* enable clock to LCD */ 7 val = readl(S3C_HCLK_GATE); 8 val |= S3C_CLKCON_HCLK_LCD; 9 writel(val, S3C_HCLK_GATE); 10 11 /* 3.设置I/F类型 */ 12 /* select TFT LCD type (RGB I/F) */ 13 val = readl(S3C64XX_SPCON); 14 val &= ~0x3; 15 val |= (1 << 0); 16 writel(val, S3C64XX_SPCON); 17 18 /* 1.初始化IO端口为LCD端口 */ 19 /* VD */ 20 for (i = 0; i < 16; i++) 21 s3c_gpio_cfgpin(S3C64XX_GPI(i), S3C_GPIO_SFN(2)); 22 23 for (i = 0; i < 12; i++) 24 s3c_gpio_cfgpin(S3C64XX_GPJ(i), S3C_GPIO_SFN(2)); 25 26 ... 27 28 return 0; 29 }
在s3cfb_set_gpio()函数中实现了LCD控制器裸机操作中的第一、第二和第三个步骤。
退回到s3cfb_probe()函数中,调用了s3cfb_map_video_memory()函数,在这个函数中调用了dma_alloc_writecombine()函数完成帧缓冲设备显示缓冲区的分配。为什么是dma_alloc_writecombine()函数?因为系统对数据的搬移采用的是DMA方式。
再退回到s3cfb_probe()函数,调用了s3cfb_init_registers()函数:
1 /* 4.LCD控制器相关寄存器配置 */ 2 int s3cfb_init_registers(s3cfb_info_t *fbi) 3 { 4 ... 5 6 if (win_num == 0) { 7 s3cfb_fimd.vidcon0 = s3cfb_fimd.vidcon0 & ~(S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE); 8 writel(s3cfb_fimd.vidcon0, S3C_VIDCON0); 9 10 lcd_clock = clk_get(NULL, "lcd"); 11 s3cfb_fimd.vidcon0 |= S3C_VIDCON0_CLKVAL_F((int) (s3cfb_fimd.pixclock)); 12 ... 13 } 14 15 writel(video_phy_temp_f1, S3C_VIDW00ADD0B0 + (0x08 * win_num)); 16 writel(S3C_VIDWxxADD1_VBASEL_F((unsigned long) video_phy_temp_f1 + (page_width + offset) * (var->yres)), S3C_VIDW00ADD1B0 + (0x08 * win_num)); 17 writel(S3C_VIDWxxADD2_OFFSIZE_F(offset) | (S3C_VIDWxxADD2_PAGEWIDTH_F(page_width)), S3C_VIDW00ADD2 + (0x04 * win_num)); 18 19 if (win_num < 2) { 20 writel(video_phy_temp_f2, S3C_VIDW00ADD0B1 + (0x08 * win_num)); 21 writel(S3C_VIDWxxADD1_VBASEL_F((unsigned long) video_phy_temp_f2 + (page_width + offset) * (var->yres)), S3C_VIDW00ADD1B1 + (0x08 * win_num)); 22 } 23 24 switch (win_num) { 25 case 0: 26 writel(s3cfb_fimd.wincon0, S3C_WINCON0); 27 writel(s3cfb_fimd.vidcon0, S3C_VIDCON0); 28 writel(s3cfb_fimd.vidcon1, S3C_VIDCON1); 29 writel(s3cfb_fimd.vidtcon0, S3C_VIDTCON0); 30 writel(s3cfb_fimd.vidtcon1, S3C_VIDTCON1); 31 writel(s3cfb_fimd.vidtcon2, S3C_VIDTCON2); 32 writel(s3cfb_fimd.dithmode, S3C_DITHMODE); 33 writel(s3cfb_fimd.vidintcon0, S3C_VIDINTCON0); 34 writel(s3cfb_fimd.vidintcon1, S3C_VIDINTCON1); 35 writel(s3cfb_fimd.vidosd0a, S3C_VIDOSD0A); 36 writel(s3cfb_fimd.vidosd0b, S3C_VIDOSD0B); 37 writel(s3cfb_fimd.vidosd0c, S3C_VIDOSD0C); 38 writel(s3cfb_fimd.wpalcon, S3C_WPALCON); 39 40 s3cfb_onoff_win(fbi, ON); 41 break; 42 ... 43 } 44 45 local_irq_restore(flags); 46 47 return 0; 48 }
在s3cfb_init_registers()函数里面进行了裸机操作的第四步操作,s3cfb_fimd结构体里面的数据就来源于上面所说的s3cfb_set_fimd_info()函数。
最后调用了register_framebuffer()函数注册了一个帧缓冲设备,剩下的事情就交给内核来完成了。