基于S3C2440的嵌入式Linux驱动——Framebuffer子系统解读

来源:互联网 发布:网络博客游戏破解之法 编辑:程序博客网 时间:2024/05/19 22:50

本文将介绍Framebuffer子系统

目标平台:TQ2440 CPU:s3c2440

LCD设备:3.5英寸,分辨率320X240


1. 概述

Framebuffer,中文名字是帧缓冲,这个帧也就是一副图像所需要的数据。因此,帧缓冲其实就是LCD设备的驱动程序。Linux中,framebuffer子系统框架如下:


核心层的代码以fbmem.c为主,核心层包括许多与具体硬件无关的代码,并且提供了API给用户空间。用户空间使用系统调用,系统调用会使用相应的API函数,最后会调用驱动层实现功能。对于不同的设备,驱动层的代码将有所不同。

接下来的内容中,首先给出framerbuffer使用的数据结构;随后简单描述framerbuffer核心层;最后,针对S3C2440,对驱动代码进行分析。

2. 数据结构

2.1 fb_info 结构

  该结构是内核用于描述一个特定framebuffer设备。其中包含了几个重要的结构,将在下面介绍。

  下列代码位于include/linux/fb.h

struct fb_info {int node;int flags;struct mutex lock;/* Lock for open/release/ioctl funcs */struct fb_var_screeninfo var;/* Current var */struct fb_fix_screeninfo fix;/* Current fix */struct fb_monspecs monspecs;/* Current Monitor specs */struct work_struct queue;/* Framebuffer event queue */struct fb_pixmap pixmap;/* Image hardware mapper */struct fb_pixmap sprite;/* Cursor hardware mapper */struct fb_cmap cmap;/* Current cmap */struct list_head modelist;      /* mode list */struct fb_videomode *mode;/* current mode */#ifdef CONFIG_FB_BACKLIGHT/* assigned backlight device *//* set before framebuffer registration,    remove after unregister */struct backlight_device *bl_dev;/* Backlight level curve */struct mutex bl_curve_mutex;u8 bl_curve[FB_BACKLIGHT_LEVELS];#endif#ifdef CONFIG_FB_DEFERRED_IOstruct delayed_work deferred_work;struct fb_deferred_io *fbdefio;#endifstruct fb_ops *fbops;struct device *device;/* This is the parent */struct device *dev;/* This is this fb device */int class_flag;                    /* private sysfs flags */#ifdef CONFIG_FB_TILEBLITTINGstruct fb_tile_ops *tileops;    /* Tile Blitting */#endifchar __iomem *screen_base;/* Virtual address */unsigned long screen_size;/* Amount of ioremapped VRAM or 0 */ void *pseudo_palette;/* Fake palette of 16 colors */ #define FBINFO_STATE_RUNNING0#define FBINFO_STATE_SUSPENDED1u32 state;/* Hardware state i.e suspend */void *fbcon_par;                /* fbcon use-only private area *//* From here on everything is device dependent */void *par;};

2.2 fb_fix_screeninfo结构

  该数据结构是不可改变的,也就是说用户空间不能改变该结构中的任何成员,在核心层我们将会看到这点。

  下列代码位于include/linux/fb.h

struct fb_fix_screeninfo {char id[16];/* identification string eg "TT Builtin" */unsigned long smem_start;/* Start of frame buffer mem *//* (physical address) */__u32 smem_len;/* Length of frame buffer mem */__u32 type;/* see FB_TYPE_**/__u32 type_aux;/* Interleave for interleaved Planes */__u32 visual;/* see FB_VISUAL_**/ __u16 xpanstep;/* zero if no hardware panning  */__u16 ypanstep;/* zero if no hardware panning  */__u16 ywrapstep;/* zero if no hardware ywrap    */__u32 line_length;/* length of a line in bytes    */unsigned long mmio_start;/* Start of Memory Mapped I/O   *//* (physical address) */__u32 mmio_len;/* Length of Memory Mapped I/O  */__u32 accel;/* Indicate to driver which*//*  specific chip/card we have*/__u16 reserved[3];/* Reserved for future compatibility */};

2.3 fb_var_screeninfo结构

  该数据结构是可以改变的,也就是说用户空间可以改变该结构中的成员。该数据结构中的很多成员就由板级信息复制而来,在驱动代码中我们将会看到这点。

  下列代码位于include/linux/fb.h

struct fb_var_screeninfo {__u32 xres;/* visible resolution*/__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 */struct fb_bitfield red;/* bitfield in fb mem if true color, */struct fb_bitfield green;/* else only length is significant */struct fb_bitfield blue;struct fb_bitfield transp;/* transparency*/__u32 nonstd;/* != 0 Non standard pixel format */__u32 activate;/* see FB_ACTIVATE_**/__u32 height;/* height of picture in mm    */__u32 width;/* width of picture in mm     */__u32 accel_flags;/* (OBSOLETE) see fb_info.flags *//* Timing: All values in pixclocks, except pixclock (of course) */__u32 pixclock;/* pixel clock in ps (pico seconds) */__u32 left_margin;/* time from sync to picture*/__u32 right_margin;/* time from picture to sync*/__u32 upper_margin;/* time from sync to picture*/__u32 lower_margin;__u32 hsync_len;/* length of horizontal sync*/__u32 vsync_len;/* length of vertical sync*/__u32 sync;/* see FB_SYNC_**/__u32 vmode;/* see FB_VMODE_**/__u32 rotate;/* angle we rotate counter clockwise */__u32 reserved[5];/* Reserved for future compatibility */};

2.4 fb_ops结构

  该结构描述了用于fb_info的方法,这些方法中有些是要驱动程序提供的,而有些可以使用内核提供的方法。

 下列代码位于include/linux/fb.h

/* * Frame buffer operations * * LOCKING NOTE: those functions must _ALL_ be called with the console * semaphore held, this is the only suitable locking mechanism we have * in 2.6. Some may be called at interrupt time at this point though. */struct fb_ops {/* open/release and usage marking */struct module *owner;int (*fb_open)(struct fb_info *info, int user);int (*fb_release)(struct fb_info *info, int user);/* For framebuffers with strange non linear layouts or that do not * work with normal memory mapped access */ssize_t (*fb_read)(struct fb_info *info, char __user *buf,   size_t count, loff_t *ppos);ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,    size_t count, loff_t *ppos);/* checks var and eventually tweaks it to something supported, * DO NOT MODIFY PAR */int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);/* set the video mode according to info->var */int (*fb_set_par)(struct fb_info *info);/* set color register */int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,    unsigned blue, unsigned transp, struct fb_info *info);/* set color registers in batch */int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);/* blank display */int (*fb_blank)(int blank, struct fb_info *info);/* pan display */int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);/* Draws a rectangle */void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);/* Copy data from area to another */void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);/* Draws a image to the display */void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);/* Draws cursor */int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);/* Rotates the display */void (*fb_rotate)(struct fb_info *info, int angle);/* wait for blit idle, optional */int (*fb_sync)(struct fb_info *info);/* perform fb specific ioctl (optional) */int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,unsigned long arg);/* Handle 32bit compat ioctl (optional) */int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,unsigned long arg);/* perform fb specific mmap */int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);/* save current hardware state */void (*fb_save_state)(struct fb_info *info);/* restore saved state */void (*fb_restore_state)(struct fb_info *info);/* get capability given var */void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,    struct fb_var_screeninfo *var);};


