LCD 设备驱动

在多媒体应用的推动下,彩色 LCD 越来越多地应用到了嵌入式系统中,掌上电脑(PDA),手机等多采用 TFT 显示器件,支持彩色图形界面,能显示图片并进行视频媒体播放。帧缓冲(Framebuffer)是 Linux 为显示设备提供的一个接口,它允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。

LCD 硬件原理

利用液晶制成的显示器称为 LCD,依据驱动方式可分为静态驱动、简单矩阵驱动以及主动矩阵驱动 3 种。其中,简单矩阵型又可再细分扭转向列型(TN)和超扭转式向列型(STN)两种,而主动矩阵型则以薄膜式晶体管型(TFT)为主流。下表列出了 TN、STN 和 TFT 显示器的区别。

LCD 设备驱动_第1张图片

TN 型液晶显示技术是 LCD 中最基本的,其他种类的 LCD 都以 TN 型为基础改进而得。TN 型 LCD 显示质量很差,色彩单一,对比度低,反映速度很慢,故主要用于简单的数字符与文字的显示,如电子表及电子计算器等。

STN LCD 的显示原理与 TN 类似,区别在于 TN 型的液晶分子将入射光旋转 90°,而 STN 则可将入射光旋转 180°~270°。STN 改善了 TN 视角狭小的缺点,并提高了对比度,显示品质较 TN 高。

STN 搭配彩色滤光片,将单色显示矩阵的任一像素分成 3 个子像素,分别透过彩色滤光片显示红、绿、蓝三原色,再经由三原色按比例调和,显示出逼近全彩模式的色彩。STN 显示的画面色彩对比度仍较小,反应速度也较慢,可以作为一般的操作显示接口。

随后出现的 DSTN 通过双扫描方式来显示,显示效果相对 STN 而言有了较大幅度的提高。DSTN 的反应速度可达到 100ms,但是在电场反复改变电压的过程中,每一像素的恢复过程较慢。因此,当在屏幕画面快速变化时,会产生“拖尾”现象。

TN 与 STN 型液晶显示器都是使用场电压驱动方式,如果显示尺寸加大,中心部位对电极变化的反应时间就会拉长,显示器的速度跟不上。为了解决这个问题,主动式矩阵驱动被提出,主动式 TFT 型的液晶显示器的结构较为复杂,它包括背光管、导光板、偏光板、滤光板、玻璃基板、配向膜、液晶材料和薄膜式晶体管等。

在 TFT 型 LCD 中,晶体管矩阵依显示信号开启或关闭液晶分子的电压,使液晶分子轴转向而成“亮”或“暗”的对比,避免了显示器对电场效应的依靠。因此,TFTLCD 的显示质量较 TN/STN 更佳,画面显示对比度可达 150:1 以上,反应速度逼近 30ms甚至更快,适用于 PDA、笔记本电脑、数码相机、MP4 等。

一块 LCD 屏显示图像不但需要 LCD 驱动器,还需要有相应的 LCD 控制器。通常 LCD 驱动器会以 COF/COG 的形式与 LCD 玻璃基板制作在一起, LCD 控制器则由外部电路来实现。许多 MCU 内部直接集成了 LCD 控制器,通过 LCD控制器可以方便地控制 STN 和 TFT 屏。

TFT 屏是目前嵌入式系统应用的主流,下图给出了 TFT 屏的典型时序。时序图中的 VCLK、HSYNC 和 VSYNC 分别为像素时钟信号(用于锁存图像数据的像素时钟)、行同步信号和帧同步信号,VDEN 为数据有效标志信号,VD 为图像的数据信号。

LCD 设备驱动_第2张图片

作为帧同步信号的 VSYNC,每发出一个脉冲,都意味着新的一屏图像数据开始发送。而作为行同步信号的 HSYNC,每发出一个脉冲都表明新的一行图像资料开始发送。在帧同步以及行同步的头尾都必须留有回扫时间。这样的时序安排起源于 CRT显示器电子枪偏转所需要的时间,但后来成为实际上的工业标准,因此 TFT 屏也包含了回扫时间。

下图给出了 LCD 控制器中应该设置的 TFT 屏的参数,其中的上边界和下边界即为帧切换的回扫时间,左边界和右边界即为行切换的回扫时间,水平同步和垂直同步分别是行和帧同步本身需要的时间。xres 和 yres 则分别是屏幕的水平和垂直分辨率,常见的嵌入式设备的 LCD 分辨率主要为 320*240、640*480 等。

LCD 设备驱动_第3张图片


帧缓冲

帧缓冲的概念
帧缓冲(framebuffer)是 Linux 系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关心物理显示缓冲区的具体位置及存放方式,这些都由帧缓冲设备驱动本身来完成。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域写入颜色值,对应的颜色会自动在屏幕上显示,下面将讲解显示缓冲区与显示点的对应关系


