文章目錄
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 = ␣
/* (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截屏文件