首先我们来回归下怎么写一个字符设备驱动程序,下面以linux设备中的混杂设备为例
- static int __init misc_init(void)
- {
- int err;
- #ifdef CONFIG_PROC_FS
- proc_create("misc", 0, NULL, &misc_proc_fops);
- #endif
- misc_class = class_create(THIS_MODULE, "misc");
- err = PTR_ERR(misc_class);
- if (IS_ERR(misc_class))
- goto fail_remove;
- err = -EIO;
- if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
- goto fail_printk;
- misc_class->devnode = misc_devnode;
- return 0;
- fail_printk:
- printk("unable to get major %d for misc devices\n", MISC_MAJOR);
- class_destroy(misc_class);
- fail_remove:
- remove_proc_entry("misc", NULL);
- return err;
- }
上面的主要步骤为申请一个主次设备号,构建一个file_opertiaon,并用register_chrdev注册,构建类,并可以在类下创建设备节点。现在来看看内核是怎么实现lcd驱动程序呢?
- static int __init
- fbmem_init(void)
- {
- proc_create("fb", 0, NULL, &fb_proc_fops);//与proc文件系统有关
- 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;
- }
从上面也可以看出,内核对于lcd驱动程序也是满足一个主设备号为29的字符设备,那么用户空间访问通过fb_fops结构体的成员函数会操作到LCD控制器硬件寄存器。下面来看看linux帧缓冲设备驱动的主要结构图
framebuffer机制模仿显卡的功能,将显卡硬件结构抽象为一系列的数据结构,可以通过framebuffer的读写直接对显存进行操作。framebuffer一个字符设备,主设备号,对应于/dev/fb0...32的设备文件。对于用户程序而言,与字符设备并没有什么区别,用户具有把其看成一块内存,既可以读也可以写。那么下面来看看
在这个图中占主导地位的fb_info结构体
- struct fb_info {
- atomic_t count;
- int node; //子设备号
- int flags;
- struct mutex lock; //互斥锁
- struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
- 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; /* mode list */
- struct fb_videomode *mode; //当前的视频模式
- #ifdef CONFIG_FB_BACKLIGHT
- /* assigned backlight device *//支持lcd背光的设置
- /* set before framebuffer registration,
- remove after unregister */
- struct backlight_device *bl_dev;
- /* Backlight level curve */
- struct mutex bl_curve_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; //帧缓冲操作函数集
- struct device *device; //父设备
- struct device *dev; /* This is this fb device */
- int class_flag; /* private sysfs flags */
- #ifdef CONFIG_FB_TILEBLITTING
- struct fb_tile_ops *tileops; /* Tile Blitting */
- #endif
- char __iomem *screen_base; //虚拟基地址
- unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */
- void *pseudo_palette; /* Fake palette of 16 colors */
- #define FBINFO_STATE_RUNNING 0
- #define FBINFO_STATE_SUSPENDED 1
- u32 state; /* Hardware state i.e suspend */
- void *fbcon_par; /* fbcon use-only private area */
- /* From here on everything is device dependent */
- void *par;
- /* we need the PCI or similar aperture base/size not
- smem_start/size as smem_start may just be an object
- allocated inside the aperture so may not actually overlap */
- struct apertures_struct {
- unsigned int count;
- struct aperture {
- resource_size_t base;
- resource_size_t size;
- } ranges[0];
- } *apertures;
- };
fb_ops结构体用来实现对帧缓冲的操作,这些函数需要我们自行编写的。
- struct fb_ops {
- /* open/release and usage marking */
- struct module *owner;
- int (*fb_open)(struct fb_info *info, int user);
- int (*fb_release)(struct fb_info *info, int user);//打开和释放
- /* For framebuffers with strange non linear layouts or that do not
- * work with normal memory mapped access
- */
- ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
- size_t count, loff_t *ppos);
- ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
- size_t count, loff_t *ppos);
- /* checks var and eventually tweaks it to something supported,
- * DO NOT MODIFY PAR *///检测可变参数,并调整到支持的值
- int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
- /* set the video mode according to info->var */
- int (*fb_set_par)(struct fb_info *info);//视频设置模式
- /* set color register */
- int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
- unsigned blue, unsigned transp, struct fb_info *info);//设置color寄存器
- /* set color registers in batch */
- int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);//批量设置color寄存器,设备颜色表
- /* blank display */
- int (*fb_blank)(int blank, struct fb_info *info);//显示空白
- /* pan display */
- int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
- /* Draws a rectangle */
- void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);//填充矩形
- /* Copy data from area to another */
- void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);//数据复制
- /* Draws a image to the display */
- void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);//图形填充
- /* Draws cursor */
- int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);//绘制光标
- /* Rotates the display */
- void (*fb_rotate)(struct fb_info *info, int angle);//旋转显示
- /* wait for blit idle, optional */
- int (*fb_sync)(struct fb_info *info);//等待blit空闲
- /* perform fb specific ioctl (optional) */
- int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
- unsigned long arg);
- /* Handle 32bit compat ioctl (optional) */
- int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
- unsigned long arg);
- /* perform fb specific mmap */
- int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
- /* get capability given var */
- void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
- struct fb_var_screeninfo *var);
- /* teardown any resources to do with this framebuffer */
- void (*fb_destroy)(struct fb_info *info);
- /* called at KDB enter and leave time to prepare the console */
- int (*fb_debug_enter)(struct fb_info *info);
- int (*fb_debug_leave)(struct fb_info *info);
- };
内核中定义了一个struct fb_info *registered_fb[FB_MAX]来存放屏的相关信息,下面来看看帧缓冲设备fb_info结构体的注册。
- int
- register_framebuffer(struct fb_info *fb_info)
- {
- int ret;
- mutex_lock(®istration_lock);
- ret = do_register_framebuffer(fb_info);
- mutex_unlock(®istration_lock);
- return ret;
- }
- 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++;
- 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);
- 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);//初始化fb的属性文件
- 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;
- if (!lock_fb_info(fb_info))
- return -ENODEV;
- fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
- unlock_fb_info(fb_info);
- return 0;
- }
这个函数主要是创建设备节点,初始化fb的属性文件等一些初始化,下面来看看帧缓冲区驱动核心层的层次结构图:
那么从上面的图可以总结出怎么写一个lcd的驱动程序的步骤
1.分配一个fb_info的结构体
2.设置
3.注册register_framebuffer
4.硬件相关的初始化
上面将了一些framebuffer帧缓冲的有关的,那么我们具体看看内核怎么去实现lcd的驱动。
- int __init s3c2410fb_init(void)
- {
- int ret = platform_driver_register(&s3c2410fb_driver);
- if (ret == 0)
- ret = platform_driver_register(&s3c2412fb_driver);
- return ret;
- }
- static void __exit s3c2410fb_cleanup(void)
- {
- platform_driver_unregister(&s3c2410fb_driver);
- platform_driver_unregister(&s3c2412fb_driver);
- }
- module_init(s3c2410fb_init);
- module_exit(s3c2410fb_cleanup);
lcd驱动挂在platform总线上,注册了一个s3c2410fb_driver的驱动,由总线驱动设备模型中,当注册一个设备或者驱动的时候,会调用总线的match函数,而platform的match函数是匹配设备和驱动的名字
- static struct platform_driver s3c2410fb_driver = {
- .probe = s3c2410fb_probe,
- .remove = __devexit_p(s3c2410fb_remove),
- .suspend = s3c2410fb_suspend,
- .resume = s3c2410fb_resume,
- .driver = {
- .name = "s3c2410-lcd",
- .owner = THIS_MODULE,
- },
- };
- 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 = &samsung_device_dma_mask,
- .coherent_dma_mask = DMA_BIT_MASK(32),
- }
- };
- static struct resource s3c_lcd_resource[] = {
- [0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),//内存
- [1] = DEFINE_RES_IRQ(IRQ_LCD),//中断号
- };
驱动与设备匹配会,会调用probe函数
- static int __devinit s3c24xxfb_probe(struct platform_device *pdev,
- enum s3c_drv_type drv_type)
- {
- struct s3c2410fb_info *info;
- struct s3c2410fb_display *display;
- struct fb_info *fbinfo;
- struct s3c2410fb_mach_info *mach_info;
- struct resource *res;
- int ret;
- int irq;
- int i;
- int size;
- u32 lcdcon1;
- mach_info = pdev->dev.platform_data;
- if (mach_info == NULL) {
- dev_err(&pdev->dev,
- "no platform data for lcd, cannot attach\n");
- return -EINVAL;
- }
- if (mach_info->default_display >= mach_info->num_displays) {
- dev_err(&pdev->dev, "default is %d but only %d displays\n",
- mach_info->default_display, mach_info->num_displays);
- return -EINVAL;
- }
- display = mach_info->displays + mach_info->default_display;
- 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);//分配一个fb_info结构体
- if (!fbinfo)
- return -ENOMEM;
- platform_set_drvdata(pdev, fbinfo);
- info = fbinfo->par;
- info->dev = &pdev->dev;
- info->drv_type = drv_type;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取内存地址
- if (res == NULL) {
- dev_err(&pdev->dev, "failed to get memory registers\n");
- ret = -ENXIO;
- goto dealloc_fb;
- }
- size = resource_size(res);
- info->mem = request_mem_region(res->start, size, pdev->name);
- if (info->mem == NULL) {
- dev_err(&pdev->dev, "failed to get memory region\n");
- ret = -ENOENT;
- goto dealloc_fb;
- }
- info->io = ioremap(res->start, size);//io map
- if (info->io == NULL) {
- dev_err(&pdev->dev, "ioremap() of registers failed\n");
- ret = -ENXIO;
- goto release_mem;
- }
- info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
- dprintk("devinit\n");
- strcpy(fbinfo->fix.id, driver_name);
- /* Stop the video */
- lcdcon1 = readl(info->io + S3C2410_LCDCON1);
- writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
- //设置fbinfo结构体
- 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.accel_flags = 0;
- fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
- fbinfo->fbops = &s3c2410fb_ops;
- fbinfo->flags = FBINFO_FLAG_DEFAULT;
- fbinfo->pseudo_palette = &info->pseudo_pal;
- for (i = 0; i < 256; i++)
- info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
- ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info);
- if (ret) {
- dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
- ret = -EBUSY;
- goto release_regs;
- }
- info->clk = clk_get(NULL, "lcd");
- if (IS_ERR(info->clk)) {
- printk(KERN_ERR "failed to get lcd clock source\n");
- ret = PTR_ERR(info->clk);
- goto release_irq;
- }
- clk_enable(info->clk);
- dprintk("got and enabled clock\n");
- msleep(1);
- info->clk_rate = clk_get_rate(info->clk);
- /* find maximum required memory size for display */
- for (i = 0; i < mach_info->num_displays; i++) {
- unsigned long smem_len = mach_info->displays[i].xres;
- smem_len *= mach_info->displays[i].yres;
- smem_len *= mach_info->displays[i].bpp;
- smem_len >>= 3;
- if (fbinfo->fix.smem_len < smem_len)
- fbinfo->fix.smem_len = smem_len;
- }
- /* Initialize video memory */
- ret = s3c2410fb_map_video_memory(fbinfo);
- if (ret) {
- printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
- ret = -ENOMEM;
- goto release_clock;
- }
- dprintk("got video memory\n");
- fbinfo->var.xres = display->xres;
- fbinfo->var.yres = display->yres;
- fbinfo->var.bits_per_pixel = display->bpp;
- s3c2410fb_init_registers(fbinfo);
- s3c2410fb_check_var(&fbinfo->var, fbinfo);
- ret = s3c2410fb_cpufreq_register(info);
- if (ret < 0) {
- dev_err(&pdev->dev, "Failed to register cpufreq\n");
- goto free_video_memory;
- }
- ret = register_framebuffer(fbinfo);
- if (ret < 0) {
- printk(KERN_ERR "Failed to register framebuffer device: %d\n",
- ret);
- goto free_cpufreq;
- }
- /* create device files */
- ret = device_create_file(&pdev->dev, &dev_attr_debug);
- if (ret) {
- printk(KERN_ERR "failed to add debug attribute\n");
- }
- printk(KERN_INFO "fb%d: %s frame buffer device\n",
- fbinfo->node, fbinfo->fix.id);
- return 0;
- free_cpufreq:
- s3c2410fb_cpufreq_deregister(info);
- free_video_memory:
- s3c2410fb_unmap_video_memory(fbinfo);
- release_clock:
- clk_disable(info->clk);
- clk_put(info->clk);
- release_irq:
- free_irq(irq, info);
- release_regs:
- iounmap(info->io);
- release_mem:
- release_mem_region(res->start, size);
- dealloc_fb:
- platform_set_drvdata(pdev, NULL);
- framebuffer_release(fbinfo);
- return ret;
- }
上面的一些步骤基本与总结的一个lcd的驱动程序的步骤相同,下面来看看用户空间怎么去访问
app:read()-->kernel调用fbmem.c中的fb_read(),其他的接口类似。
- static ssize_t
- fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
- {
- unsigned long p = *ppos;
- struct fb_info *info = file_fb_info(file);//获取次设备号,根据次设备号从registered_fb数组中拿
- u8 *buffer, *dst; //到对应的fb_info
- 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;
- if (info->fbops->fb_read)//调用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 = (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;
- }
阅读(16472) | 评论(0) | 转发(7) |