帧缓冲(framebuffer)是 Linux 系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关心物理显示缓冲区的具体位置及存放方式,这些都由帧缓冲设备驱动本身来完成。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域写入颜色值,对应的颜色会自动在屏幕上显示。
帧缓冲设备最关键的一个数据结构体是(为了便于记忆,我们把它简称为“FBI”),FBI中包括了关于帧缓冲设备属性和操作的完整描述。
FBI 中记录了帧缓冲设备的全部信息,包括设备的设置参数、状态以及操作函数指针。每一个帧缓冲设备都必须对应一个 FBI。
struct fb_info {
int node;
int flags;
struct mutex lock; /* 用于 open/release/ioctl 的锁 */
struct fb_var_screeninfo var; /*可变参数 */
struct fb_fix_screeninfo fix; /*固定参数 */
struct fb_monspecs monspecs; /*显示器标准 */
struct work_struct queue; /* 帧缓冲事件队列 */
struct fb_pixmap pixmap; /* 图像硬件 mapper */
struct fb_pixmap sprite; /* 光标硬件 mapper */
struct fb_cmap cmap; /* 目前的颜色表*/
struct list_head modelist;
struct fb_videomode *mode; /* 目前的 video 模式 */
#ifdef CONFIG_FB_BACKLIGHT
/* 对应的背光设备 */
struct backlight_device *bl_dev;
/* 背光调整 */
struct mutex bl_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops; /* fb_ops,帧缓冲操作 */
struct device *device; /* 父设备 */
struct device *dev; /* fb 设备 */
int class_flag; /* 私有 sysfs 标志 */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* 图块 Blitting */
#endif
char _ _iomem *screen_base; /* 虚拟基地址 */
unsigned long screen_size; /* ioremapped 的虚拟内存大小 */
void *pseudo_palette; /* 伪 16 色颜色表 */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* 硬件状态,如挂起 */
void *fbcon_par;
void *par;
};
FBI 的成员变量 fbops 为指向底层操作的函数的指针,这些函数是需要驱动程序开发人员编写的。
struct fb_ops {
struct module *owner;
/* 打开/释放 */
int(*fb_open)(struct fb_info *info, int user);
int(*fb_release)(struct fb_info *info, int user);
/* 对于非线性布局的/常规内存映射无法工作的帧缓冲设备需要 */
ssize_t(*fb_read)(struct file *file, char _ _user *buf, size_t count,
loff_t*ppos);
ssize_t(*fb_write)(struct file *file, const char _ _user *buf, size_t count,
loff_t *ppos);
/* 检测可变参数,并调整到支持的值*/
int(*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
/* 根据 info->var 设置 video 模式 */
int(*fb_set_par)(struct fb_info *info);
/* 设置 color 寄存器 */
int(*fb_setcolreg)(unsigned regno, unsigned red, unsigned green, unsigned
blue, unsigned transp, struct fb_info *info);
/* 批量设置 color 寄存器,设置颜色表 */
int(*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/*显示空白 */
int(*fb_blank)(int blank, struct fb_info *info);
/* pan 显示 */
int(*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
/* 矩形填充 */
void(*fb_fillrect)(struct fb_info *info, const struct fb_fillrect *rect);
/* 数据复制 */
void(*fb_copyarea)(struct fb_info *info, const struct fb_copyarea *region);
/* 图形填充 */
void(*fb_imageblit)(struct fb_info *info, const struct fb_image *image);
/* 绘制光标 */
int(*fb_cursor)(struct fb_info *info, struct fb_cursor *cursor);
/* 旋转显示 */
void(*fb_rotate)(struct fb_info *info, int angle);
/* 等待 blit 空闲 (可选) */
int(*fb_sync)(struct fb_info *info);
/* fb 特定的 ioctl (可选) */
int(*fb_ioctl)(struct fb_info *info, unsigned int cmd, unsigned long arg);
/* 处理 32 位的 compat ioctl (可选) */
int(*fb_compat_ioctl)(struct fb_info *info, unsigned cmd, unsigned long arg);
/* fb 特定的 mmap */
int(*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* 保存目前的硬件状态 */
void(*fb_save_state)(struct fb_info *info);
/* 恢复被保存的硬件状态 */
void(*fb_restore_state)(struct fb_info *info);
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var);
};
FBI 的 fb_var_screeninfo 和 fb_fix_screeninfo 成员也是结构体,fb_var_screeninfo 记录用户可修改的显示控制器参数,包括屏幕分辨率和每个像素点的比特数。fb_var_screeninfo 中的 xres 定义屏幕一行有多少个点,yres 定义屏幕一列有多少个点,bits_per_pixel 定义每个点用多少个字节表示。而 fb_fix_screeninfo 中记录用户不能修改的显示控制器的参数,如屏幕缓冲区的物理地址、长度。当对帧缓冲设备进行映射操作的时候,就是从 fb_fix_screeninfo 中取得缓冲区物理地址的。上述数据成员都需要在驱动程序中初始化和设置。
struct fb_var_screeninfo {
/* 可见解析度 */
u32 xres;
u32 yres;
/* 虚拟解析度 */
u32 xres_virtual;
u32 yres_virtual;
/* 虚拟到可见之间的偏移 */
u32 xoffset;
u32 yoffset;
u32 bits_per_pixel; /* 每像素位数,BPP */
u32 grayscale; /非 0 时指灰度 */
/* fb 缓存的 R\G\B 位域 */
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp; /* 透明度 */
u32 nonstd; /* != 0 非标准像素格式 */
u32 activate;
u32 height; /*高度 */
u32 width; /*宽度 */
u32 accel_flags; /* 看 fb_info.flags */
/* 定时: 除了 pixclock 本身外,其他的都以像素时钟为单位 */
u32 pixclock; /* 像素时钟(皮秒) */
u32 left_margin; /* 行切换:从同步到绘图之间的延迟 */
u32 right_margin; /* 行切换:从绘图到同步之间的延迟 */
u32 upper_margin; /* 帧切换:从同步到绘图之间的延迟 */
u32 lower_margin; /* 帧切换:从绘图到同步之间的延迟 */
u32 hsync_len; /* 水平同步的长度 */
u32 vsync_len; /* 垂直同步的长度 */
u32 sync;
u32 vmode;
u32 rotate; /* 顺时钟旋转的角度 */
u32 reserved[5]; /* 保留 */
};
struct fb_fix_screeninfo {
char id[16]; /* 字符串形式的标识符 */
unsigned long smem_start; /* fb 缓冲内存的开始位置(物理地址) */
u32 smem_len; /* fb 缓冲的长度 */
u32 type; /* FB_TYPE_* */
u32 type_aux; /* Interleave */
u32 visual; /* FB_VISUAL_* */
u16 xpanstep; /* 如果没有硬件 panning ,赋 0 */
u16 ypanstep;
u16 ywrapstep;/
u32 line_length; /* 1 行的字节数 */
unsigned long mmio_start; /* 内存映射 I/O 的开始位置 */
u32 mmio_len; /* 内存映射 I/O 的长度 */
u32 accel;
u16 reserved[3]; /* 保留*/
};
作为一种字符设备,帧缓冲设备的文件操作结构体定义于/linux/drivers/vedio/fbmem.c文件中。
代码清单 帧缓冲设备文件操作结构体:
static struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.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
};
帧缓冲设备驱动的文件操作接口函数已经在 fbmem.c 中被统一实现,一般不需要由驱动工程师再编写。
Linux 内核提供了 register_framebuffer()和 unregister_framebuffer()函数分别注册和注销帧缓冲设备,这两个函数都接受 FBI 指针为参数,原型为:
int register_framebuffer(struct fb_info *fb_info);
int unregister_framebuffer(struct fb_info *fb_info);
对于 register_framebuffer()函数而言,如果注册的帧缓冲设备数超过了 FB_MAX(目前定义为32),则函数返回-ENXIO,注册成功则返回 0。
在帧缓冲设备驱动的模块加载函数中,应该完成如下 4 个工作:
在帧缓冲设备驱动的模块卸载函数中,应该完成相反的工作,包括释放 FBI 结构体内存、关闭 LCD、释放显示缓冲区以及注销帧缓冲设备。
由于LCD控制器经常被集成在SoC上作为一个独立的硬件模块而存在(成为platform_device),因此,LCD 驱动中也经常包含平台驱动,这样,在帧缓冲设备驱动的模块加载函数中完成的工作只是注册平台驱动,而初始化FBI结构体中的固定和可变参数、LCD 控制器硬件的初始化、申请帧缓冲设备的显示缓冲区空间和注册帧缓冲设备的工作则移交到平台驱动的探测函数中完成。 同样地,在使用平台驱动的情况下,释放 FBI 结构体内存、关闭 LCD、释放显示缓冲区以及注销帧缓冲设备的工作也移交到平台驱动的移除函数中完成。
帧缓冲设备驱动的模块加载/卸载及平台驱动的探测/移除函数的模板 :
/* 平台驱动结构体 */
static struct platform_driver xxxfb_driver = {
.probe = xxxfb_probe,
.remove = xxxfb_remove,
.suspend = xxxfb_suspend,
.resume = xxxfb_resume,
.driver = {
.name = "xxx-lcd", /* 驱动名 */
.owner = THIS_MODULE,
}
};
/* 平台驱动探测函数 */
static int _ _init xxxfb_probe(...)
{
struct fb_info *info;
/*分配 fb_info 结构体*/
info = framebuffer_alloc(...);
info->screen_base = framebuffer_virtual_memory;
info->var = xxxfb_var; /* 可变参数 */
info->fix = xxxfb_fix; /* 固定参数 */
/*分配显示缓冲区*/
alloc_dis_buffer(...);
/*初始化 LCD 控制器*/
lcd_init(...);
/*检查可变参数*/
xxxfb_check_var(&info->var, info);
/*注册 fb_info*/
if (register_framebuffer(info) < 0)
return - EINVAL;
return 0;
}
/* 平台驱动移除函数 */
static void _ _exit xxxfb_remove(...)
{
struct fb_info *info = dev_get_drv_data(dev);
if (info) {
unregister_framebuffer(info); /* 注销 fb_info */
dealloc_dis_buffer(...); /* 释放显示缓冲区 */
framebuffer_release(info); /* 注销 fb_info */
}
return 0;
}
/* 帧缓冲设备驱动模块加载与卸载函数 */
int __init xxxfb_init(void)
{
return platform_driver_register(&xxxfb_driver); /* 注册平台设备 */
}
static void __exit xxxfb_cleanup(void)
{
platform_driver_unregister(&xxxfb_driver); /* 注销平台设备 */
}
module_init(xxxfb_init);
module_exit(xxxfb_cleanup);
基于Smart210
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//lcd控制器寄存器
struct lcd_regs {
unsigned long VIDCON0;
unsigned long VIDCON1;
unsigned long VIDCON2;
unsigned long VIDCON3;
unsigned long VIDTCON0;
unsigned long VIDTCON1 ;
unsigned long VIDTCON2 ;
unsigned long VIDTCON3 ;
unsigned long WINCON0 ;
unsigned long WINCON1 ;
unsigned long WINCON2 ;
unsigned long WINCON3 ;
unsigned long WINCON4 ;
unsigned long SHADOWCON;
};
static unsigned long *VIDOSD0A;
static unsigned long *VIDOSD0B;
static unsigned long *VIDOSD0C;
static unsigned long *VIDW00ADD0B0;
static unsigned long *VIDW00ADD1B0;
static struct lcd_regs *lcd_reg;
static unsigned long *DISPLAY_CONTROL;
/* LCD参数 */
#define VSPW 9
#define VBPD 13
#define LINEVAL 479
#define VFPD 21
#define HSPW 19
#define HBPD 25
#define HOZVAL 799
#define HFPD 209
#define LINEVAL 479
#define HOZVAL 799
#define LeftTopX_F 0
#define LeftTopY_F 0
#define RightBotX_F 799
#define RightBotY_F 479
/* 用于LCD的GPIO */
static unsigned long *gpf0con;
static unsigned long *gpf1con;
static unsigned long *gpf2con;
static unsigned long *gpf3con;
static unsigned long *gpd0con;
static unsigned long *gpd0dat;
static struct fb_info *lcd_info;
static unsigned int pseudo_palette[16];
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
static int smart210_lcdfb_setcolreg(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp,
struct fb_info *info)
{
unsigned int val;
if (regno > 16)
return 1;
/* 用red,green,blue三原色构造出val */
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
//((u32 *)(info->pseudo_palette))[regno] = val;
pseudo_palette[regno] = val;
return 0;
}
static struct fb_ops smart210_lcd_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = smart210_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static int __init my_lcd_init(void)
{
struct clk *lcd_clk;
/*分配一个fb_info的结构体*/
lcd_info = framebuffer_alloc(0 ,NULL);
if(IS_ERR(lcd_info)) {
printk(KERN_ALERT"framebuffer_alloc error\n");
return -ENOMEM;
}
/*设置*/
/*配置fix固定参数*/
strcpy(lcd_info->fix.id, "Smart210_lcd");
//lcd_info->fix.smem_start
lcd_info->fix.smem_len = 800 * 480 * 4; /*7寸屏,rgb 8:8:8*/
lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;
lcd_info->fix.visual = FB_VISUAL_TRUECOLOR; //真彩
lcd_info->fix.line_length = 800 * 4;
/*配置可变参数var*/
lcd_info->var.xres = 800;
lcd_info->var.yres = 480;
lcd_info->var.xres_virtual = 800;
lcd_info->var.yres_virtual = 480;
lcd_info->var.xoffset = 0;
lcd_info->var.yoffset = 0;
lcd_info->var.bits_per_pixel = 32; //每个像素多少位 32位
lcd_info->var.grayscale = 0; //灰度级设为0
lcd_info->var.red.length = 8;
lcd_info->var.red.offset = 16;
lcd_info->var.green.length = 8;
lcd_info->var.green.offset = 8;
lcd_info->var.blue.length = 8;
lcd_info->var.blue.offset = 0;
lcd_info->var.nonstd = 0; //标准像素
lcd_info->var.activate = FB_ACTIVATE_NOW;
/*配置操作函数*/
lcd_info->fbops = &smart210_lcd_ops;
//lcd_info->screen_base = ; /*虚拟基地址*/
lcd_info->screen_size = 800 * 480 * 4;
lcd_info->pseudo_palette = pseudo_palette; //调色板
/*硬件相关的操作*/
gpf0con = ioremap(0xE0200120, 4);
gpf1con = ioremap(0xE0200140, 4);
gpf2con = ioremap(0xE0200160, 4);
gpf3con = ioremap(0xE0200180, 4);
gpd0con = ioremap(0xE02000A0, 4);
gpd0dat = ioremap(0xE02000A4, 4);
*gpf0con = 0x22222222;
*gpf1con = 0x22222222;
*gpf2con = 0x22222222;
*gpf3con = 0x22222222;
*gpd0con |= 1<<4;
*gpd0dat |= 1<<1;
lcd_clk = clk_get(NULL, "lcd");
if(IS_ERR(lcd_clk)) {
printk(KERN_ALERT"clk_get error\n");
return -EINVAL;
}
clk_enable(lcd_clk);
DISPLAY_CONTROL = ioremap( 0xE0107008, 4);
lcd_reg = ioremap(0xF8000000, sizeof(struct lcd_regs));
VIDOSD0A = ioremap(0xF8000040, 4);
VIDOSD0B = ioremap(0xF8000044, 4);
VIDOSD0C = ioremap(0xF8000048, 4);
VIDW00ADD0B0 = ioremap(0xF80000A0 , 4);
VIDW00ADD1B0 = ioremap(0xF80000D0 , 4);
*DISPLAY_CONTROL |= (2 << 0); //显示路径,必须
/*
*vidcon0[28:26] 000 = WB interface and RGB interface
*vidcon0[18] 0
*vidcon0[13:6] 0x4 5分频
*vidcon0[4] 1 = Divided by CLKVAL_F
*vidcon0[2] 0 = HCLK 166.75 MHz
*vidcon0[1]:ENVID 0 = Disables the video output and display control signal
*vidcon0[0]:ENVID_F 0 = Disables the video output and display control signal.
*NOTE: Display On: ENVID and ENVID_F are set to “1”.
Direct Off: ENVID and ENVID_F are set to “0” simultaneously.
Per Frame Off: ENVID_F is set to “0” and ENVID is set to “1”.
*/
lcd_reg->VIDCON0 &= ~((7 << 26) | (1 << 18) | (0xff << 6) | (1 << 2));
lcd_reg->VIDCON0 |= (0x4 << 6) | (1 << 4);
/*
*vidcon1[6] IHSYNC 1 = 反转 根据芯片手册和lcd手册
*vidcon1[5] IVSYNC 1 = 反转
*/
lcd_reg->VIDCON1 &= ~(1<<7); /* 在vclk的下降沿获取数据 */
lcd_reg->VIDCON1 |= (1 << 6) | (1 << 5);
/* 设置时序(需要修改) */
lcd_reg->VIDTCON0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0);
lcd_reg->VIDTCON1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0);
/*设置水平大小,垂直大小*/
lcd_reg->VIDTCON2 |= LINEVAL << 11;
lcd_reg->VIDTCON2 |= HOZVAL << 0;
/*
*WSWP_F [15] :1 = Swap Enable(为什么要使能),,能够解决掉重影问题
*BPPMODE_F [5:2] 0xb = 24bpp
*ENWIN_F [0] 1 Enables/ disables video output and logic immediately.
*/
lcd_reg->WINCON0 |= 1 << 15;
lcd_reg->WINCON0 &= ~(0xf << 2);
lcd_reg->WINCON0 |= 0xb << 2 | 1 << 0;
/*窗口0 指定左上方,右下方像素的水平屏幕坐标*/
*VIDOSD0A |= (LeftTopX_F << 11) | (LeftTopY_F << 0);
*VIDOSD0B |= (RightBotX_F << 11) | (RightBotX_F << 0);
*VIDOSD0C |= ((LINEVAL + 1) * (HOZVAL + 1));
/*分配显存*/
lcd_info->screen_base = dma_alloc_writecombine(NULL, lcd_info->fix.smem_len, (dma_addr_t *)&(lcd_info->fix.smem_start), GFP_KERNEL);
*VIDW00ADD0B0 = lcd_info->fix.smem_start; //显存的起始地址
*VIDW00ADD1B0 = lcd_info->fix.smem_start + lcd_info->fix.smem_len;//显存的结束地址
lcd_reg->SHADOWCON |= 0x1; //我们使用的是 Window0,所有需要使能 DMA 通道 0
/* LCD控制器开启 */
lcd_reg->VIDCON0 |= 0x3;
lcd_reg->WINCON0 |= 0x1;
/*注册*/
register_framebuffer(lcd_info);
return 0;
}
static void __exit my_lcd_exit(void)
{
unregister_framebuffer(lcd_info);
dma_free_writecombine(NULL, lcd_info->fix.smem_len, &(lcd_info->fix.smem_start), GFP_KERNEL);
iounmap(gpf0con);
iounmap(gpf1con);
iounmap(gpf2con);
iounmap(gpf3con);
iounmap(gpd0con);
iounmap(gpd0dat);
iounmap(lcd_reg);
iounmap(VIDOSD0A);
iounmap(VIDOSD0B);
iounmap(VIDOSD0C);
iounmap(VIDW00ADD0B0);
iounmap(VIDW00ADD1B0);
framebuffer_release(lcd_info);
}
MODULE_LICENSE("GPL");
module_init(my_lcd_init);
module_exit(my_lcd_exit);
帧缓冲设备是一种典型的字符设备,它统一了显存,将显示缓冲区直接映射到用户空间。帧缓冲设备驱动 file_operations 中 VFS 接口函数由 fbmem.c 文件统一实现。这样,驱动工程师的工作重点将是实现针对特定设备 fb_info 中的 fb_ops 的成员函数,另外,理解并能灵活地修改fb_info 中的 var 和 fix 参数非常关键。fb_info 中的 var 参数直接和 LCD 控制器的硬件设置以及LCD 屏幕对应。 在用户空间,应用程序直接按照预先设置的 R、G、B 位数和偏移写经过 mmap()映射后的显示缓冲区就可实现图形的显示,省去了内存从用户空间到内核空间的复制过程。
s5pv210 lcd裸机分析网址:http://blog.csdn.net/a158337/article/details/39802159
关注公众号"小败日记",搬砖过程遇到的问题,大家一起探讨,资源共享