framebuffer 子系统分析

一、常见结构体分析

1、fb_info

struct fb_info {
	int node;
	int flags;
	struct mutex lock;		/* 调用open/release/ioctl时的锁 */
	struct mutex mm_lock;		/* fb_mmap和smem_*的锁 */
	struct fb_var_screeninfo var;	/* 当前LCD的可变参数 */
	struct fb_fix_screeninfo fix;	/* 当前LCD的固定参数 */
	struct fb_monspecs monspecs;	/* 当前LCD标准 */
	struct work_struct queue;	/* 帧缓冲工作队列 */
	struct fb_pixmap pixmap;	/* 图像硬件mapper */
	struct fb_pixmap sprite;	/* 光标硬件mapper */
	struct fb_cmap cmap;		/* 当前LCD的颜色表 */
	struct list_head modelist;      /* mode list */
	struct fb_videomode *mode;	/* 当前的video模式 */

#ifdef CONFIG_FB_BACKLIGHT
	/* 对应的背光设备,在framebuffer之前注册及其之后注销 */
	struct backlight_device *bl_dev;

	/* 背光等级调整 */
	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;		/* 父设备 */
	struct device *dev;		/* fb设备*/
	int class_flag;                    /* 私有sysfs标志 */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	char __iomem *screen_base;	/* 虚拟基地址即framebuffer起始虚拟地址 */
	unsigned long screen_size;	/* 显存大小 */ 
	void *pseudo_palette;		/* 伪16色颜色表 */ 
#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 */
	/* 私有数据 */
	void *par;
	/* we need the PCI or similiar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	struct apertures_struct {
		unsigned int count;
		struct aperture {
			resource_size_t base;
			resource_size_t size;
		} ranges[0];
	} *apertures;
};
帧缓冲设备中最关键的数据结构就是fb_info,其中记录了设备的全部信息,包括设备的设置参数、状态以及操作函数指针,每一个帧缓冲设备必须对应一个fb_info结构体。

2、fb_var_screeninfo 和 fb_fix_screeninfo

struct fb_var_screeninfo {
	__u32 xres;			/* 可视分辨率 */
	__u32 yres;
	__u32 xres_virtual;		/* 虚拟分辨率 */
	__u32 yres_virtual;
	__u32 xoffset;			/* 虚拟到可视之间的偏移 */
	__u32 yoffset;		

	__u32 bits_per_pixel;		/* 每个像素点占用的位数 */
	__u32 grayscale;		/* 非0时指灰度值 */

	struct fb_bitfield red;		/* fb内存的RGB位域 */
	struct fb_bitfield green;
	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;		/* (OBSOLETE) see fb_info.flags */

	/* 时序: 除pixel以外,其他的都以像素时钟为单位 */
	__u32 pixclock;			/* 像素时钟(皮秒) */
	__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];		/* 保留备用 */
};

struct fb_fix_screeninfo {
	char id[16];			/* 标识符 */
	unsigned long smem_start;	/* framebuffer的物理起始地址 */
	__u32 smem_len;			/* framebuffer的长度 */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* 隔行扫描参数 */
	__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;		/* 一行的字节数 */
	unsigned long mmio_start;	/* 内存映射I/O的物理起始地址 */
	__u32 mmio_len;			/* 内存映射I/O的长度 */
	__u32 accel;			
	__u16 reserved[3];		/* 保留备用 */
};
这两个结构体分别记录了显示器可以修改和不可修改的信息,这些数据成员需要在驱动程序中初始化。其中fix.visual代表显示器使用的色彩模式,Linux中支持的色彩模式如下:

#define FB_VISUAL_MONO01		0	/* 黑白. 1=Black 0=White */
#define FB_VISUAL_MONO10		1	/* 黑白. 1=White 0=Black */
#define FB_VISUAL_TRUECOLOR		2	/* 真彩色,由红绿蓝三基色构成*/
#define FB_VISUAL_PSEUDOCOLOR		3	/* 伪彩色,采用索引颜色 */
#define FB_VISUAL_DIRECTCOLOR		4	/* 查表显示 */
#define FB_VISUAL_STATIC_PSEUDOCOLOR	5	/* 只读的伪彩色 */

fb_bitfield结构体描述每一个像素在显示缓冲区中的组织方式。

struct fb_bitfield {
	__u32 offset;			/* 位域偏移 */
	__u32 length;			/* 位域长度 */
	__u32 msb_right;		/* != 0 : 最高有效位在右边 */ 
};

3、fb_ops

struct fb_ops {
	/* 打开/释放 */
	struct module *owner;
	int (*fb_open)(struct fb_info *info, int user);
	int (*fb_release)(struct fb_info *info, int user);

	/* 下面两个函数是非线性布局的/常规内存映射无法工作的缓冲设备需要的接口 */
	ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
			   size_t count, loff_t *ppos);
	ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
			    size_t count, loff_t *ppos);

	/* 检测可变参数并调整到支持的值 */
	int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

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

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

	/* 批量设置颜色寄存器 */
	int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

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

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

	/* 绘制一个矩形 */
	void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
	/* 拷贝数据 */
	void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
	/* 绘制一幅图像 */
	void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

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

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

	/* 等待blit空闲(可选) */
	int (*fb_sync)(struct fb_info *info);

	/* ioctl函数(可选) */
	int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
			unsigned long arg);

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

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

	/* 根据var得到显示器的功能信息 */
	void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
			    struct fb_var_screeninfo *var);

	/* 销毁fb */
	void (*fb_destroy)(struct fb_info *info);
};

fb_ops是驱动编写人员应该实现的底层函数接口。

4、tccfb_info

struct tccfb_info {
	struct fb_info		*fb;  // fbi结构体
	struct device		*dev;

	//struct tccfb_mach_info *mach_info;