显示缓冲区与显示点
在帧缓冲设备中,对屏幕显示点的操作通过读写显示缓冲区来完成,在不同的色彩模式下,显示缓冲区和屏幕上的显示点有不同的对应关系, 下表分别给出了 16 级灰度、8 位色和 16 位情况下显示缓冲区与显示点的对应关系。


问题:上面这幅图的用意是什么???

Linux 帧缓冲相关数据结构与函数
1.fb_info 结构体
帧缓冲设备最关键的一个数据结构体是 fb_info 结构体(为了便于记忆,我们把它简称为“FBI”),FBI 中包括了关于帧缓冲设备属性和操作的完整描述,这个结构体的定义如下所示:

struct fb_info {
	int node;
	int flags;
	struct fb_var_screeninfo var;	/*可变参数 */
	struct fb_fix_screeninfo fix;	/*固定参数 */
	struct fb_monspecs monspecs;	/*显示器标准 */
	struct work_struct queue;	/* 帧缓冲事件队列 */
	struct fb_pixmap pixmap;	/* 图像硬件 mapper */
	struct fb_pixmap sprite;	/* 光标硬件 mapper */
	struct fb_cmap cmap;		/* 目前的颜色表*/
	struct list_head modelist;      /* mode list */
	struct fb_videomode *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;           /* fb_ops,帧缓冲操作 */
	struct device *device;		/* This is the parent */
	struct device *dev;		/* This is this fb device */
	int class_flag;                    /* 私有 sysfs 标志 */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* 图块 Blitting */
#endif
	char __iomem *screen_base;	/* 虚拟基地址 */
	unsigned long screen_size;	/* ioremapped 的虚拟内存大小 */ 
	void *pseudo_palette;		/* 伪 16 色颜色表 */
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* 硬件状态,如挂起 */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;	
};
FBI 中记录了帧缓冲设备的全部信息, 包括设备的设置参数、状态以及操作函数指针。每一个帧缓冲设备都必须对应一个 FBI。

fb_ops 结构体
FBI 的成员变量 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);

	/* For framebuffers with strange non linear layouts or that do not
	 * work with normal memory mapped access
	 *//* 对于非线性布局的/常规内存映射无法工作的帧缓冲设备需要 */
	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);

	/* checks var and eventually tweaks it to something supported,
	 * DO NOT MODIFY PAR *//* 检测可变参数,并调整到支持的值*/
	int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

	/* set the video mode according to info->var *//* 根据 info->var 设置 video 模式 */
	int (*fb_set_par)(struct fb_info *info);

	/* set color register *//* 设置 color 寄存器 */
	int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
			    unsigned blue, unsigned transp, struct fb_info *info);

	/* set color registers in batch *//* 批量设置 color 寄存器,设置颜色表 */
	int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

	/* blank display *//*显示空白 */
	int (*fb_blank)(int blank, struct fb_info *info);

	/* pan display *//* pan 显示 */
	int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

	/* Draws a rectangle *//* 矩形填充 */
	void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
	/* Copy data from area to another *//* 数据复制 */
	void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
	/* Draws a image to the display *//* 图形填充 */
	void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

	/* Draws cursor *//* 绘制光标 */
	int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);

	/* Rotates the display *//* 旋转显示 */
	void (*fb_rotate)(struct fb_info *info, int angle);

	/* wait for blit idle, optional *//* 等待 blit 空闲 (可选) */
	int (*fb_sync)(struct fb_info *info);

	/* perform fb specific ioctl (optional) */
	int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
			unsigned long arg);

	/* Handle 32bit compat ioctl (optional) *//* 处理 32 位的 compat ioctl (可选) */
	int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
			unsigned long arg);

	/* perform fb specific mmap *//* fb 特定的 mmap */
	int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

	/* save current hardware state *//* 保存目前的硬件状态 */
	void (*fb_save_state)(struct fb_info *info);

	/* restore saved state *//* 恢复被保存的硬件状态 */
	void (*fb_restore_state)(struct fb_info *info);

	/* get capability given var */
	void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
			    struct fb_var_screeninfo *var);
};
fb_ops 的 fb_check_var()成员函数用于检查可以修改的屏幕参数并调整到合适的值,而 fb_set_par()则使得用户设置的屏幕参数在硬件上有效
fb_var_screeninfo(可变参数) 和 fb_fix_screeninfo(固定参数) 结构体
fb_var_screeninfo记录用户可修改的显示控制器参数,包括屏幕分辨率和每个像素点的比特数。
fb_var_screeninfo 中的 xres 定义屏幕一行有多少个点,yres 定义屏幕一列有多少个点,bits_per_pixel 定义每个点用多少个字节表示。

fb_fix_screeninfo 中记录用户不能修改的显示控制器的参数,如屏幕缓冲区的物理地址、长度。当对帧缓冲设备进行映射操作的时候,就是从 fb_fix_screeninfo 中取得缓冲区物理地址的。上述数据成员都需要在驱动程序中初始化和设置。

