s3c2410_lcd & frame buffer 驱动分析

int __init s3c2410fb_probe(struct device *dev)
{
    struct s3c2410fb_info *info;
    struct fb_info       *fbinfo;
    struct platform_device *pdev = to_platform_device(dev);
    struct s3c2410fb_hw *mregs;
    int ret;
    int irq;
    int i;

    mach_info = dev->platform_data; //获取lcd相关寄存器配置信息
    if (mach_info == NULL) {
        dev_err(dev,"no platform data for lcd, cannot attach\n");
        return -EINVAL;
    }

    mregs = &mach_info->regs;

    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(dev, "no irq for device\n");
        return -ENOENT;
    }

    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), dev); //分配struct fb_info和struct s3c2410fb_info结构
    if (!fbinfo) {
        return -ENOMEM;
    }


    info = fbinfo->par; //调用framebuffer_alloc分配内存的时候,其实顺带分配了struct s3c2410fb_info结构,紧跟struct fb_info结构的后面,fbinfo->par指针处。
    info->fb = fbinfo;
    dev_set_drvdata(dev, fbinfo);

    s3c2410fb_init_registers(info); //lcd相关寄存器配置

    dprintk("devinit\n");

    strcpy(fbinfo->fix.id, driver_name);

    /* 配置信息存储起来,以后用到 */
    memcpy(&info->regs, &mach_info->regs, sizeof(info->regs));

    info->mach_info            = dev->platform_data;

    /* 以下配置,详细请参考网上资料 */
    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.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; //重要的fop结构(fb设备本质上是字符设备)
    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;

    /* 配色板设置(采用5、6、5模式) */
    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;
    /* 计算需要申请的framer buffer大小(以字节为单位) */
    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, SA_INTERRUPT, pdev->name, info);
    if (ret) {
        dev_err(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_use(info->clk);
    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); //怎么再次初始化lcd相关寄存器了?

    ret = s3c2410fb_check_var(&fbinfo->var, fbinfo); //注册frame buffer前的一个例行检查

    ret = register_framebuffer(fbinfo);
    if (ret < 0) {
        printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
        goto free_video_memory;
    }

    /* create device files */
    device_create_file(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_unuse(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;
}


/*
  * s3c2410fb_map_video_memory():
  *    Allocates the DRAM memory for the frame buffer.  This buffer is
  *    remapped into a non-cached, non-buffered, memory region to
  *    allow palette and pixel writes to occur without flushing the
  *    cache.  Once this area is remapped, all virtual memory
  *    access to the video memory should occur at the new region.
  */
static int __init s3c2410fb_map_video_memory(struct s3c2410fb_info *fbi)
{
    dprintk("map_video_memory(fbi=%p)\n", fbi);

    fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE);//页对齐
    fbi->map_cpu  = dma_alloc_writecombine(fbi->dev, fbi->map_size,
                           &fbi->map_dma, GFP_KERNEL); //目前我只知道用于分配可供dma使用的内存,其中返回地址fbi->map_cpu为虚拟地址,fbi->map_dma为物理地址。

    fbi->map_size = fbi->fb->fix.smem_len; //恢复真实的大小

    if (fbi->map_cpu) {
        /* prevent initial garbage on screen */
        dprintk("map_video_memory: clear %p:%08x\n",
            fbi->map_cpu, fbi->map_size);
        memset(fbi->map_cpu, 0xf0, fbi->map_size);

        fbi->screen_dma        = fbi->map_dma; //物理地址
        fbi->fb->screen_base    = fbi->map_cpu; //虚拟地址
        fbi->fb->fix.smem_start  = fbi->screen_dma; //物理地址

        dprintk("map_video_memory: dma=%08x cpu=%p size=%08x\n",
            fbi->map_dma, fbi->map_cpu, fbi->fb->fix.smem_len);
    }

    return fbi->map_cpu ? 0 : -ENOMEM;
}



static struct s3c2410fb_mach_info sbc2410_lcdcfg __initdata = {
       .fixed_syncs= 0,
       .regs={
           .lcdcon1= S3C2410_LCDCON1_TFT16BPP | \
                     S3C2410_LCDCON1_TFT | \
                     S3C2410_LCDCON1_CLKVAL(6),

           .lcdcon2= S3C2410_LCDCON2_VBPD(2) | \
                     S3C2410_LCDCON2_LINEVAL(319) | \
                     S3C2410_LCDCON2_VFPD(0) | \
                     S3C2410_LCDCON2_VSPW(4),

           .lcdcon3= S3C2410_LCDCON3_HBPD(47) | \
                     S3C2410_LCDCON3_HOZVAL(239) | \
                     S3C2410_LCDCON3_HFPD(15),

           .lcdcon4= S3C2410_LCDCON4_MVAL(1) | \
                     S3C2410_LCDCON4_HSPW(31),

           .lcdcon5= S3C2410_LCDCON5_FRM565 | \
                     S3C2410_LCDCON5_INVVLINE | \
                     S3C2410_LCDCON5_HWSWP,
       },
       .lpcsel= 0x0,
       .gpccon= 0xaaaaaaaa,
       .gpccon_mask= 0xffffffff,
       .gpcup= 0xffffffff,
       .gpcup_mask= 0xffffffff,
       .gpdcon= 0xaaaaaaaa,
       .gpdcon_mask= 0x0,
       .gpdup= 0xffffffff,
       .gpdup_mask= 0xffffffff,
       .width= 240,
       .height= 320,
       .xres= {240,240,240},
       .yres= {320,320,320},
       .bpp= {16,16,16},
};


s3c2410fb.c文件中的其它部分单独来分析的话比较难理解,现结合fbmem.c来分析:
fbmem.c文件中:
#ifdef MODULE
module_init(fbmem_init);
static void __exit
fbmem_exit(void)
{
        class_destroy(fb_class);
        unregister_chrdev(FB_MAJOR, "fb");
}

module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);
#endif