	/* raw memory addresses */
	dma_addr_t		map_dma;	/* physical */
	u_char *		map_cpu;	/* virtual */
	u_int			map_size;

	/* addresses of pieces placed in raw buffer */
	u_char *		screen_cpu;	/* virtual address of buffer */
	dma_addr_t		screen_dma;	/* physical address of buffer */

	u_int			imgch;

	struct tccfb_platform_data *pdata;
#ifdef CONFIG_HAS_EARLYSUSPEND
    struct early_suspend early_suspend;
    struct early_suspend earlier_suspend;
#endif
};

二、framebuffer注册流程分析

static struct platform_driver tccfb_driver = {  // 底层函数接口
	.probe		= tccfb_probe,
	.remove		= tccfb_remove,
	.suspend	= tccfb_suspend,
	.shutdown	= tccfb_shutdown,
	.resume		= tccfb_resume,
	.driver		= {
		.name	= "tccxxx-lcd",
		.owner	= THIS_MODULE,
	},
};

static int __init tccfb_init(void)
{
    printk(KERN_INFO " %s (built %s %s)\n", __func__, __DATE__, __TIME__);

	fb_power_state = 1;

	tca_fb_init();   // 初始化控制器

#ifdef TCC_VIDEO_DISPLAY_BY_VSYNC_INT   // 初始化锁
	spin_lock_init(&vsync_lock) ;
	spin_lock_init(&vsync_lockDisp ) ;
#endif

	return platform_driver_register(&tccfb_driver);
}

static void __exit tccfb_cleanup(void)
{
	tca_fb_cleanup();  // 注销控制器
	platform_driver_unregister(&tccfb_driver);
}

module_init(tccfb_init);
module_exit(tccfb_cleanup);
其中
int tca_fb_init(void)  // 该函数主要完成LCD控制器时钟和DDI_CACHE的初始化
{
	struct lcd_panel *lcd_info;
	pmap_t pmap_fb_video;

	printk(KERN_INFO " tcc92xx %s (built %s %s)\n", __func__, __DATE__, __TIME__);

	pmap_get_info("fb_video", &pmap_fb_video);

#ifdef TCC_LCDC1_USE  // 获取控制器地址
	pLCDC = (volatile PLCDC)tcc_p2v(HwLCDC1_BASE);
#ifdef CONFIG_ARCH_TCC92XX
	pLCDC_FB_CH = (volatile PLCDC_CHANNEL)tcc_p2v(pLCDC->LI2C);
#else
	pLCDC_FB_CH = (volatile PLCDC_CHANNEL)tcc_p2v(HwLCDC1_CH_BASE(2));
#endif//
	Fb_Lcdc_num = 1;
#else
	pLCDC = (volatile PLCDC)tcc_p2v(HwLCDC0_BASE);
#ifdef CONFIG_ARCH_TCC92XX
	pLCDC_FB_CH = (volatile PLCDC_CHANNEL)tcc_p2v(HwLCDC0_CH_BASE(0));
#else
	pLCDC_FB_CH = (volatile PLCDC_CHANNEL)tcc_p2v(HwLCDC0_CH_BASE(2));
#endif//
	Fb_Lcdc_num = 0;
#endif//	TCC_LCDC1_USE

	tca92xxfb_clock_init();  // 初始化时钟
	tca92xxfb_clock_set(PWR_CMD_ON);  // 打开时钟

	tcc_ddi_cache_setting();  // 设置DDI_CACHE

	TCC_OUTPUT_LCDC_Init();  // 初始化控制器

    init_waitqueue_head(&lcdc_struct.waitq);
	lcdc_struct.state = 1;

	#ifdef CONFIG_FB_M2M_COPY
	lcd_info = tccfb_get_panel();

	if(pmap_fb_video.size == 0)
	{
		Gmap_cpu  = dma_alloc_writecombine(0, lcd_info->xres * lcd_info->yres * 4 , &Gmap_dma, GFP_KERNEL);
	}
	else
	{
		unsigned int fb_size = lcd_info->xres * lcd_info->yres * 4;
		Gmap_dma =  pmap_fb_video.base + (fb_size * 2);
		Gmap_dma = PAGE_ALIGN(Gmap_dma);
		Gmap_cpu = ioremap_nocache(Gmap_dma, fb_size);
	}
	#endif//

	printk(" %s LCDC:%d  end \n", __func__, Fb_Lcdc_num);

	return 0;
}
EXPORT_SYMBOL(tca_fb_init);

void tca_fb_cleanup(void)  // 该函数也是完成对时钟的设置
{
	dprintk(" %s LCDC:%d \n", __func__, Fb_Lcdc_num);
	tca92xxfb_clock_delete();
}
EXPORT_SYMBOL(tca_fb_cleanup);
在板级初始化程序中有对TCC_FB设备的初始化
#if defined(CONFIG_FB_TCC)
/*----------------------------------------------------------------------
 * Device     : LCD Frame Buffer resource
 * Description: 
 *----------------------------------------------------------------------*/

static u64 tcc_device_lcd_dmamask = 0xffffffffUL;

struct platform_device tcc_lcd_device = {
	.name	  = "tccxxx-lcd",
	.id		  = -1,
	.dev      = {
		.dma_mask		    = &tcc_device_lcd_dmamask,
//		.coherent_dma_mask	= 0xffffffffUL
	}
};
#endif

然后在下面的函数中注册设备

