AM335x LCD驱动解析

1. LCD背景

帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。

帧缓冲驱动的应用广泛,在linux的桌面系统中,Xwindow服务器就是利用帧缓冲进行窗口的绘制。

Linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16 位色的FrameBuffer 来说, FrameBuffer中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。

帧缓存可以在系统存储器(内存)的任意位置,视频控制器通过访问帧缓存来刷新屏幕。 帧缓存也叫刷新缓存 Frame buffer 或 refresh buffer, 这里的帧(frame)是指整个屏幕范围。

帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。

CPU指定显示控制器工作,则显示控制器根据CPU的控制到指定的地方去取数据 和 指令, 目前的数据一般是从显存里取, 如果显存里存不下,则从内存里取, 内存也放不下,则从硬盘里取,当然也不是内存放不下,而是为了节省内存的话,可以放在硬盘里,然后通过 指令控制显示控制器去取。帧缓存 Frame Buffer,里面存储的东西是一帧一帧的, 显卡会不停的刷新Frame Buffer, 这每一帧如果不捕获的话, 则会被丢弃,也就是说是实时的。这每一帧不管是保存在内存还是显存里, 都是一个显性的信息,这每一帧假设是800x600的分辨率, 则保存的是800x600个像素点,和颜色值。

显示器可以显示无限种颜色,目前普通电脑的显卡可以显示32位真彩、24位真彩、16位增强色、256色。除256色外,大家可以根据自己的需要在显卡的允许范围之内随意选择。很多用户有一种错误概念,认为256色是最高级的选项,而实际上正好相反。256色是最低级的选项,它已不能满足彩色图像的显示需要。16位不是16种颜色,而是2的16次平方(256×256)种颜色,但256色就是256(2的8次平方)种颜色。所以16位色要比256色丰富得多。

帧缓冲设备对应的设备文件为/dev/fb*,如果系统有多个显示卡,Linux下还可支持多个帧缓冲设备,最多可达32 个,分别为/dev/fb0到/dev/fb31,而/dev/fb则为当前缺省的帧缓冲设备,通常指向/dev/fb0。当然在嵌入式系统中支持一个显示设备就够了。帧缓冲设备为标准字符设备,主设备号为29,次设备号则从0到31。分别对应/dev/fb0-/dev/fb31。通过/dev/fb,应用程序的操作主要有这几种:

  • 1. 读/写(read/write)/dev/fb:相当于读/写屏幕缓冲区。例如用 cp /dev/fb0 tmp命令可将当前屏幕的内容拷贝到一个文件中,而命令cp tmp > /dev/fb0 则将图形文件tmp显示在屏幕上。

  • 2.映射(map)操作:由于Linux工作在保护模式,每个应用程序都有自己的虚拟地址空间,在应用程序中是不能直接访问物理缓冲区地址的。为此, Linux在文件操作 file_operations结构中提供了mmap函数,可将文件的内容映射到用户空间。对于帧缓冲设备,则可通过映射操作,可将屏幕缓冲区的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图了。实际上,使用帧缓冲设备的应用程序都是通过映射操作来显示图形的。由于映射操作都是由内核来完成,下面我们将看到,帧缓冲驱动留给开发人员的工作并不多。

  • 3. I/O控制:对于帧缓冲设备,对设备文件的ioctl操作可读取/设置显示设备及屏幕的参数,如分辨率,显示颜色数,屏幕大小等等。ioctl的操作是由底层的驱动程序来完成的。

2. LCD驱动

2.1 Device

kernel\arch\arm\mach-omap2\board-am335xevm.c:

am335x_evm_setup() -> setup_am335x() -> am_board_dev_cfg:

/* am335x board */
static struct evm_dev_cfg am_board_dev_cfg[] = {

	{lcdc_init,     DEV_ON_BASEBOARD, PROFILE_ALL},

};

↓

static const struct display_panel disp_panel = {
	WVGA,
	32,
	32,
	COLOR_ACTIVE,
};

static struct lcd_ctrl_config lcd_cfg = {
	&disp_panel,
	.ac_bias		= 255,
	.ac_bias_intrpt		= 0,
	.dma_burst_sz		= 16,
	.bpp			= 32,
	.fdd			= 0x80,
	.tft_alt_mode		= 0,
	.stn_565_mode		= 0,
	.mono_8bit_mode		= 0,
	.invert_line_clock	= 1,
	.invert_frm_clock	= 0,
	.sync_edge		= 0,
	.sync_ctrl		= 1,
	.raster_order		= 0,
};

struct da8xx_lcdc_platform_data  G070VW01_pdata = {
	.manu_name              = "AUO",
	.controller_data        = &lcd_cfg,
	.type                   = "G070VW01",
};


static void lcdc_init(int evm_id, int profile)
{
	struct da8xx_lcdc_platform_data *lcdc_pdata;
	setup_pin_mux(lcdc_pin_mux);

	if (conf_disp_pll(300000000)) {
		pr_info("Failed configure display PLL, not attempting to"
				"register LCDC\n");
		return;
	}

    /* (1) 根据不同开发板的不同屏幕,选择不同的配置参数 */
	switch (evm_id) {
	case GEN_PURP_EVM:
	case GEN_PURP_DDR3_EVM:
		lcdc_pdata = &TFC_S9700RTWV35TR_01B_pdata;
		break;
	case EVM_SK:
		lcdc_pdata = &NHD_480272MF_ATXI_pdata;
		break;

    /* (2) 的配置 */
	case AM_BOARD:
		lcdc_pdata = &G070VW01_pdata;
		break;
	default:
		pr_err("LCDC not supported on this evm (%d)\n",evm_id);
		return;
	}

	lcdc_pdata->get_context_loss_count = omap_pm_get_dev_context_loss_count;

    /* (2) 创建LCD Controller对应的Device */
	if (am33xx_register_lcdc(lcdc_pdata))
		pr_info("Failed to register LCDC device\n");

	return;
}