驱动入口为fbmem_init:
/**
 *      fbmem_init - init frame buffer subsystem
 *
 *      Initialize the frame buffer subsystem.
 *
 *      NOTE: This function is _only_ to be called by drivers/char/mem.c.
 *
 */

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

可见,frame buffer驱动本质上是字符驱动,同样使用register_chrdev函数来注册自己。

下面再来看看frame buffer所支持的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、release、read、write、ioctl方法,细心的你可能会发现多了mmap方法,这是提供给用户映射frame buffer的接口函数(可是在底层函数s3c2410fb.c中确没有这个接口的实现代码,此乃后话)。

其中read、write接口很普通的字符驱动没什么不同,只是读写的对象变为frame buffer,纯粹的内存操作,在此我就不详细介绍了。

下面我们来研究一下fb_ioctl接口函数:

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: //获取屏的可变参数
        return copy_to_user(argp, &info->var,
                    sizeof(var)) ? -EFAULT : 0;
    case FBIOPUT_VSCREENINFO: //设置屏的可变参数
        if (copy_from_user(&var, argp, sizeof(var)))
            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: //获取屏的固定参数
        return copy_to_user(argp, &info->fix,
                    sizeof(fix)) ? -EFAULT : 0;
    case FBIOPUTCMAP: //设置调色板
        if (copy_from_user(&cmap, argp, sizeof(cmap)))
            return -EFAULT;
        return (fb_set_user_cmap(&cmap, info));
    case FBIOGETCMAP: //获取调色板信息
        if (copy_from_user(&cmap, argp, sizeof(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);
        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->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);
    }
}

注:
    s3c2410fb.c中var->activate设置为FB_ACTIVATE_NOW,所以这里只考虑条件2。

int fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
{
    int err, flags = info->flags;
1:
    if (var->activate & FB_ACTIVATE_INV_MODE) {
        struct fb_videomode mode1, mode2;
        int ret = 0;

        fb_var_to_videomode(&mode1, var);
        fb_var_to_videomode(&mode2, &info->var);
        /* make sure we don't delete the videomode of current var */
        ret = fb_mode_is_equal(&mode1, &mode2);

        if (!ret) {
            struct fb_event event;

            event.info = info;
            event.data = &mode1;
            ret = notifier_call_chain(&fb_notifier_list,
                          FB_EVENT_MODE_DELETE, &event);
        }

        if (!ret)
            fb_delete_videomode(&mode1, &info->modelist);

        return ret;
    }
2:
    if ((var->activate & FB_ACTIVATE_FORCE) ||
        memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) {
        if (!info->fbops->fb_check_var) {
            *var = info->var;
            return 0;
        }

        if ((err = info->fbops->fb_check_var(var, info))) //先检查一下设置是否符合要求
            return err;

        if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) { //条件符合
            struct fb_videomode mode;
            int err = 0;

            info->var = *var;
            if (info->fbops->fb_set_par)
                info->fbops->fb_set_par(info); //设置可变参数

            fb_pan_display(info, &info->var); //s3c2410不支持硬件虚拟显示,在s3c2410fb.c上没有实现该接口。

            fb_set_cmap(&info->cmap, info); //调色板设置

            fb_var_to_videomode(&mode, &info->var);

            if (info->modelist.prev && info->modelist.next &&
                !list_empty(&info->modelist))
                err = fb_add_videomode(&mode, &info->modelist);

            if (!err && (flags & FBINFO_MISC_USEREVENT)) {
                struct fb_event event;
                int evnt = (var->activate & FB_ACTIVATE_ALL) ?
                    FB_EVENT_MODE_CHANGE_ALL :
                    FB_EVENT_MODE_CHANGE;

                info->flags &= ~FBINFO_MISC_USEREVENT;
                event.info = info;
                notifier_call_chain(&fb_notifier_list, evnt,
                            &event);
            }
        }
    }
    return 0;
}

/*
 *      s3c2410fb_set_par - Optional function. Alters the hardware state.
 *      @info : frame buffer structure that represents a single frame buffer
 *
 */
static int s3c2410fb_set_par(struct fb_info *info)
{
    struct s3c2410fb_info *fbi = info->par;
    struct fb_var_screeninfo *var = &info->var;

    if (var->bits_per_pixel == 16)
        fbi->fb->fix.visual = FB_VISUAL_TRUECOLOR;
    else
        fbi->fb->fix.visual = FB_VISUAL_PSEUDOCOLOR;

    fbi->fb->fix.line_length     = (var->width*var->bits_per_pixel)/8;

    /* activate this new configuration */

    s3c2410fb_activate_var(fbi, var);
    return 0;
}

/* s3c2410fb_activate_var
 *
 * activate (set) the controller from the given framebuffer
 * information
*/

static void s3c2410fb_activate_var(struct s3c2410fb_info *fbi,
                   struct fb_var_screeninfo *var)
{
    fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_MODEMASK;

    dprintk("%s: var->xres  = %d\n", __FUNCTION__, var->xres);
    dprintk("%s: var->yres  = %d\n", __FUNCTION__, var->yres);
    dprintk("%s: var->bpp   = %d\n", __FUNCTION__, var->bits_per_pixel);

    switch (var->bits_per_pixel) {
    case 1:
        fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
        break;
    case 2:
        fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
        break;
    case 4:
        fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
        break;
    case 8:
        fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
        break;
    case 16:
        fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
        break;
    }

    /* check to see if we need to update sync/borders */

    if (!fbi->mach_info->fixed_syncs) {
        dprintk("setting vert: up=%d, low=%d, sync=%d\n",
            var->upper_margin, var->lower_margin,
            var->vsync_len);

        dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",
            var->left_margin, var->right_margin,
            var->hsync_len);

        fbi->regs.lcdcon2 =
            S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |
            S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
            S3C2410_LCDCON2_VSPW(var->vsync_len - 1);

        fbi->regs.lcdcon3 =
            S3C2410_LCDCON3_HBPD(var->right_margin - 1) |
            S3C2410_LCDCON3_HFPD(var->left_margin - 1);

