在嵌入式产品中,lcd的用途可谓很大,很多产品都会用到lcd作为人机交互的接口,在linux内核中当然也有lcd的驱动,内核中都是做好了框架,用平台设备来添加和管理不同的lcd驱动程序,因为每款arm芯片的接品都有不同,每款lcd的驱动方式也有不同,方便后期开发人员增加和修改以达到适用于不同硬件设备对应的lcd驱动程序,我们想要自已编写一个基于内核的lcd驱动程序,就需要先了解内核中lcd驱动的框架,在device那部分用上自已硬件相关的代码,再用一个适合内核lcd驱动的框架的接口,即可完成,在此,我们就有必要先了解到内核关于lcd驱动的框架,按照其框架来编写即可。下面来分析
一、lcd驱动程序框架分析 在 /drivers/video/fbmem.c 这个文件入口函数开始分析
static const struct file_operations fb_fops = { .owner = THIS_MODULE, .read = fb_read, .write = fb_write, .unlocked_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 .llseek = default_llseek, }; static int __init fbmem_init(void) { proc_create("fb", 0, NULL, &fb_proc_fops); /* 注册字符设备驱动 主设备号=29 file_operations= fb_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; }
入口函数里注册了一个字符设备 指定了主设备号和file_operations 结构体,我们只分析框架,不作很深层次的跟进,如果想完完全全了解其深层次的每个函数每条语句,需要大量的时间,我认为学习驱动其间,没有太多意义,我们得先会写,需要提升时或更深层次了解内核时再去分析内核的源码。在input输入子系统那节有说过,分析一个驱动,我们用假设的方法最为方便找出其流程框架,就是假设我们 open read 会如何操作,跟进去就可以了解框架了
A:这里假设我们打开这个设备,将会调用到 驱动的 open 函数 我们进去看一下。这个函数怎么操作的
static int fb_open(struct inode *inode, struct file *file) __acquires(&info->lock) __releases(&info->lock) { /* 得到打开设备的次设备号 保存到 fbidx 变量中 */ int fbidx = iminor(inode); /* 定义一个 fb_info 结构体指针 */ struct fb_info *info; int res = 0; /* 初始化这个 fb_info 结构体 get_fb_info 这个函数里有 */ 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)) { res = -ENODEV; goto out; } file->private_data = info; if (info->fbops->fb_open) {/* 判断这个函数是存在 */ /* 执行info结构体里的fbops里的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; }
在 fb_open 函数里,得到一个fo_info结构体 以打开设备的次设备号为下标在某个数组里得到这个结构体,然后执行这个结构体里的fops->open函数
如果没有这个函数 那么open 操作就到此结束了,但是这里有一个未知的东西,fb_info 结构体的内从是从数组里得到,那这个数组由谁构造?谁来填充呢?我们进入get_fb_info 这个函数里看一下,如何得到:
static struct fb_info *get_fb_info(unsigned int idx) { struct fb_info *fb_info; if (idx >= FB_MAX) return ERR_PTR(-ENODEV); mutex_lock(®istration_lock); fb_info = registered_fb[idx]; if (fb_info) atomic_inc(&fb_info->count); mutex_unlock(®istration_lock); return fb_info; }
从这个函数我们知道上面说的数组是那个 就是 registered_fb【idx】 idx 是传入的参数就是上面提到的次设备号,但我们目的还没达到,我们只找到数组,但数组内容那来我们还不清楚,我们搜索一下这个数组:registered_fb[i] = fb_info; 我们搜索到这么一项,是在 do_register_framebuffer 这个函数里得到,fb_info 结构体是做为这个函数参数传进来的。那我们要再往前找找。在 register_framebuffer 里调用 do_register_framebuffer再往上找 就以搜索到一大堆lcd的驱动程序调用了 register_framebuffer 这个函数,猜测一下,是否是在lcd硬件驱动调用那边里构造并设置了这个fb_info结构体呢?我们找一个驱动程序看一下就可以知道了。以s3c2410fb.c为例看一下,当然也可以用其它的,我的板子是2440所以用这个,好理解一些。看到在 s3c2410fb.c里s3c24xxfb_probe这个函数里调用了。从名字上看 基本上可以看出,这是一个平台设备枚举函数,再往上就跟到了平台设备的东西,不是我们分析的内容,这里先不管它,下面我们看看 s3c24xxfb_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结构体 */ fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); 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; } /* GPIO映射 */ info->io = ioremap(res->start, size); if (info->io == NULL) { dev_err(&pdev->dev, "ioremap() of registers failed\n"); ret = -ENXIO; goto release_mem; } if (drv_type == DRV_S3C2412) info->irq_base = info->io + S3C2412_LCDINTBASE; else info->irq_base = info->io + 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"); usleep_range(1000, 1000); 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; /* 初始化 GPIO 寄存器 */ 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; } /* 注册fbinfo */ 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; }
这里完成了,fb_info结构体的构造和填充,然后调用 register_framebuffer 把fb_info结构体做为参数传进去,再下面就是上面分析的流程了。上面我们说是打开设备 调用open函数,在fo_info 结构体里有一个fbinfo->fbops = &s3c2410fb_ops; 所以会调用到 s3c2410fb_ops->open函数 但这里没有open函数,我们回到上面看,有个判断,如果有就调用,没有话就算了。这里有个疑问,没有就算了,那调用open最后会干啥,啥也没干,我理解为,从代码上面看,注册后,即这个lcd设备就存在了,open有没有都可以,因为我们操作打开没有意义,基本上我们都是对lcd读写操作。下面我们来分析一下怎么读
B:假设app调用read函数,那流程又是怎么样的呢:会调用 fb_read 进去看看
static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; /* 得到fb_info结构体 */ struct fb_info *info = file_fb_info(file); u8 *buffer, *dst; 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; /* 判断如果有读函数就调用 info->fbops->fb_read 这个函数读 如果没有 往下执行 */ if (info->fbops->fb_read) return info->fbops->fb_read(info, buf, count, ppos); /* 得到lcd显示屏大小 */ 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 应该是显存 */ 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; /* 把 显存的内容发送给app */ if (copy_to_user(buf, buffer, c)) { err = -EFAULT; break; } *ppos += c; buf += c; cnt += c; count -= c; } kfree(buffer); return (err) ? err : cnt; }
读操作和写操作不同之处就是,如果有读函数就调用读函数,如果没有就从显存里读出数据发送给app。分析到这,应该算是基本了解了框 架了,
fbmem 是一个通用的接口,驱动程序通过平台接口调用register_framebuffer注册,在此前要先分配和设置fb_info结构体,这个结构体里就是硬件操作的数据参数。我们先不管平台设备,那么我们要自已写就可以总结出得到下面的步骤:
1:分配一个fb_info结构体
2:设置
3:注册
4:硬件相关的操作 这里包括很多东西,画点,画圆 等和硬件直接相关的操作。
以上就是框架,按照这个步骤就可以写出一个最简单的lcd驱动程序,至于一些显示文件,图片,这些复杂的操作可以后续添加。一步一步完成达到自已想要的结果。
我分析这些东西是参考别人的视频对着代码来看的,有很多看不懂的代码,没有分析,但对于学习驱动来说,学会分析一个驱动的方法,和去深扒一个函数的实现过程要有意义,并不是说了解实现不好,能看懂所有代码那是求之不得,但是linux内核没有我们想像中那么好分析,一个套一个,各种结构体链表,关系很复杂,我是看不明白,可能我能力不好吧。我认为首先学会写一些简单的东西,再慢慢深入,如果一开始就深扒每一个函数如何实现,会碰到很多看不明白的地方,会打击自已的信心,耐心。我深有体会。所以,随着学的东西慢慢的增加,理解慢慢的加深,会有明白的一天。社会的形势造成了这样的现像,你就是理论再强,不能做出实际有用的东西,那也是白搭,没人会用你。关于我,有兴趣可以看看,我前面写的自述。就知道我说这些话的道理在那了。
我写的东西,如果对一些人有帮助,那最好不过了,我的目的就是记录下我的学习过程,以后忘记的时候回来看一下,同时勉劢一下自已。