3. frambuffer核心层

首先来看下frmaebuffer子系统的初始化函数。

3.1 fbmem_init和fbmem_exit

下列代码位于drivers/video/fbmem.c

/** *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 __initfbmem_init(void){proc_create("fb", 0, NULL, &fb_proc_fops);if (register_chrdev(FB_MAJOR,"fb",&fb_fops))/*注册字符设备,major=29*/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;}#ifdef MODULEmodule_init(fbmem_init);static void __exitfbmem_exit(void){remove_proc_entry("fb", NULL);class_destroy(fb_class);unregister_chrdev(FB_MAJOR, "fb");}module_exit(fbmem_exit);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Framebuffer base");#elsesubsys_initcall(fbmem_init);#endifstatic 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};

我们看到,如果不是作为模块,那么该初始化程序将在subsys_initcall阶段被调用。初始化时,仅仅注册了一个字符设备,并创建了一个类。通过字符设备,提供了API给用户空间,包open,release,write,read等。

随后我们看看如何分配一个fb_info结构。

3.2 framebuffer_alloc

下列代码位于drivers/video/fbmem.c

/** * framebuffer_alloc - creates a new frame buffer info structure * * @size: size of driver private data, can be zero * @dev: pointer to the device for this fb, this can be NULL * * Creates a new frame buffer info structure. Also reserves @size bytes * for driver private data (info->par). info->par (if any) will be * aligned to sizeof(long). * * Returns the new structure, or NULL if an error occured. * */struct fb_info *framebuffer_alloc(size_t size, struct device *dev){#define BYTES_PER_LONG (BITS_PER_LONG/8)#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))int fb_info_size = sizeof(struct fb_info);struct fb_info *info;char *p;if (size)fb_info_size += PADDING;p = kzalloc(fb_info_size + size, GFP_KERNEL);if (!p)return NULL;info = (struct fb_info *) p;if (size)info->par = p + fb_info_size;info->device = dev;#ifdef CONFIG_FB_BACKLIGHTmutex_init(&info->bl_curve_mutex);#endifreturn info;#undef PADDING#undef BYTES_PER_LONG}EXPORT_SYMBOL(framebuffer_alloc);
在进行分配时,根据参数size的大小,分配了b_info_size + size的空间,然后让fb_info->par指向size的空间。因此par所指向的空间可视为设备特有的数据。

在分配了fb_info结构之后,需要将它注册到内核中。注册由register_framebuffer完成。我们来看下。

3.3 register_framebuffer

下列代码位于drivers/video/fbmem.c

/** *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. * */intregister_framebuffer(struct fb_info *fb_info){int i;struct fb_event event;struct fb_videomode mode;if (num_registered_fb == FB_MAX) /*最多32个FB*/return -ENXIO;if (fb_check_foreignness(fb_info))return -ENOSYS;num_registered_fb++;/*对注册的FB计数*//*寻找第一个空位*/for (i = 0 ; i < FB_MAX; i++)/*FB_MAX=32,也就是最多32个framebuffer*/if (!registered_fb[i])/*struct fb_info *registered_fb[FB_MAX]*/break;fb_info->node = i;mutex_init(&fb_info->lock);/*初始化互斥体*/fb_info->dev = device_create(fb_class, fb_info->device,/*创建设备节点,节点名为fbx*/     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);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;} elsefb_init_device(fb_info);/*初始化,在class/graphics/fbx/下创建设备属性*/if (fb_info->pixmap.addr == NULL) {fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);/*分配内存,1024 * 8字节*/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_var_screeninfo成fb_videomode*/fb_add_videomode(&mode, &fb_info->modelist);/*添加mode至链表中*/registered_fb[i] = fb_info;event.info = fb_info;if (!lock_fb_info(fb_info))return -ENODEV;fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);/*???*/unlock_fb_info(fb_info);return 0;}

从这个函数我们可以看出,framebuffer子系统只支持32个设备。在创建了设备节点以后,建立设备属性节点,随后将fb_var_screeninfo转换成fb_videomode,最后添加fb_videomode至链表中。

我们看下其中调用的函数,首先是fb_init_device。

下列代码位于drivers/video/fbsysfs.c

int fb_init_device(struct fb_info *fb_info){int i, error = 0;dev_set_drvdata(fb_info->dev, fb_info);fb_info->class_flag |= FB_SYSFS_FLAG_ATTR;/*建立设备属性*/for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {error = device_create_file(fb_info->dev, &device_attrs[i]);if (error)break;}if (error) {while (--i >= 0)device_remove_file(fb_info->dev, &device_attrs[i]);fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;}return 0;}/* When cmap is added back in it should be a binary attribute * not a text one. Consideration should also be given to converting * fbdev to use configfs instead of sysfs */static struct device_attribute device_attrs[] = {    __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),    __ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),    __ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console),    __ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor),    __ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode),    __ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes),    __ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan),    __ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual),    __ATTR(name, S_IRUGO, show_name, NULL),    __ATTR(stride, S_IRUGO, show_stride, NULL),    __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate),    __ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),#ifdef CONFIG_FB_BACKLIGHT    __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),#endif};

我们可以在/sys/class/graphics/fb0下发现这些属性文件。

[root@yj423 fb0]#pwd
/sys/class/graphics/fb0
[root@yj423 fb0]#ls
bits_per_pixel  cursor          mode            pan             state           uevent
blank           dev             modes           power           stride          virtual_size
console         device          name            rotate          subsystem


接着看下fb_var_to_videomode和fb_add_videomode函数。

下列代码位于drivers/video/modedb.c和drivers/video/fb.h

