在嵌入式产品中,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;
}
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\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;
}
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内核没有我们想像中那么好分析,一个套一个,各种结构体链表,关系很复杂,我是看不明白,可能我能力不好吧。我认为首先学会写一些简单的东西,再慢慢深入,如果一开始就深扒每一个函数如何实现,会碰到很多看不明白的地方,会打击自已的信心,耐心。我深有体会。所以,随着学的东西慢慢的增加,理解慢慢的加深,会有明白的一天。社会的形势造成了这样的现像,你就是理论再强,不能做出实际有用的东西,那也是白搭,没人会用你。关于我,有兴趣可以看看,我前面写的自述。就知道我说这些话的道理在那了。

 

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

  

 

posted on 2019-10-26 17:01  荧火虫  阅读(512)  评论(0编辑  收藏  举报