• lcd 驱动程序框架分析


    在嵌入式产品中,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
    ", FB_MAJOR);
        /* 创建一个类 */
        fb_class = class_create(THIS_MODULE, "graphics");
        if (IS_ERR(fb_class)) {
            printk(KERN_WARNING "Unable to create fb class; errno = %ld
    ", PTR_ERR(fb_class));
            fb_class = NULL;
        }
        return 0;
    }
    View Code

    入口函数里注册了一个字符设备 指定了主设备号和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;
    }
    View Code

    在 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(&registration_lock);
        fb_info = registered_fb[idx];
        if (fb_info)
            atomic_inc(&fb_info->count);
        mutex_unlock(&registration_lock);
    
        return fb_info;
    }
    View Code

    从这个函数我们知道上面说的数组是那个 就是 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
    ");
            return -EINVAL;
        }
    
        if (mach_info->default_display >= mach_info->num_displays) {
            dev_err(&pdev->dev, "default is %d but only %d displays
    ",
                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
    ");
            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
    ");
            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
    ");
            ret = -ENOENT;
            goto dealloc_fb;
        }
        /* GPIO映射 */
        info->io = ioremap(res->start, size);
        if (info->io == NULL) {
            dev_err(&pdev->dev, "ioremap() of registers failed
    ");
            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
    ");
    
        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
    ", 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
    ");
            ret = PTR_ERR(info->clk);
            goto release_irq;
        }
        /* 使能时钟 */
        clk_enable(info->clk);
        dprintk("got and enabled clock
    ");
    
        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
    ", ret);
            ret = -ENOMEM;
            goto release_clock;
        }
    
        dprintk("got video memory
    ");
    
        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
    ");
            goto free_video_memory;
        }
        /* 注册fbinfo */
        ret = register_framebuffer(fbinfo);
        if (ret < 0) {
            printk(KERN_ERR "Failed to register framebuffer device: %d
    ",
                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
    ");
    
        printk(KERN_INFO "fb%d: %s frame buffer device
    ",
            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;
    }
    View Code

    这里完成了,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;
    }
    View Code

    读操作和写操作不同之处就是,如果有读函数就调用读函数,如果没有就从显存里读出数据发送给app。分析到这,应该算是基本了解了框 架了,

    fbmem 是一个通用的接口,驱动程序通过平台接口调用register_framebuffer注册,在此前要先分配和设置fb_info结构体,这个结构体里就是硬件操作的数据参数。我们先不管平台设备,那么我们要自已写就可以总结出得到下面的步骤:

    1:分配一个fb_info结构体

    2:设置

    3:注册

    4:硬件相关的操作 这里包括很多东西,画点,画圆 等和硬件直接相关的操作。

    以上就是框架,按照这个步骤就可以写出一个最简单的lcd驱动程序,至于一些显示文件,图片,这些复杂的操作可以后续添加。一步一步完成达到自已想要的结果。

    我分析这些东西是参考别人的视频对着代码来看的,有很多看不懂的代码,没有分析,但对于学习驱动来说,学会分析一个驱动的方法,和去深扒一个函数的实现过程要有意义,并不是说了解实现不好,能看懂所有代码那是求之不得,但是linux内核没有我们想像中那么好分析,一个套一个,各种结构体链表,关系很复杂,我是看不明白,可能我能力不好吧。我认为首先学会写一些简单的东西,再慢慢深入,如果一开始就深扒每一个函数如何实现,会碰到很多看不明白的地方,会打击自已的信心,耐心。我深有体会。所以,随着学的东西慢慢的增加,理解慢慢的加深,会有明白的一天。社会的形势造成了这样的现像,你就是理论再强,不能做出实际有用的东西,那也是白搭,没人会用你。关于我,有兴趣可以看看,我前面写的自述。就知道我说这些话的道理在那了。

    我写的东西,如果对一些人有帮助,那最好不过了,我的目的就是记录下我的学习过程,以后忘记的时候回来看一下,同时勉劢一下自已。

      

    钻木取火!拼的是体力?耐心?智慧?
  • 相关阅读:
    eclipse报错 : One or more constraints have not been satisfied.
    关于一个计算机同时装两个jdk对应两个eclipse
    开发测试类2
    工作中测试类1
    Spring 读取classpath下的文件存到map里面
    CentOS6下安装JDK并且部署tomcat容器
    ConOS安装mysql5.7 及简单配置
    用sqlyog迁移mysql数据库
    FindBugs缺陷库
    使用NumberPicker定制自己喜欢的Date&TimePicker
  • 原文地址:https://www.cnblogs.com/x2i0e19linux/p/11743795.html
Copyright © 2020-2023  润新知