fb_var_screeninfo fb_fix_screeninfo 结构体的定义分别如下代码:

struct fb_var_screeninfo {//可变参数
	__u32 xres;			/* 可见分辨率		*/
	__u32 yres;
	__u32 xres_virtual;		/* 虚拟分辨率		*/
	__u32 yres_virtual;
	__u32 xoffset;			/* 虚拟分辨率到可见分辨率的偏移 */
	__u32 yoffset;			/* resolution			*/

	__u32 bits_per_pixel;		/* 每像素位数,BPP */
	__u32 grayscale;		/非 0 时指灰度 ,问题:什么是灰度?*/
        /* fb 缓存的 R\G\B 位域 */
	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
	struct fb_bitfield green;	/* else only length is significant */
	struct fb_bitfield blue;
	struct fb_bitfield transp;	/* 透明度 */

	__u32 nonstd;			/* != 0 非标准像素格式 */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/*高度 mm*/
	__u32 width;			/*宽度 mm*/

	__u32 accel_flags;		/* 看 fb_info.flags */

	/* 定时: 除了 pixclock 本身外,其他的都以像素时钟为单位 */
	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
	__u32 left_margin;		/* 行切换:从同步到绘图之间的延迟*/
	__u32 right_margin;		/* 行切换:从绘图到同步之间的延迟 */
	__u32 upper_margin;		/* 帧切换:从同步到绘图之间的延迟 */
	__u32 lower_margin;             /* 帧切换:从绘图到同步之间的延迟 */
	__u32 hsync_len;		/* 水平同步的长度*/
	__u32 vsync_len;		/* 垂直同步的长度*/
	__u32 sync;			/* see FB_SYNC_*		*/
	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 rotate;			/* 顺时钟旋转的角度 */
	__u32 reserved[5];		/* Reserved for future compatibility */
};
struct fb_fix_screeninfo {//不可变参数
	char id[16];			/* 字符串形式的标识符 */
	unsigned long smem_start;	/* fb 缓存的开始位置 */
					/* (physical address) */
	__u32 smem_len;			/* fb 缓存的长度 */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* 分界 */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* 如果没有硬件 panning ,赋 0 */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* 1 行的字节数 */
	unsigned long mmio_start;	/* 内存映射 I/O 的开始位置 */
					/* (physical address) */
	__u32 mmio_len;			/* 内存映射 I/O 的长度 */
	__u32 accel;			/* Indicate to driver which	*/
					/*  specific chip/card we have	*/
	__u16 reserved[3];		/* 保留*/
};
 上面定义的visual成员记录屏幕使用的色彩模式,在 Linux 系统中,支持的色彩模式包括如下几种。
1.Monochrome(FB_VISUAL_MONO01、FB_VISUAL_MONO10),每个像素是 黑或白
2.Pseudocolor(FB_VISUAL_PSEUDOCOLOR、FB_VISUAL_STATIC_PSEUDOCOLOR),即伪彩色,采用 索引颜色显示
3.True color(FB_VISUAL_TRUECOLOR),真彩色,分成红、绿、蓝三基色。
4.Direct color(FB_VISUAL_DIRECTCOLOR),每个像素颜色也是有红、绿、蓝组成,不过每个颜色值是个索引 ,需要查表
5.Grayscale displays,灰度显示,红、绿、蓝的值都一样。


fb_bitfield 结构体

在可变参数的成员中分别记录 R、G、B 的位域,fb_bitfield 结构体描述每一像素显示缓冲区的组织方式,包含位域偏移、位域长度和 MSB 指示,如下所示:

struct fb_bitfield {
	__u32 offset;			/* 位域偏移	*/
	__u32 length;			/* 位域长度	*/
	__u32 msb_right;		/* != 0 : Most significant bit is */ 
					/* right */ 
};


fb_cmap结构体:

fb_cmap 结 构 体 记 录 设 备 无 关 的 颜 色表 信 息 , 用户 空 间 可 以 通 过 ioctl() 的FBIOGETCMAP 和 FBIOPUTCMAP 命令读取或设定颜色表。