int __init m801_88_init_panel(void)
{
	int ret;
	if (!machine_is_m801_88())
		return 0;
	printk("%s LCD panel type %d\n", __func__, tcc_panel_id);

	switch (tcc_panel_id) {			
		#ifdef CONFIG_LCD_AT070TN93
		case PANEL_ID_AT070TN93:
			platform_device_register(&at070tn93_lcd);  // 注册lcd panel设备
			break;
		#endif
	
		default:
			pr_err("Not supported LCD panel type %d\n", tcc_panel_id);
			return -EINVAL;
	}

	ret = platform_device_register(&tcc_lcd_device);  // 注册fb设备
	if (ret)
		return ret;

	platform_device_register(&m801_88_backlight);  // 注册背光控制设备
	ret = platform_driver_register(&m801_88_backlight_driver);  // 注册背光控制驱动
	if (ret)
		return ret;

	return 0;
}
device_initcall(m801_88_init_panel);
1、另外讲下lcd panel的注册流程

static struct lcd_platform_data lcd_pdata = {  // panel的几个控制引脚
	.power_on	= GPIO_LCD_ON,
	.display_on	= GPIO_LCD_DISPLAY,
	.bl_on		= GPIO_LCD_BL,
	.reset		= GPIO_LCD_RESET,
};

#ifdef CONFIG_LCD_AT070TN93
static struct platform_device at070tn93_lcd = {
	.name	= "at070tn93_lcd",
	.dev	= {
		.platform_data	= &lcd_pdata,
	},
};
#endif//CONFIG_LCD_AT070TN93
static struct lcd_panel at070tn93_panel = {  // panel的参数
	.name		= "AT070TN93",
	.manufacturer	= "INNOLUX",
	.id		= PANEL_ID_AT070TN93,
	.xres		= 1024,
	.yres		= 600,
	.width		= 136,
	.height		= 3,
	.bpp		= 18,
	.clk_freq	= 500000,
	.clk_div	= 1,
	.bus_width	= 18,
	.lpw		= 320,
	.lpc		= 1024,
	.lswc		= 24,
	.lewc		= 160,
	.vdb		= 0,
	.vdf		= 10,
	.fpw1		= 3,
	.flc1		= 600,
	.fswc1		= 0,
	.fewc1		= 22,
	.fpw2		= 3,
	.flc2		= 600,
	.fswc2		= 0,
	.fewc2		= 22,
	.sync_invert	= IV_INVERT_EN | IH_INVERT_EN,
	.init		= at070tn93_panel_init,
	.set_power	= at070tn93_set_power,
	.set_backlight_level = at070tn93_set_backlight_level,
};

static int at070tn93_probe(struct platform_device *pdev)
{
	printk("%s : %d\n", __func__, 0);

	mutex_init(&panel_lock);
	lcd_pwr_state = 1;

	at070tn93_panel.dev = &pdev->dev;
	
	tccfb_register_panel(&at070tn93_panel);  // 注册panel
	return 0;
}

static int at070tn93_remove(struct platform_device *pdev)
{
	return 0;
}

static struct platform_driver at070tn93_lcd = {
	.probe	= at070tn93_probe,
	.remove	= at070tn93_remove,
	.driver	= {
		.name	= "at070tn93_lcd",
		.owner	= THIS_MODULE,
	},
};

static __init int at070tn93_init(void)
{
	return platform_driver_register(&at070tn93_lcd);  // 注册panel驱动
}

static __exit void at070tn93_exit(void)
{
	platform_driver_unregister(&at070tn93_lcd);
}

subsys_initcall(at070tn93_init);
module_exit(at070tn93_exit);
int tccfb_register_panel(struct lcd_panel *panel)
{
	dprintk(" %s  name:%s \n", __func__, panel->name);

	lcd_panel = panel;  // 赋值给全局变量
	return 1;
}
EXPORT_SYMBOL(tccfb_register_panel);

2、另外讲下背光控制设备的注册流程

static void m801_88_brightness_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	struct lcd_panel *lcd_panel = tccfb_get_panel();  // 获取全局的panel

	if (lcd_panel->set_backlight_level)
		lcd_panel->set_backlight_level(lcd_panel, value);  // 调用panel的函数实现背光控制
}

static struct led_classdev m801_88_backlight_led = {
	.name		= "lcd-backlight",
	.brightness	= DEFAULT_BACKLIGHT_BRIGHTNESS,
	.brightness_set	= m801_88_brightness_set,
};

#define M801_88_GPIO_LCD_RESET		TCC_GPC(29)
#define M801_88_GPIO_LCD_ON			TCC_GPG(2)
#define M801_88_GPIO_LCD_BL			TCC_GPA(4)
static int m801_88_backlight_probe(struct platform_device *pdev)
{
	tcc_gpio_config(M801_88_GPIO_LCD_ON, GPIO_FN(0)); 	//gpio_d 21  //初始化IO
	tcc_gpio_config(M801_88_GPIO_LCD_BL, GPIO_FN(0));	//gpio a 4
	tcc_gpio_config(M801_88_GPIO_LCD_RESET, GPIO_FN(0));	//gpio c 28

	gpio_request(M801_88_GPIO_LCD_ON,		"lcd_on");
	gpio_request(M801_88_GPIO_LCD_BL,	 	"lcd_bl");
	gpio_request(M801_88_GPIO_LCD_RESET, 	"lcd_reset");
	
	return led_classdev_register(&pdev->dev, &m801_88_backlight_led);  // 注册led_classdev
}

static int m801_88_backlight_remove(struct platform_device *pdev)
{
	led_classdev_unregister(&m801_88_backlight_led);
	return 0;
}

static struct platform_driver m801_88_backlight_driver = {  // 驱动
	.probe	= m801_88_backlight_probe,
	.remove	= m801_88_backlight_remove,
	.driver	= {
		.name	= "m801_88-backlight",
		.owner	= THIS_MODULE,
	},
};

static struct platform_device m801_88_backlight = {  // 设备
	.name = "m801_88-backlight",
};

回到主题

当fb的设备和驱动匹配在一起之后便执行tccfb_probe函数

