现在的位置: 首页 > 综合 > 正文

基于S3C2440的嵌入式Linux驱动——Framebuffer子系统解读

2013年03月09日 ⁄ 综合 ⁄ 共 43487字 ⁄ 字号 评论关闭
文章目录

本文将介绍Framebuffer子系统

目标平台:TQ2440 CPU:s3c2440

LCD设备:3.5英寸,分辨率320X240

1. 概述

Framebuffer,中文名字是帧缓冲,这个帧也就是一副图像所需要的数据。因此,帧缓冲其实就是LCD设备的驱动程序。Linux中,framebuffer子系统框架如下:

核心层的代码以fbmem.c为主,核心层包括许多与具体硬件无关的代码,并且提供了API给用户空间。用户空间使用系统调用,系统调用会使用相应的API函数,最后会调用驱动层实现功能。对于不同的设备,驱动层的代码将有所不同。

接下来的内容中,首先给出framerbuffer使用的数据结构;随后简单描述framerbuffer核心层;最后,针对S3C2440,对驱动代码进行分析。

2. 数据结构

2.1 fb_info 结构

  该结构是内核用于描述一个特定framebuffer设备。其中包含了几个重要的结构,将在下面介绍。

  下列代码位于include/linux/fb.h

struct fb_info {
	int node;
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct fb_var_screeninfo var;	/* Current var */
	struct fb_fix_screeninfo fix;	/* Current fix */
	struct fb_monspecs monspecs;	/* Current Monitor specs */
	struct work_struct queue;	/* Framebuffer event queue */
	struct fb_pixmap pixmap;	/* Image hardware mapper */
	struct fb_pixmap sprite;	/* Cursor hardware mapper */
	struct fb_cmap cmap;		/* Current cmap */
	struct list_head modelist;      /* mode list */
	struct fb_videomode *mode;	/* current mode */

#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 */
	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;	
};

2.2 fb_fix_screeninfo结构

  该数据结构是不可改变的,也就是说用户空间不能改变该结构中的任何成员,在核心层我们将会看到这点。

  下列代码位于include/linux/fb.h

struct fb_fix_screeninfo {
	char id[16];			/* identification string eg "TT Builtin" */
	unsigned long smem_start;	/* Start of frame buffer mem */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* length of a line in bytes    */
	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
					/* (physical address) */
	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
	__u32 accel;			/* Indicate to driver which	*/
					/*  specific chip/card we have	*/
	__u16 reserved[3];		/* Reserved for future compatibility */
};

2.3 fb_var_screeninfo结构

  该数据结构是可以改变的,也就是说用户空间可以改变该结构中的成员。该数据结构中的很多成员就由板级信息复制而来,在驱动代码中我们将会看到这点。

  下列代码位于include/linux/fb.h

struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution		*/
	__u32 yres;
	__u32 xres_virtual;		/* virtual resolution		*/
	__u32 yres_virtual;
	__u32 xoffset;			/* offset from virtual to visible */
	__u32 yoffset;			/* resolution			*/

	__u32 bits_per_pixel;		/* guess what			*/
	__u32 grayscale;		/* != 0 Graylevels instead of colors */

	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;	/* transparency			*/	

	__u32 nonstd;			/* != 0 Non standard pixel format */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/* height of picture in mm    */
	__u32 width;			/* width of picture in mm     */

	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */

	/* Timing: All values in pixclocks, except pixclock (of course) */
	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
	__u32 left_margin;		/* time from sync to picture	*/
	__u32 right_margin;		/* time from picture to sync	*/
	__u32 upper_margin;		/* time from sync to picture	*/
	__u32 lower_margin;
	__u32 hsync_len;		/* length of horizontal sync	*/
	__u32 vsync_len;		/* length of vertical sync	*/
	__u32 sync;			/* see FB_SYNC_*		*/
	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 rotate;			/* angle we rotate counter clockwise */
	__u32 reserved[5];		/* Reserved for future compatibility */
};

2.4 fb_ops结构

  该结构描述了用于fb_info的方法,这些方法中有些是要驱动程序提供的,而有些可以使用内核提供的方法。

 下列代码位于include/linux/fb.h

/*
 * Frame buffer operations
 *
 * LOCKING NOTE: those functions must _ALL_ be called with the console
 * semaphore held, this is the only suitable locking mechanism we have
 * in 2.6. Some may be called at interrupt time at this point though.
 */

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 */
	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);

	/* set color registers in batch */
	int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

	/* blank display */
	int (*fb_blank)(int blank, struct fb_info *info);

	/* pan display */
	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 */
	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) */
	int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
			unsigned long arg);

	/* perform fb specific 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);
};

3. frambuffer核心层

首先来看下frmaebuffer子系统的初始化函数。

3.1 fbmem_init和fbmem_exit

下列代码位于drivers/video/fbmem.c

/**
 *	fbmem_init - init frame buffer subsystem
 *
 *	Initialize the frame buffer subsystem.
 *
 *	NOTE: This function is _only_ to be called by drivers/char/mem.c.
 *
 */

static int __init
fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);

	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))		/*注册字符设备,major=29*/
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

	fb_class = class_create(THIS_MODULE, "graphics");	/*创建类*/
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}

#ifdef MODULE
module_init(fbmem_init);
static void __exit
fbmem_exit(void)
{
	remove_proc_entry("fb", NULL);
	class_destroy(fb_class);
	unregister_chrdev(FB_MAJOR, "fb");
}

module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);
#endif

static const struct file_operations fb_fops = {
    .owner =    THIS_MODULE,
    .read =        fb_read,
    .write =    fb_write,
    .unlocked_ioctl = fb_ioctl,
#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
};

我们看到,如果不是作为模块,那么该初始化程序将在subsys_initcall阶段被调用。初始化时,仅仅注册了一个字符设备,并创建了一个类。通过字符设备,提供了API给用户空间,包open,release,write,read等。

随后我们看看如何分配一个fb_info结构。

3.2 framebuffer_alloc

下列代码位于drivers/video/fbmem.c