↓

int __init am33xx_register_lcdc(struct da8xx_lcdc_platform_data *pdata)
{
	int id = 0;
	struct platform_device *pdev;
	struct omap_hwmod *oh;
	char *oh_name = "lcdc";
	char *dev_name = "da8xx_lcdc";     // (1) 指定device的name,用这个和Driver来进行适配

	oh = omap_hwmod_lookup(oh_name);
	if (!oh) {
		pr_err("Could not look up LCD%d hwmod\n", id);
		return -ENODEV;
	}

	pdev = omap_device_build(dev_name, id, oh, pdata,
			sizeof(struct da8xx_lcdc_platform_data), NULL, 0, 0);
	if (IS_ERR(pdev)) {
		WARN(1, "Can't build omap_device for %s:%s.\n",
			dev_name, oh->name);
		return PTR_ERR(pdev);
	}
	return 0;
}

lcdc 相关hwmod的配置,定义了寄存器和中断配置。

kernel\arch\arm\mach-omap2\omap_hwmod_33xx_data.c:

/* lcdc */
static struct omap_hwmod_class_sysconfig lcdc_sysc = {
	.rev_offs	= 0x0,
	.sysc_offs	= 0x54,
	.sysc_flags	= (SYSC_HAS_SIDLEMODE | SYSC_HAS_MIDLEMODE),
	.idlemodes	= (SIDLE_FORCE | SIDLE_NO | SIDLE_SMART),
	.sysc_fields	= &omap_hwmod_sysc_type2,
};

static struct omap_hwmod_class am33xx_lcdc_hwmod_class = {
	.name		= "lcdc",
	.sysc		= &lcdc_sysc,
};

static struct omap_hwmod_irq_info am33xx_lcdc_irqs[] = {
	{ .irq = 36 },
	{ .irq = -1 }
};

struct omap_hwmod_addr_space am33xx_lcdc_addr_space[] = {
	{
		.pa_start	= 0x4830E000,
		.pa_end		= 0x4830E000 + SZ_8K - 1,
		.flags		= ADDR_MAP_ON_INIT | ADDR_TYPE_RT,
	},
	{ }
};

struct omap_hwmod_ocp_if am33xx_l3_main__lcdc = {
	.master		= &am33xx_l3_main_hwmod,
	.slave		= &am33xx_lcdc_hwmod,
	.addr		= am33xx_lcdc_addr_space,
	.user		= OCP_USER_MPU,
};

static struct omap_hwmod_ocp_if *am33xx_lcdc_slaves[] = {
	&am33xx_l3_main__lcdc,
};

static struct omap_hwmod am33xx_lcdc_hwmod = {
	.name		= "lcdc",
	.class		= &am33xx_lcdc_hwmod_class,
	.clkdm_name	= "lcdc_clkdm",
	.mpu_irqs	= am33xx_lcdc_irqs,
	.main_clk	= "lcdc_fck",
	.prcm		= {
		.omap4	= {
			.clkctrl_offs	= AM33XX_CM_PER_LCDC_CLKCTRL_OFFSET,
			.modulemode	= MODULEMODE_SWCTRL,
		},
	},
	.slaves		= am33xx_lcdc_slaves,
	.slaves_cnt	= ARRAY_SIZE(am33xx_lcdc_slaves),
	.flags		= (HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY),
};

2.2 Driver

kernel\drivers\video\da8xx-fb.c:

#define DRIVER_NAME "da8xx_lcdc"

static struct platform_driver da8xx_fb_driver = {
	.probe = fb_probe,
	.remove = __devexit_p(fb_remove),
	.suspend = fb_suspend,
	.resume = fb_resume,
	.driver = {
		   .name = DRIVER_NAME,
		   .owner = THIS_MODULE,
		   },
};

static int __init da8xx_fb_init(void)
{
	return platform_driver_register(&da8xx_fb_driver);
}

↓

