fbmem.c 是内核自带的 LCD 驱动程序。它基于分层的思想,抽出了共性的内容,向上为APP提供统一的接口。

02_016register_framebuffer2.png
fbmem.c 分析
static int __init
fbmem_init(void)
{
create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
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;
}
static const 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
};
fb_open
static int
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);//得到这个设备节点的“次设备号”
struct fb_info *info; //帧缓冲区结构体
int res = 0;
if (fbidx >= FB_MAX)
return -ENODEV;
#ifdef CONFIG_KMOD
if (!(info = registered_fb[fbidx]))//假设“次设备号”为 0. 即从这个 registered_fb[]数组里得到“以次设备号为 0 为下标”的一项。
try_to_load(fbidx);
#endif /* CONFIG_KMOD */
if (!(info = registered_fb[fbidx]))
return -ENODEV;
if (!try_module_get(info->fbops->owner))
return -ENODEV;
file->private_data = info;
if (info->fbops->fb_open) {//若这个 info = registered_fd[0] 的“fbops”有“fb_open”函数时就调用
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
fb_read
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];//以次设备号为下标从 registered_fd 数组中得一项赋给"info"结构
u32 *buffer, *dst;
u32 __iomem *src;
int c, i, cnt = 0, err = 0;
unsigned long total_size;
if (!info || ! info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
if (info->fbops->fb_read)// //若这个 info 数组项中提供了“fbops”结构的“fb_read”读函数时:就调用此读函数
return info->fbops->fb_read(info, buf, count, ppos);
//若没有提供"info"项,往下执行
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 = (u32 __iomem *) (info->screen_base + p);//screen_base 是指显存的基地址。这里是读源 src 等于显存的基地址加上某个偏移值
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
dst = buffer;
for (i = c >> 2; i--; )
*dst++ = fb_readl(src++);//读源(从显存基地址+P 偏移)那里读到一个数据放到目标“*dst++”里。dst 是 buffer,buffer 是 kmalloc()上面分配的空间
if (c & 3) {
u8 *dst8 = (u8 *) dst;
u8 __iomem *src8 = (u8 __iomem *) src;
for (i = c & 3; i--;)
*dst8++ = fb_readb(src8++);
src = (u32 __iomem *) src8;
}
if (copy_to_user(buf, buffer, c)) {//把数据拷贝到用户空间
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (err) ? err : cnt;
}
Fbmem.c 提供的都是抽象出来的东西。最终都得依赖这个“registered_fb”数组里的“fb_info”结构体。registered_fb 在下面函数中被赋值:
int
register_framebuffer(struct fb_info *fb_info)
{。。。。。。
registered_fb[i] = fb_info;
。。。。。。
}
register_framebuffer
/**
* register_framebuffer - registers a frame buffer device
* @fb_info: frame buffer info structure
*
* Registers a frame buffer device @fb_info.
*
* Returns negative errno on error, or zero for success.
*
*/
int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
if (num_registered_fb == FB_MAX)
return -ENXIO;
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])//先找出一个空项
break;
fb_info->node = i;
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), "fb%d", i);
/*在“fd_class”类下面创建设备。只有真正有硬件设备时才有必要在这个
类下去创建设备。这样 mdev 或 udev 才能去自动创建设备节点。Fbmem.c 只是
抽象出来的 LCD 驱动框架程序,并不能支持具体的驱动。它需要依赖底层的某
个驱动程序给它注册一个“fb_info”结构体(由“register_framebuffer()
来注册”)*/
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);
registered_fb[i] = fb_info;
event.info = fb_info;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
return 0;
}
内核中有各种 具体的LCD 设备驱动程序会调用这个“register_framebuffer()”(如 6832fb.c、amifb.c、atmel_lcdfb.c、还有 2410 的如 s3fb.c、s3c2410fb.c 等)。
来看看inux/drivers/video/s3c2410fb.c
int __devinit s3c2410fb_init(void)
{
return platform_driver_register(&s3c2410fb_driver);//注册一个平台驱动(总线-设备-驱动模型,关心.probe 函数)
}
static struct platform_driver s3c2410fb_driver = {
.probe = s3c2410fb_probe,//若内核中有同名"s3c2410-lcd"的平台设备就调用"s3c2410fb_probe"函数
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
};
static int __init s3c2410fb_probe(struct platform_device *pdev)
{
struct s3c2410fb_info *info;
struct fb_info *fbinfo;
struct s3c2410fb_hw *mregs;
int ret;
int irq;
int i;
u32 lcdcon1;
mach_info = pdev->dev.platform_data;//根据pdev获得"mach_info"信息
if (mach_info == NULL) {
dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
mregs = &mach_info->regs;
irq = platform_get_irq(pdev, 0);//根据pdev获得"irq"中断信息
if (irq < 0) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
}
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);//分配一个"fb_info"结构。接着就开始设置这个 fb_info 结构体
if (!fbinfo) {
return -ENOMEM;
}
//硬件相关的操作
info = fbinfo->par;
info->fb = fbinfo;
info->dev = &pdev->dev;
platform_set_drvdata(pdev, fbinfo);
dprintk("devinit\n");
strcpy(fbinfo->fix.id, driver_name);
memcpy(&info->regs, &mach_info->regs, sizeof(info->regs));
/* Stop the video and unset ENVID if set */
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
lcdcon1 = readl(S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
info->mach_info = pdev->dev.platform_data;
//framebuffer的固定信息
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
fbinfo->fix.accel = FB_ACCEL_NONE;
//framebuffer的可变信息
fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.height = mach_info->height;
fbinfo->var.width = mach_info->width;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal;
fbinfo->var.xres = mach_info->xres.defval;
fbinfo->var.xres_virtual = mach_info->xres.defval;
fbinfo->var.yres = mach_info->yres.defval;
fbinfo->var.yres_virtual = mach_info->yres.defval;
fbinfo->var.bits_per_pixel = mach_info->bpp.defval;
fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
fbinfo->var.transp.length = 0;
fbinfo->fix.smem_len = mach_info->xres.max *
mach_info->yres.max *
mach_info->bpp.max / 8;
for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
ret = -EBUSY;
goto dealloc_fb;
}
dprintk("got LCD region\n");
ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_mem;
}
info->clk = clk_get(NULL, "lcd");
if (!info->clk || IS_ERR(info->clk)) {
printk(KERN_ERR "failed to get lcd clock source\n");
ret = -ENOENT;
goto release_irq;
}
clk_enable(info->clk);
dprintk("got and enabled clock\n");
msleep(1);
/* Initialize video memory */
ret = s3c2410fb_map_video_memory(info);
if (ret) {
printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
dprintk("got video memory\n");
ret = s3c2410fb_init_registers(info);
ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);
ret = register_framebuffer(fbinfo);//向fbmem.c注册fb_info结构体
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
goto free_video_memory;
}
/* create device files */
device_create_file(&pdev->dev, &dev_attr_debug);
printk(KERN_INFO "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);
return 0;
free_video_memory:
s3c2410fb_unmap_video_memory(info);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
release_irq:
free_irq(irq,info);
release_mem:
release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD);
dealloc_fb:
framebuffer_release(fbinfo);
return ret;
}
从fbmem.c 的接口“.ioctl”中可以得到LCD 的分辨率等信息。
static int
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_con2fbmap con2fb;
struct fb_cmap_user cmap;
struct fb_event event;
void __user *argp = (void __user *)arg;
int i;
if (!fb)
return -ENODEV;
switch (cmd) {
case FBIOGET_VSCREENINFO://GET 获得.V(var)可变的.SCREEN 屏幕.INFO 信息.
return copy_to_user(argp, &info->var,
sizeof(var)) ? -EFAULT : 0;// 将这个 info 结构中的 var 成员拷贝回用户空间
case FBIOPUT_VSCREENINFO:
。。。。。。
}
//看 info->var 这个成员中有什么内容
struct fb_var_screeninfo {
__u32 xres; /* visible resolution x,y方向的分辨率 */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what */
__u32 grayscale; /* != 0 Graylevels instead of colors */
......
内核提供的 LCD 驱动“s3c2410fb.c”:
int __devinit s3c2410fb_init(void)
-->platform_driver_register(&s3c2410fb_driver);
注册了一个“s3c2410fb_driver”平台驱动。
.name = "s3c2410-lcd", 当内核中有同名的设备时,就调用:.probe 函数。
.probe = s3c2410fb_probe,
int __init s3c2410fb_probe(struct platform_device *pdev)
根据平台设备的一些信息。来设置这个 LCD 控制器。拆分成两部分,一部分是比较稳定的代码(软件部分),另一部分是“硬件相关的代码”。
内核这个 LCD 的硬件相关的代码在:linux/arch/arm/plat-s3c24xx/devs.c
//里面有一个“平台设备”:
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)//设置平台设备里面的“私有数据”
{
struct s3c2410fb_mach_info *npd;
npd = kmalloc(sizeof(*npd), GFP_KERNEL);
if (npd) {
memcpy(npd, pd, sizeof(*npd));
s3c_device_lcd.dev.platform_data = npd;
} else {
printk(KERN_ERR "no memory for LCD platform data\n");
}
}
查看这个函数“__init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)”设置的地方:
搜索内核:
---- s3c24xx_fb_set_platdata Matches (10 in 8 files) ----
Devs.c (arch\arm\plat-s3c24xx):void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
Fb.h (include\asm-arm\arch-s3c2410):extern void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *);
Mach-amlm5900.c (arch\arm\mach-s3c2410): s3c24xx_fb_set_platdata(&amlm5900_lcd_info);
Mach-bast.c (arch\arm\mach-s3c2410): s3c24xx_fb_set_platdata(&bast_lcd_info);
Mach-h1940.c (arch\arm\mach-s3c2410): s3c24xx_fb_set_platdata(&h1940_lcdcfg);
Mach-qt2410.c (arch\arm\mach-s3c2410): s3c24xx_fb_set_platdata(&qt2410_prodlcd_cfg);
Mach-qt2410.c (arch\arm\mach-s3c2410): s3c24xx_fb_set_platdata(&qt2410_biglcd_cfg);
Mach-qt2410.c (arch\arm\mach-s3c2410): s3c24xx_fb_set_platdata(&qt2410_lcd_cfg);
Mach-rx3715.c (arch\arm\mach-s3c2440): s3c24xx_fb_set_platdata(&rx3715_lcdcfg);
Mach-smdk2440.c (arch\arm\mach-s3c2440): s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
如“linux/arch/arm/mach-s3c2440/mach-smdk2440.c”中:
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
//平台私有数据里有“smdk2440_lcd_cfg”相关寄存器的设置
/* LCD driver info */
static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
.regs = {
.lcdcon1 = S3C2410_LCDCON1_TFT16BPP |
S3C2410_LCDCON1_TFT |
S3C2410_LCDCON1_CLKVAL(0x04),
.lcdcon2 = S3C2410_LCDCON2_VBPD(7) |
S3C2410_LCDCON2_LINEVAL(319) |
S3C2410_LCDCON2_VFPD(6) |
S3C2410_LCDCON2_VSPW(3),
.lcdcon3 = S3C2410_LCDCON3_HBPD(19) |
S3C2410_LCDCON3_HOZVAL(239) |
S3C2410_LCDCON3_HFPD(7),
.lcdcon4 = S3C2410_LCDCON4_MVAL(0) |
S3C2410_LCDCON4_HSPW(3),
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
},
#if 0
/* currently setup by downloader */
.gpccon = 0xaa940659,
.gpccon_mask = 0xffffffff,
.gpcup = 0x0000ffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaa84aaa0,
.gpdcon_mask = 0xffffffff,
.gpdup = 0x0000faff,
.gpdup_mask = 0xffffffff,
#endif
.lpcsel = ((0xCE6) & ~7) | 1<<4,
.type = S3C2410_LCDCON1_TFT16BPP,
.width = 240,
.height = 320,
.xres = {
.min = 240,
.max = 240,
.defval = 240,
},
.yres = {
.min = 320,
.max = 320,
.defval = 320,
},
.bpp = {
.min = 16,
.max = 16,
.defval = 16,
},
};