/**
 * framebuffer_alloc - creates a new frame buffer info structure
 *
 * @size: size of driver private data, can be zero
 * @dev: pointer to the device for this fb, this can be NULL
 *
 * Creates a new frame buffer info structure. Also reserves @size bytes
 * for driver private data (info->par). info->par (if any) will be
 * aligned to sizeof(long).
 *
 * Returns the new structure, or NULL if an error occured.
 *
 */
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
{
#define BYTES_PER_LONG (BITS_PER_LONG/8)
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
	int fb_info_size = sizeof(struct fb_info);
	struct fb_info *info;
	char *p;

	if (size)
		fb_info_size += PADDING;

	p = kzalloc(fb_info_size + size, GFP_KERNEL);

	if (!p)
		return NULL;

	info = (struct fb_info *) p;

	if (size)
		info->par = p + fb_info_size;

	info->device = dev;

#ifdef CONFIG_FB_BACKLIGHT
	mutex_init(&info->bl_curve_mutex);
#endif

	return info;
#undef PADDING
#undef BYTES_PER_LONG
}
EXPORT_SYMBOL(framebuffer_alloc);

在进行分配时,根据参数size的大小,分配了b_info_size + size的空间,然后让fb_info->par指向size的空间。因此par所指向的空间可视为设备特有的数据。

在分配了fb_info结构之后,需要将它注册到内核中。注册由register_framebuffer完成。我们来看下。

3.3 register_framebuffer

下列代码位于drivers/video/fbmem.c

/**
 *	register_framebuffer - registers a frame buffer device
 *	@fb_info: frame buffer info structure
 *
 *	Registers a frame buffer device @fb_info.
 *
 *	Returns negative errno on error, or zero for success.
 *
 */

int
register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;

	if (num_registered_fb == FB_MAX) /*最多32个FB*/
		return -ENXIO;

	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

	num_registered_fb++;		/*对注册的FB计数*/
	/*寻找第一个空位*/
	for (i = 0 ; i < FB_MAX; i++)/*FB_MAX=32,也就是最多32个framebuffer*/
		if (!registered_fb[i])	/*struct fb_info *registered_fb[FB_MAX]*/
			break;
	fb_info->node = i;
	mutex_init(&fb_info->lock);	/*初始化互斥体*/

	fb_info->dev = device_create(fb_class, fb_info->device,/*创建设备节点,节点名为fbx*/
				     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
	if (IS_ERR(fb_info->dev)) {
		/* Not fatal */
		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info);	/*初始化,在class/graphics/fbx/下创建设备属性*/

	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);/*分配内存,1024 * 8字节*/
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	fb_info->pixmap.offset = 0;

	if (!fb_info->pixmap.blit_x)
		fb_info->pixmap.blit_x = ~(u32)0;

	if (!fb_info->pixmap.blit_y)
		fb_info->pixmap.blit_y = ~(u32)0;

	if (!fb_info->modelist.prev || !fb_info->modelist.next)	/*该链表没有指向其他节点*/
		INIT_LIST_HEAD(&fb_info->modelist);	/*初始化链表头*/

	fb_var_to_videomode(&mode, &fb_info->var);/*转换fb_var_screeninfo成fb_videomode*/
	fb_add_videomode(&mode, &fb_info->modelist);/*添加mode至链表中*/
	registered_fb[i] = fb_info;

	event.info = fb_info;
	if (!lock_fb_info(fb_info))
		return -ENODEV;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);/*???*/
	unlock_fb_info(fb_info);
	return 0;
}

从这个函数我们可以看出,framebuffer子系统只支持32个设备。在创建了设备节点以后,建立设备属性节点,随后将fb_var_screeninfo转换成fb_videomode,最后添加fb_videomode至链表中。

我们看下其中调用的函数,首先是fb_init_device。

下列代码位于drivers/video/fbsysfs.c

int fb_init_device(struct fb_info *fb_info)
{
	int i, error = 0;

	dev_set_drvdata(fb_info->dev, fb_info);

	fb_info->class_flag |= FB_SYSFS_FLAG_ATTR;

	/*建立设备属性*/
	for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {
		error = device_create_file(fb_info->dev, &device_attrs[i]);

		if (error)
			break;
	}

	if (error) {
		while (--i >= 0)
			device_remove_file(fb_info->dev, &device_attrs[i]);
		fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;
	}

	return 0;
}

/* When cmap is added back in it should be a binary attribute
 * not a text one. Consideration should also be given to converting
 * fbdev to use configfs instead of sysfs */
static struct device_attribute device_attrs[] = {
    __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),
    __ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),
    __ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console),
    __ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor),
    __ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode),
    __ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes),
    __ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan),
    __ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual),
    __ATTR(name, S_IRUGO, show_name, NULL),
    __ATTR(stride, S_IRUGO, show_stride, NULL),
    __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate),
    __ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),
#ifdef CONFIG_FB_BACKLIGHT
    __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),
#endif
};

我们可以在/sys/class/graphics/fb0下发现这些属性文件。

[root@yj423 fb0]#pwd
/sys/class/graphics/fb0
[root@yj423 fb0]#ls
bits_per_pixel  cursor          mode            pan             state           uevent
blank           dev             modes           power           stride          virtual_size
console         device          name            rotate          subsystem

接着看下fb_var_to_videomode和fb_add_videomode函数。

下列代码位于drivers/video/modedb.c和drivers/video/fb.h

struct fb_videomode {
	const char *name;	/* optional */
	u32 refresh;		/* optional */
	u32 xres;
	u32 yres;
	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 flag;
};

/**
 * fb_var_to_videomode - convert fb_var_screeninfo to fb_videomode
 * @mode: pointer to struct fb_videomode
 * @var: pointer to struct fb_var_screeninfo
 */
void fb_var_to_videomode(struct fb_videomode *mode,
             const struct fb_var_screeninfo *var)
{
    u32 pixclock, hfreq, htotal, vtotal;

    mode->name = NULL;
    mode->xres = var->xres;
    mode->yres = var->yres;
    mode->pixclock = var->pixclock;
    mode->hsync_len = var->hsync_len;
    mode->vsync_len = var->vsync_len;
    mode->left_margin = var->left_margin;
    mode->right_margin = var->right_margin;
    mode->upper_margin = var->upper_margin;
    mode->lower_margin = var->lower_margin;
    mode->sync = var->sync;
    mode->vmode = var->vmode & FB_VMODE_MASK;
    mode->flag = FB_MODE_IS_FROM_VAR;
    mode->refresh = 0;

    if (!var->pixclock)
        return;

    pixclock = PICOS2KHZ(var->pixclock) * 1000;

    htotal = var->xres + var->right_margin + var->hsync_len +
        var->left_margin;
    vtotal = var->yres + var->lower_margin + var->vsync_len +
        var->upper_margin;

    if (var->vmode & FB_VMODE_INTERLACED)
        vtotal /= 2;
    if (var->vmode & FB_VMODE_DOUBLE)
        vtotal *= 2;

    hfreq = pixclock/htotal;
    mode->refresh = hfreq/vtotal;
}

