基于frame buffer架构的lcd驱动

来源:互联网 发布:印度撤军知乎 编辑:程序博客网 时间:2024/05/29 08:42

硬件:LQ035HC111 ,3.45” TFT LCD Module ,s3c2440

内核版本2.6  

(一)frame buffer架构

在driver/video/fbmem.c 中完成对对framebuffer的初始化,初始化函数为fbmem_init如下:

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

    devfs_mk_dir("fb");
    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;
}

该函数主要完成fb字符设备好的申请,添加和注册,主设备好为29 ,次设备号为0-255,该函数并没有创建设备节点,设备节点是在

in
register_framebuffer(struct fb_info *fb_info)函数里创建的一个节点对应一个struct fb_info 结构,该函数也位于driver/video/fbmem.c下

函数为fb设备提供了统一的ops egister_chrdev(FB_MAJOR,"fb",&fb_fops))函数实现了cdev和fb_fops的绑定 

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

该函数实现了对打开节点的对应操作,应用层上的open函数最终打开对应的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]))                      //找到对应节点的struct fb_info 结构,该结构在驱动程序s3c2410fb_probe函数中完成填充和注册.
         try_to_load(fbidx);                                    
#endif /* CONFIG_KMOD */
    if (!(info = registered_fb[fbidx]))
        return -ENODEV;
    if (!try_module_get(info->fbops->owner))
        return -ENODEV;
    if (info->fbops->fb_open) {                                        //如果fbops中实现了自己的fb_open,则调用
        res = info->fbops->fb_open(info,1)                       //最终调用struct fb_info 中添加的fbops操作函数中的fb_open函数
        if (res)
            module_put(info->fbops->owner);
    }
    return res;
}

应用层write()调用fbmem.c中的fb_write():

static ssize_t
fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)              //想分配lcd dma内存偏移ppos的地方写入buf中的count字节
{
    unsigned long p = *ppos;
    struct inode *inode = file->f_dentry->d_inode;
    int fbidx = iminor(inode);
    struct fb_info *info = registered_fb[fbidx];
    u32 *buffer, *src;
    u32 __iomem *dst;
    int c, i, cnt = 0, err;
    unsigned long total_size;

    if (!info || !info->screen_base)
        return -ENODEV;

    if (info->state != FBINFO_STATE_RUNNING)
        return -EPERM;

    if (info->fbops->fb_write)                                                    //如果fb_info->fbops 实现了fb_write则调用,s3c2410fb.c中没有实现该函数
        return info->fbops->fb_write(file, buf, count, ppos);
    
    total_size = info->screen_size;
    if (total_size == 0)
        total_size = info->fix.smem_len;

    if (p > total_size)
        return -ENOSPC;
    if (count >= total_size)
        count = total_size;
    err = 0;
    if (count + p > total_size) {
        count = total_size - p;
        err = -ENOSPC;
    }
    cnt = 0;
    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
             GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;

    dst = (u32 __iomem *) (info->screen_base + p);                                     要写入数据的地址

    if (info->fbops->fb_sync)
        info->fbops->fb_sync(info);

    while (count) {
        c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
        src = buffer;
        if (copy_from_user(src, buf, c)) {
            err = -EFAULT;
            break;
        }
        for (i = c >> 2; i--; )
            fb_writel(*src++, dst++);
        if (c & 3) {
            u8 *src8 = (u8 *) src;
            u8 __iomem *dst8 = (u8 __iomem *) dst;

            for (i = c & 3; i--; )
                fb_writeb(*src8++, dst8++);

            dst = (u32 __iomem *) dst8;
        }
        *ppos += c;
        buf += c;
        cnt += c;
        count -= c;
    }
    kfree(buffer);

    return (err) ? err : cnt;
}