        fbi->regs.lcdcon4 &= ~S3C2410_LCDCON4_HSPW(0xff);
        fbi->regs.lcdcon4 |=  S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
    }

    /* update X/Y info */

    fbi->regs.lcdcon2 &= ~S3C2410_LCDCON2_LINEVAL(0x3ff);
    fbi->regs.lcdcon2 |=  S3C2410_LCDCON2_LINEVAL(var->yres - 1);

    fbi->regs.lcdcon3 &= ~S3C2410_LCDCON3_HOZVAL(0x7ff);
    fbi->regs.lcdcon3 |=  S3C2410_LCDCON3_HOZVAL(var->xres - 1);

    if (var->pixclock > 0) {
        int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock);

        clkdiv = (clkdiv / 2) -1;
        if (clkdiv < 0)
            clkdiv = 0;

        fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_CLKVAL(0x3ff);
        fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);
    }

    /* write new registers */

    dprintk("new register set:\n");
    dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);
    dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);
    dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
    dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
    dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);

    writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
    writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
    writel(fbi->regs.lcdcon3, S3C2410_LCDCON3);
    writel(fbi->regs.lcdcon4, S3C2410_LCDCON4);
    writel(fbi->regs.lcdcon5, S3C2410_LCDCON5);

    /* set lcd address pointers */
    s3c2410fb_set_lcdaddr(fbi);

    writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
}


/**
 *    fb_set_cmap - set the colormap
 *    @cmap: frame buffer colormap structure
 *    @info : frame buffer info structure
 *
 *    Sets the colormap @cmap for a screen of device @info.
 *
 *    Returns negative errno on error, or zero on success.
 *
 */

int fb_set_cmap(struct fb_cmap *cmap, struct fb_info *info)
{
    int i, start, rc = 0;
    u16 *red, *green, *blue, *transp;
    u_int hred, hgreen, hblue, htransp = 0xffff;

    red = cmap->red;
    green = cmap->green;
    blue = cmap->blue;
    transp = cmap->transp;
    start = cmap->start;

    if (start < 0 || (!info->fbops->fb_setcolreg &&
              !info->fbops->fb_setcmap))
        return -EINVAL;
    if (info->fbops->fb_setcmap) {
        rc = info->fbops->fb_setcmap(cmap, info);
    } else {
        for (i = 0; i < cmap->len; i++) {
            hred = *red++;
            hgreen = *green++;
            hblue = *blue++;
            if (transp)
                htransp = *transp++;
            if (info->fbops->fb_setcolreg(start++,
                              hred, hgreen, hblue,
                              htransp, info))
                break;
        }
    }
    if (rc == 0)
        fb_copy_cmap(cmap, &info->cmap);

    return rc;
}

static int s3c2410fb_setcolreg(unsigned regno,
                   unsigned red, unsigned green, unsigned blue,
                   unsigned transp, struct fb_info *info)
{
    struct s3c2410fb_info *fbi = info->par;
    unsigned int val;

    /* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n", regno, red, green, blue); */

    switch (fbi->fb->fix.visual) {
    case FB_VISUAL_TRUECOLOR:
        /* true-colour, use pseuo-palette */

        if (regno < 16) {
            u32 *pal = fbi->fb->pseudo_palette;

            val  = chan_to_field(red,   &fbi->fb->var.red);
            val |= chan_to_field(green, &fbi->fb->var.green);
            val |= chan_to_field(blue,  &fbi->fb->var.blue);

            pal[regno] = val;
        }
        break;

    case FB_VISUAL_PSEUDOCOLOR:
        if (regno < 256) {
            /* currently assume RGB 5-6-5 mode */

            val  = ((red   >>  0) & 0xf800);
            val |= ((green >>  5) & 0x07e0);
            val |= ((blue  >> 11) & 0x001f);

            writel(val, S3C2410_TFTPAL(regno));
            schedule_palette_update(fbi, regno, val);
        }

        break;

    default:
        return 1;   /* unknown type */
    }

    return 0;
}

