帧缓冲FrameBuffer设备驱动程序分析总结

本文详细探讨了Linux系统中的帧缓冲区概念,介绍了其在显示设备接口中的作用,以及如何通过抽象显示缓冲区来简化上层应用的读写操作。文章深入分析了fbmem.c核心层、fb_info结构体、fb_var_screeninfo和fb_fix_screeninfo的作用,同时讲解了fb_ops硬件操作方法结构,以及在s3c2410fb.c中具体设备驱动层的实现细节。
摘要由CSDN通过智能技术生成

前言:

                                             

                                                                     图1、FramerBuffer的简单层次图

                                        

                                                                     图2、FrameBuffer驱动的结构框架     

1、fbmem.c核心层

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

         framebuffer机制模仿显卡的功能,将显卡硬件结构抽象为一系列的数据结构,可以通过framebuffer的读写直接对显存进行操作。用户可以将framebuffer看成是显存的一个映像,将其映射到进程空间后,就可以直接进行读写操作,写操作会直接反映在屏幕上。

fbmem.c向上给应用程序提供完善的设备文件操作接口(即对FrameBuffer设备进行read、write、ioctl等操作),接口在Linux提供的fbmem.c文件中实现;向下提供硬件操作的接口fb_ops里面的函数,或者一些公共函数,比如fb_set_var函数,只是这些接口在Linux中有默认的实现代码,如果用户没有根据具体的LCD控制器硬件进行实现个性化的接口代码,由内核会默认使用通用的代码。所以,对于这些接口,大部分可以不实现,只要实现一小部分就可以了。

fb_info结构体记录了帧缓冲设备的全部信息,包括设备的设置参数、状态以及底层硬件操作的函数指针。在Linux中,每一个帧缓冲设备都必须对应一个fb_info,fb_info在/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 LCD可变参数结构体*/
	struct fb_fix_screeninfo fix;	/* Current fix LCD固定参数结构体*/
	struct fb_monspecs monspecs;	/* Current Monitor specs LCD显示器标准*/
	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 当前的显示模式*/

#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 图块Blitting*/
#endif
	char __iomem *screen_base;	/* Virtual address 虚拟基地址*/
	unsigned long screen_size;/*Amount of ioremapped VRAM or 0 LCD I/O映射的虚拟内存大小*/ 
	void *pseudo_palette;		/* Fake palette of 16 colors 伪16色颜色表*/ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend LCD挂起或恢复状态*/
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;	        /*LCD驱动的私有数据*/
};

其中,比较重要的成员有struct fb_var_screeninfo var、struct fb_fix_screeninfo fix和struct fb_ops* fbops。

1、LCD设备驱动可变参数结构struct fb_var_screeninfo

fb_var_screeninfo结构体主要记录用户可以修改的控制器的参数,如屏幕的分辨率和每个像素的比特数等。

该结构体定义如下:

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、LCD设备驱动固定参数结构struct fb_fix_screeninfo

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

以上结构体中比较重要的成员是smem_start、smem_len、line_length、mmio_start。这个结构是LCD固定参数,在LCD正常运行期间是不能修改的。所以这些值一般是在驱动程序的初始化阶段完成填充,当LCD工作起来后就不能再修改。

3、LCD设备驱动硬件操作方法结构struct 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 */
	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);
};

这个结构是真正实现不同LCD的硬件操作函数功能,但是一般情况下,如果我们的LCD控制器不属于独立显卡类型,那这个结构的很多成员都是不用实现的,如结构的fp_open、fp_release、fb_read、fb_write等这些函数就不同实现,而使用FrameBuffer的核心层fbmem.c实现的通用函数就可以了。

fb_ops结构体操作的具体的独立显卡类型,在设备驱动的操作函数中,调用fb_ops的函数设置具体的显卡。可以认为file_operations是统一的接口,fb_ops是具体显卡硬件的操作。例如:

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];
	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;
	if (info->fbops->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_ops的open接口函数

if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}

以上结构如果要实现和控制台相关的功能,结构中的fb_check_var、fb_set_par、fb_blank、fb_cousor、fb_fillrect、fb_copyarea、fb_imagebilt接口是要实现的,但是除fb_check_var、fb_set_par、fb_blank这三个要根据用户自己的硬件特征来实现不同的代码外,其余的都可以直接使用内核中已经实现的默认函数(内核实现的通用代码),这些在后面的代码分析中将会看到。