fb_read 被用户层的read()函数调用

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)                   //从lcd dma 缓冲起始位置偏移ppos读取count字节到用户空间的 buf中
{
    unsigned long p = *ppos;
    struct inode *inode = file->f_dentry->d_inode;
    int fbidx = iminor(inode);
    struct fb_info *info = registered_fb[fbidx];
    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)                                                         //同上面的一样如果fb_info->fbops实现了fb_read则调用,否则进行默认的操作
        return info->fbops->fb_read(file, 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;

    cnt = 0;
    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
             GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;

    src = (u32 __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;
        for (i = c >> 2; i--; )
            *dst++ = fb_readl(src++);
        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;
}

static int
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,                          //同样由用户层的ioctl()调用,通过传递不同的命令实现对底层的操作
     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:
        return copy_to_user(argp, &info->var,                             //获取fb_var_screeninfo 的信息到用户空间
                    sizeof(var)) ? -EFAULT : 0;
    case FBIOPUT_VSCREENINFO:
        if (copy_from_user(&var, argp, sizeof(var)))                       //将用户空间的var填充到fb_info中
            return -EFAULT;
        acquire_console_sem();
        info->flags |= FBINFO_MISC_USEREVENT;
        i = fb_set_var(info, &var);
        info->flags &= ~FBINFO_MISC_USEREVENT;
        release_console_sem();
        if (i) return i;
        if (copy_to_user(argp, &var, sizeof(var)))
            return -EFAULT;
        return 0;
    case FBIOGET_FSCREENINFO:                                             //获取固定的fix信息
        return copy_to_user(argp, &info->fix,
                    sizeof(fix)) ? -EFAULT : 0;
    case FBIOPUTCMAP:
        if (copy_from_user(&cmap, argp, sizeof(cmap)))                        //从用户空间获取cmap 到内核 
            return -EFAULT;
        return (fb_set_user_cmap(&cmap, info));
    case FBIOGETCMAP:
        if (copy_from_user(&cmap, argp, sizeof(cmap)))                       //从内核获取cmap到用户空间
            return -EFAULT;
        return fb_cmap_to_user(&info->cmap, &cmap);
    case FBIOPAN_DISPLAY:
        if (copy_from_user(&var, argp, sizeof(var)))
            return -EFAULT;
        acquire_console_sem();
        i = fb_pan_display(info, &var);                                              //显示打印屏幕信息,如果info->fbops->fb_pan_display中有对应的实现的话,就调用,没有执行默认操作
        release_console_sem();
        if (i)
            return i;
        if (copy_to_user(argp, &var, sizeof(var)))                            
            return -EFAULT;
        return 0;
    case FBIO_CURSOR:
        return -EINVAL;
    case FBIOGET_CON2FBMAP:
        if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
            return -EFAULT;
        if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
            return -EINVAL;
        con2fb.framebuffer = -1;
        event.info = info;
        event.data = &con2fb;
        notifier_call_chain(&fb_notifier_list,
                    FB_EVENT_GET_CONSOLE_MAP, &event);
        return copy_to_user(argp, &con2fb,
                    sizeof(con2fb)) ? -EFAULT : 0;
    case FBIOPUT_CON2FBMAP:
        if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
            return - EFAULT;
        if (con2fb.console < 0 || con2fb.console > MAX_NR_CONSOLES)
            return -EINVAL;
        if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
            return -EINVAL;
#ifdef CONFIG_KMOD
        if (!registered_fb[con2fb.framebuffer])
            try_to_load(con2fb.framebuffer);
#endif /* CONFIG_KMOD */
        if (!registered_fb[con2fb.framebuffer])
            return -EINVAL;
        event.info = info;
        event.data = &con2fb;
        return notifier_call_chain(&fb_notifier_list,
                       FB_EVENT_SET_CONSOLE_MAP,
                       &event);
    case FBIOBLANK:
        acquire_console_sem();
        info->flags |= FBINFO_MISC_USEREVENT;
        i = fb_blank(info, arg);                                            //如果info->fbops->fb_blank实现的话就调用,或则执行默认操作
        info->flags &= ~FBINFO_MISC_USEREVENT;
        release_console_sem();
        return i;
    default:
        if (fb->fb_ioctl == NULL)
            return -EINVAL;
        return fb->fb_ioctl(inode, file, cmd, arg, info);                     //自己之定义的一些操作
    }
}