static int __init tccfb_probe(struct platform_device *pdev)
{
	struct tccfb_info *info;
	struct fb_info *fbinfo;
	int ret;
	int plane = 0;
	unsigned int screen_width, screen_height;

	if (!lcd_panel) { // 是否已经注册panel
		pr_err("tccfb: no LCD panel data\n");
		return -EINVAL;
	}
	pr_info("LCD panel is %s %s %d x %d\n", lcd_panel->manufacturer, lcd_panel->name,
		lcd_panel->xres, lcd_panel->yres);

        screen_width      = lcd_panel->xres;  // 设置分辨率
        screen_height     = lcd_panel->yres;

#if defined(CONFIG_TCC_HDMI_UI_SIZE_1280_720)  // 是否定义HDMI开机显示UI
        if(tcc_display_data.resolution == 1)
        {
            screen_width      = 720;
            screen_height     = 576;
        }
        else if(tcc_display_data.resolution == 2)
        {
            screen_width 	  = 800;
            screen_height 	  = 480;
        }
#endif

	printk("%s, screen_width=%d, screen_height=%d\n", __func__, screen_width, screen_height);

	pmap_get_info("fb_video", &pmap_fb_video);

#if defined(CONFIG_TCC_EXCLUSIVE_UI_LAYER)
	pmap_get_info("exclusive_viqe", &pmap_eui_viqe);
#endif

	for (plane = 0; plane < CONFIG_FB_TCC_DEVS; plane++)
	{
		fbinfo = framebuffer_alloc(sizeof(struct tccfb_info), &pdev->dev);  // 申请fbi 下面开始填充结构体
		info = fbinfo->par;
		info->fb = fbinfo;
		//platform_set_drvdata(pdev, fbinfo);

		strcpy(fbinfo->fix.id, tccfb_driver_name);

		fbinfo->fix.type		= FB_TYPE_PACKED_PIXELS;
		fbinfo->fix.type_aux		= 0;
		fbinfo->fix.xpanstep		= 0;
		fbinfo->fix.ypanstep		= 1;
		fbinfo->fix.ywrapstep		= 0;
		fbinfo->fix.accel		= FB_ACCEL_NONE;

		fbinfo->var.nonstd		= 0;  // 标准像素格式
		fbinfo->var.activate		= FB_ACTIVATE_NOW;

		fbinfo->var.accel_flags		= 0;
		fbinfo->var.vmode		= FB_VMODE_NONINTERLACED;

		fbinfo->fbops			= &tccfb_ops;  // 注册操作函数
		fbinfo->flags			= FBINFO_FLAG_DEFAULT;

		fbinfo->var.xres		= screen_width;
		fbinfo->var.xres_virtual	= fbinfo->var.xres;
		fbinfo->var.yres		= screen_height;

#ifdef TCC_FB_DOUBLE
		fbinfo->var.yres_virtual	= fbinfo->var.yres * 2;
#else
		fbinfo->var.yres_virtual	= fbinfo->var.yres;
#endif//
		fbinfo->var.bits_per_pixel	= default_scn_depth[plane];  // 设置每个像素占用的位数  都是16位
		fbinfo->fix.line_length 	= fbinfo->var.xres * fbinfo->var.bits_per_pixel/8;  // 计算一行的大小

		tccfb_check_var(&fbinfo->var, fbinfo);  // 检测参数

		// the memory size that LCD should occupy
		fbinfo->fix.smem_len = fbinfo->var.xres * fbinfo->var.yres_virtual * SCREEN_DEPTH_MAX / 8;  // 计算缓冲区大小

		info->imgch = plane;  // 通道

		/* Initialize video memory */
		ret = tccfb_map_video_memory(info, plane);  // 申请缓冲区内存空间
		if (ret) {
			dprintk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
			ret = -ENOMEM;
		}

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

//		fbinfo->var.reserved[0] = 0x54445055;

		tccfb_set_par(fbinfo);  // 设置参数

		if (plane == 0)	// 判断是否顶层
			if (fb_prepare_logo(fbinfo,	FB_ROTATE_UR)) {  // 准备logo
			/* Start display and show logo on boot */
			fb_set_cmap(&fbinfo->cmap, fbinfo);  // 设置颜色表

			dprintk("fb_show_logo\n");
			fb_show_logo(fbinfo, FB_ROTATE_UR);  // 显示logo
        }
		printk(KERN_INFO "fb%d: %s frame buffer device\n",	fbinfo->node, fbinfo->fix.id);
	}

	tcc_lcd_interrupt_reg(TRUE);  // 设置中断寄存器

#ifdef CONFIG_HAS_EARLYSUSPEND  // 电源相关的函数
	info->early_suspend.suspend = tcc_fb_early_suspend;
	info->early_suspend.resume 	= tcc_fb_late_resume;
	info->early_suspend.level 	= EARLY_SUSPEND_LEVEL_STOP_DRAWING;
	register_early_suspend(&info->early_suspend);

	info->earlier_suspend.suspend = tcc_fb_earlier_suspend;
	info->earlier_suspend.resume 	= tcc_fb_later_resume;
	info->earlier_suspend.level 	= EARLY_SUSPEND_LEVEL_BLANK_SCREEN;
	register_early_suspend(&info->earlier_suspend);
#endif

	return 0;

free_video_memory:
	tccfb_unmap_video_memory(info);
	dprintk("TCC92xx fb init failed.\n");
	return ret;
}

操作函数接口如下

static struct fb_ops tccfb_ops = {
	.owner			= THIS_MODULE,
	.fb_check_var	= tccfb_check_var,
	.fb_set_par		= tccfb_set_par,
	.fb_blank		= tccfb_blank,
	.fb_setcolreg	= tccfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
	.fb_ioctl		= tccfb_ioctl,
	.fb_pan_display = tccfb_pan_display,
};

检测参数的函数如下