/**
 * fb_add_videomode: adds videomode entry to modelist
 * @mode: videomode to add
 * @head: struct list_head of modelist
 *
 * NOTES:
 * Will only add unmatched mode entries
 */
int fb_add_videomode(const struct fb_videomode *mode, struct list_head *head)
{
    struct list_head *pos;
    struct fb_modelist *modelist;d
    struct fb_videomode *m;
    int found = 0;
    /*遍历所有的fb_modelist,查找mode是否存在*/
    list_for_each(pos, head) {
        modelist = list_entry(pos, struct fb_modelist, list);
        m = &modelist->mode;
        if (fb_mode_is_equal(m, mode)) {    /*比较两个fb_videomode*/
            found = 1;                        /*该fb_videomode已存在*/
            break;
        }
    }
    if (!found) {    /*不存在*/
        modelist = kmalloc(sizeof(struct fb_modelist),    /*分配fb_modelist*/
                          GFP_KERNEL);

        if (!modelist)
            return -ENOMEM;
        modelist->mode = *mode;            /*保存mode*/
        list_add(&modelist->list, head);/*添加mode至链表中*/
    }
    return 0;
}

/**
 * fb_mode_is_equal - compare 2 videomodes
 * @mode1: first videomode
 * @mode2: second videomode
 *
 * RETURNS:
 * 1 if equal, 0 if not
 */
int fb_mode_is_equal(const struct fb_videomode *mode1,
             const struct fb_videomode *mode2)
{
    return (mode1->xres         == mode2->xres &&
        mode1->yres         == mode2->yres &&
        mode1->pixclock     == mode2->pixclock &&
        mode1->hsync_len    == mode2->hsync_len &&
        mode1->vsync_len    == mode2->vsync_len &&
        mode1->left_margin  == mode2->left_margin &&
        mode1->right_margin == mode2->right_margin &&
        mode1->upper_margin == mode2->upper_margin &&
        mode1->lower_margin == mode2->lower_margin &&
        mode1->sync         == mode2->sync &&
        mode1->vmode        == mode2->vmode);
}

fb_var_to_videomode函数只是将fb_var_screeninfo结构转换成fb_videomode结构。而fb_add_videomode函数查找是否该fb_videomode已经存在,如果不存在则添加到列表中。

3.4 字符设备方法

  在看过framebuffer子系统建立和注册过程后,我们看下framebuffer留给用户空间的API是怎样实现的。

本小结只分析5个常用的方法,即open,release,read,write和ioctl。

  因为所有的方法和struct fb_ops定义的方法有紧密的联系,而该结构的定义由驱动程序给出,在这里我们提前看下在驱动中是如何定义的。

 下列代码位于drivers/video/s3c2410fb..c

static struct fb_ops s3c2410fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= s3c2410fb_check_var,		        /*检查变量的合法性*/
	.fb_set_par	= s3c2410fb_set_par,			/*将参数写入LCD控制器,该函数由帧缓冲核心调用*/
	.fb_blank	= s3c2410fb_blank,			/*该方法支持显示消隐和去消隐*/
	.fb_setcolreg	= s3c2410fb_setcolreg,                  /*设置颜色寄存器*/
	.fb_fillrect	= cfb_fillrect,				/*用像素行填充矩形框,通用库函数*/
	.fb_copyarea	= cfb_copyarea,				/*将屏幕的一个矩形区域复制到另一个区域,通用库函数*/
	.fb_imageblit	= cfb_imageblit,			/*显示一副图像,通用库函数*/
};

最下面的三个方法使用的是内核提供的库函数,而上面4个则是由驱动提供。

3.4.1 open方法

下列代码位于drivers/video/fbmem.c

static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
	int fbidx = iminor(inode);
	struct fb_info *info;
	int res = 0;

	if (fbidx >= FB_MAX)
		return -ENODEV;
	info = registered_fb[fbidx];		/*在register_framebuffer函数中已经设置了元素*/
	if (!info)
		request_module("fb%d", fbidx);	/*加载模块,这里不加载*/
	info = registered_fb[fbidx];
	if (!info)
		return -ENODEV;
	mutex_lock(&info->lock);			/*加锁互斥体*/
	if (!try_module_get(info->fbops->owner)) {	/*增加模块引用计数*/
		res = -ENODEV;
		goto out;
	}
	file->private_data = info;		/*保存info*/
	if (info->fbops->fb_open) {		/*这里fb_open方法为空*/
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
#ifdef CONFIG_FB_DEFERRED_IO
	if (info->fbdefio)
		fb_deferred_io_open(info, inode, file);
#endif
out:
	mutex_unlock(&info->lock);		/*解锁互斥体*/
	return res;
}

主要的一个工作就是增加模块引用计数。还有,程序会判断是否fb_open在驱动中给出,如果有则调用该方法。我们已经知道fb_open没有给出。

3.4.2 release方法

下列代码位于drivers/video/fbmem.c

static int 
fb_release(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
	struct fb_info * const info = file->private_data;

	mutex_lock(&info->lock);
	if (info->fbops->fb_release)	/*这里fb_release为空*/
		info->fbops->fb_release(info,1);
	module_put(info->fbops->owner);	/*减少模块引用计数*/
	mutex_unlock(&info->lock);
	return 0;
}

和open相反,减少模块引用计数。

