Linux设备驱动开发 - LCD设备驱动分析

时间:2022-12-10 11:16:36

一、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]模式。
Linux设备驱动开发 - LCD设备驱动分析
2、使能LCD时钟,HCLK_GATE寄存器默认是打开的,所以不需要设置。
Linux设备驱动开发 - LCD设备驱动分析
3、设置I/F类型,根据芯片手册上的描述,需要设置SPCON寄存器把I/F模式设置成RGB I/F style。
Linux设备驱动开发 - LCD设备驱动分析
4、LCD控制器相关寄存器配置,这一步是根据LCD规格书设置时序相关的时间参数,包括水平时间参数和垂直时间参数,下面是我所使用的LCD时间参数:
Linux设备驱动开发 - 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。
Linux设备驱动开发 - LCD设备驱动分析
水平方向这三个方向的分析和垂直方向是一样的,最终得出的数据如下:

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为屏幕水平方向的像素。在芯片手册上可以看到下面的描述,所以上面的参数都要进行减一操作。
Linux设备驱动开发 - LCD设备驱动分析
最后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()函数注册了一个帧缓冲设备,剩下的事情就交给内核来完成了。