struct fb_cmap {
	__u32 start;			/* 第 1 个元素入口	*/
	__u32 len;			/* 元素数量 */
	__u16 *red;			/* Red values	*/
	__u16 *green;
	__u16 *blue;
	__u16 *transp;			/* transparency, can be NULL */
};
下面代码所示为用户空间获取颜色表的例程,若 BPP 为 8 位,则颜色表长度为 256;若 BPP 为 4 位,则颜色表长度为 16;否则,颜色表长度为 0,这是因为,对于 BPP 大于等于 16 的情况,使用颜色表是不划算的。( 问题:颜色表的作用是什么???

// 读入颜色表
if ((vinfo.bits_per_pixel == 8) || (vinfo.bits_per_pixel == 4))
{
   screencols = (vinfo.bits_per_pixel == 8) ? 256 : 16;//颜色表大小
   int loopc;
   startcmap = new fb_cmap;
   startcmap->start = 0;//第一个颜色的入口
   startcmap->len = screencols;//颜色的数目
   //分配颜色表的内存
   startcmap->red = (unsigned short int*)malloc(sizeof(unsigned short int) * screencols);
   startcmap->green = (unsigned short int*)malloc(sizeof(unsigned short int) * screencols);
   startcmap->blue = (unsigned short int*)malloc(sizeof(unsigned short int) * screencols);
   startcmap->transp = (unsigned short int*)malloc(sizeof(unsigned short int) * screencols);
   //获取颜色表
   ioctl(fd, FBIOGETCMAP, startcmap);//通过FBIOGETMAP获取颜色表
   for (loopc = 0; loopc < screencols; loopc++)
   {
     screenclut[loopc] = qRgb(startcmap->red[loopc] >> 8,startcmap->green[loopc] >> 8, startcmap->blue[loopc] >> 8);
   }
}
else
{
   screencols = 0;
}
对于一个 256 色(BPP=8)的 800*600 分辨率的图像而言,若红、绿、蓝分别用一个字节描述,则需要 800*600*3=1440000Byte 的空间,而若使用颜色表,则只需要 800*600*1+256*3= 480768Byte 的空间。(什么意思???)

文件操作结构体
作 为 一 种 字 符 设 备 , 帧 缓 冲设 备 的 文件 操 作 结 构 体 定 义 于/linux/drivers/video/fbmem.c 文件中,代码如下所示。

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,//读函数
	.write =	fb_write,//写函数
	.ioctl =	fb_ioctl,//I/O 控制函数
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap,//内存映射函数
	.open =		fb_open,//打开函数
	.release =	fb_release,//释放函数
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
};
帧缓冲设备驱动的文件操作接口函数已经在 fbmem.c 中被统一实现,一般不需要由驱动工程师再编写。
注册与注销帧缓冲设备
Linux 内核提供了 register_framebuffer()和 unregister_framebuffer()函数分别注册和注销帧缓冲设备,这两个函数都接受 FBI 指针为参数,原型为:

int register_framebuffer(struct fb_info *fb_info);
int unregister_framebuffer(struct fb_info *fb_info);
对于 register_framebuffer()函数而言,如果注册的帧缓冲设备数超过了 FB_MAX(目前定义为 32),则函数返回-ENXIO,注册成功则返回 0。
Linux 帧缓冲设备驱动结构

下图所示为 Linux 帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的ile_operations 结构体由 fbmem.c 中的 file_operations 提供,而特定帧缓冲设备 fb_info结构体的注册、注销以及其中成员的维护,尤其是 fb_ops 中成员函数的实现则由对应的 xxxfb.c 文件实现,fb_ops 中的成员函数最终会操作 LCD 控制器硬件寄存器。

LCD 设备驱动_第4张图片

帧缓冲设备驱动的模块加载与卸载函数

在帧缓冲设备驱动的模块加载函数中,应该完成如下 4 个工作。
1.申请 FBI 结构体的内存空间,初始化 FBI 结构体中固定和可变的屏幕参数,即填充 FBI 中 fb_var_screeninfo var 和 struct fb_fix_screeninfo fix 成员。
2.根据具体 LCD 屏幕的特点,完成 LCD 控制器硬件的初始化。
3.申请帧缓冲设备的显示缓冲区空间。
4.注册帧缓冲设备。

在帧缓冲设备驱动的模块卸载函数中,应该完成相反的工作,包括释放 FBI 结构体内存、关闭 LCD、释放显示缓冲区以及注销帧缓冲设备。
由于 LCD 控制器经常被集成在 SoC 上作为一个独立的硬件模块而存在(成为platform_device),因此,LCD 驱动中也经常包含平台驱动,这样,在帧缓冲设备驱动的模块加载函数中完成的工作只是注册平台驱动,而初始化 FBI 结构体中的固定和可变参数、LCD 控制器硬件的初始化、申请帧缓冲设备的显示缓冲区空间和注册帧缓冲设备的工作则移交到平台驱动的探测函数(probe)中完成。

同样地,在使用平台驱动的情况下,释放 FBI 结构体内存、关闭 LCD、释放显示缓冲区以及注销帧缓冲设备的工作也移交到平台驱动的移除函数中完成。

下面代码所示为帧缓冲设备驱动的模块加载和卸载以及平台驱动的探测和移除函数中的模板:

/* 平台驱动结构体 */
static struct platform_driver xxxfb_driver =
{
   .probe = xxxfb_probe,//平台驱动探测函数
   .remove = xxxfb_remove,//平台驱动移除函数
   .suspend = xxxfb_suspend, 
   .resume = xxxfb_resume, 
   .driver = {
       .name = "xxx-lcd", //驱动名
       .owner = THIS_MODULE,
   }
};
/* 平台驱动探测函数 */
static int _ _init xxxfb_probe(...)
{
   struct fb_info *info;
   /*分配 fb_info 结构体*/
   info = framebuffer_alloc(...);
   info->screen_base = framebuffer_virtual_memory;
   info->var = xxxfb_var; //可变参数
   info->fix = xxxfb_fix; //固定参数
   /*分配显示缓冲区*/
   alloc_dis_buffer(...);
   /*初始化 LCD 控制器*/
   lcd_init(...);
   /*检查可变参数*/
   xxxfb_check_var(&info->var, info);
   /*注册 fb_info*/
   if (register_framebuffer(info) < 0)
     return - EINVAL;
   return 0;
}
/* 平台驱动移除函数 */
static void _ _exit xxxfb_remove(...)
{
    struct fb_info *info = dev_get_drv_data(dev);
    if (info)
    {
      unregister_framebuffer(info); //注销 fb_info
      dealloc_dis_buffer(...); //释放显示缓冲区
      framebuffer_release(info); //注销 fb_info
    }

    return 0;
}
/* 帧缓冲设备驱动模块加载与卸载函数 */
int __devinit xxxfb_init(void)
{
   return platform_driver_register(&xxxfb_driver); //注册平台设备
}
static void __exit xxxfb_cleanup(void)
{
   platform_driver_unregister(&xxxfb_driver); //注销平台设备
}
module_init(xxxfb_init);
module_exit(xxxfb_cleanup);


帧缓冲设备显示缓冲区的申请与释放
在嵌入式系统中,一种常见的方式是直接在 RAM 空间中分配一段显示缓冲区,典型结构如下图所示。


在分配显示缓冲区时一定要考虑 cache 的一致性问题,因为系统往往会通过DMA 方式搬移显示数据。合适的方式是使用 dma_alloc_writecombine()函数分配一 段writecombining区 域 , 对 应 的writecombining区 域 由dma_free_writecombine()函数释放,如代码清单如下所示。

static int __init xxxfb_map_video_memory(struct xxxfb_info *fbi)
{
  fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE);
  fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size,&fbi->map_dma,GFP_KERNEL); //分配内存
  fbi->map_size = fbi->fb->fix.smem_len; //显示缓冲区大小
  if (fbi->map_cpu)
  {
    memset(fbi->map_cpu, 0xf0, fbi->map_size);
    fbi->screen_dma = fbi->map_dma;
    fbi->fb->screen_base = fbi->map_cpu;
    fbi->fb->fix.smem_start = fbi->screen_dma; // 赋 值 fix 的 smem_start
  }

  return fbi->map_cpu ? 0 : - ENOMEM;
}

static inline void xxxfb_unmap_video_memory(struct s3c2410fb_info *fbi)
{
  //释放显示缓冲区
  dma_free_writecombine(fbi->dev,fbi->map_size,fbi->map_cpu,fbi->map_dma);
}


帧缓冲设备的参数设置

定时参数
FBI 结 构 体 可 变 参 数 var 中 的 left_margin 、 right_margin 、 upper_margin 、lower_margin、hsync_len 和 vsync_len 直接查 LCD 的数据手册就可以得到, 下所示为某 LCD 数据手册中直接抓图获得的定时信息。由下图可知对该 LCD 而言,var 中各参数的较合适值分别为:left_margin = 104,right_margin =8,upper_margin = 2,lower_margin = 2,hsync_len = 2,vsync_len = 2。
像素时钟
FBI 可变参数 var 中的 pixclock 意味着像素时钟,例如,如果为 28.37516 MHz,那么画 1 个像素需要 35242 ps(皮秒):

1/(28.37516E6 Hz) = 35.242E-9 s
如果屏幕的分辨率是 640×480,显示一行需要的时间是:
640*35.242E-9 s = 22.555E-6 s
每条扫描线是 640,但是水平回扫和水平同步也需要时间,假设水平回扫和同步需要 272 个像素时钟,因此,画一条扫描线完整的时间是:
(640+272)*35.242E-9 s = 32.141E-6 s
可以计算出水平扫描率大约是 31kHz:
1/(32.141E-6 s) = 31.113E3 Hz
完整的屏幕有 480 线,但是垂直回扫和垂直同步也需要时间,假设垂直回扫和垂直同步需要 49 个象素时钟,因此,画一个完整的屏幕的时间是:
(480+49)*32.141E-6 s = 17.002E-3 s
可以计算出垂直扫描率大约是 59kHz:
1/(17.002E-3 s) = 58.815 Hz
这意味着屏幕数据每秒钟大约刷新 59 次。


颜色位域
FBI 可变参数 var 中的 red、green 和 blue 位域的设置直接由显示缓冲区与显示点的对应关系决定,例如,对于 RGB565 模式,查表表可知,red 占据 5 位,偏移为 11 位;green 占据 6 位,偏移为 5 位;blue 占据 5 位,偏移为 0 位,即:

fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
固定参数
FBI 固定参数 fix 中的 smem_start 指示帧缓冲设备显示缓冲区的首地址,smem_len为帧缓冲设备显示缓冲区的大小,计算公式为:
smem_len = max_xres * max_yres * max_bpp
即:
帧缓冲设备显示缓冲区的大小 = 最大的 x 解析度 * 最大的 y 解析度 * 最大的 BPP

帧缓冲设备驱动的 fb_ops 成员函数

FBI 中的 fp_ops 是使得帧缓冲设备工作所需函数的集合,它们最终与 LCD 控制器硬件打交道。

fb_check_var()用于调整可变参数,并修正为硬件所支持的值;fb_set_par()则根据屏幕参数设置具体读写 LCD 控制器的寄存器以使得 LCD 控制器进入相应的工作状态。
对于 fb_ops 中的 fb_fillrect()、fb_copyarea()和 fb_imageblit()成员函数,通常直接使用对应的通用的 cfb_fillrect()、cfb_copyarea()和 cfb_imageblit()函数即可。
cfb_fillrect()函 数 定 义 在 drivers/video/cfbfillrect.c 文 件 中 , cfb_copyarea() 定 义 在drivers/video/cfbcopyarea.c 文件中,cfb_imageblit()定义在 drivers/video/cfbimgblt.c 文件中。

fb_ops 中 的 fb_setcolreg() 成 员 函 数 实 现 伪 颜 色 表 ( 针 对FB_VISUAL_TRUECOLOR、FB_ VISUAL_DIRECTCOLOR 模式)和颜色表的填充,
其模板如下所示。

static int xxxfb_setcolreg(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info)
{
  struct xxxfb_info *fbi = info->par;
  unsigned int val;
  
  switch (fbi->fb->fix.visual)8
  {
    case FB_VISUAL_TRUECOLOR:
       /* 真彩色,设置伪颜色表 */
      if (regno < 16)
     {
        u32 *pal = fbi->fb->pseudo_palette;
        val = chan_to_field(red, &fbi->fb->var.red); 
        val |= chan_to_field(green, &fbi->fb->var.green);
        val |= chan_to_field(blue, &fbi->fb->var.blue); 
        pal[regno] = val;
     }
     break;
    case FB_VISUAL_PSEUDOCOLOR:
      if (regno < 256)
      {
        /* RGB565 模式 */
        val = ((red >> 0) &0xf800);
        val |= ((green >> 5) &0x07e0);
        val |= ((blue >> 11) &0x001f);
        writel(val, XXX_TFTPAL(regno));
        schedule_palette_update(fbi, regno, val);
     }
     break;
...
   }
   return 0;
}
问题:这段代码的作用是什么????有什么意义??

LCD 设备驱动的读写、mmap 和 ioctl 函数

虽然帧缓冲设备的 file_operations 中的成员函数,即文件操作函数已经由内核在fbmem.c 文件中实现,一般不再需要驱动工程师修改,但分析这些函数对于巩固字符设备驱动的知识以及加深对帧缓冲设备驱动的理解是大有裨益的。

如下所示为 LCD 设备驱动的文件操作读写函数的源代码,

fb_read

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	unsigned long p = *ppos;
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];//获得 FBI
	u32 *buffer, *dst;
	u32 __iomem *src;
	int c, i, cnt = 0, err = 0;
	unsigned long total_size;

	if (!info || ! info->screen_base)
		return -ENODEV;

	if (info->state != FBINFO_STATE_RUNNING)
		return -EPERM;

	if (info->fbops->fb_read)//如果 fb_ops 中定义了特定的读函数
		return info->fbops->fb_read(info, buf, count, ppos);
	
	total_size = info->screen_size;/*获得显示缓冲区总的大小*/

	if (total_size == 0)
		total_size = info->fix.smem_len;

	if (p >= total_size)
		return 0;

	if (count >= total_size)/*获得有效的读长度*/
		count = total_size;

	if (count + p > total_size)
		count = total_size - p;

	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);/*分配用于临时存放显示缓冲区数据的 buffer*/
	if (!buffer)
		return -ENOMEM;

	src = (u32 __iomem *) (info->screen_base + p);//获得源地址

	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);

	while (count) {/*读取显示缓冲区中的数据并复制到分配的 buffer*/
		c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		dst = buffer;
		for (i = c >> 2; i--; )
			*dst++ = fb_readl(src++);
		if (c & 3) {
			u8 *dst8 = (u8 *) dst;
			u8 __iomem *src8 = (u8 __iomem *) src;

			for (i = c & 3; i--;)
				*dst8++ = fb_readb(src8++);

			src = (u32 __iomem *) src8;
		}

		if (copy_to_user(buf, buffer, c)) {//复制到用户空间
			err = -EFAULT;
			break;
		}
		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);

	return (err) ? err : cnt;
}
fb_write