int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)
{
    int rc, size = cmap->len * sizeof(u16);
    struct fb_cmap umap;

    if (cmap->start < 0 || (!info->fbops->fb_setcolreg &&
                    !info->fbops->fb_setcmap))
        return -EINVAL;

    memset(&umap, 0, sizeof(struct fb_cmap));
    rc = fb_alloc_cmap(&umap, cmap->len, cmap->transp != NULL);
    if (rc)
        return rc;
    if (copy_from_user(umap.red, cmap->red, size) ||
        copy_from_user(umap.green, cmap->green, size) ||
        copy_from_user(umap.blue, cmap->blue, size) ||
        (cmap->transp && copy_from_user(umap.transp, cmap->transp, size))) {
        fb_dealloc_cmap(&umap);
        return -EFAULT;
    }
    umap.start = cmap->start;
    rc = fb_set_cmap(&umap, info);
    fb_dealloc_cmap(&umap);
    return rc;
}

int fb_cmap_to_user(struct fb_cmap *from, struct fb_cmap_user *to)
{
    int tooff = 0, fromoff = 0;
    int size;

    if (to->start > from->start)
        fromoff = to->start - from->start;
    else
        tooff = from->start - to->start;
    size = to->len - tooff;
    if (size > (int) (from->len - fromoff))
        size = from->len - fromoff;
    if (size <= 0)
        return -EINVAL;
    size *= sizeof(u16);

    if (copy_to_user(to->red+tooff, from->red+fromoff, size))
        return -EFAULT;
    if (copy_to_user(to->green+tooff, from->green+fromoff, size))
        return -EFAULT;
    if (copy_to_user(to->blue+tooff, from->blue+fromoff, size))
        return -EFAULT;
    if (from->transp && to->transp)
        if (copy_to_user(to->transp+tooff, from->transp+fromoff, size))
            return -EFAULT;
    return 0;
}

int
fb_blank(struct fb_info *info, int blank)
{   
     int ret = -EINVAL;

     if (blank > FB_BLANK_POWERDOWN)
         blank = FB_BLANK_POWERDOWN;

    if (info->fbops->fb_blank)
         ret = info->fbops->fb_blank(blank, info);

     if (!ret) {
        struct fb_event event;

        event.info = info;
        event.data = &blank;
        notifier_call_chain(&fb_notifier_list, FB_EVENT_BLANK, &event);
    }

     return ret;
}

/**
 *      s3c2410fb_blank
 *    @blank_mode: the blank mode we want.
 *    @info : frame buffer structure that represents a single frame buffer
 *
 *    Blank the screen if blank_mode != 0, else unblank. Return 0 if
 *    blanking succeeded, != 0 if un-/blanking failed due to e.g. a
 *    video mode which doesn't support it. Implements VESA suspend
 *    and powerdown modes on hardware that supports disabling hsync/vsync:
 *    blank_mode == 2: suspend vsync
 *    blank_mode == 3: suspend hsync
 *    blank_mode == 4: powerdown
 *
 *    Returns negative errno on error, or zero on success.
 *
 */
static int s3c2410fb_blank(int blank_mode, struct fb_info *info)
{
    dprintk("blank(mode=%d, info=%p)\n", blank_mode, info);

    if (mach_info == NULL)
        return -EINVAL;

    if (blank_mode == FB_BLANK_UNBLANK)
        writel(0x0, S3C2410_TPAL);
    else {
        dprintk("setting TPAL to output 0x000000\n");
        writel(S3C2410_TPAL_EN, S3C2410_TPAL);
    }

    return 0;
}

static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
    int fbidx = iminor(file->f_dentry->d_inode);
    struct fb_info *info = registered_fb[fbidx];
    struct fb_ops *fb = info->fbops;
    unsigned long off;
#if !defined(__sparc__) || defined(__sparc_v9__)
    unsigned long start;
    u32 len;
#endif

    if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
        return -EINVAL;
    off = vma->vm_pgoff << PAGE_SHIFT;
    if (!fb)
        return -ENODEV;
    if (fb->fb_mmap) {
        int res;
        lock_kernel();
        res = fb->fb_mmap(info, file, vma);
        unlock_kernel();
        return res;
    }
