本文将介绍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 ) } ]