LCD 驱动程序

fbmem.c 是内核自带的 LCD 驱动程序。它基于分层的思想,抽出了共性的内容,向上为APP提供统一的接口。


02_016register_framebuffer2.png

fbmem.c 分析

static int __init
fbmem_init(void)
{
    create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);

    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;
}

static const struct file_operations fb_fops = {
    .owner =    THIS_MODULE,
    .read =     fb_read,
    .write =    fb_write,
    .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
};

fb_open

static int
fb_open(struct inode *inode, struct file *file)
{
    int fbidx = iminor(inode);//得到这个设备节点的“次设备号”
    struct fb_info *info; //帧缓冲区结构体
    int res = 0;

    if (fbidx >= FB_MAX)
        return -ENODEV;
#ifdef CONFIG_KMOD
    if (!(info = registered_fb[fbidx]))//假设“次设备号”为 0. 即从这个 registered_fb[]数组里得到“以次设备号为 0 为下标”的一项。
        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) {//若这个 info = registered_fd[0] 的“fbops”有“fb_open”函数时就调用
        res = info->fbops->fb_open(info,1);
        if (res)
            module_put(info->fbops->owner);
    }
    return res;
}

fb_read

static ssize_t
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];//以次设备号为下标从 registered_fd 数组中得一项赋给"info"结构
    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)// //若这个 info 数组项中提供了“fbops”结构的“fb_read”读函数时:就调用此读函数
        return info->fbops->fb_read(info, buf, count, ppos);
    
    //若没有提供"info"项,往下执行
    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);//screen_base 是指显存的基地址。这里是读源 src 等于显存的基地址加上某个偏移值

    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++);//读源(从显存基地址+P 偏移)那里读到一个数据放到目标“*dst++”里。dst 是 buffer,buffer 是 kmalloc()上面分配的空间
        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;
}

Fbmem.c 提供的都是抽象出来的东西。最终都得依赖这个“registered_fb”数组里的“fb_info”结构体。registered_fb 在下面函数中被赋值:

int
register_framebuffer(struct fb_info *fb_info)
{。。。。。。
    registered_fb[i] = fb_info;
 。。。。。。
}

register_framebuffer

/**
 *  register_framebuffer - registers a frame buffer device
 *  @fb_info: frame buffer info structure
 *
 *  Registers a frame buffer device @fb_info.
 *
 *  Returns negative errno on error, or zero for success.
 *
 */

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;

    fb_info->dev = device_create(fb_class, fb_info->device,
                     MKDEV(FB_MAJOR, i), "fb%d", i);
    /*在“fd_class”类下面创建设备。只有真正有硬件设备时才有必要在这个
类下去创建设备。这样 mdev 或 udev 才能去自动创建设备节点。Fbmem.c 只是
抽象出来的 LCD 驱动框架程序,并不能支持具体的驱动。它需要依赖底层的某
个驱动程序给它注册一个“fb_info”结构体(由“register_framebuffer()
来注册”)*/
    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;
}

内核中有各种 具体的LCD 设备驱动程序会调用这个“register_framebuffer()”(如 6832fb.c、amifb.c、atmel_lcdfb.c、还有 2410 的如 s3fb.c、s3c2410fb.c 等)。

来看看inux/drivers/video/s3c2410fb.c

int __devinit s3c2410fb_init(void)
{
    return platform_driver_register(&s3c2410fb_driver);//注册一个平台驱动(总线-设备-驱动模型,关心.probe 函数)
}

static struct platform_driver s3c2410fb_driver = {
    .probe      = s3c2410fb_probe,//若内核中有同名"s3c2410-lcd"的平台设备就调用"s3c2410fb_probe"函数
    .remove     = s3c2410fb_remove,
    .suspend    = s3c2410fb_suspend,
    .resume     = s3c2410fb_resume,
    .driver     = {
        .name   = "s3c2410-lcd",
        .owner  = THIS_MODULE,
    },
};

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;//根据pdev获得"mach_info"信息
    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);//根据pdev获得"irq"中断信息
    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"结构。接着就开始设置这个 fb_info 结构体
    if (!fbinfo) {
        return -ENOMEM;
    }

    //硬件相关的操作
    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;

    //framebuffer的固定信息
    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;

    //framebuffer的可变信息
    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");

    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);//向fbmem.c注册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;
}