LQ035HC111驱动

/* LCD Controller */

static struct resource s3c_lcd_resource[] = {
    [0] = {
        .start = S3C2410_PA_LCD                                                            //lcd 的物理内存地址,在驱动中要申请映射,
        .end   = S3C2410_PA_LCD + S3C24XX_SZ_LCD,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_LCD,                                                                       //对应的中断
        .end   = IRQ_LCD,
        .flags = IORESOURCE_IRQ,
    }

};


在arch/arm/mach-s2c32410/devs.c 中添加设备         

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;

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

EXPORT_SYMBOL(s3c_device_lcd);

在arch/arm/mach-s3c2410/mach-utu2440.c中添加:

 //*****************************************************************
289 //lq035nc111 LCD Parameter:add by pan <panpingsheng@sina.com>
290 //******************************************************************
291 #if defined(CONFIG_FB_LQ035NC111_320X240)
292 static struct s3c2410fb_mach_info utu2440_lcdcfg __initdata = {
293         .regs={
294                 .lcdcon1 = S3C2410_LCDCON1_TFT16BPP |\                         
295                         S3C2410_LCDCON1_TFT | \                                          //TFT 面板
296                         S3C2410_LCDCON1_CLKVAL(0x07),                               //频率6.32 mhz
297
298                 .lcdcon2 = S3C2410_LCDCON2_VBPD(15) |\
299                         S3C2410_LCDCON2_LINEVAL(239) |\
300                         S3C2410_LCDCON2_VFPD(4) | \
301                         S3C2410_LCDCON2_VSPW(3),
302
303                 .lcdcon3 =      S3C2410_LCDCON3_HBPD(38) | \
304                                 S3C2410_LCDCON3_HOZVAL(319) | \
305                                 S3C2410_LCDCON3_HFPD(20),
306
307                 .lcdcon4 =      S3C2410_LCDCON4_HSPW(30),
308
309                 .lcdcon5 =      S3C2410_LCDCON5_FRM565 |
310                                 S3C2410_LCDCON5_INVVLINE |
311                                 S3C2410_LCDCON5_INVVFRAME |
312                                 S3C2410_LCDCON5_PWREN |
313                                 S3C2410_LCDCON5_HWSWP,
314
315                 },
316
317                 .gpccon=0xaa9556a9,                                                    //设置GPIO状态
318                 .gpccon_mask =  0xffc003fc,
319                 .gpcup=0x0000ffff,
320                 .gpcup_mask=0xffffffff,
321
322                 .gpdcon =       0xaa95aaa1,
323                 .gpdcon_mask =  0xffc0fff0,
324                 .gpdup =        0x0000faff,
325                 .gpdup_mask =   0xffffffff,
326
327         .fixed_syncs =  1,
328         .width  =       320,
329         .height =       240,


333         .xres   = {
334                 .min =          320,
335                 .max =          320,
336                 .defval =       320,
337         },
338
339         .yres   = {
340                 .min =          240,
341                 .max =          240,
342                 .defval =       240,
343         },
344         .bpp    = {
345                 .min =          16,
346                 .max =          16,
347                 .defval =       16,
348         },
349 };
350 #endif


注册平台驱动最后进入probe函数中:

该函数主要完成工作填充fb结构,并完成注册,初始化硬件,配置寄存器

