LCD驱动移植在在mini2440(linux2.6.29)和FS4412(linux3.14.78)上实现对比(deep dive)
1.Linux帧缓冲子系统
帧缓冲(FrameBuffer)是Linux为显示设备提供的一个接口,用户可以将帧缓冲看成是显示内存的一种映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反映到屏幕上,这种操作是抽象和统一的,用户不必关心显存的位置、换页机制等具体细节,这些都是由FrameBuffer设备驱动来实现,帧缓冲把显示设备描述成一个缓冲区,允许应用程序通过帧缓冲定义好的接口访问这些图形设备,从而不用关心具体的硬件细节。个人感觉,更抽象一点,帧缓冲从本质上是图形设备的硬件抽象,对于我们开发者而言,帧缓冲是一种显示缓存,向显示缓存写入特定格式的数据就意味着向屏幕输出内容,通过不断向帧缓存中写入数据,显示控制器会自动从帧缓冲中取数据并显示出来。FrameBuffer的设备文件一般是dev/fb0、dev/fb1等,最多支持32个设备,FrameBuffer是个字符设备,主设备号是29,对应于/dev/fb%d设备文件,对于我们驱动工程师而言,FrameBuffer设备和其他的文件没有区别,可以通过配置对FrameBuffer设备文件完成对硬件的参数设置,Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文件为fbcon.c,在这个目录下还有与各种显卡驱动相关的源文件。
在应用程序中,一般通过将 FrameBuffer 设备映射到进程地址空间的方式使用,比如下面的程序就打开 /dev/fb0 设备,并通过 mmap 系统调用进行地址映射,随后用 memset 将屏幕清空(这里假设显示模式是 1024x768-8 位色模式,线性内存模式):
int fb;
unsigned char* fb_mem;
fb = open ("/dev/fb0", O_RDWR);
fb_mem = mmap (NULL, 1024*768, PROT_READ|PROT_WRITE,MAP_SHARED,fb,0);
memset (fb_mem, 0, 1024*768); //这个命令应该只有在root可以执行
Framebuffer驱动实现:
一般来说,应用程序通过内核对Framebuffer的控制,主要有以下3种方式:
1,读/写 /dev/fb相当于读/写屏幕缓冲区;
2,通过映射操作,可将屏幕缓冲区的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图;
3,I/O控制对于帧缓冲设备,设备文件的ioctl()函数可读取/设置显示设备及屏幕的参数,如分辨率、显示颜色数、屏幕大小等,ioctl()函数由底层的驱动程序完成;
Linux帧缓冲子系统的层次结构图如下所示,由帧缓冲设备层和控制器驱动组成,帧缓冲设备层在drivers/video/fbmem.c中实现,向上给应用程序提供设备文件结构操作接口,向下提供硬件操作接口,硬件操作接口需要根据具体的LCD控制器硬件来实现,这也是控制器驱动要完成的工作,对比IIC和SPI的软件层次结构,相对较为简单,帧缓冲驱动程序主要依靠四个数据结构,分别是fb_info、fb_var_screeninfo、fb_fix_screeninfo和fb_monospecs,后3个数据结构可以在用户空间访问,数据结构fb_info只能在内核空间访问。
2.LCD工作时序分析:
(1)显示指针从矩形左上角的第一行第一个点开始,一个点一个点的在LCD上显示,在上面的时序图上用时间线表示就为VCLK,我们称之为像素时钟信号;
(2) 当显示指针一直显示到矩形的右边就结束这一行,那么这一行的动作在上面的时序图中就称之为1 Line;
(3)接下来显示指针又回到矩形的左边从第二行开始显示,注意,显示指针在从第一行的右边回到第二行的左边是需要一定的时间的,我们称之为行切换;
(4)如此类推,显示指针就这样一行一行的显示至矩形的右下角才把一副图显示完成。因此,这一行一行的显示在时间线上看,就是时序图上的HSYNC;
(5)然而,LCD的显示并不是对一副图像快速的显示一下,为了持续和稳定的在LCD上显示,就需要切换到另一幅图上(另一幅图可以和上一副图一样或者不一样,目的只是为了将图像持续的显示在LCD上)。那么这一副一副的图像就称之为帧,在时序图上就表示为1 Frame,因此从时序图上可以看出1 Line只是1 Frame中的一行;
(6)同样的,在帧与帧切换之间也是需要一定的时间的,我们称之为帧切换,那么LCD整个显示的过程在时间线上看,就可表示为时序图上的VSYNC。
参照下图: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信号;
上面时序图中上各时钟延时参数的含义如下:(这些参数的值,LCD产生厂商会提供相应的数据手册):
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;
注意以下细节:(1)VSYNC信号有效时,表示一帧数据的开始
(2)VSPW表示VSYNC信号的脉冲宽度为(VSPW+1)个HSYNC信号周期,即(VSPW+1)行,这(VSPW+1)行的数据无效。
(3)VSYNC信号脉冲之后,还要经过(VBPD+1)个HSYNC信号周期,有效的行数据才出现。所以,在VSYNC信号有效后要经过(VSPW+1+VBPD+1)个无效的行,第一个有效行才出现,对应上边框。
(4)随后即连续发出(LINEVAL+1)行的有效数据。
(5)最后是(VFPD+1)个无效的行,它对应下边框,完整的一帧结束,紧接着就是下一帧数据了。
下面我们深入到一行中像素数据的传输过程,它与上面行数据的传输相似:
(1)HSYNC信号有效时,表示一行数据的开始
(2)HSPW表示HSYNC信号的脉冲宽度为(HSPW+1)个VCLK信号周期,即(HSPW+1)个像素,这(HSPW+1)个像素的数据无效。
(3)HSYNC信号脉冲之后,还要经过(HBPD+1)个VCLK信号周期,有效的像素数据才出现。所以,在HSYNC有效之后,总共要经过(HSPW+1+HBPD+1)个无效的像素,它对应左边框,第一个有效的像素才出现。
(4)随后即连续发出(HOZVAL+1)个像素的有效数据。
(5)最后是(HFPD+1)个无效的像素,它对应右边框,完整的一行结束
3.s3c2440和Exynos4412硬件资源分析
s3c2440 LCD驱动采用Platform设备驱动模型实现,内核自带的LCD驱动模型代码路径是drivers/vide0/s3c2410fb.c,Exynos 4412的驱动代码里,framebuffer主要代码在driver/video/,文件名是s3c-fb.c Exynos 4412显示控制器可以控制0~5个windows,代码中分给它们分别编号win0, win1,win2......,这里一个window就对应一个独立的驱动控制个体, 每个framebuffer有自己的一个FBI(fb_info)结构,显示控制器对应的抽象结构是s3c_fb结构体,window对应的抽象结构是s3c_fb_win结构体。s3c2440内部LCD控制器结构图如下,根据数据手册:
1、LCD控制器由REGBANK、LCDCDMA、TIMEGEN、VIDPRCS寄存器组成;
2、REGBANK由17个可编程的寄存器组和一块256*16的调色板内存组成,它们用来配置LCD控制器的;
3、LCDCDMA是一个专用的DMA,它能自动地把在侦内存中的视频数据传送到LCD驱动器,通过使用这个DMA通道,视频数据在不需要CPU的干预的情况下显示在LCD屏上;
4、VIDPRCS接收来自LCDCDMA的数据,将数据转换为合适的数据格式,比如说4/8位单扫,4位双扫显示模式,然后通过数据端口VD[23:0]传送视频数据到LCD驱动器;
5、TIMEGEN由可编程的逻辑组成,他生成LCD驱动器需要的控制信号,比如VSYNC、HSYNC、VCLK和LEND等等,而这些控制信号又与REGBANK寄存器组中的LCDCON1/2/3/4/5的配置密切相关,通过不同的配置,TIMEGEN就能产生这些信 号的不同形态,从而支持不同的LCD驱动器(即不同的STN/TFT屏)。
Exynos4412内部集成了一个FIMD(Fully Interactive Mobile Display),在FS4412开发板上使用的是rgb接口连接外部的LCD屏,相关的电路原理图如下,
3.帧缓冲子系统中数据结构分析
Fb_info:该结构体重要是用来描述帧缓冲设备的属性和操作的完整描述,包括了设备的设置参数,状态以及操作函数指针,每个缓冲设备都必须对应一个fb_info
/* struct fb_info 结构体 */
struct fb_info {
int node;
int flags;
struct mutex lock; /* 用于 open/release/ioctl的锁 */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
struct fb_monspecs monspecs; /* 显示器标准 */
struct work_struct queue; /* Framebuffer event queue 帧缓冲事件队列 */
struct fb_pixmap pixmap; /* Image hardware mapper 图像硬件mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper 光标硬件mapper */
struct fb_cmap cmap; /* Current cmap 目前颜色表 */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode 目前video模式 */
#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device 对应的背光设备 */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev;
/* Backlight level curve 背光调整 */
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; /* This is the parent 父设备 */
struct device *dev; /* This is this fb device fb设备 */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
char __iomem *screen_base; /* Virtual address */
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
/* we need the PCI or similiar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
resource_size_t aperture_base;
resource_size_t aperture_size;
};
Fb_ops:该结构体是fb_info中的成员变量,主要是用来为指向底层操作的函数的指针,fb_ops结构体中的成员函数fb_check_var是用来检查可以修改的屏幕参数并调整到最合适的值;成员函数fb_set_par是用来使得用户设置的屏幕参数可以在硬件上生效。
/* fb_ops 结构体 */
struct fb_ops {
/* open/release and usage marking */
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
/* 对于非线性布局的/常规内存映射无法工作的帧缓冲设备*/
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos);
/* 调整可变参数,并调整到支持的值 */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
/* 根据info->var设置video模式 */
int (*fb_set_par)(struct fb_info *info);
/* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);
/* 批量设置color寄存器,设置颜色表 */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/* 显示空白 */
int (*fb_blank)(int blank, struct fb_info *info);
/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, 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);
/* 绘制光标 */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
/* 旋转显示 */
void (*fb_rotate)(struct fb_info *info, int angle);
/* 等待blit空闲(可选) */
int (*fb_sync)(struct fb_info *info);
/* fb特定的ioctl(可选) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,unsigned long arg);
/* 处理32位的compat ioctl(可选) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,unsigned long arg);
/* fb特定的mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);