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截屏文件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章