LCD(六)显示控制器、framebuffer驱动、s3c-fb.c中probe函数分析

          三星SOC芯片上集成了显示控制器(有称作fimd的,Fully Interactive Mobile Display)。它的驱动分为mainline版本的和legacy版本的,mainline版本的是通用的,由Ben Dooks <[email protected]> 管理着的,代码比较规范。下面我们来分析一下这个驱动,这个驱动在driver/video/里,文件名是s3c-fb.c 与其相关的,主要还有arch/arm/plat-s5p/dev-fimd0.carch/arm/mach-XXXX/setup-fimd0.c 。 前者是定义platform device的,后者是定义一些比较depend on 具体芯片的东西,例如GPIO。

        我们这里主要分析s3c-fb.c中的probe函数, probe函数里最重要的是s3c_fb_probe_win函数,一会也一并分析。

       首先解释一个概念,很多书中有framebuffer这个概念,但是在三星的显示控制器文档或代码中,常出现win或window的概念,显示控制器可以控制0~5个windows,代码中分给它们分别编号win0, win1,win2......这里一张win或window就对应一个framebuffer, 每个framebuffer有自己的一个FBI(fb_info)结构。

        代码中, 显示控制器是s3c_fb结构体, window是s3c_fb_win结构体。

       代码中有两种data,一种是platform data(在板文件中定义),另一种是driver data(在驱动文件中定义),在它们各自的结构体里面,又可以分为两部份,一是用于sfb的data, 另一是用于win的data。

       framebuffer是fb_info结构体,里面主要存储设置参数的数据结构有两个,fb_var_screeninfo和fb_fix_screeninfo结构体。

 ************************************************ platform data***************************************************

static struct s3c_fb_platdata smdkv210_lcd0_pdata __initdata = {
	.win[0]		= &smdkv210_fb_win0,	//用于win的部分,称为“platform data”中的win部分
	.vtiming	= &smdkv210_lcd_timing,	//时序部分
	.vidcon0	= VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB,	//sfb部分
	.vidcon1	= VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC,
	.setup_gpio	= s5pv210_fb_gpio_setup_24bpp,
};

 ************************************************ driver data***************************************************

static struct s3c_fb_driverdata s3c_fb_data_s5pv210 = {
	.variant = {					//用于sfb的driver data
		.nr_windows	= 5,
		.vidtcon	= VIDTCON0,
		.wincon		= WINCON(0),
		.winmap		= WINxMAP(0),
		.keycon		= WKEYCON,
		.osd		= VIDOSD_BASE,
		.osd_stride	= 16,
		.buf_start	= VIDW_BUF_START(0),
		.buf_size	= VIDW_BUF_SIZE(0),
		.buf_end	= VIDW_BUF_END(0),

		.palette = {
			[0] = 0x2400,
			[1] = 0x2800,
			[2] = 0x2c00,
			[3] = 0x3000,
			[4] = 0x3400,
		},

		.has_shadowcon	= 1,
		.has_blendcon	= 1,
		.has_clksel	= 1,
		.has_fixvclk	= 1,
	},
	.win[0]	= &s3c_fb_data_s5p_wins[0],	//用于各个win的部分,称为“driver data”中win的部分
	.win[1]	= &s3c_fb_data_s5p_wins[1],
	.win[2]	= &s3c_fb_data_s5p_wins[2],
	.win[3]	= &s3c_fb_data_s5p_wins[3],
	.win[4]	= &s3c_fb_data_s5p_wins[4],
};

************************************************ s3c_fb_probe() ***************************************************