3.4.3 write方法

  通过调用该方法,LCD将显示画面。

  下列代码位于drivers/video/fbmem.c

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];
    u32 *buffer, *src;
    u32 __iomem *dst;
    int c, i, cnt = 0, err = 0;
    unsigned long total_size;

    if (!info || !info->screen_base)    /*screen_base在驱动中给出*/
        return -ENODEV;

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

    if (info->fbops->fb_write)    /*没有fb_write方法*/
        return info->fbops->fb_write(info, buf, count, ppos);
    
    total_size = info->screen_size;    /*screen_size没有给出*/

    if (total_size == 0)
        total_size = info->fix.smem_len;/*153600字节,驱动probe方法中计算*/

    if (p > total_size)
        return -EFBIG;

    if (count > total_size) {    /*要写入的字节数大于153600*/
        err = -EFBIG;        /*file too big*/
        count = total_size;
    }

    if (count + p > total_size) {/*偏移量加上字节数超出了缓冲区*/
        if (!err)
            err = -ENOSPC;

        count = total_size - p;
    }

    /*分配buffer,GFP_KERNEL*/
    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
             GFP_KERNEL);                /
    if (!buffer)
        return -ENOMEM;

    dst = (u32 __iomem *) (info->screen_base + p);/*修改目的指针*/

    if (info->fbops->fb_sync)    /*没有定义fb_sync*/
        info->fbops->fb_sync(info);

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

        /*从buf(用户空间)拷贝数据到src中,一开始为1页,最后为count字节*/
        if (copy_from_user(src, buf, c)) {    
            err = -EFAULT;
            break;
        }
        /*一次for循环,写入4个字节数据到dst处*/
        for (i = c >> 2; i--; )
            fb_writel(*src++, dst++);
        /*最后还有3个,2个或者1个字节*/
        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;    /*减去1页*/
    }

    kfree(buffer);    /*释放buffer*/

    return (cnt) ? cnt : err;
}

这里,做了一系列的检查之后,开始拷贝数据。这里一个有三个buffer,一个是用户空间提供的buf,一个是在这里新开辟的buffer,还有就是驱动层提供的screen_base。

数据流如下:
 

用户空间的数据首先被复制到buffer中,然后从buffer中复制到screen_base中,最后被映射到LCD上,LCD就显示响应的画面了。

3.4.4 read方法

该方法用于读取屏幕画面的数据。

read和write类似,只是数据流是反响的,就不多做介绍了。

下列代码位于drivers/video/fbmem.c

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];
	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_read*/
		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);
	if (!buffer)
		return -ENOMEM;

	src = (u32 __iomem *) (info->screen_base + p);

	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);/*没有定义fb_sync*/

	while (count) {
		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;
}

3.4.5 ioctl方法

这里只是简单的看下ioctl方法,这个函数调用很多其他的函数,详细的请自己看吧。

下列代码位于drivers/video/fbmem.c

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	/*获取inode,再获取对应的fb_info*/
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);	
	struct fb_info *info = registered_fb[fbidx];

	return do_fb_ioctl(info, cmd, arg);
}

static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
            unsigned long arg)
{
    struct fb_ops *fb;
    struct fb_var_screeninfo var;
    struct fb_fix_screeninfo fix;
    struct fb_con2fbmap con2fb;
    struct fb_cmap cmap_from;
    struct fb_cmap_user cmap;
    struct fb_event event;
    void __user *argp = (void __user *)arg;
    long ret = 0;

    switch (cmd) {
    /*获取fb_var_screeninfo*/
    case FBIOGET_VSCREENINFO:    
        if (!lock_fb_info(info))    /*加锁互斥体info->lock*/
            return -ENODEV;
        var = info->var;            /*复制var*/
        unlock_fb_info(info);

        ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;    /*复制var到用户空间*/
        break;
    /*设置fb_var_screeninfo*/
    case FBIOPUT_VSCREENINFO:
        if (copy_from_user(&var, argp, sizeof(var)))    /*从用户空间获取var*/
            return -EFAULT;
        if (!lock_fb_info(info))
            return -ENODEV;
        acquire_console_sem();
        info->flags |= FBINFO_MISC_USEREVENT;
        ret = fb_set_var(info, &var);            /*设置var*/
        info->flags &= ~FBINFO_MISC_USEREVENT;
        release_console_sem();
        unlock_fb_info(info);
        if (!ret && copy_to_user(argp, &var, sizeof(var)))
            ret = -EFAULT;
        break;
    /*获取fb_fix_screeninfo*/    /*fix为不可改变信息,只能获取,不能设置*/
    case FBIOGET_FSCREENINFO:
        if (!lock_fb_info(info))
            return -ENODEV;
        fix = info->fix;
        unlock_fb_info(info);

        ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
        break;
    /*设置fb_cmap*/    
    case FBIOPUTCMAP:
        if (copy_from_user(&cmap, argp, sizeof(cmap)))
            return -EFAULT;
        ret = fb_set_user_cmap(&cmap, info);    /*设置fb_cmap*/
        break;
    /*获取fb_cmap*/    
    case FBIOGETCMAP:
        if (copy_from_user(&cmap, argp, sizeof(cmap)))
            return -EFAULT;
        if (!lock_fb_info(info))
            return -ENODEV;
        cmap_from = info->cmap;
        unlock_fb_info(info);
        ret = fb_cmap_to_user(&cmap_from, &cmap);/*获取fb_cmp*/
        break;
    case FBIOPAN_DISPLAY:
        if (copy_from_user(&var, argp, sizeof(var)))
            return -EFAULT;
        if (!lock_fb_info(info))
            return -ENODEV;
        acquire_console_sem();
        ret = fb_pan_display(info, &var);
        release_console_sem();
        unlock_fb_info(info);
        if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
            return -EFAULT;
        break;
    case FBIO_CURSOR:
        ret = -EINVAL;
        break;
    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.data = &con2fb;
        if (!lock_fb_info(info))
            return -ENODEV;
        event.info = info;
        fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
        unlock_fb_info(info);
        ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
        break;
    case FBIOPUT_CON2FBMAP:
        if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
            return -EFAULT;
        if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
            return -EINVAL;
        if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
            return -EINVAL;
        if (!registered_fb[con2fb.framebuffer])
            request_module("fb%d", con2fb.framebuffer);
        if (!registered_fb[con2fb.framebuffer]) {
            ret = -EINVAL;
            break;
        }
        event.data = &con2fb;
        if (!lock_fb_info(info))
            return -ENODEV;
        event.info = info;
        ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
        unlock_fb_info(info);
        break;
    case FBIOBLANK:
        if (!lock_fb_info(info))
            return -ENODEV;
        acquire_console_sem();
        info->flags |= FBINFO_MISC_USEREVENT;
        ret = fb_blank(info, arg);        ?*最后调用驱动提供的s3c2410fb_blank*/
        info->flags &= ~FBINFO_MISC_USEREVENT;
        release_console_sem();
        unlock_fb_info(info);
        break;
    default:
        if (!lock_fb_info(info))
            return -ENODEV;
        fb = info->fbops;
        if (fb->fb_ioctl)        /*fb_ioctl为空*/
            ret = fb->fb_ioctl(info, cmd, arg);
        else
            ret = -ENOTTY;
        unlock_fb_info(info);
    }
    return ret;
}