static int __devinit fb_probe(struct platform_device *device)
{
	struct da8xx_lcdc_platform_data *fb_pdata =
						device->dev.platform_data;
	struct lcd_ctrl_config *lcd_cfg;
	struct da8xx_panel *lcdc_info;
	struct fb_info *da8xx_fb_info;
	struct clk *fb_clk = NULL;
	struct da8xx_fb_par *par;
	resource_size_t len;
	int ret, i;
	unsigned long ulcm;

	if (fb_pdata == NULL) {
		dev_err(&device->dev, "Can not get platform data\n");
		return -ENOENT;
	}

    /* (1) 获取到lcdc的寄存器信息,并映射成虚拟地址来使用
        lcdc的寄存器信息是在hwmod中定义的,并且在对应platform device创建时注册成了resource
     */
	lcdc_regs = platform_get_resource(device, IORESOURCE_MEM, 0);
	if (!lcdc_regs) {
		dev_err(&device->dev,
			"Can not get memory resource for LCD controller\n");
		return -ENOENT;
	}

	len = resource_size(lcdc_regs);

	lcdc_regs = request_mem_region(lcdc_regs->start, len, lcdc_regs->name);
	if (!lcdc_regs)
		return -EBUSY;

    /* (1.1) 寄存器物理地址映射成虚拟地址 */
	da8xx_fb_reg_base = (resource_size_t)ioremap(lcdc_regs->start, len);
	if (!da8xx_fb_reg_base) {
		ret = -EBUSY;
		goto err_request_mem;
	}

	fb_clk = clk_get(&device->dev, NULL);
	if (IS_ERR(fb_clk)) {
		dev_err(&device->dev, "Can not get device clock\n");
		ret = -ENODEV;
		goto err_ioremap;
	}

	pm_runtime_irq_safe(&device->dev);
	pm_runtime_enable(&device->dev);
	pm_runtime_get_sync(&device->dev);


	/* Determine LCD IP Version */
    /* (2) 从寄存器中读出lcdc的IP版本 */
	switch (lcdc_read(LCD_PID_REG)) {
	case 0x4C100102:
		lcd_revision = LCD_VERSION_1;
		break;
	case 0x4F200800:
	case 0x4F201000:
		lcd_revision = LCD_VERSION_2;
		break;
	default:
		dev_warn(&device->dev, "Unknown PID Reg value 0x%x, "
				"defaulting to LCD revision 1\n",
				lcdc_read(LCD_PID_REG));
		lcd_revision = LCD_VERSION_1;
		break;
	}

    /* (3) 根据LCD名称,查找到lcdc对应配置
        我们的LCD配置为"G070VW01",对应lcdc配置为:
        	[5] = {
        		.name = "G070VW01",
        		.width = 800,
        		.height = 480,
        		.hfp = 24,
        		.hbp = 96,
        		.hsw = 72,
        		.vfp = 3,
        		.vbp = 7,
        		.vsw = 10,
        		.pxl_clk = 33300000,
        		.invert_pxl_clk = 0,
        	},
     */
	for (i = 0, lcdc_info = known_lcd_panels;
		i < ARRAY_SIZE(known_lcd_panels);
		i++, lcdc_info++) {
		if (strcmp(fb_pdata->type, lcdc_info->name) == 0)
			break;
	}

	if (i == ARRAY_SIZE(known_lcd_panels)) {
		dev_err(&device->dev, "GLCD: No valid panel found\n");
		ret = -ENODEV;
		goto err_pm_runtime_disable;
	} else
		dev_info(&device->dev, "GLCD: Found %s panel\n",
					fb_pdata->type);

	lcd_cfg = (struct lcd_ctrl_config *)fb_pdata->controller_data;


    /* (4) 分配fb的控制数据结构 */
	da8xx_fb_info = framebuffer_alloc(sizeof(struct da8xx_fb_par),
					&device->dev);
	if (!da8xx_fb_info) {
		dev_dbg(&device->dev, "Memory allocation failed for fb_info\n");
		ret = -ENOMEM;
		goto err_pm_runtime_disable;
	}

	par = da8xx_fb_info->par;
	par->dev = &device->dev;
	par->lcdc_clk = fb_clk;
#ifdef CONFIG_CPU_FREQ
	par->lcd_fck_rate = clk_get_rate(fb_clk);
#endif
	par->pxl_clk = lcdc_info->pxl_clk;
	if (fb_pdata->panel_power_ctrl) {
		par->panel_power_ctrl = fb_pdata->panel_power_ctrl;
		par->panel_power_ctrl(1);
	}

	lcd_reset(par);

	/* allocate frame buffer */
    /* (5) 分配frame buffer内存空间:
        非常重要的步骤,计算参数如下:
            lcdc_info->width = 800      // 宽度像素点
            lcdc_info->height = 480     // 高度像素点
            lcd_cfg->bpp = 32           // 每个像素点颜色需要的bit
            LCD_NUM_BUFFERS = 2         // buffer数量
        所以我们framebuffer的最终大小为:800x480x(32/8)x2 = 3072000 bytes.
     */
	par->vram_size = lcdc_info->width * lcdc_info->height * lcd_cfg->bpp;
	ulcm = lcm((lcdc_info->width * lcd_cfg->bpp)/8, PAGE_SIZE);
	par->vram_size = roundup(par->vram_size/8, ulcm);
	par->vram_size = par->vram_size * LCD_NUM_BUFFERS;

	par->vram_virt = dma_alloc_coherent(NULL,
					    par->vram_size,
					    (resource_size_t *) &par->vram_phys,
					    GFP_KERNEL | GFP_DMA);
	if (!par->vram_virt) {
		dev_err(&device->dev,
			"GLCD: kmalloc for frame buffer failed\n");
		ret = -EINVAL;
		goto err_release_fb;
	}

    /* (5.1) framebuffer虚拟地址 */
	da8xx_fb_info->screen_base = (char __iomem *) par->vram_virt;
    /* (5.2) framebuffer物理地址 */
	da8xx_fb_fix.smem_start    = par->vram_phys;
	da8xx_fb_fix.smem_len      = par->vram_size;
	da8xx_fb_fix.line_length   = (lcdc_info->width * lcd_cfg->bpp) / 8;

	par->dma_start = par->vram_phys;
	par->dma_end   = par->dma_start + lcdc_info->height *
		da8xx_fb_fix.line_length - 1;

	/* allocate palette buffer */
    /* (6) 分配调色板需要的内存 */
	par->v_palette_base = dma_alloc_coherent(NULL,
					       PALETTE_SIZE,
					       (resource_size_t *)
					       &par->p_palette_base,
					       GFP_KERNEL | GFP_DMA);
	if (!par->v_palette_base) {
		dev_err(&device->dev,
			"GLCD: kmalloc for palette buffer failed\n");
		ret = -EINVAL;
		goto err_release_fb_mem;
	}
	memset(par->v_palette_base, 0, PALETTE_SIZE);

	par->irq = platform_get_irq(device, 0);
	if (par->irq < 0) {
		ret = -ENOENT;
		goto err_release_pl_mem;
	}

	/* Initialize par */
    /* (7) 初始化framebuffer需要的数据结构,并且注册 */
	da8xx_fb_info->var.bits_per_pixel = lcd_cfg->bpp;

	da8xx_fb_var.xres = lcdc_info->width;
	da8xx_fb_var.xres_virtual = lcdc_info->width;

	da8xx_fb_var.yres         = lcdc_info->height;
	da8xx_fb_var.yres_virtual = lcdc_info->height * LCD_NUM_BUFFERS;

	da8xx_fb_var.grayscale =
	    lcd_cfg->p_disp_panel->panel_shade == MONOCHROME ? 1 : 0;
	da8xx_fb_var.bits_per_pixel = lcd_cfg->bpp;

	da8xx_fb_var.hsync_len = lcdc_info->hsw;
	da8xx_fb_var.vsync_len = lcdc_info->vsw;
	da8xx_fb_var.pixclock = da8xxfb_pixel_clk_period(par);

	da8xx_fb_var.right_margin = lcdc_info->hfp;
	da8xx_fb_var.left_margin  = lcdc_info->hbp;
	da8xx_fb_var.lower_margin = lcdc_info->vfp;
	da8xx_fb_var.upper_margin = lcdc_info->vbp;

	/* Initialize fbinfo */
	da8xx_fb_info->flags = FBINFO_FLAG_DEFAULT;
	da8xx_fb_info->fix = da8xx_fb_fix;
	da8xx_fb_info->var = da8xx_fb_var;
	da8xx_fb_info->fbops = &da8xx_fb_ops;
	da8xx_fb_info->pseudo_palette = par->pseudo_palette;
	da8xx_fb_info->fix.visual = (da8xx_fb_info->var.bits_per_pixel <= 8) ?
				FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;

	ret = fb_alloc_cmap(&da8xx_fb_info->cmap, PALETTE_SIZE, 0);
	if (ret)
		goto err_release_pl_mem;
	da8xx_fb_info->cmap.len = par->palette_sz;

	par->lcdc_info = lcdc_info;
	par->lcd_cfg = lcd_cfg;

	/* initialize var_screeninfo */
	da8xx_fb_var.activate = FB_ACTIVATE_FORCE;
	fb_set_var(da8xx_fb_info, &da8xx_fb_var);

	dev_set_drvdata(&device->dev, da8xx_fb_info);

	/* initialize the vsync wait queue */
	init_waitqueue_head(&par->vsync_wait);
	par->vsync_timeout = HZ / 5;
	par->which_dma_channel_done = -1;
	spin_lock_init(&par->lock_for_chan_update);

	/* Register the Frame Buffer  */
    /* (7.1) 注册framebuffer */
	if (register_framebuffer(da8xx_fb_info) < 0) {
		dev_err(&device->dev,
			"GLCD: Frame Buffer Registration Failed!\n");
		ret = -EINVAL;
		goto err_dealloc_cmap;
	}

    /* (8) 注册lcdc的调节cpu频率的接口 */
#ifdef CONFIG_CPU_FREQ
	ret = lcd_da8xx_cpufreq_register(par);
	if (ret) {
		dev_err(&device->dev, "failed to register cpufreq\n");
		goto err_cpu_freq;
	}
#endif

	if (lcd_revision == LCD_VERSION_1)
		lcdc_irq_handler = lcdc_irq_handler_rev01;
	else
		lcdc_irq_handler = lcdc_irq_handler_rev02;

    /* (9) 注册中断处理函数 */
	ret = request_irq(par->irq, lcdc_irq_handler, 0,
			DRIVER_NAME, par);
	if (ret)
		goto irq_freq;
	return 0;

    ...
}