int __init s3c2410fb_probe(struct device *dev)
{
    struct s3c2410fb_hw *mregs;
    int ret;
    int i;

    mach_info = dev->platform_data;
    if (mach_info == NULL) {
        dev_err(dev,"no platform data for lcd, cannot attach\n");
        return -EINVAL;
    }

    mregs = &mach_info->regs;

    strcpy(info.fb.fix.id, driver_name);

    memcpy(&info.regs, &mach_info->regs, sizeof(info.regs));

    info.mach_info            = dev->platform_data;

    info.fb.fix.type        = FB_TYPE_PACKED_PIXELS;
    info.fb.fix.type_aux        = 0;
    info.fb.fix.xpanstep        = 0;
    info.fb.fix.ypanstep        = 0;
    info.fb.fix.ywrapstep        = 0;
    info.fb.fix.accel        = FB_ACCEL_NONE;

    info.fb.var.nonstd        = 0;
    info.fb.var.activate        = FB_ACTIVATE_NOW;
#if 1    
    info.fb.var.height        = mach_info->height;
    info.fb.var.width        = mach_info->width;
#else    
    info.fb.var.height        = -1;
    info.fb.var.width        = -1;
#endif    
    info.fb.var.accel_flags     = 0;
    info.fb.var.vmode        = FB_VMODE_NONINTERLACED;

    info.fb.fbops            = &s3c2410fb_ops;
    info.fb.flags            = FBINFO_FLAG_DEFAULT;
    info.fb.monspecs        = monspecs;
#if 1    
    info.fb.pseudo_palette      = &info.pseudo_pal;
#else
    addr = &info;
        addr = addr + sizeof(struct s3c2410fb_info);
    info.fb.pseudo_palette      = addr;
#endif    

    info.fb.var.xres        = mach_info->xres.defval;
    info.fb.var.xres_virtual    = mach_info->xres.defval;
    info.fb.var.yres        = mach_info->yres.defval;
    info.fb.var.yres_virtual    = mach_info->yres.defval;
    info.fb.var.bits_per_pixel  = mach_info->bpp.defval;
    info.fb.var.pixclock        = mach_info->pixclock;
    
    info.fb.var.upper_margin    = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) +1;
    info.fb.var.lower_margin    = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) +1;
    info.fb.var.vsync_len        = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;

    info.fb.var.left_margin        = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
    info.fb.var.right_margin    = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
    info.fb.var.hsync_len        = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;

    info.fb.var.red.offset      = 11;
    info.fb.var.green.offset    = 5;
    info.fb.var.blue.offset     = 0;
    info.fb.var.transp.offset   = 0;
    info.fb.var.red.length      = 5;
    info.fb.var.green.length    = 6;
    info.fb.var.blue.length     = 5;
    info.fb.var.transp.length   = 0;
    info.fb.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;

    
    info.clk = clk_get(NULL, "lcd");
    if (!info.clk || IS_ERR(info.clk)) {
        printk(KERN_ERR "failed to get lcd clock source\n");
        return -ENOENT;
    }

    clk_use(info.clk);
    clk_enable(info.clk);
    gprintk("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 failed;
    }
    gprintk("got video memory\n");

    ret = s3c2410fb_init_registers(&info);

    ret = s3c2410fb_check_var(&info.fb.var, &info.fb);

    ret = register_framebuffer(&info.fb);
    if (ret < 0) {
        printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
        goto failed;
    }

    /* create device files */
    device_create_file(dev, &dev_attr_debug);

    printk(KERN_INFO "S3C24X0 fb%d: %s frame buffer device initialize done\n",
        info.fb.node, info.fb.fix.id);

    return 0;
failed:
    return ret;
}


调试中遇到问题

1.调试中出现的问题,会出现滚屏,原因是初始化时没有设置info->fix.line_length 固定参数

2.LPCSEL寄存器一定要清零,否则使能的话屏幕的中间会出现总一条黑带。

3.屏幕不能过低,会出现抖动

4.图像错位
症状
 
    常见的症状如图像左右位移几个像素,上方或下方有一条彩色条纹,或黑色条纹等等
 
分析
 
    毫无疑问,这种现象肯定是初始化参数设置不对,位置错位,和场同步型号或行同步信号有关,不外乎就是LCD模组和CPU上的LCD控制器的行场同步信号的宽度,前后延迟时间,极性等属性的匹配。这其中,对于图像错位,又以行场前后延迟时间的不匹配可能性最高。
 
    另外,这种情况,通常错位的像素数不会太大,如果出现错位了1/2屏之类的情况,通常就是由别的原因照成的了。
 
解决
 
    精确适配行场信号,有时候,有些LCD的行场信号的设置还和LCD驱动芯片的部分电压参数的取值设置相关。要协同修改。