struct fb_videomode {const char *name;/* optional */u32 refresh;/* optional */u32 xres;u32 yres;u32 pixclock;u32 left_margin;u32 right_margin;u32 upper_margin;u32 lower_margin;u32 hsync_len;u32 vsync_len;u32 sync;u32 vmode;u32 flag;};/** * fb_var_to_videomode - convert fb_var_screeninfo to fb_videomode * @mode: pointer to struct fb_videomode * @var: pointer to struct fb_var_screeninfo */void fb_var_to_videomode(struct fb_videomode *mode,             const struct fb_var_screeninfo *var){    u32 pixclock, hfreq, htotal, vtotal;    mode->name = NULL;    mode->xres = var->xres;    mode->yres = var->yres;    mode->pixclock = var->pixclock;    mode->hsync_len = var->hsync_len;    mode->vsync_len = var->vsync_len;    mode->left_margin = var->left_margin;    mode->right_margin = var->right_margin;    mode->upper_margin = var->upper_margin;    mode->lower_margin = var->lower_margin;    mode->sync = var->sync;    mode->vmode = var->vmode & FB_VMODE_MASK;    mode->flag = FB_MODE_IS_FROM_VAR;    mode->refresh = 0;    if (!var->pixclock)        return;    pixclock = PICOS2KHZ(var->pixclock) * 1000;    htotal = var->xres + var->right_margin + var->hsync_len +        var->left_margin;    vtotal = var->yres + var->lower_margin + var->vsync_len +        var->upper_margin;    if (var->vmode & FB_VMODE_INTERLACED)        vtotal /= 2;    if (var->vmode & FB_VMODE_DOUBLE)        vtotal *= 2;    hfreq = pixclock/htotal;    mode->refresh = hfreq/vtotal;}/** * fb_add_videomode: adds videomode entry to modelist * @mode: videomode to add * @head: struct list_head of modelist * * NOTES: * Will only add unmatched mode entries */int fb_add_videomode(const struct fb_videomode *mode, struct list_head *head){    struct list_head *pos;    struct fb_modelist *modelist;d    struct fb_videomode *m;    int found = 0;    /*遍历所有的fb_modelist,查找mode是否存在*/    list_for_each(pos, head) {        modelist = list_entry(pos, struct fb_modelist, list);        m = &modelist->mode;        if (fb_mode_is_equal(m, mode)) {    /*比较两个fb_videomode*/            found = 1;                        /*该fb_videomode已存在*/            break;        }    }    if (!found) {    /*不存在*/        modelist = kmalloc(sizeof(struct fb_modelist),    /*分配fb_modelist*/                          GFP_KERNEL);        if (!modelist)            return -ENOMEM;        modelist->mode = *mode;            /*保存mode*/        list_add(&modelist->list, head);/*添加mode至链表中*/    }    return 0;}/** * fb_mode_is_equal - compare 2 videomodes * @mode1: first videomode * @mode2: second videomode * * RETURNS: * 1 if equal, 0 if not */int fb_mode_is_equal(const struct fb_videomode *mode1,             const struct fb_videomode *mode2){    return (mode1->xres         == mode2->xres &&        mode1->yres         == mode2->yres &&        mode1->pixclock     == mode2->pixclock &&        mode1->hsync_len    == mode2->hsync_len &&        mode1->vsync_len    == mode2->vsync_len &&        mode1->left_margin  == mode2->left_margin &&        mode1->right_margin == mode2->right_margin &&        mode1->upper_margin == mode2->upper_margin &&        mode1->lower_margin == mode2->lower_margin &&        mode1->sync         == mode2->sync &&        mode1->vmode        == mode2->vmode);}

fb_var_to_videomode函数只是将fb_var_screeninfo结构转换成fb_videomode结构。而fb_add_videomode函数查找是否该fb_videomode已经存在,如果不存在则添加到列表中。

3.4 字符设备方法

  在看过framebuffer子系统建立和注册过程后,我们看下framebuffer留给用户空间的API是怎样实现的。

本小结只分析5个常用的方法,即open,release,read,write和ioctl。

  因为所有的方法和struct fb_ops定义的方法有紧密的联系,而该结构的定义由驱动程序给出,在这里我们提前看下在驱动中是如何定义的。

 下列代码位于drivers/video/s3c2410fb..c

static struct fb_ops s3c2410fb_ops = {.owner= THIS_MODULE,.fb_check_var= s3c2410fb_check_var,        /*检查变量的合法性*/.fb_set_par= s3c2410fb_set_par,/*将参数写入LCD控制器,该函数由帧缓冲核心调用*/.fb_blank= s3c2410fb_blank,/*该方法支持显示消隐和去消隐*/.fb_setcolreg= s3c2410fb_setcolreg,                  /*设置颜色寄存器*/.fb_fillrect= cfb_fillrect,/*用像素行填充矩形框,通用库函数*/.fb_copyarea= cfb_copyarea,/*将屏幕的一个矩形区域复制到另一个区域,通用库函数*/.fb_imageblit= cfb_imageblit,/*显示一副图像,通用库函数*/};
最下面的三个方法使用的是内核提供的库函数,而上面4个则是由驱动提供。

3.4.1 open方法

下列代码位于drivers/video/fbmem.c

static intfb_open(struct inode *inode, struct file *file)__acquires(&info->lock)__releases(&info->lock){int fbidx = iminor(inode);struct fb_info *info;int res = 0;if (fbidx >= FB_MAX)return -ENODEV;info = registered_fb[fbidx];/*在register_framebuffer函数中已经设置了元素*/if (!info)request_module("fb%d", fbidx);/*加载模块,这里不加载*/info = registered_fb[fbidx];if (!info)return -ENODEV;mutex_lock(&info->lock);/*加锁互斥体*/if (!try_module_get(info->fbops->owner)) {/*增加模块引用计数*/res = -ENODEV;goto out;}file->private_data = info;/*保存info*/if (info->fbops->fb_open) {/*这里fb_open方法为空*/res = info->fbops->fb_open(info,1);if (res)module_put(info->fbops->owner);}#ifdef CONFIG_FB_DEFERRED_IOif (info->fbdefio)fb_deferred_io_open(info, inode, file);#endifout:mutex_unlock(&info->lock);/*解锁互斥体*/return res;}
主要的一个工作就是增加模块引用计数。还有,程序会判断是否fb_open在驱动中给出,如果有则调用该方法。我们已经知道fb_open没有给出。

3.4.2 release方法

下列代码位于drivers/video/fbmem.c

static int fb_release(struct inode *inode, struct file *file)__acquires(&info->lock)__releases(&info->lock){struct fb_info * const info = file->private_data;mutex_lock(&info->lock);if (info->fbops->fb_release)/*这里fb_release为空*/info->fbops->fb_release(info,1);module_put(info->fbops->owner);/*减少模块引用计数*/mutex_unlock(&info->lock);return 0;}

和open相反,减少模块引用计数。

3.4.3 write方法

  通过调用该方法,LCD将显示画面。

  下列代码位于drivers/video/fbmem.c

static ssize_tfb_write(struct file *file, const 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];    u32 *buffer, *src;    u32 __iomem *dst;    int c, i, cnt = 0, err = 0;    unsigned long total_size;    if (!info || !info->screen_base)    /*screen_base在驱动中给出*/        return -ENODEV;    if (info->state != FBINFO_STATE_RUNNING)        return -EPERM;    if (info->fbops->fb_write)    /*没有fb_write方法*/        return info->fbops->fb_write(info, buf, count, ppos);        total_size = info->screen_size;    /*screen_size没有给出*/    if (total_size == 0)        total_size = info->fix.smem_len;/*153600字节,驱动probe方法中计算*/    if (p > total_size)        return -EFBIG;    if (count > total_size) {    /*要写入的字节数大于153600*/        err = -EFBIG;        /*file too big*/        count = total_size;    }    if (count + p > total_size) {/*偏移量加上字节数超出了缓冲区*/        if (!err)            err = -ENOSPC;        count = total_size - p;    }    /*分配buffer,GFP_KERNEL*/    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)    /*没有定义fb_sync*/        info->fbops->fb_sync(info);    while (count) {        c = (count > PAGE_SIZE) ? PAGE_SIZE : count;        src = buffer;        /*从buf(用户空间)拷贝数据到src中,一开始为1页,最后为count字节*/        if (copy_from_user(src, buf, c)) {                err = -EFAULT;            break;        }        /*一次for循环,写入4个字节数据到dst处*/        for (i = c >> 2; i--; )            fb_writel(*src++, dst++);        /*最后还有3个,2个或者1个字节*/        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;    /*减去1页*/    }    kfree(buffer);    /*释放buffer*/    return (cnt) ? cnt : err;}

这里,做了一系列的检查之后,开始拷贝数据。这里一个有三个buffer,一个是用户空间提供的buf,一个是在这里新开辟的buffer,还有就是驱动层提供的screen_base。

数据流如下:
 

用户空间的数据首先被复制到buffer中,然后从buffer中复制到screen_base中,最后被映射到LCD上,LCD就显示响应的画面了。

3.4.4 read方法

该方法用于读取屏幕画面的数据。

read和write类似,只是数据流是反响的,就不多做介绍了。

下列代码位于drivers/video/fbmem.c

static ssize_tfb_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];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_read*/return info->fbops->fb_read(info, 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;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);/*没有定义fb_sync*/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;}

3.4.5 ioctl方法

这里只是简单的看下ioctl方法,这个函数调用很多其他的函数,详细的请自己看吧。

下列代码位于drivers/video/fbmem.c

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg){/*获取inode,再获取对应的fb_info*/struct inode *inode = file->f_path.dentry->d_inode;int fbidx = iminor(inode);struct fb_info *info = registered_fb[fbidx];return do_fb_ioctl(info, cmd, arg);}static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,            unsigned long arg){    struct fb_ops *fb;    struct fb_var_screeninfo var;    struct fb_fix_screeninfo fix;    struct fb_con2fbmap con2fb;    struct fb_cmap cmap_from;    struct fb_cmap_user cmap;    struct fb_event event;    void __user *argp = (void __user *)arg;    long ret = 0;    switch (cmd) {    /*获取fb_var_screeninfo*/    case FBIOGET_VSCREENINFO:            if (!lock_fb_info(info))    /*加锁互斥体info->lock*/            return -ENODEV;        var = info->var;            /*复制var*/        unlock_fb_info(info);        ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;    /*复制var到用户空间*/        break;    /*设置fb_var_screeninfo*/    case FBIOPUT_VSCREENINFO:        if (copy_from_user(&var, argp, sizeof(var)))    /*从用户空间获取var*/            return -EFAULT;        if (!lock_fb_info(info))            return -ENODEV;        acquire_console_sem();        info->flags |= FBINFO_MISC_USEREVENT;        ret = fb_set_var(info, &var);            /*设置var*/        info->flags &= ~FBINFO_MISC_USEREVENT;        release_console_sem();        unlock_fb_info(info);        if (!ret && copy_to_user(argp, &var, sizeof(var)))            ret = -EFAULT;        break;    /*获取fb_fix_screeninfo*/    /*fix为不可改变信息,只能获取,不能设置*/    case FBIOGET_FSCREENINFO:        if (!lock_fb_info(info))            return -ENODEV;        fix = info->fix;        unlock_fb_info(info);        ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;        break;    /*设置fb_cmap*/        case FBIOPUTCMAP:        if (copy_from_user(&cmap, argp, sizeof(cmap)))            return -EFAULT;        ret = fb_set_user_cmap(&cmap, info);    /*设置fb_cmap*/        break;    /*获取fb_cmap*/        case FBIOGETCMAP:        if (copy_from_user(&cmap, argp, sizeof(cmap)))            return -EFAULT;        if (!lock_fb_info(info))            return -ENODEV;        cmap_from = info->cmap;        unlock_fb_info(info);        ret = fb_cmap_to_user(&cmap_from, &cmap);/*获取fb_cmp*/        break;    case FBIOPAN_DISPLAY:        if (copy_from_user(&var, argp, sizeof(var)))            return -EFAULT;        if (!lock_fb_info(info))            return -ENODEV;        acquire_console_sem();        ret = fb_pan_display(info, &var);        release_console_sem();        unlock_fb_info(info);        if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))            return -EFAULT;        break;    case FBIO_CURSOR:        ret = -EINVAL;        break;    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.data = &con2fb;        if (!lock_fb_info(info))            return -ENODEV;        event.info = info;        fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);        unlock_fb_info(info);        ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;        break;    case FBIOPUT_CON2FBMAP:        if (copy_from_user(&con2fb, argp, sizeof(con2fb)))            return -EFAULT;        if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)            return -EINVAL;        if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)            return -EINVAL;        if (!registered_fb[con2fb.framebuffer])            request_module("fb%d", con2fb.framebuffer);        if (!registered_fb[con2fb.framebuffer]) {            ret = -EINVAL;            break;        }        event.data = &con2fb;        if (!lock_fb_info(info))            return -ENODEV;        event.info = info;        ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);        unlock_fb_info(info);        break;    case FBIOBLANK:        if (!lock_fb_info(info))            return -ENODEV;        acquire_console_sem();        info->flags |= FBINFO_MISC_USEREVENT;        ret = fb_blank(info, arg);        ?*最后调用驱动提供的s3c2410fb_blank*/        info->flags &= ~FBINFO_MISC_USEREVENT;        release_console_sem();        unlock_fb_info(info);        break;    default:        if (!lock_fb_info(info))            return -ENODEV;        fb = info->fbops;        if (fb->fb_ioctl)        /*fb_ioctl为空*/            ret = fb->fb_ioctl(info, cmd, arg);        else            ret = -ENOTTY;        unlock_fb_info(info);    }    return ret;}