static int tccfb_check_var(struct fb_var_screeninfo *var,
			       struct fb_info *info)
{
	/* 判断bpp是否越界 */
	if (var->bits_per_pixel > 32)
		var->bits_per_pixel = 32;
	else if (var->bits_per_pixel < 16)
		var->bits_per_pixel = 16;

	/* 设置r/g/b的位置和偏移 */
	if (var->bits_per_pixel == 16) {
		var->red.offset 	= 11;
		var->green.offset	= 5;
		var->blue.offset	= 0;
		var->red.length 	= 5;
		var->green.length	= 6;
		var->blue.length	= 5;
		var->transp.length	= 0;
	} else if (var->bits_per_pixel == 32) {
		var->red.offset 	= 16;
		var->green.offset	= 8;
		var->blue.offset	= 0;
		var->transp.offset	= 24;
		var->red.length 	= 8;
		var->green.length	= 8;
		var->blue.length	= 8;
		var->transp.length	= 8;
	} else {
		var->red.length 	= var->bits_per_pixel;
		var->red.offset 	= 0;
		var->green.length	= var->bits_per_pixel;
		var->green.offset	= 0;
		var->blue.length	= var->bits_per_pixel;
		var->blue.offset	= 0;
		var->transp.length	= 0;
	}
	if (var->yres_virtual < var->yoffset + var->yres)  // 判断yres_virtual是否越界
		var->yres_virtual = var->yoffset + var->yres;

	return 0;
}

申请缓冲区内存空间函数如下