2.2.1 fbmem_init()

在分析register_framebuffer()之前,我们先看一下fb系统的初始化。特别简单:

kernel\drivers\video\fbmem.c:

static int __init
fbmem_init(void)
{
    /* (1) 创建proc文件系统'/proc/fb' */
	proc_create("fb", 0, NULL, &fb_proc_fops);

    /* (2) 注册fb系统的主字符设备 */
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

    /* (3) 注册sysfs文件系统'/sys/class/graphic' */
	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}

↓

核心的操作在这个主字符设备上面,因为用户程序都是通过/dev/fb*这样的从设备文件接口来操作framebuffer的。

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
	.llseek =	default_llseek,
};

这个字符设备定义了一套通用的对framebuffer操作的函数,如果framebuffer有自定义函数优先使用自定义的函数:

static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
    /* (1) 获取从设备号 */
	int fbidx = iminor(inode);
	struct fb_info *info;
	int res = 0;

    /* (2) 根据从设备号获取到fb的控制数据结构 */
	info = get_fb_info(fbidx);
	if (!info) {
		request_module("fb%d", fbidx);
		info = get_fb_info(fbidx);
		if (!info)
			return -ENODEV;
	}
	if (IS_ERR(info))
		return PTR_ERR(info);

	mutex_lock(&info->lock);
	if (!try_module_get(info->fbops->owner)) {
		res = -ENODEV;
		goto out;
	}
	file->private_data = info;

    /* (3) 如果有,调用fb自定义的open()函数 */
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
#ifdef CONFIG_FB_DEFERRED_IO
	if (info->fbdefio)
		fb_deferred_io_open(info, inode, file);
