嵌入式linux中的lcd驱动分析
作者:杰洲村的木棉 学校:广东工业大学 QQ:568109894
源文来自http://luwenchao100.blog.hexun.com/23060194_d.html
在嵌入式linux中,lcd和触摸屏驱动都是字符驱动,采用“文件层-驱动层”的接口方式,本文档中分析的lcd驱动是针对linux2.6.13内核的,本人用的开发板是qq2440,lcd是三星的LTV3500V(带触摸屏的),具体分析的文件:
是"include/linux/fb.h","drivers/video/s3c2410fb.h","drivers/video/s3c2410fb.c","drivers/video/fbmem.c","/include/asm/arch-s3c2410.fb.h(些头文件是针对s3c2440或s3c2410芯片的)",“/home/linux/5/kernel-2.6.13/arch/arm/mach-s3c2410/mach-smdk2410.c"(驱动移植主要就是要修改这个文件,配置一些参数)。详细看一下LCD的驱动,实际上,几乎lcd设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它
(1)fb.h包含了framebuffer所用到的结构
(2)fbmem.c处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己. fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作.
(3)s3c2410fb.c就是特定硬件驱动(针对s3c2410芯片的),fbmem.c就是沟通应用层跟s3c2410fb.c的桥梁
FrameBuffer设备驱动基于如下几个文件:
1)include/linux/fb.h
2)drivers/video/fbmem.c
3)drivers/video/s3c2410fb.c
4)drivers/video/s3c2410fb.h
5)include/asm/arch-s3c2410/fb.h
现在先来分析这两个文件:
1.fb.h包含了framebuffer所用到的结构
1)fb_fix_screeninfo
描述显示卡的属性,并且系统运行时不能被修改
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)fb_var_screeninfo
这个结构描述了显示卡的特性
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 */
};
3)fb_cmap
描述设备无关的颜 {MOD}映射信息。可以通过FBIOGETCMAP 和 FBIOPUTCMAP 对应的ioctl操作设定或获取颜 {MOD}映射信息
struct fb_cmap {
__u32 start; /* First entry */
__u32 len; /* Number of entries */
__u16 *red; /* Red values */
__u16 *green;
__u16 *blue;
__u16 *transp; /* transparency, can be NULL */
};
4)fb_info
(1)定义当显卡的当前状态;fb_info结构仅在内核中可见,在这个结构中有一个fb_ops指针, 指向驱动设备工作所需的函数集。
struct fb_info {
int node;
int flags;
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 */
struct fb_ops *fbops; //(注意这个结构)
struct device *device;
struct class_device *class_device; /* sysfs per device attrs */
#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)fb_info中纪录了帧缓冲设备的全部信息,包括设备的设置参数,状态以及操作函数指针。每一个帧缓冲设备都必须对应一个fb_info结构。其中成员 变量Modename为设备名称,fontname为显示字体,fbops为指向底层操作的函数的指针,这些函数是需要驱动程序开发人员编写的。成员 fb_var_screeninfo和 fb_fix_screeninfo也是结构体。其中fb_var_screeninfo记录用户可修改的显示控制器参数,包括屏幕分辨率和每个像素点的 比特数。fb_var_screeninfo中的xres定义屏幕一行有多少个点, yres定义屏幕一列有多少个点, bits_per_pixel定义每个点用多少个字节表示。而fb_fix_screeninfo中记录用户不能修改的显示控制器的参数,如屏幕缓冲区的 物理地址,长度。当对帧缓冲设备进行映射操作的时候,就是从fb_fix_screeninfo中取得缓冲区物理地址的。重要:::(上面所说的数据成员都是需要在驱 动程序中设置的)。
5)fb_fops
(1)结构包含在fb_info结构中,指向驱动设备工作所需的函数集。fb_ops结构中包含了很多涵数指针(在drivers/video/fbmem.c文件中定义)
(2)用户应用程序通过ioctl()系统调用操作硬件,fb_ops 中的函数就用于支持这些操作。(注: fb_ops结构与file_operations 结构不同,fb_ops是底层操作的抽象,而file_operations是提供给上层系统调用的接口,可以直接调用.
ioctl()系统调用在文件fbmem.c中实现,通过观察可以发现ioctl()命令与fb_ops’s 中函数的关系:
FBIOGET_VSCREENINFO fb_get_var
FBIOPUT_VSCREENINFO fb_set_var
FBIOGET_FSCREENINFO fb_get_fix
FBIOPUTCMAP fb_set_cmap
FBIOGETCMAP fb_get_cmap
FBIOPAN_DISPLAY fb_pan_display
如果我们定义了fb_XXX_XXX 方法,用户程序就可以使用FBIOXXXX宏的ioctl()操作来操作硬件。
当应用程序对设备文件进行ioctl操作时候会调用它们。对于fb_get_fix(),应用程序传入的是fb_fix_screeninfo结构,在函数中对其成员变量赋值,主要是smem_start(缓冲区起始地址)和smem_len(缓冲区长度),最终返回给应用程序。
static struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap, //这个在哪定义呢???(在fbmem.c中定义,这个函数比较重要,是framebuffer的映射函数)
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
};
*/
/*
* Frame buffer operations
*
* LOCKING NOTE: those functions must _ALL_ be called with the console
* semaphore held, this is the only suitable locking mecanism 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 file *file, char __user *buf, size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct file *file, 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 inode *inode, struct file *file, unsigned int cmd,
unsigned long arg, struct fb_info *info);
/* Handle 32bit compat ioctl (optional) */
long (*fb_compat_ioctl)(struct file *f, unsigned cmd, unsigned long arg,
struct fb_info *info);
/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct file *file, struct vm_area_struct *vma);
};
3)我们来研究一下fb_ioctl接口函数:
当应用程序对设备文件进行ioctl操作时候会调用它们。对于fb_get_fix(),应用程序传入的是fb_fix_screeninfo结构,在函数中对其成员变量赋值,主要是smem_start(缓冲区起始地址)和smem_len(缓冲区长度),最终返回给应用程序。
static int
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_con2fbmap con2fb;
struct fb_cmap_user cmap;
struct fb_event event;
void __user *argp = (void __user *)arg;
int i;
if (!fb)
return -ENODEV;
switch (cmd) {
case FBIOGET_VSCREENINFO://获取屏的可变参数(fb_get_var)
return copy_to_user(argp, &info->var,
sizeof(var)) ? -EFAULT : 0;
case FBIOPUT_VSCREENINFO://设置屏的可变参数(fb_set_var)
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
i = fb_set_var(info, &var);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
if (i) return i;
if (copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
return 0;
case FBIOGET_FSCREENINFO://获取屏的固定参数(fb_get_fix)
return copy_to_user(argp, &info->fix,
sizeof(fix)) ? -EFAULT : 0;
case FBIOPUTCMAP://设置调 {MOD}板(fb_get_cmap)
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
return (fb_set_user_cmap(&cmap, info));
case FBIOGETCMAP://获取调 {MOD}板信息(fb_get_cmap)
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
return fb_cmap_to_user(&info->cmap, &cmap);
case FBIOPAN_DISPLAY:(fb_pan_display)
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
acquire_console_sem();
i = fb_pan_display(info, &var);
release_console_sem();
if (i)
return i;
if (copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
return 0;
case FBIO_CURSOR:
return -EINVAL;
case FBIOGET_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return -EFAULT;
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
con2fb.framebuffer = -1;
event.info = info;
event.data = &con2fb;
notifier_call_chain(&fb_notifier_list,
FB_EVENT_GET_CONSOLE_MAP, &event);
return copy_to_user(argp, &con2fb,
sizeof(con2fb)) ? -EFAULT : 0;
case FBIOPUT_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return - EFAULT;
if (con2fb.console < 0 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
return -EINVAL;
#ifdef CONFIG_KMOD
if (!registered_fb[con2fb.framebuffer])
try_to_load(con2fb.framebuffer);
#endif /* CONFIG_KMOD */
if (!registered_fb[con2fb.framebuffer])
return -EINVAL;
event.info = info;
event.data = &con2fb;
return notifier_call_chain(&fb_notifier_list,
FB_EVENT_SET_CONSOLE_MAP,
&event);
case FBIOBLANK:
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
i = fb_blank(info, arg); //开关显示用
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
return i;
default:
if (fb->fb_ioctl == NULL)
return -EINVAL;
return fb->fb_ioctl(inode, file, cmd, arg, info);
}
}
2.fbmem.c(.mmap =fb_mmap函数比较重要,是framebuffer的映射函数,如我的程序就用到vd->map = ( unsigned char * )mmap( 0, vd->mbuf.size, PROT_READ | PROT_WRITE, MAP_SHARED, vd->fd, 0 ),请自己阅读我的一分关于视频采集系统的文档(已经付上源程序))
linux内核启动时将自动加载定义在linux/drivers/video/fbmem.c文件中的framebuffer
1)全局变量
struct fb_info *registered_fb[FB_MAX];
int num_registered_fb;
这两变量记录了所有fb_info 结构的实例,fb_info 结构描述显卡的当前状态,所有设备对应的fb_info 结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info 结构就会添加到这个结构中,同时num_registered_fb 为自动加1.
2)fbmem.c 实现了如下函数.
register_framebuffer(struct fb_info *fb_info);
unregister_framebuffer(struct fb_info *fb_info);
这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它。
3)fb_set_var()
在所有的这些函数中fb_set_var()是最重要的,它用于设定显示卡的模式和其它属性,下面是函数fb_set_var()的执行步骤:
(1)检测是否必须设定模式
(2)设定模式
(3)设定颜 {MOD}映射
(4) 根据以前的设定重新设置LCD控制器的各寄存器。
第四步表明了底层操作到底放置在何处。在系统内存中分配显存后,显存的起始地址及长度将被设定到LCD控制器的各寄存器中(一般通过 fb_set_var()函数),显存中的内容将自动被LCD控制器输出到屏幕上。另一方面,用户程序通过函数mmap()将显存映射到用户进程地址空间中,然后用户进程向映射空间发送的所有数据都将会被显示到LCD显示器上。
流程图:
s3c2410fb.c中var->activate设置为FB_ACTIVATE_NOW,所以这里只考虑条件2。
/*这是linux2.6.13的 fb_set_var
int fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
{
int err, flags = info->flags;
1:
if (var->activate & FB_ACTIVATE_INV_MODE) {
struct fb_videomode mode1, mode2;
int ret = 0;
fb_var_to_videomode(&mode1, var);
fb_var_to_videomode(&mode2, &info->var);
/* make sure we don't delete the videomode of current var */
ret = fb_mode_is_equal(&mode1, &mode2);
if (!ret) {
struct fb_event event;
event.info = info;
event.data = &mode1;
ret = notifier_call_chain(&fb_notifier_list,
FB_EVENT_MODE_DELETE, &event);
}
if (!ret)
fb_delete_videomode(&mode1, &info->modelist);
return ret;
}
2:
if ((var->activate & FB_ACTIVATE_FORCE) ||
memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) {
if (!info->fbops->fb_check_var) {
*var = info->var;
return 0;
}
if ((err = info->fbops->fb_check_var(var, info)))//先检查一下设置是否符合要求
return err;
if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {//条件符合
struct fb_videomode mode;
int err = 0;
info->var = *var;
if (info->fbops->fb_set_par)
info->fbops->fb_set_par(info);//设置可变参数
fb_pan_display(info, &info->var);//s3c2410不支持硬件虚拟显示,在s3c2410fb.c上没有实现该接口
/*什么功能都没有实现
static int s3c2410fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
gprintk("pan_display(var=%p, info=%p)/n", var, info);
gprintk("pan_display: xoffset=%d/n", var->xoffset);
gprintk("pan_display: yoffset=%d/n", var->yoffset);
return 0;
}
*/
fb_set_cmap(&info->cmap, info);//调 {MOD}板设置
fb_var_to_videomode(&mode, &info->var);
if (info->modelist.prev && info->modelist.next &&
!list_empty(&info->modelist))
err = fb_add_videomode(&mode, &info->modelist);
if (!err && (flags & FBINFO_MISC_USEREVENT)) {
struct fb_event event;
info->flags &= ~FBINFO_MISC_USEREVENT;
event.info = info;
notifier_call_chain(&fb_notifier_list,
FB_EVENT_MODE_CHANGE,
&event);
}
}
}
return 0;
}
*/
/*这是网上的一个参考函数:
static int s3c2440fb_set_var(struct fb_var_screeninfo *var,int con,struct fb_info *info){
struct s3c2440fb_info *fbi= (struct s3c2440fb_info *)info; /* 将显示模式读入结构体s3c2440fb_info*/
struct fb_var_screeninfo *dvar= get_con_var(&fbi->fb,con);
int err;
err= s3c2440fb_validate_var(var,fbi); /* 显示模式是否有效 */
if(err) /* 无效返回 */
return err;
dvar->red=fbi->rgb[rgbidx]->red; /* 将显示参数写入结构体fb_var_screeninfo */
dvar->green=fbi->rgb[rgbidx]->green;
dvar->blue=fbi->rgb[rgbidx]->bIue;
dvar->transp=fbi->rgb[rgbidx]->transp;
display->var= *dvar;
……
s3c2440fb_hw_set_var (dvar,fbi); /* 设置
RGB颜 {MOD}信息,设置S3C2440A的LCD控制寄存器 */
return 0;
}
*/
3.s3c2410fb.c
qq2440开发板的framdbuffer驱动程序在linux/drivers/video/s3c2410fb.c中实现,由内核调用int __devinit s3c2410fb_init(void)开始
1)驱动初始化
int __devinit s3c2410fb_init(void)
{
return platform_driver_register(&s3c2410fb_driver); /*注册LCD驱动进系统*/
}
很简单就是把这个驱动注册进系统,系统会找到LCD设备并调用这个驱动的probe函数。
2)初始化lcd控制器的地址指针(LCDSADDR1:高位帧缓存地址寄存器1;LCDSADDR2:高位帧缓存地址寄存器2;LCDSADDR3:虚拟屏地址寄存器)
/* s3c2410fb_set_lcdaddr
*
* initialise lcd controller address pointers
*/
static void s3c2410fb_set_lcdaddr(struct s3c2410fb_info *fbi)
{
struct fb_var_screeninfo *var = &fbi->fb.var;
unsigned long saddr1, saddr2, saddr3;
saddr1 = fbi->fb.fix.smem_start >> 1;
saddr2 = fbi->fb.fix.smem_start;
saddr2 += (var->xres * var->yres * var->bits_per_pixel)/8;
saddr2>>= 1;
saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH(var->xres);
gprintk("LCDSADDR1 = 0x%08lx/n", saddr1);
gprintk("LCDSADDR2 = 0x%08lx/n", saddr2);
gprintk("LCDSADDR3 = 0x%08lx/n", saddr3);
writel(saddr1, S3C2410_LCDSADDR1);
writel(saddr2, S3C2410_LCDSADDR2);
writel(saddr3, S3C2410_LCDSADDR3);
}
3)
static inline struct s3c2410fb_info *fb_to_s3cfb(struct fb_info *info)
{
return container_of(info, struct s3c2410fb_info, fb);
}
//解析
container_of()宏
指针ptr指向结构体type中的成员member;通过指针ptr,返回结构体type的起始地址
type
|----------|
| |
| |
|----------|
ptr-->| member --|
|----------|
| |
| |
|----------|
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
* */
#define container_of(ptr, type, member) ({ /
const typeof( ((type *)0)->member ) *__mptr = (ptr); -/
(type *)( (char *)__mptr - offsetof(type,member) );})
--------------------------------------------------
d = usb_device->dev.driver
container_of(d, struct usb_device_driver, drvwrap.driver)
struct usb_device
|----------------------------|
| |
| |
|----------------------------|
| | struct device
|struct device_driver *driver|--+
| | -|
|----------------------------| -|
| | -|
| | -|
|----------------------------| -|
|
+-------------------------------+
|
| struct usb_device_driver
| --|---------------------------|
|-- | |
|-- | |
| --|---------------------------|
+-->|struct device_driver driver| struct usbdrv_wrap drvwrap
|int for_devices|
|---------------------------|
| |
|---------------------------|
--------------------------------------------
container_of宏,它的功能是得到包含某个结构成员的结构的指针:
其实现如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ /
const typeof( ((type *)0)->member ) *__mptr = (ptr); /
(type *)( (char *)__mptr - offsetof(type,member) );})
分析可知__mptr指向的是一个type结构里typeof(((type *)0)->member)类型member成员的指针,offsetof(type,member)是这个成员在结构中的偏移,单位是字节,所以为了计算type结构的起始地址,__mptr减去它自己的偏移。
//解析
4)检查显示卡的特性
/*
* 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 = fb_to_s3cfb(info);
gprintk("check_var(var=%p, info=%p)/n", var, info);
/* validate x/y resolution */
if (var->yres > fbi->mach_info->yres.max)
var->yres = fbi->mach_info->yres.max;
else if (var->yres < fbi->mach_info->yres.min)
var->yres = fbi->mach_info->yres.min;
if (var->xres > fbi->mach_info->xres.max)
var->yres = fbi->mach_info->xres.max;
else if (var->xres < fbi->mach_info->xres.min)
var->xres = fbi->mach_info->xres.min;
/* validate bpp */
if (var->bits_per_pixel > fbi->mach_info->bpp.max)
var->bits_per_pixel = fbi->mach_info->bpp.max;
else if (var->bits_per_pixel < fbi->mach_info->bpp.min)
var->bits_per_pixel = fbi->mach_info->bpp.min;
/* set r/g/b positions */
//设置rgb的格式。每个像素可以为2字节(格式为565),还可以为3字节(格式为888)
if (var->bits_per_pixel == 16) {
var->red.offset = 11;
var->green.offset = 5;
var->blue.offset = 0;
var->red.length = 5;
var->green.length = 6;
var->blue.length = 5;
var->transp.length = 0;
} else {
var->red.length = 8;
var->red.offset = 0;
var->green.length = 0;
var->green.offset = 8;
var->blue.length = 8;
var->blue.offset = 0;
var->transp.length = 0;
}
return 0;
}
5)根据framebuffer的信息设置lcd的控制器(自己根据数据手册研究下设置)
/* s3c2410fb_activate_var
*
* activate (set) the controller from the given framebuffer
* information
*/
static int s3c2410fb_activate_var(struct s3c2410fb_info *fbi,
struct fb_var_screeninfo *var)
{
fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_MODEMASK;
gprintk("%s: var->xres = %d/n", __FUNCTION__, var->xres);
gprintk("%s: var->yres = %d/n", __FUNCTION__, var->yres);
gprintk("%s: var->bpp = %d/n", __FUNCTION__, var->bits_per_pixel);
switch (var->bits_per_pixel) {
case 1:
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
break;
case 2:
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
break;
case 4:
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
break;
case 8:
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
break;
case 16:
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
break;
default:
/* invalid pixel depth */
dev_err(fbi->dev, "invalid bpp %d/n", var->bits_per_pixel);
}
/* check to see if we need to update sync/borders */
if (!fbi->mach_info->fixed_syncs) {
gprintk("setting vert: up=%d, low=%d, sync=%d/n",
var->upper_margin, var->lower_margin,
var->vsync_len);
gprintk("setting horz: lft=%d, rt=%d, sync=%d/n",
var->left_margin, var->right_margin,
var->hsync_len);
fbi->regs.lcdcon2 =
S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |
S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
S3C2410_LCDCON2_VSPW(var->vsync_len - 1);
fbi->regs.lcdcon3 =
S3C2410_LCDCON3_HBPD(var->right_margin - 1) |
S3C2410_LCDCON3_HFPD(var->left_margin - 1);
fbi->regs.lcdcon4 &= ~S3C2410_LCDCON4_HSPW(0xff);
fbi->regs.lcdcon4 |= S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
}
/* update X/Y info */
fbi->regs.lcdcon2 &= ~S3C2410_LCDCON2_LINEVAL(0x3ff);
fbi->regs.lcdcon2 |= S3C2410_LCDCON2_LINEVAL(var->yres - 1);
fbi->regs.lcdcon3 &= ~S3C2410_LCDCON3_HOZVAL(0x7ff);
fbi->regs.lcdcon3 |= S3C2410_LCDCON3_HOZVAL(var->xres - 1);
gprintk("%s: pixclock = %u/n", __FUNCTION__, var->pixclock);
if (var->pixclock > 0) {
int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock);
clkdiv = (clkdiv / 2) -1;
if (clkdiv < 0)
clkdiv = 0;
gprintk("CLKVAL = %d/n", clkdiv);
fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_CLKVAL(0x3ff);
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv);
}
/* write new registers */
gprintk("new register set:/n");
gprintk("lcdcon[1] = 0x%08lx/n", fbi->regs.lcdcon1);
gprintk("lcdcon[2] = 0x%08lx/n", fbi->regs.lcdcon2);
gprintk("lcdcon[3] = 0x%08lx/n", fbi->regs.lcdcon3);
gprintk("lcdcon[4] = 0x%08lx/n", fbi->regs.lcdcon4);
gprintk("lcdcon[5] = 0x%08lx/n", fbi->regs.lcdcon5);
writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
writel(fbi->regs.lcdcon3, S3C2410_LCDCON3);
writel(fbi->regs.lcdcon4, S3C2410_LCDCON4);
writel(fbi->regs.lcdcon5, S3C2410_LCDCON5);
/* set lcd address pointers */
s3c2410fb_set_lcdaddr(fbi);
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
return 0;
}
6)给framebuffer分配内存空间
在分配framebuffer 时一共返回两个指针,虽然是同一块内存空间,但一个返回的是实际的物理地址,另一个返回的是经过地址转换的虚拟地址。在设置LCD 控制器中framebuffer 起始地址寄存器时,用的是所分配内存的物理地址;而当要对framebuffer 进行读写操作时,用的是同一块内存的物理地址所转换后的虚拟地址。由此可以知道,内核在对每个I/O 地址进行读写操作时用的都是经过转换的虚拟地址。
/*
* 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.
*/
1. 分配内存
2. 将这内存设置为non-cached, non-buffered,就是不使用CACHE等,读写它时将会直接操作内存
3. 映射地址:不管是在内核还是用户态,都是使用虚拟地址
4. 请看fbmem.c的fb_mmap函数,会用到:fbi->fb->fix.smem_start、info->fix.mmio_start等
static int __init s3c2410fb_map_video_memory(struct s3c2410fb_info *fbi)
{
gprintk("map_video_memory(fbi=%p)/n", fbi);
fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE);//页对齐
fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size,
&fbi->map_dma, GFP_KERNEL);//用于分配可供dma使用的内存,其中返回地址fbi->map_cpu为虚拟地址,fbi->map_dma为物理地址
fbi->map_size = fbi->fb.fix.smem_len;//恢复真实的大小
if (fbi->map_cpu) {
/* prevent initial garbage on screen */
gprintk("map_video_memory: clear %p:%08x/n",
fbi->map_cpu, fbi->map_size);
memset(fbi->map_cpu, 0xf0, fbi->map_size);
fbi->screen_dma = fbi->map_dma;//物理地址
fbi->fb.screen_base = fbi->map_cpu;//虚拟地址
fbi->fb.fix.smem_start = fbi->screen_dma;//物理地址
gprintk("map_video_memory: dma=%08x cpu=%p size=%08x/n",
fbi->map_dma, fbi->map_cpu, fbi->fb.fix.smem_len);
}
return fbi->map_cpu ? 0 : -ENOMEM;
}
关于map_size大小是如何计算出来的呢????
我们知道
s3c2410fb_probe(struct device *dev)就是分配并初始化一个fb_info的对象,保存显卡的当前状态;初始化LCD硬件控制器, 初始化好LCD的frame buffer等工作,smem_len 就在fb_info对像中。
这里首先计算出需要视频缓冲区的大小(LCD屏的宽度 * LCD屏的高度 * 每像素的位数 / 每字节的位数)
info.fb.fix.smem_len =mach_info->xres.max *mach_info->yres.max *mach_info->bpp.max / 8;(这样就可以求出framebuffer的大小)
7)初始lcd控制器的一些相关寄存器(结合datasheet来阅读)
/*
* s3c2410fb_init_registers - Initialise all LCD-related registers
*/
int s3c2410fb_init_registers(struct s3c2410fb_info *fbi)
{
unsigned long flags;
/* Initialise LCD with values from haret */
local_irq_save(flags);
/* modify the gpio(s) with interrupts set (bjd) */
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);
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
writel(fbi->regs.lcdcon3, S3C2410_LCDCON3);
writel(fbi->regs.lcdcon4, S3C2410_LCDCON4);
writel(fbi->regs.lcdcon5, S3C2410_LCDCON5);
s3c2410fb_set_lcdaddr(fbi); //ghcstop: LCD frame buffer address setting
gprintk("LPCSEL = 0x%08lx/n", mach_info->lpcsel);
writel(mach_info->lpcsel, S3C2410_LPCSEL);
gprintk("replacing TPAL %08x/n", readl(S3C2410_TPAL));
/* ensure temporary palette disabled */
writel(0x00, S3C2410_TPAL);
/* ghcstop modified */
s3c2410_gpio_cfgpin(S3C2410_GPC5, S3C2410_GPC5_OUTP); // lcd display enable/disable
s3c2410_gpio_cfgpin(S3C2410_GPB1, S3C2410_GPB1_OUTP); // back light control
s3c2410_gpio_cfgpin(S3C2410_GPH6, S3C2410_GPH6_OUTP);
s3c2410_gpio_pullup(S3C2410_GPC5, 0);
s3c2410_gpio_pullup(S3C2410_GPB1, 0);
s3c2410_gpio_pullup(S3C2410_GPH6, 0);
s3c2410_gpio_setpin(S3C2410_GPC5, 1);
s3c2410_gpio_setpin(S3C2410_GPH6, 1);
s3c2410_gpio_setpin(S3C2410_GPB1, 1);
/* probably not required */
msleep(10);
/* Enable video by setting the ENVID bit to 1 */
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
return 0;
}
8)probe函数是很重要的一个函数(其实内核初始化framebuffer时的工作实质上就是这个函数做初始化工作),这个函数主要就是分配并初始化一个fb_info的对象,保存显卡的当前状态;初始化LCD硬件控制器, 初始化好LCD的frame buffer等工作
int __init s3c2410fb_probe(struct device *dev)
{
struct s3c2410fb_hw *mregs; //s3c2410fb_hw为描述LCD的硬件控制寄存器内容的结构体
int ret;
int i;
mach_info = dev->platform_data; //获取lcd相关寄存器配置信息(在"arch/arm/mach-s3c2410/mach-sbc2440.c"文件中)
if (mach_info == NULL) {
dev_err(dev,"no platform data for lcd, cannot attach/n");
return -EINVAL;
}
mregs = &mach_info->regs;//如下的(lcdcon1,lcdcon2,lcdcon3,lcdcon4,lcdcon5)
//( .regs = {
// .lcdcon1 = S3C2410_LCDCON1_TFT16BPP | /
// S3C2410_LCDCON1_TFT | /
// S3C2410_LCDCON1_CLKVAL(0x04),
//
// .lcdcon2 = S3C2410_LCDCON2_VBPD(1) | /
// S3C2410_LCDCON2_LINEVAL(319) | /
// S3C2410_LCDCON2_VFPD(5) | /
// S3C2410_LCDCON2_VSPW(1),
//
// .lcdcon3 = S3C2410_LCDCON3_HBPD(36) | /
// S3C2410_LCDCON3_HOZVAL(239) | /
// S3C2410_LCDCON3_HFPD(19),
//
// .lcdcon4 = S3C2410_LCDCON4_MVAL(13) | /
// S3C2410_LCDCON4_HSPW(5),
//
// .lcdcon5 = S3C2410_LCDCON5_FRM565 |
// S3C2410_LCDCON5_INVVLINE |
// S3C2410_LCDCON5_INVVFRAME |
// S3C2410_LCDCON5_PWREN |
// S3C2410_LCDCON5_HWSWP,
//},
// )
strcpy(info.fb.fix.id, driver_name);//把驱动程序的名字写到frambuffer中的struct s3c2410fb_info结构
memcpy(&info.regs, &mach_info->regs, sizeof(info.regs));//分配s3c2410fb_info结构的regs成员空间并赋值
info.mach_info = dev->platform_data;
info.fb.fix.type = FB_TYPE_PACKED_PIXELS;
info.fb.fix.type_aux = 0;
info.fb.fix.xpanstep = 0;
info.fb.fix.ypanstep = 0;
info.fb.fix.ywrapstep = 0;
info.fb.fix.accel = FB_ACCEL_NONE; //没有硬件加速
info.fb.var.nonstd = 0;
info.fb.var.activate = FB_ACTIVATE_NOW;
#if 1
info.fb.var.height = mach_info->height;
info.fb.var.width = mach_info->width;
#else
info.fb.var.height = -1;
info.fb.var.width = -1;
#endif
info.fb.var.accel_flags = 0;
info.fb.var.vmode = FB_VMODE_NONINTERLACED;
info.fb.fbops = &s3c2410fb_ops;
info.fb.flags = FBINFO_FLAG_DEFAULT;
info.fb.monspecs = monspecs;
#if 1
info.fb.pseudo_palette = &info.pseudo_pal;
#else
addr = &info;
addr = addr + sizeof(struct s3c2410fb_info);
info.fb.pseudo_palette = addr;
#endif
//mach_info的值就在(arch/arm/mach-s3c2410/mach-sbc2440.c),请看下面的注释
info.fb.var.xres = mach_info->xres.defval;
info.fb.var.xres_virtual = mach_info->xres.defval;
info.fb.var.yres = mach_info->yres.defval;
info.fb.var.yres_virtual = mach_info->yres.defval;
info.fb.var.bits_per_pixel = mach_info->bpp.defval;
info.fb.var.pixclock = mach_info->pixclock;
// /*坐标信息*/
//.xres = {
// .min = 240,
// .max = 240,
// .defval = 240,
//},
// /*坐标信息*/
//.yres = {
// .max = 320,
// .min = 320,
// .defval = 320,
//},
///*象素信息*/
//.bpp = {
// .min = 16,
// .max = 16,
// .defval = 16,
//},
//
info.fb.var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) +1;
info.fb.var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) +1;
info.fb.var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
info.fb.var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
info.fb.var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
info.fb.var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
/* 配 {MOD}板设置(采用5、6、5模式) */
info.fb.var.red.offset = 11;
info.fb.var.green.offset = 5;
info.fb.var.blue.offset = 0;
info.fb.var.transp.offset = 0;
info.fb.var.red.length = 5;
info.fb.var.green.length = 6;
info.fb.var.blue.length = 5;
info.fb.var.transp.length = 0;
//设置framebuffer大小,以字节来计算
info.fb.fix.smem_len = mach_info->xres.max *
mach_info->yres.max *
mach_info->bpp.max / 8;
for (i = 0; i < 256; i++) //初始化调 {MOD}板缓冲区
info.palette_buffer[i] = PALETTE_BUFF_CLEAR;
info.clk = clk_get(NULL, "lcd");
if (!info.clk || IS_ERR(info.clk)) {
printk(KERN_ERR "failed to get lcd clock source/n");
return -ENOENT;
}
//获取并打开LCD的时钟源
clk_use(info.clk);
clk_enable(info.clk);
gprintk("got and enabled clock/n");
msleep(1);
/* Initialize video memory */
ret = s3c2410fb_map_video_memory(&info);
if (ret) {
printk( KERN_ERR "Failed to allocate video RAM: %d/n", ret);
ret = -ENOMEM;
goto failed;
}
gprintk("got video memory/n");
ret = s3c2410fb_init_registers(&info); // /*初始化LCD硬件*/
ret = s3c2410fb_check_var(&info.fb.var, &info.fb);//检查显示卡的特性
ret = register_framebuffer(&info.fb); /*把该frame buffer注册进系统, 以后对该设备文件的访问就会调到该驱动的操作函数集*/
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d/n", ret);
goto failed;
}
/* create device files */
device_create_file(dev, &dev_attr_debug);
printk(KERN_INFO "S3C24X0 fb%d: %s frame buffer device initialize done/n",
info.fb.node, info.fb.fix.id);
return 0;
failed:
return ret;
}
9) s3c2410fb_ops结构
这些函数都是用来设置和获取驱动层接口fb_info结构体中的成员变量的,前文已提过当应用程序对设备文件进行ioctl操作时会调用它们。对于 fb_get_fix()和fb_get_var()应用程序传入的是fb_info中的结构变量fix和var,fb_set_var()函数则是对 var变量进行设置。同样fb_get_cmap()和
fb_set_cmap()则是对结构变量cmap内容进行读取和设置。在这5个函数中, fb_set_var()设置了显示设备的显示模式,是最重要的一个函数。文中根据需要为当前显示硬件定义一个专有结构体s3c2440fb_info, 该结构体包括一个fb_info结构变量,及其它与所选LCD硬件有关的所有参数.因此结构体fb_ops中成员函数对结构体fb_info的操作实际上 就是对结构体s3c2440fb_info的操作
static struct fb_ops s3c2410fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = s3c2410fb_check_var,
.fb_set_par = s3c2410fb_set_par,
.fb_blank = s3c2410fb_blank,
.fb_pan_display = s3c2410fb_pan_display,
.fb_setcolreg = s3c2410fb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_cursor = soft_cursor,
};
4.“drivers/video/s3c2410fb.h”包含一个重要的结构s3c2410fb_info(这是一个针对s2c2410的framebuffer消息)
struct s3c2410fb_info {
struct fb_info fb;
struct device *dev;
struct clk *clk;
struct s3c2410fb_mach_info *mach_info;
/* raw memory addresses */
dma_addr_t map_dma; /* physical */
u_char * map_cpu; /* virtual */
u_int map_size;
struct s3c2410fb_hw regs;
/* addresses of pieces placed in raw buffer */
u_char * screen_cpu; /* virtual address of buffer */
dma_addr_t screen_dma; /* physical address of buffer */
unsigned int palette_ready;
/* keep these registers in case we need to re-write palette */
u32 palette_buffer[256];
u32 pseudo_pal[16];
};
5.现在针对我的开发板qq2440来讲解“/include/asm/arch-s3c2410/fb.h“
LCD控制器的功能是传输图像数据并产生相应的控制信号来驱动LCD显示器,驱动程序需要根据当前具体显示硬件的特性,通过读写一系列的LCD控制寄存器来完成设定显示器分辨率和显示数据的格式,设置控制信号时序,指定显示缓 中区地址等,从而提供给显示设备合适的数据信号和控制信号。文中根据需要为S3C2440A的LCD 控制器定义了一个专用结构体s3c2440fb_mach_info(此结构在“/include/asm/arch-s3c2410/fb.h“):
/*(这个是在网上找的一个结构)
struct s3c2440fb_mach_info{
u_long pixclock; /* 像素时钟频率 */
u_char bpp; /* 每像素需要的bit数 */
u_short xres; /* 显示器行分辨率 */
u_short yres; /* 显示器列分辨率 */
u_char hsync_len; /* 行同步信号的长度 */
u_char vsync_len; /* 帧同步信号的长度 */
u_char left_margin;/* 从本行图象数据输出结束到下一行的行同步信号开始之间的像素时钟数 */
u_char right_margin; /* 从行同步信号结束到该行的图象数据开始输出之间的像素时钟数*/
u_char upper_margin;/*从本帧图象数据输出结束到下一帧的帧同步信号开始之间的无效行数 */
u_char lower_margin; /*从帧同步信号结束到该帧图象数据开始输出之间的无效行数*/
u_char sync;
struct s3c2440fb_lcd_reg reg; /*S3C2440A
LCD控制寄存器结构体 */
};
*/
/*这是一个我的linux2.6.13内核的结构
struct s3c2410fb_val {
unsigned int defval;
unsigned int min;
unsigned int max;
};
struct s3c2410fb_hw {
unsigned long lcdcon1;
unsigned long lcdcon2;
unsigned long lcdcon3;
unsigned long lcdcon4;
unsigned long lcdcon5;
};
struct s3c2410fb_mach_info {
unsigned char fixed_syncs; /* do not update sync/border */
unsigned long pixclock;
/* Screen size */
int width;
int height;
/* Screen info */
struct s3c2410fb_val xres;
struct s3c2410fb_val yres;
struct s3c2410fb_val bpp;
/* lcd configuration registers */
struct s3c2410fb_hw regs;
/* 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;
};
*/
驱动程序通过定义一个s3c2440fb_mach_info结构变量并给该变量赋值来完成LCD控制器的初始化。
6.lcd驱动编写流程:(具体可以参考:http://www.dzkf.cn/html/qianrushixitong/2007/0718/2396_2.html)
1) 在显示系统硬件设计中,显示硬件的整体设计考虑全面是设计过程中的重点,这就要求对显示硬件的各特性参数有全面的了解。软件设计中,由于其中涉及到的数据 结构比较多,同时又和控制台联系在一起,有一定的难度。只有在深刻理解各个变量和操作函数的具体意义后, 才能分析编写自己需要的LCD驱动程序。在编写的过程中,最好的参考莫过于Linux内核drivers/video目录下的源代码
2) 在了解了上面所述的概念后,编写帧缓冲驱动的实际工作并不复杂,需要做的工作是:(定义LCD控制器结构体---->编写结构体fb_info中fb_ops对应的成员函数--->编写初始化函数(s3c2410fb_probe))
module_init() -->
s3c2410fb_init() -->
platform_driver_register() -->
s3c2410fb_probe()
1. 编写初始化函数:初始化函数首先初始化LCD控制器,设置显示模式和显示颜 {MOD}数,然后分配LCD显示缓冲区。在Linux可通过kmalloc函数分配一 片连续的空间。笔者采用的LCD显示方式为240x320,16位彩 {MOD}。需要分配的显示缓冲区为240x320x2 = 150k字节,缓冲区通常分配在片外SDRAM中,起始地址保存在LCD控制器寄存器中。
最后是初始化一个fb_info结构,填充其中的成员变量,并调用register_framebuffer(&fb_info)将fb_info登记入内核。
2. 编写结构fb_info中函数指针fb_ops对应的成员函数:对于嵌入式系统的简单实现,只需要下列三个函数就可以了:
struct fb_ops {
……..
int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con, struct fb_info *info);
int (*fb_get_var)(struct fb_var_screeninfo *var, int con, struct fb_info *info);
int (*fb_set_var)(struct fb_var_screeninfo *var, int con,struct fb_info *info);
…….
};
struct fb_ops在include/linux/fb.h中定义。这些函数都是用来设置/获取fb_info结构中的成员变量的。当应用程序对设备文件进行 Ioctl操作时候会调用它们,读者可参考前文中的应用程序例子。例如,对于fb_get_fix(),应用程序传入的是 fb_fix_screeninfo结构,在函数中对其成员变量赋值,主要是smem_start(缓冲区起始地址)和smem_len(缓冲区长度), 最终返回给应用程序。而fb_set_var()函数的传入参数是fb_var_screeninfo,函数中需要对xres,yres,和 bits_per_pixel赋值。
驱动程序编写完成后,开发者可选择将其编译为动态加载模块,或静态地编译入内核中
7.应用程序对帧缓冲设备的使用
Linux将所有的设备都当作文件进行处理,各种设备通常以文件的形式放在/dev目录下。帧缓冲设备和其它位于/dev目录下面的设备类似,其驱动程序 的设备文件一般是/dev/fb0、/dev/fb1等等。在应用程序中,操作/dev/fb的一般流程下如图所示:
其典型应用程序如下:
Main()
{
int fbfd=O;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
unsigned char *fbp;
fbfd=open ("/dev/fb0",O_RDWR);
/* 打开设备文件 */
if(!fbfd){ /* 失败返回 */
printf("Error:cannot open framebuffer device./n ");
exit(1);
}
Printf ("The framebuffer device was opened successfully./n");
ioctl (fbfd,FBIOGET_FSCREENINFO,&finfo);
/* 获取显示设备特性 */
ioctl(fbfd,FBIOGET_VSCREENINFO,&vinfo);
screensize=vinfo.xres*vinfo.yres*vinfo.bits_per_pixel/8 /* 计算屏幕缓冲区的大小 */
fbp= (unsigned char*)mmap (0,screensize,PORT_READ|PORT_WRITE,MAP_SHARED,fbfd,0);/* 将屏幕缓冲区映射到用户地址空间,然后应用程序就可以通过fbp访问缓冲区了*/
memset (fbp,0,screensize); /* 用memset将屏幕清空 */
}
最后
一个使用FrameBuffer的例子
1、FrameBuffer主要是根据VESA标准的实现的,所以只能实现最简单的功能。
2、由于涉及内核的问题,FrameBuffer是不允许在系统起来后修改显示模式等一系列操作。(好象很多人都想要这样干,这是不被允许的,当然如果你自己写驱动的话,是可以实现的).
3、对FrameBuffer的操作,会直接影响到本机的所有控制台的输出,包括XWIN的图形界面。
好,现在可以让我们开始实现直接写屏:
1、打开一个FrameBuffer设备
2、通过mmap调用把显卡的物理内存空间映射到用户空间
3、直接写内存。
/********************************
File name : fbtools.h
*/
#ifndef _FBTOOLS_H_
#define _FBTOOLS_H_
#i nclude
//a framebuffer device structure;
typedef struct fbdev{
int fb;
unsigned long fb_mem_offset;
unsigned long fb_mem;
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
char dev[20];
} FBDEV, *PFBDEV;
//open & init a frame buffer
//to use this function,
//you must set FBDEV.dev="/dev/fb0"
//or "/dev/fbX"
//it's your frame buffer.
int fb_open(PFBDEV pFbdev);
//close a frame buffer
int fb_close(PFBDEV pFbdev);
//get display depth
int get_display_depth(PFBDEV pFbdev);
//full screen clear
void fb_memset(void *addr, int c, size_t len);
#endif
/******************
File name : fbtools.c
*/
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude "fbtools.h"
#define TRUE 1
#define FALSE 0
#define MAX(x,y) ((x)>(y)?(x)y))
#define MIN(x,y) ((x)<(y)?(x)y))
//open & init a frame buffer
int fb_open(PFBDEV pFbdev)
{
pFbdev->fb = open(pFbdev->dev, O_RDWR);
if(pFbdev->fb < 0)
{
printf("Error opening %s: %m. Check kernel config/n", pFbdev->dev);
return FALSE;
}
if (-1 == ioctl(pFbdev->fb,FBIOGET_VSCREENINFO,&(pFbdev->fb_var)))
{
printf("ioctl FBIOGET_VSCREENINFO/n");
return FALSE;
}
if (-1 == ioctl(pFbdev->fb,FBIOGET_FSCREENINFO,&(pFbdev->fb_fix)))
{
printf("ioctl FBIOGET_FSCREENINFO/n");
return FALSE;
}
//map physics address to virtual address
pFbdev->fb_mem_offset = (unsigned long)(pFbdev->fb_fix.smem_start) & (~PAGE_MASK);
pFbdev->fb_mem = (unsigned long int)mmap(NULL, pFbdev->fb_fix.smem_len + pFbdev->fb_mem_offset, PROT_READ | PROT_WRITE, MAP_SHARED, pFbdev->fb, 0);
if (-1L == (long) pFbdev->fb_mem)
{
printf("mmap error! mem:%d offset:%d/n", pFbdev->fb_mem, pFbdev->fb_mem_offset);
return FALSE;
}
return TRUE;
}
//close frame buffer
int fb_close(PFBDEV pFbdev)
{
close(pFbdev->fb);
pFbdev->fb=-1;
}
//get display depth
int get_display_depth(PFBDEV pFbdev);
{
if(pFbdev->fb<=0)
{
printf("fb device not open, open it first/n");
return FALSE;
}
return pFbdev->fb_var.bits_per_pixel;
}
//full screen clear
void fb_memset (void *addr, int c, size_t len)
{
memset(addr, c, len);
}
//use by test
#define DEBUG
#ifdef DEBUG
main()
{
FBDEV fbdev;
memset(&fbdev, 0, sizeof(FBDEV));
strcpy(fbdev.dev, "/dev/fb0");
if(fb_open(&fbdev)==FALSE)
{
printf("open frame buffer error/n");
return;
}
fb_memset(fbdev.fb_mem + fbdev.fb_mem_offset, 0, fbdev.fb_fix.smem_len);
fb_close(&fbdev);
}