在Linux的LCD驱动中有一个很重要的概念:帧缓冲(framebuffer)。
一、帧缓冲(framebuffer)
帧缓冲(framebuffer)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。帧缓冲设备为标准字符设备,主设备号为29,对应于/dev/fb*设备文件。
二、帧缓冲设备驱动的分析
drivers\video\fbmem.c文件中对应的是framebuffer的代码,先看看它的初始化代码:
static int __init
fbmem_init(void)
{
create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))//注册一个字符设备驱动
printk("unable to get major %d for fb devs\n", FB_MAJOR);
fb_class = class_create(THIS_MODULE, "graphics");//创建一个类,但并没有创建设备
if (IS_ERR(fb_class)) {
printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
fb_class = NULL;
}
return 0;
}
从代码可见,framebuffer注册的是一个字符设备驱动,并提供了一个file_operations结构,再注意后面,它只创建了一个类,并没有创建设备。我们在看看它的file_operations结构:
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
};
它提供了/dev/fb*设备的open,read,write等函数。当我们在应用程序中打开/dev/fb*设备时,系统将调用fb_open这个函数。当我们在应用程序中往read这个设备时,系统将调用fb_read这个函数,下面让我们来看看这两个函数:
fb_open函数:
static int
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
if (fbidx >= FB_MAX)
return -ENODEV;
#ifdef CONFIG_KMOD
if (!(info = registered_fb[fbidx]))
try_to_load(fbidx);
#endif /* CONFIG_KMOD */
if (!(info = registered_fb[fbidx]))//根据次设备号从registered_fb数组中选择一个fb_info结构
return -ENODEV;
if (!try_module_get(info->fbops->owner))
return -ENODEV;
file->private_data = info;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
fb_read函数:
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];//根据次设备号从registered_fb数组中选择一个fb_info结构
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)
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);
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;
}
观察这两个函数发现他们有一个共性:根据次设备号从registered_fb数组中选择一个fb_info结构,然后判断这个结构里是否有对应的函数,有则执行。那么registered_fb数组由谁来设置呢?它是由int register_framebuffer(struct fb_info *fb_info)
这个函数设置的,它通过寻找registered_fb数组中的一个空缺项,使这个空缺项指向我们传递的fb_info结构,并通过这项的下标创建设备节点。这个函数由LCD驱动调用。所以说framebuffer为底层显示设备提供一个接口。
三、LCD驱动
上面说到,framebuffer为底层显示设备提供一个接口,也就是为我们的LCD提供了一个接口,LCD只需要调用int register_framebuffer(struct fb_info *fb_info)
函数向上注册一个fb_info结构,我们就能通过设备节点/dev/fb*中的某个设备节点就能访问我们的设备。在注册之前需要将fb_info结构填充,看看fb_info结构体:
struct fb_info {
int node;/* 次设备号从这里获得的*/
int flags;
struct fb_var_screeninfo var; /* 可变参数,非常重要 */
struct fb_fix_screeninfo 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; /* 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; /* 显存的基地址(虚拟地址),非常重要*/
unsigned long screen_size; /* 显存的大小*/
void *pseudo_palette; /* 16个颜色假的调色板*/
#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; /* 用来存放私有数据*/
};
这个结构中重要的都用文字说明了,其中有三个个结构:fb_var_screeninfo、fb_fix_screeninfo和fb_ops。再来看看它们:
fb_var_screeninfo:这个结构体主要用来设置LCD屏幕的参数,如分辨率、像素比特数等,LCD驱动程序里面硬件相关的设置很多都涉及这个结构体。
struct fb_var_screeninfo {
__u32 xres; /* 分辨率,一行有多少个像素点*/
__u32 yres; /* 分辨率,有多少行*/
__u32 xres_virtual; /* 虚拟分辨率,一行有多少个像素点*/
__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 */
};
fb_fix_screeninfo:
struct fb_fix_screeninfo {
char id[16];/* 驱动名字就保存在这里*/
unsigned long smem_start; /* fb缓冲区的基地址,这是一个物理地址 */
__u32 smem_len; /* fb缓冲区的长度 */
__u32 type; /* FB_TYPE_类型*/
__u32 type_aux;/* Interleave for interleaved Planes */
__u32 visual;/* FB_VISUAL_类型*/
__u16 xpanstep;/* 一般设置为0 */
__u16 ypanstep;/* 一般设置为0 */
__u16 ywrapstep;/* 一般设置为0 */
__u32 line_length;/* 屏幕一行有多个字节 */
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 */
};
fb_ops:考虑到fb_ops结构体里面的函数指针成员太多,这里仅简单列举几个比较常见的,具体的请参考源代码。(在include/linux/fb.h文件里定义)
struct fb_ops{
struct module *owner; /* 模块计数 */
int (*fb_open)(struct fb_info *info, int user); /* 打开函数,第一个参数为fb_info */
int (*fb_release)(struct fb_info *info, int user);
。。。。。。
/* fb_check_var函数用来检查可变参数,并调整修改可变参数 */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
。。。。。。
/*fb_setcolreg函数就是用来设置fb_info里面的pseudo_palette调色板成员 */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, 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);
。。。。。。
/* 有了fb_ioctl应用层可以通过ioctl系统调用来设置屏幕参数等 */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
。。。。。。
/* 有了fb_mmap应用层可以通过mmap系统调用来读写帧缓冲区内存 */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
。。。
};
根据LCD的datasheet配置完fb_info结构之后,我们需要初始化硬件相关的东西,才能向上注册我们的fb_info结构,因为我们一旦注册,就可能会有进程访问我们的设备,如果我们的设备还没有初始化,那么进程的访问注定是失败的。
初始化设备我们需要做的就是配置LCD控制寄存器。在设置控制寄存器时需要映射物理地址。
下面是一个LCD驱动程序的例子,基于JZ2440v2版:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>
static int S3C_lcd_setcolreg(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
struct s3c_lcd_regs{
unsigned long lcdcon1;
unsigned long lcdcon2;
unsigned long lcdcon3;
unsigned long lcdcon4;
unsigned long lcdcon5;
unsigned long lcdsaddr1;
unsigned long lcdsaddr2;
unsigned long lcdsaddr3;
unsigned long redlut;
unsigned long greenlut;
unsigned long bluelut;
unsigned long reserved[8];
unsigned long dithmode;
unsigned long tpal;
unsigned long lcdintpnd;
unsigned long lcdsrcpnd;
unsigned long lcdintmask;
unsigned long tconsel;
};
static struct fb_ops S3C_lcd_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = S3C_lcd_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static struct s3c_lcd_regs *lcd_regs;
static unsigned long pseudo_palette[16];
static struct fb_info *fbinfo;
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
static int S3C_lcd_setcolreg(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info)
{
unsigned int val;
if(regno>=16)
return 1;
/* 用red,green,blue三原色构造出val */
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pseudo_palette[regno] = val;
return 0;
}
static int __init lcd_init(void)
{
/*1、创建一个fd_info结构体*/
fbinfo = framebuffer_alloc(0,NULL);
/*2、设置fd_info结构体*/
/*2.1、设置固定 参数*/
strcpy(fbinfo->fix.id, "mylcd");
//fbinfo->fix.smem_start;/* Start of frame buffer mem (physical address) */
fbinfo->fix.smem_len = 480*272*16/8;
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
fbinfo->fix.line_length = 480*16/8;
/*2.2、设置可变参数*/
fbinfo->var.xres = 480;
fbinfo->var.yres = 272;
fbinfo->var.xres_virtual = 480;
fbinfo->var.yres_virtual = 272;
fbinfo->var.bits_per_pixel = 16;
//RGB:565
fbinfo->var.red.offset = 11;
fbinfo->var.red.length = 5;
fbinfo->var.red.msb_right = 0;
fbinfo->var.green.offset = 5;
fbinfo->var.green.length = 6;
fbinfo->var.green.msb_right = 0;
fbinfo->var.blue.offset = 0;
fbinfo->var.blue.length = 5;
fbinfo->var.blue.msb_right = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
/*2.2、设置其他参数*/
fbinfo->fbops = &S3C_lcd_ops;
fbinfo->pseudo_palette = pseudo_palette;
//fbinfo->screen_base = ; /*显存的虚拟地址*/
fbinfo->screen_size = 480*272*16/8;/*显存的大小*/
/*3、硬件相关的初始化*/
gpbcon = ioremap(0x56000010,8);
gpbdat = gpbcon+1;
gpccon = ioremap(0x56000020,4);
gpdcon = ioremap(0x56000030,4);
gpgcon = ioremap(0x56000060,4);
lcd_regs = ioremap(0X4D000000,sizeof(struct s3c_lcd_regs));
/*设置背光GPB0为输出引脚*/
*gpbcon &= ~(0x03);
*gpbcon |= 0x01;
*gpbdat &= ~1;
*gpccon = 0xaaaaaaaa;
*gpdcon = 0xaaaaaaaa;
/*设置LCD电源引脚GPG4为LCD_PWRDN*/
*gpgcon |= (0x3<<(2*4));
/*设置LCD控制寄存器*/
lcd_regs->lcdcon1 = (4<<8)|(3<<5)|(12<<1);
lcd_regs->lcdcon2 = (1<<24)|(271<<14)|(1<<6)|(9);
lcd_regs->lcdcon3 = (1<<19)|(479<<8)|(1<<0);
lcd_regs->lcdcon4 = 40;
lcd_regs->lcdcon5 = 0x00000b01;
fbinfo->screen_base = dma_alloc_writecombine(NULL,fbinfo->fix.smem_len, &fbinfo->fix.smem_start,GFP_KERNEL);
lcd_regs->lcdsaddr1 = (fbinfo->fix.smem_start>>1) & ~(0x3<<30);
lcd_regs->lcdsaddr2 = ((fbinfo->fix.smem_start+fbinfo->fix.smem_len)>>1)&0x1fffff;
lcd_regs->lcdsaddr3 = (480*16/16);
/*使能LCD*/
lcd_regs->lcdcon1 |= 1;
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */
/*2、注册fd_info结构体*/
register_framebuffer(fbinfo);
return 0;
}
static void __exit lcd_exit(void)
{
unregister_framebuffer(fbinfo);
lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
*gpbdat &= ~1; /* 关闭背光 */
dma_free_writecombine(NULL,fbinfo->fix.smem_len,fbinfo->screen_base,fbinfo->fix.smem_start);
iounmap(lcd_regs);
iounmap(gpbcon);
iounmap(gpccon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(fbinfo);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");