#endif
out:
	mutex_unlock(&info->lock);
	if (res)
		put_fb_info(info);
	return res;
}

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	unsigned long p = *ppos;
    /* (1) 获取到fb的控制数据结构 */
	struct fb_info *info = file_fb_info(file);
	u8 *buffer, *dst;
	u8 __iomem *src;
	int c, cnt = 0, err = 0;
	unsigned long total_size;

	if (!info || ! info->screen_base)
		return -ENODEV;

	if (info->state != FBINFO_STATE_RUNNING)
		return -EPERM;

    /* (2) 如果fb有自定义的read()函数,调用自定义的read()函数 */
	if (info->fbops->fb_read)
		return info->fbops->fb_read(info, buf, count, ppos);
	
    /* (3) 否则使用通用的read()函数
        da8xx-fb驱动没有自定义read()函数,是使用系统默认的read()函数。
     */
	total_size = info->screen_size;

	if (total_size == 0)
		total_size = info->fix.smem_len;

	if (p >= total_size)
		return 0;

	if (count >= total_size)
		count = total_size;

	if (count + p > total_size)
		count = total_size - p;

	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	src = (u8 __iomem *) (info->screen_base + p);

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

	while (count) {
		c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		dst = buffer;
		fb_memcpy_fromfb(dst, src, c);
		dst += c;
		src += c;

		if (copy_to_user(buf, buffer, c)) {
			err = -EFAULT;
			break;
		}
		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);

	return (err) ? err : cnt;
}

2.2.2 register_framebuffer()

那我们继续来分析register_framebuffer()做的事情:

register_framebuffer()

↓

static int do_register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;

	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

	do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
					 fb_is_primary_device(fb_info));

	if (num_registered_fb == FB_MAX)
		return -ENXIO;

	num_registered_fb++;

    /* (7.1.1) 分配一个从设备号 */
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	fb_info->node = i;
	atomic_set(&fb_info->count, 1);
	mutex_init(&fb_info->lock);
	mutex_init(&fb_info->mm_lock);

    /* (7.1.2) 创建'/sys/class/graphic/fb*' */
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
	if (IS_ERR(fb_info->dev)) {
		/* Not fatal */
		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info);

	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	fb_info->pixmap.offset = 0;

	if (!fb_info->pixmap.blit_x)
		fb_info->pixmap.blit_x = ~(u32)0;

	if (!fb_info->pixmap.blit_y)
		fb_info->pixmap.blit_y = ~(u32)0;

	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);

	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);

    /* (7.1.3) 根据从设备号注册fb控制结构 */
	registered_fb[i] = fb_info;

	event.info = fb_info;
	if (!lock_fb_info(fb_info))
		return -ENODEV;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	return 0;
}

创建了/sys/class/graphic/fb*以后,udev会自动创建/dev/fb*设备节点。

2.2.3 /dev/fb0 文件操作

用户态程序通过/dev/fb*来操作framebuffer。

open() → fb_open()

read() → fb_read()

write() → fb_write()

2.2.4 ‘/sys/class/graphics/fb0/blank’ fb notifier

在register_framebuffer()时会创建一个文件节点/sys/class/graphics/fb0/blank,操作这个节点会发出FB_BLANK相关的消息给需要接收的背光设备,来操作背光。

register_framebuffer() → do_register_framebuffer() → fb_init_device() → device_attrs[]

↓

static struct device_attribute device_attrs[] = {

	__ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),

};

↓

store_blank() → fb_blank() 

↓

int
fb_blank(struct fb_info *info, int blank)
{	
 	int ret = -EINVAL;

 	if (blank > FB_BLANK_POWERDOWN)
 		blank = FB_BLANK_POWERDOWN;

    /* (1) 调用fb驱动自定义函数来转换blank值 */
	if (info->fbops->fb_blank)
 		ret = info->fbops->fb_blank(blank, info);

 	if (!ret) {
		struct fb_event event;

		event.info = info;
		event.data = &blank;
        
        /* (2) 发出一个blank相关的notifier操作 */
		fb_notifier_call_chain(FB_EVENT_BLANK, &event);
	}

 	return ret;
}

info->fbops->fb_blank()调用到了da8xx-fb驱动的cfb_blank()函数:

static int cfb_blank(int blank, struct fb_info *info)
{
	struct da8xx_fb_par *par = info->par;
	int ret = 0;

	if (par->blank == blank)
		return 0;

	par->blank = blank;
	switch (blank) {
	case FB_BLANK_UNBLANK:
		if (par->panel_power_ctrl)
			par->panel_power_ctrl(1);

		lcd_enable_raster();
		break;
	case FB_BLANK_NORMAL:
	case FB_BLANK_VSYNC_SUSPEND:
	case FB_BLANK_HSYNC_SUSPEND:
	case FB_BLANK_POWERDOWN:
		if (par->panel_power_ctrl)
			par->panel_power_ctrl(0);

		lcd_disable_raster(WAIT_FOR_FRAME_DONE);
		break;
	default:
		ret = -EINVAL;
	}

	return ret;
}

