linux lcd设备驱动剖析四

linux lcd设备驱动剖析二文章中,我们详细分析了s3c24xxfb_probe函数。

文章链接:http://blog.csdn.net/lwj103862095/article/details/18189765

s3c2410fb.c中s3c24xxfb_probe是非常重要的函数之一,但仅仅分析probe函数,貌似感觉有点不够过瘾,貌似缺少分析了一个非常重要的成员。在probe函数中有一句:fbinfo->fbops   = &s3c2410fb_ops;

static struct fb_ops s3c2410fb_ops = {
	.owner			= THIS_MODULE,
	.fb_check_var	= s3c2410fb_check_var,		//设置可变参数
	.fb_set_par		= s3c2410fb_set_par,		//设置固定参数及lcdcon寄存器
	.fb_blank		= s3c2410fb_blank,			//设置是否使能LCD控制器
	.fb_setcolreg	= s3c2410fb_setcolreg,		//设置RGB颜色,实现伪颜色表
	.fb_fillrect	= cfb_fillrect,				//画一个矩形
	.fb_copyarea	= cfb_copyarea,				//Copy data from area to another
	.fb_imageblit	= cfb_imageblit,			//Draws a image to the display
};	

一、s3c2410fb_check_var函数主要根据tq2440_lcd_cfg实例来设置fb_info结构体的可变参数

fb_var_screeninfo结构体各个成员,如xres、yres、bits_per_pixel、height、width 等等,具体分析如下:

/* 此函数的主要功能是设置可变参数var  */	
static int s3c2410fb_check_var(struct fb_var_screeninfo *var,
			       struct fb_info *info)
{	
	struct s3c2410fb_info *fbi = info->par;

	/* platform_data就是tq2440_fb_info结构体实例 */
	struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
	struct s3c2410fb_display *display = NULL;

	/* 在tq2440_fb_info实例里,displays = tq2440_lcd_cfg,default_display = 0 */
	struct s3c2410fb_display *default_display = mach_info->displays +
						    mach_info->default_display;

	/* 在tq2440_fb_info实例里,type = S3C2410_LCDCON1_TFT */
	int type = default_display->type;
	unsigned i;

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

	/* validate x/y resolution */
	/* choose default mode if possible */

	/* 如果参数都等于tq2440_fb_info实例里的参数
	 * 那么赋值给display,此时display指向tq2440_fb_info实例
	 */
	if (var->yres == default_display->yres &&
	    var->xres == default_display->xres &&
	    var->bits_per_pixel == default_display->bpp)
		display = default_display;	

	/* 否则从tq2440_fb_info结构体实例中循环匹配,num_displays = 1 */
	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;
			}

	/* 如果匹配不成功,display = NULL 则错误 */
	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 */
	/* 找到匹配的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;
	......
	
	/* TQ2440的LCD就是采用这种模式 */
	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;
}
tq2440_lcd_cfg实例在arch/arm/mach-s3c2440/mach-tq2440.c中定义

/* LCD driver info */
/* tq2440_lcd_cfg在tq2440_fb_info中被设置 */
static struct s3c2410fb_display tq2440_lcd_cfg __initdata = {
	.lcdcon5	= S3C2410_LCDCON5_FRM565 |
			  S3C2410_LCDCON5_INVVLINE |
			  S3C2410_LCDCON5_INVVFRAME |
			  S3C2410_LCDCON5_PWREN |
			  S3C2410_LCDCON5_HWSWP,
	.type		= S3C2410_LCDCON1_TFT,
	
	......
	
/* TQ(LCD W4.3)的配置,config_EmbedSky_W43:CONFIG_FB_S3C24X0_TFT480272=y */
#elif defined(CONFIG_FB_S3C24X0_TFT480272)  
	.width		  = 480,
	.height		  = 272,

	.pixclock	  = 40000, /* HCLK 100 MHz, divisor 1 */