/* 所有的驱动应该拥有自己的mmap实现,可惜的是s3c2410fb.c中没有实现mmap接口,以下内容我也不想去研究了。 */

#if defined(__sparc__) && !defined(__sparc_v9__)
    /* Should never get here, all fb drivers should have their own
       mmap routines */
    return -EINVAL;
#else
    /* !sparc32... */
    lock_kernel();

    /* frame buffer memory */
    start = info->fix.smem_start;
    len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
    if (off >= len) {
        /* memory mapped io */
        off -= len;
        if (info->var.accel_flags) {
            unlock_kernel();
            return -EINVAL;
        }
        start = info->fix.mmio_start;
        len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
    }
    unlock_kernel();
    start &= PAGE_MASK;
    if ((vma->vm_end - vma->vm_start + off) > len)
        return -EINVAL;
    off += start;
    vma->vm_pgoff = off >> PAGE_SHIFT;
    /* This is an IO map - tell maydump to skip this VMA */
    vma->vm_flags |= VM_IO | VM_RESERVED;
#if defined(__sparc_v9__)
    if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
                vma->vm_end - vma->vm_start, vma->vm_page_prot))
        return -EAGAIN;
#else
#if defined(__mc68000__)
#if defined(CONFIG_SUN3)
    pgprot_val(vma->vm_page_prot) |= SUN3_PAGE_NOCACHE;
#elif defined(CONFIG_MMU)
    if (CPU_IS_020_OR_030)
        pgprot_val(vma->vm_page_prot) |= _PAGE_NOCACHE030;
    if (CPU_IS_040_OR_060) {
        pgprot_val(vma->vm_page_prot) &= _CACHEMASK040;
        /* Use no-cache mode, serialized */
        pgprot_val(vma->vm_page_prot) |= _PAGE_NOCACHE_S;
    }
#endif
#elif defined(__powerpc__)
    vma->vm_page_prot = phys_mem_access_prot(file, off,
                         vma->vm_end - vma->vm_start,
                         vma->vm_page_prot);
#elif defined(__alpha__)
    /* Caching is off in the I/O space quadrant by design.  */
#elif defined(__i386__) || defined(__x86_64__)
    if (boot_cpu_data.x86 > 3)
        pgprot_val(vma->vm_page_prot) |= _PAGE_PCD;
#elif defined(__mips__)
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#elif defined(__hppa__)
    pgprot_val(vma->vm_page_prot) |= _PAGE_NO_CACHE;
#elif defined(__arm__) || defined(__sh__) || defined(__m32r__)
    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
#elif defined(__ia64__)
    if (efi_range_is_wc(vma->vm_start, vma->vm_end - vma->vm_start))
        vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
    else
        vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#else
#warning What do we have to do here??
#endif
    if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
                 vma->vm_end - vma->vm_start, vma->vm_page_prot))
        return -EAGAIN;
#endif /* !__sparc_v9__ */
    return 0;
#endif /* !sparc32 */
}

以下以内核自带的logo程序,介绍一下frame buffer的具体用法:

static struct logo_data {
    int depth;
    int needs_directpalette;
    int needs_truepalette;
    int needs_cmapreset;
    const struct linux_logo *logo;
} fb_logo;
注:
    logo图像数据结构.