static int s3c_fb_probe(struct platform_device *pdev)
{
	const struct platform_device_id *platid; 
	/*因为一个驱动要适合很多版本的设备,每个版本的设备的设置 参数都不一样,所以要用到platid来
	选择哪个版本的设备,像“s5pv210-fb”,"s3c2443-fb"..., 这些就是platid(也在s3c-fb.c中定义了),
	也表明了,这个驱动能适合于这些设备。*/
	struct s3c_fb_driverdata *fbdrv;	//driver data
	struct device *dev = &pdev->dev;	
	struct s3c_fb_platdata *pd;			//platform data
	struct s3c_fb *sfb;		//一个重要的数据结构,它代表了一个显示控制器,显示控制器的所有东西都
							//放在这里了,但是这里将其作为一个局部变量了
	struct resource *res;	//资源
	int win;
	int ret = 0;
	u32 reg;

	platid = platform_get_device_id(pdev);	//从platform device里的id_entry变量中获取platid,有一个宏实现
	fbdrv = (struct s3c_fb_driverdata *)platid->driver_data; //获取
				//platid对应的driver data,driver data在s3c-fb.c中定义,主要是一定设置参数

	if (fbdrv->variant.nr_windows > S3C_FB_MAX_WIN) {
		dev_err(dev, "too many windows, cannot attach\n");
		return -EINVAL;
	}

	pd = pdev->dev.platform_data;	//获取platform data,它在板级文件中定义,
				        //这个data里 包含了显示控制器的数据,也包含了win的数据
	if (!pd) {
		dev_err(dev, "no platform data specified\n");
		return -EINVAL;
	}

	sfb = devm_kzalloc(dev, sizeof(struct s3c_fb), GFP_KERNEL);		//分配内存空间
	if (!sfb) {
		dev_err(dev, "no memory for framebuffers\n");
		return -ENOMEM;
	}

	dev_dbg(dev, "allocate new framebuffer %p\n", sfb);
	printk(KERN_ERR "Here I am: %s:%i\n", __FILE__, __LINE__);
	
	sfb->dev = dev;		//向sfb填入显示控制器的device结构体
	sfb->pdata = pd;	//向sfb填入显示控制器的platform data结构体
	sfb->variant = fbdrv->variant;	//driver data结构体里有variant成员,
					//具体variant可以看下面s3c_fb_variant结构

	spin_lock_init(&sfb->slock);

	sfb->bus_clk = devm_clk_get(dev, "lcd");	//通过"lcd"这个名字,去clock文件中找到自己的bus clock
	if (IS_ERR(sfb->bus_clk)) {
		dev_err(dev, "failed to get bus clock\n");
		return PTR_ERR(sfb->bus_clk);
	}

	clk_prepare_enable(sfb->bus_clk);	//bus_clk具体用途待定

	if (!sfb->variant.has_clksel) {
		sfb->lcd_clk = devm_clk_get(dev, "sclk_fimd"); //如果driver data里没定义 源时钟,
				//就用"sclk_fimd",此名字去clock文件中找到自己的源时钟
		if (IS_ERR(sfb->lcd_clk)) {
			dev_err(dev, "failed to get lcd clock\n");
			ret = PTR_ERR(sfb->lcd_clk);
			goto err_bus_clk;
		}

		clk_prepare_enable(sfb->lcd_clk);
	}

	pm_runtime_enable(sfb->dev);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);	//获取资源的物理起始地址、
		//终地址、大小、类型等,放在res结构中,实际上是寄存器门的物理起始地址
	sfb->regs = devm_ioremap_resource(dev, res);	//内存映射,将寄存器的访问地址映射到
	//刚才分配的内存上,sfb->regs为起始地址
	if (IS_ERR(sfb->regs)) {
		ret = PTR_ERR(sfb->regs);
		goto err_lcd_clk;
	}

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);	//获取设备中断资源
	if (!res) {
		dev_err(dev, "failed to acquire irq resource\n");
		ret = -ENOENT;
		goto err_lcd_clk;
	}
	sfb->irq_no = res->start;
	ret = devm_request_irq(dev, sfb->irq_no, s3c_fb_irq,
			  0, "s3c_fb", sfb);
	if (ret) {
		dev_err(dev, "irq request failed\n");
		goto err_lcd_clk;
	}

	dev_dbg(dev, "got resources (regs %p), probing windows\n", sfb->regs);

	platform_set_drvdata(pdev, sfb);//将sfb填入pdev->dev->p->driverdata结构体中
	pm_runtime_get_sync(sfb->dev);

	/* setup gpio and output polarity controls */

	pd->setup_gpio();	//执行setup_gpio函数,此函数在上面说的setup_fimd0.c中定义了,
			        //用来设置GPIO端口给FIMD使用。

	writel(pd->vidcon1, sfb->regs + VIDCON1);	//设置VIDCON1寄存器

	/* set video clock running at under-run */
	if (sfb->variant.has_fixvclk) {		//run vclk
		reg = readl(sfb->regs + VIDCON1);
		reg &= ~VIDCON1_VCLK_MASK;
		reg |= VIDCON1_VCLK_RUN;
		writel(reg, sfb->regs + VIDCON1);
	}

	/* zero all windows before we do anything */

	for (win = 0; win < fbdrv->variant.nr_windows; win++)
		s3c_fb_clear_win(sfb, win);    //将各个window的wincon寄存器清0,VIDOSDxA,VIDOSDxB,
					//VIDOSDxC清0,禁止update各个window的shadow

	/* initialise colour key controls */
	for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) {
		void __iomem *regs = sfb->regs + sfb->variant.keycon;

		regs += (win * 8);
		writel(0xffffff, regs + WKEYCON0);
		writel(0xffffff, regs + WKEYCON1);
	}

	s3c_fb_set_rgb_timing(sfb);		//设置LCD时序

	/* we have the register setup, start allocating framebuffers */

	for (win = 0; win < fbdrv->variant.nr_windows; win++) {
		if (!pd->win[win])
			continue;

		ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win],
				       &sfb->windows[win]);	//分配及注册framebuffer的重要函数
		if (ret < 0) {
			dev_err(dev, "failed to create window %d\n", win);
			for (; win >= 0; win--)
				s3c_fb_release_win(sfb, sfb->windows[win]);
				//注册不成功的话就释放之前注册成功过的window
			goto err_pm_runtime;
		}
	}

	platform_set_drvdata(pdev, sfb);//再一次将sfb填入pdev->dev->p->driverdata结构体中,
			//之前曾经这样操作过一次,现在再来一次,是因为sfb里的数据更新了很多
	pm_runtime_put_sync(sfb->dev);

	return 0;