static int __init tccfb_map_video_memory(struct tccfb_info *fbi, int plane)
{
	fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE);  // 内存对齐

	if(FB_VIDEO_MEM_SIZE == 0)
	{

		fbi->map_cpu  = dma_alloc_writecombine(fbi->dev, fbi->map_size,
						       &fbi->map_dma, GFP_KERNEL);
		printk("map_video_memory (fbi=%p) kenel memoy, dma:0x%x map_size:%08x\n", fbi,fbi->map_dma, fbi->map_size);

	}
	else
	{
		fbi->map_dma =  FB_VIDEO_MEM_BASE;  // dma起始地址
		fbi->map_cpu = ioremap_nocache(fbi->map_dma, fbi->map_size);  // 申请内存空间,得到起始虚拟地址
		printk("map_video_memory (fbi=%p) used map memoy,map dma:0x%x size:%08x\n", fbi, fbi->map_dma ,fbi->map_size);
	}

	fbi->map_size = fbi->fb->fix.smem_len;

	if (fbi->map_cpu) {  // 申请是否成功
		/* prevent initial garbage on screen */
		dprintk("map_video_memory: clear %p:%08x\n", fbi->map_cpu, fbi->map_size);

		memset(fbi->map_cpu, 0x00, fbi->map_size);  // 初始化空间

		fbi->screen_dma		= fbi->map_dma;  // 赋值物理起始地址
		fbi->fb->screen_base	= fbi->map_cpu;  // 赋值虚拟起始地址
		fbi->fb->fix.smem_start  = fbi->screen_dma;  // 赋值物理起始地址

		// Set the LCD frame buffer start address
		switch (plane)  // 不同panel
		{
			case 2:	// IMG2
				fb_mem_vaddr[plane] = fbi->map_cpu;
				fb_mem_size [plane] = fbi->map_size;
				break;
			case 1:	// IMG1
				fb_mem_vaddr[plane] = fbi->map_cpu;
				fb_mem_size [plane] = fbi->map_size;
				break;
			case 0:	// IMG0
				fb_mem_vaddr[plane] = fbi->map_cpu;  
				fb_mem_size [plane] = fbi->map_size;
				break;
		}
		dprintk("map_video_memory: dma=%08x cpu=%p size=%08x\n",
			fbi->map_dma, fbi->map_cpu, fbi->fb->fix.smem_len);
	}

	return fbi->map_cpu ? 0 : -ENOMEM;
}
设置参数函数如下
static int tccfb_set_par(struct fb_info *info)
{
	struct tccfb_info *fbi = info->par;
	struct fb_var_screeninfo *var = &info->var;

	sprintk("- tccfb_set_par pwr:%d  output:%d \n",fb_power_state, Output_SelectMode);

	if (var->bits_per_pixel == 16)  // 设置显示器色彩模式
		fbi->fb->fix.visual = FB_VISUAL_TRUECOLOR;
	else if (var->bits_per_pixel == 32)
		fbi->fb->fix.visual = FB_VISUAL_TRUECOLOR;
	else
		fbi->fb->fix.visual = FB_VISUAL_PSEUDOCOLOR;

	fbi->fb->fix.line_length = (var->xres*var->bits_per_pixel)/8;

	#ifndef CONFIG_TCC_OUTPUT_STARTER
	/* activate this new configuration */
   	if( fb_power_state && Output_SelectMode != TCC_OUTPUT_COMPONENT )
		tccfb_activate_var(fbi, var);
	#endif

	return 0;
}
static void tccfb_activate_var(struct tccfb_info *fbi,  struct fb_var_screeninfo *var)
{
	unsigned int imgch = 0;

	sprintk("%s node:0x%x TCC_DEVS:%d \n", __func__, fbi->fb->node, CONFIG_FB_TCC_DEVS);

	if((0 <= fbi->fb->node) && (fbi->fb->node < CONFIG_FB_TCC_DEVS))
		imgch = fbi->fb->node;
	else
		return;

	tca_fb_activate_var(fbi, var);
}
void tca_fb_activate_var(struct tccfb_info *fbi,  struct fb_var_screeninfo *var)
{
	unsigned int imgch, bpp_value,tmp_value, base_addr;
	unsigned int regl, lcd_width, lcd_height, img_width, img_height;

	#define IMG_AOPT          	2
	
	unsigned int alpha_type = 0, alpha_blending_en = 0;
	unsigned int chromaR, chromaG, chromaB, chroma_en;

	imgch = fbi->fb->node;  // 通道
	if(fbi->fb->var.bits_per_pixel == 32)
	{
		chroma_en = 0;
		alpha_type = 1;
		alpha_blending_en = 1;
		bpp_value = 0x000C; //RGB888
	}
	else if(fbi->fb->var.bits_per_pixel == 16)  // 判断像素点深度并设置相关属性
	{
		chroma_en = 1;
		alpha_type = 0;
		alpha_blending_en = 0;
		bpp_value = 0x000A; //RGB565
	}
	else	{
		printk("%s:fb%d Not Supported BPP!\n", __FUNCTION__, fbi->fb->node);
		return;
	}

	chromaR = chromaG = chromaB = 0;

	sprintk("%s: fb%d Supported BPP!\n", __FUNCTION__, fbi->fb->node);
    // 计算缓冲区空间末尾地址
 	base_addr = fbi->map_dma + fbi->fb->var.xres * var->yoffset * (fbi->fb->var.bits_per_pixel/8);
	if(fbi->fb->var.yoffset > fbi->fb->var.yres)	{
		base_addr = PAGE_ALIGN(base_addr);
	}

	sprintk("%s: fb%d Baddr:0x%x Naddr:0x%x!\n", __FUNCTION__, fbi->fb->node, base_addr, pLCDC_FB_CH->LIBA0);

	regl = pLCDC->LDS; // get LCD size 获取寄存器值

	lcd_width = (regl & 0xFFFF);
	lcd_height = ((regl>>16) & 0xFFFF);
	img_width = fbi->fb->var.xres;
	img_height = fbi->fb->var.yres;

	if(img_width > lcd_width)	
		img_width = lcd_width;
	
	if(img_height > lcd_height)	
		img_height = lcd_height;
	/* 写新的寄存器值 */
	switch(imgch)
	{
		case 0:
			// default framebuffer 
			tmp_value = pLCDC_FB_CH->LIC;
			pLCDC_FB_CH->LIC = (tmp_value & 0xFFFFFFE0) | (bpp_value & 0x0000001F);
			pLCDC_FB_CH->LIO = fbi->fb->var.xres * (fbi->fb->var.bits_per_pixel/8);
			pLCDC_FB_CH->LIS	= (img_height<< 16) | (img_width); //Size
			pLCDC_FB_CH->LIP	= 0; // position
			#ifdef CONFIG_FB_M2M_COPY
			if(!tccxxx_overlay_use()) {
				pLCDC_FB_CH->LIBA0	= Gmap_dma;
			}
			else
			#endif//CONFIG_FB_M2M_COPY
			{
				pLCDC_FB_CH->LIBA0	= base_addr;				
			}

			#ifdef CONFIG_ARCH_TCC92XX
			pLCDC_FB_CH->LIC |= (HwLIC_IEN);    //  image enable
			#else
			pLCDC_FB_CH->LIC |= (HwLIC_IEN | HwLCT_RU);    //  image enable
			#endif//	CONFIG_ARCH_TCC92XX

			// overlay 加己
			BITCSET (pLCDC->LI2C,	0x1<< 30,  (alpha_blending_en)	<< 30); // alpha enable
			BITCSET (pLCDC->LI2C, 0x3<< 25,	(IMG_AOPT)	<< 25); // alpha opt
			BITCSET (pLCDC->LI2C, 0x1<< 24,	(alpha_type)  << 24); // alpha select  

			BITCSET (pLCDC->LI2C, 0x1<< 29,	(chroma_en)  << 29); // chroma-keying
			
			BITCSET (pLCDC->LI2KR, 0xff <<  0, (chromaR)  <<  0); // key
			BITCSET (pLCDC->LI2KR, 0xff << 16, (0xF8)	<< 16); // keymask
			BITCSET (pLCDC->LI2KG, 0xff <<  0, (chromaG)  <<  0); // key
			BITCSET (pLCDC->LI2KG, 0xff << 16, (0xFC)	<< 16); // keymask
			BITCSET (pLCDC->LI2KB, 0xff <<  0, (chromaB)  <<  0); // key
			BITCSET (pLCDC->LI2KB, 0xff << 16, (0xF8)	<< 16); // keymask

			tmp_value = pLCDC->LI2C;
			pLCDC->LI2C = (tmp_value & 0xFFFFFFE0) | (bpp_value & 0x0000001F);
			pLCDC->LI2O = fbi->fb->var.xres * (fbi->fb->var.bits_per_pixel/8);

			#ifdef CONFIG_FB_TCC_USE_VSYNC_INTERRUPT
			tca_fb_vsync_activate(var, fbi);  // 设置场同步中断
			#else
			msleep(16);
			#endif
			
			break;

		case 1:
			tmp_value = pLCDC->LI1C;
			pLCDC->LI1C = (tmp_value & 0xFFFFFFE0) | (bpp_value & 0x0000001F);
			pLCDC->LI1O = fbi->fb->var.xres * (fbi->fb->var.bits_per_pixel/8);
			pLCDC->LI1S	= (img_height<< 16) | (img_width); //Size
			pLCDC->LI1BA0 = base_addr;
			#ifdef CONFIG_ARCH_TCC92XX
			pLCDC->LI1C |= (HwLIC_IEN);
			#else
			pLCDC->LI1C |= (HwLIC_IEN | HwLCT_RU);
			#endif//
			break;

		case 2:
			tmp_value = pLCDC->LI0C;
			pLCDC->LI0C = (tmp_value & 0xFFFFFFE0) | (bpp_value & 0x0000001F);
			pLCDC->LI0O = fbi->fb->var.xres * (fbi->fb->var.bits_per_pixel/8);
			pLCDC->LI0S	= (img_height<< 16) | (img_width); //Size
			pLCDC->LI0BA0 = base_addr;
			#ifdef CONFIG_ARCH_TCC92XX
			pLCDC->LI0C |= (HwLIC_IEN);
			#else
			pLCDC->LI0C |= (HwLIC_IEN | HwLCT_RU);
			#endif//			
			break;
	}

	return;
	
}
EXPORT_SYMBOL(tca_fb_activate_var);