正如2.2小结所说的,fb_fix_screeninfo只能获取不能设置,因此,ioctl只提供了获取fb_fix_screeninfo的方法,而没有提供设置fb_fix_screeninfo的方法。

3.5 小结

  本节对frambuffer的核心层进行了介绍。包括frambuffer子系统的创建,frambuffer的注册和提供给用户空间的5个API函数。下面开始介绍驱动层。

4. 驱动层

本节将开始介绍S3C2440的frambuffer驱动,该驱动源码位于drivers/video/s3c2410fb.c

首先来看下驱动模块的初始化和清除函数。

4.1 s3c2410fb_init和s3c2410fb_cleanup

static struct platform_driver s3c2410fb_driver = {    .probe        = s3c2410fb_probe,    .remove        = s3c2410fb_remove,    .suspend    = s3c2410fb_suspend,    .resume        = s3c2410fb_resume,    .driver        = {        .name    = "s3c2410-lcd",        .owner    = THIS_MODULE,    },};int __init s3c2410fb_init(void){int ret = platform_driver_register(&s3c2410fb_driver);if (ret == 0)ret = platform_driver_register(&s3c2412fb_driver);;return ret;}static void __exit s3c2410fb_cleanup(void){platform_driver_unregister(&s3c2410fb_driver);platform_driver_unregister(&s3c2412fb_driver);}module_init(s3c2410fb_init);module_exit(s3c2410fb_cleanup);

当platform_driver_register调用的最后会调用probe方法。接下来就来看看probe方法。

4.2 probe方法

struct s3c2410fb_info {    struct device        *dev;    struct clk        *clk;    struct resource        *mem;    void __iomem        *io;        /*虚拟地址*/    void __iomem        *irq_base;    enum s3c_drv_type    drv_type;    struct s3c2410fb_hw    regs;    unsigned int        palette_ready;    /* keep these registers in case we need to re-write palette */    u32            palette_buffer[256];    u32            pseudo_pal[16];};struct s3c2410fb_mach_info {struct s3c2410fb_display *displays;/* attached diplays info */unsigned num_displays;/* number of defined displays */unsigned default_display;/* GPIOs */unsigned longgpcup;unsigned longgpcup_mask;unsigned longgpccon;unsigned longgpccon_mask;unsigned longgpdup;unsigned longgpdup_mask;unsigned longgpdcon;unsigned longgpdcon_mask;/* lpc3600 control register */unsigned longlpcsel;};/* LCD description */struct s3c2410fb_display {    /* LCD type */    unsigned type;    /* Screen size */    unsigned short width;    unsigned short height;    /* Screen info */    unsigned short xres;    unsigned short yres;    unsigned short bpp;    unsigned pixclock;        /* pixclock in picoseconds */    unsigned short left_margin;  /* value in pixels (TFT) or HCLKs (STN) */    unsigned short right_margin; /* value in pixels (TFT) or HCLKs (STN) */    unsigned short hsync_len;    /* value in pixels (TFT) or HCLKs (STN) */    unsigned short upper_margin;    /* value in lines (TFT) or 0 (STN) */    unsigned short lower_margin;    /* value in lines (TFT) or 0 (STN) */    unsigned short vsync_len;    /* value in lines (TFT) or 0 (STN) */    /* lcd configuration registers */    unsigned long    lcdcon5;};static int __init 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;    /*dev.platform_data由函数s3c24xx_fb_set_platdata(mach-smdk2410.c)设置,指向s3c2410fb_mach_info*/    mach_info = pdev->dev.platform_data;        if (mach_info == NULL) {        dev_err(&pdev->dev,            "no platform data for lcd, cannot attach\n");        return -EINVAL;    }                                    /*在mach-smdk2440.c中,default_display=0, num_displays=1*/    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);    /*获取IRQ号,16号中断*/    if (irq < 0) {        dev_err(&pdev->dev, "no irq for device\n");        return -ENOENT;    }                                        /*分配struct fb_info 其中包括sizeof字节的私有数据区*/    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);    if (!fbinfo)        return -ENOMEM;    platform_set_drvdata(pdev, fbinfo);    /*让platform_device->dev.driver_data指向struct fb_info*/    info = fbinfo->par;                    /*par指向s3c2410fb_info*/    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 = (res->end - res->start) + 1;                /*IO内存申请*/    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;    }    info->io = ioremap(res->start, size);            /*IO内存映射,获取lcd第一个寄存器的映射地址*/                    if (info->io == NULL) {        dev_err(&pdev->dev, "ioremap() of registers failed\n");                ret = -ENXIO;            goto release_mem;    }                                            /*irq_base对应的物理地址是0X4D00 0054(寄存器LCDINTPND)*/    info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : 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); /*禁止LCD*/    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;    /* no hardware accelerator    */    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, IRQF_DISABLED, pdev->name, info);    /*申请IRQ,快速中断*/    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 (!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);    /* find maximum required memory size for display */    /*在多个屏幕中,找出需要的最大memory*/    for (i = 0; i < mach_info->num_displays; i++) {        unsigned long smem_len = mach_info->displays[i].xres;        /*所需的memory空间 = xres * yres * bpp / 8*/        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 */    /*根据上面fix.smem_len的大小,获取DMA映射内存,一致性映射方式*/    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;            /*320*/    fbinfo->var.yres = display->yres;            /*240*/        fbinfo->var.bits_per_pixel = display->bpp;    /*16*/    s3c2410fb_init_registers(fbinfo);            /*LCD寄存器初始化*/            s3c2410fb_check_var(&fbinfo->var, fbinfo);    ret = register_framebuffer(fbinfo);            /*注册framebuffer*/    if (ret < 0) {        printk(KERN_ERR "Failed to register framebuffer device: %d\n",ret);        goto free_video_memory;    }    /* 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_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_resource(info->mem);    kfree(info->mem);dealloc_fb:    platform_set_drvdata(pdev, NULL);    framebuffer_release(fbinfo);        return ret;}

这里使用了三个新的数据结构。s3c2410fb_info是驱动程序使用的,里面将保存所有驱动程序所要使用的资源等。而s3c2410fb_display和s3c2410fb_mach_info,是由板级信息,通过platform总线添加到内核中。

s3c2410fb_display中的成员将被复制到fb_var_screeninfo结构中。

该板级信息的定义在arch/arm/mach-s3c2440/mach-smdk2440.c中,来看下

static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {.lcdcon5= S3C2410_LCDCON5_FRM565 |  S3C2410_LCDCON5_INVVLINE |  S3C2410_LCDCON5_INVVFRAME |  S3C2410_LCDCON5_PWREN |  S3C2410_LCDCON5_HWSWP,.type= S3C2410_LCDCON1_TFT,.width= 320,//240,.height= 240,//320,.pixclock= 149000,//166667, /* HCLK 60 MHz, divisor 10 */.xres= 320,//240,.yres= 240,//320,.bpp= 16,.left_margin= 20,.right_margin= 38,//8,.hsync_len= 30,//4,.upper_margin= 15,//8,.lower_margin= 12,//7,.vsync_len= 3,//4,};static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {.displays= &smdk2440_lcd_cfg,.num_displays= 1,.default_display = 0,#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//no//.lpcsel= ((0xCE6) & ~7) | 1<<4,};

这里NOTE:每个LCD屏幕的参数不一样,因此上面的参数会有所不同,这是需要移植的地方。

随后,我们看下在probe方法中调用的几个函数。首先是s3c2410fb_map_video_memory。

/* * 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 fb_info *info){struct s3c2410fb_info *fbi = info->par;dma_addr_t map_dma;unsigned map_size = PAGE_ALIGN(info->fix.smem_len);dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size);/*分配DMA缓冲区,并保存DMA缓冲区虚拟地址*/info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,   &map_dma, GFP_KERNEL);if (info->screen_base) {/* prevent initial garbage on screen */dprintk("map_video_memory: clear %p:%08x\n",info->screen_base, map_size);memset(info->screen_base, 0x00, map_size);/*DMA缓冲区清0*/info->fix.smem_start = map_dma;/*保存DMA缓冲区物理地址*/dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",info->fix.smem_start, info->screen_base, map_size);}return info->screen_base ? 0 : -ENOMEM;}


该函数根据fix.smem_len的大小,分配了一个DMA缓冲区,保存了该缓冲区的物理地址和虚拟地址。

接着是s3c2410fb_init_registers:

/* * s3c2410fb_init_registers - Initialise all LCD-related registers */static int s3c2410fb_init_registers(struct fb_info *info){    struct s3c2410fb_info *fbi = info->par;        /*par指向s3c2410fb_info*/    struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;    unsigned long flags;    void __iomem *regs = fbi->io;    void __iomem *tpal;    void __iomem *lpcsel;    if (is_s3c2412(fbi)) {        tpal = regs + S3C2412_TPAL;        lpcsel = regs + S3C2412_TCONSEL;    } else {        tpal = regs + S3C2410_TPAL;        lpcsel = regs + S3C2410_LPCSEL;    }    /* Initialise LCD with values from haret */    local_irq_save(flags);                        /*禁止所有中断*/    /* modify the gpio(s) with interrupts set (bjd) */    /*初始化io管脚*/    modify_gpio(S3C2410_GPCUP,  mach_info->gpcup,  mach_info->gpcup_mask);    modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);    modify_gpio(S3C2410_GPDUP,  mach_info->gpdup,  mach_info->gpdup_mask);    modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);    local_irq_restore(flags);                    /*恢复中断*/    dprintk("LPCSEL    = 0x%08lx\n", mach_info->lpcsel);    /*设置TCONSEL,禁止LPC3600*/    writel(mach_info->lpcsel, lpcsel);    dprintk("replacing TPAL %08x\n", readl(tpal));    /* ensure temporary palette disabled */    writel(0x00, tpal);    /*禁止调色板*/    return 0;}static inline void modify_gpio(void __iomem *reg,                   unsigned long set, unsigned long mask){    unsigned long tmp;    tmp = readl(reg) & ~mask;    writel(tmp | set, reg);} 
最后是s3c2410fb_check_var:

/* *s3c2410fb_check_var(): *Get the video params out of 'var'. If a value doesn't fit, round it up, *if it's too big, return -EINVAL. *检查变量的合法性 */static int s3c2410fb_check_var(struct fb_var_screeninfo *var,       struct fb_info *info){struct s3c2410fb_info *fbi = info->par;/*par指向s3c2410fb_info*/struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;/*指向s3c2410fb_mach_info*/struct s3c2410fb_display *display = NULL;struct s3c2410fb_display *default_display = mach_info->displays +    mach_info->default_display;int type = default_display->type; /*S3C2410_LCDCON1_TFT*/unsigned i;dprintk("check_var(var=%p, info=%p)\n", var, info);/* validate x/y resolution *//* choose default mode if possible *//*var中的成员在probe中设置*/if (var->yres == default_display->yres &&    var->xres == default_display->xres &&    var->bits_per_pixel == default_display->bpp)display = default_display;elsefor (i = 0; i < mach_info->num_displays; i++)if (type == mach_info->displays[i].type &&    var->yres == mach_info->displays[i].yres &&    var->xres == mach_info->displays[i].xres &&    var->bits_per_pixel == mach_info->displays[i].bpp) {display = mach_info->displays + i;break;}if (!display) {dprintk("wrong resolution or depth %dx%d at %d bpp\n",var->xres, var->yres, var->bits_per_pixel);return -EINVAL;}/* it is always the size as the display */var->xres_virtual = display->xres;var->yres_virtual = display->yres;var->height = display->height;var->width = display->width;/* copy lcd settings */var->pixclock = display->pixclock;var->left_margin = display->left_margin;var->right_margin = display->right_margin;var->upper_margin = display->upper_margin;var->lower_margin = display->lower_margin;var->vsync_len = display->vsync_len;var->hsync_len = display->hsync_len;fbi->regs.lcdcon5 = display->lcdcon5;/* set display type */fbi->regs.lcdcon1 = display->type;var->transp.offset = 0;var->transp.length = 0;/* set r/g/b positions */switch (var->bits_per_pixel) {case 1:case 2:case 4:var->red.offset= 0;var->red.length= var->bits_per_pixel;var->green= var->red;var->blue= var->red;break;case 8:if (display->type != S3C2410_LCDCON1_TFT) {/* 8 bpp 332 */var->red.length= 3;var->red.offset= 5;var->green.length= 3;var->green.offset= 2;var->blue.length= 2;var->blue.offset= 0;} else {var->red.offset= 0;var->red.length= 8;var->green= var->red;var->blue= var->red;}break;case 12:/* 12 bpp 444 */var->red.length= 4;var->red.offset= 8;var->green.length= 4;var->green.offset= 4;var->blue.length= 4;var->blue.offset= 0;break;default:case 16:if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) { /*使用565格式*//* 16 bpp, 565 format */var->red.offset= 11;var->green.offset= 5;var->blue.offset= 0;var->red.length= 5;var->green.length= 6;var->blue.length= 5;} else {/* 16 bpp, 5551 format */var->red.offset= 11;var->green.offset= 6;var->blue.offset= 1;var->red.length= 5;var->green.length= 5;var->blue.length= 5;}break;case 32:/* 24 bpp 888 and 8 dummy */var->red.length= 8;var->red.offset= 16;var->green.length= 8;var->green.offset= 8;var->blue.length= 8;var->blue.offset= 0;break;}return 0;}/* Interpretation of offset for color fields: All offsets are from the right, * inside a "pixel" value, which is exactly 'bits_per_pixel' wide (means: you * can use the offset as right argument to <<). A pixel afterwards is a bit * stream and is written to video memory as that unmodified. * * For pseudocolor: offset and length should be the same for all color * components. Offset specifies the position of the least significant bit * of the pallette index in a pixel value. Length indicates the number * of available palette entries (i.e. # of entries = 1 << length). */struct fb_bitfield {    __u32 offset;            /* beginning of bitfield    */    __u32 length;            /* length of bitfield        */    __u32 msb_right;        /* != 0 : Most significant bit is */                     /* right */ };
该函数主要将板级信息s3c2410fb_display复制到对应的地方,然后根据RGB的模式设置位域。