err_pm_runtime:
	pm_runtime_put_sync(sfb->dev);

err_lcd_clk:
	pm_runtime_disable(sfb->dev);

	if (!sfb->variant.has_clksel)
		clk_disable_unprepare(sfb->lcd_clk);

err_bus_clk:
	clk_disable_unprepare(sfb->bus_clk);

	return ret;
}

************************************************ s3c_fb_probe_win() *************************************************************

/**
 * s3c_fb_probe_win() - register an hardware window
 * @sfb: The base resources for the hardware
 * @variant: The variant information for this window.
 * @res: Pointer to where to place the resultant window.
 *
 * Allocate and do the basic initialisation for one of the hardware's graphics
 * windows.
 */
 /*将显示控制器的结构体作为参数传递进来,它有寄存器起始地址等丰富信息,
 还将win的号码也作为参数传递进来
 variant --> driver data中win的部分,它是由fbdrv->win[win]作为参数传过来的
 res --> per window private data for each framebuffer,它里面含有指向FBI(fb_info)结构体的针指
 */
static int s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
			    struct s3c_fb_win_variant *variant,
			    struct s3c_fb_win **res)
{
	struct fb_var_screeninfo *var;
	struct fb_videomode initmode;
	struct s3c_fb_pd_win *windata; //per window setup data,  也就是platform data中win的部份
	struct s3c_fb_win *win;
	struct fb_info *fbinfo;
	int palette_size;
	int ret;

	dev_dbg(sfb->dev, "probing window %d, variant %p\n", win_no, variant);

	init_waitqueue_head(&sfb->vsync_info.wait);	//初始化等待队列头

	palette_size = variant->palette_sz * 4; //调色板大小

	fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) +
				   palette_size * sizeof(u32), sfb->dev);
	//分配fb_info结构体,返回一个fb_info结构体地址,这个结构体现在没什么内容,
	//只赋值了par(win的起始地址)和device (父设备)两个变量

	if (!fbinfo) {
		dev_err(sfb->dev, "failed to allocate framebuffer\n");
		return -ENOENT;
	}

	windata = sfb->pdata->win[win_no];	//windata指向 platform data中win的部份 
	initmode = *sfb->pdata->vtiming;

	WARN_ON(windata->max_bpp == 0);
	WARN_ON(windata->xres == 0);
	WARN_ON(windata->yres == 0);

	win = fbinfo->par;
	*res = win;//par就是win的起始地址,现在把起始地址给*res,那么*res就是指向s3c_fb_win的指针 
	var = &fbinfo->var;//现在fbinfo->var还是空的,只是将地址给var而已 
	win->variant = *variant;//将win的参数填进win->variant里
	win->fbinfo = fbinfo;//让win->fbinfo指向这个FBI结构实体 
	win->parent = sfb;//win的parent是显示控制器,所以它指向sfb结构体
	win->windata = windata;//让win->windata指向 platform data中win的部分
	win->index = win_no;
	win->palette_buffer = (u32 *)(win + 1);

	ret = s3c_fb_alloc_memory(sfb, win);
	if (ret) {
		dev_err(sfb->dev, "failed to allocate display memory\n");
		return ret;
	}

	/* setup the r/b/g positions for the window's palette */ //设置调色板
	if (win->variant.palette_16bpp) {
		/* Set RGB 5:6:5 as default */
		win->palette.r.offset = 11;
		win->palette.r.length = 5;
		win->palette.g.offset = 5;
		win->palette.g.length = 6;
		win->palette.b.offset = 0;
		win->palette.b.length = 5;

	} else {
		/* Set 8bpp or 8bpp and 1bit alpha */
		win->palette.r.offset = 16;
		win->palette.r.length = 8;
		win->palette.g.offset = 8;
		win->palette.g.length = 8;
		win->palette.b.offset = 0;
		win->palette.b.length = 8;
	}

	/* setup the initial video mode from the window */
	//给FBI填上各个参数,此函数详见appendix
	initmode.xres = windata->xres;
	initmode.yres = windata->yres;
	fb_videomode_to_var(&fbinfo->var, &initmode);

	fbinfo->fix.type	= FB_TYPE_PACKED_PIXELS;
	fbinfo->fix.accel	= FB_ACCEL_NONE;
	fbinfo->var.activate	= FB_ACTIVATE_NOW;
	fbinfo->var.vmode	= FB_VMODE_NONINTERLACED;
	fbinfo->var.bits_per_pixel = windata->default_bpp;
	fbinfo->fbops		= &s3c_fb_ops;	//对framebuffer的操作,详见appendix
	fbinfo->flags		= FBINFO_FLAG_DEFAULT;
	fbinfo->pseudo_palette  = &win->pseudo_palette;

	/* prepare to actually start the framebuffer */

	ret = s3c_fb_check_var(&fbinfo->var, fbinfo);	//检查可变参数
	if (ret < 0) {
		dev_err(sfb->dev, "check_var failed on initial video params\n");
		return ret;
	}

	/* create initial colour map */

	ret = fb_alloc_cmap(&fbinfo->cmap, win->variant.palette_sz, 1);
	if (ret == 0)
		fb_set_cmap(&fbinfo->cmap, fbinfo);
	else
		dev_err(sfb->dev, "failed to allocate fb cmap\n");

	s3c_fb_set_par(fbinfo);

	dev_dbg(sfb->dev, "about to register framebuffer\n");

	/* run the check_var and set_par on our configuration. */

	ret = register_framebuffer(fbinfo);
	if (ret < 0) {
		dev_err(sfb->dev, "failed to register framebuffer\n");
		return ret;
	}

	dev_info(sfb->dev, "window %d: fb %s\n", win_no, fbinfo->fix.id);

	return 0;
}

************************************************ s3c_fb_alloc_memory() ************************************************

扫描二维码关注公众号,回复: 2659972 查看本文章
/**
 * s3c_fb_alloc_memory() - allocate display memory for framebuffer window
 * @sfb: The base resources for the hardware.
 * @win: The window to initialise memory for.
 *
 * Allocate memory for the given framebuffer.
 */
static int s3c_fb_alloc_memory(struct s3c_fb *sfb, struct s3c_fb_win *win)
{
	struct s3c_fb_pd_win *windata = win->windata;	//platform data中win的部分
	unsigned int real_size, virt_size, size;
	struct fb_info *fbi = win->fbinfo;	//让fbi指向FBI结构体
	dma_addr_t map_dma;

	dev_dbg(sfb->dev, "allocating memory for display\n");

	real_size = windata->xres * windata->yres;
	virt_size = windata->virtual_x * windata->virtual_y;	//虚拟size

	dev_dbg(sfb->dev, "real_size=%u (%u.%u), virt_size=%u (%u.%u)\n",
		real_size, windata->xres, windata->yres,
		virt_size, windata->virtual_x, windata->virtual_y);

	//一张framebuffer的大小,是按虚拟分辨率和实际分辨率两者中较大的来算的
	size = (real_size > virt_size) ? real_size : virt_size;
	size *= (windata->max_bpp > 16) ? 32 : windata->max_bpp;
	size /= 8;

	fbi->fix.smem_len = size;	//要分配的内存大小
	size = PAGE_ALIGN(size);	//页大小

	dev_dbg(sfb->dev, "want %u bytes for window\n", size);

	fbi->screen_base = dma_alloc_writecombine(sfb->dev, size,
						  &map_dma, GFP_KERNEL);	//分配framebuffer的内存
	if (!fbi->screen_base)
		return -ENOMEM;

	dev_dbg(sfb->dev, "mapped %x to %p\n",
		(unsigned int)map_dma, fbi->screen_base);

	memset(fbi->screen_base, 0x0, size);	//将framebuffer的内存清空为0
	fbi->fix.smem_start = map_dma;

	return 0;
}