3、另外讲下开机logo的显示过程

int fb_prepare_logo(struct fb_info *info, int rotate)
{
	int depth = fb_get_color_depth(&info->var, &info->fix);  // 获取颜色深度
	unsigned int yres;

	memset(&fb_logo, 0, sizeof(struct logo_data));

	if (info->flags & FBINFO_MISC_TILEBLITTING ||
	    info->flags & FBINFO_MODULE)
		return 0;

	if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {  // 判断色彩模式  我们是真彩色的
		depth = info->var.blue.length;
		if (info->var.red.length < depth)
			depth = info->var.red.length;
		if (info->var.green.length < depth)
			depth = info->var.green.length;
	}

	if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) {
		/* assume console colormap */
		depth = 4;
	}

	/* Return if no suitable logo was found */
	fb_logo.logo = fb_find_logo(depth);  // 获取logo

	if (!fb_logo.logo) {
		return 0;
	}

	if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD)  // 判断是否旋转
		yres = info->var.yres;
	else
		yres = info->var.xres;

	if (fb_logo.logo->height > yres) {
		fb_logo.logo = NULL;
		return 0;
	}

	/* 判断颜色模式 */
	if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
		fb_logo.depth = 8;
	else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
		fb_logo.depth = 4;
	else
		fb_logo.depth = 1;


 	if (fb_logo.depth > 4 && depth > 4) {
 		switch (info->fix.visual) {
 		case FB_VISUAL_TRUECOLOR:
 			fb_logo.needs_truepalette = 1;  // 真彩色
 			break;
 		case FB_VISUAL_DIRECTCOLOR:
 			fb_logo.needs_directpalette = 1;
 			fb_logo.needs_cmapreset = 1;
 			break;
 		case FB_VISUAL_PSEUDOCOLOR:
 			fb_logo.needs_cmapreset = 1;
 			break;
 		}
 	}

	return fb_prepare_extra_logos(info, fb_logo.logo->height, yres);  // 获取logo高度
}
获取logo的函数
const struct linux_logo * __init_refok fb_find_logo(int depth)
{
	const struct linux_logo *logo = NULL;

	if (nologo)
		return NULL;
    ...	
	if (depth >= 8) {
#ifdef CONFIG_LOGO_LINUX_CLUT224
		/* Generic Linux logo */
		logo = &logo_linux_clut224;
#endif
	}
	return logo;
}
EXPORT_SYMBOL_GPL(fb_find_logo);
const struct linux_logo logo_linux_clut224 __initconst = {
	.type		= LINUX_LOGO_CLUT224,
	.width		= 1024,
	.height		= 768,
	.clutsize	= 221,
	.clut		= logo_linux_clut224_clut,
	.data		= logo_linux_clut224_data
};
设置颜色表
int fb_set_cmap(struct fb_cmap *cmap, struct fb_info *info)
{
	int i, start, rc = 0;
	u16 *red, *green, *blue, *transp;
	u_int hred, hgreen, hblue, htransp = 0xffff;

	red = cmap->red;
	green = cmap->green;
	blue = cmap->blue;
	transp = cmap->transp;
	start = cmap->start;

	if (start < 0 || (!info->fbops->fb_setcolreg &&
			  !info->fbops->fb_setcmap))
		return -EINVAL;
	if (info->fbops->fb_setcmap) {
		rc = info->fbops->fb_setcmap(cmap, info);
	} else {
		for (i = 0; i < cmap->len; i++) {
			hred = *red++;
			hgreen = *green++;
			hblue = *blue++;
			if (transp)
				htransp = *transp++;
			if (info->fbops->fb_setcolreg(start++,
						      hred, hgreen, hblue,
						      htransp, info))
				break;
		}
	}
	if (rc == 0)
		fb_copy_cmap(cmap, &info->cmap);

	return rc;
}
显示logo
int fb_show_logo(struct fb_info *info, int rotate)
{
	int y;

	y = fb_show_logo_line(info, rotate, fb_logo.logo, 0,  // 显示logo
			      num_online_cpus());
	y = fb_show_extra_logos(info, y, rotate);

	return y;
}
static int fb_show_logo_line(struct fb_info *info, int rotate,
			     const struct linux_logo *logo, int y,
			     unsigned int n)
{
	u32 *palette = NULL, *saved_pseudo_palette = NULL;
	unsigned char *logo_new = NULL, *logo_rotate = NULL;
	struct fb_image image;

	/* Return if the frame buffer is not mapped or suspended */
	if (logo == NULL || info->state != FBINFO_STATE_RUNNING ||
	    info->flags & FBINFO_MODULE)
		return 0;

	image.depth = 8;  // 设置深度  224色
	image.data = logo->data;  // 数据

	if (fb_logo.needs_cmapreset)
		fb_set_logocmap(info, logo);

	if (fb_logo.needs_truepalette ||
	    fb_logo.needs_directpalette) {
		palette = kmalloc(256 * 4, GFP_KERNEL);
		if (palette == NULL)
			return 0;

		if (fb_logo.needs_truepalette)
			fb_set_logo_truepalette(info, logo, palette);  // 设置调色板
		else
			fb_set_logo_directpalette(info, logo, palette);

		saved_pseudo_palette = info->pseudo_palette;
		info->pseudo_palette = palette;
	}

	if (fb_logo.depth <= 4) {
		logo_new = kmalloc(logo->width * logo->height, GFP_KERNEL);
		if (logo_new == NULL) {
			kfree(palette);
			if (saved_pseudo_palette)
				info->pseudo_palette = saved_pseudo_palette;
			return 0;
		}
		image.data = logo_new;
		fb_set_logo(info, logo, logo_new, fb_logo.depth);
	}

	image.dx = 0;  // 显示logo的起始坐标
	image.dy = y;
	image.width = logo->width;  // logo的宽度和高度
	image.height = logo->height;

	if (rotate) {
		logo_rotate = kmalloc(logo->width *
				      logo->height, GFP_KERNEL);
		if (logo_rotate)
			fb_rotate_logo(info, logo_rotate, &image, rotate);
	}

	fb_do_show_logo(info, &image, rotate, n);

	kfree(palette);
	if (saved_pseudo_palette != NULL)
		info->pseudo_palette = saved_pseudo_palette;
	kfree(logo_new);
	kfree(logo_rotate);
	return logo->height;
}
开始显示
static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
			    int rotate, unsigned int num)
{
	unsigned int x;

	if (rotate == FB_ROTATE_UR) {
		for (x = 0;
		     x < num && image->dx + image->width <= info->var.xres;
		     x++) {
			info->fbops->fb_imageblit(info, image);  // 调用图像绘制函数显示logo
			image->dx += image->width + 8;
		}
	} else if (rotate == FB_ROTATE_UD) {
		for (x = 0; x < num && image->dx >= 0; x++) {
			info->fbops->fb_imageblit(info, image);
			image->dx -= image->width + 8;
		}
	} else if (rotate == FB_ROTATE_CW) {
		for (x = 0;
		     x < num && image->dy + image->height <= info->var.yres;
		     x++) {
			info->fbops->fb_imageblit(info, image);
			image->dy += image->height + 8;
		}
	} else if (rotate == FB_ROTATE_CCW) {
		for (x = 0; x < num && image->dy >= 0; x++) {
			info->fbops->fb_imageblit(info, image);
			image->dy -= image->height + 8;
		}
	}
}
图像绘制函数
void cfb_imageblit(struct fb_info *p, const struct fb_image *image)
{
	u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0;
	u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel;
	u32 width = image->width;
	u32 dx = image->dx, dy = image->dy;
	u8 __iomem *dst1;

	if (p->state != FBINFO_STATE_RUNNING)
		return;

	bitstart = (dy * p->fix.line_length * 8) + (dx * bpp);  // 计算起始点
	start_index = bitstart & (32 - 1);
	pitch_index = (p->fix.line_length & (bpl - 1)) * 8;

	bitstart /= 8;
	bitstart &= ~(bpl - 1);
	dst1 = p->screen_base + bitstart;  // 计算起始点的虚拟地址

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

	if (image->depth == 1) {
		if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
		    p->fix.visual == FB_VISUAL_DIRECTCOLOR) {
			fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color];
			bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color];
		} else {
			fgcolor = image->fg_color;
			bgcolor = image->bg_color;
		}	
		
		if (32 % bpp == 0 && !start_index && !pitch_index && 
		    ((width & (32/bpp-1)) == 0) &&
		    bpp >= 8 && bpp <= 32) 			
			fast_imageblit(image, p, dst1, fgcolor, bgcolor);
		else 
			slow_imageblit(image, p, dst1, fgcolor, bgcolor,
					start_index, pitch_index);
	} else
		color_imageblit(image, p, dst1, start_index, pitch_index);
}
static inline void color_imageblit(const struct fb_image *image, 
				   struct fb_info *p, u8 __iomem *dst1, 
				   u32 start_index,
				   u32 pitch_index)
{
	/* Draw the penguin */
	u32 __iomem *dst, *dst2;
	u32 color = 0, val, shift;
	int i, n, bpp = p->var.bits_per_pixel;
	u32 null_bits = 32 - bpp;
	u32 *palette = (u32 *) p->pseudo_palette;
	const u8 *src = image->data;
	u32 bswapmask = fb_compute_bswapmask(p);

	dst2 = (u32 __iomem *) dst1;  // 起始虚拟地址
	for (i = image->height; i--; ) {  // 行数
		n = image->width;  // 列数
		dst = (u32 __iomem *) dst1;  // 当前虚拟地址
		shift = 0;
		val = 0;
		
		if (start_index) {
			u32 start_mask = ~fb_shifted_pixels_mask_u32(p,
						start_index, bswapmask);
			val = FB_READL(dst) & start_mask;
			shift = start_index;
		}
		while (n--) {
			if (p->fix.visual == FB_VISUAL_TRUECOLOR ||  // 真彩色
			    p->fix.visual == FB_VISUAL_DIRECTCOLOR )
				color = palette[*src];
			else
				color = *src;
			color <<= FB_LEFT_POS(p, bpp);
			val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask);  // 合成像素
			if (shift >= null_bits) {
				FB_WRITEL(val, dst++);  // 写到buffer就从LCD上显示出来了
	
				val = (shift == null_bits) ? 0 : 
					FB_SHIFT_LOW(p, color, 32 - shift);
			}
			shift += bpp;
			shift &= (32 - 1);
			src++;
		}
		if (shift) {
			u32 end_mask = fb_shifted_pixels_mask_u32(p, shift,
						bswapmask);

			FB_WRITEL((FB_READL(dst) & end_mask) | val, dst);
		}
		dst1 += p->fix.line_length;  // 换行
		if (pitch_index) {
			dst2 += p->fix.line_length;
			dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1));

			start_index += pitch_index;
			start_index &= 32 - 1;
		}
	}
}
回到主题

下面我们分析最复杂的函数

...

你可能感兴趣的:(struct,image,video,buffer,input,output)