4.3 fb_ops方法

在驱动程序中,定义了fb_ops,如下:

static struct fb_ops s3c2410fb_ops = {.owner= THIS_MODULE,.fb_check_var= s3c2410fb_check_var,/*检查变量的合法性*/.fb_set_par= s3c2410fb_set_par,/*将参数写入LCD控制器,该函数由帧缓冲核心调用*/.fb_blank= s3c2410fb_blank,/*该方法支持显示消隐和去消隐*/.fb_setcolreg= s3c2410fb_setcolreg,/*设置颜色寄存器*/.fb_fillrect= cfb_fillrect,/*用像素行填充矩形框,通用库函数*/.fb_copyarea= cfb_copyarea,/*将屏幕的一个矩形区域复制到另一个区域,通用库函数*/.fb_imageblit= cfb_imageblit,/*显示一副图像,通用库函数*/};

其中s3c2410fb_check_var在4.2节中已经分析过了,最后三个方法是通用库函数,在这里不作分析。
剩余三个也是驱动程序提供的,现在对这三个程序进行分析。

4.3. 1 s3c2410fb_set_par

/* *      s3c2410fb_set_par - Alters the hardware state. *      @info: frame buffer structure that represents a single frame buffer *根据var中的值设置LCD控制器的寄存器 */static int s3c2410fb_set_par(struct fb_info *info){struct fb_var_screeninfo *var = &info->var;switch (var->bits_per_pixel) {case 32:case 16:case 12:info->fix.visual = FB_VISUAL_TRUECOLOR;break;case 1:info->fix.visual = FB_VISUAL_MONO01;break;default:info->fix.visual = FB_VISUAL_PSEUDOCOLOR;break;}info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; /* 320*16/8 = 640Bytes *//* activate this new configuration */s3c2410fb_activate_var(info);return 0;}

该函数根据像素的位数设置了视觉模式,本例为16为,使用真彩色。然后计算了每行的数据元素大小。共240行。

然后调用了s3c2410fb_activate_var来设置控制器并激活LCD。

s3c2410fb_activate_var函数如下:

/* s3c2410fb_activate_var * * activate (set) the controller from the given framebuffer * information */static void s3c2410fb_activate_var(struct fb_info *info){struct s3c2410fb_info *fbi = info->par;void __iomem *regs = fbi->io;int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT; /*regs.lcdcon1在s3c2410fb_check_var设置*/struct fb_var_screeninfo *var = &info->var;int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock) / 2;dprintk("%s: var->xres  = %d\n", __func__, var->xres);dprintk("%s: var->yres  = %d\n", __func__, var->yres);dprintk("%s: var->bpp   = %d\n", __func__, var->bits_per_pixel);if (type == S3C2410_LCDCON1_TFT) {s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);/*根据var,计算出控制寄存器需要设置的值*/--clkdiv;if (clkdiv < 0)clkdiv = 0;} else {s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);if (clkdiv < 2)clkdiv = 2;}fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);/*设置CLKVAL*//* 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);/*把计算好的值填入LCD控制器中*/writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,regs + S3C2410_LCDCON1);/*仍然禁止LCD*/writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);/* set lcd address pointers */s3c2410fb_set_lcdaddr(info);/*设置LCD帧缓冲起始地址*/fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);/*使能LCD*/}
其中调用的三个函数如下:
static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,                      unsigned long pixclk){    unsigned long clk = clk_get_rate(fbi->clk);         /*获取当前时钟频率(Hz)*/    unsigned long long div;    /* pixclk is in picoseconds, our clock is in Hz     *     * Hz -> picoseconds is / 10^-12     */    div = (unsigned long long)clk * pixclk;    div >>= 12;            /* div / 2^12 */    do_div(div, 625 * 625UL * 625); /* div / 5^12 */    dprintk("pixclk %ld, divisor is %ld\n", pixclk, (long)div);    return div;}/* s3c2410fb_calculate_tft_lcd_regs * * calculate register values from var settings */static void s3c2410fb_calculate_tft_lcd_regs(const struct fb_info *info,                         struct s3c2410fb_hw *regs){    const struct s3c2410fb_info *fbi = info->par;    const struct fb_var_screeninfo *var = &info->var;    switch (var->bits_per_pixel) {    case 1:        regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;        break;    case 2:        regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;        break;    case 4:        regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;        break;    case 8:        regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;        regs->lcdcon5 |= S3C2410_LCDCON5_BSWP |                 S3C2410_LCDCON5_FRM565;        regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;        break;    case 16:        regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;        regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;        regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;        break;    case 32:        regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;        regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP |                   S3C2410_LCDCON5_HWSWP |                   S3C2410_LCDCON5_BPP24BL);        break;    default:        /* invalid pixel depth */        dev_err(fbi->dev, "invalid bpp %d\n",            var->bits_per_pixel);    }    /* update X/Y info */    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);    /*        所有时序参数必须减1,因为在公式中:        Frame Rate = 1/ [ { (VSPW+1) + (VBPD+1) + (LIINEVAL + 1) + (VFPD+1) } x {(HSPW+1) + (HBPD +1)        + (HFPD+1) + (HOZVAL + 1) } x { 2 x ( CLKVAL+1 ) / ( HCLK ) } ]    */    regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) |            S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |            S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |            S3C2410_LCDCON2_VSPW(var->vsync_len - 1);    regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) |            S3C2410_LCDCON3_HFPD(var->left_margin - 1) |            S3C2410_LCDCON3_HOZVAL(var->xres - 1);    regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1);}/* s3c2410fb_set_lcdaddr * * initialise lcd controller address pointers */static void s3c2410fb_set_lcdaddr(struct fb_info *info){unsigned long saddr1, saddr2, saddr3;struct s3c2410fb_info *fbi = info->par;void __iomem *regs = fbi->io;saddr1  = info->fix.smem_start >> 1;  /*帧缓冲区起始地址*/saddr2  = info->fix.smem_start;saddr2 += info->fix.line_length * info->var.yres;saddr2 >>= 1;/*帧缓冲区结束地址*/saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH((info->fix.line_length / 2) & 0x3ff); /*offset = 0, pagewidth = 320*/dprintk("LCDSADDR1 = 0x%08lx\n", saddr1);dprintk("LCDSADDR2 = 0x%08lx\n", saddr2);dprintk("LCDSADDR3 = 0x%08lx\n", saddr3);writel(saddr1, regs + S3C2410_LCDSADDR1);writel(saddr2, regs + S3C2410_LCDSADDR2);writel(saddr3, regs + S3C2410_LCDSADDR3);}

