1. 背景知识
根据数据手册来描述一下这个集成在S3C2440内部的LCD控制器:
a:LCD控制器由REGBANK、LCDCDMA、TIMEGEN、VIDPRCS寄存器组成;
b:REGBANK由17个可编程的寄存器组和一块256*16的调色板内存组成,它们用来配置LCD控制器的;
c:LCDCDMA是一个专用的DMA,它能自动地把在侦内存中的视频数据传送到LCD驱动器,通过使用这个DMA通道,视频数据在不需要CPU的干预的情况下显示在LCD屏上;
d:VIDPRCS接收来自LCDCDMA的数据,将数据转换为合适的数据格式,比如说4/8位单扫,4位双扫显示模式,然后通过数据端口VD[23:0]传送视频数据到LCD驱动器;
e:TIMEGEN由可编程的逻辑组成,他生成LCD驱动器需要的控制信号,比如VSYNC、HSYNC、VCLK和LEND等等,而这些控制 信号又与REGBANK寄存器组中的LCDCON1/2/3/4/5的配置密切相关,通过不同的配置,TIMEGEN就能产生这些信号的不同形态,从而支 持不同的LCD驱动器(即不同的STN/TFT屏)
LCD提供的外部接口信号:
VSYNC/VFRAME/STV:垂直同步信号(TFT)/帧同步信号(STN)/SEC TFT信号;
HSYNC/VLINE/CPV:水平同步信号(TFT)/行同步脉冲信号(STN)/SEC TFT信号;
VCLK/LCD_HCLK:象素时钟信号(TFT/STN)/SEC TFT信号;
VD[23:0]:LCD
像素数据输出端口(TFT/STN/SEC TFT);
VDEN/VM/TP:数据使能信号(TFT)/LCD驱动交流偏置信号(STN)/SEC TFT 信号;
LEND/STH:行结束信号(TFT)/SEC TFT信号;
LCD_LPCOE:SEC TFT OE
信号;
LCD_LPCREV:SEC TFT REV
信号;
LCD_LPCREVB:SEC TFT REVB信
号。
VBPD(vertical back porch):
表示在一帧图像开始时,垂直同步信号以后的无效的行数,对应驱动中的upper_margin;
VFBD(vertical front porch):表示在一帧图像结束后,垂直同步信号以前的无效的行数,对应驱动中的lower_margin;
VSPW(vertical sync pulse width):表示垂直同步脉冲的宽度,用行数计算,对应驱动中的vsync_len;
HBPD(horizontal back porch):表示从水平同步信号开始到一行的有效数据开始之间的VCLK的个数,对应驱动中的left_margin;
HFPD(horizontal front porth):表示一行的有效数据结束到下一个水平同步信号开始之间的VCLK的个数,对应驱动中的right_margin;
HSPW(horizontal sync pulse width):表示水平同步信号的宽度,用VCLK计算,对应驱动中的hsync_len;
作为帧同步信号的VSYNC,每发出一个脉冲,都意味着新的一屏图像数据开始发送。而作为行同步信号的HSYNC,每发出一脉冲意味着新的一行图像资料开始发送。在帧同步和行同步的头尾留有回扫时间,这样的时序安排起源于CRT显示器电子枪偏转所需要的时间,但后来成为实际上的工业标准,因此TFT屏也包含了回扫时间。
所有显示器显示图像的原理都是从上到下,从左到右的。这是什么意思呢?这么说吧,一副图像可以看做是一个矩形,由很多排列整齐的一行一行点组成,这些点称之为像素。那么这幅图在LCD上的显示原理就是:
A:显示指针从矩形左上角的第一行第一个点开始,一个点一个点的在LCD上显示,在上面的时序图上用时间线表示就为VCLK,我们称之为像素时钟信号;
B:当显示指针一直显示到矩形的右边就结束这一行,那么这一行的动作在上面的时序图中就称之为1 Line;
C:接下来显示指针又回到矩形的左边从第二行开始显示,注意,显示指针在从第一行的右边回到第二行的左边是需要一定的时间的,我们称之为行切换;
D:如此类推,显示指针就这样一行一行的显示至矩形的右下角才把一副图显示完成。因此,这一行一行的显示在时间线上看,就是时序图上的HSYNC;
E:然 而,LCD的显示并不是对一副图像快速的显示一下,为了持续和稳定的在LCD上显示,就需要切换到另一幅图上(另一幅图可以和上一副图一样或者不一样,目的只是为了将图像持续的显示在LCD上)。那么这一副一副的图像就称之为帧,在时序图上就表示为1 Frame,因此从时序图上可以看出1 Line只是1 Frame中的一行;
F:同样的,在帧与帧切换之间也是需要一定的时间的,我们称之为帧切换,那么LCD整个显示的过程在时间线上看,就可表示为时序图上的VSYNC
LCD控制器时序参数可以用下图形象的表示出来:
帧缓冲是Linux为显示设备提供的一个接口,它把一些显示设备描述成一个缓冲区,允许应用程序通过 FrameBuffer定义好的接口访问这些图形设备,从而不用去关心具体的硬件细节。对于帧缓冲设备而言,只要在显示缓冲区与显示点对应的区域写入颜色 值,对应的颜色就会自动的在屏幕上显示。
通过帧缓冲显示汉字点阵,成为linux汉化的唯一可能性,帧缓冲设备是标准字符设备,主设备号为29,对应于/dev/fbn设备文件。
2. 帧缓冲(FrameBuffer)设备驱动结构
帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/linux/major.h,但FB_MAJOR是在include\linux\fb.h中定义,次设备号定义帧缓冲的个数,最大允许有32个FrameBuffer,定义在/include/linux/fb.h中的FB_MAX,也就是最大支持显示器的个数。对应于文件系统下/dev /fb%d设备文件
3. 帧缓冲设备驱动在Linux子系统中的结构如下:
帧缓冲设备提供给用户空间的file_operations结构体由fbmem.c的file_operation提供,而特定帧缓冲设备结构体的注册、注销以及其中成员的维护,尤其是fb_ops中成员函数的实现则由xxxfb.c文件实现,fb_ops中成员函数最终会操作LCD控制器硬件寄存器。
4. 帧缓冲相关的重要数据结构
从帧缓冲设备驱动程序结构 看,该驱动主要跟fb_info结构体有关,该结构体记录了帧缓冲设备的全部信息,包括设备的设置参数、状态以及对底层硬件操作的函数指针。在Linux 中,帧缓冲设备最关键的一个数据结构体是fb_info(为了便于记忆,我们把它简称为“FBI”),FBI中包括了关于帧缓冲设备属性和操作的完整描述。每一个帧缓冲设备都必须对应一个fb_info,fb_info在/linux/fb.h中的定义如下:(只列出重要的一些)
4.1 fb_info结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
struct
fb_info {
int
node;
int
flags;
struct
mutex
lock
;
/* 用于open/release/ioctl的锁*/
struct
fb_var_screeninfo
var
;
/*LCD可变参数*/
struct
fb_fix_screeninfo fix;
/*LCD固定参数*/
struct
fb_monspecs monspecs;
/*LCD显示器标准*/
struct
work_struct queue;
/*帧缓冲事件队列*/
struct
fb_pixmap pixmap;
/*图像硬件mapper*/
struct
fb_pixmap sprite;
/*光标硬件mapper*/
struct
fb_cmap cmap;
/*当前的颜色表*/
struct
fb_videomode *mode;
/*当前的显示模式*/
#ifdef CONFIG_FB_BACKLIGHT
struct
backlight_device *bl_dev;
/*对应的背光设备*/
struct
mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
/*背光调整*/
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct
delayed_work deferred_work;
struct
fb_deferred_io *fbdefio;
#endif
struct
fb_ops *fbops;
/*对底层硬件操作的函数指针*/
struct
device *device;
struct
device *dev;
/*fb设备*/
int
class_flag;
#ifdef CONFIG_FB_TILEBLITTING
struct
fb_tile_ops *tileops;
/*图块Blitting*/
#endif
char
__iomem *screen_base;
/*虚拟基地址*/
unsigned
long
screen_size;
/*LCD IO映射的虚拟内存大小*/
void
*pseudo_palette;
/*伪16色颜色表*/
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state;
/*LCD的挂起或恢复状态*/
void
*fbcon_par;
void
*par;
};
|
其中,比较重要的成员有struct fb_var_screeninfo var、struct fb_fix_screeninfo fix和struct fb_ops *fbops,他们也都是结构体,下面我们一个一个的来看。
4.2 struct fb_var_screeninfo
fb_var_screeninfo结构体主要记录用户可以修改的控制器的参数,比如屏幕的分辨率和每个像素的比特数等。例如:fb_var_screeninfo中的xres定义屏幕一行有多少个点,yres定义一屏幕一列有多少个点,bits_per_pixel定义每个点用多少个自己表示。该结构体定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
struct
fb_var_screeninfo {
__u32 xres;
/*可见屏幕一行有多少个像素点*/
__u32 yres;
/*可见屏幕一列有多少个像素点*/
__u32 xres_virtual;
/*虚拟屏幕一行有多少个像素点*/
__u32 yres_virtual;
/*虚拟屏幕一列有多少个像素点*/
__u32 xoffset;
/*虚拟到可见屏幕之间的行偏移*/
__u32 yoffset;
/*虚拟到可见屏幕之间的列偏移*/
__u32 bits_per_pixel;
/*每个像素的位数即BPP*/
__u32 grayscale;
/*非0时,指的是灰度*/
struct
fb_bitfield red;
/*fb缓存的R位域*/
struct
fb_bitfield green;
/*fb缓存的G位域*/
struct
fb_bitfield blue;
/*fb缓存的B位域*/
struct
fb_bitfield transp;
/*透明度*/
__u32 nonstd;
/* != 0 非标准像素格式*/
__u32 activate;
__u32 height;
/*高度*/
__u32 width;
/*宽度*/
__u32 accel_flags;
/*定时:除了pixclock本身外,其他的都以像素时钟为单位*/
__u32 pixclock;
/*像素时钟(皮秒)*/
__u32 left_margin;
/*行切换,从同步到绘图之间的延迟*/
__u32 right_margin;
/*行切换,从绘图到同步之间的延迟*/
__u32 upper_margin;
/*帧切换,从同步到绘图之间的延迟*/
__u32 lower_margin;
/*帧切换,从绘图到同步之间的延迟*/
__u32 hsync_len;
/*水平同步的长度*/
__u32 vsync_len;
/*垂直同步的长度*/
__u32 sync;
__u32 vmode;
__u32 rotate;
__u32 reserved[5];
/*保留*/
};
|
4.3 fb_fix_screeninfo结构体
fb_fix_screeninfo结构体又主要记录用户不可以修改的控制器的参数,比如屏幕缓冲区的物理地址和长度等。当帧缓冲设备进行映射操作的时候,就是从fb_fix_screeninfo中取得缓冲区物理地址。该结构体的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
struct
fb_fix_screeninfo {
char
id[16];
/*字符串形式的标示符 */
unsigned
long
smem_start;
/*fb缓存的开始位置 */
__u32 smem_len;
/*fb缓存的长度 */
__u32 type;
/*看FB_TYPE_* */
__u32 type_aux;
/*分界*/
__u32 visual;
/*看FB_VISUAL_* */
__u16 xpanstep;
/*如果没有硬件panning就赋值为0 */
__u16 ypanstep;
/*如果没有硬件panning就赋值为0 */
__u16 ywrapstep;
/*如果没有硬件ywrap就赋值为0 */
__u32 line_length;
/*一行的字节数 */
unsigned
long
mmio_start;
/*内存映射IO的开始位置*/
__u32 mmio_len;
/*内存映射IO的长度*/
__u32 accel;
__u16 reserved[3];
/*保留*/
};
|
4.4 fb_ops结构体
fb_ops结构体是对底层硬件操作的函数指针,该结构体中定义了对硬件的操作有:(这里只列出了常用的操作)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
struct
fb_ops {
struct
module *owner;
//检查可变参数并进行设置
int
(*fb_check_var)(
struct
fb_var_screeninfo *
var
,
struct
fb_info *info);
//根据设置的值进行更新,使之有效
int
(*fb_set_par)(
struct
fb_info *info);
//设置颜色寄存器
int
(*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp,
struct
fb_info *info);
//显示空白
int
(*fb_blank)(
int
blank,
struct
fb_info *info);
//矩形填充
void
(*fb_fillrect) (
struct
fb_info *info,
const
struct
fb_fillrect *rect);
//复制数据
void
(*fb_copyarea) (
struct
fb_info *info,
const
struct
fb_copyarea *region);
//图形填充
void
(*fb_imageblit) (
struct
fb_info *info,
const
struct
fb_image *image);
};
|
5. 帧缓冲设备作为平台设备
S3C2440的LCD控制器被集成在芯片的内部作为一个相对独立的单元,所以Linux把它看做是一个平台设备,故在内核代码/arch/arm/plat-s3c24xx/devs.c中定义有LCD相关的平台设备及资源,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/* LCD Controller */
//LCD控制器的资源信息
static
struct
resource s3c_lcd_resource[] = {
[0] = {
.start = S3C24XX_PA_LCD,
//控制器IO端口开始地址
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
//控制器IO端口结束地址
.flags = IORESOURCE_MEM,
//标识为LCD控制器IO端口,在驱动中引用这个就表示引用IO端口
},
[1] = {
.start = IRQ_LCD,
//LCD中断
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
//标识为LCD中断
}
};
static
u64 s3c_device_lcd_dmamask = 0xffffffffUL;
struct
platform_device s3c_device_lcd = {
.name =
"s3c2410-lcd"
,
//作为平台设备的LCD设备名
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
//资源数量
.resource = s3c_lcd_resource,
//引用上面定义的资源
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
EXPORT_SYMBOL(s3c_device_lcd);
//导出定义的LCD平台设备,好在mach-smdk2440.c的smdk2440_devices[]中添加到平台设备列表中
|
除此之外,Linux还在/arch/arm/mach-s3c2410/include/mach/fb.h中为LCD平台设备定义了一个 s3c2410fb_mach_info结构体,该结构体主要是记录LCD的硬件参数信息(比如该结构体的s3c2410fb_display成员结构中 就用于记录LCD的屏幕尺寸、屏幕信息、可变的屏幕参数、LCD配置寄存器等),这样在写驱动的时候就直接使用这个结构体。下面,我们来看一下内核是如果使用这个结构体的。在/arch/arm/mach-s3c2440/mach-smdk2440.c中定义有:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
/* LCD driver info */
//LCD硬件的配置信息,注意这里我使用的LCD是NEC 3.5寸TFT屏,这些参数要根据具体的LCD屏进行设置
static
struct
s3c2410fb_display smdk2440_lcd_cfg __initdata = {
//这个地方的设置是配置LCD寄存器5,这些宏定义在regs-lcd.h中,计算后二进制为:111111111111,然后对照数据手册上LCDCON5的各位来看,注意是从右边开始
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
//TFT类型
/* NEC 3.5'' */
.width = 240,
//屏幕宽度
.height = 320,
//屏幕高度
//以下一些参数在上面的时序图分析中讲到过,各参数的值请跟据具体的LCD屏数据手册结合上面时序分析来设定
.pixclock = 100000,
//像素时钟
.xres = 240,
//水平可见的有效像素
.yres = 320,
//垂直可见的有效像素
.bpp = 16,
//色位模式
.left_margin = 19,
//行切换,从同步到绘图之间的延迟
.right_margin = 36,
//行切换,从绘图到同步之间的延迟
.hsync_len = 5,
//水平同步的长度
.upper_margin = 1,
//帧切换,从同步到绘图之间的延迟
.lower_margin = 5,
//帧切换,从绘图到同步之间的延迟
.vsync_len = 1,
//垂直同步的长度
};
static
struct
s3c2410fb_mach_info smdk2440_fb_info __initdata = {
.displays = &smdk2440_lcd_cfg,
//应用上面定义的配置信息
.num_displays = 1,
.default_display = 0,
.gpccon = 0xaaaa555a,
//将GPC0、GPC1配置成LEND和VCLK,将GPC8-15配置成VD0-7,其他配置成普通输出IO口
.gpccon_mask = 0xffffffff,
.gpcup = 0x0000ffff,
//禁止GPIOC的上拉功能
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaaaaaaaa,
//将GPD0-15配置成VD8-23
.gpdcon_mask = 0xffffffff,
.gpdup = 0x0000ffff,
//禁止GPIOD的上拉功能
.gpdup_mask = 0xffffffff,
.lpcsel = 0x0,
//这个是三星TFT屏的参数,这里不用
};
|
下面,我们再看一下在驱动中是如果引用 到s3c2410fb_mach_info结构体的(注意上面讲的是在内核中如何使用的)。在mach-smdk2440.c中有:
1
2
3
4
5
6
7
8
9
10
11
|
//S3C2440初始化函数
static
void
__init smdk2440_machine_init(
void
)
{
//调用该函数将上面定义的LCD硬件信息保存到平台数据中
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
|
s3c24xx_fb_set_platdata定义在plat-s3c24xx/devs.c中:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void
__init s3c24xx_fb_set_platdata(
struct
s3c2410fb_mach_info *pd)
{
struct
s3c2410fb_mach_info *npd;
npd = kmalloc(
sizeof
(*npd), GFP_KERNEL);
if
(npd) {
memcpy(npd, pd,
sizeof
(*npd));
//这里就是将内核中定义的s3c2410fb_mach_info结构体数据保存到LCD平台数据中,所以在写驱动的时候就可以直接在平台数据中获取s3c2410fb_mach_info结构体的数据(即LCD各种参数信息)进行操作
s3c_device_lcd.dev.platform_data = npd;
}
else
{
printk(KERN_ERR
"no memory for LCD platform data\n"
);
}
}
|
一个小知识:不知大家有没有留意,在平台设备驱动中,platform_data可以保存各自平台设备实例的数据,但这些数据的类型都是不同的,为什么都可以保存?这就要看看platform_data的定义,定义在/linux/device.h中,void *platform_data是一个void类型的指针,在Linux中void可保存任何数据类型。
上面平台驱动如何应用平台设备的数据以达到板与驱动分离的目地可有下图表示: