linux lcd设备驱动剖析二

上一节中,分析了s3c2410fb,c的入口出口函数,以及一些重要结构体的分析,初步知道了这是一个平台驱动的架构。

上一节文章链接:http://blog.csdn.net/lwj103862095/article/details/18188259

上一节讲到probe函数就没继续往下深究了,这一节里,我们来详细分析s3c24xxfb_probe函数,整体分析如下:

static int __init s3c24xxfb_probe(struct platform_device *pdev,
				  enum s3c_drv_type drv_type)
{
	struct s3c2410fb_info *info;
	struct s3c2410fb_display *display;
	struct fb_info *fbinfo;
	struct s3c2410fb_mach_info *mach_info;  /* 包含s3c2410fb_display */
	struct resource *res;
	int ret;
	int irq;
	int i;
	int size;
	u32 lcdcon1;

	/*  s3c24xx_fb_set_platdata()里会设置platform_data
	 *  tq2440_machine_init()函数调用s3c24xx_fb_set_platdata(&tq2440_fb_info);
	 *  所以这里传入来的platform_data就是tq2440_fb_info结构体实例
	 */
	mach_info = pdev->dev.platform_data;

	/* 执行完上面的语句后mach_info指向tq2440_fb_info结构体,而不为NULL  */
	if (mach_info == NULL) {
		dev_err(&pdev->dev,
			"no platform data for lcd, cannot attach\n");
		return -EINVAL;		/* 表示无效的参数 */
	}

	/* tq2440_fb_info设置了default_display = 0,num_displays = 1,故这句不会执行 */
	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;
	}

	/* display指向tq2440_lcd_cfg,关于LCD屏相关参数的设置 */
	display = mach_info->displays + mach_info->default_display;

	/* 通过平台设备platform_device获得IRQ
	 * platform_get_irq其实是调用platform_get_resource(dev, IORESOURCE_IRQ, num)
	 */
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no irq for device\n");
		return -ENOENT;
	}

	/* 	分配一个fb_info结构体,第一参数不为0表示,额外多申请的空间
	 *	用来存放额外的数据,这里用来存放s3c2410fb_info额外的数据
	 *  比如:clk,resource,io,irq_base,drv_type等额外信息
	 */
	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
	if (!fbinfo)
		return -ENOMEM;		/* 返回NULL表示失败 */

	/* 相当于pdev->dev->driver_data = fbinfo */
	platform_set_drvdata(pdev, fbinfo);

	/* 在framebuffer_alloc函数里info->par指向了额外多申请内存空间的首地址 */
	info = fbinfo->par;			/* 将私有数据赋给info指针变量 */
	info->dev = &pdev->dev;		/* 指定struct s3c2410fb_info中dev为平台设备中的dev */
	info->drv_type = drv_type;	/* 驱动类型, DRV_S3C2410还是DRV_S3C2412 */

	/*  通过平台设备platform_device获得资源(IO) */
	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;		/* 资源的大小 */

	/* 申请以res->start地址开始大小为size的I/O内存 */
	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;
	}

	/* 映射I/O地址,其实就是将S3C2440的LCD首寄存器(LCDCON1)的物理地址映射为虚拟地址 */
	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 + 0x54,刚好是LCDINTPND寄存器的地址 */
	info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);

	dprintk("devinit\n");

	/* 驱动名,fbinfo->fix.id = s3c2410fb */
	strcpy(fbinfo->fix.id, driver_name);	

	/* Stop the video */
	lcdcon1 = readl(info->io + S3C2410_LCDCON1);
	/* 禁止Video output */
	writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);

	/* 设置fb_info结构体通用的固定参数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;	/* 无硬件加速 */

	/* 设置fb_info结构体通用的可变参数fb_var_screeninfo结构体 */
	fbinfo->var.nonstd	    	= 0;
	fbinfo->var.activate	    = FB_ACTIVATE_NOW;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    	= FB_VMODE_NONINTERLACED;

	/* 设置fb_ops结构体 */
	fbinfo->fbops		    	= &s3c2410fb_ops;
	
	fbinfo->flags		    	= FBINFO_FLAG_DEFAULT;

	/* 设置假调色板 */
	fbinfo->pseudo_palette      = &info->pseudo_pal;

	/* palette_buffer[i] = 0x80000000,清空调色板 */
	for (i = 0; i < 256; i++)
		info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

	/* 申请中断,s3c2410fb_irq是中断处理函数 */
	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;
	}

	/* 获取lcd时钟 */
	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;
	}

	/* 使能lcd时钟 */
	clk_enable(info->clk);		
	dprintk("got and enabled clock\n");

	msleep(1);

	/* find maximum required memory size for display */

	/* 计算出lcd的显存大小,显存大小为width * height * bpp所以还要左移3位,
     * 即刚好一帧大小空间,前面计算出来的是多少bit,计算出显存为多少字节。
     * 显示配置有可能有多个,所以呢,这个for循环计算出的是最大显存大小。
	 */
	for (i = 0; i < mach_info->num_displays; i++) {  			/* 这里mach_info->num_displays = 1 */
		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;					/* bpp */
		smem_len >>= 3;											/* smem_len除以8 */
		if (fbinfo->fix.smem_len < smem_len)
			fbinfo->fix.smem_len = smem_len;
	}

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

	/* display指向tq2440_lcd_cfg */
	fbinfo->var.xres = display->xres;			/* 设置x方向的分辨率 */
	fbinfo->var.yres = display->yres;			/* 设置y方向的分辨率 */
	fbinfo->var.bits_per_pixel = display->bpp;	/* 设置bpp位数 */

	/* 初始化LCD相关的寄存器 */
	s3c2410fb_init_registers(fbinfo);

	/* 检查可变参数 */
	s3c2410fb_check_var(&fbinfo->var, fbinfo);

	/* 注册fb_info结构体 */
	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");
	}


	/* TQ2440开发板内核启动时打印的信息,fb0: s3c2410fb frame buffer device  */
	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);				/* 禁止lcd时钟 */
	clk_put(info->clk);					/* 删除lcd时钟 */
release_irq:
	free_irq(irq, info);				/* 释放IRQ */
release_regs:
	iounmap(info->io);					/* 解除映射 */
release_mem:
	release_resource(info->mem);		/* 释放资源 */
	kfree(info->mem);					/* 释放刚申请的内存 */
dealloc_fb:
	platform_set_drvdata(pdev, NULL);	/* 相当于pdev->dev->driver_data = NULL */
	framebuffer_release(fbinfo);		/* 释放fb_info结构体 */
	return ret;
}
拆分详解:

一、获得平台数据

	mach_info = pdev->dev.platform_data;

	/* 执行完上面的语句后mach_info指向tq2440_fb_info结构体,而不为NULL  */
	if (mach_info == NULL) {
		dev_err(&pdev->dev,
			"no platform data for lcd, cannot attach\n");
		return -EINVAL;		/* 表示无效的参数 */
	}
s3c24xx_fb_set_platdata()里会设置platform_data,tq2440_machine_init()函数调用s3c24xx_fb_set_platdata(&tq2440_fb_info);
所以这里传入来的platform_data就是tq2440_fb_info结构体实例。

static void __init tq2440_machine_init(void)
{
	/* 初始化tq2440_fb_info实体结构体 */
	s3c24xx_fb_set_platdata(&tq2440_fb_info);
	s3c_i2c0_set_platdata(NULL);

	/* 添加tq2440_devices到内核,它是platform_device类的设备 */
	platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices));
	EmbedSky_machine_init();
	s3c2410_gpio_setpin(S3C2410_GPG12, 0);
	s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPIO_OUTPUT);
	s3c24xx_udc_set_platdata(&EmbedSky_udc_cfg);
}
s3c24xx_fb_set_platdata函数将tq2440_fb_info拷贝到s3c2410fb_mach_info,并将s3c_device_lcd.dev.platform_data指向 tq2440_fb_info

void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
	struct s3c2410fb_mach_info *npd;

	/* 申请分配s3c2410fb_mach_info大小的内存 */
	npd = kmalloc(sizeof(*npd), GFP_KERNEL);
	if (npd) {
		/* 拷贝s3c2410fb_mach_info型实体给npd */
		memcpy(npd, pd, sizeof(*npd));

		/* 最后将s3c2410fb_mach_info型实体赋给platform_data */
		s3c_device_lcd.dev.platform_data = npd;
	} else {
		printk(KERN_ERR "no memory for LCD platform data\n");
	}
}
二、设置s3c2410fb_display指向tq2440_lcd_cfg

	/* tq2440_fb_info设置了default_display = 0,num_displays = 1,故这句不会执行 */
	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;
	}

	/* display指向tq2440_lcd_cfg,关于LCD屏相关参数的设置 */
	display = mach_info->displays + mach_info->default_display;
三、获得IRQ资源

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no irq for device\n");
		return -ENOENT;
	}
四、分配fb_info内存
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
	if (!fbinfo)
		return -ENOMEM;		/* 返回NULL表示失败 */
framebuffer_alloc第一参数不为0表示,额外多申请的空间,用来存放额外的数据,这里用来存放s3c2410fb_info额外的数据,比如:clk,resource,io,irq_base,drv_type等额外信息。
五、设置s3c2410fb_info结构体

/* 相当于pdev->dev->driver_data = fbinfo */
	platform_set_drvdata(pdev, fbinfo);

	/* 在framebuffer_alloc函数里info->par指向了额外多申请内存空间的首地址 */
	info = fbinfo->par;			/* 将私有数据赋给info指针变量 */
	info->dev = &pdev->dev;		/* 指定struct s3c2410fb_info中dev为平台设备中的dev */
	info->drv_type = drv_type;	/* 驱动类型, DRV_S3C2410还是DRV_S3C2412 */
六、获取IO资源,映射IO

/*  通过平台设备platform_device获得资源(IO) */
	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;		/* 资源的大小 */

	/* 申请以res->start地址开始大小为size的I/O内存 */
	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;
	}

	/* 映射I/O地址,其实就是将S3C2440的LCD首寄存器(LCDCON1)的物理地址映射为虚拟地址 */
	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 + 0x54,刚好是LCDINTPND寄存器的地址 */
	info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
七、读写LCDCON1,禁止视频数据输出

	/* Stop the video */
	lcdcon1 = readl(info->io + S3C2410_LCDCON1);
	/* 禁止Video output */
	writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
八、设置fb_info结构体的固定参数(fb_fix_screeninfo),可变参数(fb_var_screeninfo),fbops结构体,flags,假调色板(pseudo_palette)等

/* 设置fb_info结构体通用的固定参数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;	/* 无硬件加速 */

	/* 设置fb_info结构体通用的可变参数fb_var_screeninfo结构体 */
	fbinfo->var.nonstd	    	= 0;
	fbinfo->var.activate	    = FB_ACTIVATE_NOW;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    	= FB_VMODE_NONINTERLACED;

	/* 设置fb_ops结构体 */
	fbinfo->fbops		    	= &s3c2410fb_ops;
	
	fbinfo->flags		    	= FBINFO_FLAG_DEFAULT;

	/* 设置假调色板 */
	fbinfo->pseudo_palette      = &info->pseudo_pal;

	/* palette_buffer[i] = 0x80000000,清空调色板 */
	for (i = 0; i < 256; i++)
		info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
九、申请中断、获取LCD时钟,使能LCD时钟
	/* 申请中断,s3c2410fb_irq是中断处理函数 */
	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;
	}

	/* 获取lcd时钟 */
	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;
	}

	/* 使能lcd时钟 */
	clk_enable(info->clk);		
	dprintk("got and enabled clock\n");
十、计算显存大小、分配显存内存

	/* 计算出lcd的显存大小,显存大小为width * height * bpp所以还要左移3位,
     * 即刚好一帧大小空间,前面计算出来的是多少bit,计算出显存为多少字节。
     * 显示配置有可能有多个,所以呢,这个for循环计算出的是最大显存大小。
	 */
	for (i = 0; i < mach_info->num_displays; i++) {  			/* 这里mach_info->num_displays = 1 */
		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;					/* bpp */
		smem_len >>= 3;											/* smem_len除以8 */
		if (fbinfo->fix.smem_len < smem_len)
			fbinfo->fix.smem_len = smem_len;
	}

	/* 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;
	}
十一、设置fb_info结构体中的可变参数的x、y分辨率以及BPP为tq2440_lcd_cfg中的x、y分辨率和BPP

	/* display指向tq2440_lcd_cfg */
	fbinfo->var.xres = display->xres;			/* 设置x方向的分辨率 */
	fbinfo->var.yres = display->yres;			/* 设置y方向的分辨率 */
	fbinfo->var.bits_per_pixel = display->bpp;	/* 设置bpp位数 */
十二、LCD相关寄存器的设置和fb_info的可变参数的检测

/* 初始化LCD相关的寄存器 */
	s3c2410fb_init_registers(fbinfo);

	/* 检查可变参数 */
	s3c2410fb_check_var(&fbinfo->var, fbinfo);
十三、注册fb_info结构体

	/* 注册fb_info结构体 */
	ret = register_framebuffer(fbinfo);
	if (ret < 0) {
		printk(KERN_ERR "Failed to register framebuffer device: %d\n",
			ret);
		goto free_video_memory;
	}

你可能感兴趣的:(Linux驱动)