/* 填充好logo显示用的数据 */
int fb_prepare_logo(struct fb_info *info)
{
    int depth = fb_get_color_depth(&info->var, &info->fix);//16

    memset(&fb_logo, 0, sizeof(struct logo_data));

    if (info->flags & FBINFO_MISC_TILEBLITTING)
        return 0;

    if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
        depth = info->var.blue.length;
        if (info->var.red.length < depth)
            depth = info->var.red.length;
        if (info->var.green.length < depth)
            depth = info->var.green.length;
    }

    if (depth >= 8) {
        switch (info->fix.visual) {
        case FB_VISUAL_TRUECOLOR:
            fb_logo.needs_truepalette = 1; //使用tft屏的话就选择这个吧.
            break;
        case FB_VISUAL_DIRECTCOLOR:
            fb_logo.needs_directpalette = 1;
            fb_logo.needs_cmapreset = 1;
            break;
        case FB_VISUAL_PSEUDOCOLOR:
            fb_logo.needs_cmapreset = 1;
            break;
        }
    }

    /* Return if no suitable logo was found */
    fb_logo.logo = fb_find_logo(depth); //根据内核的配置,选择合适的logo.
   
    if (!fb_logo.logo || fb_logo.logo->height > info->var.yres) {
        fb_logo.logo = NULL;
        return 0;
    }
    /* What depth we asked for might be different from what we get */
    if (fb_logo.logo->type == LINUX_LOGO_CLUT224) //在我的内核中选的就是这个.
        fb_logo.depth = 8;
    else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
        fb_logo.depth = 4;
    else
        fb_logo.depth = 1;       
    return fb_logo.logo->height;
}


好了,准备好logo显示用的数据后,在适当的时候即可调用以下函数来显示:
int fb_show_logo(struct fb_info *info)
{
    u32 *palette = NULL, *saved_pseudo_palette = NULL;
    unsigned char *logo_new = NULL;
    struct fb_image image;
    int x;

    /* Return if the frame buffer is not mapped or suspended */
    if (fb_logo.logo == NULL || info->state != FBINFO_STATE_RUNNING)
        return 0;

    image.depth = 8;
    image.data = fb_logo.logo->data;

    if (fb_logo.needs_cmapreset)
        fb_set_logocmap(info, fb_logo.logo);

    if (fb_logo.needs_truepalette ||
        fb_logo.needs_directpalette) {
        palette = kmalloc(256 * 4, GFP_KERNEL); //分配调色板所需的空间.
        if (palette == NULL)
            return 0;

        if (fb_logo.needs_truepalette)
            fb_set_logo_truepalette(info, fb_logo.logo, palette); //根据我们所找到的logo图像填充调色板
        else
            fb_set_logo_directpalette(info, fb_logo.logo, palette);

        saved_pseudo_palette = info->pseudo_palette;
        info->pseudo_palette = palette;
    }

    if (fb_logo.depth <= 4) {
        logo_new = kmalloc(fb_logo.logo->width * fb_logo.logo->height,
                   GFP_KERNEL);
        if (logo_new == NULL) {
            kfree(palette);
            if (saved_pseudo_palette)
                info->pseudo_palette = saved_pseudo_palette;
            return 0;
        }
        image.data = logo_new;
        fb_set_logo(info, fb_logo.logo, logo_new, fb_logo.depth);
    }

    image.width = fb_logo.logo->width;
    image.height = fb_logo.logo->height;
    image.dy = 0;

    /* 以下循环用于查询调色板,把logo信息填写到相应的frame buffer中 */
    for (x = 0; x < num_online_cpus() * (fb_logo.logo->width + 8) &&
         x <= info->var.xres-fb_logo.logo->width; x += (fb_logo.logo->width + 8)) {
        image.dx = x;
        info->fbops->fb_imageblit(info, &image);
    }
   
    kfree(palette);
    if (saved_pseudo_palette != NULL)
        info->pseudo_palette = saved_pseudo_palette;
    kfree(logo_new);
    return fb_logo.logo->height;

}

static struct fb_ops s3c2410fb_ops = {
    .owner        = THIS_MODULE,
    .fb_check_var    = s3c2410fb_check_var,
    .fb_set_par    = s3c2410fb_set_par,
    .fb_blank    = s3c2410fb_blank,
    .fb_setcolreg    = s3c2410fb_setcolreg,
    .fb_fillrect    = cfb_fillrect,
    .fb_copyarea    = cfb_copyarea,
    .fb_imageblit    = cfb_imageblit,
    .fb_cursor    = soft_cursor,
};