	/* VCLK=HCLK/[(CLKVAL+1)x2],HCLK = 100MHz
	 * 根据LCD手册"WXCAT43-TG6#001_V1.0.pdf"的第22页可得,VCLK = 10MHz
	 * 即10 = 100/[(CLKVAL+1)x2],可得CLKVAL = 4
     */
	.setclkval	  = 0x4,   
	.xres		  = 480,
	.yres		  = 272,
	.bpp		  = 16,

	/* 为什么是这样?参考linux/Documentation/fb/framebuffer.txt */
	.left_margin  = 19,	  /* 左边沿  : for HFPD*/	
	.right_margin = 10,	  /* 右边沿  : for HBPD*/
	.hsync_len	  = 30,	  /* 水平同步: for HSPW*/
	.upper_margin = 4,	  /* 上边沿  : for VFPD*/
	.lower_margin = 2,	  /* 下边沿  : for VBPD*/
	.vsync_len	  = 8,	  /* 垂直同步: for VSPW*/
	
	......
};

二、s3c2410fb_set_par函数先根据var->bits_per_pixel来选择fix.visual,这里bits_per_pixel = 32,

故fix.visual = FB_VISUAL_TRUECOLOR,然后计算一行的字节数,最后调用s3c2410fb_activate_var

函数来激活LCD控制器,即设置各个lcdcon寄存器。

static int s3c2410fb_set_par(struct fb_info *info)
{
	/* 获得刚被s3c2410fb_check_var函数设置过的var */
	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;
	}

	/* 一行的字节数 = x*bpp/8 = 480*32/8 = 480*4 */
	info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;

	/* activate this new configuration */
	s3c2410fb_activate_var(info);
	
	return 0;
}
s3c2410fb_activate_var函数先调用s3c2410fb_calc_pixclk函数来计算LCD时钟频率,然后调用s3c2410fb_calculate_tft_lcd_regs函数来设置lcdcon1~lcdcon5,然后调用writel函数将前面s3c2410fb_calculate_tft_lcd_regs函数设置好的lcdconx写入对应寄存器,接着调用s3c2410fb_set_lcdaddr函数来设置LCDSADDR1、LCDSADDR2、LCDSADDR3寄存器,也就是将之前在probe函数通过s3c2410fb_map_video_memory-->dma_alloc_writecombine函数分配好的“显存”告诉LCD控制器,最后使能LCD控制器。
static void s3c2410fb_activate_var(struct fb_info *info)
{
	/* 在framebuffer_alloc函数里info->par指向了额外多申请
	 * 内存空间的首地址,即info->par指向s3c2410fb_info结构体
	 */
	struct s3c2410fb_info *fbi = info->par;
	void __iomem *regs = fbi->io;		/* IO基地址 */

	/* 设置显示模式为: TFT LCD panel */
	int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;

	/* 通过probe函数后platform_data指向tq2440_fb_info结构体实例 */
	struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
	struct s3c2410fb_display *default_display = mach_info->displays +
						    mach_info->default_display;
	struct fb_var_screeninfo *var = &info->var;


	/* 计算LCD时钟频率, 在mach_tq2440.c里 pixclock = 40000 */
	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);
		--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);
	fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(default_display->setclkval);

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

	/* 将前面s3c2410fb_calculate_tft_lcd_regs函数设置好的lcdconx写入对应寄存器 */
	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);
}
s3c2410fb_calculate_tft_lcd_regs函数比较简单这里就不分析了,这里只分析s3c2410fb_set_lcdaddr函数