在s3c2410fb.c中实现对具体显卡硬件设备的操作:

static struct fb_ops s3c2410fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= s3c2410fb_check_var,
	.fb_set_par	= s3c2410fb_set_par,
	.fb_blank	= s3c2410fb_blank,
	.fb_setcolreg	= s3c2410fb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};

s3c2410fb.c具体设备驱动层

注册一个fb_info进入数组中,完成一个帧缓冲设备驱动程序的初始化。在probe函数s3c24xxfb_probe分析:

//s3c_drv_type表示是DRV_S3C2410
static int __init s3c24xxfb_probe(struct platform_device *pdev,
				  enum s3c_drv_type drv_type)
{
	struct s3c2410fb_info *info;//对应lcd显示屏的设备结构体
	struct s3c2410fb_display *display;//lcd显示屏的一些参数
	struct fb_info *fbinfo;//lcd的现鼠缓冲区的结构体
	struct s3c2410fb_mach_info *mach_info;//lcd显示器硬件寄存器地址
	struct resource *res;
	int ret;
	int irq;
	int i;
	int size;
	u32 lcdcon1;
	
//对于一个具体的lcd一定要有描述特定的lcd设备的结构体s3c2410fb_mach_info
	mach_info = pdev->dev.platform_data;//devs目录下的s3c24xx_fb_set_platdata中设置了dev.platform_data
	if (mach_info == NULL) {
		dev_err(&pdev->dev,
			"no platform data for lcd, cannot attach\n");
		return -EINVAL;
	}
//default_display默认的显示器,num_displays显示器数量
	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;
	}
	
//指向默认使用的显示器的displays数组里面的displays成员指针
	display = mach_info->displays + mach_info->default_display;

//获取中断号
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no irq for device\n");
		return -ENOENT;
	}

//申请帧缓冲设备空间
	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
	if (!fbinfo)
		return -ENOMEM;

//设置为是由数据
	platform_set_drvdata(pdev, fbinfo);

//指向fb_info是由数据指针
	info = fbinfo->par;
	info->dev = &pdev->dev;//指示父设备
	info->drv_type = drv_type;

//获取地址resource
	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;
	//设置了resource的res->flags = IORESOURCE_BUSY;
	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;
	}

//io的虚拟地址
	info->io = ioremap(res->start, size);
	if (info->io == NULL) {
		dev_err(&pdev->dev, "ioremap() of registers failed\n");
		ret = -ENXIO;
		goto release_mem;
	}

//中断寄存器地址
	info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);

	dprintk("devinit\n");

//名字,用于区分
	strcpy(fbinfo->fix.id, driver_name);

//配置LCD控制器的寄存器
	/* Stop the video */
	lcdcon1 = readl(info->io + S3C2410_LCDCON1);
	writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);

//配置struct fb_fix_screeninfo
	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;

//配置struct fb_var_screeninfo 
	fbinfo->var.nonstd	    = 0;
	fbinfo->var.activate	    = FB_ACTIVATE_NOW;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    = FB_VMODE_NONINTERLACED;

//ops通用的lcd操作函数
	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);
	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);

//mach_info->displays[i]是一系列的lcd显示屏支持列表,在这里之前就已经初始化好,或者定义好
//在mach-smdk中集成了设备的自身配置的一些初始化操作
	/* find maximum required memory size for display */
	for (i = 0; i < mach_info->num_displays; i++) {
		unsigned long smem_len = mach_info->displays[i].xres;//x行数量

		smem_len *= mach_info->displays[i].yres;//y行数量
		smem_len *= mach_info->displays[i].bpp;//总共需要的数据位数
		smem_len >>= 3;//右移3为=除以8,转化为字节
		if (fbinfo->fix.smem_len < smem_len)
			fbinfo->fix.smem_len = smem_len;//存储字节数
	}

//初始化内存,获取DMA地址
	/* Initialize video memory */
	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;
	fbinfo->var.yres = display->yres;
	fbinfo->var.bits_per_pixel = display->bpp;

	s3c2410fb_init_registers(fbinfo);

	s3c2410fb_check_var(&fbinfo->var, fbinfo);

//把fbinfo加入数组中
	ret = register_framebuffer(fbinfo);
	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;
}