正如2.2小结所说的,fb_fix_screeninfo只能获取不能设置,因此,ioctl只提供了获取fb_fix_screeninfo的方法,而没有提供设置fb_fix_screeninfo的方法。

3.5 小结

  本节对frambuffer的核心层进行了介绍。包括frambuffer子系统的创建,frambuffer的注册和提供给用户空间的5个API函数。下面开始介绍驱动层。

4. 驱动层

本节将开始介绍S3C2440的frambuffer驱动,该驱动源码位于drivers/video/s3c2410fb.c

首先来看下驱动模块的初始化和清除函数。

4.1 s3c2410fb_init和s3c2410fb_cleanup

static struct platform_driver s3c2410fb_driver = {
    .probe        = s3c2410fb_probe,
    .remove        = s3c2410fb_remove,
    .suspend    = s3c2410fb_suspend,
    .resume        = s3c2410fb_resume,
    .driver        = {
        .name    = "s3c2410-lcd",
        .owner    = THIS_MODULE,
    },
};

int __init s3c2410fb_init(void)
{
	int ret = platform_driver_register(&s3c2410fb_driver);

	if (ret == 0)
		ret = platform_driver_register(&s3c2412fb_driver);;

	return ret;
}

static void __exit s3c2410fb_cleanup(void)
{
	platform_driver_unregister(&s3c2410fb_driver);
	platform_driver_unregister(&s3c2412fb_driver);
}

module_init(s3c2410fb_init);
module_exit(s3c2410fb_cleanup);

当platform_driver_register调用的最后会调用probe方法。接下来就来看看probe方法。

4.2 probe方法

struct s3c2410fb_info {
    struct device        *dev;
    struct clk        *clk;

    struct resource        *mem;
    void __iomem        *io;        /*虚拟地址*/
    void __iomem        *irq_base;

    enum s3c_drv_type    drv_type;
    struct s3c2410fb_hw    regs;

    unsigned int        palette_ready;

    /* keep these registers in case we need to re-write palette */
    u32            palette_buffer[256];
    u32            pseudo_pal[16];
};

struct s3c2410fb_mach_info {

	struct s3c2410fb_display *displays;	/* attached diplays info */
	unsigned num_displays;			/* number of defined displays */
	unsigned default_display;

	/* GPIOs */

	unsigned long	gpcup;
	unsigned long	gpcup_mask;
	unsigned long	gpccon;
	unsigned long	gpccon_mask;
	unsigned long	gpdup;
	unsigned long	gpdup_mask;
	unsigned long	gpdcon;
	unsigned long	gpdcon_mask;

	/* lpc3600 control register */
	unsigned long	lpcsel;
};

/* LCD description */
struct s3c2410fb_display {
    /* LCD type */
    unsigned type;

    /* Screen size */
    unsigned short width;
    unsigned short height;

    /* Screen info */
    unsigned short xres;
    unsigned short yres;
    unsigned short bpp;

    unsigned pixclock;        /* pixclock in picoseconds */
    unsigned short left_margin;  /* value in pixels (TFT) or HCLKs (STN) */
    unsigned short right_margin; /* value in pixels (TFT) or HCLKs (STN) */
    unsigned short hsync_len;    /* value in pixels (TFT) or HCLKs (STN) */
    unsigned short upper_margin;    /* value in lines (TFT) or 0 (STN) */
    unsigned short lower_margin;    /* value in lines (TFT) or 0 (STN) */
    unsigned short vsync_len;    /* value in lines (TFT) or 0 (STN) */

    /* lcd configuration registers */
    unsigned long    lcdcon5;
};

static int __init s3c24xxfb_probe(struct platform_device *pdev,
                  enum s3c_drv_type drv_type)
{
    struct s3c2410fb_info *info;
    struct s3c2410fb_display *display;
    struct fb_info *fbinfo;
    struct s3c2410fb_mach_info *mach_info;
    struct resource *res;
    int ret;
    int irq;
    int i;
    int size;
    u32 lcdcon1;
    /*dev.platform_data由函数s3c24xx_fb_set_platdata(mach-smdk2410.c)设置,指向s3c2410fb_mach_info*/
    mach_info = pdev->dev.platform_data;    
    if (mach_info == NULL) {
        dev_err(&pdev->dev,
            "no platform data for lcd, cannot attach\n");
        return -EINVAL;
    }
                                    /*在mach-smdk2440.c中,default_display=0, num_displays=1*/
    if (mach_info->default_display >= mach_info->num_displays) {     
        dev_err(&pdev->dev, "default is %d but only %d displays\n",
            mach_info->default_display, mach_info->num_displays);
        return -EINVAL;
    }

    display = mach_info->displays + mach_info->default_display;

    irq = platform_get_irq(pdev, 0);    /*获取IRQ号,16号中断*/
    if (irq < 0) {
        dev_err(&pdev->dev, "no irq for device\n");
        return -ENOENT;
    }
                                        /*分配struct fb_info 其中包括sizeof字节的私有数据区*/
    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
    if (!fbinfo)
        return -ENOMEM;

    platform_set_drvdata(pdev, fbinfo);    /*让platform_device->dev.driver_data指向struct fb_info*/

