文章目录
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截屏文件