s3c2410fb_calc_pixclk用于计算时钟频率。

s3c2410fb_calculate_tft_lcd_regs设置了LCD的控制寄存器。

s3c2410fb_set_lcdaddr设置了LCD的地址寄存器,该寄存器的设置请参考datasheet。

4.3. 2 s3c2410fb_blank

该方法完成消隐功能。

/* *      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: * *Returns negative errno on error, or zero on success. * */static int s3c2410fb_blank(int blank_mode, struct fb_info *info){    struct s3c2410fb_info *fbi = info->par;    void __iomem *tpal_reg = fbi->io;    dprintk("blank(mode=%d, info=%p)\n", blank_mode, info);    tpal_reg += is_s3c2412(fbi) ? S3C2412_TPAL : S3C2410_TPAL;    if (blank_mode == FB_BLANK_POWERDOWN) {    /*消隐*/        s3c2410fb_lcd_enable(fbi, 0);        /*禁止LCD*/    } else {        s3c2410fb_lcd_enable(fbi, 1);        /*启动LCD*/    }    if (blank_mode == FB_BLANK_UNBLANK)        /*去消隐*/        writel(0x0, tpal_reg);                /*禁止temporary palette*/    else {                                    /*消隐*/        dprintk("setting TPAL to output 0x000000\n");        writel(S3C2410_TPAL_EN, tpal_reg);    /*使能temporary palette,颜色为黑色*/    }    return 0;}
在消隐时,屏幕将全黑。

4.3.3 s3c2410fb_setcolreg

该函数的功能用于设置LCD的调色板。调色板的概念请看我的转帖:LCD调色板

static int s3c2410fb_setcolreg(unsigned regno,                   unsigned red, unsigned green, unsigned blue,                   unsigned transp, struct fb_info *info){    struct s3c2410fb_info *fbi = info->par;    /*par指向s3c2410fb_info*/    void __iomem *regs = fbi->io;    unsigned int val;    /* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",           regno, red, green, blue); */    switch (info->fix.visual) {        case FB_VISUAL_TRUECOLOR:    /*使用真彩色*/        /* true-colour, use pseudo-palette */        if (regno < 16) {    /*16种颜色,为什么只用16种颜色???????????*/            u32 *pal = info->pseudo_palette;            val  = chan_to_field(red,   &info->var.red);            val |= chan_to_field(green, &info->var.green);            val |= chan_to_field(blue,  &info->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, regs + S3C2410_TFTPAL(regno));            schedule_palette_update(fbi, regno, val);        }        break;    default:        return 1;    /* unknown type */    }    return 0;}/* from pxafb.c */static inline unsigned int chan_to_field(unsigned int chan,                     struct fb_bitfield *bf){        /*下面用到的length和offset在s3c2410fb_check_var函数中设置*/    chan &= 0xffff;                /*取低16位*/    chan >>= 16 - bf->length;   /*取第length为到16位为有效位*/    return chan << bf->offset;    /*移动到相应的位置。*/}

我们使用的是真彩色,可以设置16种颜色,颜色的位域值通过调用chan_to_field获得,然后保存了颜色的值。但是比较奇怪的是,这里并没有将值保存到0x4d000400为起始的内存中,不知为何。 反而倒是在伪彩色模式下, 使用了writel(val, regs + S3C2410_TFTPAL(regno))写到内存中,然后调用schedule_palette_update函数来更新palette表。我们来看下。

static void schedule_palette_update(struct s3c2410fb_info *fbi,    unsigned int regno, unsigned int val){unsigned long flags;unsigned long irqen;void __iomem *irq_base = fbi->irq_base;local_irq_save(flags);fbi->palette_buffer[regno] = val;if (!fbi->palette_ready) {fbi->palette_ready = 1;/* enable IRQ */irqen = readl(irq_base + S3C24XX_LCDINTMSK);irqen &= ~S3C2410_LCDINT_FRSYNC;writel(irqen, irq_base + S3C24XX_LCDINTMSK);}local_irq_restore(flags);}
这个函数的作用就是开启了LCD的帧同步中断,这个中断在VSYNC信号从无效变成有效时产生。当中断产生时,会调用在probe方法中注册的ISR。ISR如下:

static irqreturn_t s3c2410fb_irq(int irq, void *dev_id){struct s3c2410fb_info *fbi = dev_id;void __iomem *irq_base = fbi->irq_base;unsigned long lcdirq = readl(irq_base + S3C24XX_LCDINTPND);bif (lcdirq & S3C2410_LCDINT_FRSYNC) {if (fbi->palette_ready)s3c2410fb_write_palette(fbi);writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDINTPND);writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDSRCPND);}return IRQ_HANDLED;}static void s3c2410fb_write_palette(struct s3c2410fb_info *fbi){    unsigned int i;    void __iomem *regs = fbi->io;    fbi->palette_ready = 0;  /*清除ready标志*/    for (i = 0; i < 256; i++) {        unsigned long ent = fbi->palette_buffer[i];        if (ent == PALETTE_BUFF_CLEAR)            continue;        writel(ent, regs + S3C2410_TFTPAL(i));        /* it seems the only way to know exactly         * if the palette wrote ok, is to check         * to see if the value verifies ok         */        if (readw(regs + S3C2410_TFTPAL(i)) == ent)            fbi->palette_buffer[i] = PALETTE_BUFF_CLEAR;        else            fbi->palette_ready = 1;   /* retry */    }}
在中断函数中调用了s3c2410fb_write_palette,该函数对写入内存的颜色值进行检查。如果确认其已经写入,则将palette_buffer中的清除,否则retry。

5. 总结

本文对frambuffer子系统做了简单的介绍。frambuffer子系统的函数相当之多,在这里是不可能一一介绍的。本文首先介绍了主要的数据结构,

随后分析了frambuffer核心层,在核心层简单的分析了5个API函数,接着,对驱动层做了介绍,驱动层的大多数函数都给出了分析。