    info = fbinfo->par;                    /*par指向s3c2410fb_info*/
    info->dev = &pdev->dev;
    info->drv_type = drv_type;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);/*获取平台资源*/
    if (res == NULL) {
        dev_err(&pdev->dev, "failed to get memory registers\n");
        ret = -ENXIO;
        goto dealloc_fb;
    }

    size = (res->end - res->start) + 1;                /*IO内存申请*/
    info->mem = request_mem_region(res->start, size, pdev->name);    
    if (info->mem == NULL) {
        dev_err(&pdev->dev, "failed to get memory region\n");
        ret = -ENOENT;
        goto dealloc_fb;
    }

    info->io = ioremap(res->start, size);            /*IO内存映射,获取lcd第一个寄存器的映射地址*/                
    if (info->io == NULL) {
        dev_err(&pdev->dev, "ioremap() of registers failed\n");        
        ret = -ENXIO;    
        goto release_mem;
    }
                                            /*irq_base对应的物理地址是0X4D00 0054(寄存器LCDINTPND)*/
    info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
                                                                            
    dprintk("devinit\n");

    strcpy(fbinfo->fix.id, driver_name);    /*复制名字*/

    /* Stop the video */
    lcdcon1 = readl(info->io + S3C2410_LCDCON1);
    writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1); /*禁止LCD*/

    fbinfo->fix.type        = FB_TYPE_PACKED_PIXELS;
    fbinfo->fix.type_aux        = 0;
    fbinfo->fix.xpanstep        = 0;
    fbinfo->fix.ypanstep        = 0;
    fbinfo->fix.ywrapstep        = 0;
    fbinfo->fix.accel        = FB_ACCEL_NONE;    /* no hardware accelerator    */

    fbinfo->var.nonstd        = 0;
    fbinfo->var.activate        = FB_ACTIVATE_NOW;
    fbinfo->var.accel_flags     = 0;
    fbinfo->var.vmode        = FB_VMODE_NONINTERLACED;

    fbinfo->fbops            = &s3c2410fb_ops;
    fbinfo->flags            = FBINFO_FLAG_DEFAULT;
    fbinfo->pseudo_palette      = &info->pseudo_pal;

    for (i = 0; i < 256; i++)
        info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

    ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);    /*申请IRQ,快速中断*/
    if (ret) {
        dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
        ret = -EBUSY;
        goto release_regs;
    }

    info->clk = clk_get(NULL, "lcd");        /*获取时钟信息*/
    if (!info->clk || IS_ERR(info->clk)) {
        printk(KERN_ERR "failed to get lcd clock source\n");
        ret = -ENOENT;
        goto release_irq;
    }

    clk_enable(info->clk);                    /*使能时钟*/
    dprintk("got and enabled clock\n");

    msleep(1);

    /* find maximum required memory size for display */
    /*在多个屏幕中,找出需要的最大memory*/
    for (i = 0; i < mach_info->num_displays; i++) {
        unsigned long smem_len = mach_info->displays[i].xres;
        /*所需的memory空间 = xres * yres * bpp / 8*/
        smem_len *= mach_info->displays[i].yres;
        smem_len *= mach_info->displays[i].bpp;
        smem_len >>= 3;
        if (fbinfo->fix.smem_len < smem_len)
            fbinfo->fix.smem_len = smem_len;
    }

    /* Initialize video memory */    /*根据上面fix.smem_len的大小,获取DMA映射内存,一致性映射方式*/
    ret = s3c2410fb_map_video_memory(fbinfo);
    if (ret) {
        printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
        ret = -ENOMEM;
        goto release_clock;
    }

    dprintk("got video memory\n");

    fbinfo->var.xres = display->xres;            /*320*/
    fbinfo->var.yres = display->yres;            /*240*/    
    fbinfo->var.bits_per_pixel = display->bpp;    /*16*/

    s3c2410fb_init_registers(fbinfo);            /*LCD寄存器初始化*/        

    s3c2410fb_check_var(&fbinfo->var, fbinfo);

    ret = register_framebuffer(fbinfo);            /*注册framebuffer*/
    if (ret < 0) {
        printk(KERN_ERR "Failed to register framebuffer device: %d\n",ret);
        goto free_video_memory;
    }

    /* create device files */
    ret = device_create_file(&pdev->dev, &dev_attr_debug); /*添加设备属性*/
    if (ret) {
        printk(KERN_ERR "failed to add debug attribute\n");
    }

    printk(KERN_INFO "fb%d: %s frame buffer device\n",
        fbinfo->node, fbinfo->fix.id);

    return 0;
/*一旦某个步骤发生错误,以注册的相反顺序开始注销*/
free_video_memory:
    s3c2410fb_unmap_video_memory(fbinfo);
release_clock:
    clk_disable(info->clk);
    clk_put(info->clk);
release_irq:
    free_irq(irq, info);
release_regs:
    iounmap(info->io);
release_mem:
    release_resource(info->mem);
    kfree(info->mem);
dealloc_fb:
    platform_set_drvdata(pdev, NULL);
    framebuffer_release(fbinfo);    
    return ret;
}

这里使用了三个新的数据结构。s3c2410fb_info是驱动程序使用的,里面将保存所有驱动程序所要使用的资源等。而s3c2410fb_display和s3c2410fb_mach_info,是由板级信息,通过platform总线添加到内核中。

s3c2410fb_display中的成员将被复制到fb_var_screeninfo结构中。

该板级信息的定义在arch/arm/mach-s3c2440/mach-smdk2440.c中,来看下

static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {

	.lcdcon5	= S3C2410_LCDCON5_FRM565 |
			  S3C2410_LCDCON5_INVVLINE |
			  S3C2410_LCDCON5_INVVFRAME |
			  S3C2410_LCDCON5_PWREN |
			  S3C2410_LCDCON5_HWSWP,

	.type		= S3C2410_LCDCON1_TFT,

	.width		= 320,//240,
	.height		= 240,//320,

	.pixclock	= 149000,//166667, /* HCLK 60 MHz, divisor 10 */
	.xres		= 320,//240,
	.yres		= 240,//320,
	.bpp		= 16,
	.left_margin	= 20,
	.right_margin	= 38,//8,
	.hsync_len	= 30,//4,
	.upper_margin	= 15,//8,
	.lower_margin	= 12,//7,
	.vsync_len	= 3,//4,
};

static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
	.displays	= &smdk2440_lcd_cfg,
	.num_displays	= 1,
	.default_display = 0,

#if 0
	/* currently setup by downloader */
	.gpccon		= 0xaa940659,
	.gpccon_mask	= 0xffffffff,
	.gpcup		= 0x0000ffff,
	.gpcup_mask	= 0xffffffff,
	.gpdcon		= 0xaa84aaa0,
	.gpdcon_mask	= 0xffffffff,
	.gpdup		= 0x0000faff,
	.gpdup_mask	= 0xffffffff,
#endif
//no
//	.lpcsel		= ((0xCE6) & ~7) | 1<<4,
};

这里NOTE:每个LCD屏幕的参数不一样,因此上面的参数会有所不同,这是需要移植的地方。

随后,我们看下在probe方法中调用的几个函数。首先是s3c2410fb_map_video_memory。

/*
 * s3c2410fb_map_video_memory():
 *	Allocates the DRAM memory for the frame buffer.  This buffer is
 *	remapped into a non-cached, non-buffered, memory region to
 *	allow palette and pixel writes to occur without flushing the
 *	cache.  Once this area is remapped, all virtual memory
 *	access to the video memory should occur at the new region.
 */