2.3 Boot Logo

在frambuffer初始化完以后,可以显示初始logo:

fbcon_init() -> fbcon_prepare_logo() -> fb_prepare_logo() -> fb_find_logo():

const struct linux_logo * __init_refok fb_find_logo(int depth)
{

#ifdef CONFIG_LOGO_LINUX_CLUT224
		/* Generic Linux logo */
		logo = &logo_linux_clut224;
#endif

}

最终找到了logo_linux_clut224,对应kernel\drivers\video\logo\logo_linux_clut224.ppm

3. 背光驱动

可以使用两种方式调节背光:

Backlight is through eCAP0_in_PWM0_out pin, controls brightness via eCAP0 module. LCD EVM also has alternative backlight control via TLC59108 power control chip. This is via do not implement(DNI) R36 resistor on non-alpha boards, only populated in-case of non-availability of eCAP0_in_PWM0_out pin.

背光通过eCAP0_in_PWM0_out引脚,通过eCAP0模块控制亮度。 LCD EVM还可以通过TLC59108电源控制芯片来控制背光。 这是通过仅在eCAP0_in_PWM0_out引脚不可用的情况下在非Alpha板上通过(DNI)R36电阻来实现的。

3.1 eCAP0模块

3.1.1 Device

kernel\arch\arm\mach-omap2\board-am335xevm.c:

static struct platform_pwm_backlight_data am335x_backlight_data2 = {
	.pwm_id         = "ecap.2",
	.ch             = -1,
	.lth_brightness	= 21,
	.max_brightness = AM335X_BACKLIGHT_MAX_BRIGHTNESS,
	.dft_brightness = AM335X_BACKLIGHT_DEFAULT_BRIGHTNESS,
	.pwm_period_ns  = AM335X_PWM_PERIOD_NANO_SECONDS,
};

/* HX START */
static struct platform_device am335x_backlight = {
	.name           = "pwm-backlight",
	.id             = -1,
	.dev		= {
		.platform_data = &am335x_backlight_data2,
	},
};

static struct pwmss_platform_data  pwm_pdata[3] = {
	{
		.version = PWM_VERSION_1,
	},
	{
		.version = PWM_VERSION_1,
	},
	{
		.version = PWM_VERSION_1,
	},
};

static int __init backlight_init(void)
{
	int status = 0;

	if (backlight_enable) {
		int ecap_index = 0;

		switch (am335x_evm_get_id()) {
		case GEN_PURP_EVM:
		case GEN_PURP_DDR3_EVM:
			ecap_index = 0;
			break;
		case EVM_SK:

        /* 配置 */
		case AM_BOARD:
			/*
			 * Invert polarity of PWM wave from ECAP to handle
			 * backlight intensity to pwm brightness
			 */
             		ecap_index = 2;//hxtest1
			pwm_pdata[ecap_index].chan_attrib[0].inverse_pol = true;
			am335x_backlight.dev.platform_data =
				&am335x_backlight_data2;//hxtest1 2018/12/19
			break;
		default:
			pr_err("%s: Error on attempting to enable backlight,"
				" not supported\n", __func__);
			return -EINVAL;
		}

		am33xx_register_ecap(ecap_index, &pwm_pdata[ecap_index]);
		platform_device_register(&am335x_backlight);
	}
	return status;
}

3.1.2 Driver

kernel\drivers\video\backlight\pwm_bl.c:

static struct platform_driver pwm_backlight_driver = {
	.driver		= {
		.name	= "pwm-backlight",
		.owner	= THIS_MODULE,
	},
	.probe		= pwm_backlight_probe,
	.remove		= pwm_backlight_remove,
	.suspend	= pwm_backlight_suspend,
	.resume		= pwm_backlight_resume,
};

static int __init pwm_backlight_init(void)
{
	return platform_driver_register(&pwm_backlight_driver);
}

↓

pwm_backlight_probe()

↓

struct backlight_device *backlight_device_register(const char *name,
	struct device *parent, void *devdata, const struct backlight_ops *ops,
	const struct backlight_properties *props)
{
	struct backlight_device *new_bd;
	int rc;

	pr_debug("backlight_device_register: name=%s\n", name);

	new_bd = kzalloc(sizeof(struct backlight_device), GFP_KERNEL);
	if (!new_bd)
		return ERR_PTR(-ENOMEM);

	mutex_init(&new_bd->update_lock);
	mutex_init(&new_bd->ops_lock);

	new_bd->dev.class = backlight_class;
	new_bd->dev.parent = parent;
	new_bd->dev.release = bl_device_release;
	dev_set_name(&new_bd->dev, name);
	dev_set_drvdata(&new_bd->dev, devdata);

	/* Set default properties */
	if (props) {
		memcpy(&new_bd->props, props,
		       sizeof(struct backlight_properties));
		if (props->type <= 0 || props->type >= BACKLIGHT_TYPE_MAX) {
			WARN(1, "%s: invalid backlight type", name);
			new_bd->props.type = BACKLIGHT_RAW;
		}
	} else {
		new_bd->props.type = BACKLIGHT_RAW;
	}

    /* (1) 注册'/sys/class/backlight/pwm-backlight'节点 */
	rc = device_register(&new_bd->dev);
	if (rc) {
		kfree(new_bd);
		return ERR_PTR(rc);
	}

    /* (2) 注册一个fb的回调钩子函数来响应FB_EVENT_BLANK/FB_EVENT_CONBLANK事件 */
	rc = backlight_register_fb(new_bd);
	if (rc) {
		device_unregister(&new_bd->dev);
		return ERR_PTR(rc);
	}

	new_bd->ops = ops;

#ifdef CONFIG_PMAC_BACKLIGHT
	mutex_lock(&pmac_backlight_mutex);
	if (!pmac_backlight)
		pmac_backlight = new_bd;
	mutex_unlock(&pmac_backlight_mutex);
#endif

	return new_bd;
}