struct fb_info结构体描述的是一个显卡设备,里面包含了一个显卡所必须的资源信息,配置参数等。基于平台设备驱动程序的框架在platform_device中保留硬件所需的资源信息,物理地址以及中断号。在probe函数中填充fb_info以及在器私有数据结构体中附带上其私有数据信息。最后,调用ret = register_framebuffer(fbinfo);把一个完整的fbinfo加入全局数组registered_fb中,其它函数首先在数组中利用设备驱动节点的次设备号作为数组下标找到fbinfo,取出里面对应的数据,完成实现的功能。

register_framebuffer实现如下:

/**
 *	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)
		return -ENXIO;
//检查struct fb_info中的flag
	if (fb_check_foreignness(fb_info))
		return -ENOSYS;
//+1个设备注册
	num_registered_fb++;
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])//检查一个空的
			break;
	fb_info->node = i;
	mutex_init(&fb_info->lock);
//class在子系统的已经建立,fb_class是全局变量,生成设备节点
	fb_info->dev = device_create(fb_class, fb_info->device,
				     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);//建立sys下的文件
//初始化fb_infod的pixmap
	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		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_add_videomode(&mode, &fb_info->modelist);
	registered_fb[i] = fb_info;//加入数组中,根据打开的设备文件的设备号中的次设备号查找到fb_info

	event.info = fb_info;
	if (!lock_fb_info(fb_info))
		return -ENODEV;
//最终调用了take_over_console
//event对应fbcon_event_notify函数
//fb_notifier_call_chain最终执行的是fb_notifier_call_chain
//该函数跨越到了fbcon.c文件的函数执行
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	return 0;
}

 

帧缓冲设备驱动中使用DMA进行内存数据和外设之间的传递,帧缓冲设备驱动的DMA缓冲区内存分配及映射由如下函数实现

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

/*
A = dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *handle, gfp_t gfp);

含义:
A          : 内存的虚拟起始地址,在内核要用此地址来操作所分配的内存
dev      : 可以平台初始化里指定,主要是用到dma_mask之类参数,可参考framebuffer
size      : 实际分配大小,传入dma_map_size即可
handle: 返回的内存物理地址,dma就可以用。


A和hanle是一一对应的,A是虚拟地址,而handle是总线地址。对任意一个操作都将改变写缓冲区内容。

*/

//获取DMA,info->screen_base
	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);

		info->fix.smem_start = map_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;
}

s3c2410fb_map_video_memory在s3c24xxfb_probe中调用

//s3c_drv_type表示是DRV_S3C2410
static int __init s3c24xxfb_probe(struct platform_device *pdev,
				  enum s3c_drv_type drv_type)
{
	struct s3c2410fb_info *info;//对应lcd显示屏的设备结构体
	struct s3c2410fb_display *display;//lcd显示屏的一些参数
	struct fb_info *fbinfo;//lcd的现鼠缓冲区的结构体
	struct s3c2410fb_mach_info *mach_info;//lcd显示器硬件寄存器地址
	struct resource *res;
	int ret;
	int irq;
	int i;
	int size;
	u32 lcdcon1;
	
//对于一个具体的lcd一定要有描述特定的lcd设备的结构体s3c2410fb_mach_info
	mach_info = pdev->dev.platform_data;//devs目录下的s3c24xx_fb_set_platdata中设置了dev.platform_data
	if (mach_info == NULL) {
		dev_err(&pdev->dev,
			"no platform data for lcd, cannot attach\n");
		return -EINVAL;
	}
//default_display默认的显示器,num_displays显示器数量
	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;
	}
	
//指向默认使用的显示器的displays数组里面的displays成员指针
	display = mach_info->displays + mach_info->default_display;

//获取中断号
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no irq for device\n");
		return -ENOENT;
	}

//申请帧缓冲设备空间
	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
	if (!fbinfo)
		return -ENOMEM;

//设置为是由数据
	platform_set_drvdata(pdev, fbinfo);

//指向fb_info是由数据指针
	info = fbinfo->par;
	info->dev = &pdev->dev;//指示父设备
	info->drv_type = drv_type;

//获取地址resource
	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;
	//设置了resource的res->flags = IORESOURCE_BUSY;
	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;
	}

//io的虚拟地址
	info->io = ioremap(res->start, size);
	if (info->io == NULL) {
		dev_err(&pdev->dev, "ioremap() of registers failed\n");
		ret = -ENXIO;
		goto release_mem;
	}