从fbmem.c 的接口“.ioctl”中可以得到LCD 的分辨率等信息。

static int 
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
     unsigned long arg)
{
    int fbidx = iminor(inode);
    struct fb_info *info = registered_fb[fbidx];
    struct fb_ops *fb = info->fbops;
    struct fb_var_screeninfo var;
    struct fb_fix_screeninfo fix;
    struct fb_con2fbmap con2fb;
    struct fb_cmap_user cmap;
    struct fb_event event;
    void __user *argp = (void __user *)arg;
    int i;
    
    if (!fb)
        return -ENODEV;
    switch (cmd) {
    case FBIOGET_VSCREENINFO://GET 获得.V(var)可变的.SCREEN 屏幕.INFO 信息.
        return copy_to_user(argp, &info->var,
                    sizeof(var)) ? -EFAULT : 0;// 将这个 info 结构中的 var 成员拷贝回用户空间
    case FBIOPUT_VSCREENINFO:
        。。。。。。
}
 //看 info->var 这个成员中有什么内容
    struct fb_var_screeninfo {
    __u32 xres;         /* visible resolution   x,y方向的分辨率   */
    __u32 yres;
    __u32 xres_virtual;     /* virtual resolution       */
    __u32 yres_virtual;
    __u32 xoffset;          /* offset from virtual to visible */
    __u32 yoffset;          /* resolution           */

    __u32 bits_per_pixel;       /* guess what           */
    __u32 grayscale;        /* != 0 Graylevels instead of colors */
        ......

内核提供的 LCD 驱动“s3c2410fb.c”:

int __devinit s3c2410fb_init(void)
 -->platform_driver_register(&s3c2410fb_driver);
注册了一个“s3c2410fb_driver”平台驱动。
.name = "s3c2410-lcd", 当内核中有同名的设备时,就调用:.probe 函数。
.probe = s3c2410fb_probe,
int __init s3c2410fb_probe(struct platform_device *pdev)
 根据平台设备的一些信息。来设置这个 LCD 控制器。拆分成两部分,一部分是比较稳定的代码(软件部分),另一部分是“硬件相关的代码”。

内核这个 LCD 的硬件相关的代码在:linux/arch/arm/plat-s3c24xx/devs.c

//里面有一个“平台设备”:
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       = &s3c_device_lcd_dmamask,
        .coherent_dma_mask  = 0xffffffffUL
    }
};

void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)//设置平台设备里面的“私有数据”
{
    struct s3c2410fb_mach_info *npd;

    npd = kmalloc(sizeof(*npd), GFP_KERNEL);
    if (npd) {
        memcpy(npd, pd, sizeof(*npd));
        s3c_device_lcd.dev.platform_data = npd;
    } else {
        printk(KERN_ERR "no memory for LCD platform data\n");
    }
}

查看这个函数“__init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)”设置的地方:

搜索内核:

---- s3c24xx_fb_set_platdata Matches (10 in 8 files) ----
Devs.c (arch\arm\plat-s3c24xx):void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
Fb.h (include\asm-arm\arch-s3c2410):extern void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *);
Mach-amlm5900.c (arch\arm\mach-s3c2410):    s3c24xx_fb_set_platdata(&amlm5900_lcd_info);
Mach-bast.c (arch\arm\mach-s3c2410):    s3c24xx_fb_set_platdata(&bast_lcd_info);
Mach-h1940.c (arch\arm\mach-s3c2410):   s3c24xx_fb_set_platdata(&h1940_lcdcfg);
Mach-qt2410.c (arch\arm\mach-s3c2410):      s3c24xx_fb_set_platdata(&qt2410_prodlcd_cfg);
Mach-qt2410.c (arch\arm\mach-s3c2410):      s3c24xx_fb_set_platdata(&qt2410_biglcd_cfg);
Mach-qt2410.c (arch\arm\mach-s3c2410):      s3c24xx_fb_set_platdata(&qt2410_lcd_cfg);
Mach-rx3715.c (arch\arm\mach-s3c2440):  s3c24xx_fb_set_platdata(&rx3715_lcdcfg);
Mach-smdk2440.c (arch\arm\mach-s3c2440):    s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);