注册完成后,我们可以通过以下接口来控制背光:

root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness        
80
root@am335x-evm:~# echo 80 >  /sys/class/backlight/pwm-backlight/brightness 
root@am335x-evm:~# echo 10 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# echo 0 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness        
0
root@am335x-evm:~# 
root@am335x-evm:~# 
root@am335x-evm:~# echo 80 >  /sys/class/backlight/pwm-backlight/brightness 
root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness        
80
root@am335x-evm:~# 
root@am335x-evm:~# echo 40 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# echo 30 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# echo 10 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# echo 1 >  /sys/class/backlight/pwm-backlight/brightness  

3.2 PWM蜂鸣器

另外一个PWM接口被注册成了蜂鸣器,可以通过以下接口控制:

root@am335x-evm:/sys/class/backlight/pwm-buzzer# cat brightness 
0
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 10 > brightness 
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 0 > brightness  
root@am335x-evm:/sys/class/backlight/pwm-buzzer# 
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 20 > brightness  
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 0 > brightness  
root@am335x-evm:/sys/class/backlight/pwm-buzzer# 
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 100 > brightness  
root@am335x-evm:/sys/class/backlight/pwm-buzzer# cat brightness          
100
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 10 > brightness  
root@am335x-evm:/sys/class/backlight/pwm-buzzer# echo 0 > brightness         

4. FrameBuffer调试

4.1 背光

打开、关闭背光:

to unblank 打开背光:
$echo "0" > /sys/class/graphics/fb0/blank
to blank 关闭背光:
$echo "4" > /sys/class/graphics/fb0/blank

调节背光亮度:

root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness        
80
root@am335x-evm:~# echo 80 >  /sys/class/backlight/pwm-backlight/brightness 
root@am335x-evm:~# echo 10 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# echo 0 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness        
0
root@am335x-evm:~# 
root@am335x-evm:~# 
root@am335x-evm:~# echo 80 >  /sys/class/backlight/pwm-backlight/brightness 
root@am335x-evm:~# cat /sys/class/backlight/pwm-backlight/brightness        
80
root@am335x-evm:~# 
root@am335x-evm:~# echo 40 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# echo 30 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# echo 10 >  /sys/class/backlight/pwm-backlight/brightness  
root@am335x-evm:~# echo 1 >  /sys/class/backlight/pwm-backlight/brightness  

4.2 fb

读取fb配置信息:

root@am335x-evm:~# fbset -i

mode "800x480-77"
    # D: 38.401 MHz, H: 38.711 kHz, V: 77.421 Hz
    geometry 800 480 800 960 32
    timings 26041 96 24 7 3 72 10
    rgba 8/16,8/8,8/0,8/24
endmode

Frame buffer device information:
    Name        : DA8xx FB Drv
    Address     : 0x87400000
    Size        : 3072000
    Type        : PACKED PIXELS
    Visual      : TRUECOLOR
    XPanStep    : 0
    YPanStep    : 1
    YWrapStep   : 0
    LineLength  : 3200
    Accelerator : No
root@am335x-evm:~# 

读出fb原始内容:

root@am335x-evm:~# dd if=/dev/fb0 of=fb.cap 
6000+0 records in
6000+0 records out
root@am335x-evm:~# ls -l fb.cap 
-rw-r--r--    1 root     root       3072000 Jan  1 00:09 fb.cap

计算出每个像素点占用8字节:

800x480 = 384000
3072000/384000 = 8

fb测试程序:

#include <linux/fb.h>  
#include <stdio.h>  
#include<sys/types.h> 
#include<sys/stat.h> 
#include<fcntl.h>
#include <sys/mman.h>

int main()  
{  
        int fbfd = 0;  
        struct fb_var_screeninfo vinfo;  
        struct fb_fix_screeninfo finfo;  
        long int screensize = 0;  
        char * fbp = NULL;
        int i = 0;

        /*打开设备文件*/  
        fbfd = open("/dev/fb0", O_RDWR);  

        /*取得屏幕相关参数*/  
        ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);  
        ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);  
        /*计算屏幕缓冲区大小*/  
        screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;  

        printf("vinfo.xres = %d \n", vinfo.xres);
        printf("vinfo.yres = %d \n", vinfo.yres);
        printf("vinfo.bits_per_pixel = %d \n", vinfo.bits_per_pixel);
        printf("screensize = %d bytes\n", screensize);


        /*映射屏幕缓冲区到用户地址空间*/  
        fbp=(char*)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED, fbfd, 0);  
        /*下面可通过fbp指针读写缓冲区*/ 

        for(i=0; i<screensize;i+=4)
        {
            // Little endian 小端
            *fbp = 0xFF;        // Blue
            *(fbp+1) = 0xFF;    // Green
            *(fbp+2) = 0xFF;    // Red
            *(fbp+3) = 0xFF;    // Alpha 透明度?
            fbp+=4;
        }

        close(fbfd);
}
root@am335x-evm:~# /home/root/mmcblk0p1/fb_test 
vinfo.xres = 800 
vinfo.yres = 480 
vinfo.bits_per_pixel = 32 