//中断寄存器地址
	info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);

	dprintk("devinit\n");

//名字,用于区分
	strcpy(fbinfo->fix.id, driver_name);

//配置LCD控制器的寄存器
	/* Stop the video */
	lcdcon1 = readl(info->io + S3C2410_LCDCON1);
	writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);

//配置struct fb_fix_screeninfo
	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;

//配置struct fb_var_screeninfo 
	fbinfo->var.nonstd	    = 0;
	fbinfo->var.activate	    = FB_ACTIVATE_NOW;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    = FB_VMODE_NONINTERLACED;

//ops通用的lcd操作函数
	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);
	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);

//mach_info->displays[i]是一系列的lcd显示屏支持列表,在这里之前就已经初始化好,或者定义好
//在mach-smdk中集成了设备的自身配置的一些初始化操作
	/* find maximum required memory size for display */
	for (i = 0; i < mach_info->num_displays; i++) {
		unsigned long smem_len = mach_info->displays[i].xres;//x行数量

		smem_len *= mach_info->displays[i].yres;//y行数量
		smem_len *= mach_info->displays[i].bpp;//总共需要的数据位数
		smem_len >>= 3;//右移3为=除以8,转化为字节
		if (fbinfo->fix.smem_len < smem_len)
			fbinfo->fix.smem_len = smem_len;//存储字节数
	}

//初始化内存,获取DMA地址
	/* Initialize video memory */
	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;
	fbinfo->var.yres = display->yres;
	fbinfo->var.bits_per_pixel = display->bpp;

	s3c2410fb_init_registers(fbinfo);

	s3c2410fb_check_var(&fbinfo->var, fbinfo);

//把fbinfo加入数组中
	ret = register_framebuffer(fbinfo);
	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;
}

s3c24xxfb_probe完成了部分fb_info结构体的初始化。剩余的初始化在    ret = register_framebuffer(fbinfo);中

/**
 *	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)
		return -ENXIO;
//检查struct fb_info中的flag
	if (fb_check_foreignness(fb_info))
		return -ENOSYS;
//+1个设备注册
	num_registered_fb++;
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])//检查一个空的
			break;
	fb_info->node = i;
	mutex_init(&fb_info->lock);
//class在子系统的已经建立,fb_class是全局变量,生成设备节点
	fb_info->dev = device_create(fb_class, fb_info->device,
				     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);//建立sys下的文件
//初始化fb_infod的pixmap
	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		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_add_videomode(&mode, &fb_info->modelist);
	registered_fb[i] = fb_info;//加入数组中,根据打开的设备文件的设备号中的次设备号查找到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;
}

实现上面的过程分析的过程中都是没有疑惑的,可是到了DMA地址配置到LCD控制器的地址寄存器的这个步骤就不明白了,这里找了两天,今天终于迷迷糊糊明白了。

对于地址的设置不是在注册帧缓冲区设备驱动的过程实现,而是在系统中fbcon中调用,里面有很多函数调用到功能。

/*帧缓冲控制台操作函数
 *  The console `switch' structure for the frame buffer based console
 */

static const struct consw fb_con = {
	.owner			= THIS_MODULE,
	.con_startup 		= fbcon_startup,
	.con_init 		= fbcon_init,
	.con_deinit 		= fbcon_deinit,
	.con_clear 		= fbcon_clear,
	.con_putc 		= fbcon_putc,
	.con_putcs 		= fbcon_putcs,
	.con_cursor 		= fbcon_cursor,
	.con_scroll 		= fbcon_scroll,
	.con_bmove 		= fbcon_bmove,
	.con_switch 		= fbcon_switch,
	.con_blank 		= fbcon_blank,
	.con_font_set 		= fbcon_set_font,
	.con_font_get 		= fbcon_get_font,
	.con_font_default	= fbcon_set_def_font,
	.con_font_copy 		= fbcon_copy_font,
	.con_set_palette 	= fbcon_set_palette,
	.con_scrolldelta 	= fbcon_scrolldelta,
	.con_set_origin 	= fbcon_set_origin,
	.con_invert_region 	= fbcon_invert_region,
	.con_screen_pos 	= fbcon_screen_pos,
	.con_getxy 		= fbcon_getxy,
	.con_resize             = fbcon_resize,
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值