*********************************************************Appendix******************************************************

/**
 * struct s3c_fb_variant - fb variant information
 * @is_2443: Set if S3C2443/S3C2416 style hardware.
 * @nr_windows: The number of windows.
 * @vidtcon: The base for the VIDTCONx registers
 * @wincon: The base for the WINxCON registers.
 * @winmap: The base for the WINxMAP registers.
 * @keycon: The abse for the WxKEYCON registers.
 * @buf_start: Offset of buffer start registers.
 * @buf_size: Offset of buffer size registers.
 * @buf_end: Offset of buffer end registers.
 * @osd: The base for the OSD registers.
 * @palette: Address of palette memory, or 0 if none.
 * @has_prtcon: Set if has PRTCON register.
 * @has_shadowcon: Set if has SHADOWCON register.
 * @has_blendcon: Set if has BLENDCON register.
 * @has_clksel: Set if VIDCON0 register has CLKSEL bit.
 * @has_fixvclk: Set if VIDCON1 register has FIXVCLK bits.
 */
struct s3c_fb_variant {
	unsigned int	is_2443:1;
	unsigned short	nr_windows;
	unsigned int	vidtcon;
	unsigned short	wincon;
	unsigned short	winmap;
	unsigned short	keycon;
	unsigned short	buf_start;
	unsigned short	buf_end;
	unsigned short	buf_size;
	unsigned short	osd;
	unsigned short	osd_stride;
	unsigned short	palette[S3C_FB_MAX_WIN];

	unsigned int	has_prtcon:1;
	unsigned int	has_shadowcon:1;
	unsigned int	has_blendcon:1;
	unsigned int	has_clksel:1;
	unsigned int	has_fixvclk:1;
};
/**
 * fb_videomode_to_var - convert fb_videomode to fb_var_screeninfo
 * @var: pointer to struct fb_var_screeninfo
 * @mode: pointer to struct fb_videomode
 */
void fb_videomode_to_var(struct fb_var_screeninfo *var,
			 const struct fb_videomode *mode)
{
	var->xres = mode->xres;
	var->yres = mode->yres;
	var->xres_virtual = mode->xres;
	var->yres_virtual = mode->yres;
	var->xoffset = 0;
	var->yoffset = 0;
	var->pixclock = mode->pixclock;
	var->left_margin = mode->left_margin;
	var->right_margin = mode->right_margin;
	var->upper_margin = mode->upper_margin;
	var->lower_margin = mode->lower_margin;
	var->hsync_len = mode->hsync_len;
	var->vsync_len = mode->vsync_len;
	var->sync = mode->sync;
	var->vmode = mode->vmode & FB_VMODE_MASK;
}
static struct fb_ops s3c_fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= s3c_fb_check_var,
	.fb_set_par	= s3c_fb_set_par,
	.fb_blank	= s3c_fb_blank,
	.fb_setcolreg	= s3c_fb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
	.fb_pan_display	= s3c_fb_pan_display,
	.fb_ioctl	= s3c_fb_ioctl,
};
/**
 * struct s3c_fb_win - per window private data for each framebuffer.
 * @windata: The platform data supplied for the window configuration.
 * @parent: The hardware that this window is part of.
 * @fbinfo: Pointer pack to the framebuffer info for this window.
 * @varint: The variant information for this window.
 * @palette_buffer: Buffer/cache to hold palette entries.
 * @pseudo_palette: For use in TRUECOLOUR modes for entries 0..15/
 * @index: The window number of this window.
 * @palette: The bitfields for changing r/g/b into a hardware palette entry.
 */
struct s3c_fb_win {
	struct s3c_fb_pd_win	*windata;
	struct s3c_fb		*parent;
	struct fb_info		*fbinfo;
	struct s3c_fb_palette	 palette;
	struct s3c_fb_win_variant variant;

	u32			*palette_buffer;
	u32			 pseudo_palette[16];
	unsigned int		 index;
};

猜你喜欢

转载自blog.csdn.net/JerryGou/article/details/81156595