Linux下编写显示设备的驱动程序有两种方法:一种是把显示设备抽象成一般的字符设备,驱动程序的写法和一般的字符设备驱动类似;第二种就是帧缓冲设备驱动程序的编写。由于第一种方法不规范,而且实现功能有限,故一般不提倡;而帧缓冲驱动程序比较简单,也容易实现,因而在嵌入式系统里得到了广泛应用。
帧缓冲设备是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备,是Linux为图形设备提供的一个抽象接口,它将显示设备抽象为帧缓冲区。帧缓冲允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。这种操作是抽象的、统一的。应用程序不必关心物理显存的位置、换页机制等等具体细节。
帧缓冲设备属于字符设备,采用“文件层-驱动层”的接口方式。
Linux内核include/fnux/fb.h中定义了帧缓冲设备的驱动层接口fb_info结构体,fb_info定义了当前工作的显示卡的状态和帧缓冲设备的操作函
数, 它仅对内核可见。文件fb.h中还定义了fb_var_screeninfo(显示卡可变特性,这些特性在程序运行期间可以由应用程序动态改变)、fb_fix_screeninfo(显示卡不可变特性,这些特性在硬件初始化时就被定义了, 以后不得修改)、fb_cmap(RGB颜色映射表)等结构体,帧缓冲设备驱动程序主要依靠这几个结构体工作。除了结构体fb_info只能在内核空间访问外,其他三个结构体都可以在用户空间访问。
Linux内核drivers\video\fbmem.c中定义了帧缓冲设备的文件层接口file_operations结构体,它对应用程序可见,结构体中功能函数open()和
release()不需要底层的支持.而read()、write()、mmap()则需要调用fb_get_fix()、fb_get_var()、fb_set_var()、fb_get_cmap、fb_set_cmap()(这些函数位于结构体fb_info中指针fbops指向的结构体变量中)等与底层LCD硬件相关的函数的支持。另一个功能函数是ioctl(),ioctl()是设备驱动程序中对设备的I/O通道进行管理的函数,应用程序通过ioctl()系统调用来调用fb_get_fix()、fb_get_var()、fb_set_var()、fb_set_cmap()、fb_get_cmap()等来获得和设置结构体fb_info中fb_var_screeninfo结构变量var、fb_fix_screeninfo 结构变量fix 和fb_cmap结构变量cmap等变量的信息。在fbmem.c中给出了ioctl()命令和fb_info中结构体fb_ops的成员函数的对应关系如下:
FBIOGET_VSCREENINFO fb_get_var
FBIOPUT_VSCREENINFO fb_set_var
FBIOGET_FSCREENINFO fb_get_fix
FBIOPUTCMAP fb_set_cmap
FBIOGETCMAP fb_get_cmap
用户应用程序只需要调用FBIOXXXX来操作LCD硬件。
文件fbmem.c中还定义了帧缓冲设备底层驱动的管理函数:
register_framebuffer(struct fb_info 3 fb_info)
unregister_framebuffer(struct fb_info 3 fb_info)
帧缓冲设备在驱动层所要做的工作仅仅是对Linux为帧缓冲的驱动层接口fb_info进行初始化.然后调用这两个函数对其注册或注销。帧缓冲设备驱动层接口直接对LCD设备硬件进行操作,而fbmem.c可以记录和管理多个底层设备驱动。
3.1 定义LCD控制器结构体
前面已经说过,LCD控制器的功能是传输图像数据并产生相应的控制信号来驱动LCD显示器,驱动程序需要根据当前具体显示硬件的特性,通过读写一系列的LCD控制寄存器来完成设定显示器分辨率和显示数据的格式,设置控制信号时序,指定显示缓 中区地址等,从而提供给显示设备合适的数据信号和控制信号。文中根据需要为S3C2440A的LCD 控制器定义了一个专用结构体s3c2440fb_mach_info:
struct s3c2440fb_mach_info{
u_long pixclock; /* 像素时钟频率 */
u_char bpp; /* 每像素需要的bit数 */
u_short xres; /* 显示器行分辨率 */
u_short yres; /* 显示器列分辨率 */
u_char hsync_len; /* 行同步信号的长度 */
u_char vsync_len; /* 帧同步信号的长度 */
u_char left_margin;/* 从本行图象数据输出结束到下一行的行同步信号开始之间的像素时钟数 */
u_char right_margin; /* 从行同步信号结束到该行的图象数据开始输出之间的像素时钟数*/
u_char upper_margin;/*从本帧图象数据输出结束到下一帧的帧同步信号开始之间的无效行数 */
u_char lower_margin; /*从帧同步信号结束到该帧图象数据开始输出之间的无效行数*/
u_char sync;
struct s3c2440fb_lcd_reg reg; /*S3C2440A
LCD控制寄存器结构体 */
};
驱动程序通过定义一个s3c2440fb_mach_info结构变量并给该变量赋值来完成LCD控制器的初始化。
3.2 编写结构体fb_info中fb_ops对应的成员函数
对于本嵌入式系统的实现,需要下列5个函数:
static struct fb_ops s3c2440fb_ops={
owner:THIS_MODULE,/*THIS_MODULE宏用来防止驱动模块在使用状态下被卸载 /
fb_get_fix:s3c2440fb_get_fix,
fb_get_var:s3c2440fb_get_var,
fb_set_var:s3c2440fb_set_var,
fb_get_cmap:s3c2440fb_get_cmap,
fb_set_cmap:s3c2440fb_set_cmap,
};
结构体fb_ops在Linux内核include/linux/fb.h中定义。
这些函数都是用来设置和获取驱动层接口fb_info结构体中的成员变量的,前文已提过当应用程序对设备文件进行ioctl操作时会调用它们。对于fb_get_fix()和fb_get_var()应用程序传入的是fb_info中的结构变量fix和var,fb_set_var()函数则是对var变量进行设置。同样fb_get_cmap()和
fb_set_cmap()则是对结构变量cmap内容进行读取和设置。在这5个函数中,fb_set_var()设置了显示设备的显示模式,是最重要的一个函数。文中根据需要为当前显示硬件定义一个专有结构体s3c2440fb_info,该结构体包括一个fb_info结构变量,及其它与所选LCD硬件有关的所有参数.因此结构体fb_ops中成员函数对结构体fb_info的操作实际上就是对结构体s3c2440fb_info的操作。该结构体定义如下:
struct s3c2440fb_info{
struct fb_info fb; /* fb_info结构变量 */
signed int currcon; /* 当前终端控制台的序号*/
u_int max_xres;/*屏幕能显示的最大行分辨率 */
u_int max_yres;/* 屏幕能显示的最大列分辨率 */
struct s3c2440fb_lcd_reg reg;
/*S3C2440A LCD控制寄存器 */
……
/* 其他与LCD硬件有关的参数 */
};
结构体fb_ops中的成员函数流程相似,本文在此仅给出函数s3c2440fb_set_var()的流程图和程序代码。
函数s3c2440fb_set_var()流程图如图6所示:
:
static struct fb_ops s3c2440fb_ops={
owner:THIS_MODULE,/*THIS_MODULE宏用来防止驱动模块在使用状态下被卸载 /
fb_get_fix:s3c2440fb_get_fix,
fb_get_var:s3c2440fb_get_var,
fb_set_var:s3c2440fb_set_var,
fb_get_cmap:s3c2440fb_get_cmap,
fb_set_cmap:s3c2440fb_set_cmap,
};
结构体fb_ops在Linux内核include/linux/fb.h中定义。
这些函数都是用来设置和获取驱动层接口fb_info结构体中的成员变量的,前文已提过当应用程序对设备文件进行ioctl操作时会调用它们。对于fb_get_fix()和fb_get_var()应用程序传入的是fb_info中的结构变量fix和var,fb_set_var()函数则是对var变量进行设置。同样fb_get_cmap()和
fb_set_cmap()则是对结构变量cmap内容进行读取和设置。在这5个函数中,fb_set_var()设置了显示设备的显示模式,是最重要的一个函数。文中根据需要为当前显示硬件定义一个专有结构体s3c2440fb_info,该结构体包括一个fb_info结构变量,及其它与所选LCD硬件有关的所有参数.因此结构体fb_ops中成员函数对结构体fb_info的操作实际上就是对结构体s3c2440fb_info的操作。该结构体定义如下:
struct s3c2440fb_info{
struct fb_info fb; /* fb_info结构变量 */
signed int currcon; /* 当前终端控制台的序号*/
u_int max_xres;/*屏幕能显示的最大行分辨率 */
u_int max_yres;/* 屏幕能显示的最大列分辨率 */
struct s3c2440fb_lcd_reg reg;
/*S3C2440A LCD控制寄存器 */
……
/* 其他与LCD硬件有关的参数 */
};
结构体fb_ops中的成员函数流程相似,本文在此仅给出函数s3c2440fb_set_var()的流程图和程序代码。
函数s3c2440fb_set_var()流程图如图6所示:
图6 函数s3c2440fb_set_var()流程图
函数s3c2440fb_set_var()程序如下:
static int s3c2440fb_set_var(struct fb_var_screeninfo *var,int con,struct fb_info *info){
struct s3c2440fb_info *fbi= (struct s3c2440fb_info *)info; /* 将显示模式读入结构体s3c2440fb_info*/
struct fb_var_screeninfo *dvar= get_con_var(&fbi->fb,con);
int err;
err= s3c2440fb_validate_var(var,fbi); /* 显示模式是否有效 */
if(err) /* 无效返回 */
return err;
dvar->red=fbi->rgb[rgbidx]->red; /* 将显示参数写入结构体fb_var_screeninfo */
dvar->green=fbi->rgb[rgbidx]->green;
dvar->blue=fbi->rgb[rgbidx]->bIue;
dvar->transp=fbi->rgb[rgbidx]->transp;
display->var= *dvar;
……
s3c2440fb_hw_set_var (dvar,fbi); /* 设置
RGB颜色信息,设置S3C2440A的LCD控制寄存器 */
return 0;
}
3.3 编写初始化函数
初始化函数首先初始化LCD控制器和结构体s3c2440fb_info,填充s3c2440fb_info中结构变量fb_info的成员变量,这些成员变量的参数值由LCD
显示器厂商的手册获得。然后通过consistent_alloc函数分配一片连续的空间。显示系统采用的LCD显示方式为320×240,16位彩色。需要分配的显示缓冲区为320×240×16/8=150k字节,缓冲区通常分配在片外SDRAM 中,起始地址和末地址保存在LCD控制器寄存器LCDSADDR1和LCDSADDR2里,最后调用register_framebuffer(&fbi->fb)将fb_info结构变量fb登记入内核。初始化函数如下:
int _init s3c2440fb_init(void)(
struct s3c2440fb_info *fbi;
int ret;
fbi=s3c2440fb_init_fbinfo(); /* 初始化LCD控制器和s3c2440fb_info */
ret=s3c2440fb_map_video_memory (fbi); /*分配150K字节大小的LCD显示缓冲区 */
if(ret) /* 出错返回 */
{
if(fbi)
kfree(fbi);
return ret;
}
s3c2440fb_set_var (&fbi->fb.var,-1,&fbi->fb);
ret=register_framebuffer(&fbi->fb);/* 将fb注册到内核 */
if(ret) /* 出错返回 */
{
if(fbi)
kfree(fbi);
return ret;
}
printk ("Installed S3C2440 frame bufferkn");/*在控制台显示安装显示驱动程序成功 */
MOD_INC_USE_COUNT;/* 该宏用来管理自己被使用的计数,模块在被使用时,是不允许被卸载的 */
return 0;
}
驱动程序嵌入到内核有两种方式:一种是直接编译入内核,随Linux启动的时候加载;另一种是编译成模块,动态加载。如果要将其直接编译入Linux内核,则需要将源代码文件拷贝到Linux内核源代码的相应路径里,并修改Makefile文件和config.in文件,这种方法会增加内核的大小,而且不能动态卸载,不利于调试,所以目前一般推荐采用第二种方式,如果这样,在本驱动程序的最后还需要加上宏module_init(s3c2440fb_init);这是告诉编译器该驱动程序的入口地址为初始化函数s3c2440fb_init()。需要注意的是初始化函数必须在宏module_init(s3c2440fb_init)使用前定义,否则会出现编译错误。而且在编译时至少要加上-D_KERNEL_ -DMODULE -DLINUX这几个参数,编译完成后通过insmod命令将驱动模块加载进内核, 通过rmmod命令卸载驱动模块。
Linux将所有的设备都当作文件进行处理,各种设备通常以文件的形式放在/dev目录下。帧缓冲设备和其它位于/dev目录下面的设备类似,其驱动程序的设备文件一般是/dev/fb0、/dev/fb1等等。在应用程序中,操作/dev/fb的一般流程如图7所示:
图7 应用程序对帧缓冲设备的操作流程
其典型应用程序如下:
Main()
{
int fbfd=O;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
unsigned char *fbp;
fbfd=open ("/dev/fb0",O_RDWR);
/* 打开设备文件 */
if(!fbfd){/* 失败返回 */
printf("Error:cannot open framebuffer device.\n ");
exit(1);
}
Printf ("The framebuffer device was opened successfully.\n");
ioctl (fbfd,FBIOGET_FSCREENINFO,&finfo);
/* 获取显示设备特性 */
ioctl(fbfd,FBIOGET_VSCREENINFO,&vinfo);
screensize=vinfo.xres*vinfo.yres*vinfo.bits_per_pixel/8 /* 计算屏幕缓冲区的大小 */
fbp= (unsigned char*)mmap (0,screensize,PORT_READ|PORT_WRITE,MAP_SHARED,fbfd,0);/* 将屏幕缓冲区映射到用户地址空间,然后应用程序就可以通过fbp访问缓冲区了*/
memset (fbp,0,screensize); /* 用memset将屏幕清空 */
}
在显示系统硬件设计中,显示硬件的整体设计考虑全面是设计过程中的重点,这就要求对显示硬件的各特性参数有全面的了解。软件设计中,由于其中涉及到的数据结构比较多,同时又和控制台联系在一起,有一定的难度。只有在深刻理解各个变量和操作函数的具体意义后, 才能分析编写自己需要的LCD驱动程序。在编写的过程中,最好的参考莫过于Linux内核drivers/video目录下的源代码。