void cfb_imageblit(struct fb_info *p, const struct fb_image *image)
{
    u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0;
    u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel;
    u32 width = image->width, height = image->height;
    u32 dx = image->dx, dy = image->dy;
    int x2, y2, vxres, vyres;
    u8 __iomem *dst1;

    if (p->state != FBINFO_STATE_RUNNING)
        return;

    vxres = p->var.xres_virtual;
    vyres = p->var.yres_virtual;
    /*
     * We could use hardware clipping but on many cards you get around
     * hardware clipping by writing to framebuffer directly like we are
     * doing here.
     */
    if (image->dx > vxres || image->dy > vyres)
        return;

    x2 = image->dx + image->width;
    y2 = image->dy + image->height;
    dx = image->dx > 0 ? image->dx : 0;
    dy = image->dy > 0 ? image->dy : 0;
    x2 = x2 < vxres ? x2 : vxres;
    y2 = y2 < vyres ? y2 : vyres;
    width  = x2 - dx;
    height = y2 - dy;

    bitstart = (dy * p->fix.line_length * 8) + (dx * bpp); //算出要绘图的起始点位置(以bit为单位)
    start_index = bitstart & (32 - 1); //非字对齐地址
    pitch_index = (p->fix.line_length & (bpl - 1)) * 8;

    bitstart /= 8;
    bitstart &= ~(bpl - 1);
    dst1 = p->screen_base + bitstart; //图像在frame buffer上的地址(目标地址)

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

    if (image->depth == 1) {
        if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
            p->fix.visual == FB_VISUAL_DIRECTCOLOR) {
            fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color];
            bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color];
        } else {
            fgcolor = image->fg_color;
            bgcolor = image->bg_color;
        }   
       
        if (32 % bpp == 0 && !start_index && !pitch_index &&
            ((width & (32/bpp-1)) == 0) &&
            bpp >= 8 && bpp <= 32)            
            fast_imageblit(image, p, dst1, fgcolor, bgcolor);
        else
            slow_imageblit(image, p, dst1, fgcolor, bgcolor,
                    start_index, pitch_index);
    } else
        color_imageblit(image, p, dst1, start_index, pitch_index);
}

static inline void color_imageblit(const struct fb_image *image,
                   struct fb_info *p, u8 __iomem *dst1,
                   u32 start_index,
                   u32 pitch_index)
{
    /* Draw the penguin */
    u32 __iomem *dst, *dst2;
    u32 color = 0, val, shift;
    int i, n, bpp = p->var.bits_per_pixel;
    u32 null_bits = 32 - bpp;
    u32 *palette = (u32 *) p->pseudo_palette;
    const u8 *src = image->data; //image->data里的数据,实际上记录着的是调色板上对应颜色的序号.

    dst2 = (u32 __iomem *) dst1;
    for (i = image->height; i--; ) { //行
        n = image->width;
        dst = (u32 __iomem *) dst1;
        shift = 0;
        val = 0;
       
        if (start_index) {
            u32 start_mask = ~(SHIFT_HIGH(~(u32)0, start_index));
            val = FB_READL(dst) & start_mask;
            shift = start_index;
        }
        while (n--) { //列
            if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
                p->fix.visual == FB_VISUAL_DIRECTCOLOR )
                color = palette[*src]; //调色板在这个时候终于派上用场了
            else
                color = *src;
            color <<= LEFT_POS(bpp);.
            val |= SHIFT_HIGH(color, shift);
            if (shift >= null_bits) {
                FB_WRITEL(val, dst++);
   
                val = (shift == null_bits) ? 0 :
                    SHIFT_LOW(color, 32 - shift);
            }
            shift += bpp;
            shift &= (32 - 1);
            src++;
        }
        if (shift) {
            u32 end_mask = SHIFT_HIGH(~(u32)0, shift);

            FB_WRITEL((FB_READL(dst) & end_mask) | val, dst);
        }
        dst1 += p->fix.line_length;
        if (pitch_index) {
            dst2 += p->fix.line_length;
            dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1));

            start_index += pitch_index;
            start_index &= 32 - 1;
        }
    }
}

你可能感兴趣的:(s3c2410_lcd & frame buffer 驱动分析)