linux驱动由浅入深系列:显示子系统之二(高通平台lcd驱动代码分析)

在上一篇文章中我们初步了解了lcd对应用层提供的接口-framebuffer,以及应用层通过fb在lcd上画图的示例。本文我们来看一下驱动层的相关逻辑,主要介绍几个核心数据结构fb_info, fb_var_screeninfo, fb_fix_screeninfo,以及framebuffer设备注册的过程,framebuffer大小的确定。
我们从kernel中lcd最核心的一个文件看起
kernel\msm-3.18\drivers\video\fbdev\core\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 ret;
	mutex_lock(&registration_lock);
	ret = do_register_framebuffer(fb_info);
	mutex_unlock(&registration_lock);
	return ret;
}
EXPORT_SYMBOL(register_framebuffer);/////////////////////////导出函数,以供实际设备驱动中调用
核心结构体
struct fb_info {
	atomic_t count;
	int node;
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
	struct fb_var_screeninfo var;	/* Current var *////////////////////////framebuffer中的可变参数
	struct fb_fix_screeninfo fix;	/* Current fix *////////////////////////framebuffer中的固定参数
	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 */
	struct file *file;		/* current file node */
	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;
	/* we need the PCI or similar 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;
	bool skip_vt_switch; /* no VT switch on suspend/resume required */
};
实际设备驱动部分,以高通平台msm8937为例
kernel\msm-3.18\drivers\video\msm\mdss\mdss_fb.c
MMIO(Memory mapping I/O)即内存映射I/O,它是PCI规范的一部分,I/O设备被放置在内存空间而不是I/O空间。从处理器的角度看,内存映射I/O后系统设备访问起来和内存一样。
static int mdss_fb_register(struct msm_fb_data_type *mfd)
{
	int ret = -ENODEV;
	int bpp;
	char panel_name[20];
	struct mdss_panel_info *panel_info = mfd->panel_info;
	struct fb_info *fbi = mfd->fbi;
	struct fb_fix_screeninfo *fix;
	struct fb_var_screeninfo *var;
	int *id;
	/*
	 * fb info initialization
	 */
	fix = &fbi->fix;
	var = &fbi->var;
	fix->type_aux = 0;	/* if type == FB_TYPE_INTERLEAVED_PLANES */
	fix->visual = FB_VISUAL_TRUECOLOR;	/* True Color */
	fix->ywrapstep = 0;	/* No support */
	fix->mmio_start = 0;	/* No MMIO Address */
	fix->mmio_len = 0;	/* No MMIO Address */
	fix->accel = FB_ACCEL_NONE;/* FB_ACCEL_MSM needes to be added in fb.h */
	var->xoffset = 0,	/* Offset from virtual to visible */
	var->yoffset = 0,	/* resolution */
	var->grayscale = 0,	/* No graylevels */
	var->nonstd = 0,	/* standard pixel format */
	var->activate = FB_ACTIVATE_VBL,	/* activate it at vsync */
	var->height = -1,	/* height of picture in mm */
	var->width = -1,	/* width of picture in mm */
	var->accel_flags = 0,	/* acceleration flags */
	var->sync = 0,	/* see FB_SYNC_* */
	var->rotate = 0,	/* angle we rotate counter clockwise */
	mfd->op_enable = false;
	switch (mfd->fb_imgType) {
	case MDP_RGB_565:////////////////////////////lcd R、G、B三原色各自占的位数,565一般用于地段lcd上
		fix->type = FB_TYPE_PACKED_PIXELS;
		fix->xpanstep = 1;
		fix->ypanstep = 1;
		var->vmode = FB_VMODE_NONINTERLACED;
		var->blue.offset = 0;
		var->green.offset = 5;
		var->red.offset = 11;
		var->blue.length = 5;
		var->green.length = 6;
		var->red.length = 5;
		var->blue.msb_right = 0;
		var->green.msb_right = 0;
		var->red.msb_right = 0;
		var->transp.offset = 0;
		var->transp.length = 0;
		bpp = 2;
		break;
.......
	case MDP_RGBA_8888:///////////////////////本文示例的imgType
		fix->type = FB_TYPE_PACKED_PIXELS;
		fix->xpanstep = 1;
		fix->ypanstep = 1;
		var->vmode = FB_VMODE_NONINTERLACED;
		var->blue.offset = 16;
		var->green.offset = 8;
		var->red.offset = 0;
		var->blue.length = 8;
		var->green.length = 8;
		var->red.length = 8;
		var->blue.msb_right = 0;
		var->green.msb_right = 0;
		var->red.msb_right = 0;
		var->transp.offset = 24;
		var->transp.length = 8;
		bpp = 4;
		break;
	default:
		pr_err("msm_fb_init: fb %d unkown image type!\n",
			    mfd->index);
		return ret;
	}
	mdss_panelinfo_to_fb_var(panel_info, var);


	fix->type = panel_info->is_3d_panel;
	if (mfd->mdp.fb_stride)
		fix->line_length = mfd->mdp.fb_stride(mfd->index, var->xres,
							bpp);
	else
		fix->line_length = var->xres * bpp;
	var->xres_virtual = var->xres;
	var->yres_virtual = panel_info->yres * mfd->fb_page;
	var->bits_per_pixel = bpp * 8;	/* FrameBuffer color depth */
	/*
	 * Populate smem length here for uspace to get the
	 * Framebuffer size when FBIO_FSCREENINFO ioctl is called.
	 */
	fix->smem_len = PAGE_ALIGN(fix->line_length * var->yres) * mfd->fb_page;//////////设置fbi->fix->smem_len的长度,即分配framebuffer的长度
	/* id field for fb app  */
	id = (int *)&mfd->panel;
	snprintf(fix->id, sizeof(fix->id), "mdssfb_%x", (u32) *id);
	fbi->fbops = &mdss_fb_ops;
	fbi->flags = FBINFO_FLAG_DEFAULT;
	fbi->pseudo_palette = mdss_fb_pseudo_palette;
	mfd->ref_cnt = 0;
	mfd->panel_power_state = MDSS_PANEL_POWER_OFF;
	mfd->dcm_state = DCM_UNINIT;
	if (mdss_fb_alloc_fbmem(mfd))////////////////////////////////////////分配framebuffer的内存空间
		pr_warn("unable to allocate fb memory in fb register\n");
	mfd->op_enable = true;
	mutex_init(&mfd->update.lock);
	mutex_init(&mfd->no_update.lock);
	mutex_init(&mfd->mdp_sync_pt_data.sync_mutex);
	atomic_set(&mfd->mdp_sync_pt_data.commit_cnt, 0);
	atomic_set(&mfd->commits_pending, 0);
	atomic_set(&mfd->ioctl_ref_cnt, 0);
	atomic_set(&mfd->kickoff_pending, 0);
	init_timer(&mfd->no_update.timer);
	mfd->no_update.timer.function = mdss_fb_no_update_notify_timer_cb;
	mfd->no_update.timer.data = (unsigned long)mfd;
	mfd->update.ref_count = 0;
	mfd->no_update.ref_count = 0;
	mfd->update.init_done = false;
	init_completion(&mfd->update.comp);
	init_completion(&mfd->no_update.comp);
	init_completion(&mfd->power_off_comp);
	init_completion(&mfd->power_set_comp);
	init_waitqueue_head(&mfd->commit_wait_q);
	init_waitqueue_head(&mfd->idle_wait_q);
	init_waitqueue_head(&mfd->ioctl_q);
	init_waitqueue_head(&mfd->kickoff_wait_q);
	ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
	if (ret)
		pr_err("fb_alloc_cmap() failed!\n");


	if (register_framebuffer(fbi) < 0) {//////////////////////////////////////调用fbmem.c中导出的函数注册framebuffer设备
		fb_dealloc_cmap(&fbi->cmap);


		mfd->op_enable = false;
		return -EPERM;
	}
	snprintf(panel_name, ARRAY_SIZE(panel_name), "mdss_panel_fb%d",
		mfd->index);
	mdss_panel_debugfs_init(panel_info, panel_name);
	pr_info("FrameBuffer[%d] %dx%d registered successfully!\n", mfd->index,
					fbi->var.xres, fbi->var.yres);
	return 0;
}
framebuffer在内核开辟空间的函数
kernel\msm-3.18\drivers\video\msm\mdss\mdss_fb.c
static int mdss_fb_alloc_fbmem_iommu(struct msm_fb_data_type *mfd, int dom)
{
	void *virt = NULL;
	phys_addr_t phys = 0;
	size_t size = 0;
	struct platform_device *pdev = mfd->pdev;
	int rc = 0;
	struct device_node *fbmem_pnode = NULL;
	if (!pdev || !pdev->dev.of_node) {
		pr_err("Invalid device node\n");
		return -ENODEV;
	}
	fbmem_pnode = of_parse_phandle(pdev->dev.of_node,
		"linux,contiguous-region", 0);
	if (!fbmem_pnode) {
		pr_debug("fbmem is not reserved for %s\n", pdev->name);
		mfd->fbi->screen_base = NULL;//////////////////////////framebuffer起始虚拟地址设置为NULL
		mfd->fbi->fix.smem_start = 0;//////////////////////////framebuffer起始物理地址设置为0
		return 0;
	} else {
		const u32 *addr;
		u64 len;
		addr = of_get_address(fbmem_pnode, 0, &len, NULL);
		if (!addr) {
			pr_err("fbmem size is not specified\n");
			of_node_put(fbmem_pnode);
			return -EINVAL;
		}
		size = (size_t)len;
		of_node_put(fbmem_pnode);
	}
	pr_debug("%s frame buffer reserve_size=0x%zx\n", __func__, size);
	if (size < PAGE_ALIGN(mfd->fbi->fix.line_length *
			      mfd->fbi->var.yres_virtual))
		pr_warn("reserve size is smaller than framebuffer size\n");
	rc = mdss_smmu_dma_alloc_coherent(&pdev->dev, size, &phys, &mfd->iova,////////////////////在内核中分配连续的内存空间
			&virt, GFP_KERNEL, dom);
	if (rc) {
		pr_err("unable to alloc fbmem size=%zx\n", size);
		return -ENOMEM;
	}
	if (MDSS_LPAE_CHECK(phys)) {
		pr_warn("fb mem phys %pa > 4GB is not supported.\n", &phys);
		mdss_smmu_dma_free_coherent(&pdev->dev, size, &virt,
				phys, mfd->iova, dom);
		return -ERANGE;
	}
	pr_debug("alloc 0x%zxB @ (%pa phys) (0x%pK virt) (%pa iova) for fb%d\n",
		 size, &phys, virt, &mfd->iova, mfd->index);
	mfd->fbi->screen_base = virt;
	mfd->fbi->fix.smem_start = phys;
	mfd->fbi->fix.smem_len = size;
	return 0;
}
fb_info->screen_base  是framebuffer起始虚拟地址,也就是mmap后程序写入fb的地址,该地址会直接写入到fb_info->fix.smem_start指向的物理地址。
在用户空间的Surfaceflinger把数据写入screen_base时候,改数据通过DMA直接映射到smem_start,LCD控制器就能读到数据。
其中framebuffer大小的确定在mdss_fb_register函数中
fix->smem_len = PAGE_ALIGN(fix->line_length * var->yres) * mfd->fb_page;
其中
1,var.yres = 1440,在dtsi文件中指定,见下文的dts代码
2,fix.line_length 是何值呢
在上文中的mdss_fb_register函数中有
if (mfd->mdp.fb_stride)
fix->line_length = mfd->mdp.fb_stride(mfd->index, var->xres, bpp);
else
fix->line_length = var->xres * bpp;
fb_stride是一个函数指针,我们定义了,因此由这个函数生成fix->line_length = mfd->mdp.fb_stride(mfd->index, var->xres, bpp);
var.xres = 720,在dtsi文件中指定,见下文的dts代码
而bpp因为mfd->fb_imgType = MDP_RGBA_8888,所以,bpp = 4
fb_stride函数是将xres对齐到32的整数倍,然后乘以bpp
因此:line_length = ALIGN(xres, 32) * bpp = ALIGN(720, 32) *4 = 736 * 4 = 2994
3,mfd->fb_page
在Z:\work\motoE_lenovo_drvonly_o\kernel\msm-3.18\drivers\video\msm\mdss\mdss_fb.c
的static int mdss_fb_probe(struct platform_device *pdev)函数中有如下定义
mfd->fb_page = MDSS_FB_NUM;
而MDSS_FB_NUM是由系统配置为双framebuffer还是三framebuffer决定的
#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER
#define MDSS_FB_NUM 3
#else
#define MDSS_FB_NUM 2
#endif
我们示例中的平台是双framebuffer的,因此mfd->fb_page = 2
最后:fix.smem_len = PAGE_ALIGN(fix->line_length * var->yres) * mfd->fb_page = PAGE_ALIGN(2994 *  1440) * 2 =PAGE_ALIGN(4239360)*2 /*PAGE_ALIGN宏是将地址对齐到4096*/ = 8478720

设备相关参数定义在dtsi文件中
arch/arm/boot/dts/qcom/msm8917-mtp.dtsi
&mdss_dsi0 {
	qcom,dsi-pref-prim-pan = <&dsi_tianma_hd720_v_vid>;
	pinctrl-names = "mdss_default", "mdss_sleep";
	pinctrl-0 = <&mdss_dsi_active &mdss_te_active>;
	pinctrl-1 = <&mdss_dsi_suspend &mdss_te_suspend>;
	qcom,platform-te-gpio = <&tlmm 24 0>;
	qcom,platform-reset-gpio = <&tlmm 60 0>;
	//qcom,platform-bklight-en-gpio = <&tlmm 98 0>;
};
arch/arm/boot/dts/qcom/dsi-panel-tianma-ft8006m-hd720-v-video.dtsi
&mdss_mdp {
	dsi_tianma_hd720_v_vid: qcom,mdss_dsi_tianma_hd720_v_video {
		qcom,mdss-dsi-panel-name = "tianma hd720 video mode dsi panel";
		qcom,mdss-dsi-panel-type = "dsi_video_mode";
		qcom,mdss-dsi-panel-framerate = <60>;
		qcom,mdss-dsi-virtual-channel-id = <0>;
		qcom,mdss-dsi-stream = <0>;
		qcom,mdss-dsi-panel-width = <720>;
		qcom,mdss-dsi-panel-height = <1440>;
		qcom,mdss-dsi-h-front-porch = <45>;
		qcom,mdss-dsi-h-back-porch = <36>;
		qcom,mdss-dsi-h-pulse-width = <14>;
		qcom,mdss-dsi-h-sync-skew = <0>;
		qcom,mdss-dsi-v-back-porch = <130>;
		qcom,mdss-dsi-v-front-porch = <210>;
		qcom,mdss-dsi-v-pulse-width = <20>;
		qcom,mdss-dsi-h-left-border = <0>;
		qcom,mdss-dsi-h-right-border = <0>;
		qcom,mdss-dsi-v-top-border = <0>;
		qcom,mdss-dsi-v-bottom-border = <0>;
		qcom,mdss-dsi-bpp = <24>;
		qcom,mdss-dsi-underflow-color = <0xff>;
		qcom,mdss-dsi-border-color = <0>;
		qcom,mdss-dsi-h-sync-pulse = <0>;
		qcom,mdss-dsi-traffic-mode = "burst_mode";
		qcom,mdss-dsi-bllp-eof-power-mode;
		qcom,mdss-dsi-bllp-power-mode;
		qcom,mdss-dsi-lane-0-state;
		qcom,mdss-dsi-lane-1-state;
		qcom,mdss-dsi-lane-2-state;
//		qcom,mdss-dsi-lane-3-state;
		qcom,mdss-dsi-panel-timings = [
//			87 1c 12 00 40 44 16 1e 17 03 04 00
			c2 2c 1c 00 58 5c 22 30 22 03 04 00
			];
		qcom,mdss-dsi-t-clk-post = <0x03>;
		qcom,mdss-dsi-t-clk-pre = <0x24>;
		qcom,mdss-dsi-bl-min-level = <1>;
		qcom,mdss-dsi-bl-max-level = <4095>;
		qcom,mdss-dsi-dma-trigger = "trigger_sw";
		qcom,mdss-dsi-mdp-trigger = "none";
		qcom,mdss-dsi-on-command = [
			05 01 00 00 78 00 02 11 00
			05 01 00 00 14 00 02 29 00];
		qcom,mdss-dsi-off-command = [05 01 00 00 14 00 02 28 00
				 05 01 00 00 78 00 02 10 00];
		qcom,mdss-dsi-on-command-state = "dsi_lp_mode";
		qcom,mdss-dsi-off-command-state = "dsi_lp_mode";
		qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled";
		qcom,mdss-dsi-reset-sequence = <1 10>, <0 10>, <1 120>;
		qcom,mdss-dsi-tx-eot-append;
		qcom,mdss-dsi-lp11-init;
		qcom,mdss-dsi-post-init-delay = <1>;
	};
};
lcd驱动的probe函数中还会在sys文件系统中建立相关节点
/sys/class/graphics/fb0

猜你喜欢

转载自blog.csdn.net/RadianceBlau/article/details/78458099