static int __init s3c2410fb_map_video_memory(struct fb_info *info)
{
	struct s3c2410fb_info *fbi = info->par;
	dma_addr_t map_dma;
	unsigned map_size = PAGE_ALIGN(info->fix.smem_len);

	dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size);
		/*分配DMA缓冲区,并保存DMA缓冲区虚拟地址*/
	info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,
						   &map_dma, GFP_KERNEL);

	if (info->screen_base) {
		/* prevent initial garbage on screen */
		dprintk("map_video_memory: clear %p:%08x\n",
			info->screen_base, map_size);
		memset(info->screen_base, 0x00, map_size);	/*DMA缓冲区清0*/

		info->fix.smem_start = map_dma;	/*保存DMA缓冲区物理地址*/

		dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",
			info->fix.smem_start, info->screen_base, map_size);
	}

	return info->screen_base ? 0 : -ENOMEM;
}

该函数根据fix.smem_len的大小,分配了一个DMA缓冲区,保存了该缓冲区的物理地址和虚拟地址。

接着是s3c2410fb_init_registers:

/*
 * s3c2410fb_init_registers - Initialise all LCD-related registers
 */
static int s3c2410fb_init_registers(struct fb_info *info)
{
    struct s3c2410fb_info *fbi = info->par;        /*par指向s3c2410fb_info*/
    struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
    unsigned long flags;
    void __iomem *regs = fbi->io;
    void __iomem *tpal;
    void __iomem *lpcsel;

    if (is_s3c2412(fbi)) {
        tpal = regs + S3C2412_TPAL;
        lpcsel = regs + S3C2412_TCONSEL;
    } else {
        tpal = regs + S3C2410_TPAL;
        lpcsel = regs + S3C2410_LPCSEL;
    }

    /* Initialise LCD with values from haret */

    local_irq_save(flags);                        /*禁止所有中断*/

    /* modify the gpio(s) with interrupts set (bjd) */    /*初始化io管脚*/

    modify_gpio(S3C2410_GPCUP,  mach_info->gpcup,  mach_info->gpcup_mask);
    modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
    modify_gpio(S3C2410_GPDUP,  mach_info->gpdup,  mach_info->gpdup_mask);
    modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);

    local_irq_restore(flags);                    /*恢复中断*/

    dprintk("LPCSEL    = 0x%08lx\n", mach_info->lpcsel);    /*设置TCONSEL,禁止LPC3600*/
    writel(mach_info->lpcsel, lpcsel);

    dprintk("replacing TPAL %08x\n", readl(tpal));

    /* ensure temporary palette disabled */
    writel(0x00, tpal);    /*禁止调色板*/

    return 0;
}

static inline void modify_gpio(void __iomem *reg,
                   unsigned long set, unsigned long mask)
{
    unsigned long tmp;

    tmp = readl(reg) & ~mask;
    writel(tmp | set, reg);
} 

最后是s3c2410fb_check_var:

/*
 *	s3c2410fb_check_var():
 *	Get the video params out of 'var'. If a value doesn't fit, round it up,
 *	if it's too big, return -EINVAL.
 *	检查变量的合法性
 */
static int s3c2410fb_check_var(struct fb_var_screeninfo *var,
			       struct fb_info *info)
{
	struct s3c2410fb_info *fbi = info->par;			/*par指向s3c2410fb_info*/
	struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;/*指向s3c2410fb_mach_info*/
	struct s3c2410fb_display *display = NULL;
	struct s3c2410fb_display *default_display = mach_info->displays +
						    mach_info->default_display;
	int type = default_display->type; 	/*S3C2410_LCDCON1_TFT*/
	unsigned i;

	dprintk("check_var(var=%p, info=%p)\n", var, info);

	/* validate x/y resolution */
	/* choose default mode if possible */			/*var中的成员在probe中设置*/
	if (var->yres == default_display->yres &&
	    var->xres == default_display->xres &&
	    var->bits_per_pixel == default_display->bpp)
		display = default_display;
	else
		for (i = 0; i < mach_info->num_displays; i++)
			if (type == mach_info->displays[i].type &&
			    var->yres == mach_info->displays[i].yres &&
			    var->xres == mach_info->displays[i].xres &&
			    var->bits_per_pixel == mach_info->displays[i].bpp) {
				display = mach_info->displays + i;
				break;
			}

	if (!display) {
		dprintk("wrong resolution or depth %dx%d at %d bpp\n",
			var->xres, var->yres, var->bits_per_pixel);
		return -EINVAL;
	}

	/* it is always the size as the display */
	var->xres_virtual = display->xres;
	var->yres_virtual = display->yres;
	var->height = display->height;
	var->width = display->width;

	/* copy lcd settings */
	var->pixclock = display->pixclock;
	var->left_margin = display->left_margin;
	var->right_margin = display->right_margin;
	var->upper_margin = display->upper_margin;
	var->lower_margin = display->lower_margin;
	var->vsync_len = display->vsync_len;
	var->hsync_len = display->hsync_len;

	fbi->regs.lcdcon5 = display->lcdcon5;
	/* set display type */
	fbi->regs.lcdcon1 = display->type;

	var->transp.offset = 0;
	var->transp.length = 0;
	/* set r/g/b positions */
	switch (var->bits_per_pixel) {
	case 1:
	case 2:
	case 4:
		var->red.offset	= 0;
		var->red.length	= var->bits_per_pixel;
		var->green	= var->red;
		var->blue	= var->red;
		break;
	case 8:
		if (display->type != S3C2410_LCDCON1_TFT) {
			/* 8 bpp 332 */
			var->red.length		= 3;
			var->red.offset		= 5;
			var->green.length	= 3;
			var->green.offset	= 2;
			var->blue.length	= 2;
			var->blue.offset	= 0;
		} else {
			var->red.offset		= 0;
			var->red.length		= 8;
			var->green		= var->red;
			var->blue		= var->red;
		}
		break;
	case 12:
		/* 12 bpp 444 */
		var->red.length		= 4;
		var->red.offset		= 8;
		var->green.length	= 4;
		var->green.offset	= 4;
		var->blue.length	= 4;
		var->blue.offset	= 0;
		break;

	default:
	case 16:
		if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) { /*使用565格式*/
			/* 16 bpp, 565 format */
			var->red.offset		= 11;
			var->green.offset	= 5;
			var->blue.offset	= 0;
			var->red.length		= 5;
			var->green.length	= 6;
			var->blue.length	= 5;
		} else {
			/* 16 bpp, 5551 format */
			var->red.offset		= 11;
			var->green.offset	= 6;
			var->blue.offset	= 1;
			var->red.length		= 5;
			var->green.length	= 5;
			var->blue.length	= 5;
		}
		break;
	case 32:
		/* 24 bpp 888 and 8 dummy */
		var->red.length		= 8;
		var->red.offset		= 16;
		var->green.length	= 8;
		var->green.offset	= 8;
		var->blue.length	= 8;
		var->blue.offset	= 0;
		break;
	}
	return 0;
}