如“linux/arch/arm/mach-s3c2440/mach-smdk2440.c”中:

static void __init smdk2440_machine_init(void)
{
    s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);

    platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
    smdk_machine_init();
}

//平台私有数据里有“smdk2440_lcd_cfg”相关寄存器的设置
/* LCD driver info */

static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
    .regs   = {

        .lcdcon1    = S3C2410_LCDCON1_TFT16BPP |
                  S3C2410_LCDCON1_TFT |
                  S3C2410_LCDCON1_CLKVAL(0x04),

        .lcdcon2    = S3C2410_LCDCON2_VBPD(7) |
                  S3C2410_LCDCON2_LINEVAL(319) |
                  S3C2410_LCDCON2_VFPD(6) |
                  S3C2410_LCDCON2_VSPW(3),

        .lcdcon3    = S3C2410_LCDCON3_HBPD(19) |
                  S3C2410_LCDCON3_HOZVAL(239) |
                  S3C2410_LCDCON3_HFPD(7),

        .lcdcon4    = S3C2410_LCDCON4_MVAL(0) |
                  S3C2410_LCDCON4_HSPW(3),

        .lcdcon5    = S3C2410_LCDCON5_FRM565 |
                  S3C2410_LCDCON5_INVVLINE |
                  S3C2410_LCDCON5_INVVFRAME |
                  S3C2410_LCDCON5_PWREN |
                  S3C2410_LCDCON5_HWSWP,
    },

#if 0
    /* currently setup by downloader */
    .gpccon     = 0xaa940659,
    .gpccon_mask    = 0xffffffff,
    .gpcup      = 0x0000ffff,
    .gpcup_mask = 0xffffffff,
    .gpdcon     = 0xaa84aaa0,
    .gpdcon_mask    = 0xffffffff,
    .gpdup      = 0x0000faff,
    .gpdup_mask = 0xffffffff,
#endif

    .lpcsel     = ((0xCE6) & ~7) | 1<<4,
    .type       = S3C2410_LCDCON1_TFT16BPP,

    .width      = 240,
    .height     = 320,

    .xres       = {
        .min    = 240,
        .max    = 240,
        .defval = 240,
    },

    .yres       = {
        .min    = 320,
        .max    = 320,
        .defval = 320,
    },

    .bpp        = {
        .min    = 16,
        .max    = 16,
        .defval = 16,
    },
};
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 入局:应用程序是如何操控LCD显示器的? 我们知道应用程序的调用接口,无非open/read/write...然后...
    JalynFang阅读 1,772评论 0 3
  • 一、LCD硬件原理 版权声明:本文为小斑马学习总结文章,技术来源于韦东山著作,转载请注明出处!先简单介绍下LCD的...
    ZebraWei阅读 2,774评论 0 3
  • 在上一节 Linux-LCD 驱动程序概述 中,可知LCD驱动主要实现是帧缓冲设备的实现。 一、帧缓冲设备驱动在L...
    JalynFang阅读 2,223评论 0 2
  • 内核LCD框架分析 最近在看韦佬的LCD驱动部分的视频,韦佬对内核自带的LCD驱动框架的讲解较少,所以在此先分析一...
    章辉_8bef阅读 864评论 0 0
  • 宋宝华 Barry Song 21cnbao@gmail.comhttp://blog.csdn.net/21cn...
    JosephDHF阅读 1,426评论 0 1

友情链接更多精彩内容