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

2019-07-12 18: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_IO struct delayed_work deferred_work; struct fb_deferred_io *fbdefio; #endif struct 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_TILEBLITTING struct fb_tile_ops *tileops; /* Tile Blitting */ #endif char __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_RUNNING 0 #define FBINFO_STATE_SUSPENDED 1 u32 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 __init fbmem_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 ", FB_MAJOR); fb_class = class_create(THIS_MODULE, "graphics"); /*创建类*/ if (IS_ERR(fb_class)) { printk(KERN_WARNING "Unable to create fb class; errno = %ld ", PTR_ERR(fb_class)); fb_class = NULL; } return 0; } #ifdef MODULE module_init(fbmem_init); static void __exit fbmem_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"); #else subsys_initcall(fbmem_init); #endif static const struct file_operations fb_fops = {     .owner =    THIS_MODULE,     .read =        fb_read,     .write =    fb_write,     .unlocked_ioctl = fb_ioctl, #ifdef CONFIG_COMPAT     .compat_ioctl = fb_compat_ioctl, #endif     .mmap =        fb_mmap,     .open =        fb_open,     .release =    fb_release, #ifdef HAVE_ARCH_FB_UNMAPPED_AREA     .get_unmapped_area = get_fb_unmapped_area, #endif #ifdef CONFIG_FB_DEFERRED_IO     .fsync =    fb_deferred_io_fsync, #endif };
我们看到,如果不是作为模块,那么该初始化程序将在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_BACKLIGHT mutex_init(&info->bl_curve_mutex); #endif return 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. * */ int register_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 ", i, PTR_ERR(fb_info->dev)); fb_info->dev = NULL; } else fb_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, /*设置颜 {MOD}寄存器*/ .fb_fillrect = cfb_fillrect, /*用像素行填充矩形框,通用库函数*/ .fb_copyarea = cfb_copyarea, /*将屏幕的一个矩形区域复制到另一个区域,通用库函数*/ .fb_imageblit = cfb_imageblit, /*显示一副图像,通用库函数*/ };最下面的三个方法使用的是内核提供的库函数,而上面4个则是由驱动提供。

3.4.1 open方法

下列代码位于drivers/video/fbmem.c
static int fb_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_IO if (info->fbdefio) fb_deferred_io_open(info, inode, file); #endif out: 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_t fb_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_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; struct inode *inode = file->f_path.dentry->d_inode; int fbidx = iminor(inode); struct fb_info *info = registered_fb[fbidx]; 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 long gpcup; unsigned long gpcup_mask; unsigned long gpccon; unsigned long gpccon_mask; unsigned long gpdup; unsigned long gpdup_mask; unsigned long gpdcon; unsigned long gpdcon_mask; /* lpc3600 control register */ unsigned long lpcsel; }; /* 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 ");         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 ",             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 ");         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 ");         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 ");         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 ");                 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 ");     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 ", 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 ");         ret = -ENOENT;         goto release_irq;     }     clk_enable(info->clk);                    /*使能时钟*/     dprintk("got and enabled clock ");     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 ", ret);         ret = -ENOMEM;         goto release_clock;     }     dprintk("got video memory ");     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 ",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 ");     }     printk(KERN_INFO "fb%d: %s frame buffer device ",         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 ", 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 ", 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 ", 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 ", mach_info->lpcsel); /*设置TCONSEL,禁止LPC3600*/ writel(mach_info->lpcsel, lpcsel); dprintk("replacing TPAL %08x ", readl(tpal)); /* ensure temporary palette disabled */ writel(0x00, tpal); /*禁止调 {MOD}板*/ 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) ", 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; else for (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 ", 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, /*设置颜 {MOD}寄存器*/ .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为,使用真彩 {MOD}。然后计算了每行的数据元素大小。共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 ", __func__, var->xres); dprintk("%s: var->yres = %d ", __func__, var->yres); dprintk("%s: var->bpp = %d ", __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: "); dprintk("lcdcon[1] = 0x%08lx ", fbi->regs.lcdcon1); dprintk("lcdcon[2] = 0x%08lx ", fbi->regs.lcdcon2); dprintk("lcdcon[3] = 0x%08lx ", fbi->regs.lcdcon3); dprintk("lcdcon[4] = 0x%08lx ", fbi->regs.lcdcon4); dprintk("lcdcon[5] = 0x%08lx ", 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 ", 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 ",             var->bits_per_pixel);     }     /* update X/Y info */     dprintk("setting vert: up=%d, low=%d, sync=%d ",         var->upper_margin, var->lower_margin, var->vsync_len);     dprintk("setting horz: lft=%d, rt=%d, sync=%d ",         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 ) } ]