/* Interpretation of offset for color fields: All offsets are from the right,
 * inside a "pixel" value, which is exactly 'bits_per_pixel' wide (means: you
 * can use the offset as right argument to <<). A pixel afterwards is a bit
 * stream and is written to video memory as that unmodified.
 *
 * For pseudocolor: offset and length should be the same for all color
 * components. Offset specifies the position of the least significant bit
 * of the pallette index in a pixel value. Length indicates the number
 * of available palette entries (i.e. # of entries = 1 << length).
 */
struct fb_bitfield {
    __u32 offset;            /* beginning of bitfield    */
    __u32 length;            /* length of bitfield        */
    __u32 msb_right;        /* != 0 : Most significant bit is */ 
                    /* right */ 
};

该函数主要将板级信息s3c2410fb_display复制到对应的地方,然后根据RGB的模式设置位域。

4.3 fb_ops方法

在驱动程序中,定义了fb_ops,如下:

static struct fb_ops s3c2410fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= s3c2410fb_check_var,		/*检查变量的合法性*/
	.fb_set_par	= s3c2410fb_set_par,			/*将参数写入LCD控制器,该函数由帧缓冲核心调用*/
	.fb_blank	= s3c2410fb_blank,				/*该方法支持显示消隐和去消隐*/
	.fb_setcolreg	= s3c2410fb_setcolreg,		/*设置颜色寄存器*/
	.fb_fillrect	= cfb_fillrect,				/*用像素行填充矩形框,通用库函数*/
	.fb_copyarea	= cfb_copyarea,				/*将屏幕的一个矩形区域复制到另一个区域,通用库函数*/
	.fb_imageblit	= cfb_imageblit,			/*显示一副图像,通用库函数*/
};

其中s3c2410fb_check_var在4.2节中已经分析过了,最后三个方法是通用库函数,在这里不作分析。
剩余三个也是驱动程序提供的,现在对这三个程序进行分析。

4.3. 1 s3c2410fb_set_par

/*
 *      s3c2410fb_set_par - Alters the hardware state.
 *      @info: frame buffer structure that represents a single frame buffer
 *		根据var中的值设置LCD控制器的寄存器
 */
static int s3c2410fb_set_par(struct fb_info *info)
{
	struct fb_var_screeninfo *var = &info->var;

	switch (var->bits_per_pixel) {
	case 32:
	case 16:
	case 12:
		info->fix.visual = FB_VISUAL_TRUECOLOR;
		break;
	case 1:
		info->fix.visual = FB_VISUAL_MONO01;
		break;
	default:
		info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
		break;
	}

	info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; /* 320*16/8 = 640Bytes */

	/* activate this new configuration */

	s3c2410fb_activate_var(info);
	return 0;
}

该函数根据像素的位数设置了视觉模式,本例为16为,使用真彩色。然后计算了每行的数据元素大小。共240行。

然后调用了s3c2410fb_activate_var来设置控制器并激活LCD。

s3c2410fb_activate_var函数如下:

/* s3c2410fb_activate_var
 *
 * activate (set) the controller from the given framebuffer
 * information
 */
static void s3c2410fb_activate_var(struct fb_info *info)
{
	struct s3c2410fb_info *fbi = info->par;
	void __iomem *regs = fbi->io;
	int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT; /*regs.lcdcon1在s3c2410fb_check_var设置*/
	struct fb_var_screeninfo *var = &info->var;
	int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock) / 2;

	dprintk("%s: var->xres  = %d\n", __func__, var->xres);
	dprintk("%s: var->yres  = %d\n", __func__, var->yres);
	dprintk("%s: var->bpp   = %d\n", __func__, var->bits_per_pixel);

	if (type == S3C2410_LCDCON1_TFT) {
		s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);/*根据var,计算出控制寄存器需要设置的值*/
		--clkdiv;
		if (clkdiv < 0)
			clkdiv = 0;
	} else {
		s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);
		if (clkdiv < 2)
			clkdiv = 2;
	}

	fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);/*设置CLKVAL*/

	/* write new registers */

	dprintk("new register set:\n");
	dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);
	dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);
	dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
	dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
	dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);
	/*把计算好的值填入LCD控制器中*/
	writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,	
		regs + S3C2410_LCDCON1);						/*仍然禁止LCD*/
	writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);
	writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);
	writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);
	writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);

	/* set lcd address pointers */
	s3c2410fb_set_lcdaddr(info);						/*设置LCD帧缓冲起始地址*/

	fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
	writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);	/*使能LCD*/
}

其中调用的三个函数如下:

static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,
                      unsigned long pixclk)
{
    unsigned long clk = clk_get_rate(fbi->clk);         /*获取当前时钟频率(Hz)*/
    unsigned long long div;

    /* pixclk is in picoseconds, our clock is in Hz
     *
     * Hz -> picoseconds is / 10^-12
     */

    div = (unsigned long long)clk * pixclk;
    div >>= 12;            /* div / 2^12 */
    do_div(div, 625 * 625UL * 625); /* div / 5^12 */

    dprintk("pixclk %ld, divisor is %ld\n", pixclk, (long)div);
    return div;
}

/* s3c2410fb_calculate_tft_lcd_regs
 *
 * calculate register values from var settings
 */
static void s3c2410fb_calculate_tft_lcd_regs(const struct fb_info *info,
                         struct s3c2410fb_hw *regs)
{
    const struct s3c2410fb_info *fbi = info->par;
    const struct fb_var_screeninfo *var = &info->var;

    switch (var->bits_per_pixel) {
    case 1:
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
        break;
    case 2:
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
        break;
    case 4:
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
        break;
    case 8:
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
        regs->lcdcon5 |= S3C2410_LCDCON5_BSWP |
                 S3C2410_LCDCON5_FRM565;
        regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;
        break;
    case 16:
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
        regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
        regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;
        break;
    case 32:
        regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;
        regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP |
                   S3C2410_LCDCON5_HWSWP |
                   S3C2410_LCDCON5_BPP24BL);
        break;
    defa

抱歉!评论已关闭.