fb_var_screeninfo、fb_fix_screeninfo两个结构体的定义:

//不可变信息,应用程序没法设置它,偶尔读出来查阅!
struct fb_fix_screeninfo { 
    char id[16]; /* identification string eg "TT Bui ltin" */
    //framebuffer在显存中的物理地址:
    unsigned long smem_start; /* Start of frame buffer mem */
    /* (physical address) */
    __u32 smem_len; /* Length of frame buffer mem */
    __u32 type; /* see FB_TYPE_* */
    __u32 type_aux; /* Interleave for interleaved Plane s */
    __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; /* length of a line in bytes */
    unsigned long mmio_start; /* Start of Memory Mapped I/O */
    /* (physical address) */
    __u32 mmio_len; /* Length of Memory Mapped I/O */
    __u32 accel; /* Indicate to driver which */
    /* specific chip/card we have */
    __u16 capabilities; /* see FB_CAP_* */
    __u16 reserved[2]; /* Reserved for future compatibilit y */
};

//可变信息,应用程序可以设置它,较多的可关注。
struct fb_var_screeninfo { 
    __u32 xres;//可视分辨率 /* visible resolution */
    __u32 yres;
    __u32 xres_virtual;//虚拟分辨率 /* virtual resolution */
    __u32 yres_virtual;
    __u32 xoffset;//参考点座标 /* offset from virtual to visible */
    __u32 yoffset; /* resolution */

    __u32 bits_per_pixel;//bpp /* guess what */
    __u32 grayscale;//灰度级图 /* 0 = color, 1 = grayscale, */
    /* >1 = FOURCC */
    struct fb_bitfield red;//描述红色/* bitfield in fb mem if true color, */
    struct fb_bitfield green;//描述绿色/* else only length is significant */
    struct fb_bitfield blue;//描述蓝色
    struct fb_bitfield transp;//描述透明度/* transparency */

    __u32 nonstd; /* != 0 Non standard pixel format */

    __u32 activate; /* see FB_ACTIVATE_* */

    __u32 height;//物理尺寸大小 /* height of picture in mm */
    __u32 width; /* width of picture in mm */

    __u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
    /* Timing: All values in pixclocks, except pixclock (of course) */
    __u32 pixclock;//像素时钟 /* pixel clock in ps (pico seconds) */
    __u32 left_margin;//初始化时序要用 /* time from sync to picture */
    __u32 right_margin; /* time from picture to sync */
    __u32 upper_margin; /* time from sync to picture */
    __u32 lower_margin;
    __u32 hsync_len; /* length of horizontal sync */
    __u32 vsync_len; /* length of vertical sync */
    __u32 sync; /* see FB_SYNC_* */
    __u32 vmode; /* see FB_VMODE_* */
    __u32 rotate; /* angle we rotate counter clockwise */
    __u32 colorspace; /* colorspace for FOURCC-based modes */
    __u32 reserved[4]; /* Reserved for future compatibility */
};

4.3 截屏

ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt argb  -s 800X480 -i fb.cap.robot -f image2 -vcodec png fb.cap.robot.png
ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt argb  -s 800X480 -i fb.cap.red -f image2 -vcodec png fb.cap.red.png
ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt argb  -s 800X480 -i fb.cap.green -f image2 -vcodec png fb.cap.green.png
ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt argb  -s 800X480 -i fb.cap.blue -f image2 -vcodec png fb.cap.blue.png

交叉编译zlib

tar xf zlib-1.2.11.tar.gz  && cd zlib-1.2.11
CC=arm-linux-gnueabihf-gcc ./configure --prefix=/home/myc/vx/qt_project/libpng_install
make && make install

交叉编译libpng

tar xf libpng-1.6.37.tar.gz && cd libpng-1.6.37
export CPPFLAGS="-I /home/myc/vx/qt_project/libpng_install/include"
export LDFLAGS="-L/home/myc/vx/qt_project/libpng_install/lib"
./configure CC=arm-linux-gnueabihf-gcc --prefix=/home/myc/vx/qt_project/libpng_install --host=arm-linux
make && make install

交叉编译截图软件:

cd Framebuffer_shot-master
arm-linux-gnueabihf-gcc famebuffer_shot_png.c -o famebuffer_shot_png -lpng -lz -I /home/myc/vx/qt_project/libpng_install/include -L/home/myc/vx/qt_project/libpng_install/lib

打包lib.tar:

cd /home/myc/vx/qt_project/libpng_install
tar -cvf lib.tar lib/

目标板:

cp  /home/root/mmcblk0p1/lib.tar /home/root/
cd /home/root/
tar -xvf lib.tar
export LD_LIBRARY_PATH=/home/root/lib

cd /home/root/mmcblk0p1/
./famebuffer_shot_png

// 生成screen.png截屏文件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章