static ssize_t
fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	unsigned long p = *ppos;
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];//获取到fb_info
	u32 *buffer, *src;
	u32 __iomem *dst;
	int c, i, cnt = 0, err = 0;
	unsigned long total_size;

	if (!info || !info->screen_base)
		return -ENODEV;

	if (info->state != FBINFO_STATE_RUNNING)
		return -EPERM;

	if (info->fbops->fb_write)//如果 fb_ops 中定义了特定的写函数
		return info->fbops->fb_write(info, buf, count, ppos);
	
	total_size = info->screen_size;/*获得显示缓冲区总的大小*/

	if (total_size == 0)
		total_size = info->fix.smem_len;

	if (p > total_size)
		return -EFBIG;

	if (count > total_size) {/*获得有效的写长度*/
		err = -EFBIG;
		count = total_size;
	}

	if (count + p > total_size) {
		if (!err)
			err = -ENOSPC;

		count = total_size - p;
	}

	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);/*分配用于存放用户空间传过来的显示缓冲区数据的 buffer*/
	if (!buffer)
		return -ENOMEM;

	dst = (u32 __iomem *) (info->screen_base + p);//要写的显示缓冲区基地址

	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);

	while (count) {
		c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		src = buffer;

		if (copy_from_user(src, buf, c)) {/*读取用户空间数据并复制到显示缓冲区*/
			err = -EFAULT;
			break;
		}

		for (i = c >> 2; i--; )
			fb_writel(*src++, dst++);

		if (c & 3) {
			u8 *src8 = (u8 *) src;
			u8 __iomem *dst8 = (u8 __iomem *) dst;

			for (i = c & 3; i--; )
				fb_writeb(*src8++, dst8++);

			dst = (u32 __iomem *) dst8;
		}

		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);

	return (cnt) ? cnt : err;
}
file_operations 中的 mmap()函数非常关键,它将显示缓冲区映射到用户空间,从而使得用户空间可以直接操作显示缓冲区而省去一次用户空间到内核空间的内存复制过程,提高效率,其源代码如下所示。
static int 
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
	int fbidx = iminor(file->f_path.dentry->d_inode);
	struct fb_info *info = registered_fb[fbidx];
	struct fb_ops *fb = info->fbops;
	unsigned long off;
#if !defined(__sparc__) || defined(__sparc_v9__)
	unsigned long start;
	u32 len;
#endif

	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
		return -EINVAL;
	off = vma->vm_pgoff << PAGE_SHIFT;
	if (!fb)
		return -ENODEV;
	if (fb->fb_mmap) {//FBI 中实现了 mmap,则调用 FBI 的 mmap
		int res;
		lock_kernel();
		res = fb->fb_mmap(info, vma);
		unlock_kernel();
		return res;
	}

#if defined(__sparc__) && !defined(__sparc_v9__)
	/* Should never get here, all fb drivers should have their own
	   mmap routines */
	return -EINVAL;
#else
	/* !sparc32... */
	lock_kernel();

	/* frame buffer memory *//* 映射帧缓冲设备的显示缓冲区 */
	start = info->fix.smem_start;//开始地址
	len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);//长度
	if (off >= len) {
		/* /* 内存映射的 I/O */ */
		off -= len;
		if (info->var.accel_flags) {
			unlock_kernel();
			return -EINVAL;
		}
		start = info->fix.mmio_start;
		len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
	}
	unlock_kernel();
	start &= PAGE_MASK;
	if ((vma->vm_end - vma->vm_start + off) > len)
		return -EINVAL;
	off += start;
	vma->vm_pgoff = off >> PAGE_SHIFT;
	/* This is an IO map - tell maydump to skip this VMA */
	vma->vm_flags |= VM_IO | VM_RESERVED;
#if defined(__mc68000__)
#if defined(CONFIG_SUN3)
	pgprot_val(vma->vm_page_prot) |= SUN3_PAGE_NOCACHE;
#elif defined(CONFIG_MMU)
	if (CPU_IS_020_OR_030)
		pgprot_val(vma->vm_page_prot) |= _PAGE_NOCACHE030;
	if (CPU_IS_040_OR_060) {
		pgprot_val(vma->vm_page_prot) &= _CACHEMASK040;
		/* Use no-cache mode, serialized */
		pgprot_val(vma->vm_page_prot) |= _PAGE_NOCACHE_S;
	}
#endif
#elif defined(__powerpc__)
	vma->vm_page_prot = phys_mem_access_prot(file, off >> PAGE_SHIFT,
						 vma->vm_end - vma->vm_start,
						 vma->vm_page_prot);
#elif defined(__alpha__)
	/* Caching is off in the I/O space quadrant by design.  */
#elif defined(__i386__) || defined(__x86_64__)
	if (boot_cpu_data.x86 > 3)
		pgprot_val(vma->vm_page_prot) |= _PAGE_PCD;
#elif defined(__mips__) || defined(__sparc_v9__)
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#elif defined(__hppa__)
	pgprot_val(vma->vm_page_prot) |= _PAGE_NO_CACHE;
