http://www.cnblogs.com/lifexy/p/7603327.html
如果我们的系统用GUI(图形界面接口),这是LCD设备驱动程序就应该编写成frambuffer接口,而不是像之前那样只编写操作底层的LCD控制器接口。
什么是frambuffer设备?
frambuffer设备层是对图像设备的一种抽象,它代表了视频硬件的帧缓存,使得应用程序通过定义好的接口就可以访问硬件。所以应用程序不需要考虑底层的(寄存器级)的操作。应用程序对设备文件的访问一般在/dev目录,如/dev/fb*。
内核的frambuffer在drivers/video/fbmem.c(fb:frame buffer)
1、我们进入fbmem.c找到它的入口函数:
fbmem_init(void) { create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL); if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) //(1)创建字符设备 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; }
(1)创建字符设备"fb",FB_MAJOR=29,主设备号为29,使用命令cat /proc/devices找到这个字符设备
和我们之前的驱动程序一样,但是没有创建设备节点,为什么?
因为需要注册了LCD驱动后,才会有设备节点,所以这里的代码没有,后面分析哪里有。
2、我们来看看注册的file_operations结构体fb_ops的.open函数和.read函数,应用层是如何打开驱动,读取驱动数据
2.1 fb_open函数如下:
fb_open(struct inode *inode, struct file *file) { int fbidx = iminor(inode); //获取设备节点的次设备号 struct fb_info *info; //定义fb_info结构体 int res = 0; if (fbidx >= FB_MAX) return -ENODEV; #ifdef CONFIG_KMOD if (!(info = registered_fb[fbidx])) //(1)获取此设备的LCD驱动信息 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) { //调用register_fb[fbidx]->fbops->fb_open res = info->fbops->fb_open(info,1); if (res) module_put(info->fbops->owner); } return res; }
(1)register_fb[fbidx]这个数组也是fb_info结构体,其中fbidx等于次设备号id,显然这个数组就是保存我们各个LCD驱动的信息。
2.2 fb_read函数如下:
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]; //获取设备号的lcd驱动信息 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) return info->fbops->fb_read(info, buf, count, ppos); 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); //获得显存物理地址 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++); //读取显存每个像素点数据,放到dst地址上 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; }
从.open和.write函数中可以发现,都依赖于fb_info帧缓冲信息结构体,它从registed_fb[index]数组中得到,这个数组保存我们各个lcd驱动的信息。
3、我们来找找这个数组在哪里被注册,位于register_frambuffer():
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; //创建设备节点,名称为fbi,主设备号为29,次设备号为i fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), "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); registered_fb[i] = fb_info; event.info = fb_info; fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); return 0; }
得出这个register_frambuffer()除了注册fb_info,还创建了设备节点。
所以要注册驱动时就调用这个,如下图所示
4、我们来看看/dirvers/video/s3c2410fb.c中又是怎么实现驱动的
4.1 先找到入口和出口函数:
int __devinit s3c2410fb_init(void) { return platform_driver_register(&s3c2410fb_driver); } static void __exit s3c2410fb_cleanup(void) { platform_driver_unregister(&s3c2410fb_driver); }
发现是注册、注销设备平台drv
4.2 来查看s3c2410fb_driver 如何定义的
static struct platform_driver s3c2410fb_driver = { .probe = s3c2410fb_probe, .remove = s3c2410fb_remove, .suspend = s3c2410fb_suspend, .resume = s3c2410fb_resume, .driver = { .name = "s3c2410-lcd", .owner = THIS_MODULE, }, };
和我们上节分析的platform机制一样,当与设备匹配成功,就会进入probe函数,初始化驱动设备。
4.3 来看看.probe函数,如何实现驱动的
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; //获取LCD设备信息(长度、类型等) 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); if (irq < 0) { dev_err(&pdev->dev, "no irq for device\n"); return -ENOENT; } fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);//1、分配一个fb_info结构体 if (!fbinfo) { return -ENOMEM; } //2、设置fb_info 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; 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; 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"); //3、硬件相关的操作,设置中断,LCD时钟频率,显存地址,配置银奖 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); //4、注册一个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; }
4.4 显然要写个LCD驱动程序,需要以下4步:
1)分配一个fb_info结构体:framebuffer_alloc();
2)设置fb_info;
3)硬件相关操作(设置中断,LCD时钟频率,显存地址,配置引脚… …)
4)注册fb_info:register_frambuffer()