static void s3c2410fb_set_lcdaddr(struct fb_info *info)
{
	unsigned long saddr1, saddr2, saddr3;
	struct s3c2410fb_info *fbi = info->par;
	void __iomem *regs = fbi->io;

	/* LCDSADDR1 = 帧缓冲区起始地址再右移1位 */
	saddr1  = info->fix.smem_start >> 1;

	/* LCDSADDR2 = 帧缓冲区结束地址再右移1位 */
	saddr2  = info->fix.smem_start;
	saddr2 += info->fix.line_length * info->var.yres;  /* 帧缓冲区大小 */
	saddr2 >>= 1;

	/* LCDSADDR3 = 一行的长度,单位为2字节 */
	saddr3 = S3C2410_OFFSIZE(0) |
		 S3C2410_PAGEWIDTH((info->fix.line_length / 2) & 0x3ff);

	dprintk("LCDSADDR1 = 0x%08lx\n", saddr1);
	dprintk("LCDSADDR2 = 0x%08lx\n", saddr2);
	dprintk("LCDSADDR3 = 0x%08lx\n", saddr3);

	writel(saddr1, regs + S3C2410_LCDSADDR1);
	writel(saddr2, regs + S3C2410_LCDSADDR2);
	writel(saddr3, regs + S3C2410_LCDSADDR3);
}
三、s3c2410fb_setcolreg函数主要通过red,green,blue三原色构造出val,然后再将val写入pseudo_palette假调色板中。
static int s3c2410fb_setcolreg(unsigned regno,
			       unsigned red, unsigned green, unsigned blue,
			       unsigned transp, struct fb_info *info)
{
	struct s3c2410fb_info *fbi = info->par;
	void __iomem *regs = fbi->io;
	unsigned int val;

	/* TQ2440的LCD是FB_VISUAL_TRUECOLOR,即TFT */
	switch (info->fix.visual) {
	case FB_VISUAL_TRUECOLOR:
		/* true-colour, use pseudo-palette */
		if (regno < 16) {
			u32 *pal = info->pseudo_palette;

			/* 用red,green,blue三原色构造出val */
			val  = chan_to_field(red,   &info->var.red);
			val |= chan_to_field(green, &info->var.green);
			val |= chan_to_field(blue,  &info->var.blue);

			pal[regno] = val;
		}
		break;
	......

	default:
		return 1;	/* unknown type */
	}
	return 0;
}
chan_to_field 函数如下,将具体的RGB数据代入就比较容易理解这个函数了,相应的var.red、var.green、var.blue在s3c2410fb_check_var函数的最后面有设置。

static inline unsigned int chan_to_field(unsigned int chan,
					 struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

s3c2410fb_check_var函数设置rgb的部分代码,这里省略了大部分源码,为的是方便参考。

static int s3c2410fb_check_var(struct fb_var_screeninfo *var,
			       struct fb_info *info)
{	
	.......
	/* set r/g/b positions */
	switch (var->bits_per_pixel) {
	.......
	/* TQ2440的LCD就是采用这种模式 */
	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;
}
而cfb_fillrect、cfb_copyarea、cfb_imageblit是通用的函数,不用驱动工程师去理会,只需要在加载lcd驱动时,将其对应的模块加载,而要加载模块,必须在编译内核后,再执行make modules,这样就可以得到相应的cfb*.ko了。
到这里s3c2410fb.c内核自带的lcd驱动基本剖析完毕,这里总结一下难点:

一、内核自带的lcd驱动是以平台驱动设备模型来编写的,难点不在框架,框架其实很简单,

1、分配一个fb_info结构体;

2、设置 fb_info结构体;

3、注册;

4、硬件相关的设置

二、好啦,难点就是如何设置fb_info结构体,而fb_info结构体成员那么多,是不每个成员都要一一设置呢?当然不是,

主要设置fb_info结构体的固定参数 fb_fix_screeninfo结构体和可变参数fb_var_screeninfo结构体,还有就是硬件相关

的设置,比如lcd时序参数的设置,也就是要设置lcdcon1~lcdcon5,lcdaddr1~lcdaddr3各个寄存器。

linux lcd设备驱动剖析四

上一篇:POJ 1679 次小生成树裸题


下一篇:《TCP/IP详解》(卷1)读书笔记:01章:概述