#elif defined(__arm__) || defined(__sh__) || defined(__m32r__)
	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
#elif defined(__avr32__)
	vma->vm_page_prot = __pgprot((pgprot_val(vma->vm_page_prot)
				      & ~_PAGE_CACHABLE)
				     | (_PAGE_BUFFER | _PAGE_DIRTY));
#elif defined(__ia64__)
	if (efi_range_is_wc(vma->vm_start, vma->vm_end - vma->vm_start))
		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
	else
		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#else
#warning What do we have to do here??
#endif
	if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
			     vma->vm_end - vma->vm_start, vma->vm_page_prot))
		return -EAGAIN;
	return 0;
#endif /* !sparc32 */
}
fb_ioctl() 函 数 最 终 实 现 对 用 户 I/O 控 制 命 令 的 执 行 , 这 些 命 令 包 括FBIOGET_VSCREENINFO(获得可变的屏幕参数)
、FBIOPUT_VSCREENINFO(设置可变的屏幕参数)、FBIOGET _FSCREENINFO(获得固定的屏幕参数设置,注意,固定的屏幕参数不能由用户设置)、FBIOPUTCMAP(设置颜色表)、FBIOGETCMAP(获得颜色表)等。下面代码所示为帧缓冲设备 ioctl()函数的源代码。
static int 
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
	 unsigned long arg)
{
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];
	struct fb_ops *fb = info->fbops;
	struct fb_var_screeninfo var;
	struct fb_fix_screeninfo fix;
	struct fb_con2fbmap con2fb;
	struct fb_cmap_user cmap;
	struct fb_event event;
	void __user *argp = (void __user *)arg;
	int i;
	
	if (!fb)
		return -ENODEV;
	switch (cmd) {
	case FBIOGET_VSCREENINFO:// 获得可变的屏幕参数
		return copy_to_user(argp, &info->var,
				    sizeof(var)) ? -EFAULT : 0;
	case FBIOPUT_VSCREENINFO://设置可变的屏幕参数
		if (copy_from_user(&var, argp, sizeof(var)))
			return -EFAULT;
		acquire_console_sem();
		info->flags |= FBINFO_MISC_USEREVENT;
		i = fb_set_var(info, &var);
		info->flags &= ~FBINFO_MISC_USEREVENT;
		release_console_sem();
		if (i) return i;
		if (copy_to_user(argp, &var, sizeof(var)))
			return -EFAULT;
		return 0;
	case FBIOGET_FSCREENINFO://获得固定的屏幕参数设置
		return copy_to_user(argp, &info->fix,
				    sizeof(fix)) ? -EFAULT : 0;
	case FBIOPUTCMAP://设置颜色表
		if (copy_from_user(&cmap, argp, sizeof(cmap)))
			return -EFAULT;
		return (fb_set_user_cmap(&cmap, info));
	case FBIOGETCMAP://获得颜色表
		if (copy_from_user(&cmap, argp, sizeof(cmap)))
			return -EFAULT;
		return fb_cmap_to_user(&info->cmap, &cmap);
	case FBIOPAN_DISPLAY:
		if (copy_from_user(&var, argp, sizeof(var)))
			return -EFAULT;
		acquire_console_sem();
		i = fb_pan_display(info, &var);
		release_console_sem();
		if (i)
			return i;
		if (copy_to_user(argp, &var, sizeof(var)))
			return -EFAULT;
		return 0;
	case FBIO_CURSOR:
		return -EINVAL;
	case FBIOGET_CON2FBMAP:
		if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
			return -EFAULT;
		if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
		    return -EINVAL;
		con2fb.framebuffer = -1;
		event.info = info;
		event.data = &con2fb;
		fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
		return copy_to_user(argp, &con2fb,
				    sizeof(con2fb)) ? -EFAULT : 0;
	case FBIOPUT_CON2FBMAP:
		if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
			return - EFAULT;
		if (con2fb.console < 0 || con2fb.console > MAX_NR_CONSOLES)
		    return -EINVAL;
		if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
		    return -EINVAL;
#ifdef CONFIG_KMOD
		if (!registered_fb[con2fb.framebuffer])
		    try_to_load(con2fb.framebuffer);
#endif /* CONFIG_KMOD */
		if (!registered_fb[con2fb.framebuffer])
		    return -EINVAL;
		event.info = info;
		event.data = &con2fb;
		return fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP,
					      &event);
	case FBIOBLANK:
		acquire_console_sem();
		info->flags |= FBINFO_MISC_USEREVENT;
		i = fb_blank(info, arg);
		info->flags &= ~FBINFO_MISC_USEREVENT;
		release_console_sem();
		return i;
	default:
		if (fb->fb_ioctl == NULL)
			return -EINVAL;
		return fb->fb_ioctl(info, cmd, arg);
	}
}



你可能感兴趣的:(LCD)